Rails Glossary > Convention Over Configuration

Convention Over Configuration

A term coined by DHH, Rails framework creator, that states a great preference to use as many convened upon decisions over the extensive use of configuration and customization before hand.

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.

Almost any convention can be overridden by configuration if it makes sense. However, besides writing clean code, sticking as close as possible to Rails defaults is usually a good practice to help other developers work with our codebase and make the upgrade process easier.

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 indexview:

<%# 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.

A configuration-centric approach can make for a custom and more enjoyable experience, but consider that dependencies are liabilities and that moving away too far from standards can produce issues eventually.

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.

Try Avo for free