No Rails plugins for DRY “show” and “edit?”

Irksome Rails problem. It seems obvious to me that the “show” and “edit” views will often want to be updated together. You might add a column to a table, and want to add that column to both views. You might want to re-order the fields. Yet, as far as I can see, there is no plugin that makes display of the “edit” and display of the “show” actions occur in the same file.

It shouldn’t be too hard to implement, and I might implement it myself. See my question at StackOverflow.

Parsing huge XML exports from FileMaker with PHP

I have been working on a conversion of an old FileMaker system to a new PostgreSQL system, and for the export of the old data, I’ve been using the FileMaker XML export feature, and then converting the data to a set of CSV files with a PHP script.

The problem is, my export XML file is too huge for PHP to parse the XML in memory and then walk the DOM.

So instead, I’ve written a PHP class, FMXMLParser, which uses sequential parsing of the export file, not requiring all the data in memory. Instead, I write:

require_once("FMXMLReader.php");

$reader = FMXMLReader::open("some_filemaker_export.xml");

while($row=$reader->nextRow()) {
    // $row is an array of field names and values
}

This class uses the PHP XMLReader class. The XMLReader class is a wrapper around a SAX parser, which parses XML sequentially.

Download an early version.

Rails and indexing polymorphic relationships

I was noticing that the rails acts_as_auditable plugin defines an index on tha audits table as:
add_index :audits, [:auditable_id, :auditable_type], :name => 'auditable_index'

The fields auditable_id and auditable_type together represent the object which is changed, but I found the order of the indexing seemed odd to me, because you might want to find all audits of changes to a particular type. Say you are auditing the “Customer” and “Order” tables. Then an administrator might want to see all recent edits to Customer data, and this index doesn’t make that easy.
So, why does acts_as_audited do it this way?
I asked the question at StackOverflow, and did not get an answer, but an answer finally dawned on me. Rails plugins have to support a myriad of databases, and some databases don’t support indices defined on multiple fields. In that case, Rails actually will drop the other fields and only index on the first field. In that case, which index is going to be of more use:
add_index :audits, :auditable_id, :name => 'auditable_index'

or

add_index :audits, :auditable_type, :name => 'auditable_index'

The second index is much less useful, because you have only a finite number of values for auditable_type..

Narrative in relational databases

Suppose you have a database that tracks employees.

A person’s employment record can be thought of a narrative of events – changes that occur during a person’s employment. It’s something that we will likely want to add data to at a later date – say, if you decide to track new information.

That makes this sort of narrative history different from audit trails, which can be seen as recording a narrative about the data in your system. An audit trail is very useful, but you rarely want to edit past data in an audit trail. When you ask an audit trail for the value of a piece of data on a particular date, you are asking “What was recorded in the database on that date,” and you really don’t ever want to update that value.

On the other hand, for employment history, you might ask, “What was this person’s salary at the beginning of last year?” Now, if the data is not present for that date, or if the data is wrong in your stored employment history, you will want to change it.

I don’t know what the appropriate approach to this is, but I’m investigating some more. It seems like a natural area for a rails plugin. Something like acts_as_audited, but with better access to past data.

Rails scopes and DRY

The DRY principle is a good one, and Rails and Ruby have great tools which, when used properly, make repetition a rare thing.

However, I have a current piece of code which, unfortunately, seems to require repetition. Specifically, when using a scope to define an “active_on” scope of people, I write:

named_scope :active_on, lambda {|date|
  { :condition => ["start_date<= :date and end_date>:date", {:date => date}] }
}

But I also have to write a method active_on? which uses the same logic, but is written in Ruby rather than SQL:

def active_on?(date)
  start_date <= date and end_date>date
end

That seems unfortunate, but, as far as I can tell, in Rail 2.3.5, there’s no way around it (without making active_on? do a query, which would be very ugly.)

ActiveRecord conditions: problems with DateTime

[Using Rails 2.3.5 with PostgreSQL]

I had code like:
ChangeHistory.new :completed_at => datetime, :before => 10, :after => 20
and later find calls like:
ChangeHistory.find :all, :conditions => ["completed_at > ?", datetime]

The “new” call correctly inserts the DateTime in the database. The database stores the date in UTC.

The later query, however, compares the “completed_at” column to the datetime in the local time zone (as defined in your rails application.)

In my case, if I insert a time at 02:00:00 Eastern time, the data gets stored as 07:00:00 UTC.

However, the later query, with DateTime set to 02:00:00, compares the UTC time to 02:00:00, and so the just-created record matches the query.

A little poking turned up the class ActiveSupport::TimeWithZone, which is the class used by ActiveRecord to return data stored as date-times. Rather than using: DateTime.now, you can use Time.zone.now, for example.

Ruby Unit Tests: Don’t make this mistake…

I’ve written a lot of unit tests for my current Rails project, and I could not figure out why my tests were passing but the same logic failing when using the application.

It turned out that the problem was in my tests, where I had accidentally used assert rather than assert_equal. My tests looked like:

test "do something and check the result" do
   ... do something ...
   assert 199, result
   # meant:
   #   assert_equal 199, result
end

I could have avoided this error if I had added a message to the assert:

  assert 199, count, "Should include shipping"

This would have errored as too many parameters to the assert method.

This is one of the risks of a type-unsafe language with lots of optional parameters.