Подтвердить что ты не робот

Как запустить проверки подкласса в одиночном наследовании?

В моем приложении у меня есть класс Budget. Бюджет может быть многих типов. Например, скажем, что есть два бюджета: FlatRateBudget и HourlyRateBudget. Оба наследуются от класса Budget.

Это то, что я получаю до сих пор:

class Budget < ActiveRecord::Base
  validates_presence_of :price
end

class FlatRateBudget < Budget
end

class HourlyRateBudget < Budget
  validates_presence_of :quantity
end

В консоли, если я это сделаю:

b = HourlyRateBudget.new(:price => 10)
b.valid?
=> false
b.errors.full_messages
=> ["Quantity can't be blank"]

Как ожидалось.

Проблема в том, что поле "type", в STI, происходит из параметров. Поэтому мне нужно сделать что-то вроде:

b = Budget.new(:type => "HourlyRateBudget", :price => 10)
b.valid?
=> true

Это означает, что рельсы запускают проверки в суперклассе вместо создания экземпляра подкласса после того, как я установил тип.

Я знаю, что это ожидаемое поведение, так как я создаю класс, который не нуждается в поле количества, но мне интересно, есть ли вообще сказать rails для запуска проверки для подкласса, а не супер.

4b9b3361

Ответ 1

Вероятно, вы можете решить это с помощью специального валидатора, аналогичного ответу на этот вопрос: Две модели, один STI и валидация Однако, если вы можете просто создать экземпляр предполагаемый подтип для начала, в этом случае вы бы вообще не захотели использовать специальный валидатор.

Как вы заметили, установка одного только поля типа не волшебным образом изменяет экземпляр из одного типа в другой. Хотя ActiveRecord будет использовать поле type для создания соответствующего класса при чтении объекта из базы данных, делая это наоборот (создание экземпляра суперкласса, а затем изменение поля типа вручную) не влияют на изменение типа объекта во время работы вашего приложения - он просто не работает таким образом.

Пользовательский метод проверки, с другой стороны, может самостоятельно проверить поле type, создать экземпляр соответствующего типа (на основе значения поля type), а затем запустить .valid? на этом объект, в результате чего проверки в подклассе выполняются таким образом, который кажется динамическим, даже если он фактически создает экземпляр соответствующего подкласса в процессе.

Ответ 2

Я сделал что-то подобное.

Адаптация к вашей проблеме:

class Budget < ActiveRecord::Base

    validates_presence_of :price
    validates_presence_of :quantity, if: :hourly_rate?

    def hourly_rate?
        self.class.name == 'HourlyRateBudget'
    end

end

Ответ 3

Для тех, кто ищет пример кода, вот как я выполнил первый ответ:

validate :subclass_validations

def subclass_validations
  # Typecast into subclass to check those validations
  if self.class.descends_from_active_record?
    subclass = self.becomes(self.type.classify.constantize)
    self.errors.add(:base, "subclass validations are failing.") unless subclass.valid?
  end
end

Ответ 4

Вместо того, чтобы устанавливать тип, непосредственно задайте тип, подобный этому... Вместо этого попробуйте:

new_type = params.fetch(:type)
class_type = case new_type
  when "HourlyRateBudget"
    HourlyRateBudget
  when "FlatRateBudget"
    FlatRateBudget
  else
    raise StandardError.new "unknown budget type: #{new_type}"
end
class_type.new(:price => 10)

Вы даже можете преобразовать строку в свой класс: new_type.classify.constantize, но если он поступает из параметров, это кажется немного опасным.

Если вы это сделаете, вы получите класс HourlyRateBudget, иначе это будет только бюджет.

Ответ 5

Еще лучше, используйте type.constantize.new("10"), однако это зависит от того, что тип из params должен быть правильной строкой, идентичной HourlyRateBudget.class.to_s

Ответ 6

Я также потребовал то же самое, и с помощью Брайс ответил:

class  ActiveRecord::Base
  validate :subclass_validations, :if => Proc.new{ is_sti_supported_table? }

  def is_sti_supported_table?
  self.class.columns_hash.include? (self.class.inheritance_column)
  end

  def subclass_validations
      subclass = self.class.send(:compute_type, self.type)
      unless subclass == self.class
        subclass_obj= self.becomes(subclass)
        self.errors.add(:base, subclass_obj.errors.full_messages.join(', ')) unless subclass_obj.valid?
      end
  end
end