티스토리 뷰

안녕하세요 강정호입니다. 오늘은 루비온레일즈로 유저 정보와 프로필 이미지 보여주기 기능에 대해서 어떻게 구현하였는지 알아보겠습니다.



1단계 : 라우터와 UserController 메서드 맵핑


유저 정보 보기 라우터:

GET /users/:id     users#show


그래서 UserController에 다음과 같이 show 메서드를 생성합니다.

def show
@user = User.find(params[:id])
end

params 메서드로 :id를 파라메터로 받습니다. localhost:3000/users/1  URL에서 1을 :id로 받아 User를 검색하여 인스턴스 변수에 반환합니다.


params 메서드에 대한 자세한 내용은 다음과 같습니다.

params 링크


4 매개 변수

컨트롤러의 액션에서는 사용자로부터 전송된 데이터나 그 이외의 매개 변수를 사용하여 어떤 작업을 하는 경우가 많습니다. 일반적인 웹 애플리케이션에서는 2종류의 매개 변수를 사용할 수 있습니다. 첫번째는 URL의 일부로서 전송되는 매개 변수로, '쿼리 문자열 매개 변수'라고 부릅니다. 쿼리 문자열은 URL의 "?"의 뒤에 위치합니다. 두번째는 'POST 데이터'라고 불리는 것입니다. POST 데이터는 보통 사용자가 기입한 HTML 폼으로부터 전송됩니다. 이는 HTTP POST 요청의 일부로 전송되기 때문에 POST 데이터라고 불립니다. Rails는 쿼리 문자열 매개 변수와 POST 데이터를 동일하게 다루며, 어느 쪽도 컨트롤러 내부에서는 params라는 이름의 해시를 통해 접근할 수 있습니다.

class ClientsController < ApplicationController
  # 이 액션에서는 쿼리 문자열 매개 변수가 사용됩니다.
  # 전송측에서 HTTP GET 요청을 사용하기 때문입니다.
  # 단 매개 변수에 접근하는 방법은 아래의 방식과 다르지 않습니다.
  # 유효한 고객 목록을 얻기 위해 이 액션의 URL은 다음과 같이 되어 있습니다.
  # clients: /clients?status=activated
  def index
    if params[:status] == "activated"
      @clients = Client.activated
    else
      @clients = Client.inactivated
    end
  end
 
  # 이 액션에서는 POST 데이터를 사용하고 있습니다.
  # 이 매개 변수는 일반적으로 사용자가 전송한 HTML 폼으로부터 생성됩니다.
  # 이것은 RESTful한 접근이며, URL은 "/clients"가 됩니다.
  # 데이터는 URL이 아닌 요청의 body에 포함되어 전송됩니다.
  def create
    @client = Client.new(params[:client])
    if @client.save
      redirect_to @client
    else
      # 아래 줄에서는 기본 랜더링 동작을 덮어씁니다.
      # 기본으로는 "create" 뷰가 랜더링됩니다.
      render "new"
    end
  end
end

4.1 해시와 배열 매개 변수

params 해시는 1차원의 키-값 쌍만 저장할 수 있는 것이 아닙니다. 배열이나 중첩된 해시를 가질 수도 있습니다. 값의 배열을 전송하고 싶은 경우에는 아래와 같이 키의 이름에 빈 대괄호를 추가해주세요.

GET /clients?ids[]=1&ids[]=2&ids[]=3

"["와 "]"는 URL에서는 사용불가능한 문자이므로, 이 예시의 실제 URL은 "/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3"처럼 구성됩니다. 이는 브라우저가 자동으로 변환해주며 나아가 Rails가 매개 변수를 가져올 때 자동으로 복원해주므로, 평소에는 신경쓸 필요가 없습니다. 단, 어떤 이유로 서버에 직접 요청을 해야하는 경우에는 이를 주의해야 합니다.

받은 params[:ids]의 값은 ["1", "2", "3"]이 됩니다. 매개 변수의 값은 모두 '문자열'이라는 점을 기억하세요. Rails는 매개 변수의 타입을 추측하지 않으며, 타입 변환도 해주지 않습니다.

params에 [nil][nil, nil, ...] 같은 값이 있으면 모두 자동적으로 []로 변환됩니다. 이 동작은 보안 상의 이유 때문이며 자세한 설명은 보안 가이드을 참조해주세요.

해시를 전송하기 위해서는 대괄호에 키 이름을 넣어 전송하면 됩니다.

<form accept-charset="UTF-8" action="/clients" method="post">
  <input type="text" name="client[name]" value="Acme" />
  <input type="text" name="client[phone]" value="12345" />
  <input type="text" name="client[address][postcode]" value="12345" />
  <input type="text" name="client[address][city]" value="Carrot City" />
</form>

이 폼을 전송하면 params[:client]의 값은 { "name" => "Acme", "phone" => "12345", "address" => { "postcode" => "12345", "city" => "Carrot City" } }가 됩니다. params[:client][:address]처럼 해시가 중첩되어 있는 부분을 주목해주세요.

이 params 객체는 해시처럼 동작합니다만, 키로 심볼이나 문자열, 어느 쪽을 사용할 수 있다는 점이 다릅니다.

4.2 JSON 매개 변수

웹 애플리케이션을 개발하다 보면, 매개 변수를 JSON 형식으로 받는다면 편리할텐데, 라고 생각할 때가 종종 있습니다. Rails에서는 요청의 "Content-Type"에 "application/json"가 지정되어 있으면, 자동적으로 매개 변수를 params 해시로 변환해줍니다. 그 이후로는 일반적인 params 해시를 조작하듯 사용할 수 있습니다.

예를 들어 아래의 JSON 데이터를 전송한다고 가정합시다.

{ "company": { "name": "acme", "address": "123 Carrot Street" } }

params[:company]가 넘겨받는 값은 { "name" => "acme", "address" => "123 Carrot Street" }가 됩니다.

마찬가지로 initializer에서 config.wrap_parameters를 활성화했거나, 컨트롤러에서 wrap_parameters를 호출했을 경우, JSON 매개 변수의 루트 요소를 안전하게 제거할 수 있습니다. 이 경우, 매개 변수는 복사되고, 컨트롤러의 이름에 맞는 키로 감싸지게 됩니다. 따라서 위의 JSON 요청은 아래와 같이 처리됩니다.

{ "name": "acme", "address": "123 Carrot Street" }

데이터를 전송한 곳이 CompaniesController라고 가정하면, 아래와 같이 :company라는 키로 감싸집니다.

{ name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } }

키의 이름을 변경하거나, 특정 매개 변수를 감싸고 싶은 경우에는 API 문서를 참조해주세요.

XML 매개 변수 해석을 도와주던 코드는 actionpack-xml_parser라는 잼으로 분리되었습니다.

4.3 라우팅 매개 변수

params 해시는 :controller와 :action를 반드시 포함합니다. 단, 이 값에는 직접 사용하는 대신, controller_name과 action_name이라는 전용 메소드를 사용해주세요. 라우팅에 정의된 다른 값(id등)에도 접근할 수 있습니다. 예를 들어, "유효" 또는 "무효"로 표기되는 고객 리스트를 생각해봅시다. "보기 좋은" URL에 포함되는 :status 매개 변수를 가져오기 위해 다음과 같은 라우트를 하나 추가합시다.

get '/clients/:status' => 'clients#index', foo: 'bar'

이 경우, 브라우저에서 /clients/active라는 URL에 접근하면, params[:status]가 "active"(유효)로 설정됩니다. 이 라우팅을 사용하면 넘겨진 쿼리 문자열은 당연히 params[:foo]에 "bar"로 설정됩니다. 마찬가지로 params[:action]에는 "index"가, 그리고 params[:controller]에는 "clients"가 포함됩니다.

4.4 default_url_options

컨트롤러에서 default_url_options라는 이름의 메소드를 정의하면, URL 생성용 전역 기본 매개 변수를 설정할 수 있습니다. 이러한 메소드는 필요한 기본값을 가지는 해시를 반환해야 하며, 키로 심볼을 사용해야 합니다.

class ApplicationController < ActionController::Base
  def default_url_options
    { locale: I18n.locale }
  end
end

이런 옵션은 URL 생성의 시작점으로 사용할 수 있으며, url_for 호출에 넘겨지는 옵션으로 덮어쓸 수 있습니다.

ApplicationController에서 default_url_options을 정의하면 위의 예시에서 볼 수 있듯 모든 URL 생성시에 사용하게 됩니다. 이 메소드를 특정 컨트롤러에서 정의하면 그 컨트롤러에서 생성되는 URL에만 영향을 미치게 됩니다.

주어진 요청에서 이 메소드는 생성된 모든 URL에서 각각 호출되지는 않습니다. 성능 상의 이유로 반환되는 해시는 캐싱되어 있으며, 요청당 많아야 한번 호출됩니다.

4.5 Strong Parameters

Strong parameters를 사용하면 액션 컨트롤러가 받은 매개 변수를 화이트리스트로 검증하기 전에는 Active Model에 통째로 넘길 수 없게 됩니다. 이것은 여러 속성을 한번에 갱신하고 싶을 때에 어떤 속성의 갱신을 허가하고, 또다른 속성의 갱신을 금지할 지 명시적으로 결정해야 한다는 의미입니다. 이는 사용자가 보안 상의 이유로 변경해서는 안되는 속성을 실수로 변경할 수 없게끔 방지하기 위한 방책입니다.

나아가 매개 변수의 속성에는 '필수(required)'를 지정할 수 있으며, 사전에 정의해둔 raise/rescue를 실행하여 400 Bad Request를 돌려줄 수도 있습니다.

class PeopleController < ActionController::Base
  # 이 코드는 ActiveModel::ForbiddenAttributesError 예외를 던집니다.
  # 명시적으로 검증을 하지 않고 매개 변수를 그냥 통째로 넘기고 있기 때문입니다.
  def create
    Person.create(params[:person])
  end
 
  # 이 코드는 매개 변수에 person이라는 키가 존재하는 경우에만 성공합니다.
  # person이라는 키가 없는 경우에는 ActionController::ParameterMissing 예외를 던집니다.
  # 이 예외는 ActionController::Base가 잡아 400 Bad Request로 반환합니다.
  def update
    person = current_account.people.find(params[:id])
    person.update!(person_params)
    redirect_to person
  end
 
  private
    # private 메소드를 사용해서 매개 변수 검증을 캡슐화합니다.
    # 이를 통해 create와 update에서 같은 검증을 쉽게 재사용할 수 있습니다.
    # 또한 허가할 속성을 사용자마다 다르게 만들 수도 있습니다.
    def person_params
      params.require(:person).permit(:name, :age)
    end
end
4.5.1 허가된 값

다음의 예제에서는,

params.permit(:id)

:id 키가 params에 들어있으며 허가된 형식의 값이 들어있다면 화이트리스트 검증을 통과할 수 있습니다. 그렇지 않으면 그 값은 필터에 의해 제거됩니다. 따라서 배열이나 해시, 그 이외의 객체를 외부에서 주입할 수 없게 됩니다.

허가된 형식은 StringSymbolNilClassNumericTrueClassFalseClassDateTimeDateTimeStringIOIOActionDispatch::Http::UploadedFileRack::Test::UploadedFile입니다.

params의 값이 허가된 형식의 배열이어야 한다고 선언하려면, 아래와 같이 빈 배열을 매핑하면 됩니다.

params.permit(id: [])

매개 변수 해시 전체를 화이트리스트로 만들고 싶은 경우에는 permit! 메소드를 사용할 수 있습니다.

params.require(:log_entry).permit!

이렇게 작성하면, :log_entry 매개 변수 해시와 그 내부의 모든 값들을 허가하게 됩니다. 단, permit!은 가진 속성을 모두 허가하게 되므로, 신중하게 사용해주세요. 현재 모델은 물론, 나중에 속성이 추가되더라도 일괄 할당되기 때문입니다.

4.5.2 중첩된 매개 변수

중첩된 매개 변수에 대해서도 아래와 같이 검증할 수 있습니다.

params.permit(:name, { emails: [] },
              friends: [ :name,
                         { family: [ :name ], hobbies: [] }])

이 선언에서는 nameemailsfriends 속성이 화이트리스트에 포함됩니다. 여기에서는 emails는 허가된 형식들을 포함하는 배열이기를, friends는 특정 속성을 가지는 리소스의 배열이길 요구하고, 어느 쪽이든 name 속성(사용 가능한 형식일 경우에만)을 가져야 합니다. 또한 hobbies와 family를 요구합니다.

4.5.3 추가 예제

이번에는 new 액션에서 검증된 속성을 사용해봅시다. 하지만 new를 호출하는 시점에서는 이를 사용할 객체가 존재하지 않으므로 require를 사용할 대상이 없다는 문제가 있습니다.

# `fetch`를 사용해서 기본 값을 제공하여
# Strong Parameters API를 사용할 수 있습니다.
params.fetch(:blog, {}).permit(:title, :author)

accepts_nested_attributes_for 메소드를 사용하면, 관계가 맺어진 레코드를 갱신하거나 삭제할 수 있습니다. 이 동작은 id와 _destroy 매개 변수를 사용합니다.

# :id와 :_destroy를 허가합니다.
params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy])

정수 키를 가지는 해시는 다른 방식으로 처리됩니다. 이것들은 자식 객체를 가지고 있는 것처럼 선언할 수 있습니다. 이러한 종류의 매개 변수는 has_many 관계와 함께 accepts_nested_attributes_for메소드를 사용할 때 가져올 수 있습니다.

# 아래의 데이터를 화이트리스트로 만들기
# {"book" => {"title" => "Some Book",
#             "chapters_attributes" => { "1" => {"title" => "First Chapter"},
#                                        "2" => {"title" => "Second Chapter"}}}}
 
params.require(:book).permit(:title, chapters_attributes: [:title])
4.5.4 Strong Parameters의 스코프 외부

strong parameter API는 가장 일반적인 사용 상황을 고려하여 설계되어 있습니다. 다시 말해, 화이트리스트를 사용하는 모든 문제를 다룰 수 있을 정도로 만능은 아니라는 의미입니다. 그러나 이 API를 사용하여 상황에 대응하기 쉬워질 수는 있을 것입니다.

다음과 같은 상황을 가정해봅시다. 제품명과 그 제품명에 관련된 임의의 데이터를 표현하는 매개 변수가 있으며, 그 모두를 화이트리스트로 만들고 싶습니다. strong parameter API는 임의의 키를 가지는 중첩된 해시 전체를 직접 화이트리스트로 만들 수는 없습니다만, 중첩된 해시의 키를 사용해서 화이트리스트로 만들 대상을 선언할 수 있습니다.

def product_params
  params.require(:product).permit(:name, data: params[:product][:data].try(:keys))
end




2단계 : users/show.html.erb로 유저 정보 보여주기


Controller에서 받아온 @user를 show.html에서 사용합니다.


<h1 align="center">Welcome to <%= @user.username %>'s page</h1>
<div class="row">
<div class="col-md-4 col-md-offset-4 center">
<%= gravatar_for @user, size: 150 %>
</div>
</div>
<h4 align="center"><%= @user.username %>'s articles</h4>
<%= render 'articles/article', obj: @user.articles %>


@user의 데이터를 이용해 View 페이지에 보여줍니다. 그 중에서도 사용자의 프로필 이미지를 보여주는 것은 gravatar_for 메서드입니다. 메서드 같아 보이지 않지만 괄호가 생략되고, 파라메터가 2개인 메서드입니다.



gravatar_for는 ApplicationHelper에 구현되어 있는 메서드입니다.

module ApplicationHelper
def gravatar_for(user, options = {size: 80})
# Digest 해쉬로 파라메터로 넘겨 받은 유저의 이메일을 id로 함.
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
# 파라메터로 넘긴 size를 받아온다.
size = options[:size]
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
# 이미지 태그를 반환한다.
image_tag(gravatar_url, alt: user.username, class:"img-circle")
end
end


user의 이메일 주소와 사진 사이즈를 이용해서 img tag를 만들어서 다시 메서드를 호출한 곳으로 리턴합니다.


<%= render 'articles/article', obj: @user.articles %> 

이 코드는 articles 폴더에 있는 article.html를 include 한다는 문장입니다. 이것을 Partial 뷰라고 하는데 Partial뷰의 이름은 언더바(_)를 붙입니다. 그래서 articles 폴더에 _article.html.erb로 저장되어 있습니다.


<% obj.each do |article| %>

<div class="row">

<div class="col-xs-8 col-xs-offset-2">

<div class="well well-lg">

<div class="article-title">

<%= link_to article.title, article_path(article) %>

</div>

<div class="article-body">

<%= truncate(article.description, length: 100) %>
<div class="article-meta-details">

<small>Created by: <%= article.user.username if article.user%>,

users 폴더의 show.html에서 보낸 obj:@user.article 객체를 받아서 위와 같이 article 관련 데이터를 뷰에 보여줍니다. 그리고 이 코드는 show.html에 include 되어 클라이언트에게 보여집니다.



결과 페이지는 다음과 같이 보여집니다.



이상으로 포스팅을 마치겠습니다.








댓글