Рельсы after_initialize только на "новых" - PullRequest
46 голосов
/ 08 марта 2012

У меня есть следующие 2 модели

class Sport < ActiveRecord::Base
  has_many :charts, order: "sortWeight ASC"
  has_one :product, :as => :productable
  accepts_nested_attributes_for :product, :allow_destroy => true
end

class Product < ActiveRecord::Base
  belongs_to :category
  belongs_to :productable, :polymorphic => true
end

Спорт не может существовать без продукта, поэтому в моем sports_controller.rb у меня было:

def new
  @sport = Sport.new
  @sport.product = Product.new
...
end

Я попытался перенести создание продукта на спортивную модель, используя after_initialize:

after_initialize :create_product

def create_product
 self.product = Product.new
end

Я быстро узнал, что after_initialize вызывается всякий раз, когда создается экземпляр модели (т. Е. Из вызова find). Так что это было не то поведение, которое я искал.

Как я должен моделировать требование, чтобы все sport имели product?

Спасибо

Ответы [ 6 ]

60 голосов
/ 08 марта 2012

Размещение логики в контроллере может быть лучшим ответом, как вы заявили, но вы можете заставить after_initialize работать следующим образом:

after_initialize :add_product

def add_product
  self.product ||= Product.new
end

Таким образом, он устанавливает продукт, только еслипродукт не существуетЭто может не стоить накладных расходов и / или быть менее понятным, чем наличие логики в контроллере.

Редактировать: Согласно ответу Райана, с точки зрения производительности, следующее, вероятно, будет лучше:

after_initialize :add_product

def add_product
  self.product ||= Product.new if self.new_record?
end
40 голосов
/ 09 октября 2015

Конечно, after_initialize :add_product, if: :new_record? - самый чистый путь здесь.

Не допускайте условного выражения в функции add_product

27 голосов
/ 06 мая 2013

Если вы делаете self.product ||= Product.new, он все равно будет искать продукт каждый раз, когда вы делаете find, потому что он должен проверить, является ли он нулевым или нет.В результате он не будет загружаться.Для того, чтобы сделать это только при создании новой записи, вы можете просто проверить, является ли она новой записью, прежде чем устанавливать продукт.повлиять на производительность любым заметным способом.

2 голосов
/ 08 марта 2012

Вместо использования after_initialize, как насчет after_create?

after_create :create_product

def create_product
  self.product = Product.new
  save
end

Похоже, это решит вашу проблему?

1 голос
/ 08 марта 2012

Похоже, вы очень близки.Вы должны быть в состоянии полностью отказаться от вызова after_initialize, но сначала я верю, что если ваша спортивная модель имеет отношение "has_one" с: product, как вы указали, то ваша модель Product также должна быть "own_to" sport.Добавьте это к вашей модели продукта

belongs_to: :sport

На следующем шаге вы сможете создать экземпляр модели Sport, например,

@sport = @product.sport.create( ... )

Это основано на информации, полученной от AssociationОсновы из Ruby on Rails Guides, с которыми вы могли бы ознакомиться, если я не совсем прав

0 голосов
/ 13 сентября 2013

Вы должны просто переопределить метод инициализации, как

class Sport < ActiveRecord::Base

  # ...

  def initialize(attributes = {})
    super
    self.build_product
    self.attributes = attributes
  end

  # ...

end

Метод Initialize никогда не вызывается, когда запись загружается из базы данных. Обратите внимание, что в приведенном выше коде атрибуты назначаются после сборки продукта. При такой настройке назначение атрибута может повлиять на созданный экземпляр продукта.

...