diff --git a/Gemfile b/Gemfile index 5625c42..5f7d0a6 100644 --- a/Gemfile +++ b/Gemfile @@ -8,4 +8,5 @@ group :test do gem 'rspec', '>= 2.0.0.beta.17' gem 'rspec-rails', '>= 2.0.0.beta.17' gem 'timecop' + gem 'rspec_tag_matchers' end diff --git a/Gemfile.lock b/Gemfile.lock index cf7ac4e..2d95c0e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -43,7 +43,7 @@ GEM mime-types treetop (>= 1.4.5) mime-types (1.16) - nokogiri (1.4.2) + nokogiri (1.4.3.1) polyglot (0.3.1) rack (1.2.1) rack-mount (0.6.9) @@ -75,6 +75,9 @@ GEM rspec-rails (2.0.0.beta.19) rspec (= 2.0.0.beta.19) webrat (>= 0.7.2.beta.1) + rspec_tag_matchers (1.0.0) + nokogiri (>= 1.4.0) + rspec-rails (>= 1.2.6) ruby-debug (0.10.3) columnize (>= 0.1) ruby-debug-base (~> 0.10.3.0) @@ -99,6 +102,7 @@ DEPENDENCIES rails (= 3.0.0.rc) rspec (>= 2.0.0.beta.17) rspec-rails (>= 2.0.0.beta.17) + rspec_tag_matchers ruby-debug sqlite3-ruby timecop diff --git a/lib/validates_timeliness.rb b/lib/validates_timeliness.rb index 8a1bcfc..992da07 100644 --- a/lib/validates_timeliness.rb +++ b/lib/validates_timeliness.rb @@ -36,6 +36,7 @@ end require 'validates_timeliness/conversion' require 'validates_timeliness/validator' require 'validates_timeliness/helper_methods' +require 'validates_timeliness/extensions' require 'validates_timeliness/version' I18n.load_path << File.expand_path(File.dirname(__FILE__) + '/validates_timeliness/locale/en.yml') diff --git a/lib/validates_timeliness/extensions.rb b/lib/validates_timeliness/extensions.rb new file mode 100644 index 0000000..cf865fc --- /dev/null +++ b/lib/validates_timeliness/extensions.rb @@ -0,0 +1,9 @@ +module ValidatesTimeliness + module Extensions + autoload :DateTimeSelect, 'validates_timeliness/extensions/date_time_select' + end + + def self.enable_date_time_select_extension! + ::ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::Extensions::DateTimeSelect) + end +end diff --git a/lib/validates_timeliness/extensions/date_time_select.rb b/lib/validates_timeliness/extensions/date_time_select.rb new file mode 100644 index 0000000..9eacd7f --- /dev/null +++ b/lib/validates_timeliness/extensions/date_time_select.rb @@ -0,0 +1,45 @@ +module ValidatesTimeliness + module Extensions + module DateTimeSelect + extend ActiveSupport::Concern + + # Intercepts the date and time select helpers to reuse the values from the + # the params rather than the parsed value. This allows invalid date/time + # values to be redisplayed instead of blanks to aid correction by the user. + # Its a minor usability improvement which is rarely an issue for the user. + + included do + alias_method_chain :datetime_selector, :timeliness + alias_method_chain :value, :timeliness + end + + module InstanceMethods + + TimelinessDateTime = Struct.new(:year, :month, :day, :hour, :min, :sec) + + def datetime_selector_with_timeliness(*args) + @timeliness_date_or_time_tag = true + datetime_selector_without_timeliness(*args) + end + + def value_with_timeliness(object) + unless @timeliness_date_or_time_tag && @template_object.params[@object_name] + return value_without_timeliness(object) + end + + pairs = @template_object.params[@object_name].select {|k,v| k =~ /^#{@method_name}\(/ } + return value_without_timeliness(object) if pairs.empty? + + values = [nil] * 6 + pairs.map do |(param, value)| + position = param.scan(/\(([0-9]*).*\)/).first.first + values[position.to_i-1] = value + end + + TimelinessDateTime.new(*values) + end + end + + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 59ec910..0ed8020 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,12 +7,16 @@ require 'rspec/autorun' require 'active_model' require 'active_model/validations' require 'active_record' +require 'action_view' require 'timecop' +require 'rspec_tag_matchers' +require 'model_helpers' require 'validates_timeliness' ValidatesTimeliness.setup do |c| c.extend_classes = [ ActiveModel::Validations, ActiveRecord::Base ] + c.enable_date_time_select_extension! end Time.zone = 'Australia/Melbourne' @@ -33,10 +37,24 @@ class Person end end -require 'model_helpers' +ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'}) +ActiveRecord::Migration.verbose = false +ActiveRecord::Schema.define(:version => 1) do + create_table :employees, :force => true do |t| + t.string :first_name + t.string :last_name + t.datetime :birth_date + t.datetime :birth_time + t.datetime :birth_datetime + end +end + +class Employee < ActiveRecord::Base +end Rspec.configure do |c| c.mock_with :rspec + c.include(RspecTagMatchers) c.before do Person.reset_callbacks(:validate) Person._validators.clear diff --git a/spec/validates_timeliness/extensions/date_time_select_spec.rb b/spec/validates_timeliness/extensions/date_time_select_spec.rb new file mode 100644 index 0000000..44ecd9e --- /dev/null +++ b/spec/validates_timeliness/extensions/date_time_select_spec.rb @@ -0,0 +1,179 @@ +# require 'spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') + +describe ValidatesTimeliness::Extensions::DateTimeSelect do + include ActionView::Helpers::DateHelper + + attr_reader :employee, :params + + before do + @employee = Employee.new + @params = {} + end + + describe "datetime_select" do + it "should use param values when attribute is nil" do + params["employee"] = { + "birth_datetime(1i)" => 2009, + "birth_datetime(2i)" => 2, + "birth_datetime(3i)" => 29, + "birth_datetime(4i)" => 12, + "birth_datetime(5i)" => 13, + "birth_datetime(6i)" => 14, + } + employee.birth_datetime = nil + output = datetime_select(:employee, :birth_datetime, :include_blank => true, :include_seconds => true) + output.should have_tag('select[id=employee_birth_datetime_1i] option[selected=selected]', '2009') + output.should have_tag('select[id=employee_birth_datetime_2i] option[selected=selected]', 'February') + output.should have_tag('select[id=employee_birth_datetime_3i] option[selected=selected]', '29') + output.should have_tag('select[id=employee_birth_datetime_4i] option[selected=selected]', '12') + output.should have_tag('select[id=employee_birth_datetime_5i] option[selected=selected]', '13') + output.should have_tag('select[id=employee_birth_datetime_6i] option[selected=selected]', '14') + end + + it "should override object values and use params if present" do + params["employee"] = { + "birth_datetime(1i)" => 2009, + "birth_datetime(2i)" => 2, + "birth_datetime(3i)" => 29, + "birth_datetime(4i)" => 12, + "birth_datetime(5i)" => 13, + "birth_datetime(6i)" => 14, + } + employee.birth_datetime = "2010-01-01 15:16:17" + output = datetime_select(:employee, :birth_datetime, :include_blank => true, :include_seconds => true) + output.should have_tag('select[id=employee_birth_datetime_1i] option[selected=selected]', '2009') + output.should have_tag('select[id=employee_birth_datetime_2i] option[selected=selected]', 'February') + output.should have_tag('select[id=employee_birth_datetime_3i] option[selected=selected]', '29') + output.should have_tag('select[id=employee_birth_datetime_4i] option[selected=selected]', '12') + output.should have_tag('select[id=employee_birth_datetime_5i] option[selected=selected]', '13') + output.should have_tag('select[id=employee_birth_datetime_6i] option[selected=selected]', '14') + end + + it "should use attribute values from object if no params" do + employee.birth_datetime = "2009-01-02 12:13:14" + output = datetime_select(:employee, :birth_datetime, :include_blank => true, :include_seconds => true) + output.should have_tag('select[id=employee_birth_datetime_1i] option[selected=selected]', '2009') + output.should have_tag('select[id=employee_birth_datetime_2i] option[selected=selected]', 'January') + output.should have_tag('select[id=employee_birth_datetime_3i] option[selected=selected]', '2') + output.should have_tag('select[id=employee_birth_datetime_4i] option[selected=selected]', '12') + output.should have_tag('select[id=employee_birth_datetime_5i] option[selected=selected]', '13') + output.should have_tag('select[id=employee_birth_datetime_6i] option[selected=selected]', '14') + end + + it "should use attribute values if params does not contain attribute params" do + employee.birth_datetime = "2009-01-02 12:13:14" + params["employee"] = { } + output = datetime_select(:employee, :birth_datetime, :include_blank => true, :include_seconds => true) + output.should have_tag('select[id=employee_birth_datetime_1i] option[selected=selected]', '2009') + output.should have_tag('select[id=employee_birth_datetime_2i] option[selected=selected]', 'January') + output.should have_tag('select[id=employee_birth_datetime_3i] option[selected=selected]', '2') + output.should have_tag('select[id=employee_birth_datetime_4i] option[selected=selected]', '12') + output.should have_tag('select[id=employee_birth_datetime_5i] option[selected=selected]', '13') + output.should have_tag('select[id=employee_birth_datetime_6i] option[selected=selected]', '14') + end + + it "should not select values when attribute value is nil and has no param values" do + employee.birth_datetime = nil + output = datetime_select(:employee, :birth_datetime, :include_blank => true, :include_seconds => true) + output.should_not have_tag('select[id=employee_birth_datetime_1i] option[selected=selected]') + output.should_not have_tag('select[id=employee_birth_datetime_2i] option[selected=selected]') + output.should_not have_tag('select[id=employee_birth_datetime_3i] option[selected=selected]') + output.should_not have_tag('select[id=employee_birth_datetime_4i] option[selected=selected]') + output.should_not have_tag('select[id=employee_birth_datetime_5i] option[selected=selected]') + output.should_not have_tag('select[id=employee_birth_datetime_6i] option[selected=selected]') + end + end + + describe "date_select" do + it "should use param values when attribute is nil" do + params["employee"] = { + "birth_date(1i)" => 2009, + "birth_date(2i)" => 2, + "birth_date(3i)" => 29, + } + employee.birth_date = nil + output = date_select(:employee, :birth_date, :include_blank => true, :include_seconds => true) + output.should have_tag('select[id=employee_birth_date_1i] option[selected=selected]', '2009') + output.should have_tag('select[id=employee_birth_date_2i] option[selected=selected]', 'February') + output.should have_tag('select[id=employee_birth_date_3i] option[selected=selected]', '29') + end + + it "should override object values and use params if present" do + params["employee"] = { + "birth_date(1i)" => 2009, + "birth_date(2i)" => 2, + "birth_date(3i)" => 29, + } + employee.birth_date = "2009-03-01" + output = date_select(:employee, :birth_date, :include_blank => true, :include_seconds => true) + output.should have_tag('select[id=employee_birth_date_1i] option[selected=selected]', '2009') + output.should have_tag('select[id=employee_birth_date_2i] option[selected=selected]', 'February') + output.should have_tag('select[id=employee_birth_date_3i] option[selected=selected]', '29') + end + + it "should select attribute values from object if no params" do + employee.birth_date = "2009-01-02" + output = date_select(:employee, :birth_date, :include_blank => true, :include_seconds => true) + output.should have_tag('select[id=employee_birth_date_1i] option[selected=selected]', '2009') + output.should have_tag('select[id=employee_birth_date_2i] option[selected=selected]', 'January') + output.should have_tag('select[id=employee_birth_date_3i] option[selected=selected]', '2') + end + + it "should select attribute values if params does not contain attribute params" do + employee.birth_date = "2009-01-02" + params["employee"] = { } + output = date_select(:employee, :birth_date, :include_blank => true, :include_seconds => true) + output.should have_tag('select[id=employee_birth_date_1i] option[selected=selected]', '2009') + output.should have_tag('select[id=employee_birth_date_2i] option[selected=selected]', 'January') + output.should have_tag('select[id=employee_birth_date_3i] option[selected=selected]', '2') + end + + it "should not select values when attribute value is nil and has no param values" do + employee.birth_date = nil + output = date_select(:employee, :birth_date, :include_blank => true, :include_seconds => true) + output.should_not have_tag('select[id=employee_birth_date_1i] option[selected=selected]') + output.should_not have_tag('select[id=employee_birth_date_2i] option[selected=selected]') + output.should_not have_tag('select[id=employee_birth_date_3i] option[selected=selected]') + end + end + + describe "time_select" do + before do + Timecop.freeze Time.mktime(2009,1,1) + end + + it "should use param values when attribute is nil" do + params["employee"] = { + "birth_time(1i)" => 2000, + "birth_time(2i)" => 1, + "birth_time(3i)" => 1, + "birth_time(4i)" => 12, + "birth_time(5i)" => 13, + "birth_time(6i)" => 14, + } + employee.birth_time = nil + output = time_select(:employee, :birth_time, :include_blank => true, :include_seconds => true) + output.should have_tag('select[id=employee_birth_time_4i] option[selected=selected]', '12') + output.should have_tag('select[id=employee_birth_time_5i] option[selected=selected]', '13') + output.should have_tag('select[id=employee_birth_time_6i] option[selected=selected]', '14') + end + + it "should select attribute values from object if no params" do + employee.birth_time = "2000-01-01 12:13:14" + output = time_select(:employee, :birth_time, :include_blank => true, :include_seconds => true) + output.should have_tag('select[id=employee_birth_time_4i] option[selected=selected]', '12') + output.should have_tag('select[id=employee_birth_time_5i] option[selected=selected]', '13') + output.should have_tag('select[id=employee_birth_time_6i] option[selected=selected]', '14') + end + + it "should not select values when attribute value is nil and has no param values" do + employee.birth_time = nil + output = time_select(:employee, :birth_time, :include_blank => true, :include_seconds => true) + output.should_not have_tag('select[id=employee_birth_time_4i] option[selected=selected]') + output.should_not have_tag('select[id=employee_birth_time_5i] option[selected=selected]') + output.should_not have_tag('select[id=employee_birth_time_6i] option[selected=selected]') + end + end + +end