LearnRuby on Rails: Configuration over Convention

Treehouse
writes on April 10, 2017

What’s that you say? We have it backwards? Ruby on Rails is supposed to favor “convention over configuration”? Well, we’re going to break that rule today…

When I was first learning Rails years ago, I thought that the model, view, and controller were inseparable. If I was creating an ArticlesController, then no matter what, it had to have a show method that used Article.find to store a model in @article, to be rendered in the articles/show.html.erb template. When the code did something unusual (like the ArticlesController loading in a Comment model) I got confused.

Rails learners, I want to save you from that trap. So in this post, we’re going to throw aside scaffolds and other conventions. We’re going to configure a totally unrelated model, view, and controller to work with each other. When we’re done, you’ll see they’re not as tightly coupled as you may have thought.

Model

From your terminal, create a new Rails app, named whatever you want.

$ rails new anapp

Open up the app directory in your favorite editor.

Next, create a new file in the app/models/ subdirectory, named a_model.rb. You don’t have to use rails generate model ... or anything like that; creating the file manually will work just fine. In that file, type the following code (you can exclude the explanatory comments, if you like):

app/models/a_model.rb

# Any subclass of ApplicationRecord is considered a model.
class AModel < ApplicationRecord
  # By default Rails looks at the class name and determines
  # the database table name based on that. "AModel" would
  # default to a table name of "a_models", so instead the
  # below line overrides that with something more sensible.
  self.table_name = "a_table"
end

Now we need a migration to create a_table in our database. If you had used rails generate model ... to create AModel, it would have created a migration along with it. But we can create our own pretty easily. Create another file within the db/migrate/ subdirectory (you might have to create the migrate/ directory first), named 20170101000000_a_migration.rb. (We have to put that 20170101000000 date at the start of the file name so that it’s recognized as a migration, but it doesn’t matter what date we use.)

db/migrate/20170101000000_a_migration.rb

# Again, any subclass of ActiveRecord::Migration[5.0] is
# considered a migration.
class AMigration < ActiveRecord::Migration[5.0]
  def change
    # This will create a database table named "a_table".
    create_table :a_table do |t|
      # This creates a "string" column named "an_attribute".
      t.string :an_attribute
    end
  end
end

Save that, and run the migration from your terminal with:

$ bin/rails db:migrate

Rails sets up attribute reader and writer methods on the model class for every column in the database table. Because you specified in AModel that it should use the a_table table, and a_table has an an_attribute column, you can now assign an_attribute on any AModel instance from your Ruby code. We can save a new AModel object from the Rails console:

$ bin/rails console
Loading development environment (Rails 5.0.1)
2.3.0 :001 > model = AModel.new
 => #<AModel id: nil, an_attribute: nil>
2.3.0 :002 > model.an_attribute = "A value!"
 => "A value!"
2.3.0 :003 > model.save
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "a_table" ("an_attribute") VALUES (?)
               [["an_attribute", "A value!"]]
   (1.1ms)  commit transaction
 => true
2.3.0 :004 > exit
$

Controller

Now let’s see if we can view that AModel object from a web browser. First, we’re going to need to direct requests from a particular URL to a particular method on a controller class. We can do that by adding a route. Edit the existing routes file to look like this:

config/routes.rb

Rails.application.routes.draw do
  # When you access "http://localhost:3000/apath" in your
  # browser, the "anaction" method on the "AController"
  # class will be called. (Rails sees "a", capitalizes it,
  # and adds "Controller" onto the end.)
  get "/apath", to: "a#anaction"
end

Now we need to create the AController class, and give it an anaction method:

app/controllers/a_controller.rb

# Any subclass of ApplicationController is treated as a
# controller.
class AController < ApplicationController
  def anaction
    # Load the object we created in the console. We can
    # store it in any variable we want.
    @a_variable = AModel.first
    # If you add a "text:" argument, "render" will just add
    # the given string to the response instead of loading a
    # template.
    render text: @a_variable.an_attribute
  end
end

At this point, you can run bin/rails server from your terminal, and visit http://localhost:3000/apath in your browser. You should see the text “A value!”.

Our custom model in the browser

View

Now that we know our controller’s working, we can set it up with a proper view. The render text: line causes Rails to render the given text instead of a view, so remove that line from app/controllers/a_controller.rb, or the next step won’t work.

By default, Rails uses the name of the current controller and action method to determine where to look for a view template. Since we’re invoking AController‘s anaction method, it will look in the app/views/a/ directory for a file named anaction.html.erb. So, let’s create an ERB template file there:

app/views/a/anaction.html.erb

<h1><%= @a_variable.an_attribute %></h1>

Refreshing the browser should show you “A value!” as a level 1 heading.

But this blog post isn’t about using the defaults, it’s about using configuration to override them. So let’s move the template file to a new subfolder, and give it a new file name: app/views/a_subfolder/a_template.html.erb.

Then, let’s revise the controller to load the template from the new location:

app/controllers/a_controller.rb

class AController < ApplicationController
  def anaction
    @a_variable = AModel.first
    # "app/views/" and ".html.erb" are added by default.
    render "a_subfolder/a_template"
  end
end

Refresh your browser, and Rails should load the template from your custom path instead.

Don’t Try this in Production, Kids

Is reconfiguring Rails so you can use names like AModel and AController best practice? Definitely not. But if you someday have a model or a template that you want to share between two completely different controllers, you can do that; Rails will let you. When it makes sense to do so, you can step outside the Rails conventions, as long as you know how to tweak the configuration.

If you liked this post, you’ll love these Rails courses and workshops on Treehouse:

Start learning to code today with a free trial on Treehouse.

3 Responses to “Ruby on Rails: Configuration over Convention”

  1. Wow, some great points and very thorough! Convention over Configuration is more of a problem with estimates and prediction than scaling. Sometimes it just seems like everything is easy and all of a sudden, you will spend a lot of time on something that you thought was a 5 minute work. But all of this is actually a learning problem. Once you get proficient enough with Rails, the would be no danger anymore.

  2. Benjamin on April 11, 2017 at 2:17 am said:

    Nice write up. Just the abstract examples “AModel” are a bit weird to read through 😉

    But in general i agree. Rails just says it favors convention over configuration. You can still configure a ton of stuff – and whatever has no real configuration can always be adjusted with the sledgehammer (“monkey patching” style). It just hides most of it under to hood 🙂

    I used Rails a couple of times in combination with legacy projects. Usually hooking onto the old database for read access to get data from it.
    It’s actually pretty easy with rails to adjust table name, attributes, and even building relationships with tables that are totally not in the rails scheme.
    And at the end working with the legacy database is easier in rails than it was in whatever application it was build for.

    The separation between controller and model is also a lot more common today. Sure usually you have a UsersController to your User Model. But especially for more than just little scaffold applications this usually changes and you start having different controllers using the same model or even put in another layer, for example with form objects.

  3. This is really great man! I really enjoyed learning that you can tweak it in those ways. I got really excited about rendering a specific view with an unconventional name and path.

Leave a Reply

You must be logged in to post a comment.

Learning to code can be fun!

Get started today with a free trial and discover why thousands of students are choosing Treehouse to learn about web development, design, and business.

Learn more