Multiple Table Inheritance vs. Single Table Inheritance in Ruby on Rails

前端 未结 4 726
天命终不由人
天命终不由人 2020-12-23 11:08

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 ma

相关标签:
4条回答
  • 2020-12-23 11:22

    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 subclasses.

    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.name}Detail"
          accepts_nested_attributes_for :details
          default_scope -> { includes(: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.

    Detailed gem

    I've built a gem over your solution: https://github.com/czaks/detailed. Using it you can simplify your Notification class to:

    class Notification < ActiveRecord::Base
      include Detailed
    end
    

    The rest goes the previous way.

    As an added bonus, you can now access (read, write, relate) the subclass-specific attributes directly: notification.phone_number, without resorting to: notification.details.phone_number. You can also write all code in the main classes and subclasses, leaving the Details model empty. You will also be able to do less queries (in the above example 4 instead of N+1) on large datasets using Notification.all_with_details instead of the regular Notification.all.

    Be aware, that at the current time this gem isn't tested very well, though it works in my usecase.

    0 讨论(0)
  • 2020-12-23 11:26
    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!

    0 讨论(0)
  • 2020-12-23 11:33

    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.

    0 讨论(0)
  • 2020-12-23 11:39

    I know this is old, but after having come up with a solution I see potential answers which could use it everywhere! I recently forked a promising project to implement multiple table inheritance and class inheritance in Rails. I have spent a few days subjecting it to rapid development, fixes, commenting and documentation and have re-released it as CITIER Class Inheritance and Table Inheritance Embeddings for Rails.

    I think it should allow you to do what you needed by simply constructing the models where Twitter, Email and SMS inherit from Notification. Then have the migration for Notifications only include common attributes, and the ones for the three subtypes include their unique attributes.

    Or even define a function in the root Notification class and overload it in subclasses to return something different.

    Consider giving it a look: https://github.com/PeterHamilton/citier

    I am finding it so useful! I would (by the way) welcome any help for the community in issues and testing, code cleanup etc! I know this is something many people would appreciate.

    Please make sure you update regularly however because like I said, it has been improving/evolving by the day.

    0 讨论(0)
提交回复
热议问题