Rails 3 has_and_belongs_to_many association: how to assign related objects without saving them to the database

你说的曾经没有我的故事 提交于 2019-12-11 18:08:07

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!