ユーザー登録/ログインしないと見れないページを LocomotiveCMS に組み込みたくなりましたので、やってみました。Rails のバージョンは3.2.16、LocomotiveCMS は2.4.1が対象です。
ユーザークラスの作成
まず、LocomotiveCMS には最初から devise というRails用の認証プラグインが組み込まれていますので、新しい認証機能を付け加えるのは簡単そうです。LocomotiveCMS 上ではAccountモデルというのが、CMS自体の管理者ユーザーの管理に使われていますが、それとは別にしたいのでUserモデルを新たに作ってみます。
bundle exec rails g devise User
LocomotiveCMSでは mongoid を使っているので、db:migrate は必要ありません。unicorn_rails の再起動をして、http://localhost:8080/users/sign_up を開いてみると、登録画面が表示されるはずです。
ここでID/Passを登録すると、ユーザ登録がされた状態でトップ画面にリダイレクトされます。mongodb 側を見てみると、確かにユーザーが作られています。
% mongo localhost:27017
MongoDB shell version: 2.4.9
connecting to: localhost:27017/test
Server has startup warnings:
Thu Mar 27 17:45:06.188 [initandlisten]
Thu Mar 27 17:45:06.188 [initandlisten] ** WARNING: soft rlimits too low. Number of files is 256, should be at least 1000
> use mysite_dev
switched to db mysite_dev
> db.users.find()
{ "_id" : ObjectId("533675464857099c8d000001"), "current_sign_in_at" : ISODate("2014-03-29T07:24:54.852Z"), "current_sign_in_ip" : "127.0.0.1", "email" : "hal@georepublic.co.jp", "encrypted_password" : "$2a$1*******", "last_sign_in_at" : ISODate("2014-03-29T07:24:54.852Z"), "last_sign_in_ip" : "127.0.0.1", "sign_in_count" : 1 }
>
デザインや追加の登録項目を作るのはとりあえず後回しで先に進みます。
PageController の拡張
LocomotiveCMS はEngineを使っているので、公式ガイドで書かれているように、LocomotiveCMS engine 側のパスと同じ場所にモデルやコントローラーのクラスを置くことで機能を拡張できます。
development.log を見てみると、公開ページへのGETリクエストは以下のコントローラーにたどり着くようです。
Started GET "/outline" for 127.0.0.1 at 2014-03-30 11:45:19 +0900
Creating scope :root. Overwriting existing method Locomotive::Page.root.
Processing by Locomotive::Public::PagesController#show as HTML
Parameters: {"path"=>"outline"}
ですので、今回はこのPagesControllerを拡張してみます。まず、LocomotiveCMS Engine側と同じように、app/controllers/locomotive/public/pages_controller.rb に拡張用のクラスを作成します。
cd {RAILS_ROOT}
mkdir -p app/controllers/locomotive/public/
vi app/controllers/locomotive/public/pages_controller.rb
中身は以下のようにしておきます。これで、既存のPagesControllerクラスを再オープンして、新たな定義を追加することができます。Auth::AuthPage クラスはこれから作る認証用のプラグインです。
module Locomotive
module Public
class PagesController < ApplicationController
include Auth::AuthPage #AuthPageというプラグインを読み込む
end
end
end
実際に認証機能を追加する、Auth::AuthPage クラスを作ります。元々 /app/concerns 以下は autoload されるようになっているので、そこに以下のクラスを作ります。
module Auth
module AuthPage
extend ActiveSupport::Concern
included do
before_filter :authcheck
end
def authcheck
logger.info "Auth Check"
end
end
end
この状態でサイトにアクセスすると、ログファイルに Auth Check というログが出力されているはずです。
Started GET "/entry" for 127.0.0.1 at 2014-03-30 22:07:38 +0900
Processing by Locomotive::Public::PagesController#show as HTML
Parameters: {"path"=>"entry"}
[LocomotiveCMS] [fetch site] host = localhost / localhost:8080
MOPED: 127.0.0.1:27017 QUERY database=mysite_dev collection=locomotive_sites selector={"$query"=>{}, "$orderby"=>{:_id=>1}} flags=[:slave_ok] limit=-1 skip=0 batch_size=nil fields=nil (0.5510ms)
Auth Check ←これ
MOPED: 127.0.0.1:27017 QUERY
まだログを出力するだけで実際の認証処理は行なっていません。とりあえずはこのままにしておいて、先に認証のオンオフをページ毎に設定できるようにしましょう。
ページ属性に、ログイン必須オプションを追加する
ページに対して、ログインが必須になるような設定を追加できるようにします。app/concerns/auth/auth_field.rb を追加し、以下の内容を記述します。
module Auth
module AuthField
extend ActiveSupport::Concern
included do
field :require_login, type: Boolean, localize: true
end
end
end
上記モジュールをPageモデルからincludeすることで、require_login というフィールドを作ることができます。
app/models/locomotive/page.rb ファイルを新たに作り、include 文を記載します。
module Locomotive
class Page
include Auth::AuthField
end
end
Page クラスが正しく拡張されているか、確かめてみましょう。rails console からLocomotie.Page
クラスを確認してみます。
% bundle exec rails console
Loading development environment (Rails 3.2.16)
[1] pry(main)>
[2] pry(main)> Locomotive::Page.first()
=> #<Locomotive::Page _id: 53303a0c485709d69800000e, created_at: 2014-03-24 13:58:36 UTC, updated_at: 2014-03-27 03:27:16 UTC, parent_id: "5330384f485709d698000004", parent_ids: ["5330384f485709d698000004"], position: 0, depth: 1, serialized_template: {"ja"=>#<Moped::BSON::Binary type=:generic length=7286>}, template_dependencies: {"ja"=>["5330384f485709d698000004"]}, snippet_dependencies: {"ja"=>["footer"]}, templatized: false, templatized_from_parent: false, target_klass_name: nil, redirect: false, redirect_url: nil, redirect_type: 301, listed: true, seo_title: {"ja"=>"お知らせ"}, meta_keywords: {"ja"=>"html5, "}, meta_description: {"ja"=>"html5, "}, title: {"ja"=>"お知らせ"}, slug: {"ja"=>"news"}, fullpath: {"ja"=>"news"}, handle: "news", raw_template: {"ja"=>"{% extends 'parent' %}\n{% block main %}\n<div class='row'>\n <div class='large-8 columns'>\n <p>News</p>\n {% editable_text \"news_body\", rows: 20 %}\n hoge\n {% endeditable_text %}\n </div>\n</div>\n{% endblock %}\n", "en"=>"{% extends 'parent' %}"}, locales: [:ja], published: true, cache_strategy: "simple", response_type: "text/html", site_id: "5330384f485709d698000002", require_login: nil, cache_strategy_text: "Simple", lang: "ja">
require_login
というプロパティがあるのを確認できました。
管理画面から切り替えができるようにする
さて、あと一息です。今度は管理画面のViewにrequire login属性を追加しましょう。
管理画面のページは、/locomotive/pages/{ID}/edit というURLになっていますので、Engine 側の /app/views/locomotive/pages/edit.html.hamlを見てみますと、ページの下の方に以下のような記述があります。
= semantic_form_for @page, url: page_path(@page), html: { multipart: true } do |form|
= render 'form', f: form ←これ
= render 'locomotive/shared/form_actions', back_url: pages_path, button_label: :update
というわけで、今度は /app/views/locomotive/pages/_form.html.haml を開きますと、それっぽいファイルが出てきました。
- content_for :head do
= render '/locomotive/content_assets/picker'
= render '/locomotive/theme_assets/picker'
= render 'editable_elements'
- content_for :backbone_view_data do
:plain
page: #{escape_json to_json(@page)}
= f.inputs name: :information do
= f.input :title, wrapper_html: { class: 'highlighted' }
(省略)
まさに求めていたファイルそのものです。このフォームに一行追加をしたいので、このファイルを自分のアプリケーションの下の同じパスにコピーして開きます。
cp {オリジナルのEngineをcloneしておいた場所}/app/views/locomotive/pages/_form.html.haml {RAILS_ROOT}/app/views/locomotive/pages/_form.html.haml
vi {RAILS_ROOT}/app/views/locomotive/pages/_form.html.haml
その後そのファイルを開き、published フィールドの行の下に以下のように行を追加します。
= f.input :published, as: :'Locomotive::Toggle', input_html: { class: 'simple-toggle' }
= f.input :require_login, as: :'Locomotive::Toggle', input_html: { class: 'simple-toggle' } #この行を追加
= f.input :listed, as: :'Locomotive::Toggle', input_html: { class: 'simple-toggle' }
(この一行追加するためだけに元のviewファイル全体をコピーしてくるのは、今後の本家のバージョンアップへの追随などを考えると少々面倒。全部コピーせずに実現する方法があれば教えて欲しいです!)
そして管理画面からページ編集画面を見てみます。
無事新しいフィールドが編集できるようになりました!ONにするとちゃんと新しい属性が更新されるようになっているはずです。
認証が必要なページでログイン画面を出力する
いよいよ最後のステップです。PageController の拡張の時に作った AuthPage モジュールを以下のように書き換えます。
module Auth
module AuthPage
extend ActiveSupport::Concern
included do
before_filter :authcheck
end
def authcheck
@page ||= self.locomotive_page
authenticate_user! if @page && @page.require_login
end
end
end
locomotive_page というメソッドは元々のコントローラーが持っていたメソッドで、表示対象のページを取得するものです。authenticate_user! メソッドは、devise が提供するもので、ログインチェックをしてログインしていなければログイン画面へリダイレクト、ログイン後戻ってくるというすぐれものです。
ページのrequire_login属性がtrueの場合のみ、このauthenticate_user! メソッドを叩くという処理をしているわけです。
この状態で、先ほど requre_login を ON にしたページにログインをすると、以下のようにログイン画面が出てくるはずです。
ログインを行うと、元々表示したかったページが表示されます。一方、require_loginを設定していないページは、今まで通りログインしなくても表示できます。
以上です!
(追記)ここまで書いたものはローカルの開発環境では動いているのですが、なぜか上記手順で作ったものを Heroku にデプロイすると、AuthFieldクラスがundefined method 'field'
というエラーを吐いて落ちてしまうという事象が発生中。どうもapp/models/locomotive/page.rb やapp/controllers/locomotive/public/pages_controller.rb を置いた時点で、Engine側のクラスが読み込まれず新しいクラスで上書き状態になってしまっている模様。解決策が分かる人がいたら教えて下さい!
2014/04/07追記:とりあえず現状では、engine 側のファイルをコピって来て、それにメソッドを追加したものをデプロイして動いています。ダサい・・・
Locomotive 関連の記事一覧:
- Locomotive Engine を Heroku で動かすまで
- LocomotiveCMS でニュース記事一覧機能を作る
- LocomotiveCMS のページにユーザー認証機能を組み込む(Engineの拡張)
- LocomotiveCMS + Devise を使って作ったログインページのデザインをCMS側でできるようにする(1)
- LocomotiveCMS + Devise を使って作ったログインページのデザインをCMS側でできるようにする(2)
- LocomotiveCMS + Devise を使って作ったログインページのデザインをCMS側でできるようにする(3)
- LocomotiveCMS で、複数の Heroku インスタンスを使う