2

I'm using single table inheritance successfully like so:

class Transaction < ActiveRecord::Base
    belongs_to :order
end

class Purchase < Transaction
end

class Refund < Transaction
end

The abbreviated/simplified PurchaseController looks like this:

class PurchaseController < TransactionController
  def new
    @transaction = @order.purchases.new(type: type)
  end

  def create
    @transaction = @order.purchases.new secure_params
    if @transaction.save
      redirect_to @order
    else
      render :new
    end
  end
end

The abbreviated/simplified Purchase model looks like this:

class Purchase < Transaction
  attr_accessor :cc_number, :cc_expiry, :cc_csv
end

What I'm trying to do is have different variations of a purchase, for instance a cash purchase & a cheque purchase. The issue is I'm not sure how to call the model for that variation.

For example:

class Cash < Purchase
  attr_accessor :receipt_no
end

class CashController < TransactionController
  def new
    # This will use the Purchase.rb model so that's no good because I need the Cash.rb model attributes
    @transaction = @order.purchases.new(type: type)

    # This gives me the following error:
    # ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: Purchase is not a subclass of Cash
    @transaction = Cash.new(type: 'Purchase', order: @order.id)
  end
end
EasyCo
  • 2,036
  • 20
  • 39

2 Answers2

1

Class

Maybe I'm being obtuse, but perhaps you'll be willing to not make the Purchase type an inherited class?

The problem I see is that you're calling Cash.new, when really you may be better to include all the functionality you require in the Purchase model, which will then be able to be re-factored afterwards.

Specifically, why don't you just include your own type attribute in your Purchase model, which you'll then be able to use with the following setup:

#app/controllers/cash_controller.rb
class CashController < ApplicationController
   def new
      @transaction = Purchase.new 
   end

   def create
      @transaction = Purchase.new transaction_params
      @transaction.type ||= "cash"
   end

   private

   def cash_params
      params.require(:transaction).permit(:x, :y, :z)
   end
end

The only downside to this would be that if you wanted to include different business logic for each type of purchase, you'll still want to use your inherited model. However, you could simply split the functionality in the before_create callback:

#app/models/puchase.rb
class Purchase < Transaction
   before_create :set_type

   private

   def set_type
      if type =="cash"
        # do something here
      else
        # do something here
      end
   end
end

As such, right now, I think your use of two separate models (Cash and Cheque) will likely be causing much more of an issue than is present. Although I'd love to see how you could inherit from an inherited Model, what I've provided is something you also may wish to look into

Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • Hey @richpeck, the reason I have them split into their own models is because they have different attributes and validations and I'd end up with too many conditional validations and unused attributes if I kept it all in one model. However, the actual data that gets persisted to the database is the same independent of purchase method. Also, the `type` attribute is reserved for STI for my two types of transactions(`Purchase` & `Refund`) so I'd have to use a different attribute name which is fine but things start getting messy. – EasyCo Sep 17 '14 at 23:27
1

I'm not sure why it doesn't work for you, this works fine for me:

@order.purchases.new(type: "Cash") # returns a new Cash instance

You can also push a new Cash on to the association if you are ready to save it:

@order.purchases << Cash.new

Or you can define a separate association in Order:

class Order < ActiveRecord::Base
  has_many :cashes
end

@order.cashes.new # returns a new Cash instance
zetetic
  • 47,184
  • 10
  • 111
  • 119
  • In the end I just created a bunch of different namespaced types. I.e: `type: Purchase::Online` & `type: Purchase::Cash`... – EasyCo Nov 19 '14 at 20:24