initial Rails 3 rewrite commit

completely rewritten for ActiveModel compatibility
uses ActiveModel EachValidator class as validator base class
simplifies :between by splitting into a :on_or_before and an :on_of_after
only :is_at option tested
This commit is contained in:
Adam Meehan 2010-08-01 18:35:18 +10:00
commit fdc3086976
18 changed files with 642 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
pkg/

2
.rspec Normal file
View File

@ -0,0 +1,2 @@
--format nested
--color

11
Gemfile Normal file
View File

@ -0,0 +1,11 @@
source 'http://rubygems.org'
group :test do
gem 'ZenTest'
gem 'rails', '3.0.0.rc'
gem 'sqlite3-ruby', :require => 'sqlite3'
gem 'ruby-debug'
gem 'rspec', '>= 2.0.0.beta.17'
gem 'rspec-rails', '>= 2.0.0.beta.17'
gem 'timecop'
end

104
Gemfile.lock Normal file
View File

@ -0,0 +1,104 @@
GEM
remote: http://rubygems.org/
specs:
ZenTest (4.3.3)
abstract (1.0.0)
actionmailer (3.0.0.rc)
actionpack (= 3.0.0.rc)
mail (~> 2.2.5)
actionpack (3.0.0.rc)
activemodel (= 3.0.0.rc)
activesupport (= 3.0.0.rc)
builder (~> 2.1.2)
erubis (~> 2.6.6)
i18n (~> 0.4.1)
rack (~> 1.2.1)
rack-mount (~> 0.6.9)
rack-test (~> 0.5.4)
tzinfo (~> 0.3.22)
activemodel (3.0.0.rc)
activesupport (= 3.0.0.rc)
builder (~> 2.1.2)
i18n (~> 0.4.1)
activerecord (3.0.0.rc)
activemodel (= 3.0.0.rc)
activesupport (= 3.0.0.rc)
arel (~> 0.4.0)
tzinfo (~> 0.3.22)
activeresource (3.0.0.rc)
activemodel (= 3.0.0.rc)
activesupport (= 3.0.0.rc)
activesupport (3.0.0.rc)
arel (0.4.0)
activesupport (>= 3.0.0.beta)
builder (2.1.2)
columnize (0.3.1)
diff-lcs (1.1.2)
erubis (2.6.6)
abstract (>= 1.0.0)
i18n (0.4.1)
linecache (0.43)
mail (2.2.5)
activesupport (>= 2.3.6)
mime-types
treetop (>= 1.4.5)
mime-types (1.16)
nokogiri (1.4.2)
polyglot (0.3.1)
rack (1.2.1)
rack-mount (0.6.9)
rack (>= 1.0.0)
rack-test (0.5.4)
rack (>= 1.0)
rails (3.0.0.rc)
actionmailer (= 3.0.0.rc)
actionpack (= 3.0.0.rc)
activerecord (= 3.0.0.rc)
activeresource (= 3.0.0.rc)
activesupport (= 3.0.0.rc)
bundler (>= 1.0.0.rc.1)
railties (= 3.0.0.rc)
railties (3.0.0.rc)
actionpack (= 3.0.0.rc)
activesupport (= 3.0.0.rc)
rake (>= 0.8.3)
thor (~> 0.14.0)
rake (0.8.7)
rspec (2.0.0.beta.19)
rspec-core (= 2.0.0.beta.19)
rspec-expectations (= 2.0.0.beta.19)
rspec-mocks (= 2.0.0.beta.19)
rspec-core (2.0.0.beta.19)
rspec-expectations (2.0.0.beta.19)
diff-lcs (>= 1.1.2)
rspec-mocks (2.0.0.beta.19)
rspec-rails (2.0.0.beta.19)
rspec (= 2.0.0.beta.19)
webrat (>= 0.7.2.beta.1)
ruby-debug (0.10.3)
columnize (>= 0.1)
ruby-debug-base (~> 0.10.3.0)
ruby-debug-base (0.10.3)
linecache (>= 0.3)
sqlite3-ruby (1.3.1)
thor (0.14.0)
timecop (0.3.5)
treetop (1.4.8)
polyglot (>= 0.3.1)
tzinfo (0.3.22)
webrat (0.7.2.beta.1)
nokogiri (>= 1.2.0)
rack (>= 1.0)
rack-test (>= 0.5.3)
PLATFORMS
ruby
DEPENDENCIES
ZenTest
rails (= 3.0.0.rc)
rspec (>= 2.0.0.beta.17)
rspec-rails (>= 2.0.0.beta.17)
ruby-debug
sqlite3-ruby
timecop

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2008-2010 Adam Meehan
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

44
Rakefile Normal file
View File

@ -0,0 +1,44 @@
require 'rubygems'
require 'rake/rdoctask'
require 'rake/gempackagetask'
require 'rubygems/specification'
require 'rspec/core/rake_task'
require 'lib/validates_timeliness/version'
GEM_NAME = "active_enum"
GEM_VERSION = ValidatesTimeliness::VERSION
desc 'Default: run specs.'
task :default => :spec
desc "Run specs"
RSpec::Core::RakeTask.new do |t|
t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
end
desc "Generate code coverage"
RSpec::Core::RakeTask.new(:coverage) do |t|
t.rcov = true
t.rcov_opts = ['--exclude', 'spec']
end
desc 'Generate documentation for plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'ActiveEnum'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
end
desc "install the gem locally"
task :install => [:package] do
sh %{gem install pkg/#{GEM_NAME}-#{GEM_VERSION}}
end
desc "create a gemspec file"
task :make_spec do
File.open("#{GEM_NAME}.gemspec", "w") do |file|
file.puts spec.to_ruby
end
end

1
autotest/discover.rb Normal file
View File

@ -0,0 +1 @@
Autotest.add_discovery { "rspec2" }

View File

@ -0,0 +1,26 @@
require 'date'
require 'active_support/time_with_zone'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/string/conversions'
require 'active_support/core_ext/date/acts_like'
require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/date/zones'
require 'active_support/core_ext/time/acts_like'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/date_time/acts_like'
require 'active_support/core_ext/date_time/conversions'
require 'active_support/core_ext/date_time/zones'
module ValidatesTimeliness
# Set the dummy date part for a time type values.
mattr_accessor :dummy_date_for_time_type
@@dummy_date_for_time_type = [ 2000, 1, 1 ]
end
require 'validates_timeliness/conversion'
require 'validates_timeliness/validator'
require 'validates_timeliness/helper_methods'
require 'validates_timeliness/version'

View File

@ -0,0 +1,27 @@
module ValidatesTimeliness
module Conversion
def type_cast_value(value, type)
value = case type
when :time
dummy_time(value)
when :date
value.to_date
when :datetime
value.to_time.in_time_zone
end
end
def dummy_time(value)
time = if value.acts_like?(:time)
value = value.in_time_zone
[value.hour, value.min, value.sec]
else
[0,0,0]
end
dummy_date = ValidatesTimeliness.dummy_date_for_time_type
Time.local(*(dummy_date + time))
end
end
end

View File

@ -0,0 +1,19 @@
module ValidatesTimeliness
module HelperMethods
def validates_date(*attr_names)
validates_with Validator, _merge_attributes(attr_names).merge(:type => :date)
end
def validates_time(*attr_names)
validates_with Validator, _merge_attributes(attr_names).merge(:type => :time)
end
def validates_datetime(*attr_names)
validates_with Validator, _merge_attributes(attr_names).merge(:type => :datetime)
end
end
end
module ActiveModel::Validations::HelperMethods
include ValidatesTimeliness::HelperMethods
end

View File

@ -0,0 +1,16 @@
en:
errors:
messages:
invalid_date: "is not a valid date"
invalid_time: "is not a valid time"
invalid_datetime: "is not a valid datetime"
is_at: "must be at %{restriction}"
before: "must be before %{restriction}"
on_or_before: "must be on or before %{restriction}"
after: "must be after %{restriction}"
on_or_after: "must be on or after %{restriction}"
validates_timeliness:
error_value_formats:
date: '%Y-%m-%d'
time: '%H:%M:%S'
datetime: '%Y-%m-%d %H:%M:%S'

View File

@ -0,0 +1,61 @@
require 'active_model/validator'
module ValidatesTimeliness
class Validator < ActiveModel::EachValidator
include Conversion
CHECKS = {
:is_at => :==,
:before => :<,
:after => :>,
:on_or_before => :<=,
:on_or_after => :>=,
}.freeze
def self.kind
:timeliness
end
def initialize(options)
@allow_nil, @allow_blank = options.delete(:allow_nil), options.delete(:allow_blank)
@type = options.delete(:type)
if range = options.delete(:between)
raise ArgumentError, ":between must be a Range or an Array" unless range.is_a?(Range) || range.is_a?(Array)
options[:on_or_after], options[:on_or_before] = range.begin, range.end
end
super
end
def check_validity!
end
def validate_each(record, attr_name, value)
raw_value = attribute_raw_value(record, attr_name) || value
return if (@allow_nil && raw_value.nil?) || (@allow_blank && raw_value.blank?)
return record.errors.add(attr_name, :"invalid_#{@type}") if value.blank?
value = type_cast(value)
(CHECKS.keys & options.keys).each do |check|
check_value = type_cast(options[check])
unless value.send(CHECKS[check], check_value)
return record.errors.add(attr_name, check, :restriction => check_value)
end
end
end
def attribute_raw_value(record, attr_name)
before_type_cast = "#{attr_name}_before_type_cast"
record.send("#{attr_name}_before_type_cast") if record.respond_to?(before_type_cast)
end
def type_cast(value)
type_cast_value(value, @type)
end
end
end
# Compatibility with ActiveModel validates method which tries match option keys to their validator class
TimelinessValidator = ValidatesTimeliness::Validator

View File

@ -0,0 +1,3 @@
module ValidatesTimeliness
VERSION = '3.0.0'
end

27
spec/model_helpers.rb Normal file
View File

@ -0,0 +1,27 @@
module ModelHelpers
# Some test helpers from Rails source
def invalid!(attr_name, values, error = nil)
with_each_person_value(attr_name, values) do |record, value|
record.should be_invalid
record.errors[attr_name].size.should >= 1
record.errors[attr_name].first.should == error if error
end
end
def valid!(attr_name, values)
with_each_person_value(attr_name, values) do |record, value|
record.should be_valid
end
end
def with_each_person_value(attr_name, values)
record = Person.new
values = [values] unless values.is_a?(Array)
values.each do |value|
record.send("#{attr_name}=", value)
yield record, value
end
end
end

44
spec/spec_helper.rb Normal file
View File

@ -0,0 +1,44 @@
$:.unshift(File.dirname(__FILE__) + '/../lib')
$:.unshift(File.dirname(__FILE__))
require 'rspec'
require 'rspec/autorun'
require 'active_model'
require 'active_model/validations'
# require 'active_record'
# require 'action_controller'
# require 'action_view'
# require 'action_mailer'
# require 'rspec/rails'
require 'timecop'
require 'model_helpers'
require 'validates_timeliness'
Time.zone = 'Australia/Melbourne'
LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/../lib/validates_timeliness/locale/en.yml')
I18n.load_path.unshift(LOCALE_PATH)
class Person
include ActiveModel::Validations
extend ActiveModel::Translation
attr_accessor :birth_date, :birth_time, :birth_datetime
def initialize(attributes = {})
attributes.each do |key, value|
send "#{key}=", value
end
end
end
Rspec.configure do |c|
c.mock_with :rspec
c.before do
Person.reset_callbacks(:validate)
Person._validators.clear
end
end

View File

@ -0,0 +1,102 @@
require 'spec_helper'
describe ValidatesTimeliness::Conversion do
include ValidatesTimeliness::Conversion
before do
Timecop.freeze(Time.mktime(2010, 1, 1, 0, 0, 0))
end
describe "#type_cast_value" do
describe "for date type" do
it "should return same value for date value" do
type_cast_value(Date.new(2010, 1, 1), :date).should == Date.new(2010, 1, 1)
end
it "should return date part of time value" do
type_cast_value(Time.mktime(2010, 1, 1, 0, 0, 0), :date).should == Date.new(2010, 1, 1)
end
it "should return date part of datetime value" do
type_cast_value(DateTime.new(2010, 1, 1, 0, 0, 0), :date).should == Date.new(2010, 1, 1)
end
end
describe "for time type" do
it "should return same value for time value matching dummy date part" do
type_cast_value(Time.mktime(2000, 1, 1, 0, 0, 0), :time).should == Time.mktime(2000, 1, 1, 0, 0, 0)
end
it "should return dummy time value with same time part for time value with different date" do
type_cast_value(Time.mktime(2010, 1, 1, 0, 0, 0), :time).should == Time.mktime(2000, 1, 1, 0, 0, 0)
end
it "should return dummy time only for date value" do
type_cast_value(Date.new(2010, 1, 1), :time).should == Time.mktime(2000, 1, 1, 0, 0, 0)
end
it "should return dummy date with shifted local time for UTC datetime value" do
type_cast_value(DateTime.new(2010, 1, 1, 12, 34, 56), :time).should == Time.mktime(2000, 1, 1, 23, 34, 56)
end
it "should return dummy date with time part for local datetime value" do
type_cast_value(DateTime.civil_from_format(:local, 2010, 1, 1, 12, 34, 56), :time).should == Time.mktime(2000, 1, 1, 12, 34, 56)
end
end
describe "for datetime type" do
it "should return time in local offset for date value" do
type_cast_value(Date.new(2010, 1, 1), :datetime).should == Time.local_time(2010, 1, 1, 0, 0, 0)
end
it "should return same value for same time value in local offset" do
type_cast_value(Time.local_time(2010, 1, 1, 12, 34, 56), :datetime).should == Time.local_time(2010, 1, 1, 12, 34, 56)
end
it "should return shifted local time value for UTC time value" do
type_cast_value(Time.utc(2010, 1, 1, 12, 34, 56), :datetime).should == Time.local_time(2010, 1, 1, 23, 34, 56)
end
it "should return shifted local time value for UTC datetime value" do
type_cast_value(DateTime.new(2010, 1, 1, 12, 34, 56), :datetime).should == Time.local_time(2010, 1, 1, 23, 34, 56)
end
it "should return time value with same component values for local datetime value" do
type_cast_value(DateTime.civil_from_format(:local, 2010, 1, 1, 12, 34, 56), :datetime).should == Time.local_time(2010, 1, 1, 12, 34, 56)
end
end
end
describe "#dummy_time" do
it 'should return dummy date with shifted local time part for UTC time value' do
dummy_time(Time.utc(2010, 11, 22, 12, 34, 56)).should == Time.local_time(2000, 1, 1, 23, 34, 56)
end
it 'should return dummy date with same time part part for local time value with non-dummy date' do
dummy_time(Time.local_time(2010, 11, 22, 12, 34, 56)).should == Time.local_time(2000, 1, 1, 12, 34, 56)
end
it 'should return same value for local time with dummy date' do
dummy_time(Time.local_time(2000, 1, 1, 12, 34, 56)).should == Time.local_time(2000, 1, 1, 12, 34, 56)
end
it 'should return exact dummy time value for date value' do
dummy_time(Date.new(2010, 11, 22)).should == Time.mktime(2000, 1, 1, 0, 0, 0)
end
describe "with custom dummy date" do
before(:all) do
@@original_dummy_date = ValidatesTimeliness.dummy_date_for_time_type
ValidatesTimeliness.dummy_date_for_time_type = [2010, 1, 1]
end
it 'should return dummy time with custom dummy date' do
dummy_time(Time.local_time(1999, 11, 22, 12, 34, 56)).should == Time.local_time(2010, 1, 1, 12, 34, 56)
end
after(:all) do
ValidatesTimeliness.dummy_date_for_time_type = @@original_dummy_date
end
end
end
end

View File

@ -0,0 +1,85 @@
require 'spec_helper'
describe ValidatesTimeliness::Validator, ":is_at option" do
include ModelHelpers
before do
Timecop.freeze(Time.local_time(2010, 1, 1, 0, 0, 0))
end
describe "for date type" do
it "should be equal to same date value" do
Person.validates_date :birth_date, :is_at => Date.new(2010, 1, 1)
valid!(:birth_date, Date.new(2010, 1, 1))
end
it "should be equal to Time with same date part" do
Person.validates_date :birth_date, :is_at => Time.local_time(2010, 1, 1, 0, 0, 0)
valid!(:birth_date, Date.new(2010, 1, 1))
end
it "should be equal to DateTime with same date part" do
Person.validates_date :birth_date, :is_at => DateTime.civil_from_format(:local, 2010, 1, 1, 0, 0, 0)
valid!(:birth_date, Date.new(2010, 1, 1))
end
end
describe "for time type" do
it "should be equal to Date if attribute value is midnight" do
Person.validates_time :birth_time, :is_at => Date.new(2010, 1, 1)
valid!(:birth_time, Time.local_time(2000, 1, 1, 0, 0, 0))
end
it "should not be be equal to Date if attribute value is other than midnight" do
Person.validates_time :birth_time, :is_at => Date.new(2010, 1, 1)
invalid!(:birth_time, Time.local_time(2000, 1, 1, 9, 30, 0))
end
it "should be equal to local Time with same time part" do
Person.validates_time :birth_time, :is_at => Time.local_time(2010, 1, 1, 0, 0, 0)
valid!(:birth_time, Time.local_time(2000, 1, 1, 0, 0, 0))
end
it "should not be equal to UTC Time with same time part" do
Person.validates_time :birth_time, :is_at => Time.utc(2010, 1, 1, 0, 0, 0)
invalid!(:birth_time, Time.local_time(2000, 1, 1, 0, 0, 0))
end
it "should be equal to local DateTime with same time part" do
Person.validates_time :birth_time, :is_at => DateTime.civil_from_format(:local, 2010, 1, 1, 0, 0, 0)
valid!(:birth_time, Time.local_time(2000, 1, 1, 0, 0, 0))
end
it "should not be equal to UTC DateTime with same time part" do
Person.validates_time :birth_time, :is_at => DateTime.new(2010, 1, 1, 0, 0, 0)
invalid!(:birth_time, Time.local_time(2000, 1, 1, 0, 0, 0))
end
end
describe "for datetime type" do
it "should be equal to Date with same" do
Person.validates_datetime :birth_datetime, :is_at => Date.new(2010, 1, 1)
valid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 0, 0, 0))
end
it "should be equal to local Time with same component values" do
Person.validates_datetime :birth_datetime, :is_at => Time.local_time(2010, 1, 1, 0, 0, 0)
valid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 0, 0, 0))
end
it "should not be equal to UTC Time with same component values" do
Person.validates_datetime :birth_datetime, :is_at => Time.utc(2010, 1, 1, 0, 0, 0)
invalid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 0, 0, 0))
end
it "should be equal to same local DateTime value" do
Person.validates_datetime :birth_datetime, :is_at => DateTime.civil_from_format(:local, 2010, 1, 1, 0, 0, 0)
valid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 0, 0, 0))
end
it "should not be equal to UTC DateTime with same component values" do
Person.validates_datetime :birth_datetime, :is_at => DateTime.new(2010, 1, 1, 0, 0, 0)
invalid!(:birth_datetime, DateTime.civil_from_format(:local, 2010, 1, 1, 0, 0, 0))
end
end
end

View File

@ -0,0 +1,49 @@
require 'spec_helper'
describe ValidatesTimeliness::Validator do
include ModelHelpers
NIL = [nil]
before do
Timecop.freeze(Time.local_time(2010, 1, 1, 0, 0, 0))
end
it 'should return validator kind as :timeliness' do
ValidatesTimeliness::Validator.kind.should == :timeliness
end
describe "Model.validates :timeliness option" do
it 'should use plugin validator class' do
Person.validates :birth_date, :timeliness => {:is_at => Date.new(2010,1,1), :type => :date}
Person.validators.should have(1).kind_of(TimelinessValidator)
valid!(:birth_date, Date.new(2010,1,1))
invalid!(:birth_date, Date.new(2010,1,2))
end
end
describe ":allow_nil option" do
it 'should not allow nil by default' do
Person.validates_datetime :birth_date
invalid!(:birth_date, NIL)
valid!(:birth_date, Date.today)
end
it 'should allow nil when true' do
Person.validates_datetime :birth_date, :allow_nil => true
valid!(:birth_date, NIL)
end
end
describe ":allow_blank option" do
it 'should not allow blank by default' do
Person.validates_datetime :birth_date
invalid!(:birth_date, '')
valid!(:birth_date, Date.today)
end
it 'should allow blank when true' do
Person.validates_datetime :birth_date, :allow_blank => true
valid!(:birth_date, '')
end
end
end