Move conversion module methods in Converter class

Encapsulate conversion helper methods
This commit is contained in:
Adam Meehan 2018-05-19 16:28:35 +10:00
parent e5bb096161
commit 093e33fbed
4 changed files with 106 additions and 70 deletions

View File

@ -62,7 +62,7 @@ module ValidatesTimeliness
def self.parser; Timeliness end
end
require 'validates_timeliness/conversion'
require 'validates_timeliness/converter'
require 'validates_timeliness/validator'
require 'validates_timeliness/helper_methods'
require 'validates_timeliness/attribute_methods'

View File

@ -1,10 +1,18 @@
module ValidatesTimeliness
module Conversion
class Converter
attr_reader :type, :format, :ignore_usec
def type_cast_value(value, type)
def initialize(type:, format: nil, ignore_usec: false, time_zone_aware: false)
@type = type
@format = format
@ignore_usec = ignore_usec
@time_zone_aware = time_zone_aware
end
def type_cast_value(value)
return nil if value.nil? || !value.respond_to?(:to_time)
value = value.in_time_zone if value.acts_like?(:time) && @timezone_aware
value = value.in_time_zone if value.acts_like?(:time) && time_zone_aware?
value = case type
when :time
dummy_time(value)
@ -15,8 +23,8 @@ module ValidatesTimeliness
else
value
end
if options[:ignore_usec] && value.is_a?(Time)
Timeliness::Parser.make_time(Array(value).reverse[4..9], (:current if @timezone_aware))
if ignore_usec && value.is_a?(Time)
Timeliness::Parser.make_time(Array(value).reverse[4..9], (:current if time_zone_aware?))
else
value
end
@ -24,30 +32,30 @@ module ValidatesTimeliness
def dummy_time(value)
time = if value.acts_like?(:time)
value = value.in_time_zone if @timezone_aware
value = value.in_time_zone if time_zone_aware?
[value.hour, value.min, value.sec]
else
[0,0,0]
end
values = ValidatesTimeliness.dummy_date_for_time_type + time
Timeliness::Parser.make_time(values, (:current if @timezone_aware))
Timeliness::Parser.make_time(values, (:current if time_zone_aware?))
end
def evaluate_option_value(value, record)
def evaluate(value, scope=nil)
case value
when Time, Date
value
when String
parse(value)
when Symbol
if !record.respond_to?(value) && restriction_shorthand?(value)
if !scope.respond_to?(value) && restriction_shorthand?(value)
ValidatesTimeliness.restriction_shorthand_symbols[value].call
else
evaluate_option_value(record.send(value), record)
evaluate(scope.send(value))
end
when Proc
result = value.arity > 0 ? value.call(record) : value.call
evaluate_option_value(result, record)
result = value.arity > 0 ? value.call(scope) : value.call
evaluate(result, scope)
else
value
end
@ -59,14 +67,18 @@ module ValidatesTimeliness
def parse(value)
return nil if value.nil?
if ValidatesTimeliness.use_plugin_parser
Timeliness::Parser.parse(value, @type, :zone => (:current if @timezone_aware), :format => options[:format], :strict => false)
Timeliness::Parser.parse(value, type, zone: (:current if time_zone_aware?), format: format, strict: false)
else
@timezone_aware ? Time.zone.parse(value) : value.to_time(ValidatesTimeliness.default_timezone)
time_zone_aware? ? Time.zone.parse(value) : value.to_time(ValidatesTimeliness.default_timezone)
end
rescue ArgumentError, TypeError
nil
end
def time_zone_aware?
@time_zone_aware
end
end
end

View File

@ -3,9 +3,7 @@ require 'active_model/validator'
module ValidatesTimeliness
class Validator < ActiveModel::EachValidator
include Conversion
attr_reader :type, :attributes
attr_reader :type, :attributes, :converter
RESTRICTIONS = {
:is_at => :==,
@ -59,9 +57,10 @@ module ValidatesTimeliness
raw_value = attribute_raw_value(record, attr_name) || value
return if (@allow_nil && raw_value.nil?) || (@allow_blank && raw_value.blank?)
@timezone_aware = timezone_aware?(record, attr_name)
value = parse(raw_value) if value.is_a?(String) || options[:format]
value = type_cast_value(value, @type)
@converter = initialize_converter(record, attr_name)
value = @converter.parse(raw_value) if value.is_a?(String) || options[:format]
value = @converter.type_cast_value(value)
add_error(record, attr_name, :"invalid_#{@type}") and return if value.blank?
@ -71,7 +70,7 @@ module ValidatesTimeliness
def validate_restrictions(record, attr_name, value)
@restrictions_to_check.each do |restriction|
begin
restriction_value = type_cast_value(evaluate_option_value(options[restriction], record), @type)
restriction_value = @converter.type_cast_value(@converter.evaluate(options[restriction], record))
unless value.send(RESTRICTIONS[restriction], restriction_value)
add_error(record, attr_name, restriction, restriction_value) and break
end
@ -100,10 +99,20 @@ module ValidatesTimeliness
record.read_timeliness_attribute_before_type_cast(attr_name.to_s)
end
def timezone_aware?(record, attr_name)
def time_zone_aware?(record, attr_name)
record.class.respond_to?(:skip_time_zone_conversion_for_attributes) &&
!record.class.skip_time_zone_conversion_for_attributes.include?(attr_name.to_sym)
end
def initialize_converter(record, attr_name)
ValidatesTimeliness::Converter.new(
type: @type,
time_zone_aware: time_zone_aware?(record, attr_name),
format: options[:format],
ignore_usec: options[:ignore_usec]
)
end
end
end

View File

@ -1,94 +1,109 @@
RSpec.describe ValidatesTimeliness::Conversion do
include ValidatesTimeliness::Conversion
RSpec.describe ValidatesTimeliness::Converter do
subject(:converter) { described_class.new(type: type, time_zone_aware: time_zone_aware, ignore_usec: ignore_usec) }
let(:options) { Hash.new }
let(:type) { :date }
let(:time_zone_aware) { false }
let(:ignore_usec) { false }
before do
Timecop.freeze(Time.mktime(2010, 1, 1, 0, 0, 0))
end
delegate :type_cast_value, :evaluate, :parse, :dummy_time, to: :converter
describe "#type_cast_value" do
describe "for date type" do
let(:type) { :date }
it "should return same value for date value" do
expect(type_cast_value(Date.new(2010, 1, 1), :date)).to eq(Date.new(2010, 1, 1))
expect(type_cast_value(Date.new(2010, 1, 1))).to eq(Date.new(2010, 1, 1))
end
it "should return date part of time value" do
expect(type_cast_value(Time.mktime(2010, 1, 1, 0, 0, 0), :date)).to eq(Date.new(2010, 1, 1))
expect(type_cast_value(Time.mktime(2010, 1, 1, 0, 0, 0))).to eq(Date.new(2010, 1, 1))
end
it "should return date part of datetime value" do
expect(type_cast_value(DateTime.new(2010, 1, 1, 0, 0, 0), :date)).to eq(Date.new(2010, 1, 1))
expect(type_cast_value(DateTime.new(2010, 1, 1, 0, 0, 0))).to eq(Date.new(2010, 1, 1))
end
it 'should return nil for invalid value types' do
expect(type_cast_value(12, :date)).to eq(nil)
expect(type_cast_value(12)).to eq(nil)
end
end
describe "for time type" do
let(:type) { :time }
it "should return same value for time value matching dummy date part" do
expect(type_cast_value(Time.utc(2000, 1, 1, 0, 0, 0), :time)).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
expect(type_cast_value(Time.utc(2000, 1, 1, 0, 0, 0))).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
end
it "should return dummy time value with same time part for time value with different date" do
expect(type_cast_value(Time.utc(2010, 1, 1, 0, 0, 0), :time)).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
expect(type_cast_value(Time.utc(2010, 1, 1, 0, 0, 0))).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
end
it "should return dummy time only for date value" do
expect(type_cast_value(Date.new(2010, 1, 1), :time)).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
expect(type_cast_value(Date.new(2010, 1, 1))).to eq(Time.utc(2000, 1, 1, 0, 0, 0))
end
it "should return dummy date with time part for datetime value" do
expect(type_cast_value(DateTime.civil_from_format(:utc, 2010, 1, 1, 12, 34, 56), :time)).to eq(Time.utc(2000, 1, 1, 12, 34, 56))
expect(type_cast_value(DateTime.civil_from_format(:utc, 2010, 1, 1, 12, 34, 56))).to eq(Time.utc(2000, 1, 1, 12, 34, 56))
end
it 'should return nil for invalid value types' do
expect(type_cast_value(12, :time)).to eq(nil)
expect(type_cast_value(12)).to eq(nil)
end
end
describe "for datetime type" do
let(:type) { :datetime }
let(:time_zone_aware) { true }
it "should return Date as Time value" do
expect(type_cast_value(Date.new(2010, 1, 1), :datetime)).to eq(Time.local(2010, 1, 1, 0, 0, 0))
expect(type_cast_value(Date.new(2010, 1, 1))).to eq(Time.local(2010, 1, 1, 0, 0, 0))
end
it "should return same Time value" do
value = Time.utc(2010, 1, 1, 12, 34, 56)
expect(type_cast_value(Time.utc(2010, 1, 1, 12, 34, 56), :datetime)).to eq(value)
expect(type_cast_value(Time.utc(2010, 1, 1, 12, 34, 56))).to eq(value)
end
it "should return as Time with same component values" do
expect(type_cast_value(DateTime.civil_from_format(:utc, 2010, 1, 1, 12, 34, 56), :datetime)).to eq(Time.utc(2010, 1, 1, 12, 34, 56))
expect(type_cast_value(DateTime.civil_from_format(:utc, 2010, 1, 1, 12, 34, 56))).to eq(Time.utc(2010, 1, 1, 12, 34, 56))
end
it "should return same Time in correct zone if timezone aware" do
@timezone_aware = true
value = Time.utc(2010, 1, 1, 12, 34, 56)
result = type_cast_value(value, :datetime)
result = type_cast_value(value)
expect(result).to eq(Time.zone.local(2010, 1, 1, 23, 34, 56))
expect(result.zone).to eq('AEDT')
end
it 'should return nil for invalid value types' do
expect(type_cast_value(12, :datetime)).to eq(nil)
expect(type_cast_value(12)).to eq(nil)
end
end
describe "ignore_usec option" do
let(:options) { {:ignore_usec => true} }
let(:type) { :datetime }
let(:ignore_usec) { true }
it "should ignore usec on time values when evaluated" do
value = Time.utc(2010, 1, 1, 12, 34, 56, 10000)
expect(type_cast_value(value, :datetime)).to eq(Time.utc(2010, 1, 1, 12, 34, 56))
expect(type_cast_value(value)).to eq(Time.utc(2010, 1, 1, 12, 34, 56))
end
it "should ignore usec and return time in correct zone if timezone aware" do
@timezone_aware = true
value = Time.utc(2010, 1, 1, 12, 34, 56, 10000)
result = type_cast_value(value, :datetime)
expect(result).to eq(Time.zone.local(2010, 1, 1, 23, 34, 56))
expect(result.zone).to eq('AEDT')
context do
let(:time_zone_aware) { true }
it "should ignore usec and return time in correct zone if timezone aware" do
value = Time.utc(2010, 1, 1, 12, 34, 56, 10000)
result = type_cast_value(value)
expect(result).to eq(Time.zone.local(2010, 1, 1, 23, 34, 56))
expect(result.zone).to eq('AEDT')
end
end
end
end
@ -103,7 +118,6 @@ RSpec.describe ValidatesTimeliness::Conversion do
end
it 'should return time component values shifted to current zone if timezone aware' do
@timezone_aware = true
expect(dummy_time(Time.utc(2000, 1, 1, 12, 34, 56))).to eq(Time.zone.local(2000, 1, 1, 23, 34, 56))
end
@ -120,61 +134,64 @@ RSpec.describe ValidatesTimeliness::Conversion do
end
end
describe "#evaluate_option_value" do
describe "#evaluate" do
let(:person) { Person.new }
it 'should return Date object as is' do
value = Date.new(2010,1,1)
expect(evaluate_option_value(value, person)).to eq(value)
expect(evaluate(value, person)).to eq(value)
end
it 'should return Time object as is' do
value = Time.mktime(2010,1,1)
expect(evaluate_option_value(value, person)).to eq(value)
expect(evaluate(value, person)).to eq(value)
end
it 'should return DateTime object as is' do
value = DateTime.new(2010,1,1,0,0,0)
expect(evaluate_option_value(value, person)).to eq(value)
expect(evaluate(value, person)).to eq(value)
end
it 'should return Time value returned from proc with 0 arity' do
value = Time.mktime(2010,1,1)
expect(evaluate_option_value(lambda { value }, person)).to eq(value)
expect(evaluate(lambda { value }, person)).to eq(value)
end
it 'should return Time value returned by record attribute call in proc arity of 1' do
value = Time.mktime(2010,1,1)
person.birth_time = value
expect(evaluate_option_value(lambda {|r| r.birth_time }, person)).to eq(value)
expect(evaluate(lambda {|r| r.birth_time }, person)).to eq(value)
end
it 'should return Time value for attribute method symbol which returns Time' do
value = Time.mktime(2010,1,1)
person.birth_datetime = value
expect(evaluate_option_value(:birth_datetime, person)).to eq(value)
expect(evaluate(:birth_datetime, person)).to eq(value)
end
it 'should return Time value is default zone from string time value' do
value = '2010-01-01 12:00:00'
expect(evaluate_option_value(value, person)).to eq(Time.utc(2010,1,1,12,0,0))
expect(evaluate(value, person)).to eq(Time.utc(2010,1,1,12,0,0))
end
it 'should return Time value is current zone from string time value if timezone aware' do
@timezone_aware = true
value = '2010-01-01 12:00:00'
expect(evaluate_option_value(value, person)).to eq(Time.zone.local(2010,1,1,12,0,0))
context do
let(:converter) { described_class.new(type: :date, time_zone_aware: true) }
it 'should return Time value is current zone from string time value if timezone aware' do
value = '2010-01-01 12:00:00'
expect(evaluate(value, person)).to eq(Time.zone.local(2010,1,1,12,0,0))
end
end
it 'should return Time value in default zone from proc which returns string time' do
value = '2010-11-12 13:00:00'
expect(evaluate_option_value(lambda { value }, person)).to eq(Time.utc(2010,11,12,13,0,0))
expect(evaluate(lambda { value }, person)).to eq(Time.utc(2010,11,12,13,0,0))
end
skip 'should return Time value for attribute method symbol which returns string time value' do
it 'should return Time value for attribute method symbol which returns string time value' do
value = '13:00:00'
person.birth_time = value
expect(evaluate_option_value(:birth_time, person)).to eq(Time.utc(2000,1,1,13,0,0))
expect(evaluate(:birth_time, person)).to eq(Time.utc(2000,1,1,13,0,0))
end
context "restriction shorthand" do
@ -183,17 +200,17 @@ RSpec.describe ValidatesTimeliness::Conversion do
end
it 'should evaluate :now as current time' do
expect(evaluate_option_value(:now, person)).to eq(Time.now)
expect(evaluate(:now, person)).to eq(Time.now)
end
it 'should evaluate :today as current time' do
expect(evaluate_option_value(:today, person)).to eq(Date.today)
expect(evaluate(:today, person)).to eq(Date.today)
end
it 'should not use shorthand if symbol if is record method' do
time = 1.day.from_now
allow(person).to receive(:now).and_return(time)
expect(evaluate_option_value(:now, person)).to eq(time)
expect(evaluate(:now, person)).to eq(time)
end
end
end
@ -212,13 +229,11 @@ RSpec.describe ValidatesTimeliness::Conversion do
with_config(:use_plugin_parser, false)
it 'should use Time.zone.parse attribute is timezone aware' do
@timezone_aware = true
expect(Time.zone).to receive(:parse)
expect(Timeliness::Parser).to_not receive(:parse)
parse('2000-01-01')
end
it 'should use value#to_time if use_plugin_parser setting is false and attribute is not timezone aware' do
@timezone_aware = false
value = '2000-01-01'
expect(value).to receive(:to_time)
parse(value)