bbatsov/rails-style-guideが非常に参考になりそうなドキュメントだと思ったので、自分で利用しやすいように適当に日本語訳してみたのが1年以上前のことです。

この1年でRailsは4.0になり、セオリーが変わった部分も少なくありません。このガイドの原文はガンガンアップデートされていましたが、オレオレ翻訳版は更新をサボっていました。

あらためて新しい原文を見てみると、言ってることが1年前と真逆になっているような箇所もあり、これはいかんということで、更新しておきました。相変わらず英語が苦手だから自分で日本語化しておいた。という品質ですが。

当ブログ記事はエントリー時点のスナップショットです。このドキュメントはGitHubで管理していますので、最新版はGitHub miyamae/rails-style-guideをご確認ください。


序曲

役割モデルが重要なのだ。
-- アレックス・マーフィー巡査 / ロボコップ

このガイドのゴールは、Ruby on Rails 3と4開発のための1セットのベストプラクティスおよびスタイル規則を示すことです。これは、コミュニティー駆動ですでに存在する Ruby coding style guide の補足的なガイドです。

ガイドの中では「Railsアプリケーションのテスト」は「Railsアプリケーション開発」の後にあります。私は、振る舞い駆動開発 (BDD) がソフトウェアを開発する最良の方法であると本当に信じています。それを覚えておいてください。

Railsは信念の強いフレームワークです。そしてこれは信念の強いガイドです。心の中では、RSpecがTest::Unitより優れていると完全に確信しています。SassはCSSより優れています。そして、Haml (Slim)はErbより優れています。したがって、Test::Unit、CSS、Erbに関するどんな助言も、この中で見つけることは期待できません。

ここにある助言うちのいくつかは、Rails 3.1以上でのみ適用できます。

Transmuterを使用して、PDFあるいはこのガイドのHTMLコピーを生成することができます。

このガイドには次の言語の翻訳版があります。

目次

Railsアプリケーション開発

設定

  • config/initializersにカスタム初期化コードを入れてください。initializersの中のコードはアプリケーション起動時に実行されます。
  • 各gemの初期化コードは、gemと同じ名前の個別のファイルを維持してください。例えば、carrierwave.rbactive_admin.rbなどです。
  • development、test、productionの各環境の設定 (config/environments/の下の対応するファイル) に従って調節します。

    • (もしあれば) プリコンパイルの追加アセットを記します。

        # config/environments/production.rb
        # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
        config.assets.precompile += %w( rails_admin/rails_admin.css rails_admin/rails_admin.js )
      
  • config/application.rb では、すべての環境に適用可能な設定をしてください。

  • production環境に似た、staging環境を追加で作成してください。

ルーティング

  • RESTfulリソース (本当に必要ですか?) に対して、さらにアクションを追加する必要がある場合、memberおよびcollectionルートを使用します。

    # 悪い
    get 'subscriptions/:id/unsubscribe'
    resources :subscriptions
    
    # 良い
    resources :subscriptions do
      get 'unsubscribe', on: :member
    end
    
    # 悪い
    get 'photos/search'
    resources :photos
    
    # 良い
    resources :photos do
      get 'search', on: :collection
    end
    
  • 複数のmember/collectionルートを定義する必要がある場合は、代替ブロック・シンタックスを使用します。

    resources :subscriptions do
      member do
        get 'unsubscribe'
        # more routes
      end
    end
    
    resources :photos do
      collection do
        get 'search'
        # more routes
      end
    end
    
  • ActiveRecordモデルの関係をよりよく表現するために、入れ子のルートを使用してください。

    class Post < ActiveRecord::Base
      has_many :comments
    end
    
    class Comments < ActiveRecord::Base
      belongs_to :post
    end
    
    # routes.rb
    resources :posts do
      resources :comments
    end
    
  • 関連するアクションをグループ化するためにnamespaceを使用してください。

    namespace :admin do
      # Directs /admin/products/* to Admin::ProductsController
      # (app/controllers/admin/products_controller.rb)
      resources :products
    end
    
  • 古い記法のワイルド・コントローラ・ルートを使用しないでください。このルートはGETリクエストによってアクセス可能なすべてのコントローラの中ですべてのアクションを生成します。

    # とても悪い
    match ':controller(/:action(/:id(.:format)))'
    
  • どのようなルート定義でもmatchを使用しないでください。これはRails 4で廃止されました。

コントローラ

  • コントローラは皮だけの状態を保ってください。これらは単にビュー層のためのデータを取り出すだけのものであるべきであり、ビジネスロジックを含むべきではありません。(すべてのビジネスロジックは、当然モデルに存在するべきです)
  • それぞれのコントローラー・アクションは、初期のfindとnew以外、(理想的には) 1つのメソッドだけを起動するべきです。
  • コントローラーとビューの間の変数の共有は、2つを超えない範囲にしてください。

モデル

  • 非ActiveRecordモデルのクラスは自由に導入してください。
  • モデルには、略語のない意味のある (しかし短い) 名前を付けてください。
  • ActiveRecordのバリデーションのような振る舞いをサポートするモデルオブジェクトが必要な場合は、ActiveAttr gemを使用してください。

    class Message
      include ActiveAttr::Model
    
      attribute :name
      attribute :email
      attribute :content
      attribute :priority
    
      attr_accessible :name, :email, :content
    
      validates :name, presence: true
      validates :email, format: { with: /\A[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}\z/i }
      validates :content, length: { maximum: 500 }
    end
    

    より完全な例は RailsCast on the subject を参照してください。

ActiveRecord

  • 非常に十分な理由 (あなたの管理下にないデータベースを使用する場合など) がない限りは、ActiveRecordデフォルト (テーブル名、主キーなど) を変更しないようにしてください。

    # 悪い - スキーマを修正できるのなら、このようにしないでください。
    class Transaction < ActiveRecord::Base
      self.table_name = 'order'
      ...
    end
    
  • マクロスタイルのメソッド (has_many, validates, など) はクラス定義の始めにまとめてください。

    class User < ActiveRecord::Base
      # デフォルトスコープは最初に(あれば)
      default_scope { where(active: true) }
    
      # 続いて定数
      GENDERS = %w(male female)
    
      # その後attr関係のマクロを置きます
      attr_accessor :formatted_date_of_birth
    
      attr_accessible :login, :first_name, :last_name, :email, :password
    
      # 関連マクロが続きます
      belongs_to :country
    
      has_many :authentications, dependent: :destroy
    
      # そしてバリデーションマクロ
      validates :email, presence: true
      validates :username, presence: true
      validates :username, uniqueness: { case_sensitive: false }
      validates :username, format: { with: /\A[A-Za-z][A-Za-z0-9._-]{2,19}\z/ }
      validates :password, format: { with: /\A\S{8,128}\z/, allow_nil: true}
    
      # 次にコールバックです
      before_save :cook
      before_save :update_username_lower
    
      # その他のマクロ(deviseなど)はコールバックの後に置かれるべきです
    
      ...
    end
    
  • has_and_belongs_to_manyよりもhas_many :throughを好んでください。has_many :throughを使うことは、結合モデルに対して追加の属性とバリデーションを許可します。

    # has_and_belongs_to_many を使用
    class User < ActiveRecord::Base
      has_and_belongs_to_many :groups
    end
    
    class Group < ActiveRecord::Base
      has_and_belongs_to_many :users
    end
    
    # 好ましい方法 - has_many :through を使用
    class User < ActiveRecord::Base
      has_many :memberships
      has_many :groups, through: :memberships
    end
    
    class Membership < ActiveRecord::Base
      belongs_to :user
      belongs_to :group
    end
    
    class Group < ActiveRecord::Base
      has_many :memberships
      has_many :users, through: :memberships
    end
    
  • read_attribute(:attribute)よりもself[:attribute]を好んでください。

    # 悪い
    def amount
      read_attribute(:amount) * 100
    end
    
    # 良い
    def amount
      self[:amount] * 100
    end
    
  • 常に新しい "sexy" validations を使用してください。

    # 悪い
    validates_presence_of :email
    
    # 良い
    validates :email, presence: true
    
  • カスタムバリデーションが2度以上使用されるか、バリデーションが正規表現マッチングである場合は、カスタムバリデータファイルを作成してください。

    # 悪い
    class Person
      validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
    end
    
    # 良い
    class EmailValidator < ActiveModel::EachValidator
      def validate_each(record, attribute, value)
        record.errors[attribute] << (options[:message] || 'is not a valid email') unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      end
    end
    
    class Person
      validates :email, email: true
    end
    
  • カスタムバリデータはapp/validatorsに置いてください。
  • あなたが複数の関連するアプリケーションをメンテンスしているか、バリデータが十分に一般的であるならば、共有されるgemへカスタムバリデータを抽出することを検討してください。
  • named scopeは自由に使用してください。

    class User < ActiveRecord::Base
      scope :active, -> { where(active: true) }
      scope :inactive, -> { where(active: false) }
    
      scope :with_orders, -> { joins(:orders).select('distinct(users.id)') }
    end
    
  • 遅延初期化のためにnamed scopeをlambdasで包んでください。(Rails 3では単なる処方箋ですが、Rails 4では必須です。)

    # 悪い
    class User < ActiveRecord::Base
      scope :active, where(active: true)
      scope :inactive, where(active: false)
    
      scope :with_orders, joins(:orders).select('distinct(users.id)')
    end
    
    # 良い
    class User < ActiveRecord::Base
      scope :active, -> { where(active: true) }
      scope :inactive, -> { where(active: false) }
    
      scope :with_orders, -> { joins(:orders).select('distinct(users.id)') }
    end
    
  • ラムダとパラメータを使用したnamed scope定義が複雑になる場合、named scopeと同じ目的のために、代わりにActiveRecord::Relationオブジェクトを返すクラスメソッドを作るのは望ましいことです。恐らくこのようにさらに単純なscopeを定義することができます。

    class User < ActiveRecord::Base
      def self.with_orders
        joins(:orders).select('distinct(users.id)')
      end
    end
    
  • update_attributeメソッドの振る舞いに用心してください。これはモデルバリデーションを実行せず (update_attributesと異なる)、容易にモデルの状態を悪くするかもしれません。このメソッドは最終的にRails 3.2.7で非推奨となり、Rails 4では存在しません。

  • ユーザー・フレンドリーなURLを使用してください。URLの中ではモデルのidではなく、モデルの記述的な属性を表示してください。このためのいくつかの方法があります。

    • to_paramメソッドをオーバーライドしてください。これはオブジェクトへのURLを構築するためにRailsによって使用されます。デフォルト実装では、レコードのidを文字列として返します。他の「人間が判読可能な」属性を含めるために、これをオーバーライドすることができます。

        class Person
          def to_param
            "#{id}#{name}".parameterize
          end
        end
      

      URLフレンドリーな値に変換するために、文字列のparameterizeを呼ぶ必要があります。ActiveRecordのfindメソッドで見つけることができるように、オブジェクトのidが始めにある必要があります。

    • friendly_id gemを使用してください。これは、そのidの代わりにモデルの記述的な属性を使用することによって、人間が判読可能なURLを生成できるようにします。

        class Person
          extend FriendlyId
          friendly_id :name, use: :slugged
        end
      

      使い方について、より詳細は gemドキュメント を確認してください。

マイグレーション

  • schema.rb (またはstructure.sql)はバージョン管理下に置いてください。
  • 空のデータベースを初期化するために、rake db:migrateの代わりにrake db:schema:loadを使用してください。
  • テストデータベースのスキーマをアップデートするにはrake db:test:prepareを使用してください。
  • テーブル自体にデフォルトを設定しないようにしてください。モデル層を代わりに使用してください。
  • アプリケーション層での代わりに、マイグレーションでのデフォルト値を強制してください。

    # 悪い - アプリケーションで強制したデフォルト値
    def amount
      self[:amount] or 0
    end
    

    Rails上でのみデフォルトを設定する手法は、多くのRails開発者によって提案されましたが、脆弱にデータを残す多くのアプリケーションのバグに対して非常に脆いアプローチです。     そのようにRailsのアプリケーションからのデータの整合性を課すことは、ほとんどの普通でないアプリが他のアプリケーションとデータベースを共有することを考慮することは不可能です。

  • 外部キー制約を強制してください。ActiveRecordはネイティブにはサポートしていませんが、schema_plusforeignerのようないくつかの素晴らしいサードパーティのgemsがあります。

  • 構造変更的なマイグレーションを書くとき (テーブルやカラムの追加など) の、新しいRails 3.1における方法は、 - updownメソッドの代わりに、changeメソッドを使用することです。

    # 古い方法
    class AddNameToPeople < ActiveRecord::Migration
      def up
        add_column :people, :name, :string
      end
    
      def down
        remove_column :people, :name
      end
    end
    
    # 好ましい新しい方法
    class AddNameToPeople < ActiveRecord::Migration
      def change
        add_column :people, :name, :string
      end
    end
    
  • マイグレーションではモデルクラスを使用しないでください。モデルクラスは絶えず進化します。将来のマイグレーションのあるポイントで、変更されたモデルを使用したことが原因で停止する場合があります。

ビュー

  • ビューからモデル層を直接呼び出さないでください。
  • ビューの中で複雑な体裁出力を作らないでください。ビューヘルパー、またはモデルのメソッドに体裁を出してください。
  • 部分テンプレートやレイアウトを使用してコードの重複を軽減させてください。

国際化

  • 文字列や他のロケール固有の設定は、ビュー、モデル、コントローラで使用するべきではありません。これらのテキストはconfig/localesディレクトリ内のロケールファイルに移動するべきです。
  • ActiveRecordモデルのラベルを翻訳する必要がある場合は、activerecordスコープを使用してください:

    en: activerecord: models: user: Member attributes: user: name: "Full name" 

    User.model_name.humanは "Member" を返し、User.human_attribute_name("name")は "Full name" を返します。これらの属性の翻訳は、ビューのラベルとして使用されます。

  • ActiveRecord属性の翻訳からのビューで使用されるテキストを分離してください。modelsフォルダの中にはモデル、viewsフォルダの中にはビューで使用されるテキスト用のローカルファイルを置いてください。

    • 追加ディレクトリからのローカルファイルの編成が完了したということは、これらのディレクトリはロードされるためにapplication.rbファイルで定義されているはずです。

        # config/application.rb
        config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
      
  • localesディレクトリのルート下のファイルに、日付や通貨の書式のような共有される地域化オプションを置いてください。

  • I18nメソッドの短縮形を使用してください。I18n.translateの代わりにI18n.tI18n.localizeの代わりにI18n.lです。
  • ビューで使われるテキストに "lazy" ルックアップを使用してください。次のような構造です。

    en: users: show: title: "User details page" 

    users.show.titleの値は、app/views/users/show.html.hamlテンプレートで次のように得ることができます。

    = t '.title'
    
  • :scopeオプションを指定する代わりに、コントローラとモデルをドットで区切ったキーを使用してください。ドットで区切った呼び出しは、読みやすく、階層のトレースがより簡単です。

    # 方法
    I18n.t 'activerecord.errors.messages.record_invalid'
    
    # 代わりの方法
    I18n.t :record_invalid, :scope => [:activerecord, :errors, :messages]
    
  • Rails I18nのより詳細な情報は以下で見つけることができます。Rails Guides

アセット

アプリケーション内の構成にてこ入れするためにアセットパイプラインを使用してください。

  • カスタムのstyleshees, javascripts, imagesのためにapp/assetsを使用してください。
  • あなたが所有するライブラリにはlib/assetsを使用してください。アプリケーションのスコープに入りません。
  • jQuerybootstrap のような第三者のコードはvendor/assetsに置かれるべきです。
  • 可能な場合は、アセットをgem化したバージョンを使用してください。(例:jquery-rails, jquery-ui-rails, bootstrap-sass, zurb-foundation). )

Mailer

  • Mailerの名前はSomethingMailerとしてください。接尾辞のないMailerでは、どれがMailerか、どのビューがMailerと関係があるか、明白ではありません。
  • HTMLとプレーンテキストの両方のビューテンプレートを提供してください。
  • development環境中でメール配信に失敗した際のエラー発生を有効にしてください。エラーはデフォルトでは無効です。

    # config/environments/development.rb
    
    config.action_mailer.raise_delivery_errors = true
    
  • development環境ではSMTPサーバにsmtp.gmail.comを使用してください。(もちろん、あなたがローカルSMTPサーバを持っていない場合です)

  • development環境では Mailcatcher のようなローカルSMTPサーバを使用してください。

    # config/environments/development.rb
    
    config.action_mailer.smtp_settings = {
      address: 'localhost',
      port: 1025,
      # more settings
    }
    
  • ホスト名にデフォルト設定を与えてください。

    # config/environments/development.rb
    config.action_mailer.default_url_options = { host: "#{local_ip}:3000" }
    
    # config/environments/production.rb
    config.action_mailer.default_url_options = { host: 'your_site.com' }
    
    # in your mailer class
    default_url_options[:host] = 'your_site.com'
    
  • メールの中でサイトへのリンクを使用する必要がある場合は、_pathメソッドではなく、常に_urlを使用してください。_urlメソッドはホスト名を含んでいますが、_pathメソッドは含んでいません。

    # 間違い
    You can always find more info about this course
    = link_to 'here', url_for(course_path(@course))
    
    # 正しい
    You can always find more info about this course
    = link_to 'here', url_for(course_url(@course))
    
  • FromとToのアドレスに適切な書式を使ってください。次のような書式を使ってください。

    # in your mailer class
    default from: 'Your Name <info@your_site.com>'
    
  • test環境のためのメールdelivery methodにはtestが設定されていることを確認してください。

    # config/environments/test.rb
    
    config.action_mailer.delivery_method = :test
    
  • developmentとproduction環境のためのメールdelivery methodにはsmtpが設定されていることを確認してください。

    # config/environments/development.rb, config/environments/production.rb
    
    config.action_mailer.delivery_method = :smtp
    
  • いくつかのメールクライアントが外部スタイルの問題を抱えているため、HTMLメールを送るときは、すべてのスタイルがインラインでなければなりません。しかしながら、これはメンテンナンスを困難にし、コードの重複にもつながります。スタイルを変換し、それを対応するHTMLタグに挿入するための、2つの同じようなgemがあります。premailer-railsroadie です。

  • ページレスポンスを生成する間にメールを送ることは避けるべきです。ページの読み込みに遅延が発生しますし、もし複数のメールが送信されるのなら、リクエストがタイムアウトする場合があります。これを克服するためには、sidekiq gemの助けを借りてバックグラウンドプロセスで送信します。

Bundler

  • 開発またはテストのみ使用されるgemは、Gemfileの適切なグループの中に置いてください。
  • プロジェクトでは定評のあるgemだけ使用してください。ほとんど知られていないgemを含めることを検討しているのなら、そのソースコードの注意深い調査を最初に行うべきです。
  • OS特有のgemは、異なるOSを使用している複数の開発者と一緒のプロジェクトのために、頻繁に変わるGemfile.lockによって得ます。OS Xの特有のgemをすべてGemfileの中のdarwinグループに、Linux特有のgemをlinuxグループに加えてください。

    # Gemfile
    group :darwin do
      gem 'rb-fsevent'
      gem 'growl'
    end
    
    group :linux do
      gem 'rb-inotify'
    end
    

    正しい環境で適切なgemを要求するためには、config/application.rbに以下を加えてください。

    platform = RUBY_PLATFORM.match(/(linux|darwin)/)[0].to_sym
    Bundler.require(platform)
    
  • バージョン管理からGemfile.lockを取り除かないでください。これは任意に生成されたファイルではありません。- これはbundle installした時に、チームメンバーが確実にすべて同じgemバージョンを得るためのものです。

有用なgem

最も重要なプログラミングの法則の1つはこうです。「車輪を再発明するな!」。あるタスクに直面した時には、自分のものを広げる前に、常に既存の解決策を見つけるために周りを見回すべきです。ここにあるリストは、多くのRailsプロジェクトに役立つ「有用な」gemです。(すべてRails 3.1準拠)

  • active_admin - ActiveAdminがあれば、Railsアプリにおける管理者インタフェースの作成は子供の遊びのようなものです。素敵なダッシュボード、CRUD UI、その他いろいろ。とても柔軟でカスタマイズ可能です。
  • better_errors - Better ErrorsはRailsのエラーページをより良く有益なページに取り替えます。Rackミドルウェアとして、Railsの外で使用できます。
  • bullet - The Bulletは、アプリケーションが作るクエリの数を減らすことによって、パフォーマンス向上に役立つように設計されています。あなたがアプリケーションを開発している間、一括読み込み(N+1クエリ)をいつ加えなければならないか、また、いつ必要でない一括読み込みを使用するか、いつカウンタキャッシュを使用しなければならないかを通知します。
  • cancan - CanCanは、リソースへのユーザーのアクセスを制限させることができる認証gemです。べての権限は、単一のファイル(ability.rb)で定義され、アクセス権をチェックし確保するための便利な方法は、アプリケーション全体で利用できます。
  • capybara - Capybaraは、Rails、Sinatra、MerbのようなRackアプリケーションにおける統合テストのプロセスを単純化することを目標としています。CapybaraはリアルユーザーのWebアプリケーションとの対話をシミュレートします。それはテストを実行するドライバーに関して不可知論者で、Rack::Testに付属し、Seleniumをビルトインでサポートしています。HtmlUnit、WebKit、env.jsは外部gemによってサポートされます。RSpecとCucmberとのコンビネーションで素晴らしい仕事をします。
  • carrierwave - Railsにおける究極のファイルアップロードソリューション。アップロードファイルはローカルと、クラウドストレージの両方をサポートします。画像の後処理のためにImageMagickと素晴らしく統合します。
  • compass-rails - いくつかのCSSフレームワークのサポートを追加する、素晴らしいgem。CSSファイルのコードが削減され、ブラウザ非互換性との戦いを助けるsaas mixinsのコレクションが含まれています。
  • cucumber-rails - Cucumber はRubyで機能テストを開発するプレミアムツールです。cucumber-railsは、RailsへのCucumberの統合を提供します。
  • devise - Deviseは、Railsアプリケーションのフル機能の認証ソリューションです。 カスタム認証ソリューションを展開するほとんどの場合、Deviseの使用が適しています。
  • fabrication - 素晴らしいfixtureの代替 (editor's choice).
  • factory_girl - fabricationの代わり。とても成熟したfixtureの代替です。fabricationの精神を持っています。
  • ffaker - ダミーデータを生成する手軽なgem。(氏名、住所、その他)
  • feedzirra - とても高速で柔軟なRSS/Atomフィードのパーサー。
  • friendly_id - モデルのIDの代わりに記述的な属性を使って、人間が読めるURLを生成します
  • globalize - ActiveRecordのモデル/データ変換のためのRails国際化のデファクトスタンダードのライブラリです。Railsのグローバル化とActiveRecordのバージョン4.xを対象としています。これは、Ruby on Railsの新しいI18n APIと互換性があり、ActiveRecordのためのモデルの変換が追加されます。ActiveRecord 3.xのユーザーは、3-0-stable branch を確認してください。
  • guard - ファイルの変更を監視し、それに基づいたタスクを起動する、素晴らしいgemです。多くの有用な拡張が載せられました。自動テストとwatchrよりはるかに優れています。
  • haml-rails - haml-railsは、HamlのRailsへの統合を提供します。
  • haml - HAMLは簡潔なテンプレート言語で、多くの人に (あなたも含まれます) Erbよりはるかに優れいていると考えられています。
  • kaminari - 素晴らしいページングソリューション。
  • machinist - fixtureは楽しくありません。mechanistなら。
  • rspec-rails - RSpecはTest::MiniTestの代替です。私はRSpecを十分に強く推奨することができません。rspec-railsは、RSpecのRailsへの統合を供給します。 sidekiq - SidekiqはRailsアプリでバックグラウンドジョブを実行するための、おそらく最も簡単で拡張性の高い方法です。
  • simple_form - 一度simple_form (あるいはformtastic) を使用したならば、あなたは決してRailsのデフォルト形式に関して聞かされたくありません。フォームを構築するためにマークアップに対して文句のない素晴らしいDSLを持っています。
  • simplecov-rcov - SimpleCovのためのRCovフォーマッタ。Hudsonのcontininousな統合サーバーとSimpleCovを使用しようとしているなら有用です。
  • simplecov - コードカバレッジツール。RCovと異なり、Ruby 1.9と完全に互換性をもちます。素晴らしいレポートを生成します。必携!
  • slim - Slimは簡潔なテンプレート言語です。HAMLより優れています (Erbには言及しない)。私が使用するのを止める重くて細いただ1つの理由は、主要なエディタ/IDEのサポートの不足です。そのパフォーマンスは驚異的です。
  • spork - フレームワーク (現状、RSpec/Cucumber) をテストするためのDRbサーバです。クリーンなテスト状態を保障するために各々の実行前にフォークします。簡単に言えば、多くのテスト環境を事前ロードします。その結果として、テストの最初の時間が大幅に減少します。絶対に必携。
  • sunspot - SOLRによる全文検索エンジン。

このリストは完全ではありません。また、他のgemは今後加えられるかもしれません。リストのgemはすべてフィールドテストされています。これらすべては活発な開発およびコミュニティ活動をしており、よいコード品質であることが知られています。

欠陥のgem

これは問題があるか、他のgemによって取って代わられるgemのリストです。プロジェクトの中でこれらを使用しないようにするべきです。

  • rmagick - このgemはメモリ消費で悪名高い。代わりに minimagick を使用してください。
  • autotest - テストを自動的に実行するための古いソリューションです。 guardwatchr よりも遥かに劣っています。
  • rcov - コードカバレッジツールです。Ruby 1.9と互換性がありません。SimpleCov を使用してください。
  • therubyracer - 非常に大量のメモリを使用するので、本番環境でのこのgemの使用には強く反対します。node.jsの利用を提案します。

このリストは作成途中です。他にもポピュラーであるが欠陥のあるgemを知っていれば教えてください。

プロセス管理

  • プロジェクトが様々な外部プロセスに依存している場合は、それらを管理するために foreman を使います。

Railsアプリケーションのテスト

新しい機能を実装するためのの最良のアプローチは、おそらくBDDアプローチです。いくつかのハイレベルの機能テスト (一般にCucumberを使用して書かれる) を書くことにより開始します。そして、機能の実装を洗い出すためにこれらのテストを使用します。始めに機能用のビューのspecを書き、適切なビューを作成するためにそれらのspecを使用します。後でビューにデータを与えて、コントローラを実装するために、それらのspecを使用するコントローラ用のspecを作成します。最後に、モデルspecおよびモデル自身を実装します。

Cucumber

  • 保留中のシナリオに@wip (work in progress: 作業中) タグを付けてください。これらのシナリオは無視され、失敗としてマークされません。保留中のシナリオの作業が完了し、テストするための機能性が実装されたら、テストスイートにこのシナリオを含めるために@wipタグを削除します。
  • @javascriptタグが付いたシナリオを除外するようにデフォルトのプロフィールをセットアップしてください。それらはテストにブラウザを使用します。通常のシナリオの実行速度を上げるために、それらを無効にすることをお勧めします。
  • @javascriptタグが付けられたシナリオのための別のプロフィールをセットアップしてください。

    • プロフィールはcucumber.ymlファイルで設定することができます。

        # definition of a profile:
        profile_name: --tags @tag_name
      
    • プロフィールは次のコマンドで実行されます。

       cucumber -p profile_name 
  • fixtureの代わりに fabrication 使用している場合は、あらかじめ定義された fabrication steps を使用してください。

  • 古いweb_steps.rbステップ定義を使わないでください!WebステップはCucumberの最新バージョンで削除されています。その使い方は、適切にアプリケーションのドメインを反映しない冗長なシナリオの作成につながります。
  • 要素idではなく、可視のテキスト (リンク、ボタンなど) で要素の存在を調べる場合。これは、i18nに関する問題を見つけることができます。
  • 同じ種類のオブジェクトの異なる機能のために、個別のフィーチャを作ってください:

    # 悪い
    Feature: Articles
    # ... フィーチャ実装 ...
    
    # 良い
    Feature: Article Editing
    # ... フィーチャ実装 ...
    
    Feature: Article Publishing
    # ... フィーチャ実装 ...
    
    Feature: Article Search
    # ... フィーチャ実装 ...
    
    
  • 各々のフィーチャには3つの主成分があります。

    • タイトル
    • ナラティブ (物語) - フィーチャについての短い説明。
    • 合格基準 - 個々のステップから構成されたシナリオのセット。
  • 最も一般的なフォーマットがConnextraフォーマットとして知られています。

    In order to [benefit] ...
    A [stakeholder]...
    Wants to [feature] ...
    

このフォーマットは最も一般的なものですが、必須ではありません、ナラティブはフィーチャの複雑さに応じて自由なテキストになりえます。

  • シナリオのDRYを維持するために、シナリオアウトラインを自由に使用してください。

    Scenario Outline: User cannot register with invalid e-mail
      When I try to register with an email "<email>"
      Then I should see the error message "<error>"
    
    Examples:
      |email         |error                 |
      |              |The e-mail is required|
      |invalid email |is not a valid e-mail |
    
  • シナリオ用のステップはstep_definitionsディレクトリの下の.rbファイルにあります。ステップファイルのファイル名命名規則は[description]_steps.rbです。ステップは異なる基準に基づいた異なるファイルへ分けることができます。各フィーチャ (home_page_steps.rb) のために1ステップのファイルを持つことは可能です。また、特定のオブジェクト(articles_steps.rb) のためにすべてのフィーチャの1ステップのファイルがさらにある場合もあります。

  • 繰り返しを回避するために複数行ステップ引数を使用してください。

    Scenario: User profile
      Given I am logged in as a user "John Doe" with an e-mail "user@test.com"
      When I go to my profile
      Then I should see the following information:
        |First name|John         |
        |Last name |Doe          |
        |E-mail    |user@test.com|
    
    # the step:
    Then /^I should see the following information:$/ do |table|
      table.raw.each do |field, value|
        find_field(field).value.should =~ /#{value}/
      end
    end
    

RSpec

  • exampleごとに1つの期待値だけを使用してください。

    # 悪い
    describe ArticlesController do
      #...
    
      describe 'GET new' do
        it 'assigns new article and renders the new article template' do
          get :new
          assigns[:article].should be_a_new Article
          response.should render_template :new
        end
      end
    
      # ...
    end
    
    # 良い
    describe ArticlesController do
      #...
    
      describe 'GET new' do
        it 'assigns a new article' do
          get :new
          assigns[:article].should be_a_new Article
        end
    
        it 'renders the new article template' do
          get :new
          response.should render_template :new
        end
      end
    
    end
    
  • describecontextを多用してください。

  • describeブロックの命名は次のようにしてください。

    • メソッド以外は "description" とする。
    • メソッドには "#method" 「#」を使う。
    • クラスメソッドには ".method" 「.」を使う。

      class Article
        def summary
          #...
        end
      
        def self.latest
          #...
        end
      end
      
      # the spec...
      describe Article do
        describe '#summary' do
          #...
        end
      
        describe '.latest' do
          #...
        end
      end
      
  • テストオブジェクトの生成には fabricators を使用してください。

  • モックとスタブを多用してください。

    # モデルをモック
    article = mock_model(Article)
    
    # メソッドをスタブ
    Article.stub(:find).with(article.id).and_return(article)
    
  • モデルをモックする場合、as_null_objectメソッドを使用してください。これは、期待するメッセージにのみをリスニングし、他のメッセージを無視するように出力に命じます。

    article = mock_model(Article).as_null_object
    
  • exampleのためにデータを作る際は、before(:each)ブロックの代わりにletブロックを使用してください。

    # こうしてください
    let(:article) { Fabricate(:article) }
    
    # ... これよりも
    before(:each) { @article = Fabricate(:article) }
    
  • できればsubjectを使用してください。

    describe Article do
      subject { Fabricate(:article) }
    
      it 'is not published on creation' do
        subject.should_not be_published
      end
    end
    
  • できればspecifyを使用してください。これはitの同意語ですが、docstringがない場合、より読みやすいです。

    # 悪い
    describe Article do
      before { @article = Fabricate(:article) }
    
      it 'is not published on creation' do
        @article.should_not be_published
      end
    end
    
    # 良い
    describe Article do
      let(:article) { Fabricate(:article) }
      specify { article.should_not be_published }
    end
    
  • できればitsを使用してください。

    # 悪い
    describe Article do
      subject { Fabricate(:article) }
    
      it 'has the current date as creation date' do
        subject.creation_date.should == Date.today
      end
    end
    
    # 良い
    describe Article do
      subject { Fabricate(:article) }
      its(:creation_date) { should == Date.today }
    end
    
  • 他の多くのテストで共有することができるspecグループを作成したい場合は、shared_examplesを使用してください。

    # 悪い
    describe Array do
      subject { Array.new [7, 2, 4] }
    
      context "initialized with 3 items" do
        its(:size) { should eq(3) }
      end
    end
    
    describe Set do
      subject { Set.new [7, 2, 4] }
    
      context "initialized with 3 items" do
        its(:size) { should eq(3) }
      end
    end
    
    # 良い
    shared_examples "a collection" do
      subject { described_class.new([7, 2, 4]) }
    
      context "initialized with 3 items" do
        its(:size) { should eq(3) }
      end
    end
    
    describe Array do
      it_behaves_like "a collection"
    end
    
    describe Set do
      it_behaves_like "a collection"
    end
    

ビュー

  • ビューのspecspec/viewsのディレクトリ構造は、app/viewsの中のものと一致します。 例えば、app/views/usersの中のspecはspec/views/usersに置かれます。
  • ビューのspecの命名規則はビューの名前に_spec.rbを視界名に加えたものです。例えば、ビュー_form.html.hamlには対応するspec_form.html.haml_spec.rbがあります。
  • spec_helper.rbは各ビューspecファイルの中で要求されるために必要です。
  • 外側のdescribeブロックには、app/viewsなしのビューへのパスを使用します。引数なしで呼ばれた場合、renderメソッドによって使用されます。

    # spec/views/articles/new.html.haml_spec.rb
    require 'spec_helper'
    
    describe 'articles/new.html.haml' do
      # ...
    end
    
  • ビューspec中では常にモデルをモックしてください。ビューの目的は情報を単に表示することだけです。

  • assignメソッドは、コントローラーによって供給されビューが使用する変数を供給します。

    # spec/views/articles/edit.html.haml_spec.rb
    describe 'articles/edit.html.haml' do
    it 'renders the form for a new article creation' do
      assign(
        :article,
        mock_model(Article).as_new_record.as_null_object
      )
      render
      rendered.should have_selector('form',
        method: 'post',
        action: articles_path
      ) do |form|
        form.should have_selector('input', type: 'submit')
      end
    end
    
  • should_not肯定よりもCapybaraの否定セレクタを使ってください。

    # 悪い
    page.should_not have_selector('input', type: 'submit')
    page.should_not have_xpath('tr')
    
    # 良い
    page.should have_no_selector('input', type: 'submit')
    page.should have_no_xpath('tr')
    
  • ビューがヘルパーメソッドを使用する場合、これらのメソッドをスタブする必要があります。ヘルパーメソッドのスタブはtemplateオブジェクト上で行われます。

    # app/helpers/articles_helper.rb
    class ArticlesHelper
      def formatted_date(date)
        # ...
      end
    end
    
    # app/views/articles/show.html.haml
    = "Published at: #{formatted_date(@article.published_at)}"
    
    # spec/views/articles/show.html.haml_spec.rb
    describe 'articles/show.html.haml' do
      it 'displays the formatted date of article publishing' do
        article = mock_model(Article, published_at: Date.new(2012, 01, 01))
        assign(:article, article)
    
        template.stub(:formatted_date).with(article.published_at).and_return('01.01.2012')
    
        render
        rendered.should have_content('Published at: 01.01.2012')
      end
    end
    
  • ヘルパーのspecはビューspecから分けられ、spec/helpersディレクトリに置かれます。

コントローラ

  • モデルをモックしてメソッドをスタブしてください。コントローラーのテストはモデル生成に左右されるべきではありません。
  • コントローラーが責任を負うべき振る舞いだけをテストしてください:

    • 特別なメソッドを実行してください。
    • アクションから返されたデータ - assigns、など
    • アクションからの結果 - template、render、redirect、など

        # Example of a commonly used controller spec
        # spec/controllers/articles_controller_spec.rb
        # We are interested only in the actions the controller should perform
        # So we are mocking the model creation and stubbing its methods
        # And we concentrate only on the things the controller should do
      
        describe ArticlesController do
          # The model will be used in the specs for all methods of the controller
          let(:article) { mock_model(Article) }
      
          describe 'POST create' do
            before { Article.stub(:new).and_return(article) }
      
            it 'creates a new article with the given attributes' do
              Article.should_receive(:new).with(title: 'The New Article Title').and_return(article)
              post :create, message: { title: 'The New Article Title' }
            end
      
            it 'saves the article' do
              article.should_receive(:save)
              post :create
            end
      
            it 'redirects to the Articles index' do
              article.stub(:save)
              post :create
              response.should redirect_to(action: 'index')
            end
          end
        end
      
  • アクションが受信パラメータに応じて異なる振る舞いをする場合、contextを使用してください。

    # A classic example for use of contexts in a controller spec is creation or update when the object saves successfully or not.
    
    describe ArticlesController do
      let(:article) { mock_model(Article) }
    
      describe 'POST create' do
        before { Article.stub(:new).and_return(article) }
    
        it 'creates a new article with the given attributes' do
          Article.should_receive(:new).with(title: 'The New Article Title').and_return(article)
          post :create, article: { title: 'The New Article Title' }
        end
    
        it 'saves the article' do
          article.should_receive(:save)
          post :create
        end
    
        context 'when the article saves successfully' do
          before { article.stub(:save).and_return(true) }
    
          it 'sets a flash[:notice] message' do
            post :create
            flash[:notice].should eq('The article was saved successfully.')
          end
    
          it 'redirects to the Articles index' do
            post :create
            response.should redirect_to(action: 'index')
          end
        end
    
        context 'when the article fails to save' do
          before { article.stub(:save).and_return(false) }
    
          it 'assigns @article' do
            post :create
            assigns[:article].should be_eql(article)
          end
    
          it 're-renders the "new" template' do
            post :create
            response.should render_template('new')
          end
        end
      end
    end
    

モデル

  • specではモデルをモックしないでください。
  • 本物のオブジェクトを作るためにfabricationを使用してください。
  • 他のモデルや子オブジェクトをモックすることは容認できます。
  • 重複を避けるために、specにすべてのexampleのためのモデルを作成してください。

    describe Article do
      let(:article) { Fabricate(:article) }
    end
    
  • 作ったモデルが有効であることを保証するexampleを加えてください。

    describe Article do
      it 'is valid with valid attributes' do
        article.should be_valid
      end
    end
    
  • バリデーションをテストする場合、検証する必要がある属性を指定するにはhave(x).errors_onを使用してください。be_validの使用は、問題が意図した属性にあることを保証するものではありません。

    # 悪い
    describe '#title' do
      it 'is required' do
        article.title = nil
        article.should_not be_valid
      end
    end
    
    # 好ましい
    describe '#title' do
      it 'is required' do
        article.title = nil
        article.should have(1).error_on(:title)
      end
    end
    
  • バリデーションされる各属性ごとに個別のdescribeを追加してください。

    describe Article do
      describe '#title' do
        it 'is required' do
          article.title = nil
          article.should have(1).error_on(:title)
        end
      end
    end
    
  • モデル属性の一意性をテストするときは、他方のオブジェクトにanother_objectという名前を付けてください。

    describe Article
      describe '#title'
        it 'is unique' do
          another_article = Fabricate.build(:article, title: article.title)
          article.should have(1).error_on(:title)
        end
      end
    end
    

Mailer

Mailer specの中のモデルはモックされるべきです。Mailerはモデル生成に依存するべきではありません。

  • Mailer specは以下のことを確認する必要があります:

    • 見出しが正しい。
    • 送信者のE-mailアドレスが正しい。
    • 受信者のE-mailアドレスが正しく記載されている。
    • メールには必要な情報が含まれている。

      describe SubscriberMailer do
       let(:subscriber) { mock_model(Subscription, email: 'johndoe@test.com', name: 'John Doe') }
      
       describe 'successful registration email' do
         subject { SubscriptionMailer.successful_registration_email(subscriber) }
      
         its(:subject) { should == 'Successful Registration!' }
         its(:from) { should == ['info@your_site.com'] }
         its(:to) { should == [subscriber.email] }
      
         it 'contains the subscriber name' do
           subject.body.encoded.should match(subscriber.name)
         end
       end
      end
      

アップローダー

  • アップローダーに関してテストすることができるものは、画像が正しくリサイズされるかどうかです。ここでは carrierwave 画像アップローダーのサンプルを示します。

    # rspec/uploaders/person_avatar_uploader_spec.rb
    require 'spec_helper'
    require 'carrierwave/test/matchers'
    
    describe PersonAvatarUploader do
      include CarrierWave::Test::Matchers
    
      # Enable images processing before executing the examples
      before(:all) do
        UserAvatarUploader.enable_processing = true
      end
    
      # Create a new uploader. The model is mocked as the uploading and resizing images does not depend on the model creation.
      before(:each) do
        @uploader = PersonAvatarUploader.new(mock_model(Person).as_null_object)
        @uploader.store!(File.open(path_to_file))
      end
    
      # Disable images processing after executing the examples
      after(:all) do
        UserAvatarUploader.enable_processing = false
      end
    
      # Testing whether image is no larger than given dimensions
      context 'the default version' do
        it 'scales down an image to be no larger than 256 by 256 pixels' do
          @uploader.should be_no_larger_than(256, 256)
        end
      end
    
      # Testing whether image has the exact dimensions
      context 'the thumb version' do
        it 'scales down an image to be exactly 64 by 64 pixels' do
          @uploader.thumb.should have_dimensions(64, 64)
        end
      end
    end
    
    

参考文献

Railsのスタイルには優れたリソースがあります。時間があるなら検討してみてください。

貢献

このガイドの中で書かれた何も石の中でセットされていません。これはRailsコーディングスタイルに興味を持っている皆と一緒に働きたいという私の願望です。だから私たちはRubyコミュニティ全体に有益となるリソースを作成することができました。

改善のためにチケットやPull Requestを自由に送ってください。あなたの助力に感謝します!

ライセンス

Creative Commons License This work is licensed under a Creative Commons Attribution 3.0 Unported License

Spread the Word

コミュニティー駆動のスタイルガイドは、その存在を知らないコミュニティーにほとんど役に立ちません。ガイドに関してツイートして、友達や同僚と共有してください。私たちが得るすべてのコメントや提案、意見はガイドをわずかにより良くします。そして私たちはできるだけ最良のガイドを持ちたいですよね。

乾杯!
Bozhidar