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