From c64a7ba3cd7301ee1f8f3bfeec987decaef93d70 Mon Sep 17 00:00:00 2001 From: wvengen Date: Mon, 2 Jun 2014 15:47:29 +0200 Subject: [PATCH] validate date and time inputs --- app/inputs/date_picker_time_input.rb | 22 ++++++---- app/models/order.rb | 3 +- lib/date_time_attribute_validate.rb | 66 ++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 9 deletions(-) create mode 100644 lib/date_time_attribute_validate.rb diff --git a/app/inputs/date_picker_time_input.rb b/app/inputs/date_picker_time_input.rb index c261199f..6ab83a74 100644 --- a/app/inputs/date_picker_time_input.rb +++ b/app/inputs/date_picker_time_input.rb @@ -1,7 +1,9 @@ -# DateTime picker using bootstrap-datepicker for the time part -# requires `date_time_attribute` gem and active on the attribute -# http://stackoverflow.com/a/20317763/2866660 -# https://github.com/einzige/date_time_attribute +# DateTime picker using bootstrap-datepicker for the time part. +# +# Requires +date_time_attribute+ gem (+workaround) and active on the attribute. +# @see DateTimeAttributeValidate +# @see http://stackoverflow.com/a/20317763/2866660 +# @see https://github.com/einzige/date_time_attribute class DatePickerTimeInput < SimpleForm::Inputs::StringInput def input # Date format must match datepicker's, see app/assets/application.js . @@ -9,11 +11,15 @@ class DatePickerTimeInput < SimpleForm::Inputs::StringInput # In the future, use html5 date&time inputs. This needs modernizr or equiv. to avoid # double widgets, and perhaps conditional css to adjust input width (chrome). value = @builder.object.send attribute_name - date_options = {as: :string, class: 'input-small datepicker', value: value.try {|e| e.strftime('%Y-%m-%d')}} - time_options = {as: :string, class: 'input-mini', value: value.try {|e| e.strftime('%H:%M')}} - @builder.input_field("#{attribute_name}_date", input_html_options.merge(date_options)) + ' ' + - @builder.input_field("#{attribute_name}_time", input_html_options.merge(time_options)) + date_options = {as: :string, class: 'input-small datepicker'} + time_options = {as: :string, class: 'input-mini'} + @builder.input_field("#{attribute_name}_date_value", input_html_options.merge(date_options)) + ' ' + + @builder.input_field("#{attribute_name}_time_value", input_html_options.merge(time_options)) # time_select requires a date_select #@builder.time_select("#{attribute_name}_time", {ignore_date: true}, input_html_options.merge(time_options)) end + + def label_target + "#{attribute_name}_date_value" + end end diff --git a/app/models/order.rb b/app/models/order.rb index 21a1cdc8..94e6bb79 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -32,7 +32,8 @@ class Order < ActiveRecord::Base scope :recent, -> { order('starts DESC').limit(10) } # Allow separate inputs for date and time - include DateTimeAttribute + # with workaround for https://github.com/einzige/date_time_attribute/issues/14 + include DateTimeAttributeValidate date_time_attribute :starts, :ends def stockit? diff --git a/lib/date_time_attribute_validate.rb b/lib/date_time_attribute_validate.rb new file mode 100644 index 00000000..bfd85f6b --- /dev/null +++ b/lib/date_time_attribute_validate.rb @@ -0,0 +1,66 @@ +# workaround for https://github.com/einzige/date_time_attribute/issues/14 +require 'date_time_attribute' + +module DateTimeAttributeValidate + extend ActiveSupport::Concern + include DateTimeAttribute + + module ClassMethods + + def date_time_attribute(*attributes) + super + + attributes.each do |attribute| + + validate "#{attribute}_datetime_value_valid" + + # allow resetting the field to nil + before_validation do + if self.instance_variable_get("@#{attribute}_is_set") + date = self.instance_variable_get("@#{attribute}_date_value") + time = self.instance_variable_get("@#{attribute}_time_value") + if date.blank? and time.blank? + self.send("#{attribute}=", nil) + end + end + end + + # remember old date and time values + define_method("#{attribute}_date_value=") do |val| + self.instance_variable_set("@#{attribute}_is_set", true) + self.instance_variable_set("@#{attribute}_date_value", val) + self.send("#{attribute}_date=", val) rescue nil + end + define_method("#{attribute}_time_value=") do |val| + self.instance_variable_set("@#{attribute}_is_set", true) + self.instance_variable_set("@#{attribute}_time_value", val) + self.send("#{attribute}_time=", val) rescue nil + end + + # fallback to field when values are not set + define_method("#{attribute}_date_value") do + self.instance_variable_get("@#{attribute}_date_value") || self.send("#{attribute}_date").try {|e| e.strftime('%Y-%m-%d')} + end + define_method("#{attribute}_time_value") do + self.instance_variable_get("@#{attribute}_time_value") || self.send("#{attribute}_time").try {|e| e.strftime('%H:%m')} + end + + private + + # validate date and time + define_method("#{attribute}_datetime_value_valid") do + date = self.instance_variable_get("@#{attribute}_date_value") + unless date.blank? or (Date.parse(date) rescue nil) + errors.add(attribute, "is not a valid date") # @todo I18n + end + time = self.instance_variable_get("@#{attribute}_time_value") + unless time.blank? or (Time.parse(time) rescue nil) + errors.add(attribute, "is not a valid time") # @todo I18n + end + end + + end + end + + end +end