2009-05-23 3 views
1

고유하지 않은 적합성으로 Ruby on Rails를 사용하여 블로그 애플리케이션을 작성하고 있습니다. 내 PostsController에는 로그인 한 사용자가 자신의 게시물 만 편집하거나 삭제할 수있는 코드가 포함되어 있습니다.레일 : 사용자 스푸핑 체크 유지 DRY

이 코드를 표시 할 플래시 메시지에 대한 단일 인수가 포함 된 개인 메서드로 분해하려고 시도했지만 다른 작성자의 게시물을 편집하여 테스트했을 때 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 

답변

2

before 필터 방식은 여전히 ​​ok 옵션입니다. 컨트롤러의 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 

, 나는 모두를 오프로드하는 것을 선호 이 루틴은 플러그인으로 작동합니다. 개인적으로 가장 좋아하는 인증 플러그인은 Authorization입니다. 지난 몇 년간 큰 성공을 거두었습니다. 을 변형하여 사용하는 컨트롤러를 리팩토링 것

:

permit "author of :post" 
+0

인증 확인 전에 쿼리를 작성하지 마십시오! –

+0

@Pedro 특정 모델을 복사하기 전에 특정 모델을 기준으로 인증 (인증 아님)을 확인하는 방법을 설명해야합니다. :) –

+0

귀하의 인증 방법은 0 개의 검색어를 만듭니다. 사용자가 권한이 있는지 여부를 확인하기 전에 찾기를 수행하십시오. –

1

간단한 대답이 모두 맞는 뭔가 메시지를 변경하는 것입니다 :

여기에 관련 컨트롤러 코드입니다 ". 당신은 할 수 없습니다 엉망이 다른 저자의 글과 함께"

+0

그래,하지만 그렇게하고 싶지는 않아. –

1

당신은 추한 마음에 들지 않으면 * 마지막 솔루션의 반환, 당신은 주위 필터를 사용하여 조건부로 사용자가있는 경우에만 얻을 수 인정 받은.

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 % 확신 할 수는 없습니다. 시도하기 쉽지만 확실합니다.