4

I am trying to create several models that all pull from the same table. How do I limit the table records in each model? And before you tell me to change my data structure, this is a reporting application that is pulling from a preexisting backing DB over which I have no control.

My table looks something like:

Vehicle_Table
id vehicle_type name
--------------------
1  Car          Foo
2  Car          Bar
3  Motorcycle   Baz
4  Car          Barf

And I want to build models for Car and Motorcycle like:

class Car < ActiveRecord::Base
  set_table_name 'Vehicle_Table'

end

and

class Motorcycle < ActiveRecord::Base
  set_table_name 'Vehicle_Table'

end

But I have no idea how to say, "Hey Active Record, I only want records where vehicle_type = motorcycle in the motorcycle model."

I'm sure this is friggin' obvious, but all of my Google searches return ways to FIND subsets in a model rather than RESTRICT a model to a specific subset of records.

Bruce P. Henry
  • 412
  • 1
  • 4
  • 14

2 Answers2

5

This is called Single Table Inheritance (STI).

If you had a column named type in your table, it would likely work automatically. But you can change this column name that Rails uses to tell types apart.

http://api.rubyonrails.org/classes/ActiveRecord/Base.html

Single table inheritance

Active Record allows inheritance by storing the name of the class in a column that by default is named “type” (can be changed by overwriting Base.inheritance_column). This means that an inheritance looking like this:

class Company < ActiveRecord::Base; end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Client; end

When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = “Firm”. You can then fetch this row again using Company.where(:name => '37signals').first and it will return a Firm object.

So, try this code

class Car < ActiveRecord::Base
  set_table_name 'Vehicle_Table'
  self.inheritance_column = :vehicle_type
end
Community
  • 1
  • 1
Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
  • Okay, that makes sense. And now of course I run into the problem that the "type" data is normalized and stored in another table. So rather than all of the "car" records having vehicle_type = "car" what I have is vehicle_type = 1 and then a table of vehicleTypes. – Bruce P. Henry Feb 17 '12 at 00:36
  • @BruceP.Henry: yeah, that complicates things. Can you denormalize? – Sergio Tulentsev Feb 17 '12 at 07:47
  • I really cannot. I'm dealing with another application's schema for the data (JIRA actually) and they have done some things that break all kinds of Rails conventions (JIRA appears to be a java application). – Bruce P. Henry Feb 17 '12 at 14:53
  • Well, if I were you, I'd use some [named scopes](http://archives.ryandaigle.com/articles/2008/8/20/named-scope-it-s-not-just-for-conditions-ya-know). – Sergio Tulentsev Feb 17 '12 at 15:14
  • HEY! That did get me unblocked though. Once I was sure STI was how to go at this I was able to create a view in mySQL without disrupting the schema for the other app. Now I have a denormalized table from which to pull. In fact, this makes me think that a good pattern for doing this kind of "attaching Rails to another program's DB" is to create views with view names and column names that Rails expects by convention. That way all your models suddenly become much easier. Thanks for your help! – Bruce P. Henry Feb 17 '12 at 15:14
  • Just in case someone else comes across this, you can do 'def self.sti_name 1 end' to have the sublasses key off of the integer values instead of string values. – BaroldGene Jul 23 '15 at 03:55
3

Commented above but had limited editing abilities. I came across this exact problem and found the second half of the solution elsewhere. STI will allow you to get a subset of a table based on a column in the table but it will key off of the class name to find the records for that class. For example:

class Company < ActiveRecord::Base; end    
class Client < Company; end

This will look at the table named Company for records that have the value 'Client' in a column named 'Type'.

You can override the column that STI checks by doing

class Company < ActiveRecord::Base
  self.inheritance_column = :company_type
end

But it still looks for that column to contain 'Client'. You can override what value it looks for by doing this:

class Client < Company
  def self.sti_name
    1
  end
end

This will now look at the company_type column for rows with a value of 1.

For Rails-4.2 this is nearly identical but does not need a class method:

  private

  self.inheritance_column = :company_type

  def sti_name
    1
  end
James B. Byrne
  • 1,048
  • 12
  • 27
BaroldGene
  • 220
  • 2
  • 8