Rails Conventions (Rootstrap)
Apply these whenever producing or modifying Rails-specific code. Full guide: https://github.com/rootstrap/tech-guides/blob/master/ruby/rails.md
Complements ruby-conventions (language-level style still applies).
Configuration
- Custom initialization in
config/initializers; one file per gem, named after the gem (e.g.carrierwave.rb). - Environment-specific settings in
config/environments/; shared settings inconfig/application.rb. - Create a
stagingenvironment that mirrors production. - Extra YAML config under
config/, loaded viaRails::Application.config_for(:yaml_file). - Append non-default assets to
config.assets.precompileinproduction.rb(e.g. admin CSS/JS).application.*and non-JS/CSS assets are already included.
Routing
- Prefer
resourcesover custom routes; use:only/:exceptto limit routes.# bad get 'topics/:id', to: 'topics#show' # good resources :topics, only: :show member/collectionfor extra RESTful actions; use block form when many.- Express associations with nested routes; use
shallow: truebeyond 1 level deep. namespaceto group related actions (e.g.admin).- Never use the wildcard
match ':controller(/:action(/:id(.:format)))'route. - Avoid
matchunless mapping multiple HTTP verbs via:via.
Controllers
- Keep controllers skinny — no business logic (belongs in models/services).
- Each action should ideally call only one method beyond an initial
find/new. - Share at most two instance variables between controller and view.
Rendering
- Prefer templates/partials over
render inline:. render plain:overrender text:.- Use HTTP status symbols, not numbers.
# bad render status: 500 # good render status: :forbidden
Models
- Introduce non-ActiveRecord model classes freely; short, meaningful names.
- Use ActiveAttr gem for non-persisted models needing AR-like behavior.
- Keep models for business logic/persistence; move formatting/HTML concerns to decorators.
ActiveRecord
- Don't alter AR defaults (table names, primary keys) without strong reason.
- Group macros at top in order:
default_scope, constants,attr_*,enum, associations, validations, callbacks, other macros (e.g. devise). - Prefer
has_many :throughoverhas_and_belongs_to_many. - Prefer
self[:attr]/self[:attr] = valueoverread_attribute/write_attribute. - Use "sexy" validation syntax:
validates :email, presence: true, length: { maximum: 100 } - Extract reused/regex validators into
app/validatorsasEachValidatorsubclasses. If the validator is generic and used across multiple apps, extract it to a shared gem instead. - Use named scopes; convert complex parameterized scopes into class methods returning a relation.
- Beware validation-skipping methods:
update_attribute,update_columns,update_all,increment!,toggle,touch, counter methods. - User-friendly URLs: override
to_paramor usefriendly_id.def to_param "#{id} #{name}".parameterize end - Use
find_eachfor iterating AR collections, notall.each. - Always add
prepend: trueonbefore_destroycallbacks that perform validation. - Always set
dependent:onhas_many/has_one. - When persisting, use bang methods (
save!,create!,update!) or handle the returned status.
ActiveRecord Queries
- Never interpolate params into SQL strings. Use
?placeholders or named placeholders (prefer named when >1). find(id)overwhere(id: id).take;find_by(attrs)for attribute-based single lookups.where.not(id: id)overwhere("id != ?", id).- Heredocs with
.squishfor explicit SQL infind_by_sql:User.find_by_sql(<<-SQL.squish) SELECT ... SQL
Migrations
- Keep
schema.rb(orstructure.sql) in version control; userake db:schema:loadfor new DBs. - Enforce defaults at the DB level, not the application.
- Enforce foreign-key constraints (Rails 4.2+).
- Use
changefor constructive migrations; useup/downfor non-reversible ones likedrop_table(or pass a block todrop_tableinsidechange).changeonly works for commands listed inActiveRecord::Migration::CommandRecorder. - If using a model inside a migration, redefine it with an explicit
table_name:class MigrationProduct < ActiveRecord::Base self.table_name = :products end - Name foreign keys explicitly (e.g.
name: :articles_author_id_fk). - Avoid
FLOATfor rational numbers; useDECIMALor a base-unit integer (money in cents).
Views
- Never call models directly from views.
- Complex formatting → decorators.
- Use partials and layouts to deduplicate.
Internationalization
- No user-facing strings in views/models/controllers; move to
config/locales. activerecordscope for model/attribute translations (User.model_name.human,human_attribute_name).- Organize locales into
locales/modelsandlocales/views; load extra dirs viaconfig.i18n.load_path. - Short forms
I18n.t/I18n.l. - Lazy lookup (
t '.title') in views; dot-separated keys elsewhere instead of:scope.
Assets
app/assets→ app-specific.lib/assets→ in-house libs.vendor/assets→ third-party.- Prefer gemified assets (e.g.
jquery-rails,bootstrap-sass).
Mailers
- Name classes
SomethingMailer; provide both HTML and plain-text templates. config.action_mailer.raise_delivery_errors = truein development.- Local SMTP (Mailcatcher/Letter Opener) in development.
- Set
default_url_options[:host]per environment. - Always use
_urlhelpers (not_path) in email bodies. - Format
default from:as'Your Name <info@your_site.com>'. - Test env:
delivery_method = :test; dev/prod::smtp. - Inline CSS for HTML emails (use
premailer-railsorroadie). - Send emails in background jobs (e.g. Sidekiq); never inline during request.
Active Support Core Extensions
- Prefer
&.overtry!. - Prefer stdlib (
start_with?,end_with?,include?) over AS aliases (starts_with?,in?). - Prefer plain comparisons over
inquiry,Numeric#positive?/negative?, etc.
Time
- Set
config.time_zoneinapplication.rb. - Never use
Time.parseorTime.now. UseTime.zone.parse,Time.zone.now, orTime.current.
Bundler
- Dev/test gems in proper Gemfile groups.
- Prefer well-established gems; review source of obscure ones.
- Group OS-specific gems under
darwin/linuxand useBundler.require(platform). - Never remove
Gemfile.lockfrom version control.
Managing Processes
- Use
foremanto manage multiple external processes (seeProcfile/Procfile.dev).
Logging
- Pass a block to
Rails.logger.debugwhen interpolating to avoid string-building at suppressed levels.# good Rails.logger.debug { "attrs: #{@person.attributes.inspect}" }