From 5f55fad0764892b4a2843676f0a2423de4a3b499 Mon Sep 17 00:00:00 2001 From: Adam Meehan Date: Sun, 20 Jul 2008 07:45:22 +1000 Subject: [PATCH] added :before option to add_formats to insert above existing format --- README | 16 +++++++++++++--- lib/validates_timeliness/formats.rb | 26 ++++++++++++++++++-------- spec/formats_spec.rb | 22 +++++++++++++++++++++- spec/validations_spec.rb | 24 +++++++++++++++--------- 4 files changed, 67 insertions(+), 21 deletions(-) diff --git a/README b/README index a5aa974..982ab98 100644 --- a/README +++ b/README @@ -94,7 +94,7 @@ as dates. validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now } -== DATE/TIME FORMATS: +=== DATE/TIME FORMATS: So what formats does the plugin allow. Well there are default formats which can be added to easily using the plugins format rules. Also formats can be easily @@ -175,8 +175,9 @@ For the technically minded (well you are developers), these formats are compiled into regular expressions at runtime so don't add any extra overhead than using regular expressions directly. So, no, it won't make your app slow! +To see all defined formats look in the lib/validates_timeliness/formats.rb. -== CUSTOMISING FORMATS: +=== CUSTOMISING FORMATS: I hear you say "Thats greats but I don't want X format to be valid". Well to remove a format stick this in an initializer file or environment.rb @@ -192,8 +193,17 @@ Ahh, then add it yourself. Again stick this in an initializer file or environmen Now '10 o'clock' will be a valid value. So easy, no more whingeing! +Because formats are evaluated in order, adding a format which may be ambiguous +with an existing format, will mean your format is ignored. If you need to make +your new format higher precedence than an existing format, you can include the +before option like so -== EXTERNAL PARSER: + ValidatesTimeliness::Formats.add_formats(:time, 'ss:nn:hh', :before => 'hh:nn:ss') + +Now a time of '59:30:23' will be interpreted as 11:30:59 pm. This option saves +you adding a new one and deleting an old one to get it to work. + +=== EXTERNAL PARSER: I mentioned earlier that you could use a pluggable or alternative parser such as Chronic instead of the in built one. So if you need some super fancy stuff that diff --git a/lib/validates_timeliness/formats.rb b/lib/validates_timeliness/formats.rb index 713a174..36fee77 100644 --- a/lib/validates_timeliness/formats.rb +++ b/lib/validates_timeliness/formats.rb @@ -179,15 +179,15 @@ module ValidatesTimeliness # # Examples: # - # 'yyyy-mm-dd hh:nn' => lambda {|y,m,d,h,n| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|t| t.to_i unless t.nil? } } - # 'dd/mm/yyyy h:nn_ampm' => lambda {|d,m,y,h,n,md| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|t| t.to_i unless t.nil? } } + # 'yyyy-mm-dd hh:nn' => lambda {|y,m,d,h,n| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|t| t.to_i if t } } + # 'dd/mm/yyyy h:nn_ampm' => lambda {|d,m,y,h,n,md| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|t| t.to_i if t } } # def format_proc(order) arg_map = format_proc_args args = order.invert.sort.map {|p| arg_map[p[1]][1] } arr = [nil] * 7 order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? } - proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|t| t.to_i unless t.nil? } }" + proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|t| t.to_i if t } }" eval proc_string end @@ -202,7 +202,8 @@ module ValidatesTimeliness end # Loop through format expressions for type and call proc on matches. Allow - # pre or post match strings to exist if strict is false. + # pre or post match strings to exist if strict is false. Otherwise wrap + # regexp in start and end anchors. # Returns 7 part datetime array. def extract_date_time_values(time_string, type, strict=true) expressions = self.send("#{type}_expressions") @@ -217,6 +218,7 @@ module ValidatesTimeliness return time_array end + # Delete formats of specified type. Error raised if format not found. def remove_formats(type, *remove_formats) remove_formats.each do |format| unless self.send("#{type}_formats").delete(format) @@ -226,14 +228,22 @@ module ValidatesTimeliness compile_format_expressions end + # Adds new formats. Must specify format type and can specify a :before + # option to nominate which format the new formats should be inserted in + # front on to take higher precedence. Error is raise if format already + # exists or if :before format is not found. def add_formats(type, *add_formats) formats = self.send("#{type}_formats") + options = {} + options = add_formats.pop if add_formats.last.is_a?(Hash) + before = options[:before] + raise "Format for :before option #{format} was not found." if before && !formats.include?(before) add_formats.each do |format| - if formats.include?(format) - raise "Format #{format} is already included in #{type} formats" - end - formats << format + raise "Format #{format} is already included in #{type} formats" if formats.include?(format) + + index = before ? formats.index(before) : -1 + formats.insert(index, format) end compile_format_expressions end diff --git a/spec/formats_spec.rb b/spec/formats_spec.rb index 6c79d7d..91cfa87 100644 --- a/spec/formats_spec.rb +++ b/spec/formats_spec.rb @@ -180,6 +180,10 @@ describe ValidatesTimeliness::Formats do validate('2.12am', :time).should be_false end + it "should raise error if format does not exist" do + lambda { formats.remove_formats(:time, "ss:hh:nn") }.should raise_error() + end + after do formats.time_formats << 'h.nn_ampm' end @@ -190,7 +194,7 @@ describe ValidatesTimeliness::Formats do formats.compile_format_expressions end - it "should add format to format array" do + it "should add format to format array" do formats.add_formats(:time, "h o'clock") formats.time_formats.should include("h o'clock") end @@ -201,8 +205,24 @@ describe ValidatesTimeliness::Formats do validate("12 o'clock", :time).should be_true end + it "should add format before specified format and be higher precedence" do + formats.add_formats(:time, "ss:hh:nn", :before => 'hh:nn:ss') + validate("59:23:58", :time).should be_true + time_array = formats.extract_date_time_values('59:23:58', :time) + time_array.should == [nil,nil,nil,23,58,59,nil] + end + + it "should raise error if format exists" do + lambda { formats.add_formats(:time, "hh:nn:ss") }.should raise_error() + end + + it "should raise error if format exists" do + lambda { formats.add_formats(:time, "ss:hh:nn", :before => 'nn:hh:ss') }.should raise_error() + end + after do formats.time_formats.delete("h o'clock") + formats.time_formats.delete("ss:hh:nn") end end diff --git a/spec/validations_spec.rb b/spec/validations_spec.rb index ed963b8..b5da8c7 100644 --- a/spec/validations_spec.rb +++ b/spec/validations_spec.rb @@ -53,10 +53,12 @@ describe ValidatesTimeliness::Validations do @person.should be_valid end - it "should be valid with values before epoch" do - @person.birth_date_and_time = "1960-01-31 12:12:12" - @person.birth_date = "1960-01-31" - @person.birth_time = "23:59" + # What is going on? No fall back. + it "should be valid with values before out of Time range" do + @person.birth_date_and_time = "1890-01-31 12:12:12" + @person.birth_date = "1890-01-31" + @person.birth_time = "23:59:59" + puts @person.errors.inspect @person.should be_valid end @@ -74,7 +76,7 @@ describe ValidatesTimeliness::Validations do before :all do class DateTimeBeforeAfter < Person validates_timeliness_of :birth_date_and_time, :type => :datetime, - :before => Time.now, :after => 1.day.ago + :before => lambda { Time.now }, :after => lambda { 1.day.ago} end end @@ -111,8 +113,8 @@ describe ValidatesTimeliness::Validations do before :all do class DateTimeOnOrBeforeAndAfter < Person validates_timeliness_of :birth_date_and_time, :type => :datetime, - :on_or_before => Time.now.at_midnight, - :on_or_after => 1.day.ago + :on_or_before => lambda { Time.now.at_midnight }, + :on_or_after => lambda { 1.day.ago } end end @@ -311,8 +313,12 @@ describe ValidatesTimeliness::Validations do describe "with mixed value and restriction types" do before :all do class MixedBeforeAndAfter < Person - validates_timeliness_of :birth_date_and_time, :before => Date.new(2008,1,2), :after => lambda { Time.mktime(2008, 1, 1) } - validates_timeliness_of :birth_date, :type => :date, :on_or_before => Time.mktime(2008, 1, 2), :on_or_after => :birth_date_and_time + validates_timeliness_of :birth_date_and_time, + :before => Date.new(2008,1,2), + :after => lambda { Time.mktime(2008, 1, 1) } + validates_timeliness_of :birth_date, :type => :date, + :on_or_before => lambda { Time.mktime(2008, 1, 2) }, + :on_or_after => :birth_date_and_time end end