问题
Working with an has_and_belongs_to_many_association
class Category
has_and_belongs_to_many :projects
end
I would would like to use a before_filter to set projects before saving categories
before_filter :set_projects, :only => [:create, :update]
def set_projects
@category.assign_attributes({projects: Project.all})
end
This works well, except when the category cannot be saved and there is a rollback. The projects are still updated in database.
Why this line
@category.assign_attributes({projects: Project.all})
generate these database records immediately?
BEGIN
INSERT INTO "categories_projects" ("category_id", "project_id") VALUES (86, 1)
INSERT INTO "categories_projects" ("category_id", "project_id") VALUES (86, 2)
INSERT INTO "categories_projects" ("category_id", "project_id") VALUES (86, 3)
COMMIT
I would like to wait for @category.save before commiting these new categories_projects relations. How to postpone these commits?
Please note that I can't modify the main "update" action. I have to use before/after filters and callbacks in order to override current functionality of my app.
------ EDIT ----------
Ok, after reading the doc carefully here, I think I have a solution:
When are Objects Saved?
When you assign an object to a has_and_belongs_to_many association, that object is automatically saved (in order to update the join table). If you assign multiple objects in one statement, then they are all saved.
If you want to assign an object to a has_and_belongs_to_many association without saving the object, use the collection.build method.
I will try to use the collection.build method. Do you have any idea how to do that with existing projects?
回答1:
Why not move this into the Category
model in an after_save
call back? e.g.
class Category
#based on comment need virtual attribute
attr_accessor :assignable_projects
after_save :set_projects
private
def set_projects
self.assign_attributes({projects: self.assignable_projects})
end
end
Since you need to set this for specific projects only you will need to create a virtual attribute. This attribute will be stored in the instance but will not be saved to the database. To do this we add an attr_accessor
line which will create bot the getter and setter methods needed.
Then in the controller
class CategoriesController < ApplicationContoller
before_filter :set_assignable_projects, only: [:create,:update]
private
def set_assignable_projects
@category.assignable_projects = params[:project_ids]
end
end
This event will fire after category
validations are run and the category is save successfully. It will then use the values assigned in the before_filter
to create the associations. Since assign_attributes
does not call save
again it will avoid an infinite loop. You could also place this in an after_validation
callback but make sure you check for self.errors.empty?
before using assign_attributes
or you will be in the same boat you are now.
If the category
fails to save the assignable_projects
will still be set for that instance so they will show up in the rendered view for a failed save.
来源:https://stackoverflow.com/questions/26491920/rails-3-has-and-belongs-to-many-association-how-to-assign-related-objects-witho