Overloading ActiveRecord methods

I’m currently working on a project management tool. The way the company works is with a project being either a group of jobs or a single job. So the first job could be X0001. To this you can then add sub-jobs as X0001A, X0001B and so on. The project X0001 is then a container holding the original job and information about all its sub-jobs.

It seemed to make sense to me to use the same database for both projects and jobs, and to have the Project class inherit from the Job class. This would let me use the same tools to analyse project figures at either the job or project level. So project.profit would give me the sum of the profit for the core job and all its sub-jobs. Whereas job.profit would give me the profit for just the one job.

There are probably better ways of doing this but this is the solution I came up with.

I first created the Job class as a standard Rails model. The required custom stuff was then put into the Project class. Here is core of the Project class definition:

class Project < Job

  #Used to check that a job number is of the correct pattern
  PROJECT_NUMBER_PATTERN = /^[A-Za-z]{1,2}[0-9]{4,}$/  

  # Replaced standard find method with one that will exclude sub-jobs from and find operation.
  def self.find(*args)
    options = modify_args_conditions_options(args.extract_options!)
    do_normal_find_things(args, options)
  end

  # Replace standard calculate method with one that will exclude results from sub-jobs.
  # For example if there are three jobs: 'J0001', 'J0002', and 'J0002A'
  # Job.count         ---> 3
  # Project.count     ---> 2
  # as 'J0002A' is a sub-job.
  def self.calculate(operation, column_name, options = {})
    options = modify_args_conditions_options(options)
    do_normal_calculate_things(operation, column_name, options)
  end


  private
  def self.do_normal_find_things(args, options)
    validate_find_options(options)
    set_readonly_option!(options)
    case args.first
      when :first then find_initial(options)
      when :last  then find_last(options)
      when :all   then find_every(options)
    else
      find_from_ids(args, options)
    end
  end

  def self.do_normal_calculate_things(operation, column_name, options)
    validate_calculation_options(operation, options)
    column_name     = options[:select] if options[:select]
    column_name     = '*' if column_name == :all
    column          = column_for column_name
    catch :invalid_query do
      if options[:group]
        return execute_grouped_calculation(operation, column_name, column, options)
      else
        return execute_simple_calculation(operation, column_name, column, options)
      end
    end
    0
  end

  def self.modify_args_conditions_options(options)
    projects_only_condition = "number REGEXP '#{PROJECT_NUMBER_PATTERN.source}'"
    if options[:conditions].kind_of? Array
      options[:conditions][0] << " AND #{projects_only_condition}"
    elsif options[:conditions].kind_of? String
      options[:conditions] << " AND #{projects_only_condition}"
    else
      options[:conditions] = projects_only_condition
    end
    return options
  end

end

So what is going on here?

First I dug into the ActiveRecord find method and found that there was a place within its initial definition where I could put in a bit of code that would modify the options passed into the underlying methods. The modification was to add a clause to conditions so that only jobs with numbers that match the pattern prescribed for a Project, would be returned. A project number started with one or two letters and was then followed by numbers. A sub-job added trailing letters to the project number.

I first created a find class method for Project that exactly matched the standard ActiveRecord::Base.find code, and then added the additional code.

Once I'd done that I realised that I could separate out the bits of code I'd added (modify_args_conditions_options) from the unaltered original code (do_normal_find_things). This had two advantages. First it made maintenance easier as I knew which code was original and should be left alone, and which was custom code I'd added. It also meant that I could easily use my custom code elsewhere.....

This seemed to work fine at first for both find and paginate tasks (as will_paginate (mislav_will_paginate) relies on find - so modify how find works, and you also modify how will_paginate works). However, then I noticed that the number of pages was wrong - there were too many. When I investigated this problem, I realised I also needed to modify how the ActiveRecord::Calculations.count method behaved.

Drilling into ActiveRecord's count method, I realised that the place to modify the behaviour was to alter the way ActiveRecord::Calculations.calculate works. This would have the added benefit of also altering other calculation methods like average. It was simple for me to insert the modify_args_conditions_options method into calculate and then call the original methods to carry on the rest of the task.

Hey presto, I've got what I want. I can now modify Project methods as I need - adding new ones, leaving the Job defined ones and overloading Job methods with Project specific version as needed.

So my next job is to decide how to deal with common tasks. For example lets return to profit. Do I just define a new Project profit method, or redefine the Job profit method so it will handle figures coming from either a single job or a group of jobs. Of the two the latter will probably be the better.

To get this to work I probably need to redefine again how find and calculate work so that instead of returning the results for the primary job, they return the summarised figures for the primary job and all its sub-jobs. The key thing being that, now I know where I can hack into find and calculate to modify their behaviours, making this modification should be fairly straight forward.

Posted in Ruby | Comments Off on Overloading ActiveRecord methods

Run off my feet and then time to reflect

I had a very busy weekend two weeks ago. Nine call-outs in two days. It was too much really, and a lot of it ambulance chasing. I took a few days off after that and had a rest. Fortunately since then things have been reasonably quiet.

Quiet for 2009 anyway. This time last year I was having spells of two or three weeks without a call-out. I hated that. Long periods waiting for calls that never came, was hard.

Quiet this year is two calls at the weekend and one of them just around the corner from me. Neither of the call-outs required much intervention from me. I haven’t had my oxygen out for more than a week.

I had a conversation last night with some fellow first aiders, and the subject of heart attacks came up. In particular, the frequency that we attend to patients with serious heart ailments that do not have classic chest pain symptoms. In fact, I think I’d go as far as to say that I attend as many without chest pain as those with.

For me, the combination of breathlessness and an irregular heartbeat has been a more common symptom of an underlying heart problem than a patient complaining of chest pain.

Perhaps more importantly, this is a good example that good first aid is more than just matching a set of known symptoms to a single diagnosis. You cannot just go up to a patient and diagnose heart problems because they have a certain type of chest pain. And crucially it demonstrates that you cannot rule out a condition purely on the absence of a single typical symptom.

Posted in Community First Responder | Comments Off on Run off my feet and then time to reflect

Deleting Session and Cookie entries

I’ve had a problem with clearing a users session and cookie information if they failed log on. In the past I’ve simply used:

  def clear_session
    session[:user_id] = ""
    session[:user_hash] = ""
    cookies[:user_hash] = ""
  end

But this often fails to clear the setting when I tested in Firefox. I tried to find an option to delete a session or cookie entry but could not get either session.delete(:name) or session[:name].delete (which I found references to) to work.

So I’ve used a pragmatic approach to the problem. This works:

  def clear_session
    session[:user_id] = "dead"
    session[:user_hash] = "dead"
    cookies[:user_hash] = { :value => "dead", :expires => 1.minute.ago }
  end

It’s not pretty, but it works. It seems that at least for Firefox, you need to pass something to the browser cookie (session after all is a glorified cookie) to over-write it. Passing it nothing can leave the old entry there.

Of course you need to pass it something that your application will find invalid as an entry for that value. For example User.find(’dead’) will return nil.

Posted in Ruby | Comments Off on Deleting Session and Cookie entries

MS SQL server connection

I noted the couple of steps I need to connect to a Microsoft SQL server in my Rails Tiddly Wiki under “MicrosoftSQL”. I’ve need to fire up a Rails development system on another PC and have found that the gems have moved on, and these instructions are now out of date (as are most of the instructions I found via a google search). In particular, Ruby-DBI no longer includes ADO.rb.

I was able to get my system working – that is, a connection to a SQL 2005 server from a Rails application created with a previous SQL server set up and set up on an XP Pro PC – by doing the following:

  gem install activerecord-sqlserver-adapter
  gem install rails-sqlserver-2000-2005-adapter -s http://gems.github.com
  gem install dbd-odbc

The first step is the same as previously. The current gem now installs a Ruby-DBI gem – but the version without ADO.rb (dbi-0.4.1) – as well as the activerecord-sqlserver-adapter gem.

The other two steps were taken from the Rails Wiki. To be honest I’m not sure they are needed.

I think the key step is this next one:

I then went to the gems directory (C:\ruby\lib\ruby\gems\1.8\gems) and deleted dbi-0.4.1 directory, and replaced it with dbi-0.2.2 which I downloaded from RubyForge. My system then worked correctly.

I’ve tried gem uninstall to remove dbi-0.4.1., but this doesn’t work. activerecord-sqlserver-adapter sees it as a dependant service and won’t work if dbi is uninstalled via gem uninstall. Deleting the folder works.

Posted in Ruby | Comments Off on MS SQL server connection

action_name_is?

I love it when a very small bit of code makes my life easier and my code simpler. Today I’ve defined a new helper method for one of my application and its done just that. This is the code

def action_name_is?(*actions)
  actions.include?(controller.action_name)
end

So why is that so useful?

I am a great believer in DRY (Don’t Repeat Yourself) coding which Ruby and Rails make very easy. One way to achieve this is to use partials quite a lot, which I do. I often find that there are substantial section of code that can be split out into a partial and then used in a number of views.

Quite often in the middle of this partial code, there are some elements that are view specific. Rather than leave those elements out of the partial or letting them be defined in the controller via instance variables, I sometimes prefer to have modifiers in the partials that alter the code depending on the action name.

A common example is a series of navigation links. The containing block and most of the links will probably need to be the same across views, but some links may be action specific.

In the past I’ve used code like this:

<% if controller.action_name == 'list' %>
  <p><%= link_to('Reports', 
               :controller => 'reports', 
               :view => 'list' -%></p>
<% end %>

This can be neatened by using content_tag to:

<%= content_tag("p", 
         link_to('Reports', 
           :controller => 'reports', 
           :view => 'list'
         ) if controller.action_name == 'list' %>

But even then things can get a little complicated if you want an action to do something in a small number of views. action_name_is? makes this simpler.

<%= content_tag("p", 
         link_to('Reports', 
            :controller => 'reports', 
            :view => 'list'
         ) if action_name_is?('list') %>

Now if you want the link to appear in both the list and show views, its simply a case of passing ‘show’ to the method as well as ‘list’:

<%= content_tag("p", 
          link_to('Reports', 
             :controller => 'reports', 
             :view => 'list'
          ) if action_name_is?('list', 'show') %>

And of course there is nothing to stop you using it negatively. That is, to use it with ‘unless’ to define which views the link shouldn’t appear in:

<%= content_tag("p", 
          link_to('Reports', 
             :controller => 'reports', 
             :view => 'list'
          ) unless action_name_is?('list', 'show') %>
Posted in Ruby | Comments Off on action_name_is?

Ambulance chasing

A relatively busy weekend: five call outs. However, with all of them the ambulance crew got there before me. For most of the calls it was just a case of going in and checking if the crew needed any assistance, which mostly they did not.

With one I was told the crew were 15 minutes away to a call in an out-lying village. However they were under blue lights, and I had to get through Saturday morning traffic without breaking any road traffic laws. The ambulance crew got there before me, and did not need my assistance.

However, on two calls the crews did ask for my assistance.

With one I’d heard the ambulance’s siren as it passed the front of my house whilst I was just getting into my car at the back of the house. The thought crossed my mind that I did not have to attend, but as I could not tell if they were going to the same call as I was, I carried on. That was the right decision as, although I got there after the ambulance, the crew did ask for my assistance as the patient was in a bad way.

On the other, the crew was glad to have someone to help with recording some of their stats, while they got on with the actual direct patient care.

The truth is, when a CFR is called out there is always a good chance that an ambulance or fast response car will get there first. I regularly find myself stopping to let a crew go past on their way to the same call I am going to. However, you cannot assume that you will not still be needed.

  • The crew could be going to a different call near by
  • The crew could be involved in a road traffic accident or break down
  • The crew could be diverted to a more urgent call
  • The patient may be in a very serious condition and an extra pair of hands would be welcome
  • The CFR’s local knowledge may help the crew locate a difficult to find incident

So there is always a bit of ambulance chasing involved in CFR work, but you have to do it. It’s part of the job.

Posted in Community First Responder | Comments Off on Ambulance chasing

Monitoring 2003 server – new project

I’ve been thinking about creating a Rails app to help me monitor my main Windows 2003 server. Today, one of the logical drives on the server got close to running out of space, so I realised it was time to get that monitoring in place.

One of the things that had delayed starting this project was deciding on a good way to gather the data. The solution was simple – use 2003’s in built performance monitor.

A little play with the performance monitor showed me that it was easy to get the data into a CSV file, but I could not get it to send the data directly to a SQL database. However, a little googling got me to a blog by one Hilary Cotter. That gave me the key step I needed: use relog to transfer the csv file up to the SQL server. I am still not sure why this works but writing directly to SQL does not.

I had a little fiddle with ODBC permissions, but finally got them to work using standard logins as Hilary suggests.

The bit I was worried about was configuring the tables the data would go into. However, I should not have worried as the process generates the tables automatically.

The performance counter properties allows a process to be triggered after the counter stops, so it was just a case of putting the relog command into a batch file and pointing the end process at that.

I’ve currently set the counter to close after running for 20 minutes, and scheduled a task to:

logman start counter_process_name

at midday every day. Strangely you cannot use the performance tool’s own scheduling to configure regular event runs, and that is why I’d had to schedule a logman command to start the process at a given time each day.

In a few days I should be able to assess if that’s giving me enough of a snapshot of data. With the data being regularly added to the database, it should be a fairly simple task to built a Rails app to display the data.
——–
An update: as of October 2009 this project hasn’t moved on. I may get back to it, but at the moment it looks like it will be one of those good ideas that doesn’t get acted upon. I have used the monitor data and having that has proved useful. Just at the moment purely as a manual tool with no automated monitoring.

Posted in Ruby | Comments Off on Monitoring 2003 server – new project

Ignore files and folders via TortoiseSVN

Why ignore?
Ignoring a file or folder from the Subversion system can reduce a lot of complexity from the repository. For example, if you edit your code via NetBeans, you probably don’t want to version control the .nbproject folder it puts into the root of your application. Also you probably don’t want to version logs (that caused me a big headache on my first Rails application as I found I was version controlling a very large (gig!) development.log file after I made a looping error)

Ignoring new items
It is very easy to ignore new items using TortoiseSVN. Simply right click on the object (file or folder) and select:

TortoiseSVN > 'Add to ignore list'

and select the object name presented. Job done.

Editing the result
You can edit the result by right clicking on the object’s containing folder and selecting

TortoiseSVN > Properties

You should see a line in the properties showing the ignore statement that has been added. Click on the Edit button to have a closer look:

Details of SVN properties showing an ignore example

Details of SVN properties showing an ignore example


Note that each pattern being ignored has its own line. If you wanted to add the ‘log’ folder, you could just add a new line with the word log in it. You can also use the * character to add filed cards. For example, if you add

svn:ignore *

to the log folder, the folder itself will be versioned, but not its contents.

Where to ignore
I’ve found it is best to ignore at the level of the containing folder. So if you had a file upload system that stored files in public/file_upload and you wanted to ignore that folder: the best policy is to add an

svn:ignore file_upload

to the public folder and NOT try to

svn:ignore public/file_upload

at the application root.

Recursive
Also be very careful of your use of the “Apply property recursively” option. Selecting this option will over-write the ignore list in all sub-folders so use with caution. In most cases you should not select this option.

Ignoring object already in the repository
You may well find that you have to retrospectively ignore an object because the object is versioned before you realise that it would be better to ignore it. I didn’t find it immediately obvious how to do this.

TortoiseSVN does not allow you to use the methods described above to ignore an object that is already in repository. You have to remove the object from the repository first. Do not use the delete option in the TortoiseSVN right click menu as this will remove the object from the repository AND delete the object itself. Instead use the Repository browser to delete the object from the repository directly.

ToirtoiseSVN > Repo-Browser

Then within the Repository browser right click on the object and select Delete.

Once the object is removed from the repository, the ignore options become available in the TortoiseSVN right click menu.

Posted in Ruby | Comments Off on Ignore files and folders via TortoiseSVN

Hearts are aflutter

One call out today (so far). Another lady with irregular heart beat. It seems I’m having a run of call outs to people with suspected heart attacks.

It is strange how call outs seem to come in batches. A week or two ago it was all broken hips and legs. The pattern is definitely random which means there are plenty of clusters and hiatuses. It keeps me on my toes.

Posted in Community First Responder | Comments Off on Hearts are aflutter

Gathering SQL data with TempObject

Another place I use TempObject is to organise the data created from a custom SQL statement. I use custom SQL statements when I need to gather data from three or more tables or do complicated JOIN or CASE statements. It is usually much quicker to combine data using SQL than it is to use Ruby.

Here is an example of how I can use TempObject to gather SQL data:

class Ball < ActiveRecord
  def get_all_using_sql
    sql = <<EOF
SELECT *
FROM balls
EOF
  TempObject.create_from_array_of_hashes(
     connection.select_all(sql) )
  end
end

select_all returns an array of hashes. TempObject.create_from_array_of_hashes converts the array of hashed into an array of TempObjects. The code to output that code is then of the same format as that used to display the data returned by a ActiveRecord find call.

So you can use the same code to display data gathered by Ball.get_all_using_sql as you would use on the data gathered by Ball.find(:all).

So this will work for both:

<table>
  <tr>
    <th>Size</th>
    <th>Colour</th>
  </tr>
<% for ball in @balls %>
  <tr>
    <td><%= ball.size -%></td>
    <td><%= ball.colour -%></td>
  </tr>
<% end %>
</table>

The array returned contains instances of TempObject and not Ball. So Ball instance methods won’t be available. This means that often find_by_sql is a better option. The TempObject technique is an additional way of gathering data and not a replacement for find_by_sql.

Also it make little sense to use this technique to replace ActiveRecord’s find. I would never actually use the example used above as Ball.find(:all) is so much easier to use.

However, when creating reports, the item names often don’t correspond to normal attributes. For example, if you wanted to list all balls used by month you may actually want ball.january, ball.february etc. To do that is often much faster using SQL than Ruby, and then the TempObject technique becomes very useful.

I also find myself wanting to gather data for reports and that data doesn’t easily correspond to a single model class. I could create a new class just for that report, but it is simpler just to use TempObject and use a custom SQL statement to populate each object.

Posted in Ruby | Comments Off on Gathering SQL data with TempObject