LoginSignup
40
41

More than 5 years have passed since last update.

railsのViewをDRYにするために行ってること

Posted at

simple_form

フォーム作成に手間が少なくなるsimple_formを使っている人は多いと思います。
まずsimple_formを使ってViewをDRYにする方法です。

準備

インストール

Gemfile
gem 'simple_form' # 必須
gem 'twitter-bootstrap-rails', :git => 'git://github.com/seyhunak/twitter-bootstrap-rails.git', :branch => "bootstrap3" # TwitterBootstrap本体
gem 'less-rails' # TwitterBootstrapを使うのに必要
gem 'therubyracer' # TwitterBootstrapを使うのに必要
gem 'slim-rails' # slimを使いたい
bash
# プロジェクトのルートに移動
$ cd project_home
# gemインストール
$ bundle install
# TwitterBootstrapインストール
$ rails g bootstrap:install less
# レイアウトを作成
$ rails g bootstrap:layout application fluid
# simple_formインストール
$ rails generate simple_form:install --bootstrap
# サンプルのBookモデルを作成
$ rails g scaffold Book title:string author:string review:integer description:text
# DBを作成
$ rake db:migrate
# 作成されたBook関連のviewにTwitterBootstrapを適用する
$ rails g bootstrap:themed Books

日本語化とテキストの共通化

まずは日本語化

config/application.rb
config.i18n.default_locale = :ja # 追加
日本語化ファイルを追加
  1. config/locales/ja.yml
    https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ja.yml
  2. config/locales/ja.bootstrap.yml
    https://github.com/heartrails/rails4-example/blob/master/config/locales/ja.bootstrap.yml
  3. config/locales/simple_form.ja.yml https://github.com/MiraitSystems/enju_trunk/blob/master/config/locales/simple_form.ja.yml

日本語化するときにこちらも役立つ
http://qiita.com/yujiym/items/b32c1a788aaa34eb8089
http://qiita.com/yshr04hrk/items/cb7b81c104321f660661
http://morizyun.github.io/blog/i18n-english-rails-ruby-many-languages/

共通化

モデルのカラム名やモデル名を共通化を行うのに、locales.ymlを使用する。
ただ、作成したBookの一覧と追加画面を見てみると名称が統一されていない。

日本語化を行うと以下のように修正画面のラベルは日本語化されている。
スクリーンショット 2015-07-19 16.49.04.png

一覧画面を見てみると日本語化したはずのカラム名が英語で表示されている。
スクリーンショット 2015-07-19 16.44.02.png

ここでindex.html.slimのソースを見てみる。

views/books/index.html.slim
table class="table table-striped"
  thead
    tr
      th=model_class.human_attribute_name(:id)
      th=model_class.human_attribute_name(:title)
      th=model_class.human_attribute_name(:author)
      th=model_class.human_attribute_name(:review)
      th=model_class.human_attribute_name(:desc)
      th=model_class.human_attribute_name(:created_at)
      th=t '.actions', :default => t("helpers.actions")

フォームのラベル部分がきちっと日本語になっているのはconfig/locales/simple_form.ja.ymlの
labels: => book: => id: の値を参照しているからで、一覧はこの値を見ていないため英語で表示されてしまう。
そのため、一覧のタイトルもここの値を見るように下記のように変更する。

views/books/index.html.slim
table class="table table-striped"
  thead
    tr
      th=t 'simple_form.labels.book.id'
      th=t 'simple_form.labels.book.title'
      th=t 'simple_form.labels.book.author'
      th=t 'simple_form.labels.book.review'
      th=t 'simple_form.labels.book.desc'
      th=t 'simple_form.labels.defaults.created_at'

tメソッドの説明は下記の通り。
http://morizyun.github.io/blog/i18n-english-rails-ruby-many-languages

カスタムインプット

バリデーションのためのHTMLのオプションや、入力のためのヒントメッセージ、プレースホルダを毎度viewに記述するのはDRYの流儀に反する。
そんなときに使用するのがカスタムインプット

モデルに指定している入力制限(1~5の範囲の値)のみを許可する入力項目をカスタムインプットで作成する

  1. モデルにバリデーションを作成
models/book.rb
class Book < ActiveRecord::Base
  validates :review, numericality: {
                      only_integer: true,
                      greater_than_or_equal_to: 1,
                      less_than_or_equal_to: 3 }
end
  1. app/inputsディレクトリを作成
  2. inputs/review_input.rbを作成
inputs/review_input.rb
class ReviewInput < SimpleForm::Inputs::NumericInput
  def input
    # クラスを追加
    input_html_classes.push('review form-control')
    # inputタグのオプションにminとmaxが設定される
    min, max = %i(min max).collect {|sym| input_html_options[sym] }
    # テキストボックスの下に入力のヒントが表示される
    options.merge!(hint: "※#{min}~#{max}の数値を入力")
    super
  end
end
views/books/_form.html.slim
= simple_form_for @book, :html => { :class => "form-horizontal" } do |f|
  = f.input :title
  = f.input :author
  \ as: :カスタムインプット名
  = f.input :review, as: :review
  = f.input :desc
  = f.button :submit, :class => 'btn-primary'
  = link_to t('.cancel', :default => t("helpers.links.cancel")), books_path, :class => 'btn btn-default'

実行してみると以下のようになる

スクリーンショット 2015-07-19 18.31.40.png

これでもしモデルのバリデーションのmin maxが変更されたときにview側も変更せずに済む。

カスタムインプットの作り方は以下が参考になる
http://www.ohmyenter.com/?p=214
https://github.com/plataformatec/simple_form/wiki/Custom-inputs-examples

同様にフォーム単位で作成できるカスタムフォームがある
https://github.com/plataformatec/simple_form

yieldとcontent_for

TwitterBootstrapをインストールしてレイアウトを作成するとlayouts/application.html.slimは以下のようになっている

layouts/application.html.slim
doctype html
html lang="en"
  head
    # 省略
  body
    # ヘッダー
    .navbar.navbar-default.navbar-static-top
        .container
          button.navbar-toggle type="button" data-toggle="collapse" data-target=".navbar-responsive-collapse"
            span.icon-bar
            span.icon-bar
            span.icon-bar
          a.navbar-brand href="#"Book
          .navbar-collapse.collapse.navbar-responsive-collapse
            ul.nav.navbar-nav
              li= link_to "Link 1", "/path1"
              li= link_to "Link 2", "/path2"
              li= link_to "Link 3", "/path3"

    .container
      .row
        .col-lg-9
          = bootstrap_flash
          # コンテンツ出力
          = yield
        # サイドバー
        .col-lg-3
          .well.sidebar-nav
            h3 Sidebar
            ul.nav.nav-list
              li.nav-header Sidebar
              li= link_to "Link 1", "/path1"
              li= link_to "Link 2", "/path2"
              li= link_to "Link 3", "/path3"
      # フッター
      footer
        p &copy; Company 2015

すべての画面で同様のレイアウトでヘッダー サイドバー フッターを表示するのであればこれでも問題ないが、画面によってヘッダーのデザインを変えたりサイドバーを表示しなかったりする場合これでは問題がある。
そんなときにyieldとcontent_forを使うとレイアウトの出しわけを容易に行うことができる。

yield

ブロックつきメソッドでブロックを呼び出すときに使用する。
viewで使用するときはcontent_forで定義されたブロックを呼び出すときに使用するくらいに認識しておけば今回は大丈夫かと思います。

content_for

ブロックをレイアウトファイルに渡すことができる。

content_for
# ブロックの作成
content_for :ラベル名 do
  # 行いたい処理
end

# ブロックの呼び出し
yield :ラベル名

yieldとcontent_forを使用してレイアウトを作成する

views/layouts/application.html.slim
- content_for :head do
  = stylesheet_link_tag "application", :media => "all", "data-turbolinks-track" => true
  = javascript_include_tag "application", "data-turbolinks-track" => true
- content_for :favicon, favicon_link_tag

= render :partial => "layouts/base"
views/layouts/_base.html.slim
doctype html
html lang="ja"
  head
    meta charset="utf-8"
    meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"
    meta name="viewport" content="width=device-width, initial-scale=1.0"
    title= content_for?(:title) ? yield(:title) : "SimpleForm"
    = csrf_meta_tags
    = yield :head
    = favicon_link_tag 'favicon.ico', :rel => 'shortcut icon'

  body
    = yield :header
    = render('layouts/content')
    = yield :footer
views/layouts/_header.html.slim
.navbar.navbar-default.navbar-static-top
    .container
      button.navbar-toggle type="button" data-toggle="collapse" data-target=".navbar-responsive-collapse"
        span.icon-bar
        span.icon-bar
        span.icon-bar
      a.navbar-brand href="#"SimpleForm
      .navbar-collapse.collapse.navbar-responsive-collapse
        ul.nav.navbar-nav
          li= link_to "Link 1", "/path1"
          li= link_to "Link 2", "/path2"
          li= link_to "Link 3", "/path3"
views/layouts/_content.html.slim
.container
  .row
    .col-lg-9
      = bootstrap_flash
      \ このyieldでレイアウトファイルが呼び出される
      = yield
    = yield :sidebar
views/layouts/_sidebar.html.slim
.col-lg-3
  .well.sidebar-nav
    h3 Sidebar
    ul.nav.nav-list
      li.nav-header Sidebar
      li= link_to "Link 1", "/path1"
      li= link_to "Link 2", "/path2"
      li= link_to "Link 3", "/path3"
views/layouts/_footer.html.slim
.container
  footer
    p &copy; Company 2015

_base.html.slimで必要なlayoutsの呼び出しをyieldで行っている。
次にテンプレートに対応したブロックをview側でcontent_for使用してブロックを作成する。

views/books/index.html.slim
# ブロックの作成
# ヘッダー
- content_for :header, render('layouts/header')
# サイドバー
- content_for :sidebar, render('layouts/sidebar')
# フッター
- content_for :footer, render('layouts/footer')

- model_class = Book
div class="page-header"
  h1=t '.title', :default => model_class.model_name.human.pluralize.titleize
table class="table table-striped"
# 省略

ヘッダー サイドバー フッターをいずれか表示しない場合はcontent_forを呼び出さない。
yieldでは呼び出そうとしているブロックが存在しなくてもエラーにはならないので問題ない。
内容を変更する場合はcontent_forの第二引数に指定したrenderの値を変更する。

そのほかの使い方としてはGoogleにインデックスされたくないページにnoindexを指定する場合です。
Googleのクローラにインデックスさせないことを知らせるためにnoindexメタタグをヘッダに指定する必要がある。
ページによって出力するしないを設定するにはyieldとcontent_forを使用するのが最適です。

views/layouts/_base.html.slim
doctype html
html lang="ja"
  head
    meta charset="utf-8"
    meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"
    meta name="viewport" content="width=device-width, initial-scale=1.0"
    title= content_for?(:title) ? yield(:title) : "SimpleForm"
    = csrf_meta_tags
    = yield :head
    = yield :noindex
    = favicon_link_tag 'favicon.ico', :rel => 'shortcut icon'

  body
    = yield :header
    = render('layouts/content')
    = yield :footer
_noindex.html.slim
meta content="noindex" name="robots"
views/books/index.html.slim
# インデックスされたくないテンプレートビュー
- content_for :header, render('layouts/noindex')

- model_class = Book
div class="page-header"
  h1=t '.title', :default => model_class.model_name.human.pluralize.titleize
table class="table table-striped"
# 省略
40
41
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
41