nish
nish

Reputation: 7280

Rails: unknown attribute error for derived class

I have a model named PaypalPayment:

class PaypalPayment < PaymentMethod
  belongs_to :order
  def provider_class
    PaypalPayment
  end

  def process!
  end   
end

I generated the following migrations for it:

class CreatePaypalPayments < ActiveRecord::Migration
  def change
    create_table :paypal_payments do |t|
      t.integer :order_id
      t.integer :payment_id

      t.timestamps
    end
  end
end

and

class AddDetailsToPaypalPayment < ActiveRecord::Migration
  def change
    add_column :paypal_payments, :state, :string
    add_column :paypal_payments, :amount, :decimal
    add_column :paypal_payments, :cc, :string
    add_column :paypal_payments, :cm, :string
  end
end

After the migration the table looks something like:

development_database=# select * from paypal_payments;
 id | order_id | payment_id | created_at | updated_at | state | amount | cc | cm 

But when I try to initialize an object of this model, I'm getting the unknown attribute: payment_id.

@paypal_payment = PaypalPayment.new(:payment_id => params[:tx], :state => params[:st], :cc => params[:cc], :cm => params[:cm], :order_id => params[:id])

EDIT: db/schema.rb:

create_table "paypal_payments", :force => true do |t| 
    t.integer "order_id" 
    t.integer "payment_id" 
    t.datetime "created_at" 
    t.datetime "updated_at" 
    t.string "state" 
    t.decimal "amount" 
    t.string "cc" 
    t.string "cm" 
end

Upvotes: 4

Views: 1946

Answers (4)

Spectator6
Spectator6

Reputation: 403

I know I'm a bit late the show here, but if anyone is encountering a similar problem with Rails 5.1, in my case I was able to resolve the issue by including the following line in my parent classes

self.abstract_class = true

Upvotes: 0

nathanvda
nathanvda

Reputation: 50057

There are different ways to model inheritance in a relational database, Martin Fowler lists the following options:

  1. Single Table Inheritance : all classes are stored in a single table
  2. Class Table Inheritance : all classes have their own table
  3. Concrete Table Inheritance : only concrete classes have a table (e.g. in your example PaymentMethod if being abstract, would not have a table)

Now ActiveRecord only supports STI: single table inheritance.

So if you write

class PaypalPayment < PaymentMethod

ActiveRecord will assume STI and look for a type column, and furthermore, will only look for payment_methods table.

Depending on what you want, in most cases, STI is just perfect. Sometimes I prefer the Class and Concrete Table Inheritance better, but especially for associations this needs a little more householding, since:

  • e.g. you have different payment-methods, but they are stored in different tables
  • do you want to access all payment methods at once, you need the "abstract class"
  • you need an association per possible payment-method
  • if you have the "abstract class", how do you link to the "real payment method". One way is to include table-name and id of the child as the link.

There are lots of way to solve this, but always harder than using a single table. Also this is stretching the relational datamodel, as depending on the chosen solution, foreign key constraints are not automatically supported. I could go into detail, but I am not sure if this is relevant, as your example seems a classic case for STI.

If you do want to use Class Table Inheritance or Concrete Table Inheritance, each class has to derive from `ActiveRecord::Base`` and you should include a module (or concern) with the shared behaviour if needed (since ruby does not support multiple inheritance).

Upvotes: 3

TheJKFever
TheJKFever

Reputation: 705

I believe you have to add the column "type" to your PaymentMethods table. This will allow it to be inheritable. Without the type column, when you instantiate a PaypalPayment, it thinks it's a PaymentMethod and hence has none of the unique fields of PaypalPayment. However when you add the column "type" to PaymentMethod, then it will store "PaypalPayment" and ActiveRecord knows to make the PaypalPayment methods available. You should probably make a model for PaymentMethod also and make sure it inherits ActiveRecord::Base

def change
  add_column :payment_methods, :type, :string
end

Here's some info: http://www.archonsystems.com/devblog/2011/12/20/rails-single-table-inheritance-with-polymorphic-association/

Upvotes: 1

Richard Peck
Richard Peck

Reputation: 76774

I'd do this:

Check your Rails Console --

$ rails c
$ payment = PaypalPayment.find(1)
$ payment.column_names #-> should reveal which columns Rails comes back with

Check Rails is picking up the attribute

For testing's sake, just try attr_accessor :payment_id to see if that works. You might not have permitted the attribute in your model

In Rails4, that means using strong params, but in Rails 3, I think it means using attr_accessible like this:

#app/models/paypal_payment.rb
Class PaypalPayment < ActiveRecord::Base
    attr_accessible :payment_id #-> tests parameter passing
    attr_accessor :payment_id #-> tests virtual attribute assignment
end

Upvotes: 0

Related Questions