Site icon Treehouse Blog

Ruby on Rails: Configuration over Convention

Ruby on Rails

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!”.

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.

Exit mobile version