“ロールモデルが重要なのだ。”
-- アレックス・マーフィー巡査 / 映画『ロボコップ』の登場人物
このガイドの目的は、Ruby on Rails 4 開発における コーディングスタイルのベストプラクティスを提供することです。すでに有志によって作成されたRuby coding style guideの補足資料にあたります。
記載内容の一部は Rails 4.0 以上のみを対象としています。
PDF形式やHTML形式のコピーはTransmuterを使って作成できます。
以下の言語の翻訳が利用可能です:
このガイドは、Rails開発の実務におけるコーディングスタイルのベストプラクティスを目指したものです。開発における理想論は多くありますが、このガイドはあくまで実務での利用に役立つことを目的としています。このガイドの内容は絶対ではありませんが、非常に多くのRails開発者のフィードバックや資料を基にして書かれたものです。ここで改めて、彼らへ多大な感謝を示します。
- Configuration
- Routing
- Controllers
- Models
- Migrations
- Views
- Internationalization
- Assets
- Mailers
- Bundler
- Flawed Gems
- Managing processes
-
アプリケーション起動時の処理をカスタマイズしたい場合は、
config/initializers
配下にその処理を記述したコードを配置しましょう。config/initializers
配下のコードはアプリケーション起動時に実行されます。 [link] -
gem の初期化に必要なコードは、gem 毎に作成しましょう。また、そのファイルの名前は gem の名前と同じにしましょう。例えば、
carrierwave.rb
、active_admin.rb
などです。 [link] -
Railsには3つの環境(development、test、 production)がありますが、環境毎に適切な設定をしましょう。 (
config/environments/
配下にそれぞれの環境に対応した設定ファイルが配置してあります。) [link]-
もしプリコンパイルを行う場合は、追加するアセット名を明記しましょう。:
# 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
に記述しましょう. [link] -
production
環境と同環境のstaging
環境を作成しておきましょう。 [link]
-
もしRESTfulなresourceにアクションを追加する場合(そのようなアクションが本当に必要かはわかりませんが)、
member
とcollection
を利用しましょう。 [link]# 悪い例 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
を定義しなければいけない場合は、ブロック構文を利用しましょう。[link]resources :subscriptions do member do get 'unsubscribe' # more routes end end resources :photos do collection do get 'search' # more routes end end
-
ActiveRecordのモデル間の関連を表現するには、入れ子型でルートを定義すると分かりやすいでしょう。[link]
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
を利用しましょう。[link]namespace :admin do # Directs /admin/products/* to Admin::ProductsController # (app/controllers/admin/products_controller.rb) resources :products end
-
Railsに昔あった古いルーティング記法は絶対に利用しないようにしましょう。この記法を利用した場合、すべてのアクションへのすべてのGETリクエストを許可してしまいます。[link]
# 非常に悪い例 match ':controller(/:action(/:id(.:format)))'
-
match
を利用しないようにしましょう。もし利用する必要がある場合は、アクション毎に:via
オプションで[:get, :post, :patch, :put, :delete]
を指定しましょう。 [link]
-
ビジネスロジックは controller でなく model に書きましょう。controller の役割は、view層にデータを渡すこと、またはview層からデータを受け取ることのいずれかのみです。それ以外のコードは controller に記述しないようにしましょう。[link]
-
(理想論ですが)すべての controller は find と new 以外のメソッドはひとつ程度に留められるようにしましょう。[link]
-
controller と view の間でやりとりするインスタンス変数は2つまでに留めておきましょう。[link]
-
ActiveRecord を継承しないモデルも導入しましょう。 [link]
-
モデルの名前は、意味が通じてなおかつ短いものにしましょう。その際には省略語は利用しないようにしましょう。 [link]
-
ActiveRecordのような振る舞い(validationなど)が必要なモデルには、ActiveAttr のような gem を使いましょう. [link]
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 の初期値(テーブル名、主キー等)を変更しないようにしましょう。 [link]
# 悪い例 class Transaction < ActiveRecord::Base self.table_name = 'order' ... end
-
has_many
やvalidates
などはクラス定義の最初の方に記述しましょう。(satour注:本文と例が一致していません@原文) [link]class User < ActiveRecord::Base # 一番上にデフォルト・スコープを記述する。 default_scope { where(active: true) } # 定数を記述する。 COLORS = %w(red green blue) # 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 # 上記以外のマクロがある場合、コールバックの下に続けて記述する。 ... end
-
なるべく
has_and_belongs_to_many
よりhas_many :through
を利用しましょう。has_many :through
を使うと model を join する際に属性やヴァリデーションを利用することが出来ます。 [link]# あまり良くない例 class User < ActiveRecord::Base has_and_belongs_to_many :groups end class Group < ActiveRecord::Base has_and_belongs_to_many :users end # 良い例 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]
を利用しましょう。 [link]# 悪い例 def amount read_attribute(:amount) * 100 end # 良い例 def amount self[:amount] * 100 end
-
なるべく
write_attribute(:attribute, value)
よりself[:attribute] = value
を利用しましょう。 [link]# 悪い例 def amount write_attribute(:amount, 100) end # 良い例 def amount self[:amount] = 100 end
-
"sexy" validations を利用しましょう。 [link]
# 悪い例 validates_presence_of :email # 良い例 validates :email, presence: true
-
独自のヴァリデーションが2回以上呼び出される場合、もしくは独自のヴァリデーションが正規表現を含む場合は、ファイルとして切り出しましょう。 [link]
# 悪い例 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
を作成し、その配下に配置しましょう。 [link] -
独自のヴァリデーションを使って複数のアプリケーションをメンテナンスしている場合、もしくはそのヴァリデーションが汎用的な内容である場合、gem 化することを検討しましょう。 [link]
-
名前付きスコープを取り入れましょう。 [link]
class User < ActiveRecord::Base scope :active, -> { where(active: true) } scope :inactive, -> { where(active: false) } scope :with_orders, -> { joins(:orders).select('distinct(users.id)') } end
-
名前付きスコープがラムダ式で定義されており、かつ引数が複雑になってきた際には、スコープ名と同名の
ActiveRecord::Relation
オブジェクトを返すクラスメソッドを作成することが望ましいです。そうすることで記述を簡潔にすることができます。 [link]class User < ActiveRecord::Base def self.with_orders joins(:orders).select('distinct(users.id)') end end
-
update_attribute
はupdate_attributes
と異なり、モデルのヴァリデーション実行をスキップするので、利用する際は気をつけましょう。 [link] -
ユーザー・フレンドリーな URL を用意しましょう。下記に例を示します。 [link]
-
ここでは、
to_param
を上書きします。このメソッドはRailsによるURL作成に使用されます。デフォルトの実装ではレコードのid
をString
型で返します。class Person def to_param "#{id} #{name}".parameterize end end
-
to_param
の返り値をURL-friendlyな値にするために, 文字列をparameterize
します。parameterize
ではオブジェクトをActiveRecordのfind
メソッドで検索できるようにする為、id
を文字列の一番先頭に置きます。 -
friendly_id
という gem を利用しましょう。id
のかわりに model の他の属性を使って、読みやすいURLを生成することが出来ます。class Person extend FriendlyId friendly_id :name, use: :slugged end
より詳細な内容は gem documentation に譲ります。
-
-
ActiveRecordのオブジェクトに対して繰り返し処理を行う場合は、
find_each
を利用しましょう。(all
などで)DBから取得したレコードセットを直接繰り返し処理させる場合、すべてのオブジェクトを即時にインスタンス化することが求められ、大量にメモリを消費します。find_each
はインスタンス化を逐次処理にできるため、メモリ量の消費を抑制することが出来ます。 [link]# 悪い例 Person.all.each do |person| person.do_awesome_stuff end Person.where('age > 21').each do |person| person.party_all_night! end # 良い例 Person.find_each do |person| person.do_awesome_stuff end Person.where('age > 21').find_each do |person| person.party_all_night! end
-
Rails creates callbacks for dependent associations により、
before_destroy
にはprepend: true
オプションを付けましょう。(satour注:リンク先の内容がタイトルから連想される内容と異なる。またリンク先のissueはcloseされている。)# 悪い例 (super_admin? が true でもrolesが自動的にdeleteされる。) has_many :roles, dependent: :destroy before_destroy :ensure_deletable def ensure_deletable fail "Cannot delete super admin." if super_admin? end # 良い例 has_many :roles, dependent: :destroy before_destroy :ensure_deletable, prepend: true def ensure_deletable fail "Cannot delete super admin." if super_admin? end
-
schema.rb
(またはstructure.sql
) は必ずバージョン管理しましょう。 [link] -
最新のスキーマで空のデータベースを作る際には、
rake db:migrate
でなくrake db:schema:load
を利用しましょう。 [link] -
フィールドの初期値の設定処理は、アプリケーションの中に記述せず、migration ファイルに記述しましょう。 [link]
# 悪い例 - アプリケーションでデフォルト値を設定している。 def amount self[:amount] or 0 end
多くのRails開発者が、初期値の設定をRailsのアプリケーション層だけで行っています。しかし、そのやり方はデータの不整合やアプリケーションのバグを引き起こす可能性が高いです。また、重要なアプリケーションは、データベースを他のアプリケーションと共有している場合が多く、単体のRailsアプリケーションでデータ保全性を担保するのは不可能です。
-
外部キー制約を設定しましょう。ActiveRecord は外部キー制約の機能を提供していない為、外部キー制約の設定には schema_plus や foreigner のような偉大な gem を利用しましょう。 [link]
-
スキーマを変更する(テーブルの追加、フィールドの追加など)migrationを書くときは、
up
やdown
でなくchange
を利用しましょう。 [link]# 古い書き方(古いバージョンのRailsではこのような書き方しか出来ませんでした。) 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
-
migration でモデルを利用してはいけません。モデルの内容は更新されていくものであり、migration でモデルを引用していた場合、migrationの実行時に不具合を起こす可能性が高いです。 [link]
-
モデル層から直接、viewを呼び出さないようにしましょう。 [link]
-
Viewでの表示のための複雑なフォーマット処理をviewのファイルに記述してはいけません。ヘルパーメソッドに書き出しましょう。 [link]
-
同じコードを複数箇所に書くのは非効率です。複数のviewで同じコードを書く場合は、部分テンプレートやレイアウトにまとめましょう。 [link]
-
国際化(国や土地によって文字列を翻訳する)したい文字列は、すべて
config/locales
配下のlocaleファイル(辞書ファイル)で定義しましょう。 [link] -
国際化する場合、ActiveRecordのmodelのラベルには必ず対訳が必要です。各辞書ファイルで
activerecord
スコープを使って対訳を定義しましょう。 [link]en: activerecord: models: user: Member attributes: user: name: 'Full name'
この場合、
User.model_name.human
は "Member" を返し、User.human_attribute_name("name")
は "Full name" を返します。これらの対訳はviewでのラベル表示で使用されます。 -
ActiveRecord の model の対訳と view で使用するテキストは別々の辞書ファイルに定義しましょう。model の対訳は
model
ディレクトリを作成しその配下に辞書ファイルを作成しましょう。また view での用いる対訳はviews
ディレクトリを作成し、その配下に辞書ファイルを作成しましょう. [link]-
辞書ファイルを格納したディレクトリが分散する場合、
application.rb
で辞書ファイルを格納しているディレクトリを指定する必要があります。# config/application.rb config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
-
-
データフォーマットや通貨表記など、どのlocaleでも共通して利用したい文字列は、
locales
直下にファイルを作成しそこに定義しましょう。 [link] -
挙動が同じなら、利用するメソッドは名称が短い方が望ましいです。
I18n.translate
ではなくI18n.t
を使いましょう。また、I18n.localize
でなく、I18n.l
を使いましょう。 [link] -
View では、"lazy lookup" を利用しましょう。 [link]
en: users: show: title: 'User details page'
辞書ファイルでこのように訳語が定義されている場合、
app/views/users/show.html.haml
では下記の呼び出し方でusers.show.title
を呼び出すことができます。このような呼び出し方を "lazy lookup" と呼びます。= t '.title'
-
訳語の呼び出しに用いるキーは、ドットを使った記法を利用しましょう。その方が
:scope
オプションを利用するより読みやすく、また辞書ファイルでの訳語の定義の階層がわかりやすいです。 [link]# 悪い例 I18n.t :record_invalid, :scope => [:activerecord, :errors, :messages] # 良い例 I18n.t 'activerecord.errors.messages.record_invalid'
-
より詳細な内容は Rails Guides に譲ります。 [link]
assets pipeline を使いましょう。
-
独自のstylesheetファイル・javascriptファイル・画像ファイルは、
app/assets
配下に配置しましょう。 [link] -
開発中のアプリケーションに必ずしもフィットしていない独自ライブラリは、
lib/assets
配下に配置しましょう。 [link] -
jQueryや bootstrapのようなサードパーティーのコードは、
vendor/assets
配下に配置しましょう。 [link] -
可能であれば、gem 化されたアセットを利用しましょう。 (例: jquery-rails, jquery-ui-rails, bootstrap-sass, zurb-foundation). [link]
-
mailer の名には
SomethingMailer
といったようにMailer
を語尾につけ、その Mailer とどの view を結びついているか分かるようにしましょう [link] -
html と plain-text 両方での view templateを用意するようにしましょう。. [link]
-
delelopment 環境では、メールの送信に失敗したらエラーが発生するように設定しておきましょう。デフォルトでは、develepment環境でのメール送信失敗はエラーとならないように設定されています。 [link]
# config/environments/development.rb config.action_mailer.raise_delivery_errors = true
-
development 環境ではローカルのSMTPサーバーは、Mailcatcher のように利用しましょう。 [link]
# config/environments/development.rb config.action_mailer.smtp_settings = { address: 'localhost', port: 1025, # more settings }
-
host名を設定しましょう。 [link]
# 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'
-
view で email へのリンクを記載する際には、
_path
でなく_url
を利用しましょう。_url
が生成するURLはホスト名を含みますが、_path
メソッドのそれは含まない為です。 [link]# 悪い例 詳細は下記のリンクをご参照ください。 = link_to 'こちら', course_path(@course) # 良い例 詳細は下記のリンクをご参照ください。 = link_to 'こちら', course_url(@course)
-
from
とto
は下記のような記法で記述するようにしましょう。 [link]# in your mailer class default from: 'Your Name <info@your_site.com>'
-
email 送信のテストでは、email 送信メソッドのプロトコルを
:test
に設定しましょう。 [link]# config/environments/test.rb config.action_mailer.delivery_method = :test
-
開発時と本番運用時には、email 送信メソッドのプロトコルは
:smtp
に設定しましょう。 [link]# config/environments/development.rb, config/environments/production.rb config.action_mailer.delivery_method = :smtp
-
外部の css ファイルを読み込めないメールクライアントがある為、html メールを送信する場合は、すべての style を template に直接記述するようにしましょう。styleをtemplateに直接記述した場合、template のコードの保守が非常に難しいものになりますが、 premailer-rails や roadie といった gem を使用することで、style や htmlタグを適切に編集することができます。 [link]
-
ページ描画と email の送信を同時に行うのはやめましょう。ページ描画の遅延によって、email 送信リクエストがタイムアウトになり、email が送信されない場合があす。sidekiq のような gem を利用し、email をバックグラウンドのプロセスで送信することでこの問題を回避できます。 [link]
-
開発およびテストでしか利用しない gem は、
Gemfile
にて利用する環境(development
やtest
)を指定しましょう。 [link] -
有名で利用者の多い gem を利用しましょう。もし、無名の gem を利用しなければならない場合は、利用前にソースコードをよく確認しましょう。 [link]
-
gem にはOS固有のものがあります。もし、そのようなgemを利用しており、かつプロジェクトに参加している開発者が複数のOSを利用している場合、頻繁に
Gemfile.lock
が書き変わってしまいます。そのような状況を避けるため、Gemfile
では OS X 特有のgem をdarwin
とグルーピングしましょう。同様に Linux 特有の gem はlinux
とグルーピングしましょう。 [link]# Gemfile group :darwin do gem 'rb-fsevent' gem 'growl' end group :linux do gem 'rb-inotify' end
適切な環境に適切なgemを
require
させるために、下記のコードをconfig/application.rb
に追記しましょう。platform = RUBY_PLATFORM.match(/(linux|darwin)/)[0].to_sym Bundler.require(platform)
-
Gemfile.lock
はかならずバージョン管理しましょう。これはあなた方のプロジェクトの開発者全員がbundle install
を実行する際に、同じバージョンの gem をインストールできるよう保証するためのものです。 [link]
問題をはらんでいる、もしくは陳腐化した gem の一覧です。これらの gem は利用しないことをお勧めします。
-
- この gem は莫大なメモリ量を消費してしまいます。代わりに minimagick を使いましょう。
-
- コードカバレッジツールです。Ruby 1.9 以降に対応していないため、代わりにSimpleCovを使いましょう。
-
- 莫大にメモリ量を消費するため、本番環境での利用は適当ではありません。
node.js
を用いた方法をお勧めします。
- 莫大にメモリ量を消費するため、本番環境での利用は適当ではありません。
この一覧は随時更新していきます。もし有名で問題のある gem をご存知であれば、ご一報頂けると幸いです。
この文書の他にもRailsのコーディングスタイルについて、優れた文献があります。時間があるときに目を通されてみることをお勧めします:
- The Rails 4 Way
- Ruby on Rails Guides
- The RSpec Book
- The Cucumber Book
- Everyday Rails Testing with RSpec
- Better Specs for RSpec
このガイドに書いてあることはすべて編集可能です。 Railsのコードスタイルに興味のある全ての人と共に取り組むことが私の望みです。 究極的には、全てのRubyコミュニティにとって有益なガイドを作ることができればと思っています。 遠慮せずチケットを立てたりプルリクエストを送ったりしてください。 また、このプロジェクト(とRubocop)への金銭的な貢献は、gittip経由で行うことができます。
簡単です! contribution guidelines をご覧ください!
この著作物は、 Creative Commons Attribution 3.0 Unported Licenseに従います。
このガイドは有志のコミュニティによって作成されるものです。そもそも存在が世に知られないと、このガイド自体が意味のないものになってしまいます。このページをご覧になった方には、ぜひこのガイドについてTwitterでつぶやいたり、友達や同僚にシェアして、このガイドの存在を広めていただけるようお願いします。 全てのコメント・提案・意見がこのガイドをより良いものにしていきます。
親愛をこめて
Bozhidar