보지다르 바초프(Bozhidar Batsov)
역할 모델이 중요합니다.
— Alex J. Murphy 경관 / RoboCop
팁 | https://rails.rubystyle.guide에서 훨씬 향상된 탐색 기능을 갖춘 이 가이드의 아름다운 버전을 찾을 수 있습니다. |
이 가이드의 목표는 Ruby on Rails 개발을 위한 일련의 모범 사례와 스타일 처방을 제시하는 것입니다. 이는 이미 존재하는 커뮤니티 중심의 Ruby 코딩 스타일 가이드에 대한 보완 가이드입니다.
이 Rails 스타일 가이드는 실제 Rails 프로그래머가 다른 실제 Rails 프로그래머가 유지 관리할 수 있는 코드를 작성할 수 있도록 모범 사례를 권장합니다. 실제 사용 방식을 반영하는 스타일 가이드가 사용되고, 사람들이 거부한 이상을 고수하는 스타일 가이드는 그것이 아무리 좋더라도 전혀 사용되지 않을 위험이 있다는 것을 의미합니다.
이 가이드는 관련 규칙의 여러 섹션으로 구분되어 있습니다. 나는 규칙 뒤에 근거를 추가하려고 노력했습니다(생략된 경우 꽤 분명하다고 가정했습니다).
제가 모든 규칙을 갑자기 생각해 낸 것은 아닙니다. 이는 대부분 전문 소프트웨어 엔지니어로서의 광범위한 경력, Rails 커뮤니티 구성원의 피드백 및 제안, 높이 평가되는 다양한 Rails 프로그래밍 리소스를 기반으로 합니다.
메모 | 여기에 나온 조언 중 일부는 최신 버전의 Rails에만 적용됩니다. |
AsciiDoctor PDF를 사용하여 이 가이드의 PDF 사본을 생성하고 다음 명령을 사용하여 AsciiDoctor를 사용하여 HTML 사본을 생성할 수 있습니다:
# Generates README.pdf
asciidoctor-pdf -a allow-uri-read README.adoc
# Generates README.html
asciidoctor README.adoc
팁 | 생성된 문서에서 멋진 구문 강조를 얻으려면 gem install rouge |
가이드 번역은 다음 언어로 제공됩니다.
일본어
러시아인
팁 | 정적 코드 분석기(린터) 및 포맷터인 RuboCop에는 이 스타일 가이드를 기반으로 하는 rubocop-rails 확장이 있습니다. |
config/initializers
에 사용자 정의 초기화 코드를 넣으세요. 이니셜라이저의 코드는 애플리케이션 시작 시 실행됩니다.
각 gem의 초기화 코드를 gem과 동일한 이름을 가진 별도의 파일에 보관하세요(예: carrierwave.rb
, active_admin.rb
등).
그에 따라 개발, 테스트 및 프로덕션 환경에 대한 설정을 조정합니다( 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
파일의 모든 환경에 적용 가능한 구성을 유지하세요.
최신 Rails 버전으로 업그레이드하면 애플리케이션의 구성 설정이 이전 버전으로 유지됩니다. 최신 권장 Rails 사례를 활용하려면 config.load_defaults
설정이 Rails 버전과 일치해야 합니다.
# good
config . load_defaults 6.1
development
, test
및 production
의 기본값보다 추가 환경 구성을 생성하지 마십시오. 스테이징 등 프로덕션과 유사한 환경이 필요한 경우 구성 옵션에 환경 변수를 사용하세요.
config/
디렉터리 아래의 YAML 파일에 추가 구성을 유지합니다.
Rails 4.2 YAML 구성 파일은 새로운 config_for
메소드를 사용하여 쉽게 로드할 수 있습니다.
Rails :: Application . config_for ( :yaml_file )
RESTful 리소스에 더 많은 작업을 추가해야 하는 경우(정말 필요합니까?) member
및 collection
경로를 사용합니다.
# bad
get 'subscriptions/:id/unsubscribe'
resources :subscriptions
# good
resources :subscriptions do
get 'unsubscribe' , on : :member
end
# bad
get 'photos/search'
resources :photos
# good
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
Active Record 모델 간의 관계를 더 잘 표현하려면 중첩된 경로를 사용하세요.
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post
end
# routes.rb
resources :posts do
resources :comments
end
1레벨 이상의 경로를 중첩해야 하는 경우에는 shallow: true
옵션을 사용하세요. 이렇게 하면 사용자가 긴 URL인 posts/1/comments/5/versions/7/edit
에서 저장되고 사용자는 긴 URL 도우미인 edit_post_comment_version
에서 저장됩니다.
resources :posts , shallow : true do
resources :comments do
resources :versions
end
end
네임스페이스 경로를 사용하여 관련 작업을 그룹화합니다.
namespace :admin do
# Directs /admin/products/* to Admin::ProductsController
# (app/controllers/admin/products_controller.rb)
resources :products
end
레거시 와일드 컨트롤러 경로를 사용하지 마십시오. 이 경로는 GET 요청을 통해 액세스할 수 있는 모든 컨트롤러의 모든 작업을 만듭니다.
# very bad
match ':controller(/:action(/:id(.:format)))'
:via
옵션을 사용하여 [:get, :post, :patch, :put, :delete]
간의 여러 요청 유형을 단일 작업에 매핑해야 하는 경우가 아니면 match
사용하여 경로를 정의하지 마세요.
컨트롤러를 얇게 유지하십시오. 뷰 레이어에 대한 데이터만 검색해야 하며 비즈니스 로직을 포함해서는 안 됩니다(모든 비즈니스 로직은 자연스럽게 모델에 있어야 함).
각 컨트롤러 작업은 (이상적으로) 초기 찾기 또는 새로 만들기 이외의 메서드 하나만 호출해야 합니다.
컨트롤러와 뷰 사이에 전달되는 인스턴스 변수의 수를 최소화하세요.
작업 필터 옵션에 지정된 컨트롤러 작업은 어휘 범위에 있어야 합니다. 상속된 작업에 대해 지정된 ActionFilter로 인해 해당 작업에 미치는 영향의 범위를 이해하기가 어렵습니다.
# bad
class UsersController < ApplicationController
before_action :require_login , only : :export
end
# good
class UsersController < ApplicationController
before_action :require_login , only : :export
def export
end
end
인라인 렌더링보다 템플릿 사용을 선호합니다.
# very bad
class ProductsController < ApplicationController
def index
render inline : "<% products.each do |p| %><p><%= p.name %></p><% end %>" , type : :erb
end
end
# good
## app/views/products/index.html.erb
<%= render partial : 'product' , collection : products %>
## app/views/products/_product.html.erb
< p ><%= product . name %>< /p>
<p><%= product.price %></p >
## app/controllers/products_controller.rb
class ProductsController < ApplicationController
def index
render :index
end
end
render text:
보다 render plain:
선호합니다: .
# bad - sets MIME type to `text/html`
...
render text : 'Ruby!'
...
# bad - requires explicit MIME type declaration
...
render text : 'Ruby!' , content_type : 'text/plain'
...
# good - short and precise
...
render plain : 'Ruby!'
...
숫자로 된 HTTP 상태 코드보다 해당 기호를 선호하세요. 이는 의미가 있으며 덜 알려진 HTTP 상태 코드에 대한 "마법의" 숫자처럼 보이지 않습니다.
# bad
...
render status : 403
...
# good
...
render status : :forbidden
...
Non-Active Record 모델 클래스를 자유롭게 도입해보세요.
약어 없이 의미 있는(그러나 짧은) 이름으로 모델 이름을 지정합니다.
데이터베이스 기능 없이 ActiveRecord와 유사한 동작(예: 유효성 검사)을 지원하는 개체가 필요한 경우 ActiveModel::Model
사용하세요.
class Message
include ActiveModel :: Model
attr_accessor :name , :email , :content , :priority
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
Rails 6.1부터는 ActiveModel::Attributes
사용하여 ActiveRecord에서 속성 API를 확장할 수도 있습니다.
class Message
include ActiveModel :: Model
include ActiveModel :: Attributes
attribute :name , :string
attribute :email , :string
attribute :content , :string
attribute :priority , :integer
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
비즈니스 영역에서 의미가 없는 한, 데이터 형식만 지정하는 메서드(예: 코드 생성 HTML)를 모델에 넣지 마세요. 이러한 메서드는 뷰 레이어에서만 호출될 가능성이 높으므로 그 위치는 도우미에 있습니다. 비즈니스 로직과 데이터 지속성을 위해서만 모델을 유지하세요.
매우 타당한 이유(예: 통제할 수 없는 데이터베이스)가 아닌 이상 Active Record 기본값(테이블 이름, 기본 키 등)을 변경하지 마세요.
# bad - don't do this if you can modify the schema
class Transaction < ApplicationRecord
self . table_name = 'order'
...
end
ignored_columns
열에 항상 추가 ignored_columns
설정을 피하세요. 이전 할당을 덮어쓸 수 있으며 이는 거의 항상 실수입니다. 대신 목록에 추가하는 것을 선호합니다.
class Transaction < ApplicationRecord
# bad - it may overwrite previous assignments
self . ignored_columns = %i[ legacy ]
# good - the value is appended to the list
self . ignored_columns += %i[ legacy ]
...
end
enum
에는 해시 구문을 사용하는 것이 좋습니다. 배열은 데이터베이스 값을 암시적으로 만들고 중간에 값을 삽입/제거/재배열하면 코드가 손상될 가능성이 높습니다.
class Transaction < ApplicationRecord
# bad - implicit values - ordering matters
enum type : %i[ credit debit ]
# good - explicit values - ordering does not matter
enum type : {
credit : 0 ,
debit : 1
}
end
클래스 정의 시작 부분에 매크로 스타일 메서드( has_many
, validates
등)를 그룹화합니다.
class User < ApplicationRecord
# keep the default scope first (if any)
default_scope { where ( active : true ) }
# constants come up next
COLORS = %w( red green blue )
# afterwards we put attr related macros
attr_accessor :formatted_date_of_birth
attr_accessible :login , :first_name , :last_name , :email , :password
# Rails 4+ enums after attr macros
enum role : { user : 0 , moderator : 1 , admin : 2 }
# followed by association macros
belongs_to :country
has_many :authentications , dependent : :destroy
# and validation macros
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 }
# next we have callbacks
before_save :cook
before_save :update_username_lower
# other macros (like devise's) should be placed after the callbacks
...
end
has_many :through
has_and_belongs_to_many
보다 has_many :through
선호하세요. has_many :through
사용하면 조인 모델에 대한 추가 속성과 유효성 검사가 가능합니다.
# not so good - using has_and_belongs_to_many
class User < ApplicationRecord
has_and_belongs_to_many :groups
end
class Group < ApplicationRecord
has_and_belongs_to_many :users
end
# preferred way - using has_many :through
class User < ApplicationRecord
has_many :memberships
has_many :groups , through : :memberships
end
class Membership < ApplicationRecord
belongs_to :user
belongs_to :group
end
class Group < ApplicationRecord
has_many :memberships
has_many :users , through : :memberships
end
read_attribute(:attribute)
보다 self[:attribute]
선호하세요.
# bad
def amount
read_attribute ( :amount ) * 100
end
# good
def amount
self [ :amount ] * 100
end
write_attribute(:attribute, value)
보다 self[:attribute] = value
선호하세요.
# bad
def amount
write_attribute ( :amount , 100 )
end
# good
def amount
self [ :amount ] = 100
end
항상 "새로운 스타일" 검증을 사용하십시오.
# bad
validates_presence_of :email
validates_length_of :email , maximum : 100
# good
validates :email , presence : true , length : { maximum : 100 }
사용자 정의 검증 방법의 이름을 지정할 때 다음과 같은 간단한 규칙을 따르십시오.
validate :method_name
자연스러운 문장처럼 읽힙니다.
메소드 이름은 검사 내용을 설명합니다.
해당 메서드는 조건자 메서드가 아닌 이름으로 유효성 검사 메서드로 인식될 수 있습니다.
# good
validate :expiration_date_cannot_be_in_the_past
validate :discount_cannot_be_greater_than_total_value
validate :ensure_same_topic_is_chosen
# also good - explicit prefix
validate :validate_birthday_in_past
validate :validate_sufficient_quantity
validate :must_have_owner_with_no_other_items
validate :must_have_shipping_units
# bad
validate :birthday_in_past
validate :owner_has_no_other_items
유효성 검사를 쉽게 읽을 수 있도록 유효성 검사당 여러 속성을 나열하지 마세요.
# bad
validates :email , :password , presence : true
validates :email , length : { maximum : 100 }
# good
validates :email , presence : true , length : { maximum : 100 }
validates :password , presence : true
사용자 정의 유효성 검사가 두 번 이상 사용되거나 유효성 검사가 일부 정규식 매핑인 경우 사용자 정의 유효성 검사기 파일을 만듭니다.
# bad
class Person
validates :email , format : { with : / A ([^@ s ]+)@((?:[-a-z0-9]+ . )+[a-z]{2,}) z /i }
end
# good
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으로 추출하는 것을 고려해 보세요.
명명된 범위를 자유롭게 사용하세요.
class User < ApplicationRecord
scope :active , -> { where ( active : true ) }
scope :inactive , -> { where ( active : false ) }
scope :with_orders , -> { joins ( :orders ) . select ( 'distinct(users.id)' ) }
end
람다 및 매개변수로 정의된 명명된 범위가 너무 복잡해지면 명명된 범위와 동일한 목적을 제공하고 ActiveRecord::Relation
개체를 반환하는 클래스 메서드를 만드는 것이 좋습니다. 아마도 이와 같이 훨씬 더 간단한 범위를 정의할 수 있습니다.
class User < ApplicationRecord
def self . with_orders
joins ( :orders ) . select ( 'distinct(users.id)' )
end
end
실행될 순서대로 콜백 선언을 정렬합니다. 자세한 내용은 사용 가능한 콜백을 참조하세요.
# bad
class Person
after_commit :after_commit_callback
before_validation :before_validation_callback
end
# good
class Person
before_validation :before_validation_callback
after_commit :after_commit_callback
end
다음 메서드의 동작에 주의하세요. 모델 검증을 실행하지 않으며 모델 상태를 쉽게 손상시킬 수 있습니다.
# bad
Article . first . decrement! ( :view_count )
DiscussionBoard . decrement_counter ( :post_count , 5 )
Article . first . increment! ( :view_count )
DiscussionBoard . increment_counter ( :post_count , 5 )
person . toggle :active
product . touch
Billing . update_all ( "category = 'authorized', author = 'David'" )
user . update_attribute ( :website , 'example.com' )
user . update_columns ( last_request_at : Time . current )
Post . update_counters 5 , comment_count : - 1 , action_count : 1
# good
user . update_attributes ( website : 'example.com' )
사용자 친화적인 URL을 사용하세요. id
아닌 URL에 모델의 일부 설명 속성을 표시합니다. 이를 달성하는 방법은 여러 가지가 있습니다.
to_param
메서드 재정의 이 메소드는 Rails에서 객체에 대한 URL을 생성하는 데 사용됩니다. 기본 구현은 레코드의 id
를 문자열로 반환합니다. 사람이 읽을 수 있는 다른 속성을 포함하도록 재정의될 수 있습니다.
class Person
def to_param
" #{ id } #{ name } " . parameterize
end
end
이를 URL 친화적인 값으로 변환하려면 문자열에서 parameterize
호출해야 합니다. 객체의 id
Active Record의 find
메소드로 찾을 수 있도록 시작 부분에 있어야 합니다.
friendly_id
보석 id
대신 모델의 일부 설명 속성을 사용하여 사람이 읽을 수 있는 URL을 생성할 수 있습니다.
class Person
extend FriendlyId
friendly_id :name , use : :slugged
end
사용법에 대한 자세한 내용은 gem 문서를 확인하세요.
find_each
AR 개체 컬렉션을 반복하려면 find_each
사용하세요. 데이터베이스의 레코드 컬렉션을 반복하는 것은(예를 들어 all
메서드 사용) 모든 개체를 한 번에 인스턴스화하려고 시도하므로 매우 비효율적입니다. 이 경우 일괄 처리 방법을 사용하면 레코드를 일괄적으로 작업할 수 있으므로 메모리 소비가 크게 줄어듭니다.
# bad
Person . all . each do | person |
person . do_awesome_stuff
end
Person . where ( 'age > 21' ) . each do |