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.