validates_timeliness 3.0.8 → 3.0.9

Sign up to get free protection for your applications and to get access to all the features.
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.