【问题标题】:Update attributes on has_many through associations and working with the unsaved object通过关联更新 has_many 的属性并使用未保存的对象
【发布时间】:2014-11-11 15:41:07
【问题描述】:

这和我的last quesion about unsaved objects有点关系,不过现在更多的是关于如何使用rails的具体问题。

我拥有的模型是:

class User < ActiveRecord::Base
    has_many :project_participations
    has_many :projects, through: :project_participations, inverse_of: :users
end

class ProjectParticipation < ActiveRecord::Base
    belongs_to :user
    belongs_to :project

    enum role: { member: 0, manager: 1 }
end

class Project < ActiveRecord::Base
    has_many :project_participations
    has_many :users, through: :project_participations, inverse_of: :projects

    accepts_nested_attributes_for :project_participations
end

使用这个模型,当我创建一个新项目时,我可以通过一个表单(fields_for 等)来完成,然后我可以在控制器中调用update_attributes。因此,如果我已经在数据库中有用户,我可以这样做:

u = Users.create # save one user in database (so we have at least one saved user)
p = Project.new

# add the user to the project as a manager
# the attributes could come from a form with `.fields_for :project_participations`
p.update_attributes(project_participations_attributes: [{user_id: u.id, role: 1}])
=> true

这工作正常,直到我想对项目的users 做点什么。例如,我想添加一个验证,一个项目必须至少有一个user

class Project < ActiveRecord::Base
    ...
    validates :users, presence: true # there must be at least one user in a project
    ...
end

现在给出:

u = Users.create
p = Project.new

p.update_attributes(project_participations_attributes: [{user_id: u.id, role: 1}])
=> false

p.errors
=> #<ActiveModel::Errors:... @base=#<Project id: nil>, @messages={:users=>["can't be blank"]}>

p.users
=> #<ActiveRecord::Associations::CollectionProxy []>

p.project_participations
=> #<ActiveRecord::Associations::CollectionProxy [#<ProjectParticipation id: nil, user_id: 1, project_id: nil>]>

所以在未保存的projects 上,.users 为空。这已经困扰了我(见my last quesion about unsaved objects)。但在这种情况下,我现在当然可以通过 validates :project_participations, presence: true 而不是 validates :users, presence: true 来解决这个问题,它的含义应该相同。

但这意味着我永远不应该使用.users 方法(在任何帮助器、模型、视图中,...),除非我完全确定我使用的是已保存的对象。这实际上使 .users 方法无法使用(就像验证用户的存在一样)。

如果我像这样调用update_attributes,验证会起作用并且会保存:

p.update_attributes(users: [u])

这样,它会自行创建project_participation,因此p.users 可以按预期工作。但是在这里我无法为该用户的project_participation 设置任何类似role 的数据。

所以我的问题是:无论对象是否保存,我都可以使.users 方法工作(我认为不是)吗?但是,我怎样才能将用户作为经理/成员添加到未保存的项目并使用未保存的项目?

我希望我的问题很清楚。

【问题讨论】:

    标签: ruby-on-rails validation activerecord has-many-through


    【解决方案1】:

    我想我理解您的问题,并且您认为无论是否保存 project 模型都不能使用 .users 方法是正确的。原因是在Project(即has_many :users, through: :project_participations, inverse_of: :projects)中定义关联时,您是在告诉rails 通过project_participations 连接表从数据库中读取users 属性,而当您没有保存了项目,您无需从数据库中读取任何内容。

    要以特定角色将User 添加到您的项目中,您需要创建一个新的ProjectParticipation 模型,然后将其关联到您的项目。如果您随后删除 users 关联并编写自己的 users 方法,则无论项目是否已保存,您都应该能够访问您的用户集合。

    class Project < ActiveRecord::Base
        has_many :project_participations
    
        ...
    
        def users
            project_participations.collect { |pp| pp.user }
        end
    end
    

    然后是这样的:

    u = Users.create
    p = Project.new
    
    pp = ProjectParticipation.new({user: u, project: p, role: 1})
    p.project_participations << pp
    p.users
    

    希望对您有所帮助。

    【讨论】:

    • 这暂时有效,但我认为这不是最干净的解决方案。 (例如,因为在单独的查询中收集每个用户的速度很慢等)。我会尝试解决这个问题,看看我能走多远,然后可能会接受答案/添加新的 cmets。
    • 如果您找到更好的方法,请添加 cmets。
    猜你喜欢
    • 2014-12-26
    • 1970-01-01
    • 1970-01-01
    • 2012-04-01
    • 2015-12-13
    • 2016-08-24
    • 2013-11-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多