Должен ли я создать подкласс этой модели 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
который внутренне понял бы, как вычислить их возвращаемые значения.
Таким образом, исходный класс все еще поддерживает концепцию (такую же, как модель данных), но также достаточно гибок, чтобы обеспечить различные способы предоставления ему необходимого ввода. Независимо от того, хотите ли вы показать клиентам процентную ставку или единицы фиксированной цены, вы можете сделать это независимо от предпочитаемого администратором метода ввода.
Даже внутренние методы могут использовать эти абстракции. И если выясняется, что вы используете / используете внутренне, вы можете создать вложенные классы для стратегий и создать правильный класс, как только получите запись из БД.