-
Notifications
You must be signed in to change notification settings - Fork 232
ORM 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.
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.
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
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.
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.
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
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.