2

Say I have the following example of associations in a Rails app:

enter image description here

I'm considering combining the *Posting models under STI. One problem with STI is the potential for many attributes that are only related to one subclass (i.e., a lot of denormalized nil values). This is especially worrisome when your subclasses and going to evolve and grow in the future. I've read a few related posts (such as this), however, as you can see in my example, the potential subclass-specific fields will not necessarily be just attributes, but rather, a lot of belongs_to associations.

My question is, how could I restructure this to use STI from a Posting model for all the common attributes/methods (of which there will be quite a few in my actual app), but keep the unique subclass-specific attributes and belongs_to associations from piling up in the Posting model? Also, the ability to access @board.postings and work with those standard methods is important.

For example, I've thought about moving the type-specific attributes to another model:

class CarPosting < Posting
  has_one: car_posting_detail
end

class CarPostingDetail < ActiveRecord::Base
  belongs_to :car_posting
  belongs_to :car_make
  belongs_to :car_model
end

Although, this starts to create a lot of joins, I'm not sure I have the has_one/belongs_to declarations in the right direction, and you have to start chaining calls (e.g., @posting.car_posting_detail.car_make).

Are there other design patterns you have seen for accomplishing this?

Community
  • 1
  • 1
robertwbradford
  • 6,181
  • 9
  • 36
  • 61
  • Do car posting, job posting etc. really have that much in common with each other to warrant STI? I would expect them to share some relationships(perhaps the user that posted them) but very few attributes on the table itself. Another question to ask yourself is are you ever going to want to query both the car posting and the event/whatever posting at once? Again, I would expect them to be pretty separate use cases. – Josh Mar 04 '14 at 02:16
  • Good point. I was also considering a Concern, but I forgot to mention that the ability to interact with "@board.postings" is pretty important and there would be quite a few common attributes/methods that aren't so apparent through this example. (This board/postings example is only a representation of another app I'm working on.) – robertwbradford Mar 04 '14 at 02:22
  • Have you thought about using polymorphic associations? – lsaffie Mar 04 '14 at 03:45
  • @lsaffie, I started to think about these, but maybe I'm too hung up on the commentable/taggable pattern to see how it could work here. `:board has_many :postings`, `:job_posting belongs_to :board`, and `:car_posting belongs_to :board`, etc. Because the `*_posting` models declare the `belongs_to` associations, wouldn't the `_type` and `_id` attributes be in their tables? – robertwbradford Mar 04 '14 at 18:52
  • I think it would look like: Post model belongs_to :postable, :polymorphic => true.. Then car and all the other "postalbe" classes would has_many :posts, as: :postable. Post would hold the postable_id and postable_type – lsaffie Mar 04 '14 at 19:02
  • @lsaffie, I think this will work for my scenario. I didn't think to include the Post table in between when you mentioned polymorphic associations. I'd do something like `class Posting < ActiveRecord::Base; belongs_to :board; belongs_to :posting_detail, polymorphic: true; end;` and `class CarPosting < ActiveRecord::Base; has_one :posting, as: :posting_detail; end;` Thank you. If you write up answer I can accept it. – robertwbradford Mar 04 '14 at 19:26

2 Answers2

1

You basically have to 2 options for accomplishing inheritance.

First, you can use rails STI as you suggested. The downside is that end up with nil attribute for the child classes that do not use all of the fields. Your idea to reduce this by adding type-specific attributes to another model is a great way to reduce this. However, you should keep the implementation as DRY as possible by defining a has_one :detail for the Posting. Then you can simply assign specific detail types in the Posting childs. For example, CarPosting's detail would be CarPostingDetail. This is convenient because then all Posting children will have their details accessed identically, but will still have different details. So the query now looks like @posting.detail.car_make. To take this one step further, you can define a custom helper method in your Posting model to grab each attribute in the current Posting's detail and create an accessor for it. Now the entire detail layer is transparent and you can simply access those attributes by saying @posting.car_make.

Second, you can use an abstract class. This is essentially the reverse of STI. You create an abstract model class which can never be instantiated. Thus, you cannot define any relationships in the Posting class because it has no table. Each child of the abstract Posting class has its own separate table. The main advantage of doing this would be the ability to define methods for all of your Posting types without copy and pasting them into every model. So this options is better if there are some overlapping functionality across the models, but very little data overlap.

Mike S
  • 11,329
  • 6
  • 41
  • 76
  • Isn't an abstract class essentially a concern then? – Josh Mar 04 '14 at 03:45
  • I've thought about the abstract class (or concern) as Josh mentioned. However, wouldn't this get rid of my `@board.postings` functionality which I need? (:board has_many :postings) – robertwbradford Mar 04 '14 at 03:49
  • Yes Josh, essentially a concern would accomplish the same thing as an abstract class. I suppose the only difference is that from an object oriented perspective the abstract class allows your models to be a certain type, instead of extending the functionality of the model. I think both an abstract class and a concern would get rid of the Board <=> Posting relationship. Unless there is some clever way to manipulate polymorphic associations so that each subclass of the Posting has a relationship to the Board. – Mike S Mar 04 '14 at 04:00
0

You could use polymorphic associations for this.

Post model belongs_to :postable, :polymorphic => true

car, event and all the other "postable" classes would have this relationship

has_many :posts, as: :postable 

Post would hold the postable_id and postable_type

More info here http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

lsaffie
  • 1,764
  • 1
  • 17
  • 22
  • Cool. (See our conversation in the OP comments for more discussion and how I would implement it.) I forgot that the polymorphic association can be used with a has_one relationship. – robertwbradford Mar 04 '14 at 19:37