views:

1365

answers:

3

I have been struggling for the past few hours thinking about which route I should go. I have a Notification model. Up until now I have used a notification_type column to manage the types but I think it will be better to create separate classes for the types of notifications as they behave differently.

Right now, there are 3 ways notifications can get sent out: SMS, Twitter, Email

Each notification would have:

id
subject
message
valediction
sent_people_count
deliver_by
geotarget
event_id
list_id
processed_at
deleted_at
created_at
updated_at

Seems like STI is a good candidate right? Of course Twitter/SMS won't have a subject and Twitter won't have a sent_people_count, valediction. I would say in this case they share most of their fields. However what if I add a "reply_to" field for twitter and a boolean for DM?

My point here is that right now STI makes sense but is this a case where I may be kicking myself in the future for not just starting with MTI?

To further complicate things, I want a Newsletter model which is sort of a notification but the difference is that it won't use event_id or deliver_by.

I could see all subclasses of notification using about 2/3 of the notification base class fields. Is STI a no-brainer, or should I use MTI?

Thanks!

+5  A: 

Have you considered the mixed model approach?

Where you use single table inheritance for your core notification fields. Then offload all the unique items to specific tables/models in a belongs to/has one relationship with your notification sublcasses.

It's a little more overhead to set up, but works out to be pretty DRY, once all the classes and tables are defined. Seems like a pretty efficient way to store things. With eager loading you shouldn't be causing too much additional strain on the database.

For the purposes of this example, lets assume that Emails have no unique details. Here's how it maps out.

class Notification < ActiveRecord::Base
  # common methods/validations/associations
  ...

  def self.relate_to_details
    class_eval <<-EOF
      has_one :details, :class_name => "#{self.class.name}Detail"
      accepts_nested_attributes_for :details
      default_scope :include => :details
    EOF
  end
end

class SMS < Notification
  relate_to_details

  # sms specific methods
  ...
end

class Twitter < Notification
  relate_to_details

  # twitter specific methods
  ...
end

class Email < Notification

  # email specific methods
  ...
end

class SMSDetail < ActiveRecord::Base
  belongs_to :SMS, :class_name => "SMS"           

  # sms specific validations
  ...
end

class TwiterDetail < ActiveRecord::Base
  belongs_to :twitter

  # twitter specific validations
  ...
end

Each of the detail tables will contain a notification ID and only columns that form of communication needs that isn't included in the notifications table. Although it would mean an extra method call to get media specific information.

This is great to know but do you think it's necessary?

Very few things are necessary in terms of design. As CPU and storage space drop in cost so do those necessary design concepts. I proposed this scheme because it provides the best of both STI and MTI, and removes a few of their weaknesses.

As far as advantages go:

This scheme provides the consistency of STI. With tables that do not need to be recreated. The linked table gets around dozens of columns in that are empty in 75% of your rows. You also get the easy subclass creation. Where you only need to create a matching Details table if your new type isn't completely covered by the basic notification fields. It also keeps iterating over all Notifications simple.

From MTI, you get the storage savings and the ease of customization in meeting a class's needs without needing to redefine the same columns for each new notification type. Only the unique ones.

However this scheme also carries over the major flaw with STI. The table is going to replace 4. Which can start causing slowdown once it gets huge.

The short answer is, no this approach is not necessary. I see it as the most DRY way to handle the problem efficiently. In the very short run STI is the way to do it. In the very long run MTI is the way to go, but we're talking about the point where you hit millions of notifications. This approach is some nice middle ground that is easily extensible.

EmFi
This is great to know but do you think it's necessary?
Tony
I've added my answer to this question to my answer as there's not enough room in a comment box. The short version is: No. But that doesn't mean it's a flawed solution.
EmFi
Thanks for the great explanation. I haven't found any articles online about implementing mixed model. Do I just need to create a notifications table and then a details table for each type of detail? I wish I could vote you up more for the excellent response. And one last quick question. You say in the very long run MTI is the way to go...well then why not just set that up to begin with? Do you say this because the extra details call will eventually lead to bad performance? I guess speed/DRY tradeoff.
Tony
Details tables are only necessary for those subclasses that need unique information not covered in the Notification table. MTI is the way to go in the long run, only because of the sheer number of rows that will be in your Notification table, not the details. I'm not sure where that threshold is. But I expect it to be in the region of millions of rows.
EmFi
I think Mike H might be right about sticking with STI. There is a main page on my app where users will be able to look at their notifications of all types. Going with MTI or mixed model, I would need significantly more queries to do that. I will refactor and be sure STI is the best before marking an answer right but regardless the information you provided was extremely useful.
Tony
I tried this implementation but the notification_id is not getting passed through to the details model
Damian
Of course it isn't. I guess I was a little unclear there. Unless you declare `:foreign_key => :notification_id` in the belongs to statement in a detail model, then rails assumes the notification\_id column is named after the association named in the belongs\_to relationship. Ie: `:belongs_to :twitter` is looking for a twitter\_id column, not a notification\_id column.
EmFi
+1  A: 

Given the limited info, I'd say stick with STI.

The key question is: Are there places in your app where you want to consider all types of Notifications together? If so, then that's a strong sign that you want to stick with STI.

Mike H
STI seems simpler. there are place where i would iterate through all notifications and do things based on type
Tony
A: 
has_one :details, :class_name => "#{self.class.name}Detail"

doesn't work. self.class.name in the context of a class definition is 'Class' so :class_name is always 'ClassDetail'

So it must be:

has_one :details, :class_name => "#{self.name}Detail"

But very nice idea!

tnt