Должен ли я создать подкласс этой модели Rails?

У меня есть модель под названием Coupon, который может быть установлен, чтобы иметь money_off или же percent_off атрибуты (он может иметь только один набор за раз).

Также в зависимости от того, Coupon является money_off или же percent_off Изменения, какие методы используются.

Мне интересно, если я должен использовать единую таблицу наследования, чтобы, как правило, подкласс Coupon и есть подкласс, который имеет дело с процентами от купонов и другой, имеющий дело с деньгами от купонов?

Затем я хотел бы знать, как пользователь сможет выбрать это в представлении.

3 ответа

Решение

Лучший способ - определить, какие функции вам нужны для каждого класса. Если вам нужно только небольшое количество изменений, то придерживайтесь одного класса с 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 в ваших экземплярах методов, которые не должны вызывать таких проблем, если у вас не было много изменений, которые нужно внести в класс.

Это позволит вам позвонить:

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

-

Альтернатива (STI) была бы скорее структурированным изменением и работала бы, только если вы явно ссылались на каждый класс:

#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

Важным фактором здесь является то, как вы их называете.

Для вышеупомянутых классов, вы должны ссылаться на subclassed занятия самостоятельно:

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

Это, очевидно, будет более громоздким и может даже вызвать проблемы с вашими маршрутами и контроллерами и т. Д.

.... так что может быть лучше пойти с одним классом:)

Вот пример, который иллюстрирует использование стратегий (о которых 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

Теперь вместо того, чтобы создавать внутреннюю стратегию, мы можем принять ее в конструкторе, сделав стратегию "инъекционной".

Что вы можете сделать, так это поддерживать стратегию внутри компании и предоставлять такие методы, как price, discounted?, discounted_price, Кроме того, независимо от того, решил ли администратор вводить проценты или фиксированные единицы, вы все равно можете предоставить оба метода: discount_pct, discount_units который внутренне понял бы, как вычислить их возвращаемые значения.

Таким образом, исходный класс все еще поддерживает концепцию (такую ​​же, как модель данных), но также достаточно гибок, чтобы обеспечить различные способы предоставления ему необходимого ввода. Независимо от того, хотите ли вы показать клиентам процентную ставку или единицы фиксированной цены, вы можете сделать это независимо от предпочитаемого администратором метода ввода.

Даже внутренние методы могут использовать эти абстракции. И если выясняется, что вы используете / используете внутренне, вы можете создать вложенные классы для стратегий и создать правильный класс, как только получите запись из БД.

Другие вопросы по тегам