Ruby on Rails: How to create associated models on the fly?
I have the following models:
class Product < ActiveRecord::Base
belongs_to :brand
belongs_to :model
accepts_nested_attributes_for :brand, :model
...
end
class Brand < ActiveRecord::Base
has_many :products
has_many :models
...
end
class Model < ActiveRecord::Base
has_many :products
belongs_to :brand
accepts_nested_attributes_for :brand
...
end
I have a problem to create开发者_如何转开发 a new product.
Here is the relevant code in the controller:
class ProductsController < ApplicationController
...
def create
@product = Product.new(params[:product])
if @product.save ... # Here is the error
end
...
end
When user adds a new brand and a new model, params[:product]
contains the following:
"brand_attributes"=>{"name"=>"my_new_brand"}
"model_attributes"=>{"model_no"=>"my_new_model"}
and I got the following error:
Mysql2::Error: Column 'brand_id' cannot be null: INSERT INTO `models` ...
because model has a foreign key brand_id
which is not set. I can't set it because the brand (like the model) is created on the fly when the product is created. I don't want to create the brand before the product, because then I the product has errors, I will need to delete the created brand.
Then I tried to change params[:product]
like this:
"brand_attributes"=>{"name"=>"my_new_brand",
"model_attributes"=>{"model_no"=>"my_new_model"}}
but I end up with this:
unknown attribute: model_attributes
What would be the proper way to handle this ?
1.) You should avoid using Model as a model name (I think you can see why this could lead to errors, although I don't see this as your problem here)
2.) You are referencing in too much of a circular pattern. A Product has a Model, and a Model has a Brand. Why are you having Product belong to a Model AND a Brand?? I suggest the following setup:
class Product < ActiveRecord::Base
belongs_to :model
accepts_nested_attributes_for :model
end
class Brand < ActiveRecord::Base
has_many :models
end
class Model < ActiveRecord::Base
has_many :products
belongs_to :brand
accepts_nested_attributes_for :brand
end
I'm a little confused by your data structure - which is your underlying problem.
Product < Model <> Brand
When you have the circular reference like you've define above, you can't have NESTED forms because your models are NESTED...
# schema
create_table :products do |t|
t.string :name
t.references :model
end
create_table :brands do |t|
t.string :name
end
create_table :models do |t|
t.string :name
t.references :brand
end
Well, first off, if you wrap your save in a transaction, a failure at any point in the transaction would roll back all writes, so your brand et al wouldn't be affected.
Product.transaction do
@product.save
end
You could try this:
before_create :save_associated
validates_associated :brand, :model
def save_associated
brand.save if brand.new_record?
model.save if model.new_record?
end
What that'll do is when you create a product record, it'll validate itself, and then it'll validate the attached brand and model. If all goes well, it'll go on to your before_save callback, which will save your associated models, and then your product model will be saved. If any of the three models are invalid, you'll never get to save_associated
, and if you're feeling extra paranoid, you can wrap the save in a transaction as indicated above to automatically roll back any changes if any part of the save fails.
精彩评论