Ролевые модели важны.
-- Офицер Алекс Мёрфи / Робот-полицейский
Целью этого руководства является распространение набора проверенных практик и стилистических рекомендаций при разработки приложений с помощью Ruby on Rails (для версий 3 и 4). Это руководство дополняет уже существующий сборник: Руби: руководство по стилю оформления.
Некоторые из приведенных здесь рекомендаций будут применимы только к Rails 4.0+.
Вы можете создать копию этого руководства в форматах PDF или HTML при помощи Pandoc.
Переводы данного руководства доступны на следующих языках:
- английский (исходная версия)
- вьетнамский
- китайский традиционный
- китайский упрощенный
- корейский
- португальский (pt-BR)
- русский (данный документ)
- турецкий
- японский
Настоящее руководство по стилю рекомендует лучшие практики оформления, благодаря которым обычные разработчики на Rails смогут писать код, который с легкостью будут поддерживать другие обычные программисты. Руководство по оформлению, которое отражает повседневные реалии, будет применяться постоянно, а руководство, стремящееся к идеалу, который не принимается рядовыми специалистами, подвергается риску вообще быть забытым. При этом абсолютно не важно, насколько хорошим оно является.
Данное руководство разделено на несколько частей, состоящий из связанных по смыслу правил. В каждом случае мы попытались обосновать появление этих правил (объяснение опущено в ситуациях, когда мы посчитали его очевидным).
Все эти правила не появились из пустоты, они по большей части основываются на нашем собственном обширном профессиональном опыте в разработке ПО, отзывах и предложениях других членов сообщества разработчиков на Rails и различных общепризнанных источниках по созданию Rails-приложений.
- Конфигурация
- Маршрутизация
- Контроллеры
- Модели
- Миграции
- Представления
- Интернационализация
- Ресурсы
- Почтовые модули
- Active Support Core Extensions
- Время
- Bundler
- Управление процессами
-
Код для инициализации приложения помещайте в директорию
config/initializers/
. Код в этой директории выполняется при запуске приложения. [ссылка] -
Для каждого гема записывайте код инициализации в одноименный отдельный файл. Например,
carrierwave.rb
,active_admin.rb
и т.д. [ссылка] -
Уточните настройки для рабочего (development), тестового (test) и промышленного (production) окружений в соответствующих файлах в директории
config/environments/
. [ссылка]- Укажите добавленные вами ресурсы для комплиляции (при наличии):
# config/environments/production.rb # Скомпилируйте дополнительные ресурсы (application.js, application.css, # и прочие не JS/CSS уже добавлены). config.assets.precompile += %w( rails_admin/rails_admin.css rails_admin/rails_admin.js )
-
Сохраняйте настройки, которые относятся ко всем окружениям, в файле
config/application.rb
. [ссылка] -
Создайте дополнительное окружение
staging
, которое будет очень схоже с вашим окружениемproduction
. [ссылка] -
Храните любые дополнительные файлы конфиругации в формате
YAML
в директорииconfig/
. [ссылка]Начиная с
Rails 4.2
файлы конфиругации вYAML
можно легко загружать при помощи нового методаconfig_for
:Rails::Application.config_for(:yaml_file)
-
Если вам требуется добавить дополнительные действия к ресурсу REST (и вы уверены, что это вам абсолютно необходимо), то используйте пути
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' # дополнительные маршруты end end resources :photos do collection do get 'search' # дополнительные маршруты 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
-
Если существует необходимость делать несколько уровней вложенности, то следует применять опцию
shallow: true
. Это оградит пользователя от длинных URLposts/1/comments/5/versions/7/edit
и вас от применения длинных наименований вродеedit_post_comment_version
.resources :posts, shallow: true do resources :comments do resources :versions end end
-
Используйте определенные в отдельном пространстве имен маршруты, чтобы объединить связанные действия. [ссылка]
namespace :admin do # Направляет /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
). [ссылка] -
Старайтесь не передавать более двух переменных из контроллера в шаблон. [ссылка]
-
Controller actions specified in the option of Action Filter should be in lexical scope. The ActionFilter specified for an inherited action makes it difficult to understand the scope of its impact on that action. [ссылка]
# плохо class UsersController < ApplicationController before_action :require_login, only: :export end # хорошо class UsersController < ApplicationController before_action :require_login, only: :export def export end end
-
Prefer using a template over inline rendering. [link]
# 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/foo_controller.rb class ProductsController < ApplicationController def index render :index end end
-
Prefer
render plain:
overrender text:
. [link]# 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!' ...
-
Prefer corresponding symbols to numeric HTTP status codes. They are meaningful and do not look like "magic" numbers for less known HTTP status codes. [link]
# плохо # некоторый код render status: 403 # некоторый код # хорошо # некоторый код render status: :forbidden # некоторый код
-
Без зазрений совести используйте модели, не базирующиеся на
ActiveRecord
. [ссылка] -
Называйте модели говорящими (но короткими) именами без сокращений. [ссылка]
-
Если вам нужна модель, поддерживающая некоторые аспекты
ActiveRecord
(например, валидации), без привязки к работе с БД используйте библиотеку ActiveAttr. [ссылка]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 #326: ActiveAttr.
-
Не расширяйте свои модели методами, которые реализуют форматирование данных (например, кодогенерацию HTML), кроме случаев, когда это напрямую связано с бизнес-логикой описываемой предметной области. Такие методы с большой вероятностью будут вызываться только из шаблонов представлений, поэтому их лучше разместить во вспомогательных модулях (helpers). Реализуйте в моделях только бизнес-логику и функционал работы с данными. [link]
-
Не меняйте стандартных значений
ActiveRecord
(например, наименования таблиц, первичных ключей и т.д.) без особой нужды. Это оправдано лишь в тех случаях, когда вы работаете с базой данных, схему которой (по разным причинам) нет возможности изменить. [ссылка]# плохо (не делайте так, если вы можете изменить схему) class Transaction < ActiveRecord::Base self.table_name = 'order' ... end
-
Группируйте макро-методы (
has_many
,validates
и т.д.) в начале определения класса. [ссылка]class User < ActiveRecord::Base # записывайте стандартную область видимости в начале (если имеется) default_scope { where(active: true) } # после этого записывайте константы COLORS = %w(red green blue) # далее следуют макросы доступа к атрибутам attr_accessor :formatted_date_of_birth attr_accessible :login, :first_name, :last_name, :email, :password # Энумераторы для `Rails >= 4` после макросов доступа, # предпочтительно использовать новых синтаксис для хешей. enum gender: { female: 0, male: 1 } # за которыми следуют макросы ассоциаций 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 } # после этого идут функции обратного вызова (callbacks) before_save :cook before_save :update_username_lower # оставшиеся макросы (например, Devise) дожны записываться в конце ... end
-
Используйте преимущественно
has_many :through
вместоhas_and_belongs_to_many
. Применение ассоциации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
-
Используйте
self[:attribute]
вместоread_attribute(:attribute)
. [ссылка]# плохо def amount read_attribute(:amount) * 100 end # хорошо def amount self[:amount] * 100 end
-
Преимущественно используйте
self[:attribute] = value
вместоwrite_attribute(:attribute, value)
. [ссылка]# плохо def amount write_attribute(:amount, 100) end # хорошо def amount self[:amount] = 100 end
-
Всегда применяйте новый синтаксис валидаций, так называемые "sexy" validations. [ссылка]
# плохо validates_presence_of :email validates_length_of :email, maximum: 100 # хорошо validates :email, presence: true, length: { maximum: 100 }
-
To make validations easy to read, don't list multiple attributes per validation [link]
# bad validates :email, :password, presence: true validates :email, length: { maximum: 100 } # good validates :email, presence: true, length: { maximum: 100 } validates :password, presence: true
-
Если определенная разработчиком валидация используется несколько раз или содержит сложные (регулярные) выражения, то стоит ее вынести в отдельный файл валидаторов. [ссылка]
# плохо 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
. [ссылка] -
Подумайте о том, чтобы выделить ряд определенных вами валидаторов в отдельный гем, если вы работаете над несколькими схожими приложениями и валидаторы имеют достаточно обобщенные функции. [ссылка]
-
Спокойно применяйте поименованные области поиска (named scopes). [ссылка]
class User < ActiveRecord::Base scope :active, -> { where(active: true) } scope :inactive, -> { where(active: false) } scope :with_orders, -> { joins(:orders).select('distinct(users.id)') } end
-
Если поименованная область поиска определяется с помощью
lambda
с дополнительными параметрами, то такая запись очень быстро может стать слишком сложной. В таком случае лучше определить метод класса, который будет служить той же цели и возвращать объект классаActiveRecord::Relation
. Наверное, этим же образом можно определять и более простые области поиска. [ссылка]class User < ActiveRecord::Base def self.with_orders joins(:orders).select('distinct(users.id)') end end
-
Order callback declarations in the order, in which they will be executed. For referenece, see Available Callbacks [ссылка]
# плохо class Person after_commit/after_rollback :after_commit_callback after_save :after_save_callback around_save :around_save_callback after_update :after_update_callback before_update :before_update_callback after_validation :after_validation_callback before_validation :before_validation_callback before_save :before_save_callback before_create :before_create_callback after_create :after_create_callback around_create :around_create_callback around_update :around_update_callback end # хорошо class Person before_validation :before_validation_callback after_validation :after_validation_callback before_save :before_save_callback around_save :around_save_callback before_create :before_create_callback around_create :around_create_callback after_create :after_create_callback before_update :before_update_callback around_update :around_update_callback after_update :after_update_callback after_save :after_save_callback after_commit/after_rollback :after_commit_callback end
-
Поймите принцип работы следующих методов. Они не вызывают валидацию моделей и могут быстро привести к появлению ошибочных записей в базе данных. [ссылка]
# плохо 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
. Есть несколько путей для достижения такого результата: [ссылка]-
Переопределите метод
to_param
в модели. Это метод используется вRails
для генерирования URL к объекту. Стандартная реализация возвращаетid
объекта (записи) в виде строки. Это поведение можно переопределить и включить некоторый понятный человеку атрибут.class Person def to_param "#{id} #{name}".parameterize end end
Чтобы преобразовать эту форму в более адекватную для URL, нужно вызвать метод
parameterize
на строковом объекте. Идентификаторid
объекта должен быть в начале строки, чтобы его мог найти методfind
библиотекиActiveRecord
. -
Используйте гем
friendly_id
. Эта библиотека создает легко читаемые URL с использованием некоторых говорящих атрибутов моделей вместоid
.class Person extend FriendlyId friendly_id :name, use: :slugged end
Изучите документацию гема, чтобы лучше разобраться в его применении.
-
-
Используйте
find_each
для обхода коллекций объектовActiveRecord
. Перебор объектов записей базы данных в цикле (например, с использованием методаall
) крайне неэффективен, так как в данном случае в памяти будут созданы все интересующие нас объекты за раз. Для такого рода задач больше подходит пакетная обработка, при которой методы вызывают записи порциями, что значительно сокращает расход памяти. [ссылка]# плохо Person.all.each do |person| person.do_awesome_stuff end Person.where("age > 21").each do |person| person.party_all_night! end # хорошо Person.all.find_each do |person| person.do_awesome_stuff end Person.where("age > 21").find_each do |person| person.party_all_night! end
-
Rails создает методы обратного вызова для зависимых ассоциаций, поэтому всегда вызывайте метод
before_destroy
для валидации с параметромprepend: true
. [ссылка]# плохо (роли будут удалены в любом случае, даже если super_admin? задан) has_many :roles, dependent: :destroy before_destroy :ensure_deletable def ensure_deletable raise "Cannot delete super admin." if super_admin? end # хорошо has_many :roles, dependent: :destroy before_destroy :ensure_deletable, prepend: true def ensure_deletable raise "Cannot delete super admin." if super_admin? end
-
Задавайте опцию
dependent
в ассоциация типаhas_many
иhas_one
. [link]# плохо class Post < ActiveRecord::Base has_many :comments end # хорошо class Post < ActiveRecord::Base has_many :comments, dependent: :destroy end
-
When persisting AR objects always use the exception raising bang! method or handle the method return value. This applies to
create
,save
,update
,destroy
,first_or_create
andfind_or_create_by
. [link]# bad user.create(name: 'Bruce') # bad user.save # good user.create!(name: 'Bruce') # or bruce = user.create(name: 'Bruce') if bruce.persisted? ... else ... end # good user.save! # or if user.save ... else ... end
-
Избегайте интерполяции строк в запросах, это сделает ваш код менее уязвимым к атакам типа
SQL injection
. [ссылка]# плохо (param будет вставлен без экранирования) Client.where("orders_count = #{params[:orders]}") # хорошо (param будет экранирован должным образом) Client.where('orders_count = ?', params[:orders])
-
Предпочитайте поименованные подстановки вместо позиционных подстановок, если у вас в запросе их более двух. [ссылка]
# сойдет 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] )
-
Отдавайте предпочтение использованию
find
вместоwhere(...).take!
,find_by!
,если вам нужно получить всего одну запись по ее идентификатору. Favor the use of
findover
where.take!,
find_by!, and
find_by_id!when you need to retrieve a single record by primary key id and raise
ActiveRecord::RecordNotFound` when the record is not found. [ссылка]# плохо User.where(id: id).take! # bad User.find_by_id!(id) # bad User.find_by!(id: id) # хорошо User.find(id)
-
Отдавайте предпочтение использованию
find_by
вместоwhere
иfind_by_attribute
, если вам нужно получить всего одну запись по значению какого-то ее атрибута. [ссылка]# плохо 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')
-
Favor the use of
find_by
overwhere.take
andfind_by_attribute
when you need to retrieve a single record by one or more attributes and returnnil
when the record is not found. [link]# bad User.where(id: id).take User.where(first_name: 'Bruce', last_name: 'Wayne').take # bad User.find_by_id(id) # bad, deprecated in ActiveRecord 4.0, removed in 4.1+ User.find_by_first_name_and_last_name('Bruce', 'Wayne') # good User.find_by(id: id) User.find_by(first_name: 'Bruce', last_name: 'Wayne')
-
Используйте
where.not
вместо простого SQL. [ссылка]# плохо User.where("id != ?", id) # хорошо User.where.not(id: id)
-
Don't use the
id
column for ordering. The sequence of ids is not guaranteed to be in any particular order, despite often (incidentally) being chronological. Use a timestamp column to order chronologically. As a bonus the intent is clearer. [link]# bad scope :chronological, -> { order(id: :asc) } # good scope :chronological, -> { order(created_at: :asc) }
-
Favor the use of
ids
overpluck(:id)
. [link]# bad User.pluck(:id) # good User.ids
-
-
При явном формулировании запроса в таких методах, как
find_by_sql
, используйте HEREDOC в сочетании с методом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 # прочие детали... SQL
String#squish
удаляет отступы и переносы, таким образом запросы будут отображаться в виде обычных строк, а не в виде вот таких последовательностей:SELECT\n users.id, accounts.plan\n FROM\n users\n INNER JOIN\n acounts\n ON\n accounts.user_id = users.id
-
When querying ActiveRecord collections, prefer
size
(selects between count/length behavior based on whether collection is already loaded) orlength
(always loads the whole collection and counts the array elements) overcount
(always does a database query for the count). [link]# bad User.count # good User.all.size # good - if you really need to load all users into memory User.all.length
-
Храните файл
schema.rb
(илиstructure.sql
) в вашей системе управления версиями. [ссылка] -
Используйте вызов
rake db:schema:load
вместоrake db:migrate
для инициализации пустой базы данных. [ссылка] -
Устанавливайте стандартные значения в миграциях, а не в бизнес-логике вашего приложения. [ссылка]
# плохо (стандартное значение устанавливается в приложении) class Product < ActiveRecord::Base def amount self[:amount] || 0 end end # хорошо (стандартное значение устанавливается на уровне БД) class AddDefaultAmountToProducts < ActiveRecord::Migration def change change_column_default :products, :amount, 0 end
Многие опытные разработчики на
Rails
рекомендуют устанавливать стандартные значения только на уровне приложения и миграций, однако этот подход скрывает множество уязвимостей и потенциальных ошибок. Кроме этого, стоит рассмотреть тот момент, что большиство сложных приложений используют одну совместную базу данных вместе с другими приложениями, поэтому логика проверки, реализованная в приложении наRails
, будет недоступа из других приложений. -
Устанавливайте ограничения на внешние ключи. Начиная с
Rails 4.2
библиотекаActiveRecord
поддерживает внешние ключи напрямую. [ссылка] -
При написании миграций для добавления таблиц или столбцов создавайте метод
change
вместо методовup
иdown
. [ссылка]# старый способ 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
-
Если вам нужно использовать код модели в миграциях, постарайтесь задать модели так, чтобы не получить в будущем неработающие миграции. [ссылка]
# 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
-
Явно выбирайте наименования для внешних ключей (foreign key), не полагайтесь на автоматически сгенерированные имена ключей: 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` # https://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
-
Ни при каких условиях не следует работать с моделями напрямую из представлений. [ссылка]
-
Избегайте сложной логики в представлениях, выделяйте этот код во вспомогательные методы представлений (view helpers) или выносите их в модель. [ссылка]
-
Избегайте повторений кода, используйте отдельные шаблоны и подшаблоны представлений. [ссылка]
-
Строки и другие локальные настройки и детали следует выносить из представлений и контроллеров, а также моделей в файлы для конкретных локалей в директории
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
. Размещайте файлы локалей для моделей в директорииlocales/models
, а используемые в представлениях тексты в директорииlocales/views
. [ссылка]-
Если файлы локалей сохраняются в дополнительных директориях, то пути к ним должны быть определены в файле
application.rb
, чтобы файлы локалей могли быть загружены.# config/application.rb config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
-
-
Размещайте общие параметры локализации, например, форматы записи дат и валют, в файлах в корне директории
locales
. [ссылка] -
Используйте краткую форму методов
I18n
:I18n.t
вместоI18n.translate
иI18n.l
вместоI18n.localize
. [ссылка] -
Используйте "ленивый" подход к поиску текстов в представлениях. Допустим, у вас есть следующая структура: [ссылка]
en: users: show: title: 'User details page'
Значение для
users.show.title
можно будет найти в шаблонеapp/views/users/show.html.haml
, например, так:= t '.title'
-
Задавайте ключи в контроллерах и моделях при помощи разделения точками, а не с помощью опции
:scope
. Разделенные точками вызовы проще читать, их иерархия более понятна. [ссылка]# плохо I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] # хорошо I18n.t 'activerecord.errors.messages.record_invalid'
-
Более подробную информацию по интернационализации (I18n) в Rails можно найти по адресу API интернационализации Rails либо Rails Guides (английский оригинал). [ссылка]
Применяйте конвейер ресурсов (assets pipeline) для упорядочения структуры вашего приложения.
-
Зарезервируйте директорию
app/assets
для ваших собственных таблиц стилей, скриптов и/или изображений. [ссылка] -
Используйте директорию
lib/assets
для ваших собственных библиотек, которые не реализуют первичный функционал вашего приложения. [ссылка] -
Код сторонних библиотек, например, jQuery или bootstrap, следует размещать в директории
vendor/assets
. [ссылка] -
По возможности используйте упакованные версии необходимых ресурсов, например: [ссылка]
-
Называйте почтовые модули по образцу
SomethingMailer
. Без суффиксаMailer
не сразу будет понятно, что это почтовый модуль и какие представления связаны с этим модулем. [ссылка] -
Создавайте шаблоны представлений в текстовом формате и в формате HTML. [ссылка]
-
Включите вызов ошибок при проблемах с доставкой почты в вашем окружении для разработки. По умолчанию эти вызовы отключены. [ссылка]
# config/environments/development.rb config.action_mailer.raise_delivery_errors = true
-
Используйте локальный сервер SMTP, например, Mailcatcher в вашем окружении разработки. [ссылка]
# 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' } # в вашем классе мейлера default_url_options[:host] = 'your_site.com'
-
Если вам нужно указать ссылку на ваш сайт в тексте письма, всегда используйте методы с суффиксом
_url
, а не_path
. Методы с суффиксом_url
включают имя вашего узла в текст ссылки, а с суффиксом_path
не включают. [ссылка]# плохо 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) %>
-
Записывайте адреса отправителя и получателя должным образом. Используйте следующий формат: [ссылка]
# в классе мейлера default from: 'Your Name <info@your_site.com>'
-
Убедитесь в том, что метод доставки писем в вашем тестовом окружении обозначен как
test
: [ссылка]# config/environments/test.rb config.action_mailer.delivery_method = :test
-
Методом доставки почты для разработки и развертывания должен быть
smtp
: [ссылка]# config/environments/development.rb, config/environments/production.rb config.action_mailer.delivery_method = :smtp
-
При рассылке электронной почты в формате HTML все описания стилей должны быть включены в текст, потому что некоторые почтовые программы неверно обрабатывают внешние таблицы стилей. Однако, это приводит к сложностям в поддержке таких таблиц и повторениям в коде. Для преобразования и внедрения стилей в текст письма существую два схожим по функциональности гема: premailer-rails и roadie. [ссылка]
-
Избегайте рассылки почты параллельно к генерации страницы в ответ на запрос пользователя. Это вызовет задержки в загрузке страницы, и запрос может быть отклонен из-за превышения таймаута, если вы рассылаете много писем. Вы можете преодолеть данное ограничение, вызывая процессы рассылки в фоне, например, при помощи гема sidekiq. [ссылка]
-
Prefer Ruby 2.3's safe navigation operator
&.
overActiveSupport#try!
. [link]# bad obj.try! :fly # good obj&.fly
-
Prefer Ruby's Standard Library methods over
ActiveSupport
aliases. [link]# bad 'the day'.starts_with? 'th' 'the day'.ends_with? 'ay' # good 'the day'.start_with? 'th' 'the day'.end_with? 'ay'
-
Prefer Ruby's Standard Library over uncommon ActiveSupport extensions. [link]
# bad (1..50).to_a.forty_two 1.in? [1, 2] 'day'.in? 'the day' # good (1..50).to_a[41] [1, 2].include? 1 'the day'.include? 'day'
-
Prefer Ruby's comparison operators over
ActiveSupport
Array#inquiry
,Numeric#inquiry
andString#inquiry
. [link]# bad - String#inquiry ruby = 'two'.inquiry ruby.two? # good ruby = 'two' ruby == 'two' # bad - Array#inquiry pets = %w(cat dog).inquiry pets.gopher? # good pets = %w(cat dog) pets.include? 'cat'
-
Настройте в файле
application.rb
вашу временную зону. [ссылка]config.time_zone = 'Eastern European Time' # опционально (обратите внимание, возможны только значения :utc или :local, # по умолчанию :utc) config.active_record.default_timezone = :local
-
Не используйте
Time.parse
. [ссылка]# плохо # Подразумевается, что передаваемая строка со временем отражает временную зону вашей ОС. 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
-
Не используйте
String#to_time
[ссылка]# плохо (временная зона по умолчанию равна системной) '2015-03-02 19:05:37'.to_time # хорошо Time.zone.parse('2015-03-02 19:05:37') # => Mon, 02 Mar 2015 19:05:37 EET +02:00
-
Не используйте
Time.now
. [ссылка]# плохо # Возвращает системное время и не учитывает настройки временной зоны. Time.now # хорошо Time.zone.now # => Fri, 12 Mar 2014 22:04:47 EET +02:00 Time.current # Более короткая форма записи.
-
Держите гемы, которые используются только для разработки и/или тестирования в соответствующих группах вашего
Gemfile
. [ссылка] -
Применяйте в своих проектах только проверенные временем библиотеки. Если вы задумываетесь, не включить ли в проект малоизвестный гем, вам следует сперва внимательно просмотреть его исходных код. [ссылка]
-
Системозависимые библиотеки в вашем проекте будут причиной постоянного изменения файла
Gemfile.lock
для проектов с несколькими разработчиками, работающими на разных операционных системах. Поэтому внесите все библиотеки, написанные дляOS X
, в группуdarwin
, а библиотеки, написанные дляLinux
, в группуlinux
. [ссылка]# 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
. [ссылка]
- Если в вашем проекте есть много зависимостей от внешних процессов, применяйте библиотеку foreman для управления ими. [ссылка]
Существует несколько отличных источников по стилю оформления приложений
на Rails
, на которые вы можете взглянуть, если у вас будет свободное время:
- 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
Ничто, описанное в этом руководстве, не высечено в камне. И я очень хотел бы сотрудничать со всеми, кто интересуется стилистикой оформления кода Rails, чтобы мы смогли вместе создать ресурс, который был бы полезен для всего сообщества программистов на Руби.
Не стесняйтесь создавать отчеты об ошибках и присылать мне запросы на интеграцию вашего кода. И заранее большое спасибо за вашу помощь!
Вы можете поддержать проект (и РубоКоп) денежным взносом при помощи Patreon.
Это просто! Просто следуйте руководству по сотрудничеству.
Данная работа опубликована на условиях лицензии Creative Commons Attribution 3.0 Unported License
Создаваемое сообществом руководство по стилю оформления будет малопригодным для сообщества, которое об этом руководстве ничего не знает. Делитесь ссылками на это руководство с вашими друзьями и коллегами доступными вам средствами. Каждый получаемый нами комментарий, предложение или мнение сделает это руководство еще чуточку лучше. А ведь мы хотим самое лучшее руководство из возможных, не так ли?
Всего,
Божидар