categories 컨트롤러 뷰 템블릿 파일

블로그의 글들은 적절하게 분류해서 방문자들이 보다 편리하게 모아 볼 수 있도록 해야 한다.

현재, 글을 작성할 때 카테고리를 선택할 수 있도록 구현해 놓은 상태이기 때문에, 카테고리에서 사용할 데이터를 관리하는 기능을 구현해야 한다. 이 기능은 :admin:author 권한을 가진 유저만 사용할 수 있도록 하자. 글을 작성할 때 적절할 카테고리가 없을 경우 새로운 카테고리를 추가할 수 있어야 한다.

categories 컨트롤러 파일을 열고 상단에 로그인 상태(인증)를 점검하기 위해 before_action :authenticate_user!를 추가한다.

그리고, :admin 권한을 가진 사용자만 이 컨트롤러에 접근할 수 있도록 authorize_actions_for Category를 추가한다.

또한 create 액션에는 현재 로그인 사용자의 id가 자동으로 입력되도록 하기 위해 Category.newcurrent_user.categories.new로 변경한다.

class CategoriesController < ApplicationController before_action :authenticate_user! before_action :set_category, only: [:show, :edit, :update, :destroy] authorize_actions_for Category ... def create @category = current_user.categories.new(category_params) ... end end

app/views/categories/_form.html.erb 파일을 열고 불필요한 user_id 속성에 대한 입력부분을 삭제한다.

<%= simple_form_for(@category) do |f| %> <%= f.error_notification %> <div class="form-inputs"> <%= f.input :name %> </div> <div class="form-actions"> <%= f.button :submit, class: 'button small' %> </div> <% end %>

app/views/categories/edit.html.erb 파일을 열고 아래와 같이 변경한다.

<h2>Editing category</h2> <%= render 'form' %> <hr> <%= link_to 'Show', @category, class: 'button small' %> <%= link_to 'Back', categories_path, class: 'button small' %>

app/views/categories/new.html.erb 파일을 열고 아래와 같이 변경한다.

<h2>New category</h2> <%= render 'form' %> <hr> <%= link_to 'Back', categories_path, class: 'button small' %>

app/views/categories/index.html.erb 파일을 열고 아래와 같이 변경한다.

<h2>Listing categories</h2> <table style='width:100%;'> <thead> <tr> <th style='width:40%;'>Name</th> <th>User</th> <th>Actions</th> </tr> </thead> <tbody> <% @categories.each do |category| %> <tr> <td><%= category.name %></td> <td><%= category.user.email %></td> <td> <%= link_to icon('eye'), category %> <%= link_to icon('pencil'), edit_category_path(category) %> <%= link_to icon('trash'), category, method: :delete, data: { confirm: 'Are you sure?' } %> </tr> <% end %> </tbody> </table> <br> <%= link_to 'New Category', new_category_path, class: 'button small' %>

app/views/categories/show.html.erb 파일을 열고 아래와 같이 변경한다.

<h2>Preview of Category</h2> <p> <strong>User:</strong> <%= @category.user.email %> </p> <p> <strong>Name:</strong> <%= @category.name %> </p> <hr> <%= link_to 'Edit', edit_category_path(@category), class: 'button small' %> <%= link_to 'Back', categories_path, class: 'button small' %>

다음은 위의 캡쳐된 화면에서와 같이 sidebar1Category 부분을 표시하기 위해서 app/layoutes/general_layout.html.erb 파일을 열고 아래와 같이 변경한다.

... <p>Category : <small><%= link_to "Admin", categories_path if user_signed_in? && current_user.has_role?(:admin) %></small></p> <ul> <li> <%= link_to 'Uncategoried', posts_path(category_id: 0) %> <small><%= Post.uncategorized_posts.size %></small> </li> <% Category.all.each do | category | %> <li> <%= link_to category.name, category_posts_path(category) %> <small><%= category.posts.published_posts.size %></small> </li> <% end %> ... </ul>

위의 코드 상단에서, admin 권한을 가진 사용자가 로그인한 경우 Admin이라는 링크(캡쳐화면의 2번)를 표시하도록 한 것을 주목하기 바란다. 그리고 각 카테고리 항목 옆에는 해당 카테고리로 분류되는 글의 갯수를 표시(캡쳐화면의 3번)하도록 했다.

브라우저에서 확인하기 위해서는 새로운 사용자를 등록하고 레일스 콘솔에서 :admin 권한을 추가한다.

$ bin/rails c Loading development environment (Rails 4.1.1) irb(main):001:0> admin_user = User.create! email: '[email protected]', password: '12345678', confirmed_at: Time.now irb(main):002:0> admin_user.roles.map(&:name) => [] irb(main):003:0> admin_user.add_role :admin => #<Role id: 4, name: "admin", resource_id: nil, resource_type: nil, created_at: "2014-06-26 03:42:54", updated_at: "2014-06-26 03:42:54"> irb(main):004:0> admin_user.roles.map(&:name) => ["admin"]

그리고 Post 모델 클래스에서 미분류 카테고리에 대한 scope를 추가로 작성한다.

... scope :uncategorized_posts, -> { published_posts.where(category_id: nil) } ...

각 카테고리에는 해당 카테고리의 posts들을 보여주기 위한 링크가 연결되어 있다. 이를 위한 중첩된 리소스 라우팅을 위해서는 config/routes.rb 파일을 열고 아래와 같이 변경한다.

Rails.application.routes.draw do root 'posts#index' devise_for :users resources :categories do resources :posts end resources :posts do get 'list_my', on: :collection resources :comments end end

이제 [email protected]으로 로그인한 후 위에서 작업한 내용이 제대로 반영되었는지 확인한다.

Category 우측에 있는 Admin 링크를 클릭하여 데모를 위한 카테고리를 몇개 추가한다.

이제 위 캡쳐화면에서 1번 카테고리 링크를 클릭하여 왼쪽에 해당 카테고리의 posts들이 보이게 되는데, 2과 같이 카테고리 이름을 표시하도록 한다.

이를 위해서 app/views/posts/index.html.erb 파일을 열고 아래와 같이 변경한다.

<% unless @category_name.blank? %> <div class='category'> <strong>Category:</strong> <%= @category_name %> </div> <% end %> <div id='posts'> <% if @posts.size > 0 %> <%= render @posts %> <% else %> <h3 id='no_posts'>아직 작성된 글이 없습니다.</h3> <% end %> </div>

상단에서 사용한 @category_nameposts#index 액션에서 인스턴스 변수로 선언한 것이다.

@category_name = params[:category_id] == '0' ? "Uncategorized" : (@category ? @category.name : "")

위의 코드에서 보는 바와 같이, 미분류된 post들, 즉, category_id 값이 없는 post 객체들을 보기 위한 링크(Uncategorized)를 작성할 때 사용한 posts_path() 경로 헬퍼메소드에는 category_id: 0와 같이 파라메터를 지정해서 posts#index 액션에서 이를 위한 구분을 할 수 있도록 한다.

Category 별로 posts를 보기 위해서는 posts 컨트롤러를 아래와 같이 수정한다.

class PostsController < ApplicationController before_action :authenticate_user!, except: [ :index, :show ] before_action :set_category before_action :set_post, only: [:show, :edit, :update, :destroy] authorize_actions_for Post, only: [ :new, :create ] # GET /posts # GET /posts.json def index if @category @posts = @category.posts.published_posts else if params[:category_id] == '0' @posts = Post.uncategorized_posts else @posts = Post.published_posts end end @category_name = params[:category_id] == '0' ? "Uncategorized" : (@category ? @category.name : "") end def list_my @myposts = Post.myposts(current_user) end # GET /posts/1 # GET /posts/1.json def show end # GET /posts/new def new @post = Post.new end # GET /posts/1/edit def edit authorize_action_for @post end # POST /posts # POST /posts.json def create @post = current_user.posts.new(post_params) respond_to do |format| if @post.save format.html { redirect_to @post, notice: 'Post was successfully created.' } format.json { render :show, status: :created, location: @post } else format.html { render :new } format.json { render json: @post.errors, status: :unprocessable_entity } end end end # PATCH/PUT /posts/1 # PATCH/PUT /posts/1.json def update authorize_action_for @post respond_to do |format| if @post.update(post_params) format.html { redirect_to @post, notice: 'Post was successfully updated.' } format.json { render :show, status: :ok, location: @post } else format.html { render :edit } format.json { render json: @post.errors, status: :unprocessable_entity } end end end # DELETE /posts/1 # DELETE /posts/1.json def destroy authorize_action_for @post @post.destroy respond_to do |format| format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' } format.json { head :no_content } end end private def set_category @category = Category.find(params[:category_id]) if params[:category_id] && params[:category_id] != '0' end # Use callbacks to share common setup or constraints between actions. def set_post if @category @post = @category.posts.find(params[:id]) else @post = Post.find(params[:id]) end end # Never trust parameters from the scary internet, only allow the white list through. def post_params params.require(:post).permit(:category_id, :user_id, :title, :content, :published) end end

마지막으로 app/views/posts/show.html.erb 파일을 열고 아래와 같이 변경한다.

<div class='post'> <div class='category'> <strong>Category:</strong> <%= @post.category ? @post.category.name : "Uncategorized" %> </div> ... </div>

이상으로 카테고리 부부에 대한 설명을 마치도록 하겠다. 다음은 각 post 별로 tag를 달 수 있도록 해 보자.

지금까지 작업한 내용을 로컬 저장소로 커밋한다.

$ git add . $ git commit -m "제07장 : categories 컨트롤러 뷰 템블릿 파일" $ git tag "제07장"


소스보기 https://github.com/luciuschoi/foundblog_app/tree/제07장