こんにちは。広告事業部の鈴木です。
皆さんはLiquidと呼ばれるテンプレートエンジンをご存知でしょうか? LiquidはShopifyのメンバーを中心として開発されているテンプレートエンジンの一種で、 最近ではJekyllに使われていることでも知られています。 クックパッドの広告事業部では、広告商品の一つであるタイアップページ*1でこのLiquidを活用しています。
例. マルちゃん焼そば弁当コンテスト! [クックパッド] 簡単おいしいみんなのレシピが199万品
このタイアップページでは、そこからリンクされているレシピ応募コンテストページにユーザさんが投稿してくれたレシピ数を表示するのにLiquidが使われています。
タイアップページにはデータベースに保存されたデザイン済みのHTMLに以下のようなプレイスホルダーになっているタグが含まれていて、ページがレンダリングされる際にコンテストへのレシピ投稿数と置換されます。
{{ contest_recipe_counts.537 }}
537はコンテストページのID *2
どうしてLiquidが便利なのか?
Liquidの他によく知られたテンプレートエンジンの例として、ERB, Haml, Slimが挙げられますが、これらはあらかじめ用意されたテンプレートファイルからHTMLを生成するのに向いています。これに対し、Liquidはデータベースに保存されたデザイナが用意してくれたHTML snippetの中にデータリソースを表示したいといった用途に向いています。
こんな業務を持っている人に便利
- 独自のコンテンツ管理システムがある
- CMSで入稿するコンテンツでデータベース内の情報を動的に取り扱いたいことがある
- 一般的なCMSで入稿できるデータはテキストかHTMLに制限されているのが普通ですが、そこにデータソースを表示したいなどの理由で既に自前でカスタムタグを作って解析させている場合 *3
タイアップページの例の場合
クックパッド内のほとんどのページは共通のUIの上にコンテンツを配置する方法でデザインされています。 しかし、タイアップページはクライアントの商品イメージに合わせるためにそのような共通UIを使わず、 基本レイアウトを除くほぼすべてのUIをキャンペーンごとにデザインしなおします。
当然ながらそのデザインはクライアント側にチェックしてもらう必要がありますから、 デザインを決定するまで何度も何度も本番へデザイン変更を反映しなければいけません。
しかし、細かくデザインを修正するたびにデプロイするということになると、 デザイン修正のたびにエンジニアの作業が必要となり運用が面倒です。 そこでタイアップページは、入稿してもらったHTMLスニペットをデータベースに保存しておき、 「土台」となるHTMLページの枠に流し込むだけという構造にしています。 このような構造にすることで、デザイナがエンジニアを介さずHTMLを直接入稿できるようになるわけです。
そのように、静的HTMLが保存されている場合でも上記のタイアップページの様に”コンテストへの投稿数を動的に表示してほしい”といったような要件があるものです。 そういった事例を解決するのがLiquidです。 上述したようなLiquid tagをデザインに埋め込むことでデザインからデータソースにアクセスして表示することができます。
コード上のフロー
ここでコード上のフローを確認しましょう。
ControllerがModelのデータソースをロードしViewに渡す
↓
Viewのレンダリングを開始する
↓
Viewがデータソースをテンプレート上に展開する
:
↓
Liquidでは展開されたデータソースのコンテンツ中のLiquid Tagをテンプレートエンジンがさらに展開するイメージです。
その他の用途例
Drop
Dropはあるモデルから、引数をとってsetterの役割をするmethodを排除してHashのような振る舞いの構造体を作ります。あるモデルから得られるデータリソースをデザイナがセキュアに表示できるように開放したいときに便利です。
例えば以下の例ではTieupRecipeというモデルをTieupRecipeDropに渡して、Liquidのテンプレートエンジンに{{ tieup_recipe.title }}
を含んだ文字列をパースさせると、TieupRecipe#titleが取得できるというものです。
tieup_recipe = TieupRecipe.find(id) tieup_recipe_drop = Pr::Liquid::TieupRecipeDrop.new(tieup_recipe) template = Liquid::Template.parse("タイトル:{{ tieup_recipe.title }}") template.render('tieup_recipe' => tieup_recipe_drop) => "タイトル:簡単!夏野菜の三色ロール"
TieupRecipeDropはあらかじめ自前で定義しておく必要があります。
class TieupRecipeDrop < Liquid::Drop def initialize(tieup_recipe) @tieup_recipe = tieup_recipe end def title @tieup_recipe.recipe.title end end
Liquid Block
Liquid BlockはHTML tagを他のタグで囲みたい場合に使います。下記の例ではmobileというlabelの付いたTieupMailというモデルのレコードをデータベースから探して、そのメールを送るフォームを開くボタンを生成します。
- tieup_mails tableのレコード
- 入稿されたLiquid tag
{% tieup_mail_colorbox mobile %} <img src="http://img5.cookpad.com/tieup/672/week_bt_sp.gif" width="74" height="74"> {% endtieup_mail_colorbox %}
- 生成されたHTML
<a class="colorbox_link app_open_browser" data-iframe="true" data-dialog_width="700" data-dialog_height="400" href="http://cookpad.com/ct/88594" style="background: tranparent;"> <img src="http://img5.cookpad.com/tieup/672/week_bt_sp.gif" width="74" height="74"> </a>
- その見た目
- そのボタンをクリックすると表示されるメール送信フォーム
※. colorbox_link
というCSS classがあるanchor tagをクリックするとJavascript Libraryがこのようなダイアログを表示するようになっています。
送信メールのレコードの中身が反映されています。送信メールの情報は管理画面で入稿されます。デザイナはメールフォームのlabel名さえ伝えられて知っていればメールフォームへのリンクを自由なデザインでコーディングでき便利です。
まとめ
以下のような場合にLiquidを採用すると便利です。
頻繁にデータソースを含むページを修正する必要がある
誰にでもデータソースを表示できるようになって欲しいが、誤ってデータソースが変更されたり、誤って公開されてはいけないデータソースが公開されるのを防ぎたい
用途例で示したように広告事業部のタイアップ案件では、Liquidを採用したことによってデザインフリーに(デザインに依存しないように)機能を切り出して積み立てることができるようになりました。デザインを絡めた業務フローがあるようなあなたの現場でもLiquidが思わぬライフセイビングになるかもしれませんよ。