Rails:通过控制器记录更新现有 has_many?

Rails: update existing has_many through record via controller?

所以这三分之二的作品。每次用户阅读文章时,都会创建一条历史记录(has_many 到),它只是说 "User read Article at Read_Date_X".

数据库正常,模型正常,read_date 参数在历史控制器中是允许的,以下操作都有效 1) 检查用户之前是否阅读过文章 2)如果是第一次在这篇文章上创建一个新的历史记录。

但我无法弄清楚为什么中间位(仅更新现有记录上的 read_date)不起作用。我用h.save试一下也没关系!或 h.update().

h = History.where(article_id: @article, user_id: current_user)
if h.exists?
  h = History.where(article_id: @article, user_id: current_user)
  h.read_date = Time.now
  h.save!
else
  h = History.new
  h.article_id = @article.id
  h.user_id = current_user.id
  h.read_date = Time.now
  h.save!
end

如果找到现有记录,它抛出的错误是:

undefined method `read_date=' for #<History::ActiveRecord_Relation:0x007fe7f30a5e50>

更新:有效答案

所以 Derek 是对的,这个版本有效。中间位需要单个实例,而不是数组,这是顶部条件(没有 .first)正在检查的内容。但是,将其用于 return 单个记录意味着您需要在第二部分将 "exists?" 交换为 "present?"。

h = History.where(article_id: @article, user_id: current_user).first
if h.present?
  h.read_date = Time.now
  h.save!
else
  h = History.new
  h.article_id = @article.id
  h.user_id = current_user.id
  h.read_date = Time.now
  h.save!
end

History.where(article_id: @article, user_id: current_user) 是 return 一个 History::ActiveRecord_Relation。如果要设置 read_date,则需要获取一条记录。

这是您可以使用现有资源执行此操作的一种方法:

h = History.where(article_id: @article, user_id: current_user).first

另一种处理此问题的方法是使用 find_by 而不是 where。这将 return 一条记录。像这样:

h = History.find_by(article_id: @article, user_id: current_user)

但是,如果一个用户可能对一篇文章有​​很多历史记录,我会坚持你做事的方式并进行一次更改。如果由于某种原因你有很多历史记录,这可能不是很有效。

histories = History.where(article_id: @article, user_id: current_user)
histories.each { |history| history.update(read_date: Time.now) }

我知道这个问题已经回答了。这里有一些额外的想法和建议。

  • 我不会有单独的 read_date 属性。只需使用 updated_at 即可。它已经为你准备好了。而且,您的代码的工作方式 read_dateupdated_at 将始终(本质上)相同。

  • 查询历史是否存在时,可以current_user.histories.where(article: @article)。 IMO,这似乎比 History.where(article_id: @article, user_id: current_user).first.

  • 更干净
  • 您可以通过检查 h 分配是否成功来避免所有 exists?present? 业务。因此,if h = current_user.histories.where(article: @article)

  • 如果您选择使用 updated_at 而不是 read_date,那么您只需执行 [=] 即可将 updated_at 设置为 Time.now 26=].

  • 我会使用 has_many :through 提供的 << 方法(而不是手动构建 history 记录)。同样,如果您使用 updated_at 而不是 read_date,那么您可以使用这种方法。

因此,您可以将代码归结为:

if h = current_user.histories.where(article: @article)
  h.touch 
else 
  current_user.articles << @article 
end

可以使用三元运算符而不是if then else,在这种情况下它可能类似于:

current_user.histories.where(article: @article).tap do |h|
  h ? h.touch : current_user.articles << @article 
end