update_attribute 的参数在 SQL 语句中发生变化

Arguments to update_attribute change in SQL statement

要点

此方法调用:

user.update_attribute('image_url', 'arch.png')

生成此 SQL 语句:

UPDATE "users" SET "image_url" = ?, [["image_url", "rock.jpg"]]

SQL 声明不是预期的结果。

期望的结果是这个 SQL 语句:

UPDATE "users" SET "image_url" = ?, [["image_url", "arch.png"]]

请注意,生成的 SQL 语句对 image_url 的参数有误。


背景

我使用 form_for 标签生成一个表单,用户可以在其中更改个人资料图片。表单向调用另一个方法的控制器发送一个 post 请求,并传递参数。最后一个方法在记录上调用 update_attribute,保存更改。

我正在为图像使用 gem“载波”。


调试

我已经进行了一些调试,看起来一切顺利,直到最后一个方法调用 update_attribute。该调用生成的 SQL 语句不正确。

仅供参考,旧图片文件名为“rock.jpg”,新图片文件名为“arch.png”。

大意是执行这两行:

puts "\n\nSending #{attribute} and #{value} to update_attribute\n\n" 
if user.update_attribute(attribute, value)

这导致:

Sending image_url and arch.png to update_attribute

UPDATE "users" SET "image_url" = ?, WHERE "users"."id" = ?  [["image_url", "rock.jpg"], [...]]

我删除了生成的 SQL 语句的 updated_at 部分,因为它不重要。完整的 SQL 语句以及助手的更多输出可以在下面看到。

日志的详细输出

Received update request!!!!!
---------------------------
Attribute - image_url
Value - arch.png
--------------------------

Sending image_url and arch.png to update_attribute

   (0.1ms)  begin transaction
  CACHE (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", 186]]
  SQL (0.2ms)  UPDATE "users" SET "image_url" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["image_url", "rock.jpg"], ["updated_at", "2016-04-27 01:25:49.476024"], ["id", 186]]
   (90.4ms)  commit transaction

代码

形式:

<%= form_for(@current_member, html: {id: 'image_form'}) do %>
  <%= file_field_tag :image_url, 
    type: 'file', html: {id: 'file_field'} %>
  <%= hidden_field_tag 'updateParam', 'image_url' %>
  <%= submit_tag %>
<% end %>

控制器:

def update
  update_account(@current_member, params[:updateParam], params)
end

帮手:

def update_account(user, attribute, parameters)

  puts "\n\n\n\n\n"
  puts "Received update request!!!!!\n
  puts "---------------------------\n"
  puts "Attribute - #{attribute}\n"
  puts "Value - #{parameters[attribute]}\n"
  puts "--------------------------\n\n\n"

  if editable attribute
    if valid(attribute, parameters)
      value = parameters[attribute]

      puts "\n\nSending #{attribute} and #{value} to update_attribute\n\n"
    
      if user.update_attribute(attribute, value)

      # ...
      # ...

要点

此方法调用:

user.update_attribute('image_url', 'arch.png')

生成此 SQL 语句:

UPDATE "users" SET "image_url" = ?, [["image_url", "rock.jpg"]]

SQL 声明不是预期的结果。

期望的结果是这个 SQL 语句:

UPDATE "users" SET "image_url" = ?, [["image_url", "arch.png"]]

请注意,生成的 SQL 语句对 image_url 的参数有误。

扩展评论回答:

看起来,您的模型中有一些回调正在改变 save/update 之前的 value

要跳过回调,您可以使用 update_column 而不是 update_attribute

注意:update_column 也会跳过验证并且不会更新 updated_at 属性。


作者编辑

这个答案是正确的,回调将结果更改为可以保存在数据库中的结果。

使用 update_column 我能够看到我的参数已正确传递,但并不正确。 carrierwave gem 使用回调来检查参数是否有效,如果无效,它将值恢复为调用前的值。如果该值有效(上传文件),则它将文件保存在 save_dir 中并将值更改为 url 以指向该文件。

我用它来进一步调试,发现我传递给 update_attribute 的值无效,因为 form_for 没有包含 multipart: true,这是表单所必需的使用载波上传图像。添加这个解决了问题。

我不得不重新使用 update_attribute 而不是 update_column,这样 carrierwave 的回调仍然会被执行并且上传的文件会保存在数据库中。

你的问题有很多混乱,这使得阅读和帮助变得困难。 这种 bug 搜寻的目标是减少所涉及的元素,尽可能少地担心。

绕过Rails

运行 SQL 控制台中的语句。只是为了确保您没有任何花哨的触发器和功能。从您显示的输出来看,情况并非如此。但是......它花费你1分钟。并给你一些安全感。

绕过View/Controller

运行 Rails 控制台上的代码。我倾向于将所有内容写在一行中,并在前面加上 reload! 语句。因此可以快速检查代码中的更改:

reload!; user = User.find(12345); user.update_attribute('image_url', 'arch.png'); User.find(12345).image_url

它打印什么? 我认为您的问题源于回调。所以它应该已经为 image_url 打印了错误的值。所以你可以确定问题不是由 View/Controller 层引起的。

绕过视图

如果一切正常,那么您可以确定问题出在View/Controller的某个地方。在这种情况下,我不会使用 params 来构建更新语句,而是使用硬编码值。像这样你绕过了视图层。

绕过回调

使用update_column代替update_attribute

回调

第一:不要使用回调。第二:不要使用它们。如果你使用它们,请确保你知道你在做什么:-)

如果update_column解决了问题,那么可能是回调的原因。 在您的代码中搜索属性名称和回调名称,看看是否有。