As a part of the Rails Doctrine, Convention over Configuration is a set of guidelines and defaults set by the framework to make development faster and more efficient.
It's one of the core design philosophies behind Rails: providing sensible defaults allows developers to focus on what makes the application unique instead of spending too much time writing configuration or boilerplate code.
As it usually happens, the philosophy came to life as a response to the configuration and boilerplate-heavy web applications that dominated the scene when the framework was created.
Part of the joy of removing the boilerplate can be appreciated in the famous 15-minute blog video that DHH made when presenting the framework in 2005.
Let's explore some Rails conventions:
Conventions
In the context of Rails, a convention is an agreed-upon or designed way to achieve things. Conventions are documented but frequently implicit.
For example: if we create a new model, a migration is added that will create a table for the model in its plural form: the Message
model gets the messages
table.
Autoloading
Maybe the convention that causes the most confusion: Rails autoloads classes based on the file system path.
There's no need to import them explicitly. If we add a Comment
class in the models
directory, we can use that class anywhere because Rails loads it when the class is referenced.
If you've used Ruby before, you probably know that you need to require dependencies explicitly:
require "message"
message = Message.new(body: "Hello, World!")
That's not the case with Rails: classes and modules are available everywhere without the require
calls.
If we have a User
model defined in app/models
we can just reference it in a controller without requiring anything:
class PostsController < ApplicationController
def index
user = User.first
@posts = user.posts.published
end
end
In this example, without autoloading, we would need to require the ApplicationController
class, as well as the User
.
For this purpose, Rails uses Zeitwerk and sets some loaders which provide autoloading, reloading and eager loading.
File and class naming
The next convention that can be confusing at first is the way file and class naming is used to map behavior.
In models, class names use camel case in singular. For example, UserInteraction
maps to the user_interaction.rb
file and to the user_interactions
database table.
Controllers behave similarly, class names use camel case, but every class in the controllers
directory has the controller
suffix, and it conventionally matches to the model using the plural form. For the previous example, the controller would be UserInteractionsController
and live under app/controllers
.
Function names, also called actions in the controller context, use snake case: first_name
, show
, destroy
, etc.
When it comes to tests, they live in the test
directory. Different types of test live within a subdirectory like test/models
or test/controllers
and each test class where actual tests are defined has test
as a suffix: article_test.rb
.
Many gems extend these types of conventions to make their own. For example, the pundit gem, an authorization library for Ruby and Rails apps, defines the authorization rules inside policy files. These files live in the policies
directory, they reference a resource and its name ends in policy
. For example: PostPolicy
.
Database
Besides the naming convention, foreign key relationships follow a pattern too.
An Article
model that belongs to a user should have a user_id
column in the articles
table. Then, we can add the association using the belongs_to
method.
Primary keys default to an auto-incrementing id
column, and, when creating resources with Rails commands like the model, migration or scaffold generators, two useful timestamp columns created_at
and updated_at
are added automatically.
Another thing that Rails does implicitly that involves the database is that it defines a series of methods for each table column using metaprogramming: getters and setters and many helper methods. Boolean fields also get a corresponding method with a ?
sign.
For example, a published
boolean field adds a published?
method that returns the boolean status.
Routes
Rails follows RESTful routing conventions that map HTTP verbs to controller actions automatically. When we add resources :posts
to our routes file, Rails creates seven standard routes without additional configuration.
These routes follow a predictable pattern: GET /posts
maps to the index
action, GET /posts/:id
maps to show
, POST /posts
maps to create
, and so on. The controller actions are expected to be named accordingly: index
, show
, new
, create
, edit
, update
, and destroy
.
Rails also generates helpful path and URL helpers based on the resource name. For a posts
resource, we automatically get methods like posts_path
, post_path(post)
, new_post_path
, and edit_post_path(post)
that we can use in views and controllers without defining them explicitly.
Nested routes follow similar conventions. If we have posts that belong to users, we can write resources :users do resources :posts end
and Rails will create routes like /users/:user_id/posts
that map to the same controller actions but with the parent resource context.
Views
By default, views are files that live in the app/views
directory and each one of them matches to an action in the controller for its corresponding resource.
For example, if we have:
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
Conventionally, we should have an index.html.erb
view inside the app/views/posts
directory.
However, the opposite is not true: we can define controller actions that don't match to views like the: create
action which is generally in charge of creating a given resource or entity.
The other convention that can seem magical at first is that instance variables defined in controllers are available in views without the need for us to do anything.
This means that we can use the @posts
in our index
view:
<%# app/views/posts/index.html.erb %>
<% @posts.each do |post| %>
<p><%= post.title %></p>
<% end %>
Associations
Active Record associations also use naming conventions to establish relationships between models with minimal configuration.
For example, consider the following association:
class User < ApplicationRecord
has_many :posts
end
class Post < ApplicationRecord
belongs_to :user
end
With the belongs_to :user
macro call, Rails expects a user_id
in the posts table, and it automatically adds instance methods like post.user
which will retrieve the associated user instance, post.user=
which can help us set the user for a Post instance. Inverse methods in the User
model are also defined: user.posts
to retrieve posts associated to the user or user.posts.build
to create a new Post
in memory that's associated to the user instance via the user_id
.
However, when decide to customize some things, we can achieve that via configuration:
class Post < ApplicationRecord
belongs_to :author, class_name: "User", foreign_key: :author_id
end
This goes to show that we can customize things when it makes sense, but we should try to keep customizations to a minimum to avoid deviating too much from expectations.
Configuration
The opposite of the Convention Over Configuration approach Rails embraces is a configuration-centered approach, which gives developers the ability to structure the application and its moving parts to their liking.
It implies making many decisions before Hello, World!
and it also requires experience, good judgment and the ability to future-proof the choices.
Even though its name implies writing more configuration, which is often true, it's also about deciding things like software architecture, libraries, standards, etc.
For example, some developers dislike Active Record as an ORM, or they even dislike ORMs altogether, but they might enjoy Ruby as a language, so they prefer to use alternative frameworks that are much more configurable and less opinionated in the way things should be done.
As it typically happens when making these decisions, there's a balance to be made between the pros and cons of each approach, particularly if the product we want to build should survive the short term.
Criticisms
When properly understood, the Convention over Configuration principle is probably one of the main strengths of Rails.
However, because most of the conventions are implicit, some people find that using Rails feels like magic and that finding where behavior comes from can be overwhelming.
This is especially true for developers that have extensive experience with languages that favor explicit behavior. Their initial exposure to Rails can make them feel lost or confused.
The other branch of criticism to the principle comes from the fact that onboarding developers to a big Rails codebase can become an issue if the new developers are not very familiar with the framework, which can make hiring harder when compared to other frameworks.
Considering that, even if it feels magic at times, Rails is just Ruby, learning about the language and how the framework uses it to build its own constructs can greatly help understand where the conventions come from, learn to embrace them unless there's a specific reason not to, and be more productive with the framework than with alternatives.
Summary
Rails was born in an era where extensive configuration and boilerplate code were required before starting to produce useful applications.
In that context, the framework leveraged Ruby and some of its unique features to make developers more productive by abstracting some repeating behavior into sensible and opinionated defaults that made sense for most application, most of the time.
The Convention over Configuration principle, which is a part of what makes Rails omakase, involves many decisions and things that Rails does implicitly which can make developers more productive by contemplating scenarios that are common to most applications.
As long as we strive to learn about them and stick to Rails conventions, our experience with the framework will be more enjoyable in the long term.
Of course, if you're not comfortable with the approach and you prefer things to be explicit most of the time, Rails is probably not for you and there are many other options that will better fit your taste.