3

I'm working on designing a genealogy rails application, and I'm having some trouble deciding how to design my models. Specifically, I'm working on things that will show up in a timeline. There will be many "things" that show up in the timeline. I've decided I have two types of "things": Event - A single date, and Period - a start and end date. Here's a few examples of "things" and which type the fall under:

  • Birth [Event]
  • Death [Event]
  • Marriage [Event]
  • Residence [Period]
  • Burial [Event]
  • Cremation [Event]
  • Occupation [Period]
  • Graduation [Event]
  • Family Reunion [Event]
  • Divorce [Event]
  • Adoption [Event]

Originally, I was planning on having Event and Period be models, in addition to a TimelineObject model, and then have each of the things in the list above be their own model.

So, the inheritance would be TimelineObject < ActiveRecord::Base, Event < TimelineObject, Period < TimelineObject, and (eg) Birth < Event and (eg) Residence < Period

That way in rails I could do TimelineObject.all (or some filter for an individual person) and get all the objects for a timeline.

However, I ran into a problem with how to implement this in rails. I looked into Single Table Inheritance (STI) but that doesn't seem to fit quite right. There will be a few attributes that all of these things share, but many more that they will not, so that would cause many null values in the database. I also thought about maybe using modules somehow for this, but wasn't really sure how.

How would you recommend implementing these "things". Essentially, they're a group of models.

Also, just to be clear, I only need models for these "things". They'll all be associated with a Person model, and they'll be accessed that way. So I don't intend to have controllers or views for any of them.

dkniffin
  • 1,295
  • 16
  • 25
  • Have you considered scoping them as `events` and `periods` and adding a column for "type_of_thing" then you could call `TimelineObject.events` OR `TimelineObject.periods` Or better yet using a `by_person` scope `TimelineObject.by_person(person).events` – engineersmnky Apr 29 '14 at 20:17

1 Answers1

3

How to create STI relationships in Rails

Lets say we have a model Computer

class Computer < ActiveRecord:Base
  # in app/models
  # Fields:
  #   String name
  #   String owner
  #   String manafacturer
  #   String color

  def default_browser
    "unknown!"
  end 
end

Now, we want to differentiate between Macs and PCs. It doesn’t really make sense to make a different table for each, since they both have pretty much the same columns. Instead we can create a new column, type, which tells Rails to use STI on Computer. Lets look at what the models might look like.

class Computer < ActiveRecord:Base
  # in app/models
  # Fields:
  #   String name
  #   String owner
  #   String manafacturer
  #   String color
  #   String type

  def default_browser
    "unknown!"
  end 
end

class Mac < Computer
  # in app/models
  # this is for Computers with type="Mac"
  before_save :set_color

  # Lets say all macs are silver, no point setting these ourselves
  def set_color
    self.color = "silver"
    self.manafacturer = "apple"
  end

  # Lets overwrite the default_browser method
  def default_browser
    "safari"
  end
end

class PC < Computer
  # in app/models  

  # Lets overwrite the default_browser method
  def default_browser
    "ie =("
  end
end

Anytime Rails opens up the computer object, it looks for the subclass corresponding to type. For instance, type="CoolComputer" corresponds to model CoolComputer < Computer.

How to use STI Models

To create a new mac, you can do:

m = Mac.new
m.name = "kunal's mac"
m.owner = "kunal"
m.save
m # => #<Mac id: 1, name: "kunal's mac", owner: "kunal", manafacturer: "apple", color: "silver", type: "Mac", ...>

Whats even cooler is ActiveRecord queries. Lets say we want all the computers

Computer.all # => [#<Mac id: 1, name: "kunal's mac", owner: "kunal", manafacturer: "apple", color: "silver", type: "Mac", ...>, #<Mac id: 2, name: "anuj's mac", owner: "anuj", manafacturer: "apple", color: "silver", type: "Mac", ...>, #<PC id: 3, name: "bob's pc", owner: "bob", manafacturer: "toshiba", color: "blue", type: "PC", ...>]

Yup, it automatically gives you the correct objects! You can find out the type of a particular object by calling .type, is_a? or .class

Computer.first.type == Mac # true
Computer.first.is_a? Mac # true
Computer.first.class == Mac # true

If we only want Macs, we can do

Mac.all

Custom Inheritance Column

If you want to use another column instead of type to use for STI, you can simply add this to the top of your model:

set_inheritance_column 'whatever_you want'

Note: If you have a database column named type, you can turn off Single Table Inheritance by changing the inheritance column to something other than type.

Organizing This in Rails

After using STI, I ended up with a bloated models folder because all of the many custom sub models I created were in the models folder. To solve this, I created a folder in models to store all of my computer specific models

* app
*   models
*     computer.rb
*     computers
*       pc.rb
*       mac.rb

Rails doesn’t automatically open subfolders in the models folder, so I added in config/application.rb:

# Load Subfolder Models
config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**}')]
jimagic
  • 4,045
  • 2
  • 28
  • 49
  • Jack, how would this affect the app from the 'views'/forms standpoint? i.e, If you use STI, don't you have to have two 'new.html.erb' pages...one for creating Mac & one for creating a PC? Or can you combine into one form with a dropdown selector to select mac or pc? (sorry, I don't mean to detract form Oddity's question...but thought your answer might be useful in deciding whether he goes w/ STI as well)...thx, – BB500 Apr 29 '14 at 20:31
  • 3
    Thanks for the copy-paste from here: http://blog.thirst.co/post/14885390861/rails-single-table-inheritance This still does not convince me if STI is a good way to go with this. Like I said in the question, there will be some common attributes, but many of them will not be common. That seems like it would create a lot of wasted column space in the table, would it not? – dkniffin Apr 29 '14 at 21:32
  • I did similar to your in my recent project. I made parent model as person and inherited model are 'suspects' and 'victims'. In your case, you have to make one parent model such as 'Event' and 'Period' and all these model as inherited.You also have to use nested_attributes_for on parent model. it saved me from creating 2 different table 'suspect' and 'victims' since both are using same attributes. i hope this helped you in deciding for STI – jimagic Apr 29 '14 at 21:36