简体   繁体   中英

Rails: STI best way to deal with entity subtypes?

I have an entity Order that consists of two sub entities SalesOrder and PurchaseOrder . These two sub entities share 95% of their behaviour.

Is single table inheritance and the code below the way to best approach this or is there a better way?

class Order < ActiveRecord::Base
  belongs_to :order_type
end

class SalesOrder < Order       
  before_validation(on: :create) do
    self.order_type = OrderType.find_by!(name: "Sales order")
  end
  validates_inclusion_of :order_type_id, in: [OrderType.find_by!(name: 'Sales order').id]  

  validate :potential_savings, numericality: true, allow_nil: true

  default_scope { joins(:order_type).where("order_types.name = ?", "Sales order") }

  # OrderItem also has two sub entities (SalesOrderItem and PurchaseOrderItem with slightly different behaviour)
  has_many :order_items, class_name: "SalesOrderItem", foreign_key: "order_id", inverse_of: :order  
end

class PurchaseOrder < Order       
  before_validation(on: :create) do
    self.order_type = OrderType.find_by!(name: "Purchase order")
  end
  validates_inclusion_of :order_type_id, :in => [OrderType.find_by!(name: 'Purchase order').id]  

  validates_inclusion_of :potential_savings, :in => [nil], allow_nil: true # throw error if not nil

  default_scope { joins(:order_type).where("order_types.name = ?", "Purchase order") }

  # OrderItem also has two sub entities (SalesOrderItem and PurchaseOrderItem with slightly different behaviour)
  has_many :order_items, class_name: "PurchaseOrderItem", foreign_key: "order_id", inverse_of: :order
end

I don't know all the details of your data model, but perhaps you can put the shared functionality into a Concern .

Something like:

module Orderable
  extend ActiveSupport::Concern

  included do
    cattr_reader :order_type

    # All common/shared relations and validations are defined here
    belongs_to :order_type

    before_validation(on: :create) do
      self.order_type = OrderType.find_by!(name: @@order_type)
    end

    validates_inclusion_of :order_type_id, in: [OrderType.find_by!(name: @@order_type).id] 

    default_scope { joins(:order_type).where("order_types.name = ?", @@order_type) }
  end

  module ClassMethods
    def set_order_type(order_type)
      self.class_variable_set("@@order_type", order_type)
    end
  end
end

class SalesOrder < ActiveRecord::Base
  # Include the concern and call a method on the concern to set the order_type
  include Orderable
  set_order_type 'Sales order'

  # This validation is too different from the corresponding PurchaseOrder validation
  # so it is put in the model and not in the Orderable concern
  validate :potential_savings, numericality: true, allow_nil: true
end

class PurchaseOrder < ActiveRecord::Base
  include Orderable
  set_order_type 'Purchase order'

  # This validation is too different from the corresponding PurchaseOrder validation
  # so it is put in the model and not in the Orderable concern
  validates_inclusion_of :potential_savings, :in => [nil], allow_nil: true # throw error if not nil
end

Not sure if this is more powerful or DRY then going the STI way. Also, the last validation is not implemented yet due to the different formatting of the order_type name, but that can be fixed.

I hope this will help or give some inspiration.

A SalesOrder is an Order. So, I think inheritance is the best approach. STI is the best strategy if both types have the same or almost the same data. If you feel like this approach will create a lot of empty columns, I would advise to try a different strategy, but keep the inheritance.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM