Rails: хранение пользовательских спуфинговых проверок СУХОЙ - PullRequest
1 голос
/ 23 мая 2009

В порыве неоригинальности я пишу приложение для блога с использованием Ruby on Rails. Мой PostsController содержит некоторый код, который гарантирует, что вошедший в систему пользователь может редактировать или удалять только свои собственные сообщения.

Я пытался выделить этот код в приватный метод с одним аргументом для отображения флеш-сообщения, но когда я сделал это и проверил, отредактировав пост другого автора, я получил ActionController::DoubleRenderError - "Можно только визуализировать или перенаправить один раз за действие ".

Как мне сохранить эти чеки DRY ? Очевидный подход заключается в использовании фильтра before, но метод destroy должен отображать другую вспышку.

Вот соответствующий код контроллера:

before_filter :find_post_by_slug!, :only => [:edit, :show]

def edit

  # FIXME Refactor this into a separate method
  if @post.user != current_user
    flash[:notice] = "You cannot edit another author’s posts."
    redirect_to root_path and return
  end
  ...
end

def update 
  @post = Post.find(params[:id])

  # FIXME Refactor this into a separate method
  if @post.user != current_user
    flash[:notice] = "You cannot edit another author’s posts."
    redirect_to root_path and return
  end
  ...
end

def destroy
  @post = Post.find_by_slug(params[:slug])

  # FIXME Refactor this into a separate method
  if @post.user != current_user
    flash[:notice] = "You cannot delete another author’s posts."
    redirect_to root_path and return
  end
  ...
end

private
def find_post_by_slug!
  slug = params[:slug]
  @post = Post.find_by_slug(slug) if slug
  raise ActiveRecord::RecordNotFound if @post.nil?
end

Ответы [ 3 ]

2 голосов
/ 23 мая 2009

Подход до фильтра все еще в порядке. Вы можете получить доступ к тому, какое действие было запрошено, используя метод action_name контроллера.

before_filter :check_authorization

...

protected

def check_authorization
  @post = Post.find_by_slug(params[:slug])
  if @post.user != current_user
    flash[:notice] = (action_name == "destroy") ? 
      "You cannot delete another author’s posts." : 
      "You cannot edit another author’s posts."
    redirect_to root_path and return false
  end
end

Извините за троичного оператора посередине. :) Естественно, вы можете делать любую логику, какую захотите.

Вы также можете использовать метод, если хотите, и избежать двойного рендеринга, явно возвращая его в случае сбоя. Ключевым моментом здесь является возврат, чтобы вы не делали двойной рендер.

def destroy
  @post = Post.find_by_slug(params[:slug])
  return unless authorized_to('delete')
  ...
end

protected

def authorized_to(mess_with)
  if @post.user != current_user
    flash[:notice] = "You cannot #{mess_with} another author’s posts."
    redirect_to root_path and return false
  end
  return true
end

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

def destroy
  @post = Post.find_by_slug(params[:slug])
  punt("You cannot mess with another author's post") and return unless author_of(@post)
  ...
end

protected

def author_of(post)
  post.user == current_user
end

def punt(message)
  flash[:notice] = message
  redirect_to root_path
end

Лично я предпочитаю переложить всю эту рутинную работу на плагин. Мой личный любимый плагин авторизации - Авторизация . Я использовал его с большим успехом в течение последних нескольких лет.

Это изменит ваш контроллер для использования вариантов:

permit "author of :post"
1 голос
/ 24 мая 2009

Если вам не нравится уродливое * возвращение в этом последнем решении, вы можете использовать обходной фильтр и условно уступать, только если пользователь авторизован.

around_filter :check_authorization, :only => [:destroy, :update]

private
def check_authorization
    @post = Post.find_by_slug(params[:slug])
    if @post.user == current_user
        yield
    else
        flash[:notice] = case action_name
        when "destroy"
            "You cannot delete another author's posts."
        when "update"
            "You cannot edit another author's posts."
        end
        redirect_to root_path
    end
end

* - это мое предпочтение, хотя с точки зрения кода это совершенно правильно. Я просто считаю, что по стилю он не подходит.

Я также должен добавить, что я не проверял это и не уверен на 100%, что это будет работать, хотя это должно быть достаточно легко.

1 голос
/ 23 мая 2009

Простой ответ - изменить сообщение так, чтобы оно подходило обоим: «Вы не можете связываться с постами другого автора».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...