Skip to content
adzap edited this page Sep 28, 2010 · 8 revisions

ORM/ODM Support

The plugin is built on the ActiveModel validation system. This means it can allow multiple ORMs which support this system to use the plugin. This was not possible in previous versions.

To add full and proper support to an ORM, a few class method hooks need to be defined.

Hooks

Attribute Method Generation

The first hook is a for the attribute method generation performed by the ORM. To properly validate a date/time attribute, it’s raw value must be known. This is because an attribute value of nil may result from parsing an invalid value or the attribute is just nil. The plugin needs to be able to tell the difference so that it can add an error when invalid value has been parsed, but not when it is legitimately nil.

ActiveRecord, and perhaps other ORMs, does not store the raw value for timezone enabled attributes e.g. datetimes. To get around this, the plugin creates a write method for each validated attribute which stores the raw value and then calls super. In previous ActiveRecord versions this process was more complex because you could not just call super to resume the normal write method behaviour for the attribute.

In order to properly create this method, it must be done after the ORM has generated all the regular attribute methods. This method generation process can be triggered in various ways but each ORM seems to handle it differently. This is why a custom shim for each ORM must be created to hook into this process.

Here is the hook method for ActiveRecord:

def define_attribute_methods
  super
  define_timeliness_methods(true)
end

The super will allow ActiveRecord to do it’s normal method generation and then the plugin creates it methods for each validated attribute. The ‘true’ value passed to ‘define_timeliness_methods’ is indicate that the _before_type_cast methods should also be created for each validated attribute. This before_type_cast convention originally added to ActiveRecord, but may exist in other ORMs.

Attribute Type

The type for the attribute needs to be known when the plugin parser is being used. The method will be defined with the type passed to the parse method.

def timeliness_attribute_type(attr_name)
  columns_hash[attr_name.to_s].type
end

Timezone Handling

ActiveRecord uses the sophisticated timezone handling provided by ActiveSupport. The timezone handling is added for all datetime and timestamp columns by default unless explicitly excluded. To be able to accurately handle timezones for each attribute, the plugin must be able to query whether an attribute is timezone aware. ActiveRecord has a specific technique of doing this and will likely differ in other ORMs if supported at all.

Here is how the hook works for ActiveRecord:

def timeliness_attribute_timezone_aware?(attr_name)
  create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
end

An attribute name is passed to the plugin method timeliness_attribute_timezone_aware? and you are left to define how to discover the timezone awareness of that attribute as supported by the ORM.

Reload

Most ORM/ODMs supports a reload mechanism to flush changing and for a saved record and use the original values. If this is the case then you may need to hook into the reload method to clear the @attributes_cache hash. This is cleared automatically by ActiveRecord.

Example

Here is the full shim for ActiveRecord. You can see the two required modules for inclusion at the top.

module ValidatesTimeliness
  module ORM
    module ActiveRecord
      extend ActiveSupport::Concern

      module ClassMethods
        def define_attribute_methods
          super
          # Define write method and before_type_cast method
          define_timeliness_methods(true)
        end

        def timeliness_attribute_timezone_aware?(attr_name)
          attr_name = attr_name.to_s
          create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
        end

        def timeliness_attribute_type(attr_name)
          columns_hash[attr_name.to_s].type
        end
      end

      module InstanceMethods
        def reload(*args)
          _clear_timeliness_cache
          super
        end
      end

    end
  end
end

class ActiveRecord::Base
  include ValidatesTimeliness::AttributeMethods
  include ValidatesTimeliness::ORM::ActiveRecord
end

Custom Shims

If you create a custom shim for an ORM, please let me know and it is likely I can add it to the plugin to make things easier.

Clone this wiki locally