Rails - update_attributes 遇到验证

发布于 2024-09-09 20:42:03 字数 4410 浏览 2 评论 0原文

所以我有一个用户模型,包含登录名、电子邮件地址、密码、密码确认、姓名、头像(图片)等。前 5 个有验证,基本上说明所有 5 个都需要存在才能创建一个新模型。

然而,这在更新方面给我带来了问题。

我有一个编辑页面,用户只能编辑他们的姓名和头像。我目前不打算让他们更改登录信息,我希望从不同的页面更改电子邮件和密码。

所以编辑表单看起来像这样:

<% form_for @user, :html => { :multipart => true } do |u| %>
 <p>
  <label>Name:</label>
  <%= u.text_field :name %>
 </p>
 <p>
  <label>Avatar:</label>
  <%= display_user_avatar %>
  <%= u.file_field :avatar%>
 </p>
 <p>
  <%= submit_tag %>
 </p>
<% end %>

如果我尝试执行 @user.update_attributes(params[:user]),那么因为唯一的 2 个参数是 name 和 < code>avatar,更新失败,因为验证条目需要密码、密码确认、电子邮件等内容,而它们根本不存在于该表单中。

我可以通过执行 @user.update_attribute(:name, params[:user][:name]) 来解决这个问题,但随后我担心避免验证是否是一件好事™。特别是在密码更新等方面,我确实需要验证新密码。

还有别的办法吗?

如果我只是使用 :name:avatar 的 update_attribute 来做到这一点,我将如何去做呢?

这行得通吗?

params[:user].each do |attribute|
  @user.update_attribute(attribute, params[:user][attribute])
end

这是一种可以接受的方式吗...?


--edit as follow up --
Okie, I tried as you suggested and did

  def update
    @user = User.find_by_login(params[:id])
    if @user.update_attributes!(params[:user])
      redirect_to edit_user_path(@user)
    else
      flash[:notice] = @user.errors
      redirect_to edit_user_path(@user)
    end
  end

所以它正在执行 ! 版本,并且异常被捕获 &浏览器中显示的是:

Validation failed: Password is too short (minimum is 5 characters)

服务器日志中的信息是:

Processing UsersController#update (for 127.0.0.1 at 2010-07-18 11:56:59) [PUT]
  Parameters: {"user"=>{"name"=>"testeeeeee"}, "commit"=>"Save changes", "action"=>"update", "_method"=>"put", "authenticity_token"=>"BMEGRW/pmIJVs1zlVH2TtZX2TQW8soeCXmMx4kquzMA=", "id"=>"tester", "controller"=>"users"}

Urm。看到这个,我才意识到它正在提交 "id"=>"tester"。现在,我已经设置了路由,以便它显示用户登录名,而不是 user_id...这可能是原因吗?它正在尝试查找具有 user_id == tester 的用户的更新,但由于它不存在,它会尝试创建一个吗? 这实际上是我由于路线而做错的事情吗?

嗯... rake 路线告诉我路线是:

edit_user GET    /users/:id/edit(.:format)                             {:action=>"edit", :controller=>"users"}
          PUT    /users/:id(.:format)                                  {:action=>"update", :controller=>"users"}

我像 user.rb 文件中那样设置了路线:

  def to_param
    "#{login}"
  end

但它肯定是显示 login 而不是id 一直如此。但我也在更新操作开始时执行 @user = User.find_by_login(params[:id]),然后更新该 @user

我很困惑。 >。<


Second update:

我的 User.rb 验证内容如下:

  validates_length_of :login, :within => 3..20
  validates_length_of :password, :within => 5..20
  validates_presence_of :login, :email, :password, :password_confirmation, :salt, :name, :on => :create
  validates_uniqueness_of :login, :case_sensitive => false
  validates_confirmation_of :password
  validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message => "format is invalid."
  attr_accessor :password, :password_confirmation

hashed_pa​​ssword 部分在这里:

  def password=(pass)
    @password = pass
    self.salt = User.random_string(10) if !self.salt?
    self.hashed_password = User.encrypt(@password, self.salt)
  end

u.attributes 给我

>> u.attributes
=> {"salt"=>"NHpH5glxsU", "name"=>"test er", "avatar_updated_at"=>nil, "updated_at"=>Sat Jul 17 07:04:24 UTC 2010, "avatar_file_size"=>nil, "avatar_file_name"=>nil, "hashed_password"=>"84f8675c1ed43ef7f8645a375ea9f867c9a25c83", "id"=>1, "avatar_content_type"=>nil, "login"=>"tester", "email"=>"[email protected]", "created_at"=>Fri May 07 10:09:37 UTC 2010}

Urmmm... 好吧,这就是你所说的,关于虚拟属性password实际上不存在...... 那么我该如何解决这个问题呢? 妈的,我以为我很聪明地摆弄自己的身份验证代码......

更改为这些身份验证插件之一有多容易?我需要创建一个新的用户模型吗?或者该插件应该能够与我当前的插件一起使用吗?

感谢迄今为止的所有帮助,顺便说一句! :D

So I've got a user model, with login, email address, password, password confirmation, name, avatar (picture), etc. There are validations on the first 5, basically stating that all 5 need to exist in order to create a new model.

However, this causes problems for me where updates are concerned.

I've got an edit page, where the user can only edit their name and avatar. I'm not currently intending to let them change their login, and I wish to do an email and password change from a different page.

So the edit form looks like this:

<% form_for @user, :html => { :multipart => true } do |u| %>
 <p>
  <label>Name:</label>
  <%= u.text_field :name %>
 </p>
 <p>
  <label>Avatar:</label>
  <%= display_user_avatar %>
  <%= u.file_field :avatar%>
 </p>
 <p>
  <%= submit_tag %>
 </p>
<% end %>

If I attempt to do a @user.update_attributes(params[:user]), then because the only 2 params are name and avatar, the update fails, since stuff like password, password confirmation, email, etc are required to validate the entry, and they simply don't exist in that form.

I can get around this by doing @user.update_attribute(:name, params[:user][:name]), but then I worry about whether avoiding validations is a Good Thing™ or not. Especially with regards to something like password updates, where I do need to validate the new password.

Is there another way?

And if I were to do this simply using update_attribute for :name and :avatar, how would I go about doing it?

Would this work?

params[:user].each do |attribute|
  @user.update_attribute(attribute, params[:user][attribute])
end

Is this an acceptable way to do this...?


--edit as follow up --
Okie, I tried as you suggested and did

  def update
    @user = User.find_by_login(params[:id])
    if @user.update_attributes!(params[:user])
      redirect_to edit_user_path(@user)
    else
      flash[:notice] = @user.errors
      redirect_to edit_user_path(@user)
    end
  end

So it's doing the ! version, and the exception caught & displayed in the browser is:

Validation failed: Password is too short (minimum is 5 characters)

The info in the server log is:

Processing UsersController#update (for 127.0.0.1 at 2010-07-18 11:56:59) [PUT]
  Parameters: {"user"=>{"name"=>"testeeeeee"}, "commit"=>"Save changes", "action"=>"update", "_method"=>"put", "authenticity_token"=>"BMEGRW/pmIJVs1zlVH2TtZX2TQW8soeCXmMx4kquzMA=", "id"=>"tester", "controller"=>"users"}

Urm. Looking at this, I just realised that it is submitting "id"=>"tester". Now, I have my routes set up so that it is showing the users login name, instead of the user_id... Could that be why? It is attempting to find a update a user with user_id == tester, but since it doesn't exist, it attempts to create one instead?
Is it actually something I'm doing wrong due to the route?

Hmmm... rake routes tells me that the route is:

edit_user GET    /users/:id/edit(.:format)                             {:action=>"edit", :controller=>"users"}
          PUT    /users/:id(.:format)                                  {:action=>"update", :controller=>"users"}

And I set up the route like that in the user.rb file:

  def to_param
    "#{login}"
  end

but it's definitely been displaying login instead of id all this time. But I'm also doing right at the beginning of the update action, a @user = User.find_by_login(params[:id]), and then updating that @user.

I'm very confused. >.<


Second update:

My User.rb validation stuff are as follows:

  validates_length_of :login, :within => 3..20
  validates_length_of :password, :within => 5..20
  validates_presence_of :login, :email, :password, :password_confirmation, :salt, :name, :on => :create
  validates_uniqueness_of :login, :case_sensitive => false
  validates_confirmation_of :password
  validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message => "format is invalid."
  attr_accessor :password, :password_confirmation

And the hashed_password section is here:

  def password=(pass)
    @password = pass
    self.salt = User.random_string(10) if !self.salt?
    self.hashed_password = User.encrypt(@password, self.salt)
  end

u.attributes gives me

>> u.attributes
=> {"salt"=>"NHpH5glxsU", "name"=>"test er", "avatar_updated_at"=>nil, "updated_at"=>Sat Jul 17 07:04:24 UTC 2010, "avatar_file_size"=>nil, "avatar_file_name"=>nil, "hashed_password"=>"84f8675c1ed43ef7f8645a375ea9f867c9a25c83", "id"=>1, "avatar_content_type"=>nil, "login"=>"tester", "email"=>"[email protected]", "created_at"=>Fri May 07 10:09:37 UTC 2010}

Urmmm... Ok, so it's what you said, about the virtual attribute password being actually nonexistent...
So how do I get around that?
Bugger, here I thought I was being smart fiddling with my own authentication code...

How easy is it to change to one of those authentication plugins? Will I need to create a new User model? Or should the plugin be able to work with my current one?

Thanks for all the help so far, btw! :D

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

愛放△進行李 2024-09-16 20:42:03

我已经检查过这一点,并且通过 update_attributes 仅部分更新 2 个属性效果很好。所有其他属性均保留其先前的值,这意味着验证不会失败。有几件事需要尝试:

  • 在您的控制器操作中,您是否通过 User.find 加载用户?即你是从一个有效的模型开始的吗?
  • 您确定更新由于验证错误而失败吗?尝试将 update_attributes 替换为 update_attributes!。如果更新由于验证失败,后者将抛出异常。或者在尝试更新后检查@user.errors以确认哪个验证失败。

更新

如果 User.find_by_login 未找到匹配的记录,它将返回 nil 并且不会为您创建新记录。数据库中的 tester 用户的密码是否可能太短?也许该用户是在您将验证放入代码之前创建的?在保存记录之前,您是否使用任何类型的插件或回调来加密用户密码? password 实际上是一个未保存的虚拟属性,而实际密码位于 encrypted_pa​​ssword 之类的字段中吗?

script/console 尝试此操作(使用与测试应用程序相同的环境 - 开发或生产)

> user = User.find_by_login 'tester'
> user.valid?
> user.attributes

user.valid? 将返回 true 为 false,并且会在您尝试更新之前告诉您用户是否有效。

更新 2(修复验证)

在修复您自己的代码方面,您可以将如下方法添加到您的 User 模型中:

def password_validation_required?
  hashed_password.blank? || [email protected]?
end

然后更新所有与密码相关的验证规则,以便它们仅在该方法返回 true 时才适用,例如,

validates_length_of :password, :within => 5..20, 
  :if => :password_validation_required?

这意味着仅当我们还没有 hashed_pa​​ssword 时才执行密码验证规则(在新的user 例如)或者是否通过 password= 指定了新的纯文本密码。如果用户已有密码并且保持不变,则跳过密码验证。

不过,您考虑使用插件是正确的。编写自己的身份验证代码可能是一项有趣的练习,如果您有一些不寻常的要求,则可能需要这样做。缺点是可能存在您没有想到的安全问题。对您的应用程序进行诸如 restful_authentication 之类的改造应该不会太糟糕。您可能只需要重命名 User 模型上的一两个字段。

I've checked this and a partial update of just 2 attributes via update_attributes works fine. All the other attributes are left with their previous values, meaning that the validation shouldn't fail. A couple of things to try:

  • In your controller action are you loading the user via User.find? i.e. are you starting from a valid model.
  • Are you sure the update is failing due to validation errors? Try replacing the update_attributes with update_attributes!. The latter will throw an exception if the update fails due to validation. Or check @user.errors after the attempted update to confirm which validation has failed.

Update

If User.find_by_login doesn't find a matching record it will return nil and won't create a new record for you. Is it possible that the tester user in the database has a password that is too short? Maybe that user was created before you put the validations in your code? Are you using any kind of plugin or callback to encrypt user passwords before saving the records? Is password actually a virtual attribute that isn't saved and the actual password is in a field like encrypted_password?

Try this from script/console (use the same environment as you are testing the app with - development or production)

> user = User.find_by_login 'tester'
> user.valid?
> user.attributes

The user.valid? will return true of false and will tell you whether the user is valid to start with, before you even try an update.

Update 2 (fixing the validation)

In terms of fixing your own code, you could add a method like the following to your User model:

def password_validation_required?
  hashed_password.blank? || [email protected]?
end

and then update all your password related validation rules so that they only apply if this method returns true e.g.

validates_length_of :password, :within => 5..20, 
  :if => :password_validation_required?

What this is saying is only do the password validation rule if we don't yet have a hashed_password (on a new user for example) or if a new plain text password has been specified via password=. If the user already has a password and it is being left unchanged then skip the password validation.

You are right to be considering using a plugin though. Writing your own authentication code can be an interesting excercise and can be required if you have some unusual requirements. The down side is that there can be security issues that you haven't thought of. Retrofitting something like restful_authentication to your app shouldn't be too bad. You might just need to rename one or two fields on your User model.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文