validates_timeliness 3.0.8 → 3.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG.rdoc CHANGED
@@ -1,5 +1,9 @@
1
+ = 3.0.9 [2012-03-26]
2
+ * ActiveRecord 3.1+ suport
3
+ * Fixes for multiparameter extension with empty date values (thanks @mogox, @Sharagoz)
4
+
1
5
  = 3.0.8 [2011-12-24]
2
- * Remove deprecated InstanceMethods module when using AS::Concern
6
+ * Remove deprecated InstanceMethods module when using AS::Concern (carlosantoniodasilva)
3
7
  * Update Mongoid shim for v2.3 compatability.
4
8
 
5
9
  = 3.0.7 [2011-09-21]
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
3
 
4
+ Bundler.setup
5
+
4
6
  require 'rake/rdoctask'
5
7
  require 'rspec/core/rake_task'
6
8
 
@@ -20,24 +20,34 @@ module ValidatesTimeliness
20
20
  :datetime
21
21
  end
22
22
 
23
+ def undefine_attribute_methods
24
+ super
25
+ undefine_timeliness_attribute_methods
26
+ end
27
+
23
28
  protected
24
29
 
25
30
  def define_timeliness_methods(before_type_cast=false)
26
31
  return if timeliness_validated_attributes.blank?
27
32
  timeliness_validated_attributes.each do |attr_name|
28
- define_timeliness_write_method(attr_name)
29
- define_timeliness_before_type_cast_method(attr_name) if before_type_cast
33
+ define_attribute_timeliness_methods(attr_name, before_type_cast)
30
34
  end
31
35
  end
32
36
 
37
+ def define_attribute_timeliness_methods(attr_name, before_type_cast=false)
38
+ define_timeliness_write_method(attr_name)
39
+ define_timeliness_before_type_cast_method(attr_name) if before_type_cast
40
+ end
41
+
33
42
  def define_timeliness_write_method(attr_name)
34
43
  method_body, line = <<-EOV, __LINE__ + 1
35
44
  def #{attr_name}=(value)
45
+ original_value = value
36
46
  @timeliness_cache ||= {}
37
- @timeliness_cache["#{attr_name}"] = value
38
-
47
+ @timeliness_cache["#{attr_name}"] = original_value
39
48
  #{ "if value.is_a?(String)\n#{timeliness_type_cast_code(attr_name, 'value')}\nend" if ValidatesTimeliness.use_plugin_parser }
40
- super
49
+
50
+ super(value)
41
51
  end
42
52
  EOV
43
53
  generated_timeliness_methods.module_eval(method_body, __FILE__, line)
@@ -63,6 +73,12 @@ module ValidatesTimeliness
63
73
  def generated_timeliness_methods
64
74
  @generated_timeliness_methods ||= Module.new.tap { |m| include(m) }
65
75
  end
76
+
77
+ def undefine_timeliness_attribute_methods
78
+ generated_timeliness_methods.module_eval do
79
+ instance_methods.each { |m| undef_method(m) }
80
+ end
81
+ end
66
82
  end
67
83
 
68
84
  def _timeliness_raw_value_for(attr_name)
@@ -8,7 +8,8 @@ module ValidatesTimeliness
8
8
 
9
9
  included do
10
10
  alias_method_chain :instantiate_time_object, :timeliness
11
- alias_method_chain :execute_callstack_for_multiparameter_attributes, :timeliness
11
+ alias_method :execute_callstack_for_multiparameter_attributes, :execute_callstack_for_multiparameter_attributes_with_timeliness
12
+ alias_method :read_value_from_parameter, :read_value_from_parameter_with_timeliness
12
13
  end
13
14
 
14
15
  private
@@ -20,42 +21,52 @@ module ValidatesTimeliness
20
21
  end
21
22
 
22
23
  def instantiate_time_object_with_timeliness(name, values)
23
- if Date.valid_civil?(*values[0..2])
24
+ validate_multiparameter_date_values(values) {
24
25
  instantiate_time_object_without_timeliness(name, values)
26
+ }
27
+ end
28
+
29
+ def instantiate_date_object(name, values)
30
+ validate_multiparameter_date_values(values) {
31
+ Date.new(*values)
32
+ }
33
+ end
34
+
35
+ # Yield if date values are valid
36
+ def validate_multiparameter_date_values(values)
37
+ if values[0..2].all?{ |v| v.present? } && Date.valid_civil?(*values[0..2])
38
+ yield
25
39
  else
26
40
  invalid_multiparameter_date_or_time_as_string(values)
27
41
  end
28
42
  end
29
43
 
30
- def instantiate_date_object(name, values)
31
- values = values.map { |v| v.nil? ? 1 : v }
32
- Date.new(*values)
33
- rescue ArgumentError => ex
34
- invalid_multiparameter_date_or_time_as_string(values)
44
+ def read_value_from_parameter_with_timeliness(name, values_from_param)
45
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
46
+ values = values_from_param.is_a?(Hash) ? values_from_param.to_a.sort_by(&:first).map(&:last) : values_from_param
47
+
48
+ if values.empty? || values.all?{ |v| v.nil? }
49
+ nil
50
+ elsif klass == Time
51
+ instantiate_time_object(name, values)
52
+ elsif klass == Date
53
+ instantiate_date_object(name, values)
54
+ else
55
+ if respond_to?(:read_other_parameter_value)
56
+ read_date_parameter_value(name, values_from_param)
57
+ else
58
+ klass.new(*values)
59
+ end
60
+ end
35
61
  end
36
62
 
37
63
  def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
38
64
  errors = []
39
65
  callstack.each do |name, values_with_empty_parameters|
40
66
  begin
41
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
42
- values = values_with_empty_parameters.reject { |v| v.nil? }
43
-
44
- if values.empty?
45
- send(name + "=", nil)
46
- else
47
-
48
- value = if Time == klass
49
- instantiate_time_object(name, values)
50
- elsif Date == klass
51
- instantiate_date_object(name, values_with_empty_parameters)
52
- else
53
- klass.new(*values)
54
- end
55
-
56
- send(name + "=", value)
57
- end
67
+ send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
58
68
  rescue => ex
69
+ values = values_with_empty_parameters.is_a?(Hash) ? values_with_empty_parameters.values : values_with_empty_parameters
59
70
  errors << ActiveRecord::AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
60
71
  end
61
72
  end
@@ -3,13 +3,21 @@ module ValidatesTimeliness
3
3
  module ActiveRecord
4
4
  extend ActiveSupport::Concern
5
5
 
6
- module ClassMethods
7
- def define_attribute_methods
8
- super
9
- # Define write method and before_type_cast method
10
- use_before_type_cast = ::ActiveRecord::VERSION::STRING < '3.1.0'
11
- define_timeliness_methods(use_before_type_cast)
6
+ def self.use_plugin_cache?
7
+ ::ActiveRecord::VERSION::STRING < '3.1.0'
8
+ end
9
+
10
+ included do
11
+ if ValidatesTimeliness::ORM::ActiveRecord.use_plugin_cache?
12
+ include Reload
13
+ else
14
+ # Just use the built-in before_type_cast retrieval
15
+ alias_method :_timeliness_raw_value_for, :read_attribute_before_type_cast
12
16
  end
17
+ end
18
+
19
+ module ClassMethods
20
+ public
13
21
 
14
22
  def timeliness_attribute_timezone_aware?(attr_name)
15
23
  attr_name = attr_name.to_s
@@ -20,20 +28,52 @@ module ValidatesTimeliness
20
28
  columns_hash[attr_name.to_s].type
21
29
  end
22
30
 
31
+ def define_attribute_methods
32
+ super.tap do |attribute_methods_generated|
33
+ use_before_type_cast = ValidatesTimeliness::ORM::ActiveRecord.use_plugin_cache?
34
+ define_timeliness_methods use_before_type_cast
35
+ end
36
+ end
37
+
38
+ protected
39
+
40
+ def define_attribute_timeliness_methods(attr_name, before_type_cast=false)
41
+ if before_type_cast
42
+ define_timeliness_write_method(attr_name)
43
+ define_timeliness_before_type_cast_method(attr_name)
44
+ elsif ValidatesTimeliness.use_plugin_parser
45
+ define_timeliness_write_method_without_cache(attr_name)
46
+ end
47
+ end
48
+
49
+ def define_timeliness_write_method_without_cache(attr_name)
50
+ method_body, line = <<-EOV, __LINE__ + 1
51
+ def #{attr_name}=(value)
52
+ original_value = value
53
+ if value.is_a?(String)\n#{timeliness_type_cast_code(attr_name, 'value')}\nend
54
+ super(value)
55
+ @attributes['#{attr_name}'] = original_value
56
+ end
57
+ EOV
58
+ generated_timeliness_methods.module_eval(method_body, __FILE__, line)
59
+ end
60
+
23
61
  def timeliness_type_cast_code(attr_name, var_name)
24
62
  type = timeliness_attribute_type(attr_name)
25
63
 
26
- <<-END
27
- #{super}
28
- #{var_name} = #{var_name}.to_date if #{var_name} && :#{type} == :date
29
- END
64
+ method_body = super
65
+ method_body << "\n#{var_name} = #{var_name}.to_date if #{var_name}" if type == :date
66
+ method_body
30
67
  end
31
68
  end
32
69
 
33
- def reload(*args)
34
- _clear_timeliness_cache
35
- super
70
+ module Reload
71
+ def reload(*args)
72
+ _clear_timeliness_cache
73
+ super
74
+ end
36
75
  end
76
+
37
77
  end
38
78
  end
39
79
  end
@@ -7,6 +7,8 @@ module ValidatesTimeliness
7
7
  # field value in Mongoid. Parser will return nil rather than error.
8
8
 
9
9
  module ClassMethods
10
+ public
11
+
10
12
  # Mongoid has no bulk attribute method definition hook. It defines
11
13
  # them with each field definition. So we likewise define them after
12
14
  # each validation is defined.
@@ -16,12 +18,6 @@ module ValidatesTimeliness
16
18
  attr_names.each { |attr_name| define_timeliness_write_method(attr_name) }
17
19
  end
18
20
 
19
- def timeliness_type_cast_code(attr_name, var_name)
20
- type = timeliness_attribute_type(attr_name)
21
-
22
- "#{var_name} = Timeliness::Parser.parse(value, :#{type})"
23
- end
24
-
25
21
  def timeliness_attribute_type(attr_name)
26
22
  {
27
23
  Date => :date,
@@ -29,11 +25,22 @@ module ValidatesTimeliness
29
25
  DateTime => :datetime
30
26
  }[fields[attr_name.to_s].type] || :datetime
31
27
  end
28
+
29
+ protected
30
+
31
+ def timeliness_type_cast_code(attr_name, var_name)
32
+ type = timeliness_attribute_type(attr_name)
33
+
34
+ "#{var_name} = Timeliness::Parser.parse(value, :#{type})"
35
+ end
36
+
32
37
  end
33
38
 
34
- def reload
35
- _clear_timeliness_cache
36
- super
39
+ module Reload
40
+ def reload(*args)
41
+ _clear_timeliness_cache
42
+ super
43
+ end
37
44
  end
38
45
  end
39
46
  end
@@ -44,11 +51,13 @@ module Mongoid::Document
44
51
  include ValidatesTimeliness::ORM::Mongoid
45
52
 
46
53
  # Pre-2.3 reload
47
- if instance_methods.include?('reload')
54
+ if (instance_methods & ['reload', :reload]).present?
48
55
  def reload_with_timeliness
49
56
  _clear_timeliness_cache
50
57
  reload_without_timeliness
51
58
  end
52
59
  alias_method_chain :reload, :timeliness
60
+ else
61
+ include ValidatesTimeliness::ORM::Mongoid::Reload
53
62
  end
54
63
  end
@@ -1,3 +1,3 @@
1
1
  module ValidatesTimeliness
2
- VERSION = '3.0.8'
2
+ VERSION = '3.0.9'
3
3
  end
data/spec/spec_helper.rb CHANGED
@@ -88,8 +88,14 @@ RSpec.configure do |c|
88
88
  c.include(ModelHelpers)
89
89
  c.include(ConfigHelper)
90
90
  c.before do
91
- Person.reset_callbacks(:validate)
92
- PersonWithShim.timeliness_validated_attributes = []
93
- Person._validators.clear
91
+ reset_validation_setup_for(Person)
92
+ reset_validation_setup_for(PersonWithShim)
94
93
  end
94
+
95
+ RSpec.configure do |c|
96
+ c.filter_run_excluding :active_record => lambda {|version|
97
+ !(::ActiveRecord::VERSION::STRING.to_s =~ /^#{version.to_s}/)
98
+ }
99
+ end
100
+
95
101
  end
@@ -10,6 +10,16 @@ module ConfigHelper
10
10
  ValidatesTimeliness.send(:"#{preference_name}=", old_value)
11
11
  end
12
12
 
13
+ def reset_validation_setup_for(model_class)
14
+ model_class.reset_callbacks(:validate)
15
+ model_class._validators.clear
16
+ model_class.timeliness_validated_attributes = [] if model_class.respond_to?(:timeliness_validated_attributes)
17
+ model_class.undefine_attribute_methods
18
+ # This is a hack to avoid a disabled super method error message after an undef
19
+ model_class.instance_variable_set(:@generated_attribute_methods, nil)
20
+ model_class.instance_variable_set(:@generated_timeliness_methods, nil)
21
+ end
22
+
13
23
  module ClassMethods
14
24
  def with_config(preference_name, temporary_value)
15
25
  original_config_value = ValidatesTimeliness.send(preference_name)
@@ -31,7 +31,7 @@ describe ValidatesTimeliness::AttributeMethods do
31
31
  it 'should cache attribute raw value' do
32
32
  r = PersonWithCache.new
33
33
  r.birth_datetime = date_string = '2010-01-01'
34
- r._timeliness_raw_value_for(:birth_datetime).should == date_string
34
+ r._timeliness_raw_value_for('birth_datetime').should == date_string
35
35
  end
36
36
 
37
37
  it 'should not overwrite user defined methods' do
@@ -40,6 +40,21 @@ describe ValidatesTimeliness::AttributeMethods do
40
40
  e.redefined_birth_date_called.should be_true
41
41
  end
42
42
 
43
+ it 'should be undefined if model class has dynamic attribute methods reset' do
44
+ # Force method definitions
45
+ PersonWithShim.validates_date :birth_date
46
+ r = PersonWithShim.new
47
+ r.birth_date = Time.now
48
+
49
+ write_method = RUBY_VERSION < '1.9' ? 'birth_date=' : :birth_date=
50
+
51
+ PersonWithShim.send(:generated_timeliness_methods).instance_methods.should include(write_method)
52
+
53
+ PersonWithShim.undefine_attribute_methods
54
+
55
+ PersonWithShim.send(:generated_timeliness_methods).instance_methods.should_not include(write_method)
56
+ end
57
+
43
58
  context "with plugin parser" do
44
59
  with_config(:use_plugin_parser, true)
45
60
 
@@ -1,33 +1,44 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ValidatesTimeliness::Extensions::MultiparameterHandler do
4
- let(:employee) { Employee.new }
5
4
 
6
5
  context "time column" do
7
- it 'should return string value for invalid date portion' do
8
- multiparameter_attribute(:birth_datetime, [2000, 2, 31, 12, 0, 0])
9
- employee.birth_datetime_before_type_cast.should == '2000-02-31 12:00:00'
6
+ it 'should assign a string value for invalid date portion' do
7
+ employee = record_with_multiparameter_attribute(:birth_datetime, [2000, 2, 31, 12, 0, 0])
8
+ employee.birth_datetime_before_type_cast.should eq '2000-02-31 12:00:00'
10
9
  end
11
10
 
12
- it 'should return Time value for valid datetimes' do
13
- multiparameter_attribute(:birth_datetime, [2000, 2, 28, 12, 0, 0])
14
- employee.birth_datetime_before_type_cast.should be_kind_of(Time)
11
+ it 'should assign a Time value for valid datetimes' do
12
+ employee = record_with_multiparameter_attribute(:birth_datetime, [2000, 2, 28, 12, 0, 0])
13
+ employee.birth_datetime_before_type_cast.should eq Time.local(2000, 2, 28, 12, 0, 0)
14
+ end
15
+
16
+ it 'should assign a string value for incomplete time' do
17
+ employee = record_with_multiparameter_attribute(:birth_datetime, [2000, nil, nil])
18
+ employee.birth_datetime_before_type_cast.should eq '2000-00-00'
15
19
  end
16
20
  end
17
21
 
18
22
  context "date column" do
19
- it 'should return string value for invalid date' do
20
- multiparameter_attribute(:birth_date, [2000, 2, 31])
21
- employee.birth_date_before_type_cast.should == '2000-02-31'
23
+ it 'should assign a string value for invalid date' do
24
+ employee = record_with_multiparameter_attribute(:birth_date, [2000, 2, 31])
25
+ employee.birth_date_before_type_cast.should eq '2000-02-31'
26
+ end
27
+
28
+ it 'should assign a Date value for valid date' do
29
+ employee = record_with_multiparameter_attribute(:birth_date, [2000, 2, 28])
30
+ employee.birth_date_before_type_cast.should eq Date.new(2000, 2, 28)
22
31
  end
23
32
 
24
- it 'should return Date value for valid date' do
25
- multiparameter_attribute(:birth_date, [2000, 2, 28])
26
- employee.birth_date_before_type_cast.should be_kind_of(Date)
33
+ it 'should assign a string value for incomplete date' do
34
+ employee = record_with_multiparameter_attribute(:birth_date, [2000, nil, nil])
35
+ employee.birth_date_before_type_cast.should eq '2000-00-00'
27
36
  end
28
37
  end
29
38
 
30
- def multiparameter_attribute(name, values)
31
- employee.send(:execute_callstack_for_multiparameter_attributes, name.to_s => values)
39
+ def record_with_multiparameter_attribute(name, values)
40
+ hash = {}
41
+ values.each_with_index {|value, index| hash["#{name}(#{index+1}i)"] = value.to_s }
42
+ Employee.new(hash)
32
43
  end
33
44
  end
@@ -29,7 +29,7 @@ describe ValidatesTimeliness, 'ActiveRecord' do
29
29
  it 'should cache attribute raw value' do
30
30
  r = EmployeeWithCache.new
31
31
  r.birth_datetime = date_string = '2010-01-01'
32
- r._timeliness_raw_value_for(:birth_datetime).should == date_string
32
+ r._timeliness_raw_value_for('birth_datetime').should == date_string
33
33
  end
34
34
 
35
35
  context "with plugin parser" do
@@ -47,7 +47,7 @@ describe ValidatesTimeliness, 'ActiveRecord' do
47
47
  r.birth_date = '2010-01-01'
48
48
  end
49
49
 
50
- context "for a date column" do
50
+ context "for a date column", :active_record => '3.0' do
51
51
  it 'should store a date value after parsing string' do
52
52
  r = EmployeeWithParser.new
53
53
  r.birth_date = '2010-01-01'
@@ -83,7 +83,7 @@ describe ValidatesTimeliness, 'ActiveRecord' do
83
83
  r.birth_date = '2010-01-01'
84
84
  r.reload
85
85
 
86
- r._timeliness_raw_value_for(:birth_date).should be_nil
86
+ r._timeliness_raw_value_for('birth_date').should be_nil
87
87
  end
88
88
  end
89
89
 
@@ -106,5 +106,23 @@ describe ValidatesTimeliness, 'ActiveRecord' do
106
106
  r = Employee.last
107
107
  r.birth_datetime_before_type_cast.should match(/2010-01-01 00:00:00/)
108
108
  end
109
+
110
+ context "with plugin parser" do
111
+ with_config(:use_plugin_parser, true)
112
+
113
+ it 'should return original value' do
114
+ r = Employee.new
115
+ r.birth_datetime = date_string = '2010-01-31'
116
+
117
+ r.birth_datetime_before_type_cast.should == date_string
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ context "define_attribute_methods" do
124
+ it "returns a falsy value if the attribute methods have already been generated" do
125
+ Employee.define_attribute_methods.should be_false
126
+ end
109
127
  end
110
128
  end
@@ -46,6 +46,17 @@ describe ValidatesTimeliness::Validator do
46
46
  Person.validates_date :birth_date, :allow_nil => true
47
47
  valid!(:birth_date, NIL)
48
48
  end
49
+
50
+ context "with raw value cache" do
51
+ it "should not be valid with an invalid format" do
52
+ PersonWithShim.validates_date :birth_date, :allow_nil => true
53
+
54
+ p = PersonWithShim.new
55
+ p.birth_date = 'bogus'
56
+
57
+ p.should_not be_valid
58
+ end
59
+ end
49
60
  end
50
61
 
51
62
  describe ":allow_blank option" do
@@ -59,6 +70,17 @@ describe ValidatesTimeliness::Validator do
59
70
  Person.validates_date :birth_date, :allow_blank => true
60
71
  valid!(:birth_date, '')
61
72
  end
73
+
74
+ context "with raw value cache" do
75
+ it "should not be valid with an invalid format" do
76
+ PersonWithShim.validates_date :birth_date, :allow_blank => true
77
+
78
+ p = PersonWithShim.new
79
+ p.birth_date = 'bogus'
80
+
81
+ p.should_not be_valid
82
+ end
83
+ end
62
84
  end
63
85
 
64
86
  describe ":between option" do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: validates_timeliness
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease:
6
6
  segments:
7
7
  - 3
8
8
  - 0
9
- - 8
10
- version: 3.0.8
9
+ - 9
10
+ version: 3.0.9
11
11
  platform: ruby
12
12
  authors:
13
13
  - Adam Meehan
@@ -15,12 +15,10 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-12-24 00:00:00 +11:00
18
+ date: 2012-03-26 00:00:00 +11:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
- name: timeliness
23
- prerelease: false
24
22
  requirement: &id001 !ruby/object:Gem::Requirement
25
23
  none: false
26
24
  requirements:
@@ -32,6 +30,8 @@ dependencies:
32
30
  - 3
33
31
  - 4
34
32
  version: 0.3.4
33
+ name: timeliness
34
+ prerelease: false
35
35
  type: :runtime
36
36
  version_requirements: *id001
37
37
  description: Adds validation methods to ActiveModel for validating dates and times. Works with multiple ORMS.