Rails after_initialize only on "new"

Ruby on-RailsModelNested Attributes

Ruby on-Rails Problem Overview


I have the following 2 models

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

A sport can't exist without the product, so in my sports_controller.rb I had:

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

I tried to move the creation of the product to the sport model, using after_initialize:

after_initialize :create_product

def create_product
 self.product = Product.new
end

I quickly learned that after_initialize is called whenever a model is instantiated (i.e., from a find call). So that wasn't the behavior I was looking for.

Whats the way I should be modeling the requirement that all sport have a product?

Thanks

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

Putting the logic in the controller could be the best answer as you stated, but you could get the after_initialize to work by doing the following:

after_initialize :add_product

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

That way, it only sets product if no product exists. It may not be worth the overhead and/or be less clear than having the logic in the controller.

Edit: Per Ryan's answer, performance-wise the following would likely be better:

after_initialize :add_product

def add_product
  self.product ||= Product.new if self.new_record?
end

Solution 2 - Ruby on-Rails

Surely after_initialize :add_product, if: :new_record? is the cleanest way here.

Keep the conditional out of the add_product function

Solution 3 - Ruby on-Rails

If you do self.product ||= Product.new it will still search for a product every time you do a find because it needs to check to see if it is nil or not. As a result it will not do any eager loading. In order to do this only when a new record is created you could simply check if it is a new record before setting the product.

after_initialize :add_product

def add_product
  self.product ||= Product.new if self.new_record?
end

I did some basic benchmarking and checking if self.new_record? doesn't seem to affect performance in any noticeable way.

Solution 4 - Ruby on-Rails

Instead of using after_initialize, how about after_create?

after_create :create_product

def create_product
  self.product = Product.new
  save
end

Does that look like it would solve your issue?

Solution 5 - Ruby on-Rails

It looks like you are very close. You should be able to do away with the after_initialize call altogether, but first I believe if your Sport model has a "has_one" relationship with :product as you've indicated, then your Product model should also "belong_to" sport. Add this to your Product model

belongs_to: :sport

Next step, you should now be able to instantiate a Sport model like so

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

This is based off the information from Association Basics from Ruby on Rails Guides, which you could have a read through if I am not exactly correct

Solution 6 - Ruby on-Rails

You should just override initialize method like

class Sport < ActiveRecord::Base

  # ...

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

  # ...

end

Initialize method is never called when record is loaded from database. Notice that in the code above attributes are assigned after product is build. In such setting attribute assignment can affect created product instance.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTyler DeWittView Question on Stackoverflow
Solution 1 - Ruby on-RailsbostonouView Answer on Stackoverflow
Solution 2 - Ruby on-RailsPaul OdeonView Answer on Stackoverflow
Solution 3 - Ruby on-RailsRyanView Answer on Stackoverflow
Solution 4 - Ruby on-RailsJames BrooksView Answer on Stackoverflow
Solution 5 - Ruby on-RailscoderatesView Answer on Stackoverflow
Solution 6 - Ruby on-RailsVictor NazarovView Answer on Stackoverflow