Tricky Inheritence

I have created few models which are shown below.
Base models are TransactionType and TransactionItem
ExpenseType and IncomeType derives from TransactionType.
Expense and Income derives from TransactionItem.

class TransactionType < ActiveRecord::Base
  scope :expense_types, -> { where(tran_type: 'ExpenseType') }
  scope :income_types, -> { where(tran_type: 'IncomeType') }
  self.inheritance_column = "tran_type"
  validates :name, uniqueness: true
end

class ExpenseType < TransactionType
end

class IncomeType < TransactionType
end

class TransactionItem < ActiveRecord::Base
validates :note, length: { in: 2..255 }
end

class Expense < TransactionItem
belongs_to :expense_type
validates :expense_type, presence: true
end

class Income < TransactionItem
belongs_to :income_type
validates :income_type, presence: true
end

I can create objects for ExpenseType.
But, it throws error when Expense or Income object created.

<code>
ExpenseType.new(:name => "Grocceries").save!
ExpenseType.new(:name => "Travel").save!
IncomeType.new(:name => "Salary").save!
IncomeType.new(:name => "Bonus").save!
Expense.new(:note => "a soda", :expense_type => ExpenseType.first)

2.1.2 :006 > Expense.new(:note => "a soda", :expense_type =>
ExpenseType.first)
  ExpenseType Load (0.1ms) SELECT "transaction_types".* FROM
"transaction_types" WHERE "transaction_types"."tran_type" IN
('ExpenseType') ORDER BY "transaction_types"."id" ASC LIMIT 1
ActiveModel::MissingAttributeError: can't write unknown attribute
`expense_type_id'
from

It would be great if someone shares your idea for solving this.

Thanks,
Masc

I don’t think this is an inheritance problem. What does the schema for your transaction_items table look like? The error message suggests that it doesn’t have a column expense_type_id, which is what belongs_to :expense_type is going to be looking for.

A wild guess: maybe you’ve got a transaction_type_id column instead? In that case, you should use this on Expense:

belongs_to :expense_type, foreign_key: :transaction_type_id

(and similar for :income_type on Income)

–Matt Jones

Hi Matt,

Schema looks like this.

.schema transaction_items

CREATE TABLE "transaction_items" ("id" INTEGER PRIMARY KEY AUTOINCREMENT
NOT NULL, "transaction_type_id" integer, "note" varchar(255),
"transaction_date" datetime, "created_at" datetime, "updated_at"
datetime);

.schema transaction_types

CREATE TABLE "transaction_types" ("id" INTEGER PRIMARY KEY AUTOINCREMENT
NOT NULL, "name" varchar(255), "tran_type" varchar(255), "created_at"
datetime, "updated_at" datetime);

There is no specific table for expense_type and income_type.
transaction_types table's tran_type column decides whether it is a
income / expense type.

My idea is to write common code for IncomeType and ExpenseType in it's
base class transaction_type which extends ActiveRecord::Base.

Thanks.

I have uploaded the source code of model and migration at below link.

https://drive.google.com/file/d/0B6uWxuw822j9c1Z1X2F2eGxCclU/edit?usp=sharing

Anyone can create simple rails application, copy model & migration files
on place.
Run rails console and execute below commands to reproduce error.

ExpenseType.new(:name => "Grocceries").save!
ExpenseType.new(:name => "Travel").save!
IncomeType.new(:name => "Salary").save!
IncomeType.new(:name => "Bonus").save!
Expense.new(:note => "a soda", :expense_type => ExpenseType.first)

Couple things:

  • the code you posted has a single transaction_items table, but two classes (Income and Expense) that both descend from ActiveRecord::Base. That definitely won’t work. If you really want a common transaction_items table, you’ll need to include a column to put the STI type in and a base class of TransactionItem rather than a module.

  • as noted previously, if you have a column on transaction_items called transaction_type_id but want to refer to the corresponding association as expense_type or income_type, you’ll need to pass the foreign_key option to belongs_to.

–Matt Jones

Hi Matt,

Thanks for your input. Actually I made it working without using STI. Not
sure this is the right way of doing it. But, this is what I wanted.

class TransactionItem < ActiveRecord::Base
  validates :note, length: { in: 2..255 }
  validates :transaction_date, presence: true
end

class Expense < TransactionItem
  belongs_to :expense_type, :class_name => "TransactionType",
:foreign_key => "transaction_type_id"
  validates :expense_type, presence: true
end

class Income < TransactionItem
  belongs_to :income_type, :class_name => "TransactionType",
:foreign_key => "transaction_type_id"
  validates :income_type, presence: true
end

Let me know your thoughts.

Thanks.

Not sure what you mean by “without using STI”. Do you have a type column on the transaction_items table?

–Matt Jones