LoginSignup
28
28

More than 5 years have passed since last update.

パーフェクト通読Railsまとめ

Posted at

パーフェクトRailsまとめ

image

職場である程度Railsでの環境に慣れてきたところで、今のプロジェクトと照らし合せて
パーフェクトRailsで知識を補填することにしました。

わかっている箇所についてはどんどん飛ばしていくので、まとめとしては歯抜けになってしまうかもしれませんが、備忘録として作成しました。

概要

▼Rakeタスク書き方

定型コマンドをまとめておくためのタスク。

desc 'Hello, display Rake Task' 
  task :hello do
  puts 'Hello, Rake!'
end

これでrake helloで実行できるようになる。

▼Gemfile.lock

今使っているgemのバージョンが記録される。

▼設計思想

  • 設定より規約(Convention to Configuration)
  • DRY
  • REST
  • 自動テスト

▼HTTPメソッドについて

  • HTML5ではPUT/DELETEを採用してないので、Railsではoverloaded POSTで行う

▼ActiveRecordについて

  • 実際にそのSQLの実行結果が必要になるまでは、DBに対するアクセスは実行されない

▼scopeの設定

class Book < ActiveRecord::Base
  scope :costly, -> { where("price > ?", 3000) }
  scope :written_about, -> (theme) {  where ("name like ?", "%#{theme}%") }
end
  • default scope デフォルトで降順に設定、
class Book < ActiveRecord::Base
  default_scope -> { order("published_on desc") }
end

▼errorについて

ActiveRecordのオブジェクトにerrorが発生すると、エラーメッセージを参照することができる。
book.errors.full_messages

保存をせずにバリデーションだけを行いたいときはbook.valid?などとするとバリデーションを通過しているかもわかるし、失敗時にはerrorメッセージが入る

▼メソッドについて

  • !付きメソッドはバリデーション失敗時例外を発生するActiveRecord::RecordInvalid
  • !なし 例外を起こさない

例外をrescueして処理を記述したいときもあると思うので、場合によって使い分けるように。

コールバックについて

  • 前処理、後処理を綺麗に宣言
  • 必ず行いたい処理の実行漏れを防ぐことができる

バリデーション処理については一般的にこういった書き方をする

class Book < ActiveRecord::Base
  before_validation do |book|
  book.name = book.name.gsub(/Cat/) do |matched|
    "lovely #{matched}" 
  end
end

削除後にログを取るようにする

class Book < ActiveRecord::Base
  after_destroy do |book|
    Rails.logger.info "Book is deleted: #{book.attributes.inspect}"
  end
end

バリデーションによってアクションごとに適用されるされないがあるので、注意が必要。

また、コールバックがまったく実行されないメソッドがあるので、これも注意!

enum型

  • 文字列を定義した順番で0から始まる 明示的に
class Book < ActiveRecord::Base
  enum status: %w(reservation now_on_sale end_of_print)
  # enum status: { reservation: 0, now_on_sale: 1, end_of_print: 2 }
end 

ルーティングとコントローラ

  • 古典的な例
Rails.application.routes.draw do
  get '/books/;id' => "books#show"
  #...
end
  • 一般的なshowアクションの記述例
class BooksController < ApplicationController
  def show
    @book = Book.find(params[:id])
    respond_to do |format|
      format.html
      format.json
      format.csv
    end
  end
end

ApplicationControllerはすべてのControllerで使用するヘルパーや属性、挙動を定義する場所です。

コントローラの役割まとめ: リクエストからコントローラの中でアクションを決定し、アクションの中でモデルを通じて必要な操作を定義する。そして、ビューにオブジェクトを渡す。

  • コールバックはスキップもできる
    • skip_before_action
    • skip_after_action
    • skip_around_action 等のメソッドが既に用意されている。
class ApplicatiolnController < ActionController::Base
  before_action :require_login
  def require_login
    if !@crew.logged_in?
      redirect_to login_page_path
  end
end

class LoginController < ApplicationController
  # ログインするための画面にたどり着けないので
  skip_before_action :require_login, only:[:new, :create]
end

▼resourcesルーティングの拡張

拡張は基本的にresourcesブロックの中に記述する

Rails.application.routes.draw do
  resources :doctor do
    resources :patients 

    member do
      get 'detail'
    end
    collection do
      get 'search' 
    end
  end
end

ブロックに入れることで、リソースの親子関係をルーティングで表現できます。
実は単数形のresourceもある。そのときはindexへのリソースは作られない
membercollectionは基本アクション以外のルーティングを設定することもできる

  • member リソースに:idを含める必要があるとき
  • collection それ以外の場合

  • StrongParametersはMassAssignmentからの脆弱性を除外する

  • コントローラ名とアクション名が同じならrenderは省略可

▼variantsによるテンプレートの切り替え

ApplicationControllerにvariantを使うことで、アクセスする端末によって表示するテンプレートを変えることができる

▼jbuilder

基本的にrails newしたプロジェクトなら勝手にgem 'jbuilder'が入っている。
これを使用している場合、app/views/books/show.jbuilder.jsonファイルを作る必要がある。

app/views/books/show.jbuilder.json
json.extract! @book, :id, :name, :price, :created_at

このようにファイルを作成し、アクセスするだけでjsonデータが作成される。
詳細 ( https://github.com/rails/jbuilder )

Sass

SCSS記法とSass記法二つある。
SCSSはCSSと互換性あり。Sassはスタイルをインデントで表現。
拡張子もそれぞれ.scss.sassで2種類ある。
現在主流で使用されているのはSCSSである。
ただ元々Sass記法からSCSS記法が生まれたので、SCSSのこともSassと呼ぶことが多い

  • &は親セレクタの参照
#main p {
  color: #00ff00;
  width: 97%;

  .redbox {
    background-color: #ff0000;
    color: #000000;
  }
}

a {
  &:hover { text-decoration: underline; }
}

@extend

.error {
  border: 1px #f00;
  background-color: #fdd;
}

.seriousError {
  @extend .error;
  border-width: 3px;
}

@mixin, @include

mixinはextendと違い、特定のセレクタに依存しない再利用方法

  • mixinで定義
  • includeで利用

要するにmixinとincludeはセット

@mixin clearfix {
  &:before, &:after {
    content: " ";
    display: table;
  }
  &:after {
    clear: both;
  }
}
img.thumbnail {
  float: left;
  @include clearfix;
}

その他、@if@else@for@each@whileなども使え、通常のプログラミング言語のように
演算子をしようして条件分岐、ループなどもできる。

Turbolinks

▼Pjaxとは

ページ遷移の高速化手法。HTML5のpushStateという機能とAjaxを組み合わせた手法。
これによってURLを更新しつつ、ページ全体を描画せずに必要な部分のみを更新できます。
同一のヘッダーを再利用できるために、Javascriptの読み込みなどを簡略化できます。

このPjaxの簡易的な利用方法を提供するのがTurbolinksです。

Turbolinksの読み込みはapplication.jsで行われる

application.js
//= require turbolinks

任意のリンクでTurbolinksを無効にする際は、data-no-turbolinks属性を付ける。

▼jQuery-Turbolinks

application.js
//= require jquery
//= require jquery.turbolinks
//= require jquery_ujs
//= require turbolinks

読み込み順序は必ず//= require jquery.turbolinksが先

Railsのロードパス

▼libディレクトリ

役割

  • モジュールを配置
  • 独自のRakeタスクファイルを配置

libディレクトリには元々requireパスが設定されている。

▼新レイヤーの配置とオートローディング

ファイル名はスネークケース、クラス名はキャメルケースで作ると基本的にオートローディング

新しいモジュール置き場を作りたい、そういった時は?

  1. appディレクトリ以下に、新しくサブディレクトリを作成
  2. そのディレクトリをautoloadingに加える

これだけです。

config/application.rb
class Application < Rails::Application
  # config.autoload_paths += %W(#{config.root}/app/foobar)
  config.autoload_paths << Rails.root.join('lib')
  config.autoload_paths << Rails.root.join('app/validators')
  #...
end

pry-rails

このgemにより、rails consoleの中でpryを実行できるようになる。

◎ステップ実行
binding.pryでブレークポイントを設定することによって、それまでに実行されたオブジェクトを参照、操作することができる。
追加でgem 'pry-byebug'をinstallすることによってステップ実行を行うことができるようになります。
quitで終了すると、実行されていた処理が再開されます。
next
step
などのメソッドも追加される。

▼Better-errors

エラー画面を見やすくするもの。
デフォルトの真っ赤な画面にならない。
リクエストが見やすく表示されるので、オブジェクトの中身も見やすくなっている。

▼Spring

Rails4.1から導入。
ロード時間の軽減のためのgem。
Windowsでは無効(まじですか...w)

初回はSpringサーバが起動してからなので、時間はかかりますが、2回目以降からはRakeタスクの実行時間が大幅に短縮されます。
gem spring-commands-rspecinstallで、Rspecも高速化できる。

▼i18n Configuration

適用させたい場合、ディレクトリはconfig/application.rbに追加。
config.i18n.default_locale = :ja

表示させるメッセージは以下のようにymlファイルを使用して設定する。
ymlファイルはconfig/locales/hoge_ja.yml

ja:
  activerecord:
    models:
      event: イベント
    attributes:
      event: 名前
      place: 場所
      content: 内容

テストについて

▼なぜテストを書くのかについて

  • アプリケーションコードについて考える機会が増える
    • 仕様の抜け漏れを防ぐことができる
  • 手作業テストをする必要がなくなる
  • アプリケーションコードに対して自信を持てる
  • リファクタリングを行う際に安心感が持てる

▼モデルのテスト

テストすべき項目

  • メソッドが期待通りの値を返すか
  • メソッドが期待通りの副作用をもたらしているか
  • バリデーションが正しく動作しているか

rails g rspec:model crew
これで空のrspecファイルが作成される。

▼コントローラのテスト

テストすべき項目

  • ビューに渡るインスタンス変数が期待通りのオブジェクトを持っているか
  • アクションが期待通りのステータスコードを返しているか
  • アクションが期待通りのテンプレートを選択しているか

▼エンドツーエンドのテスト

クライアント側から操作して、サーバをブラックボックスとして入出力をテストすること

▼Javascriptのテスト

実際にブラウザが動作するかどうかはseleniumでテストすることができる。
しかし、Railsでの開発では積極的には行われていません。
capybaraでjavascriptをテストする際は、poltergeistというgemを使用する。

▼CI(継続的インテグレーション)

テストコードが巨大になってきた時に、手助けしてくれるツールが存在する。

  • Jenkins
  • CircleCI
  • TravisCI

▼カバレッジ, 静的解析

カバレッジ:テストの網羅率
静的解析:プログラムを実行せずに、プログラムの問題点やバグを調べて分析すること

カバレッジにはC0,C1,C2などのレベルがある。高いほど、網羅性が高く、品質のいいコードである可能性が高い。

DevOps

Dev:アプリケーション開発者
Ops:運用、インフラ担当エンジニア
おたがいの仕事を理解しようという考え。

なぜ仮想環境を作るか?

  • ローカルに開発環境を完成させることの意味
    • 本番になるべく近い環境を得ること
    • クリーンな動作環境を作ること(不要なソフトウェアとの干渉を避ける)

Vagrant

VirtualBoxを利用してVMをたちあげて、セットアップなどをコマンド一つでやってくれる

より実践的なモデルの使い方

Railsの原則:Controllerを薄く、Modelを厚く
しかし、今度はModelが大きくなりすぎる

RailsではRDBの利用を想定している。
オブジェクトとRDBのデータをマッピングするためにいくつもの手法があるが、RailsではActiveRecordで実現している。

言い換えると、Railsでのモデル層で利用するライブラリの名前がActiveRecordである。
ただし、複雑なロジックを、ドメインモデルをRubyのクラスに対応させなければいけないので、ある程度の限界はある。

何も考えずにやっていると、ActiveRecordモデルが肥大化しすぎてしまう。
まず、バリデーションとコールバックを分離することから考えてみましょう。

コールバックをクラスに分離する

シンボルで記述するのが最も一般的かつ推奨(一応ブロックも、オブジェクトも渡すことができる)

class Subscription < ActiveRecord::Base
  before_create :record_signup

  private
    def record_signup
      self.signed_up_on = Date.today
    end
end

この方法は非常にシンプルでわかりやすいですが、コールバックの数が増えるに連れてActiveRecordサブクラスが肥大化してきて、責務がどんどん増えていくことになります。

▼解決方法

class BankAccount < ActiveRecord::Base
  before_save EncryptionWrapper.new("credit_card_number")
end

class EncryptionWrapper
  def before_save(record)
    record.send("#{@attribute}=", encrypt(record.send("#{`attribute}")))
  end
end

こういった形で分離することができます。モデル層が肥大化すればするほど、この方法を取るメリットは大きくなっていくでしょう。

バリデータをクラスに分離する

コールバックと同様に、こちらもクラスに分離することができる。
ActiveModel::EachValiatorを継承したクラスを定義する

lib/autoload/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors.add attribute, (options[:message] || "is not an email") unless
      value =~ /¥A([^@¥s]+)@((?:[-a-z0-9]+¥.)+[a-z]{2,})¥z/
  end
end
app/models/person.rb
class Person
  include ActiveModel::Validations
  attr_accessor :name, :email

  validates :email, presence: true, email: true
end

RDBに直接対応しないクラスを作る

ActiveRecordは優れたORマッパーだが、モデル層がRDBの構造と直接依存したものになってしまう。
そういったときは、ActiveModelモジュールを利用することにより、ActiveRecordとほぼ同様の機能を実装することが出来ます。

  • バリデーション、コールバック
  • 属性名をもとにした動的メソッドの定義
  • 属性値に対する変更の保持、確認

▼ActiveModelが提供するモジュール

  • ActiveModel::AttributeMethods(メソッドの宣言)
  • ActiveModel::Callbacks
  • ActiveModel::Dirty(属性値の変化、追跡)
  • ActiveModel::Naming(Rails命名規約の導入)
  • ActiveModel::Serialization
  • ActiveModel::Vailidations

エンティティ, 値オブジェクト

アプリケーションがオブジェクトの同一性をどう捉えるか?

例をあげると、Userクラスは、仮にEmailアドレスが変更されても変更前のUserと変更後のUserは同一であるべきです。

エンティティ:システムでオブジェクトの同一性が重要な意味をもつもの
値オブジェクト:値が同じであればアプリケーション上では、同一であると見なしてよいオブジェクト

エンティティは基本的に同一性を認識する為の識別情報を持っており、Railsの場合は「id」カラムです。ほかの情報が異なっていても、idが同じであれば同一のオブジェクトです。
ActiveRecordのオブジェクトは基本的にエンティティオブジェクトを作成することになる。

▼値オブジェクトによる実装の例

Userクラス

# == schema information
# 
# Table name: users
#
# id           :integer
# name         :string(255)
# prefecture   :string(255)
# city         :string(255)
# house_number :string(255)
# created_at   :datetime
# updated_at   :datetime
# 

class User < ActiveRecord::Base
  def address
    @address ||= Address.new(prefecture, city, house_number)
  end

  def address=(address)
    self.prefecture    = address.prefecture
    self.city          = address.city
    self.house_nunmber = address.house_number
    @address = address 
  end
end

Addressクラス

class Address
  attr_accessor :prefecture, :city, :house_number

  def initialize(prefecture = nil, city = nil, house_number = nil)
    @prefecture = prefecture
    @city = city
    @house_number = house_number
  end

  def hash
    prefecture.hash + sity.hash + house_number.hash
  end

  def ==(other)
    return false unless other.is_a?(Address)
    same_prefecture?(other) && same_city?(other) && same_house_number?(other)
  end

  def same_prefecture?(other)
    prefecture == other.prefecture
  end

  def same_city?(other)
    city == other.city
  end

  def same_house_number?(other)
    house_number == other.house_number
  end
end

値オブジェクトは、責任を適切に分割することを可能にします。
もし住所を持つべきクラスが増えても、このクラスを再利用すればよいだけなので非常に便利です。

▼composed_ofでActiveRecordを値オブジェクトにマッピング

先ほどのAddressオブジェクトをcomposed_ofでマッピングしてみましょう。

# == schema information
# 
# Table name: users
#
# id           :integer
# name         :string(255)
# prefecture   :string(255)
# city         :string(255)
# house_number :string(255)
# created_at   :datetime
# updated_at   :datetime
# 

class User < ActiveRecord::Base
  composed_of :address, mapping: [%w(prefecture prefecture), %w(city city), %w(house_number house_number)]
end

Userクラスのprefecture属性に値オブジェクトであるAddressクラスのprefectureを接続しています。

Concern

Rails4からapp/models/concerns, app/controllers/concernsディレクトリが追加された。
デフォルトでは空である。RailsはここにConcernでモジュールを置くことを想定しています。

  • アプリケーション全体で利用されるロギング機能
  • 変更に対する証跡の記録
  • タグによる分類機能
  • データの論理削除や有効化、無効化処理

記述を補助してくれるActiveSupport::Concernモジュールが存在する。

サービスクラス

イメージ的にはコントローラとモデルの中間をイメージするのがよい。
一連の処理をサービスクラスに抽出することで、モデルが過剰に複雑になるのを防ぐ。

ただしサービスクラスなどを多用してしまうと、本来ビジネスロジックがモデルに集約されているのが自然なのに、それがサービスクラスに漏れ出てしまうことがある。多用注意。

Railsを拡張する

  1. RackMiddlewareを作る
  2. Railtieの仕組みを利用する
  3. その他、コマンドラインツールを作る

Rackとは、たくさんのアプリケーションサーバとRubyフレームワークをつなぐ規約のこと
PythonのPSGI規約をもとに提案されたのがRack。
WEBサーバ実装と、Rubyフレームワークの中間に存在して、お互いの差を吸収するアダプター的存在。
Rubyの標準的なオブジェクトのみで構成されているシンプルなもの。

Rackに準拠したたった二つの規約に準拠している
1. 1つのHashオブジェクトを引数に取るcallというメソッドが実装されていること
2. callメソッドは「ステータスコード」、「ヘッダーを表現したHash」、「eachに反応するオブジェクト」の3つの要素を持った配列を返すこと

慣習的にcallメソッドの仮引数の名前はenvが用いられる。

Railtieとは

Rails公式のプラグイン機構。
Railsの基本的なコンポーネントもRailtieを経由して読み込まれるようになっている。
これにより、以下の処理を簡潔に実現できます。

  1. 初期化処理を実行
  2. ジェネレータやRakeタスクをRailsに実行する
  3. Railsの設定項目を追加し、environment毎にカスタマイズ可能にする
  4. ActiveSupport::Notificationのための固有のサブスクライバを設定する

Railtie自体もrailltiesというgemで配布されている。

まとめ

実際に現プロジェクトで使用しているTipsも多く含まれており、実際の開発環境に役立てられそうだ。

特にルーティングの細かい指定方法、modelのenum型定義の便利さ、jbuilderの原理や定義方法、コールバックとバリデータのクラス分離、Addressクラスの値オブジェクトによる再利用など、普段普通に使用しているものについて頭の中で整理することができた点が良かった。

28
28
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
28
28