我应该将这个 Rails 模型子类化吗?

Should I should subclass this Rails model?

我有一个名为 Coupon 的模型,可以将其设置为具有 money_offpercent_off 属性(一次只能设置一组)。

也根据 Couponmoney_off 还是 percent_off 改变使用的方法。

我想知道我是否应该使用单一 table 继承来本质上子 class Coupon 并有一个处理 % 的子 class off coupons 和另一个处理 money off coupons?

然后我想知道用户如何能够从视图中select这个。

你能做的就是在内部维护策略,提供pricediscounted?discounted_price等方法。此外,无论管理员选择输入百分比还是固定单位,您仍然可以提供这两种方法:discount_pctdiscount_units,这将在内部实现如何计算它们的 return 值。

这样原始的 class 仍然支持概念(与数据模型相同),但也足够灵活,允许以各种方式为其提供必要的输入。无论您是想向客户显示 pct off 还是固定价格单位,您都可以独立于管理员的首选输入方法。

甚至内部方法也可以使用这些抽象。如果事实证明你在内部到处都是 if/elsing,你可以为策略创建嵌套的 classes,并在你从数据库中获取记录后实例化正确的策略。

最好的方法是确定每个 class 需要哪些功能。如果您只需要少量更改,则坚持使用 class 和 enum:

#app/models/coupon.rb
class Coupon < ActiveRecord::Base
   enum type: [:percent, :money]

   def value
      if type.percent?
         # ...
      elsif type.money?
         # ...
      end
   end
end

这将允许您在实例方法中使用 type,这应该不会导致这样的问题如果您没有做太多更改在 class.

内制作

这样您就可以调用:

@coupon = Coupon.find x
@coupon.value #-> returns value based on the type

--

备选方案 (STI) 更像是一种结构化更改,并且只有在您明确引用每个 class 时才会起作用:

#app/models/coupon.rb
class Coupon < ActiveRecord::Base
end

#app/models/percent.rb
class Percent < Coupon
   def amount
      # ...
   end
end

#app/models/money.rb
class Money < Coupon
   def takeout
      # ...
   end
end

这里的一个重要因素是如何称呼它们。

对于上述classes,你必须自己参考subclassed classes:

@percentage_coupon = Percent.find x
@money_coupon      = Money.find y

这显然会比较麻烦,甚至可能导致你的路由和控制器等出现问题

.....所以最好使用单个 class :)

这是一个说明策略用法的示例(Yam 发布了更详细的答案):

class Coupon < Struct.new(:original_price, :amount_off, :type)
  def price_after_discount
    discount_strategy.call(self)
  end

  private

  def discount_strategy
    # note: no hardcoding here
    klass = type.to_s.camelize # :money_off to 'MoneyOff'
    "Coupon::#{klass}".constantize.new       
  end

  class MoneyOff
    def call(coupon)
      coupon.original_price - coupon.amount_off
    end
  end

  class PercentOff
    def call(coupon)
      coupon.original_price * (1.0 - coupon.amount_off / 100.0)
    end
  end
end  

Coupon.new(150, 10, :money_off).price_after_discount # => 140
Coupon.new(150, 10, :percent_off).price_after_discount # => 135.0

现在,我们可以在构造函数中接受它,而不是在内部创建策略,从而使策略成为 "injectable"。