Embracing Conventions With Namespaced Models And Partials


Six months ago when I started working a contract with Joey Beninghove, I had never done full time rails work. I had played with it a few times and built a few small example apps just to get the hang of it, but had not done anything large, “production” quality, or for a real client. I’ve learned a lot in the last six months… too much to cover in one blog post, really. One of the things that I’ve learned, though, is that I really should embrace the idea of conventions over configuration.

What Is “Convention Over Configuration”?

According to Wikipedia, convention over configuration is

a software design paradigm which seeks to decrease the number of decisions that developers need to make, gaining simplicity, but not necessarily losing flexibility.

The phrase essentially means a developer only needs to specify unconventional aspects of the application. For example, if there’s a class Sale in the model, the corresponding table in the database is called sales by default. It is only if one deviates from this convention, such as calling the table “products_sold”, that one needs to write code regarding these names.

When the convention implemented by the tool you are using matches your desired behavior, you enjoy the benefits without having to write configuration files. When your desired behavior deviates from the implemented convention, then you configure your desired behavior.

The most common conventions that people run into in web development, are usually in MVC web apps – whether it’s rail or asp.net mvc or whatever. You typically have the controller and models named with conventions: Person and PersonController, for example. There is no need to configure the system or write any code that connects the Person model with the PersonController, because the convention is that a controller for a model will be named the same as the model, with “Controller” on the end of it. Between the controller and the views, there are also conventions typically in place. For example, in Rails you typically create a view at `/app/views/person/index.html.erb’ for the `index` action of the PersonController. Again, there is no need to provide any configuration or any code that explicitly points the PersonController’s index method to this view. The convention will automatically find the right files and tie them together for you.

A Wolf In Sheep’s Clothing

Shortly after starting work on our rails app, Joey and I were building some models that represented questions and answers for an assessment. Our system supports multiple assessments and we didn’t want them to clobber each other in our models, views or controllers. With that in mind, we decided to namespace all of the assessment related items. As an example, if we had an assessment called “Health Risks”, we would create a “/health_risks” folder in each of our mode, view, and controller folders and place all of our work for this assessment in these folders. Then in our code, we would use modules to define the namespaces:

# /app/models/health_risks/assessment.rb
module HealthRisks
  class Assessment

  end
end

In this specific scenario, we did not want to create a controller or view for every question within a health risk. Our app needs to display multiple questions for each health risk, and each question needs to have a customized partial view to display on the page full of questions. Our first pass at doing this was to put the path to the view directly into the model. We also created another portion of the namespace called `questions` so that we could keep our files organized. Each of our assessment specific questions inherits from a generic Question class, as well.

# /app/models/health_risks/questions/yes_no.rb
module HealthRisks::Questions
  class YesNo < Question

    def path
      "/health_risks/questions/yes_no"
    end

  end
end

We then used this attribute to render the partial for the question, on the view that displays the list of questions for the assessment (using HAML in our case).

- @questions.each do |question|
  = render :partial => question.path, :locals => {:question => question}

This worked well for a long time. It also let us change the location of the question’s partial to be either more specific or more generic. However, this is going against the idea of convention over configuration. Even though we have a “convention” of providing a .path attribute on the question models, this is really little more than putting the configuration of which partial to render, directly into the model.

Fixing The Mistake With A Convention Based Approach

Fast forward 5 months or more and I’ve grown used to the idea of conventions in rails apps. I use them regularly and have even built my own conventions into various parts of the app. However, this old code to configure the partial view for a question is still in place and it’s driving me nuts. I hate having to specify the path on every question. It’s noise in the class, it’s a tight coupling between the model and the view’s location, it’s mixing concerns between the model and the view and it’s just plain wrong. So, last night I decided to start cleaning this up.

If you look at the value that I’m returning from the .path method in that example, you’ll notice that it’s already following the same patterns as the namespacing and class name. This makes it extremely simple to build a convention for the partials. We’ve already got everything in the right place, named correctly. All we need to do is build a helper that converts the class name to the partial name and it should work.

Converting The Class Name To A Folder/File Name

This was probably the easiest part of the process. There’s a method built into rails called `underscore`, as part of the string class. This method turns any capitalized name, such as a ClassName, into a lowercased underscore version: class_name. I also needed to convert the “::” namespace separation into “/” folder separation. To do this, I could use string class’ gsub method. In my case, though, I need a little more logic for some other specific needs, so I decided to split the name by the “::” separator and then join them with a “/” later.

module ApplicationHelper
  def question_path(question)
    # split "Namespace::ClassName" into ["Namespace", "ClassName"]
    segments = question.class.name.split("::")

    # convert the segments to lowercase, underscored; ["namespace", "class_name"]
    segments.map! { |seg| seg.underscore }

    # build the path: /namespace/class_name
    segments.join "/"
  end
end

Now that we have the question_path method in our application helper module, we can call this directly from our view to get the path to the question’s partial. In the case of the example “HealthRisks::Questions::YesNo” class, this method will return “/health_risks/questions/yes_no” – the same value that we were originally providing in our model’s path method. This means we can get rid of that method and correctly use a convention to determine which partial to load for each question.

Change the view to use this new method, and we’re done:

- @questions.each do |question|
  render :partial => question_path(question), :locals => {:question => question}

The right partial is now displayed for the right question, without having to configure which partial should be used, within the model. What should have been obvious many months ago is finally being fixed.

What’s your favorite part of software development, and why?