diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index 38b9f72..3617c43 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -1,6 +1,6 @@ = [UNRELEASED] -* Fix removed namespace ActionRecord::AttributeAssignment::MultiparameterAttribute (AquisTech) * Fix DateTimeSelect extension support (AquisTech) +* Update Multiparameter extension to use ActiveRecord type classes with multiparameter handling = 4.0.2 [2016-01-07] * Fix undefine_generated_methods ivar guard setting to false diff --git a/lib/validates_timeliness/extensions/multiparameter_handler.rb b/lib/validates_timeliness/extensions/multiparameter_handler.rb index a18c2b2..4d05277 100644 --- a/lib/validates_timeliness/extensions/multiparameter_handler.rb +++ b/lib/validates_timeliness/extensions/multiparameter_handler.rb @@ -1,74 +1,55 @@ -ActiveRecord::AttributeAssignment.class_eval do - private +module ValidatesTimeliness + module Extensions + class AcceptsMultiparameterTime < Module - # Yield if date values are valid - def validate_multiparameter_date_values(set_values) - if set_values[0..2].all?{ |v| v.present? } && Date.valid_civil?(*set_values[0..2]) - yield - else - invalid_multiparameter_date_or_time_as_string(set_values) - end - end + def initialize(defaults: {}) - def invalid_multiparameter_date_or_time_as_string(values) - value = [values[0], *values[1..2].map {|s| s.to_s.rjust(2,"0")} ].join("-") - value += ' ' + values[3..5].map {|s| s.to_s.rjust(2, "0") }.join(":") unless values[3..5].empty? - value - end + define_method(:cast) do |value| + if value.is_a?(Hash) + value_from_multiparameter_assignment(value) + else + super(value) + end + end - def instantiate_time_object(set_values) - raise if set_values.any?(&:nil?) + define_method(:assert_valid_value) do |value| + if value.is_a?(Hash) + value_from_multiparameter_assignment(value) + else + super(value) + end + end - validate_multiparameter_date_values(set_values) { - set_values = set_values.map {|v| v.is_a?(String) ? v.strip : v } + define_method(:value_from_multiparameter_assignment) do |values_hash| + defaults.each do |k, v| + values_hash[k] ||= v + end + return unless values_hash.values_at(1,2,3).all?{ |v| v.present? } && + Date.valid_civil?(*values_hash.values_at(1,2,3)) + + values = values_hash.sort.map(&:last) + ::Time.send(default_timezone, *values) + end + private :value_from_multiparameter_assignment - if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type_or_column) - Time.zone.local(*set_values) - else - Time.send(object.class.default_timezone, *set_values) end - } - rescue - invalid_multiparameter_date_or_time_as_string(set_values) - end - def read_time - # If column is a :time (and not :date or :timestamp) there is no need to validate if - # there are year/month/day fields - if cast_type_or_column.type == :time - # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil - { 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value| - values[key] ||= value - end end - - max_position = extract_max_param(6) - set_values = values.values_at(*(1..max_position)) - - instantiate_time_object(set_values) end - - def read_date - set_values = values.values_at(1,2,3).map {|v| v.is_a?(String) ? v.strip : v } - - if set_values.any? { |v| v.is_a?(String) } - Timeliness.parse(set_values.join('-'), :date).try(:to_date) or raise TypeError - else - Date.new(*set_values) - end - rescue TypeError, ArgumentError, NoMethodError => ex # if Date.new raises an exception on an invalid date - # Date.new with nil values throws NoMethodError - raise ex if ex.is_a?(NoMethodError) && ex.message !~ /undefined method `div' for/ - invalid_multiparameter_date_or_time_as_string(set_values) - end - - # Cast type is v4.2 and column before - def cast_type_or_column - @cast_type || @column - end - - def timezone_conversion_attribute? - object.class.send(:create_time_zone_conversion_attribute?, name, column) - end - end + +ActiveModel::Type::Date.class_eval do + include ValidatesTimeliness::Extensions::AcceptsMultiparameterTime.new +end + +ActiveModel::Type::Time.class_eval do + include ValidatesTimeliness::Extensions::AcceptsMultiparameterTime.new( + defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 } + ) +end + +ActiveModel::Type::DateTime.class_eval do + include ValidatesTimeliness::Extensions::AcceptsMultiparameterTime.new( + defaults: { 4 => 0, 5 => 0 } + ) +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a75afd2..a416626 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -57,6 +57,7 @@ end ActiveRecord::Base.default_timezone = :utc ActiveRecord::Base.time_zone_aware_attributes = true ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'}) +ActiveRecord::Base.time_zone_aware_types = [:datetime, :time] ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define(:version => 1) do create_table :employees, :force => true do |t| diff --git a/spec/validates_timeliness/extensions/multiparameter_handler_spec.rb b/spec/validates_timeliness/extensions/multiparameter_handler_spec.rb index f4a914d..7a5210f 100644 --- a/spec/validates_timeliness/extensions/multiparameter_handler_spec.rb +++ b/spec/validates_timeliness/extensions/multiparameter_handler_spec.rb @@ -1,36 +1,36 @@ RSpec.describe 'ValidatesTimeliness::Extensions::MultiparameterHandler' do context "time column" do - it 'should assign a string value for invalid date portion' do + it 'should be nil invalid date portion' do employee = record_with_multiparameter_attribute(:birth_datetime, [2000, 2, 31, 12, 0, 0]) - expect(employee.birth_datetime_before_type_cast).to eq '2000-02-31 12:00:00' + expect(employee.birth_datetime).to be_nil end it 'should assign a Time value for valid datetimes' do employee = record_with_multiparameter_attribute(:birth_datetime, [2000, 2, 28, 12, 0, 0]) - expect(employee.birth_datetime_before_type_cast).to eq Time.zone.local(2000, 2, 28, 12, 0, 0) + expect(employee.birth_datetime).to eq Time.zone.local(2000, 2, 28, 12, 0, 0) end - it 'should assign a string value for incomplete time' do + it 'should be nil for incomplete date portion' do employee = record_with_multiparameter_attribute(:birth_datetime, [2000, nil, nil]) - expect(employee.birth_datetime_before_type_cast).to eq '2000-00-00' + expect(employee.birth_datetime).to be_nil end end context "date column" do - it 'should assign a string value for invalid date' do + it 'should assign nil for invalid date' do employee = record_with_multiparameter_attribute(:birth_date, [2000, 2, 31]) - expect(employee.birth_date_before_type_cast).to eq '2000-02-31' + expect(employee.birth_date).to be_nil end it 'should assign a Date value for valid date' do employee = record_with_multiparameter_attribute(:birth_date, [2000, 2, 28]) - expect(employee.birth_date_before_type_cast).to eq Date.new(2000, 2, 28) + expect(employee.birth_date).to eq Date.new(2000, 2, 28) end - it 'should assign a string value for incomplete date' do + it 'should assign hash values for incomplete date' do employee = record_with_multiparameter_attribute(:birth_date, [2000, nil, nil]) - expect(employee.birth_date_before_type_cast).to eq '2000-00-00' + expect(employee.birth_date).to be_nil end end