Categories
Rails

DRYing up Actions with a page_errorhandler

When I am developing page actions, I quite often find myself approaching a standard get/post action in the same way. This is particularly because I want to validate a number of ActiveRecord models, only progressing if they are ALL valid, and showing all errors at once

Of course, that means that I need to have a standard way of doing this, and a standard way of reporting it. It also implies the use of transactions, so that all saves are rolled back on failure. I have

So to begin with, an (hypothetical) example of my approach is:


def edit_user
@user = User.find(session[:user_id])
@address = @user.address
if request.post?
begin
# note that I do not want to reference the objects in the
# method call - so that the changes are available to render
@user.transaction do
results = []
results << @user.save
results << @address.save
raise "Validation failed" if results.include?(false)
end
redirect_to :action => :show_user and return
rescue Exception
logger.warn{"Transaction terminated : #{e.message}"}
logger.warn{"@user : #{(@user.errors.full_messages.join('; ') rescue nil)}"}
logger.warn{"@address : #{(@address.errors.full_messages.join('; ') rescue nil)}"}
logger.debug{e.backtrace}
end
end
end

The first thing that is useful about this is that because all objects are saved together and the transaction is not terminated unless ONE of them is invalid, we will not redirect and the edit_user template will be rendered. Also, the validation errors are written to the log “just in case”

Also, note that I am using the log4r syntax of using braces instead of brackets. In Log4r, if the logger is not logging at the level that is specified, the code inside the braces will not be run. In the example above, if we are in production mode and we are not logging DEBUG, the e.backtrace method is actually not executed.

From here, lets DRY it up.

First, there’s those validation messages. There’s quite a lot of code here. I’ve approached this from 2 angles. First, in environment.rb, I put this code in


class ActiveRecord::Base
def full_messages
self.errors.full_messages.join('; ') rescue nil
end
end

and in application.rb

def validation_message(*objects)
str = "Validation error :"
objects.each{ |obj| str << "\n#{obj.class.name} => #{obj.full_messages}" }
str
end

This means that the validation message can now be handled by simply doing this in the rescue block:


logger.warn{validation_message(@user, @address)}

Next, because I use this structure regularly, I created a page_errorhandler method to contain the exception block that does this:


def page_errorhandler(*objects, &block)
begin
yield
rescue Exception => e
logger.warn{"Transaction terminated : #{e.message}"}
logger.warn{validation_message(*objects)}
logger.debug{e.backtrace}
end
end

Combining these with our method above:


def edit_user
@user = User.find(session[:user_id])
@address = @user.address
if request.post?
page_errorhandler(@user, @address) do
# note that I do not want to reference the objects in the
# method call - so that the changes are available to render
@user.transaction do
results = []
results << @user.save results << @address.save raise "Validation failed" if results.include?(false) end redirect_to :action => :show_user and return
end
end
end

Neatens things up nicely!

Categories
Rails

Master Pages for Rails (or, Heirarchial Layouts)

One of the things that I like about the .net web application setup is the concept of Master Pages. The most common usage of this is if you have a Home Page that has a slightly different layot from the rest of the site, but you want to share the base DIV structure and assets without having to copy them into another layout… Keep it DRY!

Although there is no “out of the box” solution for sharing common layout setups in Rails except for using Partials, there is in fact a way of replicating this functionality. First, you need to be familiar with the ActionView::Helpers::CaptureHelper class and the methods in it.

So, what you do is create a container rhtml layout that contains the common layout elements, such as the base HTML and div structure.

container.rhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<body>
<%= yield :layout -%>
</div>
</body>
</html>

From there, you can create as many other layouts that reference that container, such as one called application.rhtml:
<% @content_for_layout = capture do %>
<div id='a_new_div' />
<%= yield :layout %>
<% end %>
<%= render 'layouts/container', { 'content_for_layout' => @content_for_layout } %>

Note what happens there – the capture method is used to catch the rendering of this layout file. Then, instead of just allowing it to render, this output is injected into the container.rhtml file. In this way, we have chained the inner layout (application.rhtml) into the outer (container.rhtml) layout.

In our example, the “<div id=’a_new_div’ />” will be injected, along with the data from the view, into the container. There is nothing stopping you from doing this to another layer, although I doubt how often you’d need to do that!

From our example above, we could also easily create a home.rhtml file that had slightly different layout from our application.rhtml file and use that from our HomeController without concern – all of the pages would then render in the correct layout while giving us the benefit of a shared outer container!

I have used this in production, and it works a treat both in terms of performance and code maintainability.

Categories
jruby Rails

JRuby-OpenSSL and HTTP request timeouts (in ActiveSalesforce)

So, JRuby looks pretty cool, and I’ve got some code written that uses a weird BouncyCastle encryption implementation going in my rails app. Works well, the WAR deployment works (with goldspike) and everything’s going swimmingly.

Until, of course, I tried to integrate with Another Project that uses the ActiveSalesforce. Behind the scenes, ActiveSalesforce makes SSL encrypted connections to Salesforce API endpoints and performs operations. This was working fine until I started the app under JRuby. All of a sudden, the salesforce connections stopped working.

A bit of digging later, and I discovered that the JRuby-OpenSSL implementation is causing reads of the HTTP responses to stall. JRuby HTTP is fine, Ruby HTTPS is fine, but JRuby HTTPS is not. Boo.

It’s a bug, now. I hope it’s fixed soon!

Categories
log4r Rails

Integrating Log4r and Ruby on Rails

Aaaaaaaages ago, I wrote a message on the mailing list (before it moved to Google Groups!) about how to integrate Rails and Log4r. Since then a little bit has changed and that way may or may not entirely work any more. Since then, aaaaages ago Jason Rimmer asked me to update so that it’s all new and fresh, but I completely forgot in the move to the UK (very sorry Jason!). So here it is.

I’ve got a few outputters. One that acts like the default outputter, that writes “development.log” and so on. Then another that outputs to standard error for console lovin’ (in dev mode). Then another that uses a date file outputter to automatically roll over logs every day (for production mode), and finally an Email outputter that only runs in production and sends an email of the log for ERROR and FATAL log levels.

Log4r Rails configuration files

The first bit is the configuration YAML file, which is used to configure the loggers. Then there is logger.rb, which turns on and off the outputters as required. The final part is to include this logger.rb into the application configuration.

It is VERY IMPORTANT that you include the file before the call to the Rails::Initializer.run do block. This is because in this section of code the RAILS_DEFAULT_LOGGER is initialised, and if we don’t get in before that, we won’t get our logger injected into the Rails framework stack. So, configure it like this:

require File.join(File.dirname(__FILE__), 'boot')

require File.expand_path(File.dirname(__FILE__) + "/logger")

Rails::Initializer.run do |config|
...

Just drop the require line in there and it will load logger.rb, which loads log4r.yaml, and everything is up and going. You’ll see friendly [DEBUG] lines in your console and everthing! Of course, I prefer verbose logging on the console in development; you may not, customise by reading the log4r manual. Of course, if you expect your error mails to be delivered, change the SMTP server settings at the bottom of the yaml file.

Sorry for the delay Jason!

Categories
Rails

Rails composite primary key support

I am working with a legacy database. Usually, the built in rails stuff for table prefixing and primary key changes is enough. Sometimes, it’s not.

I had a problem with a join table that has Foreign Key names that are tablename_id, but the tables they reference is tablename_id as well. The has_and_belongs_to_many method DID NOT like it at all. Added to this is that there are attributes in the join table (is_primary) that I need to use. I considered (and discussed with the Java devs) the option of making the join table have an ID and making it a model, but there was potential for breakage and I like to tred very carefully around the existing app and making changes.

So, a bit of time on Google later, and I came across Nic Williams’ Composite Primary Key plugin. Oh man, it works a treat. I created the model for the join table, specified the primary keys, and went as normal!

Beautiful.

Categories
Rails software development

Rails ActionWebService SOAP error “No valid method call – missing method name”

For a Salesforce.com integration project, I need to create a SOAP server to accept messages from Salesforce. Seeing as there is no way directly import a wsdl into ActionWebService, I instead used wsdl2ruby from the soap4r distribution to generate server stubs. Then, I used ActionWebService to emulate the same service. I had problems with using the default layout, so I changed my structure to use a delegated structure:

class NotificationServiceController < ApplicationController
  web_service_dispatching_mode :delegated
  web_service_scaffold :invoke
  web_service :notifications, NotificationService.new
end
class NotificationServiceApi < ActionWebService::API::Base
  inflect_names false
  require_soap_action_header false
  api_method :notifications,
             :expects => [Notifications],
             :returns => [:bool]
end
class NotificationService < ActionWebService::Base
  web_service_api NotificationServiceApi
  def logger
    RAILS_DEFAULT_LOGGER
  end
  def notifications(organization_id, action_id, session_id, enterprise_url, partner_url, notification)
    my_object_id = notification.sObject.id
    ack = false
    begin
      ack = so_something(my_object_id)
    rescue Exception => e
      logger.error("Error processing payment: #{e.message}")
    end
    ack
  end
end

But STILL SOMETHING WAS WRONG. I was getting "No valid method call - missing method name" with the Salesforce outbound message queue reporting "org.xml.sax.SAXParseException: Content is not allowed in prolog." MMmmmmm helpful. The stack trace was showing that rails was trying to process the request as XMLRPC not SOAP, which was all wrong.

The stack trace looked something like this:

RuntimeError (No valid method call - missing method name!):
    /usr/lib/ruby/1.8/xmlrpc/parser.rb:476:in `parseMethodCall'
    /usr/lib/ruby/1.8/xmlrpc/marshal.rb:63:in `load_call'
    /usr/lib/ruby/1.8/xmlrpc/marshal.rb:32:in `load_call'
    /vendor/rails/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb:36:in `decode_request'
    /vendor/rails/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb:32:in `decode_action_pack_request'
    /vendor/rails/actionwebservice/lib/action_web_service/protocol/discovery.rb:20:in `discover_web_service_request'
    /vendor/rails/actionwebservice/lib/action_web_service/protocol/discovery.rb:18:in `discover_web_service_request'
    /vendor/rails/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb:49:in `dispatch_web_service_request'
    (eval):1:in `notifications'
..........

Then, I came across patch 7077 (indirectly via this and then this), so, using my new best friend Piston I checked out Rails 1.2.3 into vendor/rails, and patched with the diff in the change. It was not entirely smooth - the xmlrpc.rb file could not be patched, but I merged the change manually.

All done! Works!

One thing I did discover the hard way though is that you need to have the whole stack of definitions with the same naming scheme for this to work. That is, if you've got a NotificationServiceController, you need to ensure that you have a NotificationService and a NotificationServiceApi defined and in use - no other class names will work. No Reuse of API Definitions, which is a bit of a bugger.

Categories
hosting Rails

Capistrano, Mongrel, and Mongrel_cluster Redux

Over a year ago, I wrote a post about how to get the then-new Mongrel_cluster working with Capistrano. Since then, I have not had to touch my deployment config again.

2 days ago I needed to do a new deployment config and I thought I’d look at my config again. In reflection, it a bit dodgy, but at the time it was the best way! Honest! Also I note that in my original post, there’s broken links, and also that it is still far and away the most popular content on my site (direct links were almost 25% of the traffic!), so best to make it all new-like.

What do you do these days then?

  1. Get yourself mongrel and install the mongrel_cluster gem too:
    # sudo gem install mongrel mongrel_cluster –include-dependencies
  2. Go to the root of your Rails app
  3. Get a mongrel_cluster config:
    # mongrel_rails cluster::configure -e production \
    -p 8000 \
    -a 127.0.0.1 \
    -N 3 \
    -c /path/to/the/application’s/current
  4. Open up the /config/deploy.rb file and add:
    require ‘mongrel_cluster/recipes’
    set :mongrel_conf, “#{current_path}/config/mongrel_cluster.yml”

You’ll still need to add the @restart task something like this to ensure that the app comes up with the box:

@restart cd /path/to/the/application's/current && mongrel_rails cluster::start

Use cap cold_deploy to launch your app for the first time, and cap deploy to redeploy (cap deploy_with_migrations for your db updates too.)

Then all you need to do in configure your favourite proxy to serve the app from ports 80/443/whatever, and you’re good to go. I am using Apache 2.2, but perhaps soon I’ll have time to set up Swiftiply

Categories
Rails software development

DhtmlCalendar and Piston

Over the weekend I decided to try (again) to find a Rails plugin for a Dhtml Calendar. The previous one I tried relied on an Engine, and for some totally unreasonable errrrrrrrr reason, I don’t like that. So I came across this: http://dry.4thebusiness.com/info/dhtml_calendar. At first it was all sunshile, lollipops and rainbows, but I soon realised that the plugin did not like Firefox.

This was, of course, a right pain. A quick test in IE determined it worked fine. In Firefox, it popup worked, but when you select a date the select boxes did not get updated. I waded through the javascript that comes with the plugin, and it seemed ok, but it simply WAS NOT firing in Firefox.

After some Javascript mangulation (that’s my word but you can use it if you want) I figured out that it wasn’t picking up the HTML form. So, even though the documentation says:

“Note: :form_name is optional unless your form is named. If it is named then supply the name of the form.”

I included it anyway.

And it worked. Yay.

Also, in the past I have used svn:externals to include external plugin into my project, and at the most inopportune time the external site as either (and I have had both of these happen):

  1. The external site is inaccessible at deployment time, meaning that your site is offline, or
  2. The external site updates the codebase to suit a new Rails version that you have not upgraded to, meaning that your site is broken and once again, offline

After doing that once or twice, I gave up on svn:externals and just exported the remote source and checked it into my repository, which is a bit shit because it removes the link to the origin of the code. This time I used Piston.

Piston fixes this. It checks out the remote code as an svn export but it stores the synchronization information as svn properties so that it can later be updated, or locked, or even merge the remove changes wth your own changes. Spiffy!

Categories
Rails

warning: already initialized constant OPTIONS

Setting up my new computer now that I am settled in in London was turning out to be a right pain. I had ruby, rails, and postgresql installed, but my application was not starting.

I was getting the following error:

=> Booting Mongrel (use 'script/server webrick' to force WEBrick)
=> Rails application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with development environment...
Exiting
/opt/local/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/commands/servers/mongrel.rb:15: warning: already initialized constant OPTIONS
/opt/local/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/commands/servers/mongrel.rb:18: undefined method `options' for []:Array (NoMethodError)
        from /opt/local/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:32:in `gem_original_require'
        from /opt/local/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:32:in `require'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:342:in `new_constants_in'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require'
        from /opt/local/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/commands/server.rb:39
        from /opt/local/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
        from /opt/local/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:27:in `require'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:342:in `new_constants_in'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require'
        from script/server:3

and lots more similar ones. I tried uninstalling Mongrel, reactivating old versions of rails, and so on. Nothing worked. A fresh rails application started ok though Searching online revealed nothing either, until I came upon a post about a gem not working on win32, and the solution was to remove that gem.

Ahhhhh.

I was missing gems. I installed every one that I had on my lappy, and everthing was cheerful again. Strange that the error message (even under webrick) did not report the problem clearly. In any case, problem resolved.

Categories
Rails

ActiveRecord::RecordNotSaved – before_save problem

I am in the middle of an update to my PeopleHub application and I started getting a weird error in my tests (yay for tests): ActiveRecord::RecordNotSaved

Part of the update was changing my before_save code to only do an expensive recalculation when it was required, and after doing so, setting the flag that it used to false (so that next time it didn’t do the expensive recalc). The code looked like this:

def before_save
  if self.do_update
    if x
      write_attribute("the_field", the_field_data)
      self.do_update = false
    elsif y
    end
  end
end

(please excuse the formatting. looks like my css needs attention)

So the end result of this is that in certain cases, the update is never done again. However, note that I am setting self.do_update = false at the end of the block. For those who remember their Ruby fundamentals (d’oh) they will know that if that line is the last that is evaluated in the block, it will return FALSE. As the docs say:

If a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns false, all the later callbacks are cancelled.

Whoops. Thanks to Rick Olsen (http://threebit.net/mail-archive/rails/msg38644.html) for the help