Subscribe to the MPC blog rss feed feed-icon-14x14
 

DRYing Up My Controllers

Posted on 07:00AM on 12/31/2008
Tags: ruby, rails

I'm working on a Rails project right now. It's been a while, since I've been doing most Ruby work with Sinatra lately.

Millions of posts have talked about DRYing up controllers. I'm not talking about the skinny controller/fat model concept, necessarily, although that should be addressed as well. What I'm refering to is the constant repetition in all controllers. A create action, for example, always has essentially the same code.

I'm not writing this post to claim that I've solved the problem in some unique, world changing way. I'm really just writing to acknowledge that I see it and have decided to deal with it. Repetition bugs me. Repeatedly spec'ing the same controller code bugs me.

The bottom line: I'm writing a module that can be included in my controllers that will remove the need to constantly add index, show, new, create, edit, update, destroy and the code that goes along with them. I will test it well. I will override in the controllers to handle special circumstances. My simple controllers should NOT need to be more that 50 lines long (if that).

I guess this is actually a New Year's resolution. So there!

0 Comments (Show) (Comments are closed for this post)

Multiple Unobstrusive Onload Events Using Prototype in Ruby on Rails

Posted on 02:44PM on 10/06/2008
Tags: javascript, rails

I'm currently working on a web application in Ruby on Rails that has a high degree of client-side scripting requirements. In almost every view, many things need to take place just after the page loads. I'm using Prototype, but any good javascript framework will do.

Since the application has a large amount of forms to be filled in by users, almost every view needs to set focus on a textbox in the form. On top of this requirement, almost every view has other javascript-related requirements when a particular view loads. For example, several of the views need to be able to set the form to its previous state when reloaded.

The typical way of registering an OnLoad event looks something like:

//application.js
  window.onload = function() {
    setFocus();
  }

  function setFocus()
  {
     focusElement = getFocusElement();
     if(focusElement && focusElement.focus)
     {
        focusElement.focus();
     }
  }

  function getFocusElement()
  {
     $$('input.focus').first();
  }

This sets up a callback that, when the window is loaded, locates an input element with a class of 'focus' and calls focus on it. This works fine. I put this in my application.js file and load it with every view.

Now what about running additional OnLoad actions for specific views? I don't want to code everything in application.js. That approach has several problems. Firstly, it means that application.js gets really bloated and if it loads with every view, there is additional overhead. Secondly, it means that you may be loading a bunch of code, required for other views, that is not needed for the current view. Thirdly, some of the OnLoad code may expect certain elements to be on the page, and they won't always be there. The bottom line: this is not a good approach. So what is the solution?

For me (and you may have other, better solutions), the approach that works best is a combination of application.js and content_for in my views.

Firstly, I ensure that in my layout I include an addtional named yield in the head section of the HTML. For example:

<%= yield :head %>

This gives me a place to include additional javascript files. In my view, I can do

  <% content_for :head do %>
     <%= javascript_include_tag "user" %>
  <% end %>

In this, case, I would be adding a javascript file called user.js (located in public/javascripts) to the head section of this view.

That solves one problem: that of only loading javascript code appropriate to the view currently being rendered.

There is only one more problem to get past: In my application.js, I am registering an OnLoad handler (as show above). In my user.js file, I also need to register an OnLoad event handler. As it sits right now, whichever file gets loaded last will clobber all the previous OnLoad handler registrations. How can this be addressed?

My solution (and, I'm sure, others') is as follows:

  //application.js
  var onLoadHandlers = $A();

  window.onload = function() {
    onLoadHandlers.reverse();
    onLoadHandlers.each(function(method) {
       method();
    })
  }
  
  function registerOnLoadHandler(method) {
     onLoadHandlers.push(method)
  }

  registerOnLoadHandler(focusHandler);

  function focusHandler() {
    //as shown previously
  }

If application.js is loaded with every view, we can effectively ensure that it's possible to call registerOnLoadHandler from any other javascript file loaded after it.

As a result, in my user.js file, I can do the following:

  //user.js
  registerPageLoadHandler(setViewState);
  registerPageLoadHandler(roleChangedHandler);

  function roleChangedHandler() {
    //code
  }

  function setViewState() {
    //code
  }

These functions will also be run when the page is loaded.

I like this technique and it seems to work well. It minimizes the amount of javascript code that I need to load on a per page basis and ensures that only javascript required for a particular view is loaded for that view. I also think that it makes it very easy to find javascript code related to a particular view.

2 Comments (Show) (Comments are closed for this post)

named_scope

Posted on 03:45AM on 09/21/2008
Tags: activerecord, ruby, rails

Lately everyone has been going mad over a relatively new addition to rails: named_scope. Up until now, I hadn't had the chance to try it out. I always just assumed that it was more syntactic fluff that seems to accumulate. Wow, was I wrong! It's an unbelievably cool and useful idea. Here's a really simple example. Say I have 2 models: Country and Region. They are as you would expect. A Region belongs to a Country and a Country has many Regions, etc. I use them pretty much for dropdown lists and things like that. Here's some code:

class Region < ActiveRecord::Base
  belongs_to :country
end

class Country < ActiveRecord::Base
  has_many :regions
end

Prior to named_scope, if you wanted to get all Regions in alphabetical order (as an example), you'd have to do something like:

class Region < ActiveRecord::Base
  #other code
  def self.ordered
    find(:all, :order => "name ASC")
  end
end

Which was ok, but this is nicer:

class Region < ActiveRecord::Base
  named_scope :ordered, :order => "name ASC"
  #other code
end

This creates a method on the Region class called ordered that can simply be called.

OK, so that's cool and all, but really not that big a deal, right? Well, no - until you consider that you can chain calls to named_scope, thus adding more and more constraints. For example:

class Region < ActiveRecord::Base
  belongs_to :country
  named_scope :ordered, :order => "name ASC"
  named_scope :by_country, lambda { |c| { :conditions => ["country_id = ?", c.id] } }
  named_scope :containing, lambda { |s| { :conditions => ["name like ?", "%#{s}%"] } }
end

class Runner
 c = Country.find_by_name("Canada")
 regions = Region.by_country(c).containing("A").ordered
 puts "Provinces in Canada containing the letter A in ascending order: #{regions.inspect}"
end

This generates SQL that looks like this:

SELECT * FROM `regions` WHERE ((name like '%A%') AND (country_id = 1)) ORDER BY name ASC

Another reason that I like name_scope is that because models such as these are frequently eused in dropdowns, etc, you can use named scope to return a very lightweight version of the class, while excluding attributes that are unnecessary for a simple dropdown. For example:

class Region < ActiveRecord::Base
  belongs_to :country
  named_scope :ordered, :order => "name ASC"
  named_scope :by_country, lambda { |c| { :conditions => ["country_id = ?", c.id] } }
  named_scope :containing, lambda { |s| { :conditions => ["name like ?", "%#{s}%"] } }
  named_scope :simple, :select => "id, name"
end

This allows me to only bring back the attributes that I need. And again, it could be chained with other named scopes

I think that this is a very powerful concept that allows developers to write more DRY and readable code.

0 Comments (Show) (Comments are closed for this post)

Full Text Search

Posted on 04:12PM on 09/12/2008
Tags: search, rails, python, ruby, django, sinatra

I've come up with my next chunk of functionality that I'd like to implement for this site - full text searching on blog posts. There appear to be several options:

UPDATED: I could also use MySQL fulltext indexes and just search those if I wanted to make it really easy ;)

I don't want anything complicated and I'd rather not add any big additional overhead to my already low on resources slice, so I'm leaning towards acts_as_indexed.

I realize that there'll be some work involved regardless since I'm not using ActiveRecord here and all of these plugins assume Rails.

Additionally, I'd like to be able to use/port the search solution within Django so that my Django mirror site works the same way.

Anyone out there have an opinion?

0 Comments (Show) (Comments are closed for this post)

Rails Django Comparison Part 1

Posted on 05:23AM on 09/08/2008
Tags: rails, django

I've recently spent some time playing with Django. In a previous post, I mentioned that I'd post some thoughts on how Django compares with Ruby on Rails.

Both frameworks alllow for RAD style development. Each include tools that allow the developer to focus on writing code that is directly related to the domain and not to underlying infrastructure. Without a doubt, both frameworks provide a leg up.

Both frameworks follow a MVC approach. With Rails, this is very obvious - there are models, views and controllers. Django does not use exactly the same terminology, but ultimately also uses an MVC approach. Django has the concept of models. The slight deviation, however, comes with views and controllers. What Django terms views are actually (at least in my opinion) controllers (or methods of a controller). The Django equivalent to a Rails view is called a template. This is the place where the html goes.

In terms of models, both frameworks make use of the the Active Record pattern. Django models are slightly more verbose than Rails models, but also contain far less 'magic' due to the nature of the Python language. In some respects, this is good. It's certainly easier to understand a Django model than it is an Rails model - at least in cases where the model is complex.

There are definitely some differences, though. While Rails is an opinionated framework that highlights convention over configuration, Django is much more about choice. Many components of the Django stack are modular. Don't want to use Django models? Build your own (although you'll have to write your own data access layer as well). Don't want to use the built in Django template system? Use a different one. In some ways, I like the Rails approach. It's sort of nice to not have the choice. At the same time, in some cases it's nice to have alternatives. 

The templating 'views' systems of each framework are also different, but not in the way you might think. Ultimately, both get translated into pure html. The main difference is that in ERB (in Rails), Ruby code is actually executed and any arbitrary Ruby code can be placed within the template. With Django, no Python code is present within a template. There are control structures called tags and transformers called filters that serve similar purposes. Not a big deal, but something to get used to.

Lastly, I noted that there is a big difference between the two frameworks where things like view helpers are considered. Rails has more than enough helpers (link_to, link_to_remote, etc), while Django lets the developer or designer use pure html. I'm not sure yet how I feel about not having helpers. In a way I like it. I feel like I have more control. I also think it makes it easier for designers if they are the folks writing the views. On the other hand, Rails' helpers sure are handy.

Anyway, that wraps it up for now. This is a pretty high-level comparison, I realize. I'll likely expand on it in the future.

0 Comments (Show) (Comments are closed for this post)

Please note that I am currently unavailable for any large, long term work.