롤 모델이 중요하다.
-- Officer Alex J. Murphy / RoboCop
이 가이드는 루비 온 레일스 4 개발을 위한 베스트 프렉티스와 코딩 스타일 규칙을 전달하고자 작성되었으며, 커뮤니티 기반 루비 코딩 스타일 가이드를 보완하는 가이드입니다.
일부 예제는 레일스 4.0 이상의 버전에서만 적용됩니다.
Pandoc를 사용해서 이 가이드를 PDF나 HTML형식으로 변환할 수 있습니다.
이 가이드는 다음 언어들로 번역되어있습니다.
이 가이드는 레일스 개발자가 코드를 작성할 때, 다른 레일스 개발자들도 유지보수 가능하도록, 베스트 프렉티스를 적용할 것을 권장합니다. 실제 사용방법을 반영하는 가이드와 너무 이상적으로 만들어져 사람들이 생각하기에 적용이 어렵다고 느껴지는 가이드가 있다면, 내용이 아무리 좋다고 하더라도 적용하지 않는 것이 좋습니다.
이 가이드는 서로 관련있는 규칙들을 섹션들로 나누었고, (자명한 것을 제외하고는) 규칙을 적용해야 하는 근거를 덧붙이려 노력하였습니다.
이 모든 규칙들이 하루 아침에 만들어지지는 않았습니다. 대부분의 규칙은 저의 소프트웨어 엔지니어로서의 수많은 경험과 레일스 커뮤니티의 피드백과 제안을 비롯해 높은 평가를 받고 있는 레일스 프로그래밍 리소스를 기반으로 만들어졌습니다.
- 설정(configuration)
- 라우팅(Routing)
- 컨트롤러(Controllers)
- 모델(Models)
- 마이그레이션(Migrations)
- 뷰(Views)
- 국제화(Internationalization)
- 에셋(Assets)
- 메일러(Mailers)
- 액티브 서포트 코어 확장(Active Support Core Extensions)
- 시간(Time)
- 번들러(Bundler)
- 프로세스 관리(Managing processes)
-
사용자 정의 초기 설정은
config/initializers
에 둔다. 여기에 있는 코드들은 애플리케이션이 처음 구동될 때 실행된다. [link] -
각각의 젬(gem)에 대해 젬과 같은 이름으로 초기 설정 파일을 따로 만든다. 예를 들어 CarrierWave에 대한 설정은
carrierwave.rb
에 저장하고, Active Admin에 대한 설정은active_admin.rb
에 저장한다. [link] -
개발(development), 테스트(test) 그리고 배포(production) 환경에 대한 설정들은
config/environments/
아래에 각 환경의 이름으로 구분하여 저장한다. [link]-
사전 컴파일해야하는 파일은 추가적인 에셋으로 표시한다.
# config/environments/production.rb # Precompile additional assets (application.js, application.css, # 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] -
그 외의 설정들은
config/
디렉토리 아래의 YAML파일을 만들어 저장한다. [link]레일스 4.2부터 추가된
config_for
메소드를 통해 YAML 설정 파일들은 쉽게 읽어들일 수 있다.Rails::Application.config_for(:yaml_file)
-
RESTful 리소스에 더 많은 액션을 추가할 필요가 있다면 (정말로 그게 다 필요한가?)
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
-
1 단계 이상의 중첩 라우트가 필요할 때
shallow: true
옵션을 사용한다. 이는 사용자를posts/1/comments/5/versions/7/edit
같은 긴 url에서 구하고edit_post_comment_version
같은 긴 url 핼퍼를 사용하지 않아도 되게 한다.resources :posts, shallow: true do resources :comments do resources :versions end end
-
관련된 액션을 묶기위해
namespace
를 사용한다. [link]namespace :admin do # Directs /admin/products/* to Admin::ProductsController # (app/controllers/admin/products_controller.rb) resources :products end
-
예전 레일스에서 사용하던 전역 라우팅 설정(legacy wild controller route)은 절대로 사용하지 않는다. 이 라우트는 GET 요청으로 모든 컨트롤러의 모든 액션(actions)에 접근할 수 있게 만든다. [link]
# 아주 나쁜 예 match ':controller(/:action(/:id(.:format)))'
-
match
메소드를 통한 라우트는[:get, :post, :patch, :put, :delete]
중 두 종류 이상의 요청을 액션에 맵핑할 필요가 있는 경우에만via
옵션과 함께 사용하고, 그 외에는 사용하지 않는다. [link]
-
컨트롤러는 최대한 간결하게 유지한다. 컨트롤러는 단지 뷰 레이어를 위한 데이터를 전달하는 역할을 하고 어떠한 비즈니스 로직도 포함해서는 안 된다(모든 비즈니스 로직은 마땅히 모델 안에서 구현되어야 한다). [link]
-
각 컨트롤러의 액션은 (원칙적으로는) 초기 검색과 생성을 제외하고 단 하나의 메소드만을 호출해야한다. [link]
-
하나의 컨트롤러와 하나의 뷰 사이에서 두 개 이상의 인스턴스 변수들을 공유하지 않는다. [link]
- 인라인(inline) 랜더링보다는 템플릿을 사용한다. [link]
# 매우 나쁜 예
class ProductsController < ApplicationController
def index
render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>", type: :erb
end
end
# 좋은 예
## 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/foo_controller.rb
class ProductsController < ApplicationController
def index
render :index
end
end
-
render text:
대신render plain:
을 사용한다. [link]
# 나쁜 예 - MIME 타입을 `text/html`로 설정
...
render text: 'Ruby!'
...
# 나쁜 예 - 정확한 MIME 타입을 선언해야한다.
...
render text: 'Ruby!', content_type: 'text/plain'
...
# 좋은 예 - 간결하고 정확함
...
render plain: 'Ruby!'
...
- 숫자로 이루어진 HTTP상태 코드보단 corresponding symbols을 사용하면, 잘 알려지지 않은 HTTP 상태코드가 "magic" 숫자처럼 보이지 않고 그 의미를 명확하게 알 수 있다. [link]
# 나쁜 예
...
render status: 500
...
# 좋은 예
...
render status: :forbidden
...
-
엑티브 레코드를 사용하지 않는 모델은 자유롭게 사용한다. [link]
-
모델 이름에는 축약어를 쓰지않고 의미를 가진 짧은 이름을 사용한다. [link]
-
엑티브 레코드의 데이터베이스 기능을 제외한 validation과 같은 기능만 필요한 모델 오브젝트가 필요하다면 ActiveAttr 젬을 사용한다. [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.
-
비즈니스 영역(business domain)에서 어떤 의미를 가지고 있지 않는 한, HTML을 생성하는 코드와 같이 데이터가 화면에서 어떻게 보여질지를 결정하는 메소드를 모델에서 사용하지 않는다. 그러한 메소드들은 대부분 뷰 층에서만 사용되기 때문에, 헬퍼(helpers)로 구현되어야 한다. 모델은 비즈니스 로직과 데이터-영속성(persistance)과 관련이 있을 경우에만 사용하도록 하자. [link]
-
데이터베이스 소유권을 가지고 있지 않은 경우와 같이 특별한 이유가 있는 게 아니라면 엑티브 레코드의 기본 설정(테이블 이름, 기본키 등)을 가능하면 변경하지 않는다. [link]
# 나쁜 예 - 데이터베이스 스키마 변경 권한이 있다면 이렇게 하지 말 것! class Transaction < ActiveRecord::Base self.table_name = 'order' ... end
-
매크로 성격의 메소드(
has_many
,validates
등)들은 클래스 상단에 모아둔다. [link]class User < ActiveRecord::Base # 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 # 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_and_belongs_to_many
보다has_many :through
를 사용한다.has_many :through
사용하면 중간 모델(join model)에서 추가적인 속성이나 validation을 사용할 수 있다. [link]# 좋지 않은 예 - 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]
를 사용한다. [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
-
항상 새로운 "섹시한" validations을 사용한다. [link]
# 나쁜 예 validates_presence_of :email validates_length_of :email, maximum: 100 # 좋은 예 validates :email, presence: true, length: { maximum: 100 }
-
사용자 정의 검증(validation)을 한 번 이상 사용하거나 정규표현식을 사용한다면, 사용자 정의 검증을 담은 파일을 작성한다. [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
-
사용자 정의 validator는
app/validators
아래에 둔다. [link] -
여러 애플리케이션에서 사용되거나, 범용적인 사용자 정의 validator를 공유 젬으로 만드는 것을 고려한다. [link]
-
이름 있는 스코프(named scope)는 자유롭게 사용한다. [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
-
다음 메소드의 동작에 조심해야 한다. 이들은 모델 유효성 검증을 하지 않으므로, 간단하게 모델의 상태가 손상될 수 있다. [link]
# 나쁜 예 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 # 좋은 예 user.update_attributes(website: 'example.com')
-
사용자 친화적인 URL을 사용한다. URL에
id
보다 모델의 특징을 잘 나타내는 속성을 사용한다. 이를 위한 여러가지 방법들이 있다. [link]-
모델의
to_param
메소드를 오버라이드한다. 이 메소드는 레일스에서 대상 객체에 대한 URL을 생성하기 위해 사용된다. 기본적으로 레코드의id
를 String 객체로 반환한다. 다른 사람이 읽기 좋은 속성을 포함하도록 오버라이드할 수 있다.class Person def to_param "#{id} #{name}".parameterize end end
이 값을 URL에서 사용하려면 문자열에
parameterize
를 호출해야한다. 객체의 id가 앞부분에 있어야만 엑티브 레코드의find
메소드로 찾을 수 있다.-
friendly_id
젬을 사용한다. 이를 사용하면id
대신에 모델의 특징을 잘 반영한 속성들을 사용해 사람이 읽기 쉬운 URL을 만들 수 있다.class Person extend FriendlyId friendly_id :name, use: :slugged end
사용법에 대한 더 많은 정보는 문서를 참고하기 바란다.
-
-
엑티브 레코드 객체의 컬렉션을 반복할 때는
find_each
를 사용한다. (예를 들면all
메소드를 사용해) 데이터베이스에서 가져온 레코드 컬렉션에 대해서 반복 작업을 수행하는 일은 모든 객체의 객체화를 한 번에 하기 때문에 매우 비효율적이다. 이 때는 배치 작업(batch process) 메소드를 통해 레코드들이 배치에서 처리되도록 하면 메모리 소비를 크게 줄일 수 있다. [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
-
레일스는 모델 의존 관계에 대한 콜백을 생성하기 때문에, 항상 validation을 수행하는
before_destroy
콜백은prepend: true
와 함께 호출한다. [link]# 나쁜 예 - roles는 super_admin?이 true라도 자동적으로 삭제된다. 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
-
has_many
와has_one
관계에 대해서는dependent
설정을 하자. [link]# 나쁜 예 class Post < ActiveRecord::Base has_many :comments end # 좋은 예 class Post < ActiveRecord::Base has_many :comments, dependent: :destroy end
-
AR 객체를 남길 때에는 항상 예외가 발생하는 느낌표! 메소드를 사용하거나 반환 값을 처리한다. 이는
create
,save
,update
,destroy
,first_or_create
,find_or_create_by
에 적용된다. [link]# 나쁜 예 user.create(name: 'Bruce') # 나쁜 예 user.save # 좋은 예 user.create!(name: 'Bruce') # 또는 bruce = user.create(name: 'Bruce') if bruce.persisted? ... else ... end # 좋은 예 user.save! # 또는 if user.save ... else ... end
-
SQL injection 공격에 취약할 수 있으므로, 쿼리에서 문자열 보간(string interpolation)을 사용하지 않는다. [link]
# 나쁜 예 - 어떠한 매개변수든지 들어갈 수 있음 Client.where("orders_count = #{params[:orders]}") # 좋은 예 - 적절한 매개변수만 들어갈 수 있음 Client.where('orders_count = ?', params[:orders])
-
쿼리에 하나 이상의 플레이스홀더를 사용할 때는 위치로 구분되는 플레이스홀더 대신 이름을 붙여 사용하는 것을 고려해본다. [link]
# 괜찮은 예 Client.where( 'created_at >= ? AND created_at <= ?', params[:start_date], params[:end_date] ) # 좋은 예 Client.where( 'created_at >= :start_date AND created_at <= :end_date', start_date: params[:start_date], end_date: params[:end_date] )
-
id를 통해 하나의 값을 조회할 때는
where
보다find
를 사용한다. [link]# 나쁜 예 User.where(id: id).take # 좋은 예 User.find(id)
-
특정 속성을 통해 하나의 값을 조회할 때는
where
과find_by_attribute
보단find_by
를 사용한다. [link]# 나쁜 예 User.where(first_name: 'Bruce', last_name: 'Wayne').first # 나쁜 예 User.find_by_first_name_and_last_name('Bruce', 'Wayne') # 좋은 예 User.find_by(first_name: 'Bruce', last_name: 'Wayne')
-
SQL을 직접 사용하기보다 'where.not'을 사용한다. [link]
# 나쁜 예 User.where("id != ?", id) # 좋은 예 User.where.not(id: id)
-
find_by_sql
처럼 메소드에서 명시적인 쿼리를 작성할 때,squish
와 함께 히어독을 사용한다. 이렇게 하면, SQL을 줄바꿈과 들여쓰기로 읽기 쉬운 형식으로 할 수 있을 뿐만 아니라, GitHub, Atom, RubyMine을 포함한 많은 툴에서 구문 하일라이트를 해준다. [link]User.find_by_sql(<<-SQL.squish) SELECT users.id, accounts.plan FROM users INNER JOIN accounts ON accounts.user_id = users.id # further complexities... SQL
String#squish
는 들여쓰기와 줄바꿈을 지워준다. 그래서 서버 로그에서 다음과 같이 보이지 않고 유려한 SQL 문자열을 표시할 수 있다.SELECT\n users.id, accounts.plan\n FROM\n users\n INNER JOIN\n acounts\n ON\n accounts.user_id = users.id
-
schema.rb
(또는structure.sql
) 파일을 VCS(버전 관리 시스템)에 포함시킨다. [link] -
빈 database를 초기화할 때는
rake db:migrate
대신rake db:schema:load
를 사용한다. [link] -
기본 설정 값들은 애플리케이션에서 지정하기보다, 마이그레이션 자체에 포함시킨다. [link]
# 나쁜 예 - 애플리케이션에서 기본설정 값을 지정하는 예 class Product < ActiveRecord::Base def amount self[:amount] || 0 end end # 좋은 예 - 디비에서 지정 class AddDefaultAmountToProducts < ActiveRecord::Migration def change change_column_default :products, :amount, 0 end end
테이블의 기본 설정 값을 레일스 애플리케이션에서만 지정하는 것은 많은 레일스 개발자들이 사용하지만, 이는 데이터를 어플리케이션 버그에 취약하게 만드는 아주 불안정한 접근방법이다. 그리고 대부분의 중요한 애플리케이션들은 하나의 데이터베이스를 다른 애플리케이션과 공유하기 때문에, 레일스 애플리케이션을 통해 데이터 무결성을 보장하는 것은 불가능하다는 사실을 고려해야한다.
-
식 외래키 제약을 사용한다. 레일스 4.2부터 엑티브 레코드는 외래키 제약을 기본적으로 지원한다. [link]
-
(테이블이나 컬럼을 추가하는) 구조적인 마이그레이션을 작성할 때는
up
과down
메소드 대신change
메소드를 정의한다. [link]# 예전 방식 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
-
마이그레이션에서 모델을 사용해야 한다면, 이를 정의해서 나중에 마이그레이션이 깨지지 않게 만들어라. [link]
# db/migrate/<migration_file_name>.rb # frozen_string_literal: true # 나쁜 예 class ModifyDefaultStatusForProducts < ActiveRecord::Migration def change old_status = 'pending_manual_approval' new_status = 'pending_approval' reversible do |dir| dir.up do Product.where(status: old_status).update_all(status: new_status) change_column :products, :status, :string, default: new_status end dir.down do Product.where(status: new_status).update_all(status: old_status) change_column :products, :status, :string, default: old_status end end end end # 좋은 예 # 임의의 클래스에 `table_name`으로 정의하여 마이그레이션 생성시에 가지고 # 있었던 그 테이블을 사용할 수 있게 하라. # 미래에 `Product` 클래스를 오버라이드하고, `table_name`을 바꾸게 되더라도 # 이 코드는 마이그레이션을 실행하더라도 문제가 없으며 심각한 데이터 손상을 # 야기하지도 않는다. class MigrationProduct < ActiveRecord::Base self.table_name = :products end class ModifyDefaultStatusForProducts < ActiveRecord::Migration def change old_status = 'pending_manual_approval' new_status = 'pending_approval' reversible do |dir| dir.up do MigrationProduct.where(status: old_status).update_all(status: new_status) change_column :products, :status, :string, default: new_status end dir.down do MigrationProduct.where(status: new_status).update_all(status: old_status) change_column :products, :status, :string, default: old_status end end end end
-
레일즈의 자동생성된 외래 키 이름 대신 외래 키 이름을 명시적으로 지정한다. (http://guides.rubyonrails.org/active_record_migrations.html#foreign-keys) [link]
# 나쁜 예 class AddFkArticlesToAuthors < ActiveRecord::Migration def change add_foreign_key :articles, :authors end end # 좋은 예 class AddFkArticlesToAuthors < ActiveRecord::Migration def change add_foreign_key :articles, :authors, name: :articles_author_id_fk end end
-
change
메소드에서는 불가역한 마이그레이션 명령을 사용하지 말라. 가역적인 마이그레이션 명령은 다음에서 확인할 수 있다. ActiveRecord::Migration::CommandRecorder [link]# 나쁜 예 class DropUsers < ActiveRecord::Migration def change drop_table :users end end # 좋은 예 class DropUsers < ActiveRecord::Migration def up drop_table :users end def down create_table :users do |t| t.string :name end end end # 좋은 예 # 여기에서 블럭은 롤백에서 create_table을 사용한다. # http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-i-drop_table class DropUsers < ActiveRecord::Migration def change drop_table :users do |t| t.string :name end end end
-
뷰에서 직접적으로 모델을 사용하지 않는다. [link]
-
뷰에서는 절대 복잡한 포맷팅을 만들지 말고, 이러한 포맷팅은 뷰 헬퍼 메소드나 모델로 분리한다. [link]
-
부분 템플릿(partial template)과 레이아웃을 이용하여 코드 중복을 줄인다. [link]
-
뷰, 모델, 컨트롤러에서는 지역(locale) 관련 설정이나 문자열을 바로 사용하지 않는다. 이러한 문자열들은
config/locales
디렉터리 아래의 로케일 파일로 옮겨 관리한다. [link] -
엑티브 레코드 모델의 레이블에 대한 번역이 필요할 때는
activerecord
스코프 아래에 작성한다. [link]en: activerecord: models: user: Member attributes: user: name: 'Full name'
이 때
User.model_name.human
은 "Member"를 반환하고User.human_attribute_name("name")
은 "Full name"을 반환한다. 이러한 속성들에 대한 번역은 뷰에서 레이블로 사용된다. -
뷰에서 사용되는 엑티브 레코드 속성들에 대한 번역은 분리한다.
locales/models
디렉터리에 모델을 위한 로케일 파일들을 저장하고, 뷰에서 사용하는 텍스트는locales/views
에 저장한다. [link]-
로케일(locale) 파일들을 적절한 위치에 저장하기 위해 디렉터리를 추가로 만들었다면, 이 파일들을 읽어들일 수 있도록
application.rb
파일에 설정해야 한다.# config/application.rb config.i18n.load_path += dir[rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
-
-
날짜나 통화 형식과 같은 공유해서 사용할 수 있는 지역화 옵션들은
locale
바로 아래에 저장한다. [link] -
i18n의 짧은 형식의 메소드를 사용한다.
i18n.translate
=>i18n.t
i18n.localize
=>i18n.l
[link] -
뷰에서 사용되는 텍스트에 대해 게으른 참조(lazy lookup)를 사용한다. 게으른 참조란 번역 텍스트의 구조를 뷰 디렉터리 구조와 같게 하여, 뷰에서 간단히 번역 텍스트를 참조하는 방법이다. 예를 들어 다음과 같은 구조가 있다고 하자. [link]
en: users: show: title: 'user details page'
users.show.title
의 값은app/views/users/show.html.haml
템플릿에서 다음과 같이 사용될 수 있다.= t '.title'
-
컨트롤러와 모델에서
:scope
옵션을 사용하기보다, 점으로 분리된 키를 사용한다. 읽기도 쉽고 계층 구조를 파악하기도 쉽다. [link]# 나쁜 예 I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] # 좋은 예 I18n.t 'activerecord.errors.messages.record_invalid'
-
레일스 I18n과 관련된 더 자세한 정보는 레일스 가이드(Rails Guides)를 참고하라. [link]
에셋 파이프라인을 사용하라. 이는 애플리케이션 배포에 필요한 에셋 파일들을 조직해줄 것이다.
-
사용자 정의 스타일시트, 자바스크립트, 이미지는
app/assets
디렉터리 아래에 저장한다. [link] -
애플리케이션에 포함되지 않는 직접 작성한 라이브러리들은
lib/assets
에 저장한다. [link] -
가능하다면 젬으로 만들어진 에셋을 사용한다(예를 들어, jquery-rails, jquery-ui-rails, bootstrap-sass, zurb-foundation). [link]
-
메일러의 이름은
SomethingMailer
형식을 따른다. 이러한 접미사가 없다면 메일러 클래스인지 바로 파악하기가 어렵고, 어떠한 뷰에 연결되어 있는지 찾아내기 어렵다. [link] -
HTML과 텍스트(plain text) 기반의 두 가지 템플릿을 각각 준비한다. [link]
-
개발 환경에서 메일 전송에 실패하면 에러가 발생하도록 설정한다. 기본 설정 값은 에러가 발생하지 않도록 설정되어 있다. [link]
# config/environments/development.rb config.action_mailer.raise_delivery_errors = true
-
개발 환경에서는 Mailcatcher와 같은 로컬 SMTP 서버를 사용한다. [link]
# config/environments/development.rb config.action_mailer.smtp_settings = { address: 'localhost', port: 1025, # more settings }
-
호스트의 이름을 기본 설정 값을 지정한다. [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' } # 메일러 클래스 안에서 설정 default_url_options[:host] = 'your_site.com'
-
이메일에 사이트의 링크를 넣고 싶을 때엔 항상
_path
대신_url
메소드를 사용한다._url
메소드는 호스트 이름을 같이 반환하고,_path
메소드는 그렇지 않다. [link]# 나쁜 예 You can always find more info about this course <%= link_to 'here', course_path(@course) %> # 좋은 예 You can always find more info about this course <%= link_to 'here', course_url(@course) %>
-
보내는 사람(from)과 받는 사람(to)의 이메일 형식을 적절하게 지정한다. 다음 형식을 따른다. [link]
# 메일러 클래스 안에서 설정한다 default from: 'Your Name <info@your_site.com>'
-
테스트 환경에서는 이메일 전송 메소드를
test
로 설정한다. [link]# config/environments/test.rb config.action_mailer.delivery_method = :test
-
개발 및 배포 환경에서는 이메일 전송 메소드가
smtp
로 설정되어 있어야 한다. [link]# config/environments/development.rb, config/environments/production.rb config.action_mailer.delivery_method = :smtp
-
html 형식의 이메일을 전송할 때, 일부 클라이언트에서는 외부 스타일시트를 참조할 때 문제가 발생할 수 있기 때문에 css는 모두 인라인으로 작성되어야 한다. 하지만 인라인 스타일을 사용하면 유지보수가 힘들고 코드 중복이 발생하게 된다. 스타일과 html을 자동적으로 결합해주는 아래 두 가지 젬이 존재한다. premailer-rails와 roadie. [link]
-
컨트롤러에서 요청에 대한 응답을 처리하는 도중에 이메일을 보내서는 안 된다. 이는 페이지 로딩을 지연시키고, 여러 메일을 동시에 발송할 때 타임아웃이 될 수도 있다. 이메일 전송은 sidekiq과 같은 백그라운드 작업을 지원하는 젬을 사용해 이루어져야 한다. [link]
-
ActiveSupport#try!
보단 루비 2.3의 안전한 내비게이션 연산자인&.
을 사용하자 [link]
# 나쁜 예
obj.try! :fly
# 좋은 예
obj&.fly
-
ActiveSupport
보다 루비의 표준 라이브러리를 사용하자. [link]
# 나쁜 예
'the day'.starts_with? 'th'
'the day'.ends_with? 'ay'
# 좋은 예
'the day'.start_with? 'th'
'the day'.end_with? 'ay'
- 흔하지 않은 ActiveSupport 확장보단 루비 표준 라이브러리를 사용하자. [link]
# 나쁜 예
(1..50).to_a.forty_two
1.in? [1, 2]
'day'.in? 'the day'
# 좋은 예
(1..50).to_a[41]
[1, 2].include? 1
'the day'.include? 'day'
-
ActiveSupport의
Array#inquiry
,Numeric#inquiry
그리고String#inquiry
보다는 루비의 비교 연산자를 사용하자. [link]
# 나쁜 예 - String#inquiry
ruby = 'two'.inquiry
ruby.two?
# 좋은 예
ruby = 'two'
ruby == 'two'
# 나쁜 예 - Array#inquiry
pets = %w(cat dog).inquiry
pets.gopher?
# 좋은 예
pets = %w(cat dog)
pets.include? 'cat'
# 나쁜 예 - Numeric#inquiry
0.positive?
0.negative?
# 좋은 예
0 > 0
0 < 0
-
application.rb
에 타임존을 적절히 설정한다. [link]config.time_zone = 'Eastern European Time' # 아래 옵션에는 :utc나 :local만을 지정할 수 있다. (기본 설정 값은 :utc) config.active_record.default_timezone = :local
-
Time.parse
를 사용하지 않는다. [link]# 나쁜 예 Time.parse('2015-03-02 19:05:37') # => 이 메소드는 시스템 타임존에서 시간이 주어진 것으로 가정함 # 좋은 예 Time.zone.parse('2015-03-02 19:05:37') # => Mon, 02 Mar 2015 19:05:37 EET +02:00
-
Time.now
를 사용하지 않는다. [link]# 나쁜 예 Time.now # => 타임존 설정과 무관하게 시스템의 시간을 반환한다 # 좋은 예 Time.zone.now # => Fri, 12 Mar 2014 22:04:47 EET +02:00 Time.current # 위와 같지만 더 짧은 방법
-
Gemfile
에는 개발 또는 테스트 환경에 필요한 젬들의 목록을 그룹별로 기술한다. [link] -
신뢰할만한 젬들만을 사용한다. 잘 알려지지 않은 젬을 사용하고자 한다면 우선 소스 코드와 리뷰들을 살펴보자. [link]
-
다른 OS를 사용하는 개발자들과 함께 프로젝트를 진행하게 되면 지속적으로
Gemfile.lock
이 변경될 것이다. 따라서 Gemfile의darwin
그룹에는 OS X 기반의 젬을 명시하고,linux
그룹에는 리눅스 기반의 젬을 두어라. [link]# Gemfile group :darwin do gem 'rb-fsevent' gem 'growl' end group :linux do gem 'rb-inotify' end
특정 환경에서 적절한 젬을 사용하기 위해서
config/application.rb
에 아래와 같이 추가한다.platform = RUBY_PLATFORM.match(/(linux|darwin)/)[0].to_sym Bundler.require(platform)
-
Gemfile.lock
파일은 버전 관리에 포함한다. 이 파일은 무작위로 생성된 것이 아니므로, 같은 프로젝트를 진행하는 여러 개발자들이bundle install
명령으로 같은 버전의 젬을 설치할 수 있게 도와준다. [link]
레일스 스타일과 관련된 반드시 읽어야할 훌륭한 자료들이 더 많이 있다. 아래 자료들을 참고해주세요.
- The Rails 4 Way
- Ruby on Rails Guides
- The RSpec Book
- The Cucumber Book
- Everyday Rails Testing with RSpec
- Rails 4 Test Prescriptions
- Better Specs for RSpec
이 문서는 아직 완성된 것이 아닙니다. 저는 이 가이드를 레일스 코딩 스타일에 관심이 있는 사람들과 함께 만들어 가면서, 모든 루비 커뮤니티에 유용한 자료가 되었으면 합니다.
가이드 개선을 위한 풀리퀘스트(pull requests)를 마음껏 보내주세요. 이에 대해 미리 감사의 말씀을 드립니다.
또한 이 프로젝트(와 RuboCop)에 기부를 원하시는 분은 gittip을 통해 참여할 수 있습니다.
기여하기 문서를 참고해주세요.
이 문서는 Creative Commons Attribution 3.0 Unported License를 따릅니다.
이 문서의 존재가 알려지지 않는다면 커뮤니티 기반의 스타일 가이드는 아무런 의미가 없습니다. 이 가이드에 대하여 트윗하거나, 친구와 동료들에게 공유해주세요. 댓글, 의견, 제안을 주시면 조금 더 나은 가이드를 만드는데 도움이 될 것입니다. 더 좋은 가이드를 만들고 싶지 않으신가요?
화이팅
Bozhidar