Never Raise StandardError Directly

Posted by ryan Tue, 21 Nov 2006 06:40:00 GMT

It's not a good idea to raise StandardError directly from your Rails application, particularly when using an exception as a way to indicate a recoverable error from an ActiveRecord transaction. In such cases, it's better to subclass from StandardError and raise the subclass instead. If you use StandardError directly, you may find that your system is catching and recovering from errors that are related to faulty logic rather than broken business rules.

For example, consider the following bad code:

1
2
3
4
5
6
7
8
9
10
11
def some_method(x)
  x[x.length] + 1
  raise StandardError if x[0] < 2
end

begin
  a = [1, 2]
  some_method( a )
rescue StandardError
  puts "Bad value for index 0: #{a[0]}"
end
This code does nothing useful, but assuming that it did, and assuming that you're catching StandardError for the case when the first index of the given array has an invalid value, you're in for a surprise. In fact, the error that would be caught by the rescue clause above is thrown by the code on line 2. In fact, the actual error thrown by this line is an instance of NoMethodError, which is a subclass of NameError, which is a subclass of StandardError.

If you wrote a unit test for the some_method method, and asserted that StandardError was raised, it would pass incorrectly. This is especially misleading if you are writing a test for a controller action and asserting that, upon some error condition, the application redirects the user to a page displaying that error. I find that I don't explicitly raise many errors from my application code, except when I wrap the code in a controller action inside an ActiveRecord transaction. The only way to roll back the transaction is to raise an exception from within the transaction and rescue from it outside, such as:

class SomeController < ApplicationController
  def some_action
    begin
      SomeRecordClass.transaction do
        a = [1, 2]
        some_method( a )
      end
    rescue StandardError
      @error_message = "Bad value for index 0: #{a[0]}"
      render 'some_template'
      return
    end
    redirect_to some_url
  end
end
In the above code, you can't avoid raising an exception if you want to roll back the transaction. In this case, rather than rescuing from StandardError, rescue from an application-specific exception that inherits from StandardError. This way you know that you're recovering from an application error, and not faulty logic in the code.

Nested Hash Params with Named Routes in Ruby on Rails 5

Posted by ryan Fri, 17 Nov 2006 04:41:00 GMT

I've never (as of rev 5522) been able to pass a nested hash as parameters to url_for and have it flatten it properly on the generated URL. I encountered the problem again the other day and, having had just about enough, decided to try and nip it in the bud.

The problem I was having occurred when tried to make an HTTP request to a named route such as:

people_url(:person => { :name => 'Bob', :profession => 'Developer' })
This translates to a URL such as http://localhost:3000/people?person=nameBobprofessionDeveloper for a POST request. This obviously doesn't decode the hash in a format that can be encoded back its original form.

When I've encountered this problem in the past, I could never find any information from someone who's actually solved the problem. There are a few patches submitted as tickets to the Rails devs, but one seems to be a duplicate of the other, and neither seems to fix this problem on Edge, at least not for my particular use case.

The root of this particular problem lies in a method in the action_controller/routing.rb file within actionpack/actioncontroller, in the options_as_params of the RoutingSet class. The method, without comments, looks like this:

1
2
3
4
5
6
7
def options_as_params(options)
  options_as_params = options[:controller] ? { :action => "index" } : {}
  options.each do |k, value|
    options_as_params[k] = value.to_param
  end
  options_as_params
end
The problem lies on line 4 in the case where value is a Hash. In that case, the to_param method simply converts the Hash instance into its String representation, which is just a pile of keys and values mashed together, as in my people_url example.

To solve this problem, I changed the method to look like this:

def options_as_params(options)
  options_as_params = options[:controller] ? { :action => "index" } : {}
  options.parameterize do |param_key, param_value|
    options_as_params[param_key] = param_value.to_param
  end
  options_as_params
end
The parameterize method is an extension I made to the Hash class that essentially flattens a hash like:
{
  :person => {
    :name => 'Bob',
    :profession => 'Developer'
  }
}
into a new Hash that looks like:
{
  :'person[name]' => 'Bob',
  :'person[profession]' => 'Developer'
}
The new Hash is in a format that can be decoded into a URL query string that will be properly encoded back into a Hash on the request. My Hash extension for the solution looks like this:
class Hash

  def flatten(superkey)
    flattened_hash = {}
    self.each do |key, value|
      flattened_hash["#{superkey}[#{key}]".to_sym] = value
    end
    flattened_hash
  end
  
  def parameterize
    self.each do |k, value|
      if value.is_a?(Hash)
        value.flatten(k).each { |fk, fv| yield fk, fv }
      else
        yield k, value
      end
    end       
  end

end
Note that this has only been tested to work for a single-level nested Hash, as that satisfied my needs at the time I developed the solution.

Tag-based Deployment with Capistrano 1

Posted by ryan Mon, 31 Jul 2006 13:16:00 GMT

I've discovered that Capistrano is a useful deployment utility out-of-the-box if your needs are relatively simple. By simple, I mean that there is a single SCM that is accessible by all machines that you might need to do deployment to. This isn't always the case, especially when developing applications for large organizations. In such situations, you won't always get the access that you need to make deployment a straight-forward process. We've recently completed the deployment process for the project that I am currently working on, and it was one of those not-so-straight-forward scenarios.

Our source code is stored in an SVN repos that is seperate from the client's repos, and only used by the dev team (for CI and such). However, the client servers do not connect to our dev repos, and they want deployment to be possible from their own repos. To do this, we've developed a deployment policy, along with supporting logic in the deployment process, around making periodic deployments to the client SCM.

Basically, we tag the revision that we want to deploy on the dev repos, then do an export from the dev repos and import it to the client repos into a subdirectory that has the same name as the tagged version on the dev repos. We then communicate to the client that " ver X.Y.Z is now available for deployment". After that, either they or we can log into a machine on their network (which has access to their own SCM), do a checkout of the tagged deployment, and run the deployment process. The rest is your usual Capistrano stuff.

The first step is to add a Rake task to support the tag/build part of the process:

task :tag_build do
  local_repository = "svn+ssh://local/repos"
  remote_repository = "svn+ssh://remote/repos"
    
  build = TagBuild.new(local_repository, remote_repository)
  build.tag(ENV['tag'], ENV['rev'])    
end
This allows the user to tag/build from the command line like this:
rake tag_build tag=x.y.z rev=123
In the above example, the "tag" argument is the tag name to apply to both the local and remote repositories. The "rev" argument is the revision number that you want to take from the local repository. The TagBuild class contains the logic to tag the local repository and then check that tagged version into the remote repository. I leave that code as an exercise for the reader.

The next step in this process is to add tag-based deployment capabilities to your Capistrano script. This first requires a modification of the "remote:deploy" Capistrano Rake task that passes a given tag to Capistrano. Here is an example of that modification (from lib/tasks/capistrano.rake):

task(:deploy) { cap :deploy, "-Stag=#{ENV['tag']}" }
This allows the user to invoke the deployment from the command-line like:
rake deploy tag=X.Y.Z

And last but not least, a modification to the deploy.rb file must be made to consider the new "tag" value.

  if symbol_defined? "tag"
    set :repository, "#{base_repository}/tags/#{tag}"
  else
    set :repository, "#{base_repository}/trunk"
  end