{"id":13393136,"url":"https://github.com/rubocop/rails-style-guide","last_synced_at":"2026-03-11T09:32:10.240Z","repository":{"id":1723457,"uuid":"2454047","full_name":"rubocop/rails-style-guide","owner":"rubocop","description":"A community-driven Ruby on Rails style guide","archived":false,"fork":false,"pushed_at":"2025-11-24T19:52:39.000Z","size":732,"stargazers_count":6520,"open_issues_count":40,"forks_count":1051,"subscribers_count":203,"default_branch":"master","last_synced_at":"2026-02-19T05:29:59.024Z","etag":null,"topics":["rails","rubocop","ruby","styleguide"],"latest_commit_sha":null,"homepage":"http://rails.rubystyle.guide","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rubocop.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"bbatsov","patreon":"bbatsov","open_collective":"rubocop","tidelift":"rubygems/rubocop","custom":"https://www.paypal.me/bbatsov"}},"created_at":"2011-09-25T09:36:31.000Z","updated_at":"2026-02-16T01:21:07.000Z","dependencies_parsed_at":"2024-11-06T06:37:15.833Z","dependency_job_id":"bdcefe13-b481-4ef8-8096-bdcdfb63864a","html_url":"https://github.com/rubocop/rails-style-guide","commit_stats":{"total_commits":319,"total_committers":130,"mean_commits":2.453846153846154,"dds":0.9184952978056427,"last_synced_commit":"524a1ac853f8e684de4a6f7b95497e9e8dce42bb"},"previous_names":["rubocop-hq/rails-style-guide","bbatsov/rails-style-guide"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rubocop/rails-style-guide","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubocop%2Frails-style-guide","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubocop%2Frails-style-guide/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubocop%2Frails-style-guide/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubocop%2Frails-style-guide/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rubocop","download_url":"https://codeload.github.com/rubocop/rails-style-guide/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubocop%2Frails-style-guide/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30377276,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-11T06:09:32.197Z","status":"ssl_error","status_checked_at":"2026-03-11T06:09:17.086Z","response_time":84,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rails","rubocop","ruby","styleguide"],"created_at":"2024-07-30T17:00:43.902Z","updated_at":"2026-03-11T09:32:10.206Z","avatar_url":"https://github.com/rubocop.png","language":null,"funding_links":["https://github.com/sponsors/bbatsov","https://patreon.com/bbatsov","https://opencollective.com/rubocop","https://tidelift.com/funding/github/rubygems/rubocop","https://www.paypal.me/bbatsov","https://www.patreon.com/bbatsov"],"categories":["Styleguides","Frameworks","Others","Code quality","Coding Style Guides"],"sub_categories":["Ruby","Rails"],"readme":"= Rails Style Guide\n:idprefix:\n:idseparator: -\n:sectanchors:\n:sectlinks:\n:toc: preamble\n:toclevels: 1\nifndef::backend-pdf[]\n:toc-title: pass:[\u003ch2\u003eTable of Contents\u003c/h2\u003e]\nendif::[]\n:source-highlighter: rouge\n\n== Introduction\n\nBy https://github.com/bbatsov[Bozhidar Batsov]\n\n[quote]\n____\nRole models are important.\n\n-- Officer Alex J. Murphy / RoboCop\n____\n\nifdef::env-github[]\nTIP: You can find a beautiful version of this guide with much improved navigation at https://rails.rubystyle.guide.\nendif::[]\n\nThe goal of this guide is to present a set of best practices and style prescriptions for Ruby on Rails development.\nIt's a complementary guide to the already existing community-driven https://github.com/rubocop/ruby-style-guide[Ruby coding style guide].\n\nThis Rails style guide recommends best practices so that real-world Rails programmers can write code that can be maintained by other real-world Rails programmers.\nA style guide that reflects real-world usage gets used, and a style guide that holds to an ideal that has been rejected by the people it is supposed to help risks not getting used at all - no matter how good it is.\n\nThe guide is separated into several sections of related rules.\nI've tried to add the rationale behind the rules (if it's omitted I've assumed it's pretty obvious).\n\nI didn't come up with all the rules out of nowhere - they are mostly based on my extensive career as a professional software engineer, feedback and suggestions from members of the Rails community and various highly regarded Rails programming resources.\n\nNOTE: Some of the advice here is applicable only to recent versions of Rails.\n\nYou can generate a PDF copy of this guide using https://asciidoctor.org/docs/asciidoctor-pdf/[AsciiDoctor PDF], and an HTML copy https://asciidoctor.org/docs/convert-documents/#converting-a-document-to-html[with] https://asciidoctor.org/#installation[AsciiDoctor] using the following commands:\n\n[source,shell]\n----\n# Generates README.pdf\nasciidoctor-pdf -a allow-uri-read README.adoc\n\n# Generates README.html\nasciidoctor README.adoc\n----\n\n[TIP]\n====\nInstall the `rouge` gem to get nice syntax highlighting in the generated document.\n\n[source,shell]\n----\ngem install rouge\n----\n====\n\nTranslations of the guide are available in the following languages:\n\n* https://github.com/satour/rails-style-guide/blob/master/README-jaJA.md[Japanese]\n* https://github.com/arbox/rails-style-guide/blob/master/README-ruRU.md[Russian]\n\nTIP: https://github.com/rubocop/rubocop[RuboCop], a static code analyzer (linter) and formatter, has a https://github.com/rubocop/rubocop-rails[`rubocop-rails`] extension, based on this style guide.\n\n== Configuration\n\n=== Config Initializers [[config-initializers]]\n\nPut custom initialization code in `config/initializers`.\nThe code in initializers executes on application startup.\n\n=== Gem Initializers [[gem-initializers]]\n\nKeep initialization code for each gem in a separate file with the same name as the gem, for example `carrierwave.rb`, `active_admin.rb`, etc.\n\n=== Dev/Test/Prod Configs [[dev-test-prod-configs]]\n\nAdjust accordingly the settings for development, test and production environment (in the corresponding files under `config/environments/`)\n\nMark additional assets for precompilation (if any):\n\n[source,ruby]\n----\n# config/environments/production.rb\n# Precompile additional assets (application.js, application.css,\n#and all non-JS/CSS are already added)\nconfig.assets.precompile += %w( rails_admin/rails_admin.css rails_admin/rails_admin.js )\n----\n\n=== App Config [[app-config]]\n\nKeep configuration that's applicable to all environments in the `config/application.rb` file.\n\n=== Load Rails Config Defaults [[config-defaults]]\n\nWhen upgrading to a newer Rails version, your application's configuration setting will remain on the previous version. To take advantage of the latest recommended Rails practices, the `config.load_defaults` setting should match your Rails version.\n\n[source,ruby]\n----\n# good\nconfig.load_defaults 6.1\n----\n\n=== Staging Like Prod [[staging-like-prod]]\n\nAvoid creating additional environment configurations than the defaults of `development`, `test` and `production`.\nIf you need a production-like environment such as staging, use environment variables for configuration options.\n\n=== YAML Config [[yaml-config]]\n\nKeep any additional configuration in YAML files under the `config/` directory.\n\nSince Rails 4.2 YAML configuration files can be easily loaded with the new `config_for` method:\n\n[source,ruby]\n----\nRails::Application.config_for(:yaml_file)\n----\n\n== Routing\n\n=== Member Collection Routes [[member-collection-routes]]\n\nWhen you need to add more actions to a RESTful resource (do you really need them at all?) use `member` and `collection` routes.\n\n[source,ruby]\n----\n# bad\nget 'subscriptions/:id/unsubscribe'\nresources :subscriptions\n\n# good\nresources :subscriptions do\n  get 'unsubscribe', on: :member\nend\n\n# bad\nget 'photos/search'\nresources :photos\n\n# good\nresources :photos do\n  get 'search', on: :collection\nend\n----\n\n=== Many Member Collection Routes [[many-member-collection-routes]]\n\nIf you need to define multiple `member/collection` routes use the alternative block syntax.\n\n[source,ruby]\n----\nresources :subscriptions do\n  member do\n    get 'unsubscribe'\n    # more routes\n  end\nend\n\nresources :photos do\n  collection do\n    get 'search'\n    # more routes\n  end\nend\n----\n\n=== Nested Routes [[nested-routes]]\n\nUse nested routes to express better the relationship between Active Record models.\n\n[source,ruby]\n----\nclass Post \u003c ApplicationRecord\n  has_many :comments\nend\n\nclass Comment \u003c ApplicationRecord\n  belongs_to :post\nend\n\n# routes.rb\nresources :posts do\n  resources :comments\nend\n----\n\n=== Shallow Routes [[shallow-routes]]\n\nIf you need to nest routes more than 1 level deep then use the `shallow: true` option.\nThis will save user from long URLs `posts/1/comments/5/versions/7/edit` and you from long URL helpers `edit_post_comment_version`.\n\n[source,ruby]\n----\nresources :posts, shallow: true do\n  resources :comments do\n    resources :versions\n  end\nend\n----\n\n=== Namespaced Routes [[namespaced-routes]]\n\nUse namespaced routes to group related actions.\n\n[source,ruby]\n----\nnamespace :admin do\n  # Directs /admin/products/* to Admin::ProductsController\n  # (app/controllers/admin/products_controller.rb)\n  resources :products\nend\n----\n\n=== No Wild Routes [[no-wild-routes]]\n\nNever use the legacy wild controller route.\nThis route will make all actions in every controller accessible via GET requests.\n\n[source,ruby]\n----\n# very bad\nmatch ':controller(/:action(/:id(.:format)))'\n----\n\n=== No Match Routes [[no-match-routes]]\n\nDon't use `match` to define any routes unless there is need to map multiple request types among `[:get, :post, :patch, :put, :delete]` to a single action using `:via` option.\n\n== Controllers\n\n=== Skinny Controllers [[skinny-controllers]]\n\nKeep the controllers skinny - they should only retrieve data for the view layer and shouldn't contain any business logic (all the business logic should naturally reside in the model).\n\n=== One Method [[one-method]]\n\nEach controller action should (ideally) invoke only one method other than an initial find or new.\n\n=== Shared Instance Variables [[shared-instance-variables]]\n\nMinimize the number of instance variables passed between a controller and a view.\n\n=== Lexically Scoped Action Filter [[lexically-scoped-action-filter]]\n\nController actions specified in the option of Action Filter should be in lexical scope.\nThe ActionFilter specified for an inherited action makes it difficult to understand the scope of its impact on that action.\n\n[source,ruby]\n----\n# bad\nclass UsersController \u003c ApplicationController\n  before_action :require_login, only: :export\nend\n\n# good\nclass UsersController \u003c ApplicationController\n  before_action :require_login, only: :export\n\n  def export\n  end\nend\n----\n\n== Controllers: Rendering [[rendering]]\n\n=== Inline Rendering [[inline-rendering]]\n\nPrefer using a template over inline rendering.\n\n[source,ruby]\n----\n# very bad\nclass ProductsController \u003c ApplicationController\n  def index\n    render inline: \"\u003c% products.each do |p| %\u003e\u003cp\u003e\u003c%= p.name %\u003e\u003c/p\u003e\u003c% end %\u003e\", type: :erb\n  end\nend\n\n# good\n## app/views/products/index.html.erb\n\u003c%= render partial: 'product', collection: products %\u003e\n\n## app/views/products/_product.html.erb\n\u003cp\u003e\u003c%= product.name %\u003e\u003c/p\u003e\n\u003cp\u003e\u003c%= product.price %\u003e\u003c/p\u003e\n\n## app/controllers/products_controller.rb\nclass ProductsController \u003c ApplicationController\n  def index\n    render :index\n  end\nend\n----\n\n=== Plain Text Rendering [[plain-text-rendering]]\n\nPrefer `render plain:` over `render text:`.\n\n[source,ruby]\n----\n# bad - sets MIME type to `text/html`\n...\nrender text: 'Ruby!'\n...\n\n# bad - requires explicit MIME type declaration\n...\nrender text: 'Ruby!', content_type: 'text/plain'\n...\n\n# good - short and precise\n...\nrender plain: 'Ruby!'\n...\n----\n\n=== HTTP Status Code Symbols [[http-status-code-symbols]]\n\nPrefer https://gist.github.com/mlanett/a31c340b132ddefa9cca[corresponding symbols] to numeric HTTP status codes.\nThey are meaningful and do not look like \"magic\" numbers for less known HTTP status codes.\n\n[source,ruby]\n----\n# bad\n...\nrender status: 403\n...\n\n# good\n...\nrender status: :forbidden\n...\n----\n\n== Models\n\n=== Model Classes [[model-classes]]\n\nIntroduce non-Active Record model classes freely.\n\n=== Meaningful Model Names [[meaningful-model-names]]\n\nName the models with meaningful (but short) names without abbreviations.\n\n=== Non-ActiveRecord Models [[non-activerecord-models]]\n\nIf you need objects that support ActiveRecord-like behavior (like validations) without the database functionality, use `ActiveModel::Model`.\n\n[source,ruby]\n----\nclass Message\n  include ActiveModel::Model\n\n  attr_accessor :name, :email, :content, :priority\n\n  validates :name, presence: true\n  validates :email, format: { with: /\\A[-a-z0-9_+\\.]+\\@([-a-z0-9]+\\.)+[a-z0-9]{2,4}\\z/i }\n  validates :content, length: { maximum: 500 }\nend\n----\n\nStarting with Rails 6.1, you can also extend the attributes API from ActiveRecord using `ActiveModel::Attributes`.\n\n[source,ruby]\n----\nclass Message\n  include ActiveModel::Model\n  include ActiveModel::Attributes\n\n  attribute :name, :string\n  attribute :email, :string\n  attribute :content, :string\n  attribute :priority, :integer\n\n  validates :name, presence: true\n  validates :email, format: { with: /\\A[-a-z0-9_+\\.]+\\@([-a-z0-9]+\\.)+[a-z0-9]{2,4}\\z/i }\n  validates :content, length: { maximum: 500 }\nend\n----\n\n=== Model Business Logic [[model-business-logic]]\n\nUnless they have some meaning in the business domain, don't put methods in your model that just format your data (like code generating HTML).\nThese methods are most likely going to be called from the view layer only, so their place is in helpers.\nKeep your models for business logic and data-persistence only.\n\n== Models: Active Record [[activerecord]]\n\n=== Keep Active Record Defaults [[keep-ar-defaults]]\n\nAvoid altering Active Record defaults (table names, primary key, etc) unless you have a very good reason (like a database that's not under your control).\n\n[source,ruby]\n----\n# bad - don't do this if you can modify the schema\nclass Transaction \u003c ApplicationRecord\n  self.table_name = 'order'\n  ...\nend\n----\n\n=== Always append to `ignored_columns` [[append-ignored-columns]]\n\nAvoid setting `ignored_columns`. It may overwrite previous assignments and that is almost always a mistake. Prefer appending to the list instead.\n\n[source,ruby]\n----\nclass Transaction \u003c ApplicationRecord\n  # bad - it may overwrite previous assignments\n  self.ignored_columns = %i[legacy]\n\n  # good - the value is appended to the list\n  self.ignored_columns += %i[legacy]\n  ...\nend\n----\n\n=== Enums [[enums]]\n\nPrefer using the hash syntax for `enum`. Array makes the database values implicit\n\u0026 any insertion/removal/rearrangement of values in the middle will most probably\nlead to broken code.\n\n[source,ruby]\n----\nclass Transaction \u003c ApplicationRecord\n  # bad - implicit values - ordering matters\n  enum :type, %i[credit debit]\n\n  # good - explicit values - ordering does not matter\n  enum :type, {\n    credit: 0,\n    debit: 1\n  }\nend\n----\n\n=== Macro Style Methods [[macro-style-methods]]\n\nGroup macro-style methods (`has_many`, `validates`, etc) in the beginning of the class definition.\n\n[source,ruby]\n----\nclass User \u003c ApplicationRecord\n  # keep the default scope first (if any)\n  default_scope { where(active: true) }\n\n  # constants come up next\n  COLORS = %w(red green blue)\n\n  # afterwards we put attr related macros\n  attr_accessor :formatted_date_of_birth\n\n  attr_accessible :login, :first_name, :last_name, :email, :password\n\n  # Rails 4+ enums after attr macros\n  enum role: { user: 0, moderator: 1, admin: 2 }\n\n  # followed by association macros\n  belongs_to :country\n\n  has_many :authentications, dependent: :destroy\n\n  # and validation macros\n  validates :email, presence: true\n  validates :username, presence: true\n  validates :username, uniqueness: { case_sensitive: false }\n  validates :username, format: { with: /\\A[A-Za-z][A-Za-z0-9._-]{2,19}\\z/ }\n  validates :password, format: { with: /\\A\\S{8,128}\\z/, allow_nil: true }\n\n  # next we have callbacks\n  before_save :cook\n  before_save :update_username_lower\n\n  # other macros (like devise's) should be placed after the callbacks\n\n  ...\nend\n----\n\n=== `has_many :through` [[has-many-through]]\n\nPrefer `has_many :through` to `has_and_belongs_to_many`.\nUsing `has_many :through` allows additional attributes and validations on the join model.\n\n[source,ruby]\n----\n# not so good - using has_and_belongs_to_many\nclass User \u003c ApplicationRecord\n  has_and_belongs_to_many :groups\nend\n\nclass Group \u003c ApplicationRecord\n  has_and_belongs_to_many :users\nend\n\n# preferred way - using has_many :through\nclass User \u003c ApplicationRecord\n  has_many :memberships\n  has_many :groups, through: :memberships\nend\n\nclass Membership \u003c ApplicationRecord\n  belongs_to :user\n  belongs_to :group\nend\n\nclass Group \u003c ApplicationRecord\n  has_many :memberships\n  has_many :users, through: :memberships\nend\n----\n\n=== Read Attribute [[read-attribute]]\n\nPrefer `self[:attribute]` over `read_attribute(:attribute)`.\n\n[source,ruby]\n----\n# bad\ndef amount\n  read_attribute(:amount) * 100\nend\n\n# good\ndef amount\n  self[:amount] * 100\nend\n----\n\n=== Write Attribute [[write-attribute]]\n\nPrefer `self[:attribute] = value` over `write_attribute(:attribute, value)`.\n\n[source,ruby]\n----\n# bad\ndef amount\n  write_attribute(:amount, 100)\nend\n\n# good\ndef amount\n  self[:amount] = 100\nend\n----\n\n=== New-style Validations [[new-style-validations]]\n\nAlways use the http://thelucid.com/2010/01/08/sexy-validation-in-edge-rails-rails-3/[\"new-style\" validations].\n\n[source,ruby]\n----\n# bad\nvalidates_presence_of :email\nvalidates_length_of :email, maximum: 100\n\n# good\nvalidates :email, presence: true, length: { maximum: 100 }\n----\n\n=== Custom Validation Methods\n\nWhen naming custom validation methods, adhere to the simple rules:\n\n - `validate :method_name` reads like a natural statement\n - the method name explains what it checks\n - the method is recognizable as a validation method by its name, not a predicate method\n\n[source,ruby]\n----\n# good\nvalidate :expiration_date_cannot_be_in_the_past\nvalidate :discount_cannot_be_greater_than_total_value\nvalidate :ensure_same_topic_is_chosen\n\n# also good - explicit prefix\nvalidate :validate_birthday_in_past\nvalidate :validate_sufficient_quantity\nvalidate :must_have_owner_with_no_other_items\nvalidate :must_have_shipping_units\n\n# bad\nvalidate :birthday_in_past\nvalidate :owner_has_no_other_items\n----\n\n=== Single-attribute Validations [[single-attribute-validations]]\n\nTo make validations easy to read, don't list multiple attributes per validation.\n\n[source,ruby]\n----\n# bad\nvalidates :email, :password, presence: true\nvalidates :email, length: { maximum: 100 }\n\n# good\nvalidates :email, presence: true, length: { maximum: 100 }\nvalidates :password, presence: true\n----\n\n=== Custom Validator File [[custom-validator-file]]\n\nWhen a custom validation is used more than once or the validation is some regular expression mapping, create a custom validator file.\n\n[source,ruby]\n----\n# bad\nclass Person\n  validates :email, format: { with: /\\A([^@\\s]+)@((?:[-a-z0-9]+\\.)+[a-z]{2,})\\z/i }\nend\n\n# good\nclass EmailValidator \u003c ActiveModel::EachValidator\n  def validate_each(record, attribute, value)\n    record.errors[attribute] \u003c\u003c (options[:message] || 'is not a valid email') unless value =~ /\\A([^@\\s]+)@((?:[-a-z0-9]+\\.)+[a-z]{2,})\\z/i\n  end\nend\n\nclass Person\n  validates :email, email: true\nend\n----\n\n=== App Validators [[app-validators]]\n\nKeep custom validators under `app/validators`.\n\n=== Custom Validators Gem [[custom-validators-gem]]\n\nConsider extracting custom validators to a shared gem if you're maintaining several related apps or the validators are generic enough.\n\n=== Named Scopes [[named-scopes]]\n\nUse named scopes freely.\n\n[source,ruby]\n----\nclass User \u003c ApplicationRecord\n  scope :active, -\u003e { where(active: true) }\n  scope :inactive, -\u003e { where(active: false) }\n\n  scope :with_orders, -\u003e { joins(:orders).select('distinct(users.id)') }\nend\n----\n\n=== Named Scope Class [[named-scope-class]]\n\nWhen a named scope defined with a lambda and parameters becomes too complicated, it is preferable to make a class method instead which serves the same purpose of the named scope and returns an `ActiveRecord::Relation` object.\nArguably you can define even simpler scopes like this.\n\n[source,ruby]\n----\nclass User \u003c ApplicationRecord\n  def self.with_orders\n    joins(:orders).select('distinct(users.id)')\n  end\nend\n----\n\n=== Callbacks Order [[callbacks-order]]\n\nOrder callback declarations in the order in which they will be executed.\nFor reference, see https://guides.rubyonrails.org/active_record_callbacks.html#available-callbacks[Available Callbacks].\n\n[source,ruby]\n----\n# bad\nclass Person\n  after_commit :after_commit_callback\n  before_validation :before_validation_callback\nend\n\n# good\nclass Person\n  before_validation :before_validation_callback\n  after_commit :after_commit_callback\nend\n----\n\n=== Beware Skip Model Validations [[beware-skip-model-validations]]\n\nBeware of the behavior of the https://guides.rubyonrails.org/active_record_validations.html#skipping-validations[following] methods.\nThey do not run the model validations and could easily corrupt the model state.\n\n[source,ruby]\n----\n# bad\nArticle.first.decrement!(:view_count)\nDiscussionBoard.decrement_counter(:post_count, 5)\nArticle.first.increment!(:view_count)\nDiscussionBoard.increment_counter(:post_count, 5)\nperson.toggle :active\nproduct.touch\nBilling.update_all(\"category = 'authorized', author = 'David'\")\nuser.update_attribute(:website, 'example.com')\nuser.update_columns(last_request_at: Time.current)\nPost.update_counters 5, comment_count: -1, action_count: 1\n\n# good\nuser.update_attributes(website: 'example.com')\n----\n\n=== User-friendly URLs [[user-friendly-urls]]\n\nUse user-friendly URLs.\nShow some descriptive attribute of the model in the URL rather than its `id`.\nThere is more than one way to achieve this.\n\n==== Override the `to_param` Method of the Model\n\nThis method is used by Rails for constructing a URL to the object.\nThe default implementation returns the `id` of the record as a String.\nIt could be overridden to include another human-readable attribute.\n\n[source,ruby]\n----\nclass Person\n  def to_param\n    \"#{id} #{name}\".parameterize\n  end\nend\n----\n\nIn order to convert this to a URL-friendly value, `parameterize` should be called on the string.\nThe `id` of the object needs to be at the beginning so that it can be found by the `find` method of Active Record.\n\n==== `friendly_id` Gem\n\nIt allows creation of human-readable URLs by using some descriptive attribute of the model instead of its `id`.\n\n[source,ruby]\n----\nclass Person\n  extend FriendlyId\n  friendly_id :name, use: :slugged\nend\n----\n\nCheck the https://github.com/norman/friendly_id[gem documentation] for more information about its usage.\n\n=== `find_each` [[find-each]]\n\nUse `find_each` to iterate over a collection of AR objects.\nLooping through a collection of records from the database (using the `all` method, for example) is very inefficient since it will try to instantiate all the objects at once.\nIn that case, batch processing methods allow you to work with the records in batches, thereby greatly reducing memory consumption.\n\n[source,ruby]\n----\n# bad\nPerson.all.each do |person|\n  person.do_awesome_stuff\nend\n\nPerson.where('age \u003e 21').each do |person|\n  person.party_all_night!\nend\n\n# good\nPerson.find_each do |person|\n  person.do_awesome_stuff\nend\n\nPerson.where('age \u003e 21').find_each do |person|\n  person.party_all_night!\nend\n----\n\n=== `before_destroy` [[before_destroy]]\n\nSince https://github.com/rails/rails/issues/3458[Rails creates callbacks for dependent associations], always call `before_destroy` callbacks that perform validation with `prepend: true`.\n\n[source,ruby]\n----\n# bad (roles will be deleted automatically even if super_admin? is true)\nhas_many :roles, dependent: :destroy\n\nbefore_destroy :ensure_deletable\n\ndef ensure_deletable\n  raise \"Cannot delete super admin.\" if super_admin?\nend\n\n# good\nhas_many :roles, dependent: :destroy\n\nbefore_destroy :ensure_deletable, prepend: true\n\ndef ensure_deletable\n  raise \"Cannot delete super admin.\" if super_admin?\nend\n----\n\n=== `has_many`/`has_one` Dependent Option [[has_many-has_one-dependent-option]]\n\nDefine the `dependent` option to the `has_many` and `has_one` associations.\n\n[source,ruby]\n----\n# bad\nclass Post \u003c ApplicationRecord\n  has_many :comments\nend\n\n# good\nclass Post \u003c ApplicationRecord\n  has_many :comments, dependent: :destroy\nend\n----\n\n=== `save!` [[save-bang]]\n\nWhen persisting AR objects always use the exception raising bang! method or handle the method return value.\nThis applies to `create`, `save`, `update`, `destroy`, `first_or_create` and `find_or_create_by`.\n\n[source,ruby]\n----\n# bad\nuser.create(name: 'Bruce')\n\n# bad\nuser.save\n\n# good\nuser.create!(name: 'Bruce')\n# or\nbruce = user.create(name: 'Bruce')\nif bruce.persisted?\n  ...\nelse\n  ...\nend\n\n# good\nuser.save!\n# or\nif user.save\n  ...\nelse\n  ...\nend\n----\n\n== Models: Active Record Queries [[activerecord-queries]]\n\n=== Avoid Interpolation [[avoid-interpolation]]\n\nAvoid string interpolation in queries, as it will make your code susceptible to SQL injection attacks.\n\n[source,ruby]\n----\n# bad - param will be interpolated unescaped\nClient.where(\"orders_count = #{params[:orders]}\")\n\n# good - param will be properly escaped\nClient.where('orders_count = ?', params[:orders])\n----\n\n=== Named Placeholder [[named-placeholder]]\n\nConsider using named placeholders instead of positional placeholders when you have more than 1 placeholder in your query.\n\n[source,ruby]\n----\n# okish\nClient.where(\n  'orders_count \u003e= ? AND country_code = ?',\n  params[:min_orders_count], params[:country_code]\n)\n\n# good\nClient.where(\n  'orders_count \u003e= :min_orders_count AND country_code = :country_code',\n  min_orders_count: params[:min_orders_count], country_code: params[:country_code]\n)\n----\n\n=== `find` [[find]]\n\nPrefer `find` over `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.\n\n[source,ruby]\n----\n# bad\nUser.where(id: id).take!\n\n# bad\nUser.find_by_id!(id)\n\n# bad\nUser.find_by!(id: id)\n\n# good\nUser.find(id)\n----\n\n=== `find_by` [[find_by]]\n\nPrefer `find_by` over `where.take` and `find_by_attribute` when you need to retrieve a single record by one or more attributes and return `nil` when the record is not found.\n\n[source,ruby]\n----\n# bad\nUser.where(email: email).take\nUser.where(first_name: 'Bruce', last_name: 'Wayne').take\n\n# bad\nUser.find_by_email(email)\nUser.find_by_first_name_and_last_name('Bruce', 'Wayne')\n\n# good\nUser.find_by(email: email)\nUser.find_by(first_name: 'Bruce', last_name: 'Wayne')\n----\n\n=== Hash conditions [[where-not]] [[hash-conditions]]\n\nPrefer passing conditions to `where` and `where.not` as a hash over using fragments of SQL.\n\n[source,ruby]\n----\n# bad\nUser.where(\"name = ?\", name)\n\n# good\nUser.where(name: name)\n\n# bad\nUser.where(\"id != ?\", id)\n\n# good\nUser.where.not(id: id)\n----\n\n=== Finding missing relationship records [[finding-missing-relationship-records]]\n\nIf you're using Rails 6.1 or higher, use https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods/WhereChain.html#method-i-missing[where.missing] to find missing relationship records.\n\n[source,ruby]\n----\n# bad\nPost.left_joins(:author).where(authors: { id: nil })\n\n# good\nPost.where.missing(:author)\n----\n\n=== Order by `id` [[order-by-id]]\n\nDon't use the `id` column for ordering.\nThe sequence of ids is not guaranteed to be in any particular order, despite often (incidentally) being chronological.\nUse a timestamp column to order chronologically.\nAs a bonus the intent is clearer.\n\n[source,ruby]\n----\n# bad\nscope :chronological, -\u003e { order(id: :asc) }\n\n# good\nscope :chronological, -\u003e { order(created_at: :asc) }\n----\n\n=== Order arguments [[order-arguments]]\n\nPrefer symbol arguments over strings for ordering.\n\nString columns without a table name can cause an \"ambiguous column name\" error when ordering by a column that exists in multiple tables joined in the same query.\n\n[source,ruby]\n----\n# bad\nUser.order('created_at DESC')\n\n# good\nUser.order(created_at: :desc)\n----\n\n=== `pluck`\n\nUse https://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-pluck[pluck] to select a single value from multiple records.\n\n[source,ruby]\n----\n# bad\nUser.all.map(\u0026:name)\n\n# bad\nUser.all.map { |user| user[:name] }\n\n# good\nUser.pluck(:name)\n----\n\n=== `pick`\n\nUse https://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-pick[pick] to select a single value from a single record.\n\n[source,ruby]\n----\n# bad\nUser.pluck(:name).first\n\n# bad\nUser.first.name\n\n# good\nUser.pick(:name)\n----\n\n=== `ids` [[ids]]\n\nPrefer `ids` over `pluck(:id)`.\n\n[source,ruby]\n----\n# bad\nUser.pluck(:id)\n\n# good\nUser.ids\n----\n\n=== Squished Heredocs [[squished-heredocs]]\n\nWhen specifying an explicit query in a method such as `find_by_sql`, use heredocs with `squish`.\nThis allows you to legibly format the SQL with line breaks and indentations, while supporting syntax highlighting in many tools (including GitHub, Atom, and RubyMine).\n\n[source,ruby]\n----\nUser.find_by_sql(\u003c\u003c-SQL.squish)\n  SELECT\n    users.id, accounts.plan\n  FROM\n    users\n  INNER JOIN\n    accounts\n  ON\n    accounts.user_id = users.id\n  # further complexities...\nSQL\n----\n\nhttps://api.rubyonrails.org/classes/String.html#method-i-squish[`String#squish`] removes the indentation and newline characters so that your server log shows a fluid string of SQL rather than something like this:\n\n----\nSELECT\\n    users.id, accounts.plan\\n  FROM\\n    users\\n  INNER JOIN\\n    accounts\\n  ON\\n    accounts.user_id = users.id\n----\n\n=== `size` over `count` or `length` [[size-over-count-or-length]]\n\nWhen querying Active Record collections, prefer `size` (selects between count/length behavior based on whether collection is already loaded) or `length` (always loads the whole collection and counts the array elements) over `count` (always does a database query for the count).\n\n[source,ruby]\n----\n# bad\nUser.count\n\n# good\nUser.all.size\n\n# good - if you really need to load all users into memory\nUser.all.length\n----\n\n=== Where with Ranges [[where-ranges]]\n\nUse ranges instead of defining comparative conditions using a template for scalar values.\n\n[source,ruby]\n----\n# bad\nUser.where(\"created_at \u003e= ?\", 30.days.ago).where(\"created_at \u003c= ?\", 7.days.ago)\nUser.where(\"created_at \u003e= ? AND created_at \u003c= ?\", 30.days.ago, 7.days.ago)\nUser.where(\"created_at \u003e= :start AND created_at \u003c= :end\", start: 30.days.ago, end: 7.days.ago)\n\n# good\nUser.where(created_at: 30.days.ago..7.days.ago)\n\n# bad\nUser.where(\"created_at \u003e= ?\", 7.days.ago)\n\n# good\nUser.where(created_at: 7.days.ago..)\n\n# note - ranges are inclusive or exclusive of their ending, not beginning\nUser.where(created_at: 7.days.ago..)  # produces \u003e=\nUser.where(created_at: 7.days.ago...) # also produces \u003e=\nUser.where(created_at: ..7.days.ago)  # inclusive: produces \u003c=\nUser.where(created_at: ...7.days.ago) # exclusive: produces \u003c\n\n# okish - there is no range syntax that would denote exclusion at the beginning of the range\nCustomer.where(\"purchases_count \u003e :min AND purchases_count \u003c= :max\", min: 0, max: 5)\n----\n\nNOTE: Rails 6.0 or later is required for endless range Ruby 2.6 syntax, and Rails 6.0.3 for beginless range Ruby 2.7 syntax.\n\n=== `where.not` with multiple attributes\n\nAvoid passing multiple attributes to `where.not`. Rails logic in this case has changed in Rails 6.1 and\nwill now yield results matching either of those conditions,\ne.g. `where.not(status: 'active', plan: 'basic')` would return records with active status when the plan is business.\n\n[source, ruby]\n----\n# bad\nUser.where.not(status: 'active', plan: 'basic')\n\n# good\nUser.where.not('status = ? AND plan = ?', 'active', 'basic')\n----\n\n=== Redundant `all` [[redundant-all]]\n\nUsing `all` as a receiver is redundant. The result won't change without `all`, so it should be removed.\n\n[source, ruby]\n----\n# bad\nUser.all.find(id)\nUser.all.order(:created_at)\nusers.all.where(id: ids)\nuser.articles.all.order(:created_at)\n\n# good\nUser.find(id)\nUser.order(:created_at)\nusers.where(id: ids)\nuser.articles.order(:created_at)\n----\n\nNOTE: When the receiver for `all` is an association, there are methods whose behavior changes by omitting `all`.\n\nThe following methods behave differently without `all`:\n\n* `delete` - https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-delete[with all] / https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-delete[without all]\n* `delete_all` - https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-delete_all[with all] / https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-delete_all[without all]\n* `destroy` - https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-destroy[with all] / https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-destroy[without all]\n* `destroy_all` - https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-destroy_all[with all] / https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-destroy_all[without all]\n\nSo, when considering removing `all` from the receiver of these methods, it is recommended to refer to the documentation to understand how the behavior changes.\n\n=== `find_by` memoization [[find-by-memoization]]\n\nAvoid memoizing `find_by` results with `||=`.\n`find_by` may return `nil`, in which case it will not be memoized as intended.\n\n[source,ruby]\n----\n# bad\ndef current_user\n  @current_user ||= User.find_by(id: session[:user_id])\nend\n\n# good\ndef current_user\n  return @current_user if defined?(@current_user)\n\n  @current_user = User.find_by(id: session[:user_id])\nend\n----\n\n== Migrations\n\n=== Schema Version [[schema-version]]\n\nKeep the `schema.rb` (or `structure.sql`) under version control.\n\n=== DB Schema Load [[db-schema-load]]\n\nUse `rake db:schema:load` instead of `rake db:migrate` to initialize an empty database.\n\n=== Default Migration Values [[default-migration-values]]\n\nEnforce default values in the migrations themselves instead of in the application layer.\n\n[source,ruby]\n----\n# bad - application enforced default value\nclass Product \u003c ApplicationRecord\n  def amount\n    self[:amount] || 0\n  end\nend\n\n# good - database enforced\nclass AddDefaultAmountToProducts \u003c ActiveRecord::Migration\n  def change\n    change_column_default :products, :amount, 0\n  end\nend\n----\n\nWhile enforcing table defaults only in Rails is suggested by many Rails developers, it's an extremely brittle approach that leaves your data vulnerable to many application bugs.\nAnd you'll have to consider the fact that most non-trivial apps share a database with other applications, so imposing data integrity from the Rails app is impossible.\n\n=== 3-state Boolean [[three-state-boolean]]\n\nWith SQL databases, if a boolean column is not given a default value, it will have three possible values: `true`, `false` and `NULL`.\nBoolean operators https://en.wikipedia.org/wiki/Three-valued_logic[work in unexpected ways] with `NULL`.\n\nFor example in SQL queries, `true AND NULL` is `NULL` (not false), `true AND NULL OR false` is `NULL` (not false). This can make SQL queries return unexpected results.\n\nTo avoid such situations, boolean columns should always have a default value and a `NOT NULL` constraint.\n\n[source,ruby]\n----\n# bad - boolean without a default value\nadd_column :users, :active, :boolean\n\n# good - boolean with a default value (`false` or `true`) and with restricted `NULL`\nadd_column :users, :active, :boolean, default: true, null: false\nadd_column :users, :admin, :boolean, default: false, null: false\n----\n\n=== Foreign Key Constraints [[foreign-key-constraints]]\n\nEnforce foreign-key constraints. As of Rails 4.2, Active Record supports foreign key constraints natively.\n\n[source,ruby]\n----\n# bad - does not add foreign keys\ncreate_table :comment do |t|\n  t.references :article\n  t.belongs_to :user\n  t.integer :category_id\nend\n\n# good\ncreate_table :comment do |t|\n  t.references :article, foreign_key: true\n  t.belongs_to :user, foreign_key: true\n  t.references :category, foreign_key: { to_table: :comment_categories }\nend\n----\n\n=== Change vs Up/Down [[change-vs-up-down]]\n\nWhen writing constructive migrations (adding tables or columns), use the `change` method instead of `up` and `down` methods.\n\n[source,ruby]\n----\n# the old way\nclass AddNameToPeople \u003c ActiveRecord::Migration\n  def up\n    add_column :people, :name, :string\n  end\n\n  def down\n    remove_column :people, :name\n  end\nend\n\n# the new preferred way\nclass AddNameToPeople \u003c ActiveRecord::Migration\n  def change\n    add_column :people, :name, :string\n  end\nend\n----\n\n=== Define Model Class Migrations [[define-model-class-migrations]]\n\nIf you have to use models in migrations, make sure you define them so that you don't end up with broken migrations in the future.\n\n[source,ruby]\n----\n# db/migrate/\u003cmigration_file_name\u003e.rb\n# frozen_string_literal: true\n\n# bad\nclass ModifyDefaultStatusForProducts \u003c ActiveRecord::Migration\n  def change\n    old_status = 'pending_manual_approval'\n    new_status = 'pending_approval'\n\n    reversible do |dir|\n      dir.up do\n        Product.where(status: old_status).update_all(status: new_status)\n        change_column :products, :status, :string, default: new_status\n      end\n\n      dir.down do\n        Product.where(status: new_status).update_all(status: old_status)\n        change_column :products, :status, :string, default: old_status\n      end\n    end\n  end\nend\n\n# good\n# Define `table_name` in a custom named class to make sure that you run on the\n# same table you had during the creation of the migration.\n# In future if you override the `Product` class and change the `table_name`,\n# it won't break the migration or cause serious data corruption.\nclass MigrationProduct \u003c ActiveRecord::Base\n  self.table_name = :products\nend\n\nclass ModifyDefaultStatusForProducts \u003c ActiveRecord::Migration\n  def change\n    old_status = 'pending_manual_approval'\n    new_status = 'pending_approval'\n\n    reversible do |dir|\n      dir.up do\n        MigrationProduct.where(status: old_status).update_all(status: new_status)\n        change_column :products, :status, :string, default: new_status\n      end\n\n      dir.down do\n        MigrationProduct.where(status: new_status).update_all(status: old_status)\n        change_column :products, :status, :string, default: old_status\n      end\n    end\n  end\nend\n----\n\n=== Meaningful Foreign Key Naming [[meaningful-foreign-key-naming]]\n\nName your foreign keys explicitly instead of relying on Rails auto-generated FK names. (https://guides.rubyonrails.org/active_record_migrations.html#foreign-keys)\n\n[source,ruby]\n----\n# bad\nclass AddFkArticlesToAuthors \u003c ActiveRecord::Migration\n  def change\n    add_foreign_key :articles, :authors\n  end\nend\n\n# good\nclass AddFkArticlesToAuthors \u003c ActiveRecord::Migration\n  def change\n    add_foreign_key :articles, :authors, name: :articles_author_id_fk\n  end\nend\n----\n\n=== Reversible Migration [[reversible-migration]]\n\nDon't use non-reversible migration commands in the `change` method.\nReversible migration commands are listed below.\nhttps://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html[ActiveRecord::Migration::CommandRecorder]\n\n[source,ruby]\n----\n# bad\nclass DropUsers \u003c ActiveRecord::Migration\n  def change\n    drop_table :users\n  end\nend\n\n# good\nclass DropUsers \u003c ActiveRecord::Migration\n  def up\n    drop_table :users\n  end\n\n  def down\n    create_table :users do |t|\n      t.string :name\n    end\n  end\nend\n\n# good\n# In this case, block will be used by create_table in rollback\n# https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-i-drop_table\nclass DropUsers \u003c ActiveRecord::Migration\n  def change\n    drop_table :users do |t|\n      t.string :name\n    end\n  end\nend\n----\n\n== Views\n\n=== No Direct Model View [[no-direct-model-view]]\n\nNever call the model layer directly from a view.\n\n=== No Complex View Formatting [[no-complex-view-formatting]]\n\nAvoid complex formatting in the views.\nA view helper is useful for simple cases, but if it's more complex then consider using a decorator or presenter.\n\n=== Partials [[partials]]\n\nMitigate code duplication by using partial templates and layouts.\n\n=== No Instance Variables in Partials [[no-instance-variables-in-partials]]\n\nAvoid using instance variables in partials, pass a local variable to `render` instead.\nThe partial may be used in a different controller or action, where the variable can have a different name or even be absent.\nIn these cases, an undefined instance variable will not raise an exception whereas a local variable will.\n\n[source,erb]\n----\n\u003c!-- bad --\u003e\n\u003c!-- app/views/courses/show.html.erb --\u003e\n\u003c%= render 'course_description' %\u003e\n\u003c!-- app/views/courses/_course_description.html.erb --\u003e\n\u003c%= @course.description %\u003e\n\n\u003c!-- good --\u003e\n\u003c!-- app/views/courses/show.html.erb --\u003e\n\u003c%= render 'course_description', course: @course %\u003e\n\u003c!-- app/views/courses/_course_description.html.erb --\u003e\n\u003c%= course.description %\u003e\n----\n\n== Internationalization\n\n=== Locale Texts [[locale-texts]]\n\nNo strings or other locale specific settings should be used in the views, models and controllers.\nThese texts should be moved to the locale files in the `config/locales` directory.\n\n=== Translated Labels [[translated-labels]]\n\nWhen the labels of an Active Record model need to be translated, use the `activerecord` scope:\n\n----\nen:\n  activerecord:\n    models:\n      user: Member\n    attributes:\n      user:\n        name: 'Full name'\n----\n\nThen `User.model_name.human` will return \"Member\" and `User.human_attribute_name(\"name\")` will return \"Full name\".\nThese translations of the attributes will be used as labels in the views.\n\n=== Organize Locale Files [[organize-locale-files]]\n\nSeparate the texts used in the views from translations of Active Record attributes.\nPlace the locale files for the models in a folder `locales/models` and the texts used in the views in folder `locales/views`.\n\nWhen organization of the locale files is done with additional directories, these directories must be described in the `application.rb` file in order to be loaded.\n\n[source,ruby]\n----\n# config/application.rb\nconfig.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]\n----\n\n=== Shared Localization [[shared-localization]]\n\nPlace the shared localization options, such as date or currency formats, in files under the root of the `locales` directory.\n\n=== Short I18n [[short-i18n]]\n\nUse the short form of the I18n methods: `I18n.t` instead of `I18n.translate` and `I18n.l` instead of `I18n.localize`.\n\n=== Lazy Lookup [[lazy-lookup]]\n\nUse \"lazy\" lookup for locale entries from views and controllers. Let's say we have the following structure:\n\n----\nen:\n  users:\n    show:\n      title: 'User details page'\n----\n\nThe value for `users.show.title` can be looked up in the template `app/views/users/show.html.haml` like this:\n\n[source,ruby]\n----\n# bad\n= t 'users.show.title'\n\n# good\n= t '.title'\n----\n\n=== Dot-separated Keys [[dot-separated-keys]]\n\nUse dot-separated locale keys instead of specifying the `:scope` option with an array or a single symbol.\nDot-separated notation is easier to read and trace the hierarchy.\n\n[source,ruby]\n----\n# bad\nI18n.t :record_invalid, scope: [:activerecord, :errors, :messages]\n\n# good\nI18n.t :record_invalid, scope: 'activerecord.errors.messages'\nI18n.t 'activerecord.errors.messages.record_invalid'\n\n# bad\nI18n.t :title, scope: :invitation\n\n# good\nI18n.t 'title.invitation'\n----\n\n=== I18n Guides [[i18n-guides]]\n\nMore detailed information about the Rails I18n can be found in the https://guides.rubyonrails.org/i18n.html[Rails Guides]\n\n\n== Assets\n\nUse the https://guides.rubyonrails.org/asset_pipeline.html[asset pipeline] to leverage organization within your application.\n\n=== Reserve `app/assets` [[reserve-app-assets]]\n\nReserve `app/assets` for custom stylesheets, javascripts, or images.\n\n=== `lib/assets` [[lib-assets]]\n\nUse `lib/assets` for your own libraries that don't really fit into the scope of the application.\n\n=== `vendor/assets` [[vendor-assets]]\n\nThird party code such as https://jquery.com/[jQuery] or https://twitter.github.com/bootstrap/[bootstrap] should be placed in `vendor/assets`.\n\n=== `gem/assets` [[gem-assets]]\n\nWhen possible, use gemified versions of assets (e.g. https://github.com/rails/jquery-rails[jquery-rails], https://github.com/joliss/jquery-ui-rails[jquery-ui-rails], https://github.com/thomas-mcdonald/bootstrap-sass[bootstrap-sass], https://github.com/zurb/foundation[zurb-foundation]).\n\n== Mailers\n\n=== Mailer Name [[mailer-name]]\n\nName the mailers `SomethingMailer`.\nWithout the Mailer suffix it isn't immediately apparent what's a mailer and which views are related to the mailer.\n\n=== HTML Plain Email [[html-plain-email]]\n\nProvide both HTML and plain-text view templates.\n\n=== Enable Delivery Errors [[enable-delivery-errors]]\n\nEnable errors raised on failed mail delivery in your development environment.\nThe errors are disabled by default.\n\n[source,ruby]\n----\n# config/environments/development.rb\n\nconfig.action_mailer.raise_delivery_errors = true\n----\n\n=== Local SMTP [[local-smtp]]\n\nUse a local SMTP server like https://github.com/sj26/mailcatcher[Mailcatcher] in development environment.\n\n[source,ruby]\n----\n# config/environments/development.rb\n\nconfig.action_mailer.smtp_settings = {\n  address: 'localhost',\n  port: 1025,\n  # more settings\n}\n----\n\n=== Default Hostname [[default-hostname]]\n\nProvide default settings for the host name.\n\n[source,ruby]\n----\n# config/environments/development.rb\nconfig.action_mailer.default_url_options = { host: \"#{local_ip}:3000\" }\n\n# config/environments/production.rb\nconfig.action_mailer.default_url_options = { host: 'your_site.com' }\n\n# in your mailer class\ndefault_url_options[:host] = 'your_site.com'\n----\n\n=== Email Addresses [[email-addresses]]\n\nFormat the from and to addresses properly.\nUse the following format:\n\n[source,ruby]\n----\n# in your mailer class\ndefault from: 'Your Name \u003cinfo@your_site.com\u003e'\n----\n\nIf you're using Rails 6.1 or higher, you can use the `email_address_with_name` method:\n\n[source,ruby]\n----\n# in your mailer class\ndefault from: email_address_with_name('info@your_site.com', 'Your Name')\n----\n\n=== Delivery Method Test [[delivery-method-test]]\n\nMake sure that the e-mail delivery method for your test environment is set to `test`:\n\n[source,ruby]\n----\n# config/environments/test.rb\n\nconfig.action_mailer.delivery_method = :test\n----\n\n=== Delivery Method SMTP [[delivery-method-smtp]]\n\nThe delivery method for development and production should be `smtp`:\n\n[source,ruby]\n----\n# config/environments/development.rb, config/environments/production.rb\n\nconfig.action_mailer.delivery_method = :smtp\n----\n\n=== Inline Email Styles [[inline-email-styles]]\n\nWhen sending html emails all styles should be inline, as some mail clients have problems with external styles.\nThis however makes them harder to maintain and leads to code duplication.\nThere are two similar gems that transform the styles and put them in the corresponding html tags: https://github.com/fphilipe/premailer-rails[premailer-rails] and https://github.com/Mange/roadie[roadie].\n\n=== Background Email [[background-email]]\n\nSending emails while generating page response should be avoided.\nIt causes delays in loading of the page and request can timeout if multiple email are sent.\nTo overcome this emails can be sent in background process with the help of https://github.com/mperham/sidekiq[sidekiq] gem.\n\n== Active Support Core Extensions\n\n=== `try!` [[try-bang]]\n\nPrefer Ruby 2.3's safe navigation operator `\u0026.` over `ActiveSupport#try!`.\n\n[source,ruby]\n----\n# bad\nobj.try! :fly\n\n# good\nobj\u0026.fly\n----\n\n=== Active Support Aliases [[active_support_aliases]]\n\nPrefer Ruby's Standard Library methods over `ActiveSupport` aliases.\n\n[source,ruby]\n----\n# bad\n'the day'.starts_with? 'th'\n'the day'.ends_with? 'ay'\n\n# good\n'the day'.start_with? 'th'\n'the day'.end_with? 'ay'\n----\n\n=== Active Support Extensions [[active_support_extensions]]\n\nPrefer Ruby's Standard Library over uncommon Active Support extensions.\n\n[source,ruby]\n----\n# bad\n(1..50).to_a.forty_two\n1.in? [1, 2]\n'day'.in? 'the day'\n\n# good\n(1..50).to_a[41]\n[1, 2].include? 1\n'the day'.include? 'day'\n----\n\n=== `inquiry` [[inquiry]]\n\nPrefer Ruby's comparison operators over Active Support's `Array#inquiry`, and `String#inquiry`.\n\n[source,ruby]\n----\n# bad - String#inquiry\nruby = 'two'.inquiry\nruby.two?\n\n# good\nruby = 'two'\nruby == 'two'\n\n# bad - Array#inquiry\npets = %w(cat dog).inquiry\npets.gopher?\n\n# good\npets = %w(cat dog)\npets.include? 'cat'\n----\n\n=== `exclude?` [[exclude]]\n\nPrefer Active Support's `exclude?` over Ruby's negated `include?`.\n\n[source,ruby]\n----\n# bad\n!array.include?(2)\n!hash.include?(:key)\n!string.include?('substring')\n\n# good\narray.exclude?(2)\nhash.exclude?(:key)\nstring.exclude?('substring')\n----\n\n=== Prefer using squiggly heredoc over `strip_heredoc` [[prefer-squiggly-heredoc]]\n\nIf you're using Ruby 2.3 or higher, prefer squiggly heredoc (`\u003c\u003c~`) over Active Support's `strip_heredoc`.\n\n[source,ruby]\n----\n# bad\n\u003c\u003cEOS.strip_heredoc\n  some text\nEOS\n\n# bad\n\u003c\u003c-EOS.strip_heredoc\n  some text\nEOS\n\n# good\n\u003c\u003c~EOS\n  some text\nEOS\n----\n\n=== Prefer `to_fs` for Formatted Strings [[prefer-to-fs]]\n\nIf you're using Rails 7.0 or higher, prefer `to_fs` over `to_formatted_s`. `to_formatted_s` is just too cumbersome for a method used that frequently.\n\n[source,ruby]\n----\n# bad\ntime.to_formatted_s(:db)\ndate.to_formatted_s(:db)\ndatetime.to_formatted_s(:db)\n42.to_formatted_s(:human)\n\n# good\ntime.to_fs(:db)\ndate.to_fs(:db)\ndatetime.to_fs(:db)\n42.to_fs(:human)\n----\n\n== Time\n\n=== Time Zone Config [[tz-config]]\n\nConfigure your timezone accordingly in `application.rb`.\n\n[source,ruby]\n----\nconfig.time_zone = 'Eastern European Time'\n# optional - note it can be only :utc or :local (default is :utc)\nconfig.active_record.default_timezone = :local\n----\n\n=== `Time.parse` [[time-parse]]\n\nDon't use `Time.parse`.\n\n[source,ruby]\n----\n# bad\nTime.parse('2015-03-02 19:05:37') # =\u003e Will assume time string given is in the system's time zone.\n\n# good\nTime.zone.parse('2015-03-02 19:05:37') # =\u003e Mon, 02 Mar 2015 19:05:37 EET +02:00\n----\n\n=== `to_time` [[to-time]]\n\nDon't use https://api.rubyonrails.org/classes/String.html#method-i-to_time[`String#to_time`]\n\n[source,ruby]\n----\n# bad - assumes time string given is in the system's time zone.\n'2015-03-02 19:05:37'.to_time\n\n# good\nTime.zone.parse('2015-03-02 19:05:37') # =\u003e Mon, 02 Mar 2015 19:05:37 EET +02:00\n----\n\n=== `Time.now` [[time-now]]\n\nDon't use `Time.now`.\n\n[source,ruby]\n----\n# bad\nTime.now # =\u003e Returns system time and ignores your configured time zone.\n\n# good\nTime.zone.now # =\u003e Fri, 12 Mar 2014 22:04:47 EET +02:00\nTime.current # Same thing but shorter.\n----\n\n=== Prefer `all_(day|week|month|quarter|year)` over range of date/time [[date-time-range]]\n\nPrefer `all_(day|week|month|quarter|year)` over `beginning_of_(day|week|month|quarter|year)..end_of_(day|week|month|quarter|year)`\nto get the range of date/time.\n\n[source,ruby]\n----\n# bad\ndate.beginning_of_day..date.end_of_day\ndate.beginning_of_week..date.end_of_week\ndate.beginning_of_month..date.end_of_month\ndate.beginning_of_quarter..date.end_of_quarter\ndate.beginning_of_year..date.end_of_year\n\n# good\ndate.all_day\ndate.all_week\ndate.all_month\ndate.all_quarter\ndate.all_year\n----\n\n== Duration\n\n=== Duration Application\n\nIf used without a parameter, prefer `from_now` and `ago` instead of `since`, `after`, `until` or `before`.\n\n[source,ruby]\n----\n# bad - It's not clear that the qualifier refers to the current time (which is the default parameter)\n5.hours.since\n5.hours.after\n5.hours.before\n5.hours.until\n\n# good\n5.hours.from_now\n5.hours.ago\n----\n\nIf used with a parameter, prefer `since`, `after`, `until` or `before` instead of `from_now` and `ago`.\n\n[source,ruby]\n----\n# bad - It's confusing and misleading to read\n2.days.from_now(yesterday)\n2.days.ago(yesterday)\n\n# good\n2.days.since(yesterday)\n2.days.after(yesterday)\n2.days.before(yesterday)\n2.days.until(yesterday)\n----\n\nAvoid using negative numbers for the duration subject. Always prefer using a qualifier that allows using positive literal numbers.\n\n[source,ruby]\n----\n# bad - It's confusing and misleading to read\n-5.hours.from_now\n-5.hours.ago\n\n# good\n5.hours.ago\n5.hours.from_now\n----\n\n=== Duration Arithmetic\n\nUse Duration methods instead of adding and subtracting with the current time.\n\n[source,ruby]\n----\n# bad\nTime.current - 1.minute\nTime.zone.now + 2.days\n\n# good\n1.minute.ago\n2.days.from_now\n----\n\n== Bundler\n\n=== Dev/Test Gems [[dev-test-gems]]\n\nPut gems used only for development or testing in the appropriate group in the Gemfile.\n\n=== Only Good Gems [[only-good-gems]]\n\nUse only established gems in your projects.\nIf you're contemplating on including some little-known gem you should do a careful review of its source code first.\n\n=== `Gemfile.lock` [[gemfile-lock]]\n\nDo not remove the `Gemfile.lock` from version control.\nThis is not some randomly generated file - it makes sure that all of your team members get the same gem versions when they do a `bundle install`.\n\n== Testing\n\n=== Integration Testing\n\nPrefer integration style controller tests over functional style controller tests, https://api.rubyonrails.org/v7.0.0/classes/ActionController/TestCase.html[as recommended in the Rails documentation].\n\n[source,ruby]\n----\n# bad\nclass MyControllerTest \u003c ActionController::TestCase\nend\n\n# good\nclass MyControllerTest \u003c ActionDispatch::IntegrationTest\nend\n----\n\n=== `freeze_time` [[freeze-time]]\n\nPrefer https://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html#method-i-freeze_time[ActiveSupport::Testing::TimeHelpers#freeze_time] over https://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html#method-i-travel_to[ActiveSupport::Testing::TimeHelpers#travel_to] with an argument of the current time.\n\n[source,ruby]\n----\n# bad\ntravel_to(Time.now)\ntravel_to(DateTime.now)\ntravel_to(Time.current)\ntravel_to(Time.zone.now)\ntravel_to(Time.now.in_time_zone)\ntravel_to(Time.current.to_time)\n\n# good\nfreeze_time\n----\n\n== Managing Processes\n\n=== Foreman [[foreman]]\n\nIf your projects depends on various external processes use https://github.com/ddollar/foreman[foreman] to manage them.\n\n== Further Reading\n\nThere are a few excellent resources on Rails style, that you should consider if you have time to spare:\n\n* https://www.informit.com/store/rails-5-way-9780134657677[The Rails 5 Way]\n* https://guides.rubyonrails.org/[Ruby on Rails Guides]\n* https://pragprog.com/titles/rspec3/effective-testing-with-rspec-3/[Effective Testing with RSpec 3]\n* https://pragprog.com/titles/hwcuc2/the-cucumber-book-second-edition/[The Cucumber Book]\n* https://leanpub.com/everydayrailsrspec[Everyday Rails Testing with RSpec]\n* https://pragprog.com/titles/nrtest3/rails-5-test-prescriptions/[Rails 5 Test Prescriptions]\n* https://rspec.rubystyle.guide[RSpec Style Guide]\n\n== Contributing\n\nNothing written in this guide is set in stone.\nIt's my desire to work together with everyone interested in Rails coding style, so that we could ultimately create a resource that will be beneficial to the entire Ruby community.\n\nFeel free to open tickets or send pull requests with improvements.\nThanks in advance for your help!\n\nYou can also support the project (and RuboCop) with financial contributions via https://www.patreon.com/bbatsov[Patreon].\n\n=== How to Contribute?\n\nIt's easy, just follow the contribution guidelines below:\n\n* https://help.github.com/articles/fork-a-repo[Fork] the https://github.com/rubocop/rails-style-guide[project] on GitHub\n* Make your feature addition or bug fix in a feature branch.\n* Include a http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[good description] of your changes\n* Push your feature branch to GitHub\n* Send a https://help.github.com/articles/using-pull-requests[Pull Request]\n\n== License\n\nimage:https://i.creativecommons.org/l/by/3.0/88x31.png[Creative Commons License]\nThis work is licensed under a https://creativecommons.org/licenses/by/3.0/deed.en_US[Creative Commons Attribution 3.0 Unported License]\n\n== Spread the Word\n\nA community-driven style guide is of little use to a community that doesn't know about its existence.\nTweet about the guide, share it with your friends and colleagues.\nEvery comment, suggestion or opinion we get makes the guide just a little bit better.\nAnd we want to have the best possible guide, don't we?\n\nCheers, +\nhttps://twitter.com/bbatsov[Bozhidar]\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frubocop%2Frails-style-guide","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frubocop%2Frails-style-guide","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frubocop%2Frails-style-guide/lists"}