Search code examples
ruby-on-railsmoduleincludemixins

Conditionally Include a Module based on the Instance of a Model Ruby on Rails


In the rails app I am working on I have a model Order, Order technically has 2 types (newspaper and web) but they are both represented in the Order Model. The children below Order are different based on the type of Order and thus the methods used to obtain data about the orders are different.

I have a CurrentMonthsOrders serializer where I would like to be able to call:

 order.start_date
 order.end_date

without having to check what type of Order I am dealing with. So what I would like to be able to do is include the module based on the type of Order I am dealing with. Is this possible/is this the best way to go about this problem?

Below are the modules:

  module WebOrder

    def start_date
      web_line_items.sort_by(&:start_date).first.start_date
    end

    def end_date
      web_line_items.sort_by(&:end_date).last.end_date
    end

  end

  module Newspaper

    def start_date
      newspaper_placements.last.date.strftime('%m/%d')
    end

    def end_date
      object.newspaper_placements.first.date.strftime('%Y/%-m/%-d')
    end

  end

Solution

  • Typically, in Rails, this sort of data model would be done via Single Table Inheritance (often abbreviated STI). You would have an Order class (and corresponding orders table in the database), which would have a type column. Then you would define subclasses of Order for WebOrder and Newspaper:

    class WebOrder < Order
      def start_date
        ...
      end
    
      ...
    end
    
    class Newspaper < Order
      ...
    end
    

    When WebOrder or Newspaper orders are saved to the database, Rails will record the class in the type column, and when records are pulled from the database, Rails will use the type column to create instances of the correct subclass.

    It is possible to add the methods of a module to a particular instance of a class, but I wouldn't recommend it in general, because then you have objects which are ostensibly the same class, but have different code/behavior. Also, it can lead to higher memory usage, since you are customizing many individual objects. But if you were to do it, it would work roughly like this:

     order = Order.new
     order.extend(Newspaper)
    

    You'd have to remember to do that sometime between loading your Order instance and calling the customized methods, though. If you use Single Table Inheritance, Rails will take care of it for you.

    There's a pretty good explanation of Single Table Inheritance here: http://eewang.github.io/blog/2013/03/12/how-and-when-to-use-single-table-inheritance-in-rails/