обход before_filter, но только при доступе с другого контроллера / представления

#ruby-on-rails

#ruby-on-rails

Вопрос:

Я запускаю before_filter для некоторых действий, чтобы проверить, является ли пользователь current_user .

 before_filter :correct_user,  :only => [:edit, :update, :destroy]

def update
  @user = User.find(params[:id])
  if @user.update_attributes(params[:user])
    redirect_to current_user, :notice => "User updated!"
  else
    redirect_to current_user, :notice => "User not updated. waa waa."
  end
end

private
def correct_user
  if current_user != @user        
    redirect_to root_url, :notice => "Cannot act on different user."
  end
end
 

Не уверен, что это лучший способ что-то сделать, но это работает (может быть, лучше просто использовать current_user вместо поиска @user через параметры?)

Теперь у пользователя есть_many photos, и в моем представлении индекса фотографий я перечисляю все фотографии пользователя и разрешаю пользователю устанавливать любую фотографию в качестве фотографии профиля. В таблице user есть столбец с именем primary_photo_id для хранения этого идентификатора, и я использую link_to для его установки:

 =link_to "Make this your profile photo", user_path(@user, :user => {:primary_photo_id  => "#{photo.id}"}), :method => :put
 

Проблема в том, что before_filter запускается и не позволяет этому работать, потому что @user, который извлекается через params[:id], завершается с ошибкой, потому что это неправильные параметры. Если я удалю before_filter, он будет работать нормально, но тогда он больше не проверяет правильность пользователя.

(второй несколько связанный вопрос заключается в том, почему приведенный выше код работает, но этот:

 =link_to "Make this your profile photo", user_path(@user, :primary_photo_id  => "#{photo.id}"), :method => :put
 

не работает.

Спасибо. Я довольно новичок в rails и программировании, поэтому все, что вы можете сказать по моему конкретному вопросу, и любые плохие практики, которые я делаю с кодом здесь, очень ценятся.

Ответ №1:

Фильтр before ( #correct_user ) выполняется раньше #update , поэтому ваша переменная экземпляра @user еще не установлена, когда вы сравниваете ее в фильтре, если вы сначала не установили ее в другом фильтре before. Ваша последовательность выполнения выглядит так:

  1. выполнить #correct_user — сравнить current_user с @user (если не задано, это равно нулю). я предполагаю, что они будут совпадать только в том случае, если пользователь не вошел в систему
  2. предполагая, что мы справились, запустите #update и посмотрите @user

Вероятно, самый простой способ решить вашу проблему — просто переместить @user поиск в фильтр before:

 before_filter :correct_user,  :only => [:edit, :update, :destroy]

def edit
  # .. as before, but no need to look up user first
end

def update
  if @user.update_attributes(params[:user])
    redirect_to current_user, :notice => "User updated!"
  else
    redirect_to current_user, :notice => "User not updated. waa waa."
  end
end

def destroy
  # .. as before, but no need to look up user first
end

private

def correct_user
  @user = User.find(params[:id])
  if current_user != @user        
    redirect_to root_url, :notice => "Cannot act on different user."
  end
end
 

Поскольку @user теперь находится в фильтре, нет необходимости искать его снова в каждом из ваших действий контроллера. Надеюсь, это поможет!

Комментарии:

1. Это отлично сработало. Спасибо, Мэтт! На самом деле я решил использовать два before_filters, первый для get_user, другой для проверки правильного пользователя. для меня читается немного понятнее, хотя он, вероятно, менее «сухой». Вместо того, чтобы использовать user = User.find(params[:id]), я мог бы также использовать user = current_user (поскольку есть вспомогательный метод для захвата пользователя сеанса).. Рекомендуется ли это вообще, особенно, скажем, для редактирования, обновления и других вещей, которые действительно не должны быть выполнимы кем-либо еще, кроме зарегистрированного пользователя?

2. Да, часто для такого рода условий я настраиваю свой фильтр before, чтобы он определял, является ли пользователь администратором, и если да, то разрешит настройку из параметров, в противном случае жестко подключенных к зарегистрированному пользователю. Что-то вроде @user = current_user.admin? ? User.find(params[:id]) : current_user . Если у вас нет регистра администратора, я бы просто установил его в current_user напрямую, поскольку у вас, вероятно, уже есть экземпляр пользовательского объекта, нет смысла выполнять другой поиск.