mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-22 22:06:45 +00:00
change ORM attribute generation and extension mechanism
now using shim since the attribute matcher is not required for AM
This commit is contained in:
parent
d0080ebac4
commit
9ddd150b2f
13
README.rdoc
13
README.rdoc
@ -42,13 +42,18 @@ This creates configuration initializer and locale files. In the initializer, you
|
|||||||
|
|
||||||
ValidatesTimeliness.setup do |config|
|
ValidatesTimeliness.setup do |config|
|
||||||
|
|
||||||
# Add validation helpers to these classes
|
# Add plugin to supported ORMs (only :active_record for now)
|
||||||
# config.extend_classes = [ ActiveRecord::Base ]
|
# config.extend_orms = [ :active_record ]
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
By default the plugin extends ActiveRecord if present. If you are using one or more other ORMs, you need to add them to this config option array.
|
By default the plugin extends ActiveRecord if present. Currently ActiveRecord is the only ORM included for extension. If you wish to extend
|
||||||
As long as the ORM supports ActiveModel validations, it should work.
|
another ORM then look at the shim for ActiveRecord to see how to setup the hooks.
|
||||||
|
http://github.com/adzap/validates_timeliness/tree/master/lib/validates_timeliness/orms/active_record.rb
|
||||||
|
|
||||||
|
To extend other ORMs is pretty straight forward. It matter of hooking into a couple of methods, being the attribute method generation and
|
||||||
|
timezone handling of validated attributes. However, the plugin must support the ActiveModel validations system. If you extend an ORM
|
||||||
|
successfully, please send me a pull request to add the shim to the plugin or let me know where to find it.
|
||||||
|
|
||||||
|
|
||||||
== Usage:
|
== Usage:
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
ValidatesTimeliness.setup do |config|
|
ValidatesTimeliness.setup do |config|
|
||||||
# Add validation helpers to these classes
|
# Add plugin to supported ORMs (only :active_record for now)
|
||||||
# config.extend_classes = [ ActiveRecord::Base ]
|
# config.extend_orms = [ :active_record ]
|
||||||
#
|
#
|
||||||
# Set the dummy date part for a time type values.
|
# Set the dummy date part for a time type values.
|
||||||
# config.dummy_date_for_time_type = [ 2000, 1, 1 ]
|
# config.dummy_date_for_time_type = [ 2000, 1, 1 ]
|
||||||
|
|||||||
@ -15,9 +15,9 @@ require 'active_support/core_ext/date_time/zones'
|
|||||||
module ValidatesTimeliness
|
module ValidatesTimeliness
|
||||||
autoload :VERSION, 'validates_timeliness/version'
|
autoload :VERSION, 'validates_timeliness/version'
|
||||||
|
|
||||||
# Add validation helpers to these classes
|
# Add plugin to supported ORMs (only :active_record for now)
|
||||||
mattr_accessor :extend_classes
|
mattr_accessor :extend_orms
|
||||||
@@extend_classes = [ defined?(ActiveRecord) && ActiveRecord::Base ].compact
|
@@extend_orms = [ defined?(ActiveRecord) && :active_record ].compact
|
||||||
|
|
||||||
# Set the dummy date part for a time type values.
|
# Set the dummy date part for a time type values.
|
||||||
mattr_accessor :dummy_date_for_time_type
|
mattr_accessor :dummy_date_for_time_type
|
||||||
@ -37,10 +37,7 @@ module ValidatesTimeliness
|
|||||||
# Setup method for plugin configuration
|
# Setup method for plugin configuration
|
||||||
def self.setup
|
def self.setup
|
||||||
yield self
|
yield self
|
||||||
extend_classes.each {|klass|
|
extend_orms.each {|orm| require "validates_timeliness/orms/#{orm}" }
|
||||||
klass.send(:include, ValidatesTimeliness::HelperMethods)
|
|
||||||
klass.send(:include, ValidatesTimeliness::AttributeMethods)
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -2,28 +2,39 @@ module ValidatesTimeliness
|
|||||||
module AttributeMethods
|
module AttributeMethods
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
|
||||||
if attribute_method_matchers.any? {|m| m.suffix == "_before_type_cast" && m.prefix.blank? }
|
|
||||||
extend BeforeTypeCastMethods
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
|
||||||
|
def define_timeliness_methods(before_type_cast=false)
|
||||||
|
timeliness_validated_attributes.each do |attr_name, type|
|
||||||
|
define_timeliness_write_method(attr_name, type, timeliness_attribute_timezone_aware?(attr_name))
|
||||||
|
define_timeliness_before_type_cast_method(attr_name) if before_type_cast
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def define_method_attribute=(attr_name)
|
def define_timeliness_write_method(attr_name, type, timezone_aware)
|
||||||
if timeliness_validated_attributes.include?(attr_name)
|
method_body, line = <<-EOV, __LINE__ + 1
|
||||||
method_body, line = <<-EOV, __LINE__ + 1
|
def #{attr_name}=(value)
|
||||||
def #{attr_name}=(value)
|
@attributes_cache ||= {}
|
||||||
@attributes_cache ||= {}
|
@attributes_cache["_#{attr_name}_before_type_cast"] = value
|
||||||
@attributes_cache["_#{attr_name}_before_type_cast"] = value
|
super
|
||||||
super
|
end
|
||||||
end
|
EOV
|
||||||
EOV
|
class_eval(method_body, __FILE__, line)
|
||||||
class_eval(method_body, __FILE__, line)
|
end
|
||||||
end
|
|
||||||
super rescue(NoMethodError)
|
def define_timeliness_before_type_cast_method(attr_name)
|
||||||
|
method_body, line = <<-EOV, __LINE__ + 1
|
||||||
|
def #{attr_name}_before_type_cast
|
||||||
|
_timeliness_raw_value_for('#{attr_name}')
|
||||||
|
end
|
||||||
|
EOV
|
||||||
|
class_eval(method_body, __FILE__, line)
|
||||||
|
end
|
||||||
|
|
||||||
|
def timeliness_attribute_timezone_aware?(attr_name)
|
||||||
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@ -36,22 +47,5 @@ module ValidatesTimeliness
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
module BeforeTypeCastMethods
|
|
||||||
|
|
||||||
def define_method_attribute_before_type_cast(attr_name)
|
|
||||||
if timeliness_validated_attributes.include?(attr_name)
|
|
||||||
method_body, line = <<-EOV, __LINE__ + 1
|
|
||||||
def #{attr_name}_before_type_cast
|
|
||||||
_timeliness_raw_value_for('#{attr_name}')
|
|
||||||
end
|
|
||||||
EOV
|
|
||||||
class_eval(method_body, __FILE__, line)
|
|
||||||
else
|
|
||||||
super rescue(NoMethodError)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -5,30 +5,37 @@ module ValidatesTimeliness
|
|||||||
included do
|
included do
|
||||||
include ValidationMethods
|
include ValidationMethods
|
||||||
extend ValidationMethods
|
extend ValidationMethods
|
||||||
|
class_inheritable_accessor :timeliness_validated_attributes
|
||||||
|
self.timeliness_validated_attributes = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
module ValidationMethods
|
module ValidationMethods
|
||||||
|
def validates_timeliness_of(*attr_names)
|
||||||
|
options = _merge_attributes(attr_names)
|
||||||
|
attributes = options[:attributes].inject({}) {|validated, attr_name|
|
||||||
|
attr_name = attr_name.to_s
|
||||||
|
validated[attr_name] = options[:type]
|
||||||
|
validated
|
||||||
|
}
|
||||||
|
timeliness_validated_attributes.update(attributes)
|
||||||
|
validates_with Validator, options
|
||||||
|
end
|
||||||
|
|
||||||
def validates_date(*attr_names)
|
def validates_date(*attr_names)
|
||||||
validates_with Validator, _merge_attributes(attr_names).merge(:type => :date)
|
options = attr_names.extract_options!
|
||||||
|
validates_timeliness_of *(attr_names << options.merge(:type => :date))
|
||||||
end
|
end
|
||||||
|
|
||||||
def validates_time(*attr_names)
|
def validates_time(*attr_names)
|
||||||
validates_with Validator, _merge_attributes(attr_names).merge(:type => :time)
|
options = attr_names.extract_options!
|
||||||
|
validates_timeliness_of *(attr_names << options.merge(:type => :time))
|
||||||
end
|
end
|
||||||
|
|
||||||
def validates_datetime(*attr_names)
|
def validates_datetime(*attr_names)
|
||||||
validates_with Validator, _merge_attributes(attr_names).merge(:type => :datetime)
|
options = attr_names.extract_options!
|
||||||
|
validates_timeliness_of *(attr_names << options.merge(:type => :datetime))
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
def timeliness_validated_attributes
|
|
||||||
@timeliness_validated_attributes ||= begin
|
|
||||||
_validators.map do |attr_name, validators|
|
|
||||||
attr_name.to_s if validators.any? {|v| v.is_a?(ValidatesTimeliness::Validator) }
|
|
||||||
end.compact
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
14
lib/validates_timeliness/orms/active_record.rb
Normal file
14
lib/validates_timeliness/orms/active_record.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
class ActiveRecord::Base
|
||||||
|
include ValidatesTimeliness::HelperMethods
|
||||||
|
include ValidatesTimeliness::AttributeMethods
|
||||||
|
|
||||||
|
def self.define_attribute_methods
|
||||||
|
super
|
||||||
|
# Define write method and before_type_cast method
|
||||||
|
define_timeliness_methods(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.timeliness_attribute_timezone_aware?(attr_name)
|
||||||
|
create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -18,9 +18,13 @@ module ValidatesTimeliness
|
|||||||
:timeliness
|
:timeliness
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def setup(klass)
|
||||||
|
@klass = klass
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(options)
|
def initialize(options)
|
||||||
@allow_nil, @allow_blank = options.delete(:allow_nil), options.delete(:allow_blank)
|
|
||||||
@type = options.delete(:type) || :datetime
|
@type = options.delete(:type) || :datetime
|
||||||
|
@allow_nil, @allow_blank = options.delete(:allow_nil), options.delete(:allow_blank)
|
||||||
@restrictions_to_check = RESTRICTIONS.keys & options.keys
|
@restrictions_to_check = RESTRICTIONS.keys & options.keys
|
||||||
|
|
||||||
if range = options.delete(:between)
|
if range = options.delete(:between)
|
||||||
|
|||||||
@ -13,9 +13,10 @@ require 'rspec_tag_matchers'
|
|||||||
require 'model_helpers'
|
require 'model_helpers'
|
||||||
|
|
||||||
require 'validates_timeliness'
|
require 'validates_timeliness'
|
||||||
|
require 'test_model'
|
||||||
|
|
||||||
ValidatesTimeliness.setup do |c|
|
ValidatesTimeliness.setup do |c|
|
||||||
c.extend_classes = [ ActiveModel::Validations, ActiveRecord::Base ]
|
c.extend_orms = [ :active_record ]
|
||||||
c.enable_date_time_select_extension!
|
c.enable_date_time_select_extension!
|
||||||
c.enable_multiparameter_extension!
|
c.enable_multiparameter_extension!
|
||||||
end
|
end
|
||||||
@ -25,22 +26,38 @@ Time.zone = 'Australia/Melbourne'
|
|||||||
LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/../lib/generators/validates_timeliness/templates/en.yml')
|
LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/../lib/generators/validates_timeliness/templates/en.yml')
|
||||||
I18n.load_path.unshift(LOCALE_PATH)
|
I18n.load_path.unshift(LOCALE_PATH)
|
||||||
|
|
||||||
class Person
|
# Extend TestModel as you would another ORM/ODM module
|
||||||
include ActiveModel::AttributeMethods
|
module TestModel
|
||||||
include ActiveModel::Validations
|
include ValidatesTimeliness::HelperMethods
|
||||||
extend ActiveModel::Translation
|
include ValidatesTimeliness::AttributeMethods
|
||||||
|
|
||||||
attr_accessor :birth_date, :birth_time, :birth_datetime
|
def self.included(base)
|
||||||
attr_accessor :attributes
|
base.extend HookMethods
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(attributes = {})
|
module HookMethods
|
||||||
@attributes = {}
|
# Hook method for attribute method generation
|
||||||
attributes.each do |key, value|
|
def define_attribute_methods(attr_names)
|
||||||
send "#{key}=", value
|
super
|
||||||
|
define_timeliness_methods
|
||||||
|
end
|
||||||
|
|
||||||
|
# Hook into native time zone handling check, if any
|
||||||
|
def timeliness_attribute_timezone_aware?(attr_name)
|
||||||
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class Person
|
||||||
|
include TestModel
|
||||||
|
self.model_attributes = :birth_date, :birth_time, :birth_datetime
|
||||||
|
validates_date :birth_date
|
||||||
|
validates_time :birth_time
|
||||||
|
validates_datetime :birth_datetime
|
||||||
|
define_attribute_methods model_attributes
|
||||||
|
end
|
||||||
|
|
||||||
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
|
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
|
||||||
ActiveRecord::Migration.verbose = false
|
ActiveRecord::Migration.verbose = false
|
||||||
ActiveRecord::Schema.define(:version => 1) do
|
ActiveRecord::Schema.define(:version => 1) do
|
||||||
@ -54,6 +71,10 @@ ActiveRecord::Schema.define(:version => 1) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
class Employee < ActiveRecord::Base
|
class Employee < ActiveRecord::Base
|
||||||
|
validates_date :birth_date
|
||||||
|
validates_time :birth_time
|
||||||
|
validates_datetime :birth_datetime
|
||||||
|
define_attribute_methods
|
||||||
end
|
end
|
||||||
|
|
||||||
Rspec.configure do |c|
|
Rspec.configure do |c|
|
||||||
@ -61,8 +82,10 @@ Rspec.configure do |c|
|
|||||||
c.include(RspecTagMatchers)
|
c.include(RspecTagMatchers)
|
||||||
c.before do
|
c.before do
|
||||||
Person.reset_callbacks(:validate)
|
Person.reset_callbacks(:validate)
|
||||||
|
Person.timeliness_validated_attributes = {}
|
||||||
Person._validators.clear
|
Person._validators.clear
|
||||||
Employee.reset_callbacks(:validate)
|
Employee.reset_callbacks(:validate)
|
||||||
|
Employee.timeliness_validated_attributes = {}
|
||||||
Employee._validators.clear
|
Employee._validators.clear
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
56
spec/test_model.rb
Normal file
56
spec/test_model.rb
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
module TestModel
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
extend ActiveModel::Translation
|
||||||
|
include ActiveModel::Validations
|
||||||
|
include ActiveModel::AttributeMethods
|
||||||
|
include DynamicMethods
|
||||||
|
|
||||||
|
attribute_method_suffix ""
|
||||||
|
attribute_method_suffix "="
|
||||||
|
cattr_accessor :model_attributes
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def define_method_attribute=(attr_name)
|
||||||
|
generated_attribute_methods.module_eval("def #{attr_name}=(new_value); @attributes['#{attr_name}']=new_value ; end", __FILE__, __LINE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def define_method_attribute(attr_name)
|
||||||
|
generated_attribute_methods.module_eval("def #{attr_name}; @attributes['#{attr_name}']; end", __FILE__, __LINE__)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module DynamicMethods
|
||||||
|
def method_missing(method_id, *args, &block)
|
||||||
|
if !self.class.attribute_methods_generated?
|
||||||
|
self.class.define_attribute_methods self.class.model_attributes.map(&:to_s)
|
||||||
|
method_name = method_id.to_s
|
||||||
|
send(method_id, *args, &block)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(attributes = nil)
|
||||||
|
@attributes = self.class.model_attributes.inject({}) do |hash, column|
|
||||||
|
hash[column.to_s] = nil
|
||||||
|
hash
|
||||||
|
end
|
||||||
|
self.attributes = attributes unless attributes.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def attributes
|
||||||
|
@attributes.keys
|
||||||
|
end
|
||||||
|
|
||||||
|
def attributes=(new_attributes={})
|
||||||
|
new_attributes.each do |key, value|
|
||||||
|
send "#{key}=", value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
@ -1,20 +1,18 @@
|
|||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe ValidatesTimeliness::AttributeMethods do
|
describe ValidatesTimeliness::AttributeMethods do
|
||||||
before do
|
|
||||||
Employee.validates_datetime :birth_datetime
|
|
||||||
Employee.define_attribute_methods
|
|
||||||
Person.validates_datetime :birth_datetime
|
|
||||||
Person.define_attribute_methods [:birth_datetime]
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should define _timeliness_raw_value_for instance method' do
|
it 'should define _timeliness_raw_value_for instance method' do
|
||||||
Person.instance_methods.should include('_timeliness_raw_value_for')
|
Person.instance_methods.should include('_timeliness_raw_value_for')
|
||||||
end
|
end
|
||||||
|
|
||||||
context "attribute write method" do
|
context "attribute write method" do
|
||||||
|
class EmployeeCopy < ActiveRecord::Base
|
||||||
|
set_table_name 'employees'
|
||||||
|
validates_datetime :birth_datetime
|
||||||
|
end
|
||||||
|
|
||||||
it 'should cache attribute raw value' do
|
it 'should cache attribute raw value' do
|
||||||
r = Employee.new
|
r = EmployeeCopy.new
|
||||||
r.birth_datetime = date_string = '2010-01-01'
|
r.birth_datetime = date_string = '2010-01-01'
|
||||||
r._timeliness_raw_value_for(:birth_datetime).should == date_string
|
r._timeliness_raw_value_for(:birth_datetime).should == date_string
|
||||||
end
|
end
|
||||||
|
|||||||
@ -22,7 +22,7 @@ describe ValidatesTimeliness::HelperMethods do
|
|||||||
describe ".timeliness_validated_attributes" do
|
describe ".timeliness_validated_attributes" do
|
||||||
it 'should return attributes validated with plugin validator' do
|
it 'should return attributes validated with plugin validator' do
|
||||||
Person.validates_date :birth_date
|
Person.validates_date :birth_date
|
||||||
Person.timeliness_validated_attributes.should == ["birth_date"]
|
Person.timeliness_validated_attributes.should == {"birth_date" => :date}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user