半年ほど前に書いた「コーディング規約をまとめてみた (Ruby編)」に引き続き、Railsのコーディング規約もまとめてみました。前回と同じように、できるだけ理由を併記するよう努めました (主観的なものも含まれていますが…)。
気に入らない規約や、この記事に書かれている規約以外にも気をつけていることなどありましたら、コメントなどで教えてもらえると嬉しいです (理由も合わせて書いてくれると助かります)。
Railsのコーディング規約は以下のページを参考にまとめています。
コード例は「コーディング規約をまとめてみた (Ruby編)」をベースに記述します。
バージョン3.2系を対象にしています。
RESTfulなリソース(resources
メソッドを使用したルーティング)に対して新たにrouteを追加する場合は、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
理由:
resources
メソッドのブロック内に記述することで、追加するrouteがそのリソースに結びついているということが明確になるため。member
, collection
ルーティングを複数定義する場合は、ブロック内に記述します。
resources :subscriptions do
member do
post 'subscribe'
post 'unsubscribe'
end
end
resources :photos do
collection do
get 'explore'
get 'search'
end
end
理由:
コメント:
「Nested routes」は、各モデルの関係を表現するために使用します。
class Post < ActiveRecord::Base
has_many :comments
end
class Comments < ActiveRecord::Base
belongs_to :post
end
# routes.rb
resources :posts do
resources :comments
end
「Namespaced routes」は、関係するroutesをグルーピングするために使用します。
namespace :admin do
resources :products
end
「Legacy wild controller route」は使用してはいけません。
# ダメ! ゼッタイ!
match ':controller(/:action(/:id(.:format)))'
理由:
コントローラ内に複雑なビジネスロジックを記述してはいけません。 ビジネスロジックはモデルやヘルパーに記述し、コントローラ内はリクエストからレスポンスまでの一連の流れを簡潔に表現するようにします。
理由:
has_many
, validates
などのクラスメソッドは、モデル定義の最初の部分に記述します。
# 悪い例 (クラスメソッドがメソッド宣言のあとに呼び出されている)
class User < ActiveRecord::Base
def follow
end
has_and_belongs_to_many :groups
end
# 良い例 (クラスメソッドの呼び出しはモデル定義の最初の部分で行う)
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
def follow
end
end
各クラスメソッドは機能ごとに一定の順番で記述します。
class User < ActiveRecord::Base
# 1番目にアクセッサ関連のクラスメソッドを呼び出す
attr_accessor :type
attr_accessible :name, :type
# 2番目にアソシエーション関連のクラスメソッドを呼び出す
has_and_belongs_to_many :groups
has_many :posts
# 3番目にバリデーション関連のクラスメソッドを呼び出す
validates :name, presence: true
# 4番目にNamed scopeを呼び出す
scope :deleted, where(deleted: true)
# 5番目に外部ライブラリのクラスメソッドを呼び出す
# 各外部ライブラリごとに1行空白行を挿入する
devise :database_authenticatable, :rememberable, :trackable
has_attached_file :avatar
# 6番目に before_validation などの、ブロック内が複数行になりうるクラスメソッドを呼び出す
before_validation do
end
# 7番目にクラスメソッドを定義する
# クラスメソッドの呼び出しと定義の間には2行分の空白行を挿入する
def self.find_by_full_name
end
# 8番目にインスタンスメソッドを定義する
def full_name
end
end
同じ名前のクラスメソッドは一つにまとめて呼び出します。そのとき、アルファベット順で呼び出します。
# 悪い例1 (2つのhas_manyが一箇所で呼び出されていない)
class User < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :groups
has_many :posts
end
# 良い例1 (2つのhas_manyが一箇所で呼び出されている)
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
has_many :comments
has_many :posts
end
# 悪い例2 (has_and_belongs_to_manyがhas_manyのあとで呼び出されている)
class User < ActiveRecord::Base
has_many :comments
has_many :posts
has_and_belongs_to_many :groups
end
# 良い例2 (「has_」のあとはそれぞれ「m」と「a」なので、アルファベット順に並べると
# has_and_belongs_to_manyがhas_manyより前に来る)
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
has_many :comments
has_many :posts
end
# 悪い例3 (「:posts」が「:comments」の前で呼び出されている)
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
has_many :posts
has_many :comments
end
# 良い例3 (アルファベット順に並べると(ry )
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
has_many :comments
has_many :posts
end
理由:
できるだけ has_and_belongs_to_many
は使用せず、 has_many :through
を使用します。
理由:
validates_presence_of
などの古いバリデーションメソッドは使用せず、"sexy validations" を使用します。
# 悪い例
class User < ActiveRecord::Base
validates_presence_of :email, :name
end
# 良い例
class User < ActiveRecord::Base
validates :email, presence: true
validates :name, presence: true
end
理由:
独自のバリデーションを複数回呼び出すときや正規表現を使用するバリデーションを設定したいときは、独自のバリデーション用のクラスを定義します。
# 悪い例
class Person
validates :email, format: { with: /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i }
end
# 良い例
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << (options[:message] || 'is not a valid email') unless value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
end
end
class Person
validates :email, email: true
end
理由:
独自のバリデーションはそのバリデーションのクラスごとにファイルを作成し、app/validators
ディレクトリ以下に格納します。
app/validators
├── email_validator.rb
└── phone_number_validator.rb
理由:
lib
ディレクトリ以下に置くか app
ディレクトリ以下に置くかで意見が分かれているようです。データの検索を行う処理を実装するときは、クラスメソッドを定義せず、Named scopeを使用します。引数が必要な場合は、Ruby 1.9から導入されたlambdaメソッドの省略記法 ->
を使用します。
# 悪い例1 (クラスメソッドを定義している)
def self.published
where(published: true)
end
# 良い例1
scope :published, where(published: true)
# 悪い例2-1 (クラスメソッドを定義している)
def self.liked_by(person)
joins(:likes).where(likes: { author_id: person.id })
end
# 悪い例2-2 (lambdaメソッドの省略記法を使用していない)
scope :liked_by, lambda { |person|
joins(:likes).where(:likes => {:author_id => person.id})
}
# 良い例2 (省略記法を使用したほうがタイプ数も少なくすっきりしていて見やすい)
scope :liked_by, -> person {
joins(:likes).where(likes: { author_id: person.id })
}
理由:
コメント:
更新 (2012/10/16 20:32): Rails 4.0から lambda
メソッドを使用する記述方法が推奨されます (Ruby on Rails 4.0 Release Notes - "Deprecate eager-evaluated scopes.")。
「良い例1」は、4.0以降では以下のように記述します。(@pinzolo さんありがとうございます! )
# 良い例1-1 (Rails 4.0から)
scope :published, -> { where(published: true) }
モデル内に定義するメソッドは、データを操作するメソッドか、データそのものを返すようにします。それ以外の値を返すときは、ヘルパーやデコレータ (ActiveDecorator など) を使用します。
class User < ActiveRecord::Base
# 悪い例
# データベースからの値にビューで表示する上で必要なものを付加したい場合は
# デコレータに記述します
def full_name
first_name + last_name + 'さん'
end
# 良い例
def full_name
first_name + last_name
end
end
マイグレーションの記述には、Rails 3.1から導入された change
メソッドを使用します。
# 悪い例
class CreateProducts < ActiveRecord::Migration
def up
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
def down
drop_table :products
end
end
# 良い例
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
理由:
down
メソッド) を記述する必要がなくなるためビュー内にビジネスロジックを記述してはいけません。ヘルパーやモデルに記述するようにします。
自作のライブラリは lib/assets
ディレクトリ以下に配置します。
jQueryやUnderscore.jsなどの外部ライブラリは vendor/assets
ディレクトリ以下に配置します。
メール本文内にサイトのURLを表示したいときは、_path
メソッドではなく _url
メソッドを使用します。
# 悪い例
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))
理由:
_url
メソッドはホスト名も返すため。
追記 (2012/10/16 22:03):
各方面からいくつかコメントを頂きました。ありがとうございます!
id: ntaoo さん:「rails guideには引数付きのscopeを定義するよりクラスメソッドにするほうがprefered wayだよと書いてあったような。」
確かに "Ruby on Rails Guides の "13.2 Passing in arguments"" の項に以下のように書いてありました…。知りませんでした…。
"Using a class method is the preferred way to accept arguments for scopes."
ただ、引数を要するかどうかを問わず、 ActiveRecord::Relation
オブジェクトを返す処理は scope
で、それ以外はクラスメソッドで定義する、というように区別したほうが、どのメソッドがクエリメソッドとしてchainableなのかが判別しやすいかも知れません。
@deeeki さん:「クラスマクロ呼び出しでなぜその順番が妥当かという理由もあれば知りたいところ」
Diaspora が大体こんな順番で書いているということ以外、特に理由はありません。。 なぜDiasporaがこの順番で書いているのか。推測ですが、「よく定義するクラスメソッド順」で書いたのではないかと考えています。上のほうで定義されているクラスメソッドほどよく定義している気がします。