Posted at

基礎Ruby on Rails #26(最終回) Chapter15 名前空間

More than 1 year has passed since last update.

基礎Ruby on Rails #25 Chapter14 多対多の関連付け

次 終了


名前空間付きのルーティングとコントローラ


名前空間を導入する理由


  • 一般会員、管理者で、ユーザー種類ごとに別々のコントローラを用意する。

  • Admin::MembersControllerのようなコントローラ作成する。このとき、Adminはモジュール名。

  • controllersのイメージ


    • admin


      • top_controller・・・ 管理者用トップページ

      • members_controller・・・会員管理ページ

      • articles_controller・・・記事管理ページ



    • top_controller・・・一般用トップページ

    • members_controller・・・会員表示ページ

    • articles_controller・・・記事表示ページ




管理TOPページへのルーティング設定



  • /admintop#indexへのルーティングを設定する。


config/routes.rb(一部)

  namespace :admin do

root "top#index"
end


  • 上記パスに飛ぶリンクを作成する方法は以下の通り


    • link_to "管理ページ", :admin_root

    • link_to "管理ページ", [:admin, :root]




Admin::Baseクラス


  • 全ての管理者用クラスの親クラスAdmin::Baseを作成する。

  • 全てのこのクラスの継承クラスで認証を行うためのメソッドadmin_login_requiredを追加する。エラーはForbidden。


app/controllers/admin/base.rb

class Admin::Base < ApplicationController

before_action :admin_login_required

private def admin_login_required
raise Forbidden unless current_member&.administrator?
end
end



Admin::TopController


  • Admin::TopControllerと、indexを作成する。

$ bin/rails g controller admin/top index

create app/controllers/admin/top_controller.rb
invoke erb
create app/views/admin/top
create app/views/admin/top/index.html.erb


  • 継承元親クラスApplicationControllerAdmin::Baseに書き換える。


app/controllers/admin/top_controller.rb

class Admin::TopController < Admin::Base

def index
end
end


  • indexのビューを作成する。


app/views/admin/top/index.html.erb

<% @page_title = "管理ページトップ" %>

<ul>
<li><%= link_to "会員管理", "#" %></li>
<li><%= link_to "ニュース記事管理", "#" %></li>
</ul>


  • 管理用なのか一目でわかるようにメニューバーを切り替える。

  • 一般用のメニューバーである新規ファイル_menubar.html.erbを作成する。


app/views/shared/_menubar.html.erb

<nav class="menubar">

<ul>
<%= menu_link_to "TOP", :root %>
<%= menu_link_to "ニュース", :articles %>
<%= menu_link_to "ブログ", :entries %>
<% if current_member %>
<%= menu_link_to "会員名簿", :members %>
<% if current_member.administrator? %>
<%= menu_link_to "管理ページ", :admin_root %>
<% end %>
<% end %>
</ul>
</nav>


  • 管理者用のメニューバーを新規作成する。


app/views/shared/_admin_menubar.html.erb

<nav class="menubar" id="admin-menubar">

<ul>
<%= menu_link_to "管理TOP", :admin_root %>
<%= menu_link_to "会員管理", "#" %>
<%= menu_link_to "ニュース記事管理", "#" %>
<%= menu_link_to "TOP", :root %>
</ul>
</nav>


  • コントローラの種類によって、メニューバーを切り替える。


app/views/shared/_header.html.erb(一部)

<%=

if controller.kind_of?(Admin::Base)
render "shared/admin_menubar"
else
render "shared/menubar"
end
%>


  • 管理者用のメニューバーの色を変えるために、CSSを追加する。


app/assets/stylesheets/admin.css

nav#admin-menubar {

background-color: #800;
}


  • 管理者用のメニューバーは赤く表示された。

image.png


  • Jiroでアクセスすると403エラーになることを確認。

image.png


会員管理ページの作成


ルーティング設定


  • MembersControllerではindexとshow以外のアクションを廃止するために、only: [:index, :show]を追加する。


config/routes.rb(一部)

  resources :members, only: [:index, :show] do

get "search", on: :collection
resources :entries, only: [:index]
end


  • Admin::MembersControllerへのルーティングを追加する。


config/routes.rb(一部)

  namespace :admin do

root "top#index"
resource :members do # 以下を追加する
get "search", on: :collection
end
end


  • 以上の設定変更により、新たなルーティングが次のように設定される。

アクション
パス
HTTPメソッド
パスを返すメソッド

index
/admin/members
GET
admin_members_path

show
/admin/members/123
GET
admin_members_path(member)

new
/admin/members/new
GET
new_admin_member_path

edit
/admin/members/123/edit
GET
edit_admin_members_path(member)

create
/admin/members
POST
admin_members_path

update
/admin/members/123
PATCH
admin_members_path(member)

destroy
/admin/members/123
DELETE
admin_members_path(member)

search
/admin/members/search
GET
search_admin_members_path


  • 管理者向けメニューバーのテンプレートに会員管理ページへのリンクを追加する。


app/views/shared/_admin_menubar.html.erb(一部)

    <%= menu_link_to "会員管理", :admin_members %>



  • 管理トップページのテンプレートにも会員管理ページへのリンクを追加する。


app/views/shared/_admin_menubar.html.erb(一部)

  <li><%= link_to "会員管理", :admin_members %></li>



Admin::MembersControllerの作成


  • 今までのmembersのcontrollerとviewのファイルを全てadminにコピーする。


    • app/controllersディレクトリの下のmembers_controller.rbをコピーして、app/controllers/adminディレクリにコピーする

    • app/views/membersディレクトリをまるごとコピーして、app/views/adminディレクトリの下に貼り付ける。
      #



  • MembersControllerに名前空間と親クラスを変更して、before_actionを削除する。

  • create、update、destroyアクションの中にあるリダイレクト先を名前空間付きのものに変更する。


app/controllers/admin/members_controller.rb(一部)

#変更前

class MembersController < ApplicationController
before_action :login_required
#変更後
class Admin::MembersController < Admin::Base

# create
redirect_to [:admin, @member], notice: "会員を登録しました。"
# update
redirect_to [:admin, @member], notice: "会員を更新しました。"
# destroy
redirect_to :admin_members, notice: "会員を削除しました。"



会員管理ページ用のテンプレート修正


  • パスの指定を名前空間付きのものに変更する。

  • アクションのシンボルが名前空間を表すシンボルよりも前に来る点に注意すること。


app/views/admin/members/index.html.erb(一部)

<!-- 変更前 -->

<% @page_title = "会員名簿" %>
<!-- 変更後 -->
<% @page_title = "会員管理" %>

<!-- 変更前 -->
<div class="toolbar"><%= link_to "会員の新規登録", :new_member %></div>
<!-- 変更後 -->
<div class="toolbar"><%= link_to "会員の新規登録", :new_admin_member %></div>

<!-- 変更前 -->
<td><%= link_to member.name, member %></td>
<!-- 変更後 -->
<td><%= link_to member.name, [:admin, member] %></td>

<!-- 変更前 -->
<td><%= link_to "編集", [:edit, member] %>
<%= link_to "削除", member, method: :delete, data:{ confirm: "☺本当に削除しますか❓" } %></td>
<!-- 変更後 -->
<td><%= link_to "編集", [:edit, :admin, member] %>
<%= link_to "削除", [:admin, member], method: :delete, data:{ confirm: "☺本当に削除しますか❓" } %></td>



app/views/admin/members/show.html.erb(一部)

<!-- 変更前 -->

<div class="toolbar"><%= link_to "編集", [:edit, @member] %></div>
<!-- 変更後 -->
<div class="toolbar"><%= link_to "編集", [:edit, :admin, @member] %></div>


  • 一般向けのshowアクションのテンプレートでは「編集」リンク自体を削る。


app/views/members/show.html.erb

<% @page_title = "会員の詳細" %>

<h1><%= @page_title %></h1>

<!-- 削除 <div class="toolbar"><%= link_to "編集", [:edit, @member] %></div> -->

<%= render "body" %>



  • adminのnew、editアクションでは、リンクに:adminを追加する。


app/views/admin/members/new.html.erb

<!-- 変更前 -->

<%= form_for @member do |form| %>
<!-- 変更後 -->
<%= form_for [:admin, @member] do |form| %>


  • adminのnew、editアクションでは、リンクに:adminを追加する。


app/views/admin/members/edit.html.erb

<!-- 変更前 -->

<div class="toolbar"><%= link_to "会員の詳細に戻る", @member %></div>

<%= form_for @member do |form| %>
<!-- 変更後 -->
<div class="toolbar"><%= link_to "会員の詳細に戻る", [:admin, @member] %></div>

<%= form_for [:admin, @member] do |form| %>



  • adminのnew、editアクションでは、リンクに:adminを追加する。


app/views/shared/_member_form.html.erb(一部)

<!-- 変更前 -->

<% if controller.kind_of?(MembersController) %>
<!-- 変更後 -->
<% if controller.kind_of?(Admin::MembersController) %>


  • 一覧、編集、削除が動作することを確認した。

image.png


MembersControllerの修正


  • 一般会員向けのMembersControllerにはindex、show、searchの3つのアクションだけ残す。


    • index、show、search以外のアクションを消す。ストロング・パラメータも消す。

    • viewからnew、editを削除する。




  • app/views/members/index.html.erb


    • 一覧の新規登録<div class="toolbar"><%= link_to "会員の新規登録", :new_member %></div>を消す。

    • 一覧の操作列を消す。(編集|削除リンクも消す)



  • 一般向け画面では、閲覧しかできないことを確認した。


image.png


ニュース記事管理ページの作成


  • ニュース記事も会員と同様に管理画面をAdminに持っていく。


ルーティングの設定



  • resources :articlesに、only: [:index, :show]を付加する。


  • namespace :admin do内に、resources :articlesを追加する。


config/routes.rb(一部)

  resources :articles, only: [:index, :show]  # 追加

#(省略)
namespace :admin do
root "top#index"
resources :members do
get "search", on: :collection
end
resources :articles # 追加
end
end



  • 管理者向けメニューバーのテンプレートにニュース記事管理ページへのリンクを追加する。


app/views/shared/_admin_menubar.html.erb(一部)

    <%= menu_link_to "ニュース記事管理", :admin_articles %>



  • 管理トップページのテンプレートにもニュース記事管理ページへのリンクを追加する。


app/views/admin/top/index.html.erb(一部)

  <li><%= link_to "ニュース記事管理", :admin_articles %></li>



Admin::ArticlesControllerの作成


  • app/controllersディレクトリの下のarticle_controller.rbをコピーして、app/controller/adminディレクトリの下に貼り付ける。

  • app/views/articlesディレクトリをまるごとコピーして、app/views/adminディレクトリの下に貼り付ける。


  • クラスの定義に名前空間をつける。

  • index、show、create、update、destroyアクションを書き換える


app/controllers/admin/articles_controller.rb(一部)

# 変更前

class ArticlesController < ApplicationController
# 変更後
class Admin::ArticlesController < Admin::Base

# 変更前
def index
@articles = Article.order(released_at: :desc)

@articles = @articles.open_to_the_public unless current_member
unless current_member&.administrator?
@articles = @articles.visible
end

@articles = @articles.page(params[:page]).per(5)
end
# 変更後
def index
@articles = Article.order(released_at: :desc).page(params[:page]).per(5)
end

# 変更前
def show
articles = Article.all

articles = articles.open_to_the_public unless current_member

unless current_member&.administrator?
articles = articles.visible
end
@article = articles.find(params[:id])
end
# 変更後
def show
@article = Article.find(params[:id])
end

# 変更前 create、update、destroy
redirect_to @article, notice: "ニュース記事を登録しました。"
redirect_to @article, notice: "ニュース記事を更新しました。"
redirect_to :articles, notice: "ニュース記事を削除しました。"
# 変更後
redirect_to [:admin, @article], notice: "ニュース記事を登録しました。"
redirect_to [:admin, @article], notice: "ニュース記事を更新しました。"
redirect_to :admin_articles, notice: "ニュース記事を削除しました。"



記事管理用のテンプレート修正


  • indexテンプレートの書き換え


app/views/admin/articles/index.html.erb(一部)

<!-- 変更前 -->

<div class="toolbar"><%= link_to "新規作成", :new_article %></div>
<!-- 省略 -->
<td><%= link_to article.title, article %></td>
<!-- 省略 -->
<%= link_to "編集", [:edit, article] %> |
<%= link_to "削除", article, method: :delete, data: { confirm: "本当に削除しますか?" } %>
<!-- 変更後 -->
<div class="toolbar"><%= link_to "新規作成", :new_admin_article %></div>
<!-- 省略 -->
<td><%= link_to article.title, [:admin, article] %></td>
<!-- 省略 -->
<%= link_to "編集", [:edit, :admin, article] %> |
<%= link_to "削除", [:admin, article], method: :delete, data: {confirm: "本当に削除しますか?"} %>


  • showテンプレートの書き換え


app/views/admin/articles/show.html.erb(一部)

<!-- 変更前 -->

<div class="toolbar"><%= link_to "編集", [:edit, @article] %></div>
<!-- 変更後 -->
<div class="toolbar"><%= link_to "編集", [:edit, :admin, @article] %></div>


  • newテンプレートの書き換え


app/views/admin/articles/new.html.erb(一部)

<!-- 変更前 -->

<%= form_for @article do |form| %>
<!-- 変更後 -->
<%= form_for [:admin, @article] do |form| %>


  • editテンプレートの書き換え


app/views/admin/articles/edit.html.erb(一部)

<!-- 変更前 -->

<p><%= link_to "記事の詳細に戻る", @article %></p>

<%= form_for @article do |form| %>
<!-- 変更後 -->
<p><%= link_to "記事の詳細に戻る", [:admin, @article] %></p>

<%= form_for [:admin, @article] do |form| %>



  • 管理者向けニュース一覧

image.png


ArticlesControllerの修正


  • 全ユーザー向けのArticlesControllerにはindexアクションとshowアクションだけを残す。

  • app/views/articleのnew,edit,_formの3つのビューを削除する。


app/controllers/articles_controller.rb

# 変更前

class ArticlesController < ApplicationController
before_action :login_required, except: [:index, :show]

def index
@articles = Article.order(released_at: :desc)

@articles = @articles.open_to_the_public unless current_member
unless current_member&.administrator?
@articles = @articles.visible
end

@articles = @articles.page(params[:page]).per(5)
end

def show
articles = Article.all

articles = articles.open_to_the_public unless current_member

unless current_member&.administrator?
articles = articles.visible
end
@article = articles.find(params[:id])
end
end

# 変更後
class ArticlesController < ApplicationController
before_action :login_required, except: [:index, :show]

def index
@articles = Article.visible.order(released_at: :desc)
@articles = @articles.open_to_the_public unless current_member
@articles = @articles.page(params[:page]).per(5)
end

def show
articles = Article.visible
articles = articles.open_to_the_public unless current_member
@article = articles.find(params[:id])
end
end



app/views/articles/index.html.erb

<% @page_title = "ニュース一覧" %>

<h1><%= @page_title %></h1>

<% if @articles.present? %>
<% @articles.each do |article| %>
<h2><%= article.title %></h2>
<p>
<%= truncate(article.body, length: 80) %>
<%= link_to "もっと読む", article %>
</p>
<div class="article-footer">
<%= article.released_at.strftime("%Y/%m/%d %H:%M") %>
</div>
<% end %>
<% else %>
<p>ニュースがありません。</p>
<% end %>



app/views/articles/show.html.erb

<% @page_title = @article.title %>

<h1><%= @article.title %></h1>

<%= simple_format(@article.body) %>

<div class="article-footer">
<%= @article.released_at.strftime("%Y/%m/%d %H:%M") %>
</div>



app/assets/stylesheets/articles.css

div.article-footer {

border-top: 1px #ccf dashed;
padding-top: 4px;
margin-bottom: 8px;
text-align: right;
font-size: 75%;
}


  • 一般向けニュース一覧

image.png


  • 一般向けニュース詳細

image.png


まとめ


  • 管理ページを作るには名前空間付きのコントローラを作成する。

  • 最後の方はなかなか難しかったので、整理して理解できるようにしたい。

参考

改訂4版 基礎Ruby on Rails 基礎シリーズ