#333 Extending Refinery CMS pro
- Download:
- source codeProject Files in Zip (1.54 MB)
- mp4Full Size H.264 Video (40.5 MB)
- m4vSmaller H.264 Video (19.1 MB)
- webmFull Size VP8 Video (21.2 MB)
- ogvFull Size Theora Video (44.4 MB)
今回のエピソードでは引き続きRefinery CMSを見ていきます。もし前回のエピソードを見ていない場合は、まずそちらを参照してここまでのところを理解しておいてください。前回のエピソードではRefineryを使用してピアノ販売店のサイトを作成しました。サイトには見栄えのいいトップページと店舗の所在地に関する情報を示すページがありました。このサイトに、店舗で販売しているピアノのリストをブラウズできる別のセクションを追加したいと思います。
ピアノのリストを単に表示するだけの静的なページにするのではなく、それらをデータベースに保存し、Refineryのadminセクションで管理しようと思います。そのためにadminページにデフォルトで表示されているタブに加えて、ピアノのレコードを管理するためのタブを1つ追加します。これをおこなうためには、Refineryの内部がどのような仕組みになっているのかを理解することが役に立ちます。adminセクションの各タブはそれぞれが別のRailsエンジンで実装されていて、管理側の情報とユーザにその情報を表示する公開側の両方を処理します。これはRefineryのソースコードを見るとわかります。例えばpages
、images
、dashboard
のような、Railsエンジンを含むディレクトリがいくつかあります。authenticationディレクトリには“Users”タブが含まれ、resourcesディレクトリには“Files”タブが含まれています。これらのディレクトリのそれぞれにRailsエンジンがあります。
カスタムのエンジンを追加する
このアプローチによってRefineryは非常にモジュラー性が高く、独自のエンジンを追加して簡単に拡張できるということも意味しています。それを今回のエピソードで実践していきます。ピアノの管理とそれらをユーザに表示するために新規のセクションを作成します。Refineryにはこれをおこなうためのジェネレータがあります。これはscaffoldジェネレータに似ていて、コマンドに続いてリソースの名称と属性のリストを指定します。これを使用してpiano
エンジンを作成します。
$ rails g refinery:engine piano name:string dimensions:string manufactured_on:date upright:boolean photo:image description:text
フィールドに渡す型は、もしリソースやscaffoldを生成したことがあれば見覚えがあるでしょう。ここでの唯一の違いは、各レコードに画像を含めたい場合にimage
型を使用するとRefineryが自動的にこれらの画像を処理してくれるという点です。このコマンドによってエンジンが生成され、vendor/extensions/pianos
ディレクトリの下に置かれます。またこのコマンドは実行すべきコマンドのリストを表示します。
------------------------ Now run: bundle install rails generate refinery:pianos rake db:migrate rake db:seed ------------------------
その前にアプリケーションのgemfileに加えられた変更を見てみましょう。
gem 'refinerycms-i18n', '~> 2.0.0' # gem 'refinerycms-blog', '~> 2.0.0' # gem 'refinerycms-inquiries', '~> 2.0.0' # gem 'refinerycms-search', '~> 2.0.0' # gem 'refinerycms-page-images', '~> 2.0.0' gem 'refinerycms-pianos', :path => 'vendor/extensions'
このファイルの末尾に追加された1行には、vendor/extensions
へのパスが指定されたgemが含まれています。これの興味深いところは、コマンドがエンジンを生成しただけでなく、それをアプリケーションで使用されるためのgemに変換しているという点です。これは、簡単にエンジンの部分を取り出して他のアプリケーションで使用できるようにgemに変換することができるということを意味します。gemfileの中にはいくつかコメントアウトされたgemがありますが、基本的には同じことです。それぞれはRefineryを拡張するための個別のエンジンであり、ブログや検索などの機能を追加することができます。
新規に作成した機能拡張はまだ対象のRailsアプリケーションの一部にはなっていないので、コマンドをいくつか実行して設定をおこないます。まず最初にBundlerを実行します。
$ bundle install
次にRefineryが提供するジェネレータを実行します。
$ rails generate refinery:pianos
このコマンドは、データベースにpianos
テーブルを追加するためのmigrationファイルをコピーし、seedsファイルの内容を追加します。このファイルの中を見ると、ジェネレータが追加した行があります。
Refinery::Pianos::Engine.load_seed
追加された行は、pianos
エンジンのためのseedデータをロードします。これによってRefineryに、ユーザ向けにピアノを表示する新規ページが追加されますが、エンジンをカスタマイズすることでどのようなseedデータでもロードできるようにすることが可能です。次にデータベースのマイグレーションを実行してそのseedデータをロードします。
$ rake db:migrate && rake db:seed
これでエンジンの設定ができました。カスタマイズを始める前に、初期状態で何ができるかを見ておきましょう。Railsサーバを再起動してadminページをリロードすると、pianoのレコードを管理するためのPianosという新しいタブが表示されています。
新規にピアノを登録するページには、ジェネレータで指定したすべてのフィールドが表示され、その中には画像を追加するためのフィールドも含まれています。前回のエピソードでページに画像を追加した方法とまったく同じやり方で、ここでも画像を追加することができます。description
のテキストフィールドは、ページ内容を追加するためのフィールドとまったく同じで、完全なWYSIWYGエディタになっています。
この後の作業のためにいくつかデータが必要なので、画面の外であらかじめ準備したデータを何件か追加します。
これらは入力した順番で並んでいますが、右にある“Reorder Pianos(ピアノを並び替える)”のリンクをクリックして好きな並びにレコードをドラッグすることで並び替えることが可能です。
またアプリケーションには新たに“Pianos”ページができました。その並びにある“About”ページはデータベースにseedデータをロードしたときに再表示されたものなので、再度削除します。新規ページの内容は新しいエンジンによって処理され、デフォルトでは追加したレコードのリストを表示します。ページ上部のバナーにも、このページへのリンクができていることに注意してください。
リンクのひとつをクリックするとそのピアノのページが開き、そのすべての属性のリストが、アップロードされたフルサイズの画像と一緒に表示されます。
エンジンをカスタマイズする
このページは何らかの手直しが必要です。それでは、エンジンのカスタマイズ作業の詳細を見ていくことにしましょう。アプリケーションの/vendor/extensions/pianos
ディレクトリは、Railsアプリケーションの通常のapp
、config
、db
、lib
などのディレクトリととても似た構造になっています。
Railsエンジンについてよく知らないという方は、エピソード277に詳しい説明があるので参照してください。ここからは、特に今回のカスタマイズの対象である、エンジンの/app/views
ディレクトリに注目していきます。このディレクトリにはrefinery/pianos
ディレクトリがあり、その中にadmin
とpianos
の各ディレクトリが含まれています。admin
ディレクトリにはもう一つのpianos
ディレクトリが含まれており混乱を招きやすいですが、覚えておくべきことは、このディレクトリはadminページのビューに関連していて、refinery/pianos/pianos
ディレクトリの方はユーザに公開するためのページ用であるという点です。例えばadmin
セクションにある部分テンプレートの_form.html.erb
には、ピアノ情報を追加したり編集したりするためのフォームのビューコードが含まれています。このコードを編集することで、このページに表示されているフィールドをカスタマイズすることができます。pianos
ディレクトリ内のshow
ページにはエンドユーザ向けに表示される個別のピアノに関するページ用のコードが含まれていて、我々が作業を行なうのはこちらの方です。
Refineryはcontent_for
ブロックを使ってページの内容を組み立て、これらのブロックの内容をページに表示する部分テンプレートrefinery/content_page
を出力します。
<% content_for :body_content_title do %> <%= @piano.name %> <% end %> <% content_for :body_content_left do %> <section> <h1>Name</h1> <p> <%=raw @piano.name %> </p> </section> <section> <h1>Dimensions</h1> <p> <%=raw @piano.dimensions %> </p> </section> <section> <h1>Manufactured On</h1> <p> <%=raw @piano.manufactured_on %> </p> </section> <section> <h1>Upright</h1> <p> <%=raw @piano.upright %> </p> </section> <section> <h1>Photo</h1> <p> <%= image_fu @piano.photo, nil %> </p> </section> <section> <h1>Description</h1> <p> <%=raw @piano.description %> </p> </section> <% end %> <% content_for :body_content_right do %> <aside> <h2><%= t('.other') %></h2> <ul id="pianos"> <% @pianos.each do |piano| %> <li> <%= link_to piano.name, refinery.pianos_piano_path(piano) %> </li> <% end %> </ul> </aside> <% end %> <%= render '/refinery/content_page' %>
このアプローチに従って処理を行なうことも可能ですが、より簡単な方法としてcontent_for
ブロックと部分テンプレートを無視して、それの代わりにRefineryがページを表示する仕組みを真似るやり方もあります。ビューテンプレートのコード全体を次のコードで置き換えます。
<section id="body_content"> <section id="body"> <div id="piano"> <%= image_fu @piano.photo, "400x400", class: "image-align-left" %> <h1><%= @piano.name %></h1> <p><b>Dimensions:</b> <%= @piano.dimensions %></p> <p><b>Manufacture Date:</b> <%= @piano.manufactured_on.year %></p> <%= raw @piano.description %> <p class="back"><%= link_to "Back to Pianos", refinery.pianos_pianos_path %></p> </div> </section> </section>
新しいテンプレートの2つのsection
要素は、Refineryがページを生成する方法を真似ています。画像にはRefineryが提供するimage_fu
メソッドを用いますが、それによって画像のサイズを指定することも可能です。description
にはWYSIWYGエディタからのHTMLが含まれていて、それをエスケープしないようraw
メソッドを使用します。最後にピアノの一覧に戻るためのリンクを作成します。ここに使用するパスは少し扱いにくいので注意が必要です。refinery
の部分はRefineryエンジンを表す接頭辞で、最初のpianos
はこの特定のエンジンを指す名前空間です。パスにある2番目のpianosはpianos
コントローラを参照しています。ここには名前空間がいくつもありますが、このエンジンのすべてのパスには最初にrefinery.pianos_
を付ける必要があります。
ここで個別のピアノを表示するページをリロードすると、ずっときれいになっています。
次にピアノのリストを表示するindex
テンプレートを改善します。このテンプレートはデフォルトの状態では単純に番号なしのリストで並べているだけです。
このテンプレートのコードは次のとおりです。
<% content_for :body_content_left do %> <ul id="pianos"> <% @pianos.each do |piano| %> <li> <%= link_to piano.name, refinery.pianos_piano_path(piano) %> </li> <% end %> </ul> <% end %> <%= render '/refinery/content_page' %>
これはshow
テンプレートと似た振る舞いをして、content_for
を使って部分テンプレートを出力します。ここでもコードを独自のものに置き換えます。
<section id="body_content"> <section id="body"> <div id="pianos"> <% @pianos.each do |piano| %> <div class="piano"> <%= image_fu piano.photo, "200x200", class: "image-align-#{cycle("left","right")}"%> <h2><%= piano.name %></h2> <%= raw piano.description %> <p class="more_info"><%= link_to "More Information", refinery.pianos_piano_path(piano) %></p> <% end %> </div> </div> </section> </section>
このテンプレートのコードはshow
テンプレートのコードにとても似ていますが、ここではピアノのリストをループして一つずつ出力しています。このページをリロードすると、改善されたきれいなリストをブラウズでき、それぞれには各ピアノのページへのリンクが付いています。
ピアノの画像は交互にページの左右に配置されていますが、この効果にはcycle
ヘルパーメソッドを利用しています。これはRefineryに固有の機能ではなく、Railsが提供するものでループ内の値のリストの繰り返し処理で利用できます。位置を交互に変えたいというような場合に、このメソッドはとても便利です。
ページへのリンク
サイトを仕上げるためにやることがもう少し残っています。例えばトップページの“Browse Pianos”のリンクです。これを修正するために、Refineryのトップページを編集します。テキストを選択し“Add Link“をクリックしてリンク先のピアノのページを選択します。ページ上部のバナーで、“Pianos”ページが“Store Location”ページの前にあらわれるようにしたいのですが、これもadminセクションで“Pages”タブの“Reorder Pages”をクリックして、“Pianos”を“Store Location”の上にドラッグします。このページのバナーのテキストを“Browse Pianos”に変更したいので、ページ編集画面で“Advanced Options”を選択して“Menu title”オプションを設定します。
Refineryの機能をオーバーライドする
ウェブサイトはほぼ完成に近づきました。このエピソードの最後に、いくつかのRefineryの機能をオーバーライドするチップスを紹介します。例えばRefineryが生成するHTMLの一部(例として、別にロードされる3つCSSファイルを生成する方法)を変更したいとします。代わりにRailsのasset pipelineを利用してapplication
ファイルにformatting
とtheme
の各ファイルをロードすることも可能です。Refineryの機能をオーバーライドしたい場合は、まず最初にソースコードをブラウズしてオーバーライドしたい対象のファイルを見つけます。今回の場合は、各ページのhead
セクションのすべてのコードが含まれているapp/views/refinery/_head.html.erb
部分テンプレートになります。Rakeコマンドを実行してこのファイルをオーバーライドします。
$ rake refinery:override view=refinery/_head Copied view template file to app/views/refinery/_head.html.erb Copied view template file to app/views/refinery/_header.html.erb $ rm app/views/refinery/_header.html.erb
このコマンドを実行すると2つのファイルが見つかりましたが、header
ファイルはオーバーライドしたくないので削除します。/app/views
の中のrefinery
ディレクトリにある_head
ファイルをここで修正します。修正するのは次の2行です。
<%= stylesheet_link_tag "application", "formatting", "theme" %> <%= stylesheet_link_tag "home" if home_page? %>
これを次のように書き換えます。
<%= stylesheet_link_tag "application" %>
ここで削除したファイルは、application.css
ファイルを修正することで、代わりにすべてasset pipelineで追加することが可能です。
/* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the top of the * compiled file, but it's generally better to create a new file per style scope. * *= require formatting *= require theme *= require_self *= require_tree . */
webサイトの外観はそのままですが、スタイルシートはすべてapplication.css
ファイルの一部としてロードされます。
Refineryに振る舞いを追加するもう一つの方法は、decoratorを介するやり方です。このアプリケーションには/app/decorators
ディレクトリがありますが、これはRefineryのコントローラとモデルを拡張するために設定されたものです。例えばRefineryのpagesコントローラにbefore_filter
を追加したいとします。その場合は、新規にpages_controller_decorator.rb
ファイルを作成します。
Refinery::PagesController.class_eval do before_filter :authorize private def authorize render text: "Not authorized" end end
このクラスの中で、Refinery::PagesController
クラスのclass_eval
を介して新しい振る舞いを追加します。例えばこのコントローラに認証機能を追加したい場合には、ここにbefore_filter
を追加します。しかし認証機能の追加は今回のエピソードでカバーする範囲を超えているので、ここでは単にauthorize
メソッドで適当なテキストを出力するだけにします。ページをリロードすると“Not authorized”と表示されますが、これはbeforeフィルタが呼び出されたことを示しています。
さらに上のステップへ
このエピソードも終わりに近づきましたが、ここで扱ったもの以外にもRefineryには多くの機能があります。ここで、次に見るべき場所についてのいくつかのチップスを紹介します。まずgemfileを開いて、記載されているgemのいくつかを非コメント化します。これによってRefineryにいくつかのすばらしい機能が追加されます。また設定ファイルのコメントにも目を通すことをお勧めします。ここでRefineryのいろいろな振る舞いをカスタマイズすることができます。
生成したエンジンにもいろいろ有用な機能があります。中のコントローラとモデルに目を通して、Refineryの内部がどう動いているのか把握しておくことが役立つでしょう。エンジンにはspecのセットも付いているので、これも一通り読んでおくことをお勧めします。
最後にRefinery CMSのwebサイトをぜひ参照してください。多くの優れた情報があり、その中にはここで触れることができなかった詳細情報の入ったガイドのリストも含まれています。他にも自分のアプリケーションで利用できるダウンロード可能なエンジンのリストもあります。