Rails - update_attributes встречается с проверками - PullRequest
7 голосов
/ 17 июля 2010

Итак, у меня есть модель пользователя, с логином, адресом электронной почты, паролем, подтверждением пароля, именем, аватаром (изображение) и т. Д. На первых 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 и avatar, обновление завершится неудачно, поскольку для подтверждения записи требуются такие вещи, как пароль, подтверждение пароля, электронная почта и т. Д.просто не существует в этой форме.

Я могу обойти это, выполнив @user.update_attribute(:name, params[:user][:name]), но потом я беспокоюсь о том, является ли уклонение от проверки доброй вещью ™ или нет.Особенно в отношении чего-то вроде обновления пароля, где мне нужно нужно подтвердить новый пароль.

Есть ли другой способ?

А если бы я был чтобы сделать это, просто используя update_attribute для :name и :avatar, как мне это сделать?

Будет ли это работать?

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

Это приемлемый способсделать это ...?


- отредактировать как следует - Хорошо, я попытался, как вы предложили, и сделал
  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"}

Урм.Глядя на это, я только что понял, что он отправляет "id"=>"tester".Теперь мои маршруты настроены таким образом, чтобы вместо имени_пользователя показывалось имя пользователя для входа ... Может ли это быть причиной?Он пытается найти обновление для пользователя с user_id == tester, но так как его не существует, он пытается создать его вместо?Это действительно что-то, что я делаю неправильно из-за маршрута?

Хммм ... Рейк-маршруты говорят мне, что маршрут:

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.

Я очень запутался.>. <</p>


Второе обновление:

Мои 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_password:

  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"=>"tester@tester.com", "created_at"=>Fri May 07 10:09:37 UTC 2010}

Умммм ... Хорошо, так вы сказали, что виртуальный атрибут password на самом деле не существует ... Итак, как мне обойти это ?Bugger, здесь я подумал, что я умно играю со своим собственным кодом аутентификации ...

Насколько легко перейти на один из этих плагинов аутентификации?Нужно ли создавать новую модель пользователя?Или плагин сможет работать с моим текущим?

Спасибо за всю помощь, кстати!: D

1 Ответ

10 голосов
/ 17 июля 2010

Я проверил это, и частичное обновление всего 2 атрибутов через update_attributes работает нормально. Все остальные атрибуты остаются с их предыдущими значениями, что означает, что проверка не должна завершиться ошибкой. Несколько вещей, чтобы попробовать:

  • В вашем контроллере вы загружаете пользователя через User.find? то есть вы начинаете с действительной модели.
  • Вы уверены, что обновление не удалось из-за ошибок проверки? Попробуйте заменить update_attributes на update_attributes!. Последний выдаст исключение, если обновление завершится неудачно из-за проверки. Или проверьте @user.errors после попытки обновления, чтобы подтвердить, какая проверка не удалась.

Обновление

Если User.find_by_login не найдет подходящую запись, она вернет nil и не создаст новую запись для вас. Возможно ли, что пользователь tester в базе данных имеет слишком короткий пароль? Может быть, этот пользователь был создан до того, как вы добавили проверки в свой код? Используете ли вы какой-либо плагин или обратный вызов для шифрования паролей пользователей перед сохранением записей? Является ли password фактически виртуальным атрибутом, который не сохраняется, а фактический пароль находится в поле, подобном encrypted_password?

Попробуйте это из 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? || !@password.blank?
end

, а затем обновите все правила проверки, связанные с паролем, чтобы они применялись только в том случае, если этот метод возвращает true, например,

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

Это означает, что правило проверки пароля применяется только в том случае, если у нас еще нет hashed_password (например, для нового пользователя) или если через password= был указан новый пароль в виде простого текста. Если у пользователя уже есть пароль, и он остается без изменений, пропустите проверку пароля.

Вы правы, хотя рассматриваете возможность использования плагина. Написание собственного кода аутентификации может быть интересным упражнением и может потребоваться, если у вас есть какие-то необычные требования. Недостатком является то, что могут быть проблемы с безопасностью, о которых вы даже не задумывались. Модернизация чего-то вроде restful_authentication для вашего приложения не должна быть слишком плохой. Возможно, вам просто нужно переименовать одно или два поля в вашей User модели.

...