Modelado de herencia con ORM de Ruby / Rails

I'm trying to model this inheritance for a simple blog system

Blog has many Entries, but they may be different in their nature. I don't want to model the Blog table, my concern is about the entries:

  • simplest entry is an Article que tiene title y text
  • Quote, however, does not have a title and has short text
  • Media tiene un url y una comment...
  • etc ...

What is a proper way to model this with Ruby on Rails? That is

  • Should I use ActiverRecord for this or switch to DataMapper?
  • I would like to avoid the "one big table" approach with lots of empty cells

When I split the data into Entry + PostData, QuoteData etc can I have belongs_to :entry in these Datas without having has_one ??? en el objeto Entry class? That would be standard way to do it in sql and entry.post_data may be resolved by the entry_id en el objeto postdata mesa.

EDIT: I don't want to model the Blog table, I can do that, my concern is about the entries and how would the inheritance be mapped to the table(s).

preguntado el 27 de agosto de 11 a las 17:08

2 Respuestas

I've come across this data problem several times and have tried a few different strategies. I think the one I'm a biggest fan of, is the STI approach as mentioned by cicloon. Make sure you have a type column on your entry table.

class Blog < ActiveRecord::Base
  # this is your generic association that would return all types of entries
  has_many :entries

  # you can also add other associations specific to each type.
  # through STI, rails is aware that a media_entry is in fact an Entry
  # and will do most of the work for you.  These will automatically do what cicloon.
  # did manually via his methods.
  has_many :articles
  has_many :quotes
  has_many :media
end

class Entry < ActiveRecord::Base
end

class Article < Entry 
  has_one :article_data
end

class Quote < Entry
  has_one :quote_data
end

class Media < Entry
  has_one :media_data
end

class ArticleData < ActiveRecord::Base
  belongs_to :article # smart enough to know this is actually an entry
end

class QuoteData < ActiveRecord::Base
  belongs_to :quote
end

class MediaData < ActiveRecord::Base
  belongs_to :media
end

The thing I like about this approach, is you can keep the generic Entry data in the entry model. Abstract out any of the sub-entry type data into their own data tables, and have a has_one association to them, resulting in no extra columns on your entries table. It also works very well for when you're doing your views:

app/views/articles/_article.html.erb
app/views/quotes/_quote.html.erb
app/views/media/_media.html.erb # may be medium here....

and from your views you can do either:

<%= render @blog.entries %> <!-- this will automatically render the appropriate view partial -->

or have more control:

<%= render @blog.quotes %>
<%= render @blog.articles %>

You can find a pretty generic way of generating forms as well, I usually render the generic entry fields in an entries/_form.html.erb partial. Inside that partial, I also have a

<%= form_for @entry do |f| %>
  <%= render :partial => "#{f.object.class.name.tableize}/#{f.object.class.name.underscore}_form", :object => f %>
<% end %> 

type render for the sub form data. The sub forms in turn can use accepts_nested_attributes_for + fields_for to get the data passed through properly.

The only pain I have with this approach, is how to handle the controllers and route helpers. Since each entry is of its own type, you'll either have to create custom controllers / routes for each type (you may want this...) or make a generic one. If you take the generic approach, two things to remember.

1) You can't set a :type field through update attributes, your controller will have to instantiate the appropriate Article.new to save it (you may use a factory here).

2) You'll have to use the becomes() método (@article.becomes(Entry)) to work with the entry as an Entry and not a subclass.

Espero que esto ayude.

Warning, I've actually used Media as a model name in the past. In my case it resulted in a table called medias in rails 2.3.x however in rails 3, it wanted my model to be named Medium and my table media. You may have to add a custom Inflection on this naming, though I'm not sure.

Respondido 27 ago 11, 23:08

Thanks for a great answer. One question though: do I have to specify has_many :articles and has_many :quotes in the Blog? Why not just has_many :entries ? I don't expect listing entries of one particular type that much. - tillda

Thanks for the edits mark, I was running out of time :). No tilda, you can simply use the entries association if that's all you'd like. You can just render the list of entries and rails will render the appropriate partial if you do it in the manner described above. - Policía de Kristian

You can handle this easily using ActiveRecord STI. It requires you to have a type field in your Entries table. This way you can define your models like this:

def Blog > ActiveRecord::Base
  has_many :entries

  def articles
    entries.where('Type =', 'Article')
  end

  def quotes
    entries.where('Type =', 'Quote')
  end

  def medias
    entries.where('Type =', 'Media')
  end

end

def Entry > ActiveRecord::Base
  belongs_to :blog
end

def Article > Entry
end

def Quote > Entry
end

def Media > Entry
end

Respondido 27 ago 11, 22:08

Thanks, but... where would all the una experiencia diferente entry types be stored? Isn't it the mentioned "one big table" (entries) approach ? - tillda

Hmm yes sorry, I didn't read you very good, Kristian PD approach is what you are looking for. - cicloon

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.