使用 has_many :through 和 build

Posted

技术标签:

【中文标题】使用 has_many :through 和 build【英文标题】:using has_many :through and build 【发布时间】:2012-03-09 21:58:59 【问题描述】:

我有三个模型,都是为了一个 has_many :through 关系。它们看起来像这样:

class Company < ActiveRecord::Base

  has_many :company_users, dependent: :destroy
  has_many :users, through: :company_users

  accepts_nested_attributes_for :company_users, :users

end

class CompanyUser < ActiveRecord::Base
  self.table_name = :companies_users #this is because this was originally a habtm relationship
  belongs_to :company
  belongs_to :user
end

class User < ActiveRecord::Base
  # this is a devise model, if that matters

  has_many :company_users, dependent: :destroy
  has_many :companies, through: :company_users

  accepts_nested_attributes_for :company_users, :companies

end

这可以很好地加载,并且连接可以很好地用于查询。但是,每当我做类似的事情时

@company = Company.last
@user = @company.users.build(params[:user])

@user.save    #=> true
@company.save #=> true

User 记录和CompanyUser 记录均已创建,但CompanyUser 记录中的company_id 字段设置为NULL

INSERT INTO `companies_users` (`company_id`, `created_at`,`updated_at`, `user_id`) 
VALUES (NULL, '2012-02-19 02:09:04', '2012-02-19 02:09:04', 18)

当你@company.users &lt;&lt; @user时它会做同样的事情

我确定我在这里做了一些愚蠢的事情,我只是不知道是什么。

【问题讨论】:

【参考方案1】:

你不能像那样使用 has_many :through,你必须这样做:

@company = Company.last
@user    = User.create( params[:user] ) 
@company.company_users.create( :user_id => @user.id )

然后您将正确定义关联。

更新

对于下面的评论,由于您已经有 accepts_nested_attributes_for,因此您的参数必须是这样的:

 :company => 
     :company_users_attributes => 
        [ 
           :company_id => 1, :user_id => 1  ,
           :company_id => 1, :user_id => 2 ,
           :company_id => 1, :user_id => 3  
        ]
     

您会自动为您将用户添加到公司。

【讨论】:

因此,如果您想允许某人编辑公司并允许他们使用复选框添加用户,我需要将 user_ids 数组从 params 哈希中拉出并为每个人创建一个 CompanyUser 记录? 谢谢!这就是我所缺少的。我什至没有想过要制作这样的表格。说我愚蠢。 @company.users.create(params[:user]) 也可以工作(至少在 Rails 4 中)。出于某种原因,@user = @company.users.build(params[:user]); @user.save 没有为我设置关联。 当它声明“你不能使用 has_many :through like that”时,这个答案在技术上是不正确的。不仅可以,而且在文档中得到官方支持和提及,只要在连接模型中设置 :inverse_of 即可。阅读此处 (api.rubyonrails.org/classes/ActiveRecord/Associations/…) 的“设置逆”部分,其中清楚地说明了一个示例,就像这里的示例一样。【参考方案2】:

如果您有一个 has_many :through 关联并且您想使用 build 保存关联,您可以使用 Join Model 中的 belongs_to 关联上的 :inverse_of 选项来完成此操作

这是一个来自 rails 文档的修改示例,其中标签具有 has_many :through 与帖子的关联,并且开发人员正在尝试使用 build 方法通过连接模型 (PostTag) 保存标签:

@post = Post.first
@tag = @post.tags.build name: "ruby"
@tag.save

普遍的期望是最后一行应该保存连接表(post_tags)中的“through”记录。但是,默认情况下这不起作用。 这只有在设置了 :inverse_of 时才有效

class PostTag < ActiveRecord::Base
  belongs_to :post
  belongs_to :tag, inverse_of: :post_tags # add inverse_of option
end

class Post < ActiveRecord::Base
  has_many :post_tags
  has_many :tags, through: :post_tags 
end

class Tag < ActiveRecord::Base
  has_many :post_tags
  has_many :posts, through: :post_tags  
end

所以对于上面的问题,在 Join Model (CompanyUser) 中的 belongs_to :user 关联上设置 :inverse_of 选项,如下所示:

class CompanyUser < ActiveRecord::Base
  belongs_to :company
  belongs_to :user, inverse_of: :company_users
end

将导致以下代码在连接表(company_users)中正确创建记录

company = Company.first
company.users.build(name: "James")
company.save

来源:here & here

【讨论】:

你想得到什么? 请在代码中添加一些解释(不要在评论部分,不要通过链接),以便其他人可以从中学习【参考方案3】:

我怀疑你的 params[:user] 参数,否则你的代码看起来很干净。 We can use build method with 1..n and n..n associations too,见here。

我建议您首先确保您的模型关联工作正常,为此打开 console 并尝试以下操作,

> company = Company.last
=> #<Tcompany id: 1....>
> company.users
=> []
> company.users.build(:name => "Jake")
=> > #<User id: nil, name: "Jake">
> company.save
=> true

现在,如果记录保存良好,请调试您传递给构建方法的参数。

调试愉快:)

【讨论】:

以上是关于使用 has_many :through 和 build的主要内容,如果未能解决你的问题,请参考以下文章

在 Rails 4 中使用 has_many :through :uniq 时的弃用警告

ActiveRecord、has_many :through 和多态关联

使用“has_many :through”进行验证

何时在 Rails 中使用“has_many :through”关系?

Rails RSpec 测试 has_many :through 关系

如何使用 :through 选项声明 has_many 关联的特定键?