Compare commits

..

165 Commits
0.1.0 ... 2.2.2

Author SHA1 Message Date
Adam Meehan
f109443fb7 version 2.2.2 2009-09-19 08:19:27 +10:00
Adam Meehan
96bf4bf184 fix dummy to respect timezones by using make_time 2009-09-19 07:30:33 +10:00
Adam Meehan
90be6a6db5 change to github issues for bug reports 2009-09-12 14:39:46 +10:00
Adam Meehan
4d5d82ff20 version 2.2.1 2009-09-12 14:16:44 +10:00
Adam Meehan
b11893eac0 fix dummy date part in Validator.type_cast_value
removed all core extensions
2009-09-12 14:14:37 +10:00
Adam Meehan
76e159b350 version 2.2.0 2009-09-12 13:43:50 +10:00
Adam Meehan
c762b6d4f8 added version file with VERSION constant 2009-09-12 13:42:07 +10:00
Adam Meehan
91f9f65bc0 tiny doc change 2009-09-12 13:36:57 +10:00
Adam Meehan
6db8b7d908 push dummy date value assignment into Formats.parse and allow custom values to be used 2009-09-12 13:07:01 +10:00
Adam Meehan
d3c5101f92 use implied_type in restriction evaluations 2009-09-12 13:05:12 +10:00
Adam Meehan
899e96b880 tiny clean up 2009-09-12 13:05:12 +10:00
Adam Meehan
162faf632a push strict override for format option into Formats.parse 2009-09-12 13:05:12 +10:00
Adam Meehan
1b865cc834 split ignore_sec into own describe 2009-09-12 13:05:11 +10:00
Adam Meehan
c29478df45 fix deprecation for ActiveRecord::Errors#generate_message in Rails 2.3.4
ginger scenario added 2.3.4
2009-09-12 13:05:11 +10:00
Adam Meehan
df3283e5a1 fix ignore_usec for with_date and with_time options 2009-09-12 13:05:04 +10:00
Adam Meehan
0e382e15f2 moved action view extension enable call to relevant spec 2009-09-11 13:13:02 +10:00
Adam Meehan
9a697b9cab finally fixed spec for latest rspec 2009-09-11 13:11:09 +10:00
Adam Meehan
7bf7ed0569 catch both possible exception types 2009-09-09 16:25:04 +10:00
Adam Meehan
a969a49ae8 little tweaks 2009-09-09 16:16:11 +10:00
Adam Meehan
687e61a3f2 Merge branch 'master' of github.com:adzap/validates_timeliness 2009-09-05 19:50:43 +10:00
Adam Meehan
4cc20ae620 fixed some bad rescue behaviour in parse method 2009-09-05 19:47:49 +10:00
adzap
2d510504e6 change to lambda in examples 2009-08-26 21:07:29 -07:00
Adam Meehan
2028d68b17 checking proc arity in option value for ruby 1.9 compat 2009-08-22 14:59:46 +10:00
Adam Meehan
e399c6b510 moved mutliparam helper methods our of AR to reduce method pollution 2009-07-28 12:52:25 +10:00
Adam Meehan
7aa1a87731 require matcher in spec helper 2009-07-28 12:51:32 +10:00
Adam Meehan
1a31e7463d have to manually require matcher now because the presence of the Spec
namespace caused issues for shoulda when not using rspec:wq
2009-07-07 15:32:15 +10:00
Adam Meehan
14c55e3292 version 2.1 for real 2009-06-20 22:55:59 +10:00
Adam Meehan
75a3b2bd83 version 2.1 2009-06-20 22:30:08 +10:00
Adam Meehan
9b300e084b change to spec/autorun 2009-06-20 19:43:13 +10:00
Adam Meehan
d61dddfbc6 fix i18n locale load order to allow customisation
fix interpolation values for i18n
2009-06-19 12:06:36 +10:00
Adam Meehan
4ac4281c8b rename named local var 2009-06-07 08:28:20 +10:00
Adam Meehan
a56cf674b2 removed reload alias by attributes_cache for before_type_cast value 2009-06-07 08:21:46 +10:00
Adam Meehan
7f4d7b38d7 update changelog 2009-06-06 16:56:14 +10:00
Adam Meehan
34c0f25225 dramatically simplify the before_type_cast hack and remove read method override
simplified write method with no dirty attributes hackery
2009-06-06 16:37:06 +10:00
Adam Meehan
57c3fdca88 set RAILS_ENV spec_helper 2009-06-06 11:36:03 +10:00
Adam Meehan
805bb5d7fd added another datetime format 2009-06-06 11:17:35 +10:00
Adam Meehan
7aac14c874 added ambiguous year threshold setting 2009-06-06 11:16:19 +10:00
Adam Meehan
7c0c1afe1c Merge branch 'master' of git@github.com:adzap/validates_timeliness 2009-05-26 12:01:27 +10:00
Adam Meehan
5295a494e9 token typo 2009-05-26 12:00:49 +10:00
adzap
572d80d227 doc fix 2009-04-19 05:40:36 -07:00
Adam Meehan
af3a3b84ab version 2.0.0 2009-04-12 11:13:52 +10:00
Adam Meehan
4df63c7142 another idea in TODO 2009-04-12 11:10:58 +10:00
Adam Meehan
26066dedfe update TODOs 2009-04-12 10:26:31 +10:00
Adam Meehan
3348ca3063 dont rely on add_error return value to exit. sigh, less one liners 2009-04-12 10:23:09 +10:00
Adam Meehan
88ee33ae41 consistently use instance methods vs vars in validator 2009-04-12 10:17:33 +10:00
Adam Meehan
51afb2852e update benchmark file 2009-04-12 09:44:46 +10:00
Adam Meehan
57b8a52f07 add :format option to readme 2009-04-10 11:01:19 +10:00
Adam Meehan
bb94e234bc add :format option to validate and parse with specific a format 2009-04-10 10:57:27 +10:00
Adam Meehan
f041524124 add equal_to option to matcher 2009-04-10 10:48:24 +10:00
Adam Meehan
e1d23d0f2b Merge branch '2.0.0' 2009-04-09 18:54:34 +10:00
Adam Meehan
613001791a limit time_array size explicity 2009-04-09 18:53:28 +10:00
Adam Meehan
6d12790d2b datetime select helper extension activation in readme 2009-04-06 17:34:23 +10:00
Adam Meehan
b197d537f1 fix read attribute bug for frozen record (reported by Les Nightingill) 2009-04-06 13:52:07 +10:00
Adam Meehan
1e3c802031 capture zone offset value in formats to possible usage 2009-03-31 21:35:49 +11:00
Adam Meehan
d89266d9f1 minor stuff 2009-03-31 21:25:05 +11:00
Adam Meehan
9e2d95c3e1 remove brittle and not very useful specs, which are covered elsewhere 2009-03-31 14:22:13 +11:00
Adam Meehan
fb520bbddc use plugin parser in action view extension 2009-03-31 14:21:07 +11:00
Adam Meehan
8303be05c3 little spec consistency 2009-03-30 21:03:51 +11:00
Adam Meehan
f4ed751c26 changed back to using error_value_formats for Rails 2.0/2.1 2009-03-28 19:51:11 +11:00
Adam Meehan
956933f58b disable multiparameter values extension by default for v2 2009-03-28 18:53:47 +11:00
Adam Meehan
7967b5a212 refactored error value formats to use locale file for I18n. Rail 2.0/2.1 to use default_error_value_formats now.
moved default_error_messages_method into validator
2009-03-28 18:49:26 +11:00
Adam Meehan
a836ed8434 changed Formats#parse to take options hash for strict and other possibilities 2009-03-28 17:35:41 +11:00
Adam Meehan
c2a4f45b5a removed old spec 2009-03-28 17:35:21 +11:00
Adam Meehan
312c1510cb refactored AR parsing methods into Parser module to reduce AR method pollution and make more consistent 2009-03-28 17:25:48 +11:00
Adam Meehan
88fce1d679 updated version number (1.1.7) 2009-03-26 16:49:19 +11:00
Adam Meehan
ffd8476f74 version 1.1.7 2009-03-26 16:46:53 +11:00
Adam Meehan
728c5ccda5 safely extract time values from multiparam array 2009-03-26 15:20:08 +11:00
Adam Meehan
4f0c81b6f8 properly chain multiparameter attributes 2009-03-26 15:13:35 +11:00
Adam Meehan
11e643c0fe version 1.1.6 2009-03-19 20:52:35 +11:00
Adam Meehan
d1ee94248b added :equal_to and :ignore_usec options. the later is for ignore microsecond value in datetime restrictions 2009-03-19 20:49:06 +11:00
Adam Meehan
eecef62de4 updating ginger scenarios with Rails 2.3.2 2009-03-19 20:47:50 +11:00
Adam Meehan
fae38cfecd use value as raw_value if object doesn't have _before_type_cast method 2009-03-12 19:08:31 +11:00
Adam Meehan
515a1b3126 make format sections titles 2009-03-12 07:33:13 +11:00
Adam Meehan
736b1b582f removed dupe example 2009-03-12 07:30:35 +11:00
Adam Meehan
903850bc23 Merge branch 'with' for with_date and with_time options 2009-03-10 15:51:07 +11:00
Adam Meehan
c3f3edf324 added enable method for multiparameter attribute handling of datetime which is enabled by default but will be off in version2
renamed enable action view extension method and enable both extensions in the one method as they are needed together
2009-03-08 17:55:48 +11:00
Adam Meehan
1dbac5190b fix last refactor so it casts Date object to time if attribute is datetime or time with spec added 2009-03-05 19:56:38 +11:00
Adam Meehan
497a97e0b0 cleanup setup and version check guff. enable action view extension by default until version 2 2009-03-05 18:34:16 +11:00
Adam Meehan
9dd3282a81 add method to enable action view invalid value extension. will be on by default but will off in version 2 2009-03-05 18:32:25 +11:00
Adam Meehan
e7e9a8b238 fix bad variable name 2009-03-05 18:30:48 +11:00
Adam Meehan
cb962d1157 some refactoring and behave even more friendly with alias_method_chain 2009-03-05 18:10:59 +11:00
Adam Meehan
403a91addf added if and unless to valid options 2009-03-04 21:09:51 +11:00
Adam Meehan
7ca84f3116 behaves as better AR citizen by alias_method_chaining define_attribute_methods and read_attribute instead of overriding 2009-03-03 12:38:54 +11:00
Adam Meehan
19457a6c1d removed some module inclusion silliness in spec 2009-02-27 23:40:14 +11:00
Adam Meehan
5e85649a34 readme plugin installation fix and gem dependency 2009-02-26 09:26:16 +11:00
Adam Meehan
e76c53a295 doc fix 2009-02-09 18:20:29 +11:00
Adam Meehan
f93720177b doc fixes 2009-02-09 18:14:51 +11:00
Adam Meehan
1181b725d0 doc tweaks 2009-02-09 18:12:08 +11:00
Adam Meehan
12aa78271e added docs and removed with_* from todo 2009-02-09 17:49:39 +11:00
Adam Meehan
862b41f903 added :with_date and :with_time options
refactored restriction_value into evaluate_option_value class method for more general usage
refactored type_cast_value into class method
2009-02-09 16:44:03 +11:00
Adam Meehan
904c202fb4 little cleanup and consistency 2009-02-09 12:11:56 +11:00
Adam Meehan
1001d29c01 rails 2.3.0 fix with I18n.reload!
added 2.3 to ginger list
2009-02-08 20:54:45 +11:00
Adam Meehan
29c23a7a26 move format compilcation call to where it belongs 2009-02-08 13:02:06 +11:00
Adam Meehan
7ef9078369 use the value from validates_each in validator 2009-02-08 12:46:22 +11:00
Adam Meehan
a1ae5f9313 added option key validation to prevent silly validation problems due to bad key name 2009-02-01 20:08:07 +11:00
Adam Meehan
b3e235a8a1 release 1.1.5 2009-01-21 14:15:35 +11:00
Adam Meehan
71583805c8 fixed regex for yy format token which wasn't greedy enough when datetime string parsed as date causing a 4 digit year to be extracted as first 2 digits 2009-01-21 14:07:35 +11:00
Adam Meehan
2ee971623c whitespace 2009-01-21 14:07:20 +11:00
Adam Meehan
817e49940c removed the 'resume' call, um wtf? 2009-01-21 14:05:46 +11:00
Adam Meehan
a76fc112e7 release 1.1.4 2009-01-13 20:11:04 +11:00
Adam Meehan
575ff85346 Merge branch 'months' 2009-01-13 20:07:54 +11:00
Adam Meehan
7ed76b5161 removed i18n of month names from TODO 2009-01-13 20:07:24 +11:00
Adam Meehan
65ed8a657e format months names now respect i18n 2009-01-13 20:05:55 +11:00
Adam Meehan
360108c39f release version 1.1.3 2009-01-13 10:25:27 +11:00
Adam Meehan
0ad8ace335 refactored AR attribute methods to define read method for all date, time and datetime attributes. Makes things much clearer and fixes bug reported (#2) by Brad (pvjg) 2009-01-13 10:12:41 +11:00
Adam Meehan
7c9ec695f4 small refactor and cleanup of formats class 2009-01-12 21:42:14 +11:00
Adam Meehan
6af61917dd release v1.1.2 2009-01-12 13:20:58 +11:00
Adam Meehan
43e6748cd2 actually removed DM and Merb todos this time and added month name i18n handling and remove_formats 2009-01-12 13:12:51 +11:00
Adam Meehan
760a52a2a4 cleanup matcher spec a little 2009-01-12 13:08:22 +11:00
Adam Meehan
9f1642c730 fix matcher error_message_for when i18n is loaded and custom error message to use regular string interpolation 2009-01-12 13:04:41 +11:00
Adam Meehan
011ea070db fix interpolation_values examples for rails version without i18n 2009-01-12 13:03:21 +11:00
Adam Meehan
b632093ce2 add examples for custom_error_messages and interpolation values. what can I say? TATFT 2009-01-12 12:36:37 +11:00
Adam Meehan
525b3b9941 fix custom_error_message hash bug using wrong match data index 2009-01-12 12:35:35 +11:00
Adam Meehan
db8dd9ac99 version 1.1.1 2009-01-03 19:11:36 +11:00
Adam Meehan
1fdfc23cb8 fixed bug in matcher using local variable for options, must not have run the specs one last time before last release. Umm marr 2009-01-03 19:07:27 +11:00
Adam Meehan
7d3ee4bc1b added rspec matcher to features list 2009-01-02 12:15:09 +11:00
Adam Meehan
694a4bdd69 change error to warning for untested Rails version 2009-01-01 20:53:19 +11:00
Adam Meehan
07359c6157 updated TODO to remove between and DM and Merb support in light of Rails 3 merge 2009-01-01 20:51:49 +11:00
Adam Meehan
215b3dedfd updated changelog for v1.1 2009-01-01 20:38:09 +11:00
Adam Meehan
753a63417b version bumped to 1.1 2009-01-01 20:36:57 +11:00
Adam Meehan
af923014f5 added ignore file 2009-01-01 20:30:32 +11:00
Adam Meehan
a7c6e37333 Merge branch 'between' 2009-01-01 20:30:22 +11:00
Adam Meehan
a14bc306b3 added between option details to README 2009-01-01 20:28:02 +11:00
Adam Meehan
a71d6f7945 added between option testing to matcher and refactored 2009-01-01 20:13:44 +11:00
Adam Meehan
45ab815039 added between option and some refactoring 2009-01-01 20:11:30 +11:00
Adam Meehan
c308aaf4a9 refactored attribute name handling in spec 2008-12-28 17:22:24 +11:00
Adam Meehan
6584d0f1f0 removed random blob of code in readme 2008-12-10 08:41:31 +11:00
Adam Meehan
5abaec66ae remove version check, check for I what I want 2008-12-09 16:56:03 +11:00
Adam Meehan
ea5452a604 installation instructions for plugin corrected 2008-12-08 08:38:04 +11:00
Adam Meehan
40437c970d make format error raise with message 2008-12-07 21:29:50 +11:00
Adam Meehan
37bfbfe5e7 doc update with new feature 2008-12-07 18:01:01 +11:00
Adam Meehan
897a9a3bd3 final touch ups to changelog, todo and gemspec for release 1.0.0 2008-12-07 12:06:42 +11:00
Adam Meehan
e9fa4ca20a fixed bug where accessor methods not properly generating due columns_hash lookup failing on method name as a symbol
force value to time on write unless is a date attribute
2008-12-07 11:07:39 +11:00
Adam Meehan
e4760126e2 allow for a string column being validated as a date/time 2008-12-07 11:06:43 +11:00
Adam Meehan
12af4d8d9d updated changelog and added to gem files 2008-12-06 15:43:56 +11:00
Adam Meehan
81330e7aad gemified using newgem with --simple option 2008-12-06 14:58:05 +11:00
Adam Meehan
e82b1e2033 removed some time_travel setup which are not used here 2008-12-06 14:42:21 +11:00
Adam Meehan
2ba85772b6 renamed license file 2008-12-06 14:42:02 +11:00
Adam Meehan
64ffb52dae spec text tweaks 2008-12-06 14:27:20 +11:00
Adam Meehan
ea6ec0cd75 added rdoc extension to readme for github prettiness 2008-12-06 14:25:58 +11:00
Adam Meehan
a691b4ed35 moved sqlite patch into seperate file 2008-12-06 09:06:24 +11:00
Adam Meehan
e2790538a8 added make_time examples 2008-12-05 20:46:40 +11:00
Adam Meehan
75f3ef34e8 made parameters explicit for validates_timeliness since its private and should not be used directly for validation 2008-12-05 20:35:37 +11:00
Adam Meehan
4cb51ae602 teeny comment correction 2008-12-05 20:30:54 +11:00
Adam Meehan
dad55456d5 patched sqlite adapter in spec_helper to fix time attributes in rails 2.0.2 errorneously reporting time attributes as datetime column types 2008-12-05 20:25:28 +11:00
Adam Meehan
87b0beef5a moved ignore_restriction_errors and error_value_formats into Validator class 2008-12-05 20:24:06 +11:00
Adam Meehan
6cd6cd9dc0 made restriction_value instance method as it relies on validator instance attribute type
removed old type_cast_method class method
2008-12-05 18:45:28 +11:00
Adam Meehan
0c5cc1a536 more refactoring of validator
fixed bug when Date is restriction value for a datetime attribute so its cast to time in default timezone
2008-12-05 18:25:44 +11:00
Adam Meehan
c224db7af8 moved specs to validator 2008-12-05 18:24:31 +11:00
Adam Meehan
4868746e94 refactor specs from validation_methods to validator and refactored to make much more sense 2008-12-05 18:23:39 +11:00
Adam Meehan
bf999170d7 fix timezone issue for to_dummy_time as per normal Rails which uses AR default_timezone 2008-12-05 16:34:28 +11:00
Adam Meehan
dbfd9231b5 moved including of modules into module files 2008-12-03 21:44:39 +11:00
Adam Meehan
3da24f0f33 a few changes to prepare for better version support 2008-12-03 21:22:19 +11:00
Adam Meehan
6f4306973b updated some comments 2008-12-03 21:20:53 +11:00
Adam Meehan
71f2a43424 added i18n support for error messages in Rails 2.2 2008-12-03 18:20:34 +11:00
Adam Meehan
c386a9cdcf removed some naff comments 2008-12-02 21:40:41 +11:00
Adam Meehan
1596ffd2cb namespaced matcher 2008-12-02 21:07:08 +11:00
Adam Meehan
d847d3b95a removed a debug hint in matcher 2008-12-02 20:42:00 +11:00
Adam Meehan
d0e60ece92 updated with new plugin settings method names and some refinements 2008-12-02 20:35:12 +11:00
Adam Meehan
3ac65b507f renamed methods which have been moved out of AR and so dont need to be fully qualified 2008-12-02 20:34:02 +11:00
Adam Meehan
d71f581e10 refactored error message handling and specs 2008-12-02 19:36:03 +11:00
Adam Meehan
aa42fb76b6 get the model to add errors to record returned from validation instead of validator 2008-11-30 08:34:17 +11:00
Adam Meehan
b0647d456e refactored validations in validation methods for model and validator class 2008-11-30 07:40:11 +11:00
Adam Meehan
9610d79d7d move core extension spec to namespace folder 2008-11-29 18:55:18 +11:00
Adam Meehan
412ff22dd9 namespaced ActiveRecord and ActionView specifc modules and specs with a mind to making the plugin framework agnostic in the future 2008-11-29 18:32:32 +11:00
41 changed files with 2392 additions and 1715 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
pkg/

118
CHANGELOG
View File

@@ -1,28 +1,104 @@
[2008-11-13] = 2.2.2 [2009-09-19]
- allow uppercase meridian to be valid [reported by Alex (http://alex.digns.com/)] - Fixed dummy_time using make_time to respect timezone. Fixes 1.9.1 bug.
[2008-10-28] = 2.2.1 [2009-09-12]
- fixed bug when dirty attributes not reflecting change when attribute changed from time value to nil [reported by Brad (pvjq)] - Fixed dummy date part for time types in Validator.type_cast_value
- fixes for Rails 2.2 compatibility. Will refactor in to Rails version specific branches in the future. - No more core extensions! Removed dummy_time methods.
[2008-09-24] = 2.2.0 [2009-09-12]
- refactored attribute write method definitions - Ruby 1.9 support!
- Customise dummy date values for time types. See DUMMY DATE FOR TIME TYPES.
- Fixed matcher conflict with Shoulda. Load plugin matcher manually now see matcher section in README
- Fixed :ignore_usec when used with :with_time or :with_date
- Some clean up and refactoring
[2008-08-25] = 2.1.0 [2009-06-20]
- fixed bug for non-timezone write method not updating changed attributes hash [reported by Sylvestre Mergulhão] - Added ambiguous year threshold setting in Formats class to customize the threshold for 2 digit years (See README)
- Fixed interpolation values in custom error message for Rails 2.2+
- Fixed custom I18n local override of en locale
- Dramatically simplified ActiveRecord monkey patching and hackery
[2008-08-22] = 2.0.0 [2009-04-12]
- fixed bug with attribute cache not clearing on write for date and time columns [reported by Sylvestre Mergulhão] - Error value formats are now specified in the i18n locale file instead of updating plugin hash. See OTHER CUSTOMISATION section in README.
- parse method returns Date object for date column assigned string as per normal Rails behaviour - Date/time select helper extension is disabled by default. To enable see DISPLAY INVALID VALUES IN DATE HELPERS section in README to enable.
- parse method returns same object type when assigned Date or Time object as per normal Rails behaviour - Added :format option to limit validation to a single format if desired
- Matcher now supports :equal_to option
- Formats.parse can take :include_offset option to include offset value from string in seconds, if string contains an offset. Offset not used in rest of plugin yet.
- Refactored to remove as much plugin code from ActiveRecord as possible.
[2008-08-07] = 1.1.7 [2009-03-26]
- modified matcher option value parsing to allow same value types as validation method - Minor change to multiparameter attributes which I had not properly implemented for chaining
- fixed matcher message
[2008-08-02] = 1.1.6 [2009-03-19]
- refactored validation - Rail 2.3 support
- refactored matcher - Added :with_date and :with_time options. They allow an attribute to be combined with another attribute or value to make a datetime value for validation against the temporal restrictions
- Added :equal_to option
- Option key validation
- Better behaviour with other plugins using alias_method_chain on read_attribute and define_attribute_methods
- Added option to enable datetime_select extension for future use to optionally enable. Enabled by default until version 2.
- Added :ignore_usec option for datetime restrictions to be compared without microsecond
- some refactoring
[2008-07-30] = 1.1.5 [2009-01-21]
- removed setting values to nil when validation fails to preserve before_type_cast value - Fixed regex for 'yy' format token which wasn't greedy enough for date formats ending with year when a datetime string parsed as date with a 4 digit year
= 1.1.4 [2009-01-13]
- Make months names respect i18n in Formats
= 1.1.3 [2009-01-13]
- Fixed bug where time and date attributes still being parsed on read using Rails default parser [reported by Brad (pvjq)]
= 1.1.2 [2009-01-12]
- Fixed bugs
- matcher failing for custom error message without interpolation keys using I18n
- validator custom error messages not being extracted
= 1.1.1 [2009-01-03]
- Fixed bug in matcher for options local variable
= 1.1.0 [2009-01-01]
- Added between option
= 1.0.0 [2008-12-06]
- Gemified!
- Refactor of plugin into a Data Mapper style validator class which makes for a cleaner implementation and possible future Merb\Data Mapper support
- Added Rails 2.2 i18n support. Plugin error messages can specified in locale files. See README.
- ignore_datetime_restriction_errors setting has been moved from AR to ValidatesTimeliness::Validator.ignore_restriction_errors
- date_time_error_value_formats setting has been moved from AR to ValidatesTimeliness::Validator.error_value_formats
- Namespaced modules and specs
- Clean up of specs
- fixed a few bugs
- accessor methods not generating properly due method name stored as symbol in generated_attributes which fails on lookup
- force value assigned to time/datetime attributes to time objects
= 0.1.0 [2008-12-06]
- Tagged plugin as version 0.1.0
= 2008-11-13
- allow uppercase meridian to be valid [reported by Alex (http://alex.digns.com/)]
= 2008-10-28
- fixed bug when dirty attributes not reflecting change when attribute changed from time value to nil [reported by Brad (pvjq)]
- fixes for Rails 2.2 compatibility. Will refactor in to Rails version specific branches in the future.
= 2008-09-24
- refactored attribute write method definitions
= 2008-08-25
- fixed bug for non-timezone write method not updating changed attributes hash [reported by Sylvestre Mergulhão]
= 2008-08-22
- fixed bug with attribute cache not clearing on write for date and time columns [reported by Sylvestre Mergulhão]
- parse method returns Date object for date column assigned string as per normal Rails behaviour
- parse method returns same object type when assigned Date or Time object as per normal Rails behaviour
= 2008-08-07
- modified matcher option value parsing to allow same value types as validation method
- fixed matcher message
= 2008-08-02
- refactored validation
- refactored matcher
= 2008-07-30
- removed setting values to nil when validation fails to preserve before_type_cast value

View File

@@ -1,7 +1,7 @@
= validates_timeliness = validates_timeliness
* Source: http://github.com/adzap/validates_timeliness * Source: http://github.com/adzap/validates_timeliness
* Tickets: http://adzap.lighthouseapp.com/projects/14111-validates_timeliness * Bugs: http://github.com/adzap/validates_timeliness/issues
== DESCRIPTION: == DESCRIPTION:
@@ -22,14 +22,28 @@ think should be a valid date or time string.
* Restores ability to see raw value entered for date/time attributes with * Restores ability to see raw value entered for date/time attributes with
_before_type_cast modifier, which was lost in Rails 2.1. _before_type_cast modifier, which was lost in Rails 2.1.
* Respects new timezone features of Rails 2.1. * Supports Rails timezone handling
* Supports Rails I18n for the error messages
* Rspec matcher for testing model validation of dates and times
== INSTALLATION: == INSTALLATION:
./script/plugin git://github.com/adzap/validates_timeliness As plugin (from master)
./script/plugin install git://github.com/adzap/validates_timeliness.git
As gem
sudo gem install validates_timeliness
# in environment.rb
config.gem 'validates_timeliness'
== USAGE: == USAGE:
To validate a model with a date, time or datetime attribute you just use the To validate a model with a date, time or datetime attribute you just use the
@@ -41,51 +55,59 @@ validation method
end end
The list of validation methods available are as follows: The list of validation methods available are as follows:
validates_date - validate value as date
* validates_date - validate value as date validates_time - validate value as time only i.e. '12:20pm'
validates_datetime - validate value as a full date and time
* validates_time - validate value as time only i.e. '12:20pm'
* validates_datetime - validate value as a full date and time
The validation methods take the usual options plus some specific ones to restrict The validation methods take the usual options plus some specific ones to restrict
the valid range of dates or times allowed the valid range of dates or times allowed
Temporal options: Temporal options (or restrictions):
:before - Attribute must be before this value to be valid :equal_to - Attribute must be equal to value to be valid
:on_or_before - Attribute must be equal to or before this value to be valid :before - Attribute must be before this value to be valid
:after - Attribute must be after this value to be valid :on_or_before - Attribute must be equal to or before this value to be valid
:on_or_after - Attribute must be equal to or after this value to be valid :after - Attribute must be after this value to be valid
:on_or_after - Attribute must be equal to or after this value to be valid
:between - Attribute must be between the values to be valid. Takes an array of two values or a range
Regular validation options: Regular validation options:
:allow_nil - Allow a nil value to be valid :allow_nil - Allow a nil value to be valid
:allow_blank - Allows a nil or empty string value to be valid :allow_blank - Allows a nil or empty string value to be valid
:if - Execute validation when :if evaluates true :if - Execute validation when :if evaluates true
:unless - Execute validation when :unless evaluates false :unless - Execute validation when :unless evaluates false
Message options: - Use these to override the default error messages Special options:
:invalid_date_message :with_time - Validate a date attribute value combined with a time value against any temporal restrictions
:invalid_time_message :with_date - Validate a time attribute value combined with a date value against any temporal restrictions
:invalid_datetime_message :ignore_usec - Ignores microsecond value on datetime restrictions
:before_message :format - Limit validation to a single format for special cases. Takes plugin format value.
:on_or_before_message
:after_message Message options: - Use these to override the default error messages
:on_or_after_message :invalid_date_message
:invalid_time_message
:invalid_datetime_message
:equal_to_message
:before_message
:on_or_before_message
:after_message
:on_or_after_message
:between_message
The temporal options can take 4 different value types: The temporal restrictions, with_date and with_time can take 4 different value types:
* String value
* Date, Time, or DateTime object value
* Proc or lambda object which may take an optional parameter being the record object
* A symbol matching the method name in the model
* String value When an attribute value is compared to temporal restrictions, they are compared as
* Date, Time, or DateTime object value
* Proc or lambda object
* A symbol matching the method name in the model
When an attribute value is compared to temporal options, they are compared as
the same type as the validation method type. So using validates_date means all the same type as the validation method type. So using validates_date means all
values are compared as dates. values are compared as dates. This is except in the case of with_time and with_date
options which effectively force the value to validated as a datetime against the
temporal options.
== EXAMPLES: == EXAMPLES:
validates_date :date_of_birth :before => Proc.new { 18.years.ago }, validates_date :date_of_birth :before => lambda { 18.years.ago },
:before_message => "must be at least 18 years old" :before_message => "must be at least 18 years old"
validates_time :breakfast_time, :on_or_after => '6:00am', validates_time :breakfast_time, :on_or_after => '6:00am',
@@ -93,7 +115,9 @@ values are compared as dates.
:before => :second_breakfast_time, :before => :second_breakfast_time,
:allow_nil => true :allow_nil => true
validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now } validates_datetime :appointment_date, :before => lambda { 1.week.from_now }
validates_date :entry_date, :with_time => '17:00', :on_or_before => :competition_closing
=== DATE/TIME FORMATS: === DATE/TIME FORMATS:
@@ -107,44 +131,43 @@ be happy to know that is exactly the format you can use to define your own if
you want. No complex regular expressions or duck punching (monkey patching) the you want. No complex regular expressions or duck punching (monkey patching) the
plugin is needed. plugin is needed.
Time formats: ==== Time formats:
hh:nn:ss hh:nn:ss
hh-nn-ss hh-nn-ss
h:nn h:nn
h.nn h.nn
h nn h nn
h-nn h-nn
h:nn_ampm h:nn_ampm
h.nn_ampm h.nn_ampm
h nn_ampm h nn_ampm
h-nn_ampm h-nn_ampm
h_ampm h_ampm
NOTE: Any time format without a meridian token (the 'ampm' token) is considered
in 24 hour time.
Date formats:
yyyy/mm/dd
yyyy-mm-dd
yyyy.mm.dd
m/d/yy OR d/m/yy
m\d\yy OR d\m\yy
d-m-yy
d.m.yy
d mmm yy
NOTE: To use non-US date formats see US/EURO FORMATS section
Datetime formats:
m/d/yy h:nn:ss OR d/m/yy hh:nn:ss
m/d/yy h:nn OR d/m/yy h:nn
m/d/yy h:nn_ampm OR d/m/yy h:nn_ampm
yyyy-mm-dd hh:nn:ss
yyyy-mm-dd h:nn
ddd mmm d hh:nn:ss zo yyyy # Ruby time string
yyyy-mm-ddThh:nn:ss(?:Z|zo) # ISO 8601
NOTE: To use non-US date formats see US/EURO FORMATS section NOTE: Any time format without a meridian token (the 'ampm' token) is considered in 24 hour time.
==== Date formats:
yyyy/mm/dd
yyyy-mm-dd
yyyy.mm.dd
m/d/yy OR d/m/yy
m\d\yy OR d\m\yy
d-m-yy
d.m.yy
d mmm yy
NOTE: To use non-US date formats see US/EURO FORMATS section
==== Datetime formats:
m/d/yy h:nn:ss OR d/m/yy hh:nn:ss
m/d/yy h:nn OR d/m/yy h:nn
m/d/yy h:nn_ampm OR d/m/yy h:nn_ampm
yyyy-mm-dd hh:nn:ss
yyyy-mm-dd h:nn
ddd mmm d hh:nn:ss zo yyyy # Ruby time string
yyyy-mm-ddThh:nn:ss(?:Z|zo) # ISO 8601
NOTE: To use non-US date formats see US/EURO FORMATS section
Here is what each format token means: Here is what each format token means:
@@ -167,7 +190,7 @@ Here is what each format token means:
Special Cases: Special Cases:
yy = 2 or 4 digit year yy = 2 or 4 digit year
yyyyy = exactly 4 digit year yyyy = exactly 4 digit year
mmm = month long name (e.g. 'Jul' or 'July') mmm = month long name (e.g. 'Jul' or 'July')
ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday) ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
u = microseconds matches 1 to 3 digits u = microseconds matches 1 to 3 digits
@@ -183,7 +206,7 @@ To see all defined formats look in lib/validates_timeliness/formats.rb.
The perenial problem for non-US developers or applications not primarily for the The perenial problem for non-US developers or applications not primarily for the
US, is the US date format of m/d/yy. This is ambiguous with the European format US, is the US date format of m/d/yy. This is ambiguous with the European format
of d/my/yy. By default the plugin uses the US formats as this is the Ruby default of d/m/yy. By default the plugin uses the US formats as this is the Ruby default
when it does date interpretation, and is in keeping PoLS (principle of least when it does date interpretation, and is in keeping PoLS (principle of least
surprise). surprise).
@@ -206,7 +229,7 @@ Done! That format is no longer considered valid. Easy!
Ok, now I hear you say "Well I have format that I want to use but you don't have it". Ok, now I hear you say "Well I have format that I want to use but you don't have it".
Ahh, then add it yourself. Again stick this in an initializer file Ahh, then add it yourself. Again stick this in an initializer file
ValidatesTimeliness::Formats.add_formats(:time, "d o'clock") ValidatesTimeliness::Formats.add_formats(:time, "d o'clock")
Now "10 o'clock" will be a valid value. So easy, no more whingeing! Now "10 o'clock" will be a valid value. So easy, no more whingeing!
@@ -221,40 +244,75 @@ 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 your new format higher precedence than an existing format, you can include the
before option like so before option like so
ValidatesTimeliness::Formats.add_formats(:time, 'ss:nn:hh', :before => 'hh:nn:ss') 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 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. you adding a new one and deleting an old one to get it to work.
=== TEMPORAL OPTION ERRORS: === AMBIGUOUS YEAR THRESHOLD
When using the validation temporal options there are times when the restriction When dealing with 2 digit year values, by default a year is interpreted as being
in the last century at or above 30. You can customize this however
ValidatesTimeliness::Formats.ambiguous_year_threshold = 20
Now you get:
year of 19 is considered 2019
year of 20 is considered 1920
=== DUMMY DATE FOR TIME TYPES
Given that Ruby has no support for a time-only type, all time type columns are evaluated
as a regular Time class objects with a dummy date value set. Rails defines the dummy date as
2000-01-01. So a time of '12:30' is evaluated as a Time value of '2000-01-01 12:30'. If you
need to customize this for some reason you can do so as follows
ValidatesTimeliness::Formats.dummy_date_for_time_type = [2009, 1, 1]
The value should be an array of 3 values being year, month and day in that order.
=== TEMPORAL RESTRICTION ERRORS:
When using the validation temporal restrictions there are times when the restriction
value itself may be invalid. Normally this will add an error to the model such as value itself may be invalid. Normally this will add an error to the model such as
'restriction :before value was invalid'. These can be annoying if you are using 'restriction :before value was invalid'. These can be annoying if you are using
procs or methods as restrictions and don't care if they don't evaluate properly procs or methods as restrictions and don't care if they don't evaluate properly
and you want the validation to complete. In these situations you turn them off. and you want the validation to complete. In these situations you turn them off.
To turn them off globally: To turn them off:
ActiveRecord::Base.ignore_datetime_restriction_errors = true ValidatesTimeliness::Validator.ignore_restriction_errors = true
To switch them on or off per model:
class Person < ActiveRecord::Base
self.ignore_datetime_restriction_errors = false
end
A word of warning though, as this may hide issues with the model and make those A word of warning though, as this may hide issues with the model and make those
corner cases a little harder to test. In general if you are using procs or corner cases a little harder to test. In general if you are using procs or
model methods and you only care when they return a value, then they should model methods and you only care when they return a value, then they should
return nil in all other situations. Restrictions are skipped if they are nil. return nil in all other situations. Restrictions are skipped if they are nil.
=== DISPLAY INVALID VALUES IN DATE HELPERS:
The plugin has some extensions to ActionView and ActiveRecord by allowing invalid
date and time values to be redisplayed to the user as feedback, instead of
a blank field which happens by default in Rails. Though the date helpers make this a
pretty rare occurence, given the select dropdowns for each date/time component, but
it may be something of interest.
To activate it, put this in an initializer:
ValidatesTimeliness.enable_datetime_select_extension!
=== OTHER CUSTOMISATION: === OTHER CUSTOMISATION:
The error messages for each temporal option can also be globally overridden by The error messages for each temporal restrictions can also be globally overridden by
updating the default AR error messages like so updating the default AR error messages like so
For Rails 2.0/2.1:
ActiveRecord::Errors.default_error_messages.update( ActiveRecord::Errors.default_error_messages.update(
:invalid_date => "is not a valid date", :invalid_date => "is not a valid date",
:invalid_time => "is not a valid time", :invalid_time => "is not a valid time",
@@ -262,18 +320,44 @@ updating the default AR error messages like so
:before => "must be before %s", :before => "must be before %s",
:on_or_before => "must be on or before %s", :on_or_before => "must be on or before %s",
:after => "must be after %s", :after => "must be after %s",
:on_or_after => "must be on or after %s" :on_or_after => "must be on or after %s",
:between => "must be between %s and %s"
) )
Or for something a little more specific you can override the format of the values Where %s is the interpolation value for the restriction.
inserted in the error messages for temporal options like so
ActiveRecord::Errors.date_time_error_value_formats.update( Rails 2.2+ using the I18n system to define new defaults:
en:
activerecord:
errors:
messages:
on_or_before: "must be equal to or before {{restriction}}"
on_or_after: "must be equal to or after {{restriction}}"
between: "must be between {{earliest}} and {{latest}}"
The {{restriction}} signifies where the interpolation value for the restriction
will be inserted.
And for something a little more specific you can override the format of the interpolation
values inserted in the error messages for temporal restrictions like so
For Rails 2.0/2.1:
ValidatesTimeliness::Validator.error_value_formats.update(
:time => '%H:%M:%S', :time => '%H:%M:%S',
:date => '%Y-%m-%d', :date => '%Y-%m-%d',
:datetime => '%Y-%m-%d %H:%M:%S' :datetime => '%Y-%m-%d %H:%M:%S'
) )
Rails 2.2+ using the I18n system to define new defaults:
validates_timeliness:
error_value_formats:
date: '%Y-%m-%d'
time: '%H:%M:%S'
datetime: '%Y-%m-%d %H:%M:%S'
Those are Ruby strftime formats not the plugin formats. Those are Ruby strftime formats not the plugin formats.
@@ -283,10 +367,15 @@ To sweeten the deal that little bit more, you have an Rspec matcher available fo
you model specs. Now you can easily test the validations you have just written you model specs. Now you can easily test the validations you have just written
with the plugin or better yet *before* you write them! You just use the with the plugin or better yet *before* you write them! You just use the
validation options you want as you would with the validation method. Those validation options you want as you would with the validation method. Those
options are then verified and reported if they fail. Use it like so: options are then verified and reported if they fail.
First require it in your spec_helper.rb
@person.should validate_date(:birth_date, :before => Time.now, :before_message => 'should be before today') require 'validates_timeliness/matcher'
Use it like so:
@person.should validate_date(:birth_date, :before => Time.now, :before_message => 'should be before today')
The matcher names are just the singular of the validation methods. The matcher names are just the singular of the validation methods.

View File

@@ -1,14 +1,56 @@
require 'rake' require 'rubygems'
require 'rake/testtask' require 'rake/gempackagetask'
require 'rake/rdoctask' require 'rubygems/specification'
require 'date'
require 'spec/rake/spectask' require 'spec/rake/spectask'
require 'lib/validates_timeliness/version'
spec_files = Rake::FileList["spec/**/*_spec.rb"] GEM = "validates_timeliness"
GEM_VERSION = ValidatesTimeliness::VERSION
AUTHOR = "Adam Meehan"
EMAIL = "adam.meehan@gmail.com"
HOMEPAGE = "http://github.com/adzap/validates_timeliness"
SUMMARY = "Date and time validation plugin for Rails 2.x which allows custom formats"
desc "Run specs" spec = Gem::Specification.new do |s|
Spec::Rake::SpecTask.new do |t| s.name = GEM
t.spec_files = spec_files s.version = GEM_VERSION
t.spec_opts = ["-c"] s.platform = Gem::Platform::RUBY
s.rubyforge_project = "validatestime"
s.has_rdoc = true
s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"]
s.summary = SUMMARY
s.description = s.summary
s.author = AUTHOR
s.email = EMAIL
s.homepage = HOMEPAGE
s.require_path = 'lib'
s.autorequire = GEM
s.files = %w(LICENSE README.rdoc Rakefile TODO CHANGELOG) + Dir.glob("{lib,spec}/**/*")
end end
task :default => :spec task :default => :spec
desc "Run specs"
Spec::Rake::SpecTask.new do |t|
t.spec_files = FileList['spec/**/*_spec.rb']
t.spec_opts = %w(--color)
end
Rake::GemPackageTask.new(spec) do |pkg|
pkg.gem_spec = spec
end
desc "install the gem locally"
task :install => [:package] do
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
end
desc "create a gemspec file"
task :make_spec do
File.open("#{GEM}.gemspec", "w") do |file|
file.puts spec.to_ruby
end
end

5
TODO Normal file
View File

@@ -0,0 +1,5 @@
- valid formats could come from locale file
- add replace_formats instead add_formats :before
- array of values for all temporal options
- use tz and zo value from time string?
- filter valid formats rather than remove for hot swapping without recompilation

View File

@@ -4,73 +4,74 @@ require 'date'
require 'parsedate' require 'parsedate'
require 'benchmark' require 'benchmark'
require 'rubygems' require 'rubygems'
require 'active_support'
require 'active_record' require 'active_record'
require 'action_controller'
require 'rails/version'
require 'validates_timeliness' require 'validates_timeliness'
def parse(*args)
ValidatesTimeliness::Parser.parse(*args)
end
n = 10000 n = 10000
Benchmark.bm do |x| Benchmark.bm do |x|
x.report('timeliness - datetime') { x.report('timeliness - datetime') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("2000-01-04 12:12:12", :datetime) parse("2000-01-04 12:12:12", :datetime)
end end
} }
x.report('timeliness - date') { x.report('timeliness - date') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("2000-01-04", :date) parse("2000-01-04", :date)
end end
} }
x.report('timeliness - date as datetime') { x.report('timeliness - date as datetime') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("2000-01-04", :datetime) parse("2000-01-04", :datetime)
end end
} }
x.report('timeliness - time') { x.report('timeliness - time') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("12:01:02", :time) parse("12:01:02", :time)
end end
} }
x.report('timeliness - invalid format datetime') { x.report('timeliness - invalid format datetime') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("20xx-01-04 12:12:12", :datetime) parse("20xx-01-04 12:12:12", :datetime)
end end
} }
x.report('timeliness - invalid format date') { x.report('timeliness - invalid format date') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("20xx-01-04", :date) parse("20xx-01-04", :date)
end end
} }
x.report('timeliness - invalid format time') { x.report('timeliness - invalid format time') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("12:xx:02", :time) parse("12:xx:02", :time)
end end
} }
x.report('timeliness - invalid value datetime') { x.report('timeliness - invalid value datetime') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("2000-01-32 12:12:12", :datetime) parse("2000-01-32 12:12:12", :datetime)
end end
} }
x.report('timeliness - invalid value date') { x.report('timeliness - invalid value date') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("2000-01-32", :date) parse("2000-01-32", :date)
end end
} }
x.report('timeliness - invalid value time') { x.report('timeliness - invalid value time') {
n.times do n.times do
ActiveRecord::Base.parse_date_time("12:61:02", :time) parse("12:61:02", :time)
end end
} }
x.report('date/time') { x.report('date/time') {
@@ -96,4 +97,3 @@ Benchmark.bm do |x|
end end
} }
end end

View File

@@ -1,21 +1,49 @@
require 'validates_timeliness/attribute_methods'
require 'validates_timeliness/validations'
require 'validates_timeliness/formats' require 'validates_timeliness/formats'
require 'validates_timeliness/multiparameter_attributes' require 'validates_timeliness/parser'
require 'validates_timeliness/instance_tag' require 'validates_timeliness/validator'
require 'validates_timeliness/validate_timeliness_matcher' if ENV['RAILS_ENV'] == 'test' require 'validates_timeliness/validation_methods'
require 'validates_timeliness/core_ext/time' require 'validates_timeliness/active_record/attribute_methods'
require 'validates_timeliness/core_ext/date' require 'validates_timeliness/active_record/multiparameter_attributes'
require 'validates_timeliness/core_ext/date_time' require 'validates_timeliness/action_view/instance_tag'
ActiveRecord::Base.send(:include, ValidatesTimeliness::AttributeMethods) module ValidatesTimeliness
ActiveRecord::Base.send(:include, ValidatesTimeliness::Validations)
ActiveRecord::Base.send(:include, ValidatesTimeliness::MultiparameterAttributes) mattr_accessor :default_timezone
ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::InstanceTag) self.default_timezone = :utc
Time.send(:include, ValidatesTimeliness::CoreExtensions::Time) mattr_accessor :use_time_zones
Date.send(:include, ValidatesTimeliness::CoreExtensions::Date) self.use_time_zones = false
DateTime.send(:include, ValidatesTimeliness::CoreExtensions::DateTime)
ValidatesTimeliness::Formats.compile_format_expressions LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/validates_timeliness/locale/en.yml')
class << self
def enable_datetime_select_extension!
enable_datetime_select_invalid_value_extension!
enable_multiparameter_attributes_extension!
end
def load_error_messages
if defined?(I18n)
I18n.load_path.unshift(LOCALE_PATH)
I18n.reload!
else
defaults = YAML::load(IO.read(LOCALE_PATH))['en']
errors = defaults['activerecord']['errors']['messages'].inject({}) {|h,(k,v)| h[k.to_sym] = v.gsub(/\{\{\w*\}\}/, '%s');h }
::ActiveRecord::Errors.default_error_messages.update(errors)
ValidatesTimeliness::Validator.error_value_formats = defaults['validates_timeliness']['error_value_formats'].symbolize_keys
end
end
def setup_for_rails
self.default_timezone = ::ActiveRecord::Base.default_timezone
self.use_time_zones = ::ActiveRecord::Base.time_zone_aware_attributes rescue false
self.enable_active_record_datetime_parser!
load_error_messages
end
end
end
ValidatesTimeliness.setup_for_rails

View File

@@ -0,0 +1,48 @@
module ValidatesTimeliness
def self.enable_datetime_select_invalid_value_extension!
::ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::ActionView::InstanceTag)
end
module ActionView
# Intercepts the date and time select helpers to allow the
# attribute value before type cast to be used as in the select helpers.
# This means that an invalid date or time will be redisplayed rather than the
# type cast value which would be nil if invalid.
module InstanceTag
def self.included(base)
selector_method = Rails::VERSION::STRING < '2.2' ? :date_or_time_select : :datetime_selector
base.class_eval do
alias_method :datetime_selector_without_timeliness, selector_method
alias_method selector_method, :datetime_selector_with_timeliness
end
base.alias_method_chain :value, :timeliness
end
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)
return value_without_timeliness(object) unless @timeliness_date_or_time_tag
raw_value = value_before_type_cast(object)
if raw_value.nil? || raw_value.acts_like?(:time) || raw_value.is_a?(Date)
return value_without_timeliness(object)
end
time_array = ValidatesTimeliness::Formats.parse(raw_value, :datetime)
TimelinessDateTime.new(*time_array[0..5])
end
end
end
end

View File

@@ -0,0 +1,70 @@
module ValidatesTimeliness
def self.enable_active_record_datetime_parser!
::ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::AttributeMethods)
end
module ActiveRecord
# Overrides write method for date, time and datetime columns
# to use plugin parser. Also adds mechanism to store value
# before type cast.
#
module AttributeMethods
def self.included(base)
base.extend ClassMethods
base.class_eval do
alias_method_chain :read_attribute_before_type_cast, :timeliness
class << self
alias_method_chain :define_attribute_methods, :timeliness
end
end
end
def write_date_time_attribute(attr_name, value, type, time_zone_aware)
@attributes_cache["_#{attr_name}_before_type_cast"] = value
value = ValidatesTimeliness::Parser.parse(value, type)
if value && type != :date
value = value.to_time
value = value.in_time_zone if time_zone_aware
end
write_attribute(attr_name.to_sym, value)
end
def read_attribute_before_type_cast_with_timeliness(attr_name)
cached_attr = "_#{attr_name}_before_type_cast"
return @attributes_cache[cached_attr] if @attributes_cache.has_key?(cached_attr)
read_attribute_before_type_cast_without_timeliness(attr_name)
end
module ClassMethods
def define_attribute_methods_with_timeliness
return if generated_methods?
timeliness_methods = []
columns_hash.each do |name, column|
if [:date, :time, :datetime].include?(column.type)
time_zone_aware = create_time_zone_conversion_attribute?(name, column) rescue false
define_method("#{name}=") do |value|
write_date_time_attribute(name, value, column.type, time_zone_aware)
end
timeliness_methods << name
end
end
define_attribute_methods_without_timeliness
@generated_methods += timeliness_methods
end
end
end
end
end

View File

@@ -0,0 +1,70 @@
module ValidatesTimeliness
def self.enable_multiparameter_attributes_extension!
::ActiveRecord::Base.send(:include, ValidatesTimeliness::ActiveRecord::MultiparameterAttributes)
end
module ActiveRecord
class << self
def time_array_to_string(values, type)
values.collect! {|v| v.to_s }
case type
when :date
extract_date_from_multiparameter_attributes(values)
when :time
extract_time_from_multiparameter_attributes(values)
when :datetime
extract_date_from_multiparameter_attributes(values) + " " + extract_time_from_multiparameter_attributes(values)
end
end
def extract_date_from_multiparameter_attributes(values)
year = ValidatesTimeliness::Formats.unambiguous_year(values[0].rjust(2, "0"))
[year, *values.slice(1, 2).map { |s| s.rjust(2, "0") }].join("-")
end
def extract_time_from_multiparameter_attributes(values)
values[3..5].map { |s| s.rjust(2, "0") }.join(":")
end
end
module MultiparameterAttributes
def self.included(base)
base.alias_method_chain :execute_callstack_for_multiparameter_attributes, :timeliness
end
# Assign dates and times as formatted strings to force the use of the plugin parser
# and store a before_type_cast value for attribute
def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
errors = []
callstack.each do |name, values|
column = column_for_attribute(name)
if column && [:date, :time, :datetime].include?(column.type)
begin
callstack.delete(name)
if values.empty?
send("#{name}=", nil)
else
value = ValidatesTimeliness::ActiveRecord.time_array_to_string(values, column.type)
send("#{name}=", value)
end
rescue => ex
errors << ::ActiveRecord::AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
end
end
end
unless errors.empty?
raise ::ActiveRecord::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
end
execute_callstack_for_multiparameter_attributes_without_timeliness(callstack)
end
end
end
end

View File

@@ -1,150 +0,0 @@
module ValidatesTimeliness
# The crux of the plugin is being able to store raw user entered values
# while not interfering with the Rails 2.1 automatic timezone handling. This
# requires us to distinguish a user entered value from a value read from the
# database. Both maybe in string form, but only the database value should be
# interpreted as being in the default timezone which is normally UTC. The user
# entered value should be interpreted as being in the current zone as indicated
# by Time.zone.
#
# To do this we must cache the user entered values on write and store the raw
# value in the attributes hash for later retrieval and possibly validation.
# Any value from the database will not be in the attribute cache on first
# read so will be considered in default timezone and converted to local time.
# It is then stored back in the attributes hash and cached to avoid the need
# for any subsequent differentiation.
#
# The wholesale replacement of the Rails time type casting is not done to
# preserve the quickest conversion for timestamp columns and also any value
# which is never changed during the life of the record object.
module AttributeMethods
def self.included(base)
base.extend ClassMethods
if Rails::VERSION::STRING < '2.1'
base.class_eval do
class << self
def create_time_zone_conversion_attribute?(name, column)
false
end
end
end
end
end
# Adds check for cached date/time attributes which have been type cast already
# and value can be used from cache. This prevents the raw date/time value from
# being type cast using default Rails type casting when writing values
# to the database.
def read_attribute(attr_name)
attr_name = attr_name.to_s
if !(value = @attributes[attr_name]).nil?
if column = column_for_attribute(attr_name)
if unserializable_attribute?(attr_name, column)
unserialize_attribute(attr_name)
elsif [:date, :time, :datetime].include?(column.type) && @attributes_cache.has_key?(attr_name)
@attributes_cache[attr_name]
else
column.type_cast(value)
end
else
value
end
else
nil
end
end
# Writes attribute value by storing raw value in attributes hash,
# then convert it with parser and cache it.
#
# If Rails dirty attributes is enabled then the value is added to
# changed attributes if changed. Can't use the default dirty checking
# implementation as it chains the write_attribute method which deletes
# the attribute from the cache.
def write_date_time_attribute(attr_name, value)
attr_name = attr_name.to_s
column = column_for_attribute(attr_name)
old = read_attribute(attr_name) if defined?(ActiveRecord::Dirty)
new = self.class.parse_date_time(value, column.type)
if self.class.send(:create_time_zone_conversion_attribute?, attr_name, column)
new = new.in_time_zone rescue nil
end
@attributes_cache[attr_name] = new
if defined?(ActiveRecord::Dirty) && !changed_attributes.include?(attr_name) && old != new
changed_attributes[attr_name] = (old.duplicable? ? old.clone : old)
end
@attributes[attr_name] = value
end
module ClassMethods
# Override AR method to define attribute reader and writer method for
# date, time and datetime attributes to use plugin parser.
def define_attribute_methods
return if generated_methods?
columns_hash.each do |name, column|
unless instance_method_already_implemented?(name)
if self.serialized_attributes[name]
define_read_method_for_serialized_attribute(name)
elsif create_time_zone_conversion_attribute?(name, column)
define_read_method_for_time_zone_conversion(name.to_sym)
else
define_read_method(name.to_sym, name, column)
end
end
unless instance_method_already_implemented?("#{name}=")
if [:date, :time, :datetime].include?(column.type)
define_write_method_for_dates_and_times(name.to_sym)
else
define_write_method(name.to_sym)
end
end
unless instance_method_already_implemented?("#{name}?")
define_question_method(name)
end
end
end
# Define write method for date, time and datetime columns
def define_write_method_for_dates_and_times(attr_name)
method_body = <<-EOV
def #{attr_name}=(value)
write_date_time_attribute('#{attr_name}', value)
end
EOV
evaluate_attribute_method attr_name, method_body
end
# Define time attribute reader. If reloading then check if cached,
# which means its in local time. If local, convert with parser as local
# timezone, otherwise use read_attribute method for quick default type
# cast of values from database using default timezone.
def define_read_method_for_time_zone_conversion(attr_name)
method_body = <<-EOV
def #{attr_name}(reload = false)
cached = @attributes_cache['#{attr_name}']
return cached if @attributes_cache.has_key?('#{attr_name}') && !reload
if @attributes_cache.has_key?('#{attr_name}')
time = read_attribute_before_type_cast('#{attr_name}')
time = self.class.parse_date_time(date, :datetime)
else
time = read_attribute('#{attr_name}')
@attributes['#{attr_name}'] = time.in_time_zone rescue nil
end
@attributes_cache['#{attr_name}'] = time.in_time_zone rescue nil
end
EOV
evaluate_attribute_method attr_name, method_body
end
end
end
end

View File

@@ -1,11 +0,0 @@
module ValidatesTimeliness
module CoreExtensions
module Date
def to_dummy_time
::Time.mktime(2000, 1, 1, 0, 0, 0)
end
end
end
end

View File

@@ -1,11 +0,0 @@
module ValidatesTimeliness
module CoreExtensions
module DateTime
def to_dummy_time
::Time.mktime(2000, 1, 1, hour, min, sec)
end
end
end
end

View File

@@ -1,11 +0,0 @@
module ValidatesTimeliness
module CoreExtensions
module Time
def to_dummy_time
self.class.mktime(2000, 1, 1, hour, min, sec)
end
end
end
end

View File

@@ -1,3 +1,5 @@
require 'date'
module ValidatesTimeliness module ValidatesTimeliness
# A date and time format regular expression generator. Allows you to # A date and time format regular expression generator. Allows you to
@@ -10,17 +12,35 @@ module ValidatesTimeliness
# string values. # string values.
# #
class Formats class Formats
cattr_accessor :time_formats cattr_accessor :time_formats,
cattr_accessor :date_formats :date_formats,
cattr_accessor :datetime_formats :datetime_formats,
:time_expressions,
cattr_accessor :time_expressions :date_expressions,
cattr_accessor :date_expressions :datetime_expressions,
cattr_accessor :datetime_expressions :format_tokens,
:format_proc_args
cattr_accessor :format_tokens
cattr_accessor :format_proc_args
# Set the threshold value for a two digit year to be considered last century
#
# Default: 30
#
# Example:
# year = '29' is considered 2029
# year = '30' is considered 1930
#
cattr_accessor :ambiguous_year_threshold
self.ambiguous_year_threshold = 30
# Set the dummy date part for a time type value. Should be an array of 3 values
# being year, month and day in that order.
#
# Default: [ 2000, 1, 1 ] same as ActiveRecord
#
cattr_accessor :dummy_date_for_time_type
self.dummy_date_for_time_type = [ 2000, 1, 1 ]
# Format tokens: # Format tokens:
# y = year # y = year
# m = month # m = month
@@ -46,7 +66,7 @@ module ValidatesTimeliness
# #
# Special Cases: # Special Cases:
# yy = 2 or 4 digit year # yy = 2 or 4 digit year
# yyyyy = exactly 4 digit year # yyyy = exactly 4 digit year
# mmm = month long name (e.g. 'Jul' or 'July') # mmm = month long name (e.g. 'Jul' or 'July')
# ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday) # ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
# u = microseconds matches 1 to 6 digits # u = microseconds matches 1 to 6 digits
@@ -85,6 +105,7 @@ module ValidatesTimeliness
@@datetime_formats = [ @@datetime_formats = [
'yyyy-mm-dd hh:nn:ss', 'yyyy-mm-dd hh:nn:ss',
'yyyy-mm-dd h:nn', 'yyyy-mm-dd h:nn',
'yyyy-mm-dd h:nn_ampm',
'yyyy-mm-dd hh:nn:ss.u', 'yyyy-mm-dd hh:nn:ss.u',
'm/d/yy h:nn:ss', 'm/d/yy h:nn:ss',
'm/d/yy h:nn_ampm', 'm/d/yy h:nn_ampm',
@@ -115,7 +136,7 @@ module ValidatesTimeliness
{ 'mm' => [ /m{2}/, '(\d{2})', :month ] }, { 'mm' => [ /m{2}/, '(\d{2})', :month ] },
{ 'm' => [ /(\A|[^ap])m{1}/, '(\d{1,2})', :month ] }, { 'm' => [ /(\A|[^ap])m{1}/, '(\d{1,2})', :month ] },
{ 'yyyy' => [ /y{4,}/, '(\d{4})', :year ] }, { 'yyyy' => [ /y{4,}/, '(\d{4})', :year ] },
{ 'yy' => [ /y{2,}/, '(\d{2}|\d{4})', :year ] }, { 'yy' => [ /y{2,}/, '(\d{4}|\d{2})', :year ] },
{ 'hh' => [ /h{2,}/, '(\d{2})', :hour ] }, { 'hh' => [ /h{2,}/, '(\d{2})', :hour ] },
{ 'h' => [ /h{1}/, '(\d{1,2})', :hour ] }, { 'h' => [ /h{1}/, '(\d{1,2})', :hour ] },
{ 'nn' => [ /n{2,}/, '(\d{2})', :min ] }, { 'nn' => [ /n{2,}/, '(\d{2})', :min ] },
@@ -124,13 +145,13 @@ module ValidatesTimeliness
{ 's' => [ /s{1}/, '(\d{1,2})', :sec ] }, { 's' => [ /s{1}/, '(\d{1,2})', :sec ] },
{ 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] }, { 'u' => [ /u{1,}/, '(\d{1,6})', :usec ] },
{ 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] }, { 'ampm' => [ /ampm/, '((?:[aApP])\.?[mM]\.?)', :meridian ] },
{ 'zo' => [ /zo/, '(?:[+-]\d{2}:?\d{2})'] }, { 'zo' => [ /zo/, '([+-]\d{2}:?\d{2})', :offset ] },
{ 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] }, { 'tz' => [ /tz/, '(?:[A-Z]{1,4})' ] },
{ '_' => [ /_/, '\s?' ] } { '_' => [ /_/, '\s?' ] }
] ]
# Arguments whichs will be passed to the format proc if matched in the # Arguments which will be passed to the format proc if matched in the
# time string. The key must should the key from the format tokens. The array # time string. The key must be the key from the format tokens. The array
# consists of the arry position of the arg, the arg name, and the code to # consists of the arry position of the arg, the arg name, and the code to
# place in the time array slot. The position can be nil which means the arg # place in the time array slot. The position can be nil which means the arg
# won't be placed in the array. # won't be placed in the array.
@@ -139,13 +160,14 @@ module ValidatesTimeliness
# should just be the arg name. # should just be the arg name.
# #
@@format_proc_args = { @@format_proc_args = {
:year => [0, 'y', 'unambiguous_year(y)'], :year => [0, 'y', 'unambiguous_year(y)'],
:month => [1, 'm', 'month_index(m)'], :month => [1, 'm', 'month_index(m)'],
:day => [2, 'd', 'd'], :day => [2, 'd', 'd'],
:hour => [3, 'h', 'full_hour(h,md)'], :hour => [3, 'h', 'full_hour(h,md)'],
:min => [4, 'n', 'n'], :min => [4, 'n', 'n'],
:sec => [5, 's', 's'], :sec => [5, 's', 's'],
:usec => [6, 'u', 'microseconds(u)'], :usec => [6, 'u', 'microseconds(u)'],
:offset => [7, 'z', 'offset_in_seconds(z)'],
:meridian => [nil, 'md', nil] :meridian => [nil, 'md', nil]
} }
@@ -160,20 +182,34 @@ module ValidatesTimeliness
# Loop through format expressions for type and call proc on matches. Allow # Loop through format expressions for type and call proc on matches. Allow
# pre or post match strings to exist if strict is false. Otherwise wrap # pre or post match strings to exist if strict is false. Otherwise wrap
# regexp in start and end anchors. # regexp in start and end anchors.
# Returns 7 part time array. # Returns time array if matches a format, nil otherwise.
def parse(string, type, strict=true) def parse(string, type, options={})
return string unless string.is_a?(String) return string unless string.is_a?(String)
options.reverse_merge!(:strict => true)
expressions = expression_set(type, string)
time_array = nil sets = if options[:format]
expressions.each do |(regexp, processor)| options[:strict] = true
regexp = strict || type == :datetime ? /\A#{regexp}\Z/ : (type == :date ? /\A#{regexp}/ : /#{regexp}\Z/) [ send("#{type}_expressions").assoc(options[:format]) ]
if matches = regexp.match(string.strip) else
time_array = processor.call(*matches[1..7]) expression_set(type, string)
break end
end
matches = nil
processor = sets.each do |format, regexp, proc|
full = /\A#{regexp}\Z/ if options[:strict]
full ||= case type
when :date then /\A#{regexp}/
when :time then /#{regexp}\Z/
when :datetime then /\A#{regexp}\Z/
end
break(proc) if matches = full.match(string.strip)
end
last = options[:include_offset] ? 8 : 7
if matches
values = processor.call(*matches[1..last])
values[0..2] = dummy_date_for_time_type if type == :time
return values
end end
return time_array
end end
# Delete formats of specified type. Error raised if format not found. # Delete formats of specified type. Error raised if format not found.
@@ -205,8 +241,7 @@ module ValidatesTimeliness
end end
compile_format_expressions compile_format_expressions
end end
# Removes formats where the 1 or 2 digit month comes first, to eliminate # Removes formats where the 1 or 2 digit month comes first, to eliminate
# formats which are ambiguous with the European style of day then month. # formats which are ambiguous with the European style of day then month.
# The mmm token is ignored as its not ambigous. # The mmm token is ignored as its not ambigous.
@@ -217,13 +252,56 @@ module ValidatesTimeliness
compile_format_expressions compile_format_expressions
end end
def full_hour(hour, meridian)
hour = hour.to_i
return hour if meridian.nil?
if meridian.delete('.').downcase == 'am'
hour == 12 ? 0 : hour
else
hour == 12 ? hour : hour + 12
end
end
def unambiguous_year(year)
if year.length <= 2
century = Time.now.year.to_s[0..1].to_i
century -= 1 if year.to_i >= ambiguous_year_threshold
year = "#{century}#{year.rjust(2,'0')}"
end
year.to_i
end
def month_index(month)
return month.to_i if month.to_i.nonzero?
abbr_month_names.index(month.capitalize) || month_names.index(month.capitalize)
end
def month_names
defined?(I18n) ? I18n.t('date.month_names') : Date::MONTHNAMES
end
def abbr_month_names
defined?(I18n) ? I18n.t('date.abbr_month_names') : Date::ABBR_MONTHNAMES
end
def microseconds(usec)
(".#{usec}".to_f * 1_000_000).to_i
end
def offset_in_seconds(offset)
sign = offset =~ /^-/ ? -1 : 1
parts = offset.scan(/\d\d/).map {|p| p.to_f }
parts[1] = parts[1].to_f / 60
(parts[0] + parts[1]) * sign * 3600
end
private private
# Compile formats into validation regexps and format procs # Compile formats into validation regexps and format procs
def format_expression_generator(string_format) def format_expression_generator(string_format)
regexp = string_format.dup regexp = string_format.dup
order = {} order = {}
regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes ]/ regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes
format_tokens.each do |token| format_tokens.each do |token|
token_name = token.keys.first token_name = token.keys.first
@@ -237,8 +315,7 @@ module ValidatesTimeliness
return Regexp.new(regexp), format_proc(order) return Regexp.new(regexp), format_proc(order)
rescue rescue
puts "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}." raise "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}."
raise
end end
# Generates a proc which when executed maps the regexp capture groups to a # Generates a proc which when executed maps the regexp capture groups to a
@@ -246,65 +323,44 @@ module ValidatesTimeliness
# argument in the position indicated by the first element of the proc arg # argument in the position indicated by the first element of the proc arg
# array. # array.
# #
# 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 {|i| i.to_i } }
# '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 {|i| i.to_i } }
#
def format_proc(order) def format_proc(order)
arg_map = format_proc_args arg_map = format_proc_args
args = order.invert.sort.map {|p| arg_map[p[1]][1] } args = order.invert.sort.map {|p| arg_map[p[1]][1] }
arr = [nil] * 7 arr = [nil] * 7
order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? } 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 {|i| i.to_i } }" proc_string = <<-EOL
lambda {|#{args.join(',')}|
md ||= nil
[#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|i| i.is_a?(Float) ? i : i.to_i }
}
EOL
eval proc_string eval proc_string
end end
def compile_formats(formats) def compile_formats(formats)
formats.collect { |format| regexp, format_proc = format_expression_generator(format) } formats.map { |format| [ format, *format_expression_generator(format) ] }
end end
# Pick expression set and combine date and datetimes for # Pick expression set and combine date and datetimes for
# datetime attributes to allow date string as datetime # datetime attributes to allow date string as datetime
def expression_set(type, string) def expression_set(type, string)
case type case type
when :date when :date
date_expressions date_expressions
when :time when :time
time_expressions time_expressions
when :datetime when :datetime
# gives a speed-up for date string as datetime attributes # gives a speed-up for date string as datetime attributes
if string.length < 11 if string.length < 11
date_expressions + datetime_expressions date_expressions + datetime_expressions
else else
datetime_expressions + date_expressions datetime_expressions + date_expressions
end end
end end
end end
def full_hour(hour, meridian)
hour = hour.to_i
return hour if meridian.nil?
if meridian.delete('.').downcase == 'am'
hour == 12 ? 0 : hour
else
hour == 12 ? hour : hour + 12
end
end
def unambiguous_year(year, threshold=30)
year = "#{year.to_i < threshold ? '20' : '19'}#{year}" if year.length == 2
year.to_i
end
def month_index(month)
return month.to_i if month.to_i.nonzero?
Date::ABBR_MONTHNAMES.index(month.capitalize) || Date::MONTHNAMES.index(month.capitalize)
end
def microseconds(usec)
(".#{usec}".to_f * 1_000_000).to_i
end
end end
end end
end end
ValidatesTimeliness::Formats.compile_format_expressions

View File

@@ -1,40 +0,0 @@
module ValidatesTimeliness
# Intercepts the date and time select helpers to allow the
# attribute value before type cast to be used as in the select helpers.
# This means that an invalid date or time will be redisplayed rather than the
# type cast value which would be nil if invalid.
module InstanceTag
def self.included(base)
selector_method = Rails::VERSION::STRING < '2.2' ? :date_or_time_select : :datetime_selector
base.class_eval do
alias_method :datetime_selector_without_timeliness, selector_method
alias_method selector_method, :datetime_selector_with_timeliness
end
base.alias_method_chain :value, :timeliness
end
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)
return value_without_timeliness(object) unless @timeliness_date_or_time_tag
raw_value = value_before_type_cast(object)
if raw_value.nil? || raw_value.acts_like?(:time) || raw_value.is_a?(Date)
return value_without_timeliness(object)
end
time_array = ParseDate.parsedate(raw_value)
TimelinessDateTime.new(*time_array[0..5])
end
end
end

View File

@@ -0,0 +1,18 @@
en:
activerecord:
errors:
messages:
invalid_date: "is not a valid date"
invalid_time: "is not a valid time"
invalid_datetime: "is not a valid datetime"
equal_to: "must be equal to {{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}}"
between: "must be between {{earliest}} and {{latest}}"
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 @@
require 'validates_timeliness/spec/rails/matchers/validate_timeliness'

View File

@@ -1,58 +0,0 @@
module ValidatesTimeliness
module MultiparameterAttributes
def self.included(base)
base.alias_method_chain :execute_callstack_for_multiparameter_attributes, :timeliness
end
# Overrides AR method to store multiparameter time and dates as string
# allowing validation later.
def execute_callstack_for_multiparameter_attributes_with_timeliness(callstack)
errors = []
callstack.each do |name, values|
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
if values.empty?
send(name + "=", nil)
else
column = column_for_attribute(name)
begin
value = if [:date, :time, :datetime].include?(column.type)
time_array_to_string(values, column.type)
else
klass.new(*values)
end
send(name + "=", value)
rescue => ex
errors << ActiveRecord::AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
end
end
end
unless errors.empty?
raise ActiveRecord::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
end
end
def time_array_to_string(values, type)
values = values.map(&:to_s)
case type
when :date
extract_date_from_multiparameter_attributes(values)
when :time
extract_time_from_multiparameter_attributes(values)
when :datetime
date_values, time_values = values.slice!(0, 3), values
extract_date_from_multiparameter_attributes(date_values) + " " + extract_time_from_multiparameter_attributes(time_values)
end
end
def extract_date_from_multiparameter_attributes(values)
[values[0], *values.slice(1, 2).map { |s| s.rjust(2, "0") }].join("-")
end
def extract_time_from_multiparameter_attributes(values)
values.last(3).map { |s| s.rjust(2, "0") }.join(":")
end
end
end

View File

@@ -0,0 +1,44 @@
module ValidatesTimeliness
module Parser
class << self
def parse(raw_value, type, options={})
return nil if raw_value.blank?
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
time_array = ValidatesTimeliness::Formats.parse(raw_value, type, options.reverse_merge(:strict => true))
return nil if time_array.nil?
if type == :date
Date.new(*time_array[0..2]) rescue nil
else
make_time(time_array[0..7])
end
end
def make_time(time_array)
# Enforce date part validity which Time class does not
return nil unless Date.valid_civil?(*time_array[0..2])
if Time.respond_to?(:zone) && ValidatesTimeliness.use_time_zones
Time.zone.local(*time_array)
else
# Older AR way of handling times with datetime fallback
begin
time_zone = ValidatesTimeliness.default_timezone
Time.send(time_zone, *time_array)
rescue ArgumentError, TypeError
zone_offset = time_zone == :local ? DateTime.local_offset : 0
time_array.pop # remove microseconds
DateTime.civil(*(time_array << zone_offset))
end
end
rescue ArgumentError, TypeError
nil
end
end
end
end

View File

@@ -0,0 +1,163 @@
module Spec
module Rails
module Matchers
class ValidateTimeliness
VALIDITY_TEST_VALUES = {
:date => {:pass => '2000-01-01', :fail => '2000-01-32'},
:time => {:pass => '12:00', :fail => '25:00'},
:datetime => {:pass => '2000-01-01 00:00:00', :fail => '2000-01-32 00:00:00'}
}
OPTION_TEST_SETTINGS = {
:equal_to => { :method => :+, :modify_on => :invalid },
:before => { :method => :-, :modify_on => :valid },
:after => { :method => :+, :modify_on => :valid },
:on_or_before => { :method => :+, :modify_on => :invalid },
:on_or_after => { :method => :-, :modify_on => :invalid }
}
def initialize(attribute, options)
@expected, @options = attribute, options
@validator = ValidatesTimeliness::Validator.new(options)
end
def matches?(record)
@record = record
@type = @options[:type]
valid = test_validity
valid = test_option(:equal_to) if valid && @options[:equal_to]
valid = test_option(:before) if valid && @options[:before]
valid = test_option(:after) if valid && @options[:after]
valid = test_option(:on_or_before) if valid && @options[:on_or_before]
valid = test_option(:on_or_after) if valid && @options[:on_or_after]
valid = test_between if valid && @options[:between]
return valid
end
def failure_message
"expected model to validate #{@type} attribute #{@expected.inspect} with #{@last_failure}"
end
def negative_failure_message
"expected not to validate #{@type} attribute #{@expected.inspect}"
end
def description
"have validated #{@type} attribute #{@expected.inspect}"
end
private
def test_validity
invalid_value = VALIDITY_TEST_VALUES[@type][:fail]
valid_value = parse_and_cast(VALIDITY_TEST_VALUES[@type][:pass])
error_matching(invalid_value, "invalid_#{@type}".to_sym) &&
no_error_matching(valid_value, "invalid_#{@type}".to_sym)
end
def test_option(option)
settings = OPTION_TEST_SETTINGS[option]
boundary = parse_and_cast(@options[option])
method = settings[:method]
valid_value, invalid_value = if settings[:modify_on] == :valid
[ boundary.send(method, 1), boundary ]
else
[ boundary, boundary.send(method, 1) ]
end
error_matching(invalid_value, option) &&
no_error_matching(valid_value, option)
end
def test_before
before = parse_and_cast(@options[:before])
error_matching(before - 1, :before) &&
no_error_matching(before, :before)
end
def test_between
between = parse_and_cast(@options[:between])
error_matching(between.first - 1, :between) &&
error_matching(between.last + 1, :between) &&
no_error_matching(between.first, :between) &&
no_error_matching(between.last, :between)
end
def parse_and_cast(value)
value = @validator.class.send(:evaluate_option_value, value, @type, @record)
@validator.class.send(:type_cast_value, value, @type)
end
def error_matching(value, option)
match = error_message_for(option)
@record.send("#{@expected}=", value)
@record.valid?
errors = @record.errors.on(@expected)
pass = [ errors ].flatten.any? {|error| /#{match}/ === error }
@last_failure = "error matching '#{match}' when value is #{format_value(value)}" unless pass
pass
end
def no_error_matching(value, option)
pass = !error_matching(value, option)
unless pass
error = error_message_for(option)
@last_failure = "no error matching '#{error}' when value is #{format_value(value)}"
end
pass
end
def error_message_for(option)
msg = @validator.error_messages[option]
restriction = @validator.class.send(:evaluate_option_value, @validator.configuration[option], @type, @record)
if restriction
restriction = [restriction] unless restriction.is_a?(Array)
restriction.map! {|r| @validator.class.send(:type_cast_value, r, @type) }
interpolate = @validator.send(:interpolation_values, option, restriction )
# get I18n message if defined and has interpolation keys in msg
if defined?(I18n) && !@validator.send(:custom_error_messages).include?(option)
msg = if defined?(ActiveRecord::Error)
ActiveRecord::Error.new(@record, @expected, option, interpolate).message
else
@record.errors.generate_message(@expected, option, interpolate)
end
else
msg = msg % interpolate
end
end
msg
end
def format_value(value)
return value if value.is_a?(String)
value.strftime(@validator.class.error_value_formats[@type])
end
end
def validate_date(attribute, options={})
options[:type] = :date
ValidateTimeliness.new(attribute, options)
end
def validate_time(attribute, options={})
options[:type] = :time
ValidateTimeliness.new(attribute, options)
end
def validate_datetime(attribute, options={})
options[:type] = :datetime
ValidateTimeliness.new(attribute, options)
end
end
end
end

View File

@@ -1,119 +0,0 @@
module Spec
module Rails
module Matchers
class ValidateTimeliness
def initialize(attribute, options)
@expected, @options = attribute, options
@options.reverse_merge!(error_messages)
end
def matches?(record)
@record = record
type = options[:type]
test_values = {
:date => {:pass => '2000-01-01', :fail => '2000-01-32'},
:time => {:pass => '12:00', :fail => '25:00'},
:datetime => {:pass => '2000-01-01 00:00:00', :fail => '2000-01-32 00:00:00'}
}
invalid_value = test_values[type][:fail]
valid_value = parse_and_cast(test_values[type][:pass])
valid = error_matching(invalid_value, /#{options["invalid_#{type}_message".to_sym]}/) &&
no_error_matching(valid_value, /#{options["invalid_#{type}_message".to_sym]}/)
valid = test_option(:before, :-) if options[:before] && valid
valid = test_option(:after, :+) if options[:after] && valid
valid = test_option(:on_or_before, :+, :modify_on => :invalid) if options[:on_or_before] && valid
valid = test_option(:on_or_after, :-, :modify_on => :invalid) if options[:on_or_after] && valid
return valid
end
def failure_message
"expected model to validate #{options[:type]} attribute #{expected.inspect} with #{last_failure}"
end
def negative_failure_message
"expected not to validate #{options[:type]} attribute #{expected.inspect}"
end
def description
"have validated #{options[:type]} attribute #{expected.inspect}"
end
private
attr_reader :actual, :expected, :record, :options, :last_failure
def test_option(option, modifier, settings={})
settings.reverse_merge!(:modify_on => :valid)
boundary = parse_and_cast(options[option])
valid_value, invalid_value = if settings[:modify_on] == :valid
[ boundary.send(modifier, 1), boundary ]
else
[ boundary, boundary.send(modifier, 1) ]
end
message = options["#{option}_message".to_sym]
error_matching(invalid_value, /#{message}/) &&
no_error_matching(valid_value, /#{message}/)
end
def parse_and_cast(value)
value = ActiveRecord::Base.send(:timeliness_restriction_value, value, record, options[:type])
cast_method = ActiveRecord::Base.send(:restriction_type_cast_method, options[:type])
value.send(cast_method) rescue nil
end
def error_messages
messages = ActiveRecord::Base.send(:timeliness_default_error_messages)
messages = messages.inject({}) {|h, (k, v)| h[k] = v.sub(' %s', ''); h }
@options.reverse_merge!(messages)
end
def error_matching(value, match)
record.send("#{expected}=", value)
record.valid?
errors = record.errors.on(expected)
pass = [ errors ].flatten.any? {|error| match === error }
@last_failure = "error matching #{match.inspect} when value is #{format_value(value)}" unless pass
pass
end
def no_error_matching(value, match)
pass = !error_matching(value, match)
@last_failure = "no error matching #{match.inspect} when value is #{format_value(value)}" unless pass
pass
end
def format_value(value)
return value if value.is_a?(String)
value.strftime(ActiveRecord::Errors.date_time_error_value_formats[options[:type]])
end
end
def validate_date(attribute, options={})
options[:type] = :date
validate_timeliness_of(attribute, options)
end
def validate_time(attribute, options={})
options[:type] = :time
validate_timeliness_of(attribute, options)
end
def validate_datetime(attribute, options={})
options[:type] = :datetime
validate_timeliness_of(attribute, options)
end
private
def validate_timeliness_of(attribute, options={})
ValidateTimeliness.new(attribute, options)
end
end
end
end

View File

@@ -0,0 +1,46 @@
module ValidatesTimeliness
module ValidationMethods
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def validates_time(*attr_names)
configuration = attr_names.extract_options!
configuration[:type] = :time
validates_timeliness_of(attr_names, configuration)
end
def validates_date(*attr_names)
configuration = attr_names.extract_options!
configuration[:type] = :date
validates_timeliness_of(attr_names, configuration)
end
def validates_datetime(*attr_names)
configuration = attr_names.extract_options!
configuration[:type] = :datetime
validates_timeliness_of(attr_names, configuration)
end
private
def validates_timeliness_of(attr_names, configuration)
validator = ValidatesTimeliness::Validator.new(configuration.symbolize_keys)
# bypass handling of allow_nil and allow_blank to validate raw value
configuration.delete(:allow_nil)
configuration.delete(:allow_blank)
validates_each(attr_names, configuration) do |record, attr_name, value|
validator.call(record, attr_name, value)
end
end
end
end
end
ActiveRecord::Base.send(:include, ValidatesTimeliness::ValidationMethods)

View File

@@ -1,189 +0,0 @@
module ValidatesTimeliness
# Adds ActiveRecord validation methods for date, time and datetime validation.
# The validity of values can be restricted to be before or after certain dates
# or times.
module Validations
# Error messages and error value formats added to AR defaults to allow
# global override.
def self.included(base)
base.extend ClassMethods
base.class_inheritable_accessor :ignore_datetime_restriction_errors
base.ignore_datetime_restriction_errors = false
ActiveRecord::Errors.class_inheritable_accessor :date_time_error_value_formats
ActiveRecord::Errors.date_time_error_value_formats = {
:time => '%H:%M:%S',
:date => '%Y-%m-%d',
:datetime => '%Y-%m-%d %H:%M:%S'
}
ActiveRecord::Errors.default_error_messages.update(
:invalid_date => "is not a valid date",
:invalid_time => "is not a valid time",
:invalid_datetime => "is not a valid datetime",
:before => "must be before %s",
:on_or_before => "must be on or before %s",
:after => "must be after %s",
:on_or_after => "must be on or after %s"
)
end
module ClassMethods
def parse_date_time(raw_value, type, strict=true)
return nil if raw_value.blank?
return raw_value if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
time_array = ValidatesTimeliness::Formats.parse(raw_value, type, strict)
raise if time_array.nil?
# Rails dummy time date part is defined as 2000-01-01
time_array[0..2] = 2000, 1, 1 if type == :time
# Date.new enforces days per month, unlike Time
date = Date.new(*time_array[0..2]) unless type == :time
return date if type == :date
# Create time object which checks time part, and return time object
make_time(time_array)
rescue
nil
end
# The main validation method which can be used directly or called through
# the other specific type validation methods.
def validates_timeliness_of(*attr_names)
configuration = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false }
configuration.update(timeliness_default_error_messages)
configuration.update(attr_names.extract_options!)
# we need to check raw value for blank or nil
allow_nil = configuration.delete(:allow_nil)
allow_blank = configuration.delete(:allow_blank)
validates_each(attr_names, configuration) do |record, attr_name, value|
raw_value = record.send("#{attr_name}_before_type_cast")
next if (raw_value.nil? && allow_nil) || (raw_value.blank? && allow_blank)
record.errors.add(attr_name, configuration[:blank_message]) and next if raw_value.blank?
column = record.column_for_attribute(attr_name)
begin
unless time = parse_date_time(raw_value, configuration[:type])
record.errors.add(attr_name, configuration["invalid_#{configuration[:type]}_message".to_sym])
next
end
validate_timeliness_restrictions(record, attr_name, time, configuration)
rescue Exception => e
record.errors.add(attr_name, configuration["invalid_#{configuration[:type]}_message".to_sym])
end
end
end
# Use this validation to force validation of values and restrictions
# as dummy time
def validates_time(*attr_names)
configuration = attr_names.extract_options!
configuration[:type] = :time
validates_timeliness_of(attr_names, configuration)
end
# Use this validation to force validation of values and restrictions
# as Date
def validates_date(*attr_names)
configuration = attr_names.extract_options!
configuration[:type] = :date
validates_timeliness_of(attr_names, configuration)
end
# Use this validation to force validation of values and restrictions
# as Time/DateTime
def validates_datetime(*attr_names)
configuration = attr_names.extract_options!
configuration[:type] = :datetime
validates_timeliness_of(attr_names, configuration)
end
private
def timeliness_restriction_value(restriction, record, type)
case restriction
when Time, Date, DateTime
restriction
when Symbol
timeliness_restriction_value(record.send(restriction), record, type)
when Proc
timeliness_restriction_value(restriction.call(record), record, type)
else
parse_date_time(restriction, type, false)
end
end
def restriction_type_cast_method(type)
case type
when :time then :to_dummy_time
when :date then :to_date
when :datetime then :to_time
end
end
# Validate value against the temporal restrictions. Restriction values
# maybe of mixed type, so they are evaluated as a common type, which may
# require conversion. The type used is defined by validation type.
def validate_timeliness_restrictions(record, attr_name, value, configuration)
restriction_methods = {:before => '<', :after => '>', :on_or_before => '<=', :on_or_after => '>='}
type_cast_method = restriction_type_cast_method(configuration[:type])
display = ActiveRecord::Errors.date_time_error_value_formats[configuration[:type]]
value = value.send(type_cast_method)
restriction_methods.each do |option, method|
next unless restriction = configuration[option]
begin
compare = timeliness_restriction_value(restriction, record, configuration[:type])
next if compare.nil?
compare = compare.send(type_cast_method)
record.errors.add(attr_name, configuration["#{option}_message".to_sym] % compare.strftime(display)) unless value.send(method, compare)
rescue
record.errors.add(attr_name, "restriction '#{option}' value was invalid") unless self.ignore_datetime_restriction_errors
end
end
end
# Map error message keys to *_message to merge with validation options
def timeliness_default_error_messages
defaults = ActiveRecord::Errors.default_error_messages.slice(
:blank, :invalid_date, :invalid_time, :invalid_datetime, :before, :on_or_before, :after, :on_or_after)
returning({}) do |messages|
defaults.each {|k, v| messages["#{k}_message".to_sym] = v }
end
end
# Create time in correct timezone. For Rails 2.1 that is value in
# Time.zone. Rails 2.0 should be default_timezone.
def make_time(time_array)
if Time.respond_to?(:zone) && time_zone_aware_attributes
Time.zone.local(*time_array)
else
begin
Time.send(ActiveRecord::Base.default_timezone, *time_array)
rescue ArgumentError, TypeError
zone_offset = ActiveRecord::Base.default_timezone == :local ? DateTime.local_offset : 0
time_array.pop # remove microseconds
DateTime.civil(*(time_array << zone_offset))
end
end
end
end
end
end

View File

@@ -0,0 +1,233 @@
module ValidatesTimeliness
class Validator
cattr_accessor :ignore_restriction_errors
self.ignore_restriction_errors = false
RESTRICTION_METHODS = {
:equal_to => :==,
:before => :<,
:after => :>,
:on_or_before => :<=,
:on_or_after => :>=,
:between => lambda {|v, r| (r.first..r.last).include?(v) }
}
VALID_OPTIONS = [
:on, :if, :unless, :allow_nil, :empty, :allow_blank,
:with_time, :with_date, :ignore_usec, :format,
:invalid_time_message, :invalid_date_message, :invalid_datetime_message
] + RESTRICTION_METHODS.keys.map {|option| [option, "#{option}_message".to_sym] }.flatten
attr_reader :configuration, :type
def initialize(configuration)
defaults = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false, :ignore_usec => false }
@configuration = defaults.merge(configuration)
@type = @configuration.delete(:type)
validate_options(@configuration)
end
def call(record, attr_name, value)
raw_value = raw_value(record, attr_name) || value
if value.is_a?(String) || configuration[:format]
value = ValidatesTimeliness::Parser.parse(raw_value, type, :strict => false, :format => configuration[:format])
end
return if (raw_value.nil? && configuration[:allow_nil]) || (raw_value.blank? && configuration[:allow_blank])
return add_error(record, attr_name, :blank) if raw_value.blank?
return add_error(record, attr_name, "invalid_#{type}".to_sym) if value.nil?
validate_restrictions(record, attr_name, value)
end
def error_messages
@error_messages ||= self.class.default_error_messages.merge(custom_error_messages)
end
private
def raw_value(record, attr_name)
record.send("#{attr_name}_before_type_cast") rescue nil
end
def validate_restrictions(record, attr_name, value)
if configuration[:with_time] || configuration[:with_date]
value = combine_date_and_time(value, record)
end
value = self.class.type_cast_value(value, implied_type, configuration[:ignore_usec])
return if value.nil?
RESTRICTION_METHODS.each do |option, method|
next unless restriction = configuration[option]
begin
restriction = self.class.evaluate_option_value(restriction, implied_type, record)
next if restriction.nil?
restriction = self.class.type_cast_value(restriction, implied_type, configuration[:ignore_usec])
unless evaluate_restriction(restriction, value, method)
add_error(record, attr_name, option, interpolation_values(option, restriction))
end
rescue
unless self.class.ignore_restriction_errors
add_error(record, attr_name, "restriction '#{option}' value was invalid")
end
end
end
end
def interpolation_values(option, restriction)
format = self.class.error_value_formats[type]
restriction = [restriction] unless restriction.is_a?(Array)
if defined?(I18n)
message = I18n.t('activerecord.errors.messages')[option]
subs = message.scan(/\{\{([^\}]*)\}\}/)
interpolations = {}
subs.each_with_index {|s, i| interpolations[s[0].to_sym] = restriction[i].strftime(format) }
interpolations
else
restriction.map {|r| r.strftime(format) }
end
end
def evaluate_restriction(restriction, value, comparator)
return true if restriction.nil?
case comparator
when Symbol
value.send(comparator, restriction)
when Proc
comparator.call(value, restriction)
end
end
def add_error(record, attr_name, message, interpolate=nil)
if defined?(I18n)
custom = custom_error_messages[message]
record.errors.add(attr_name, message, { :default => custom }.merge(interpolate || {}))
else
message = error_messages[message] if message.is_a?(Symbol)
message = message % interpolate
record.errors.add(attr_name, message)
end
end
def custom_error_messages
@custom_error_messages ||= configuration.inject({}) {|msgs, (k, v)|
if md = /(.*)_message$/.match(k.to_s)
msgs[md[1].to_sym] = v
end
msgs
}
end
def combine_date_and_time(value, record)
if type == :date
date = value
time = configuration[:with_time]
else
date = configuration[:with_date]
time = value
end
date, time = self.class.evaluate_option_value(date, :date, record), self.class.evaluate_option_value(time, :time, record)
return if date.nil? || time.nil?
ValidatesTimeliness::Parser.make_time([date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
end
def validate_options(options)
invalid_for_type = ([:time, :date, :datetime] - [type]).map {|k| "invalid_#{k}_message".to_sym }
invalid_for_type << :with_date unless type == :time
invalid_for_type << :with_time unless type == :date
options.assert_valid_keys(VALID_OPTIONS - invalid_for_type)
end
def implied_type
@implied_type ||= configuration[:with_date] || configuration[:with_time] ? :datetime : type
end
# class methods
class << self
def default_error_messages
if defined?(I18n)
I18n.t('activerecord.errors.messages')
else
::ActiveRecord::Errors.default_error_messages
end
end
def error_value_formats
if defined?(I18n)
I18n.t('validates_timeliness.error_value_formats')
else
@@error_value_formats
end
end
def error_value_formats=(formats)
@@error_value_formats = formats
end
def evaluate_option_value(value, type, record)
case value
when Time, Date
value
when Symbol
evaluate_option_value(record.send(value), type, record)
when Proc
result = value.arity > 0 ? value.call(record) : value.call
evaluate_option_value(result, type, record)
when Array
value.map {|r| evaluate_option_value(r, type, record) }.sort
when Range
evaluate_option_value([value.first, value.last], type, record)
else
ValidatesTimeliness::Parser.parse(value, type, :strict => false)
end
end
def type_cast_value(value, type, ignore_usec=false)
if value.is_a?(Array)
value.map {|v| type_cast_value(v, type, ignore_usec) }
else
value = case type
when :time
dummy_time(value)
when :date
value.to_date
when :datetime
if value.is_a?(Time) || value.is_a?(DateTime)
value.to_time
else
value.to_time(ValidatesTimeliness.default_timezone)
end
else
nil
end
if ignore_usec && value.is_a?(Time)
ValidatesTimeliness::Parser.make_time(Array(value).reverse[4..9])
else
value
end
end
end
def dummy_time(value)
if value.is_a?(Time) || value.is_a?(DateTime)
time = [value.hour, value.min, value.sec]
else
time = [0,0,0]
end
dummy_date = ValidatesTimeliness::Formats.dummy_date_for_time_type
ValidatesTimeliness::Parser.make_time(dummy_date + time)
end
end
end
end

View File

@@ -0,0 +1,3 @@
module ValidatesTimeliness
VERSION = "2.2.2"
end

View File

@@ -1,6 +1,10 @@
require File.dirname(__FILE__) + '/spec_helper' require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe ValidatesTimeliness::InstanceTag, :type => :helper do ValidatesTimeliness.enable_datetime_select_extension!
describe 'ValidatesTimeliness::ActionView::InstanceTag' do
include ActionView::Helpers::DateHelper
include ActionController::Assertions::SelectorAssertions
before do before do
@person = Person.new @person = Person.new

View File

@@ -1,25 +1,37 @@
require File.dirname(__FILE__) + '/spec_helper' require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe ValidatesTimeliness::AttributeMethods do describe ValidatesTimeliness::ActiveRecord::AttributeMethods do
include ValidatesTimeliness::AttributeMethods
include ValidatesTimeliness::Validations
before do before do
@person = Person.new @person = Person.new
end end
it "should call write_date_time_attribute when date attribute assigned value" do
@person.should_receive(:write_date_time_attribute)
@person.birth_date = "2000-01-01"
end
it "should call write_date_time_attribute when time attribute assigned value" do
@person.should_receive(:write_date_time_attribute)
@person.birth_time = "12:00"
end
it "should call write_date_time_attribute when datetime attribute assigned value" do
@person.should_receive(:write_date_time_attribute)
@person.birth_date_and_time = "2000-01-01 12:00"
end
it "should call parser on write for datetime attribute" do it "should call parser on write for datetime attribute" do
@person.class.should_receive(:parse_date_time).once ValidatesTimeliness::Parser.should_receive(:parse).once
@person.birth_date_and_time = "2000-01-01 02:03:04" @person.birth_date_and_time = "2000-01-01 02:03:04"
end end
it "should call parser on write for date attribute" do it "should call parser on write for date attribute" do
@person.class.should_receive(:parse_date_time).once ValidatesTimeliness::Parser.should_receive(:parse).once
@person.birth_date = "2000-01-01" @person.birth_date = "2000-01-01"
end end
it "should call parser on write for time attribute" do it "should call parser on write for time attribute" do
@person.class.should_receive(:parse_date_time).once ValidatesTimeliness::Parser.should_receive(:parse).once
@person.birth_time = "12:00" @person.birth_time = "12:00"
end end
@@ -39,6 +51,11 @@ describe ValidatesTimeliness::AttributeMethods do
@person.birth_date_and_time.should be_kind_of(Time) @person.birth_date_and_time.should be_kind_of(Time)
end end
it "should return Time object for datetime attribute read method when assigned Date object" do
@person.birth_date_and_time = Date.today
@person.birth_date_and_time.should be_kind_of(Time)
end
it "should return Time object for datetime attribute read method when assigned string" do it "should return Time object for datetime attribute read method when assigned string" do
@person.birth_date_and_time = "2000-01-01 02:03:04" @person.birth_date_and_time = "2000-01-01 02:03:04"
@person.birth_date_and_time.should be_kind_of(Time) @person.birth_date_and_time.should be_kind_of(Time)
@@ -68,7 +85,20 @@ describe ValidatesTimeliness::AttributeMethods do
@person.birth_date_and_time_before_type_cast.should be_nil @person.birth_date_and_time_before_type_cast.should be_nil
end end
unless RAILS_VER < '2.1' if RAILS_VER < '2.1'
it "should return time object from database in default timezone" do
ActiveRecord::Base.default_timezone = :utc
time_string = "2000-01-01 09:00:00"
@person = Person.new
@person.birth_date_and_time = time_string
@person.save
@person.reload
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z').should == time_string + ' GMT'
end
else
it "should return stored time string as Time with correct timezone" do it "should return stored time string as Time with correct timezone" do
Time.zone = 'Melbourne' Time.zone = 'Melbourne'
time_string = "2000-06-01 02:03:04" time_string = "2000-06-01 02:03:04"
@@ -86,84 +116,6 @@ describe ValidatesTimeliness::AttributeMethods do
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z %z').should == time_string + ' EST +1000' @person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z %z').should == time_string + ' EST +1000'
end end
describe "dirty attributes" do
it "should return true for attribute changed? when value updated" do
time_string = "2000-01-01 02:03:04"
@person.birth_date_and_time = time_string
@person.birth_date_and_time_changed?.should be_true
end
it "should show changes when time attribute changed from nil to Time object" do
time_string = "2000-01-01 02:03:04"
@person.birth_date_and_time = time_string
time = @person.birth_date_and_time
@person.changes.should == {"birth_date_and_time" => [nil, time]}
end
it "should show changes when time attribute changed from Time object to nil" do
time_string = "2020-01-01 02:03:04"
@person.birth_date_and_time = time_string
@person.save false
@person.reload
time = @person.birth_date_and_time
@person.birth_date_and_time = nil
@person.changes.should == {"birth_date_and_time" => [time, nil]}
end
it "should show no changes when assigned same value as Time object" do
time_string = "2020-01-01 02:03:04"
@person.birth_date_and_time = time_string
@person.save false
@person.reload
time = @person.birth_date_and_time
@person.birth_date_and_time = time
@person.changes.should == {}
end
it "should show no changes when assigned same value as time string" do
time_string = "2020-01-01 02:03:04"
@person.birth_date_and_time = time_string
@person.save false
@person.reload
@person.birth_date_and_time = time_string
@person.changes.should == {}
end
end
else
it "should return time object from database in default timezone" do
ActiveRecord::Base.default_timezone = :utc
time_string = "2000-01-01 09:00:00"
@person = Person.new
@person.birth_date_and_time = time_string
@person.save
@person.reload
@person.birth_date_and_time.strftime('%Y-%m-%d %H:%M:%S %Z').should == time_string + ' GMT'
end
end
it "should return same time object on repeat reads on existing object" do
Time.zone = 'Melbourne' unless RAILS_VER < '2.1'
time_string = "2000-01-01 09:00:00"
@person = Person.new
@person.birth_date_and_time = time_string
@person.save!
@person.reload
time = @person.birth_date_and_time
@person.birth_date_and_time.should == time
end
it "should return same date object on repeat reads on existing object" do
date_string = Date.today
@person = Person.new
@person.birth_date = date_string
@person.save!
@person.reload
date = @person.birth_date
@person.birth_date.should == date
end end
it "should return correct date value after new value assigned" do it "should return correct date value after new value assigned" do
@@ -185,5 +137,5 @@ describe ValidatesTimeliness::AttributeMethods do
@person.reload @person.reload
@person.birth_date.should == tomorrow @person.birth_date.should == tomorrow
end end
end end

View File

@@ -1,22 +1,22 @@
require File.dirname(__FILE__) + '/spec_helper' require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe ValidatesTimeliness::MultiparameterAttributes do describe ValidatesTimeliness::ActiveRecord::MultiparameterAttributes do
def obj def obj
@obj ||= Person.new @obj ||= Person.new
end end
it "should convert array for datetime type into datetime string" do it "should convert array for datetime type into datetime string" do
time_string = obj.time_array_to_string([2000,2,1,9,10,11], :datetime) time_string = time_array_to_string([2000,2,1,9,10,11], :datetime)
time_string.should == "2000-02-01 09:10:11" time_string.should == "2000-02-01 09:10:11"
end end
it "should convert array for date type into date string" do it "should convert array for date type into date string" do
time_string = obj.time_array_to_string([2000,2,1], :date) time_string = time_array_to_string([2000,2,1], :date)
time_string.should == "2000-02-01" time_string.should == "2000-02-01"
end end
it "should convert array for time type into time string" do it "should convert array for time type into time string" do
time_string = obj.time_array_to_string([2000,1,1,9,10,11], :time) time_string = time_array_to_string([2000,1,1,9,10,11], :time)
time_string.should == "09:10:11" time_string.should == "09:10:11"
end end
@@ -39,12 +39,14 @@ describe ValidatesTimeliness::MultiparameterAttributes do
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack) obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
end end
unless RAILS_VER < '2.1' # sqlite doesn't support :time attribute in rails 2.0.x it "should store time string for a time column" do
it "should store time string for a time column" do obj.should_receive(:birth_time=).once.with("09:10:11")
obj.should_receive(:birth_time=).once.with("09:10:11") obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
obj.send(:execute_callstack_for_multiparameter_attributes, @callstack)
end
end end
end end
def time_array_to_string(*args)
ValidatesTimeliness::ActiveRecord.time_array_to_string(*args)
end
end end

View File

@@ -1,31 +0,0 @@
require File.dirname(__FILE__) + '/spec_helper'
describe ValidatesTimeliness::CoreExtensions::Date do
before do
@a_date = Date.new(2008, 7, 1)
end
it "should " do
@a_date.to_dummy_time.should == Time.mktime(2000,1,1,0,0,0)
end
end
describe ValidatesTimeliness::CoreExtensions::Time do
before do
@a_time = Time.mktime(2008, 7, 1, 2, 3, 4)
end
it "should " do
@a_time.to_dummy_time.should == Time.mktime(2000,1,1,2,3,4)
end
end
describe ValidatesTimeliness::CoreExtensions::DateTime do
before do
@a_datetime = DateTime.new(2008, 7, 1, 2, 3, 4)
end
it "should " do
@a_datetime.to_dummy_time.should == Time.mktime(2000,1,1,2,3,4)
end
end

View File

@@ -1,51 +1,6 @@
require File.dirname(__FILE__) + '/spec_helper' require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
describe ValidatesTimeliness::Formats do describe ValidatesTimeliness::Formats do
attr_reader :formats
before do
@formats = ValidatesTimeliness::Formats
end
describe "expression generator" do
it "should generate regexp for time" do
generate_regexp_str('hh:nn:ss').should == '/(\d{2}):(\d{2}):(\d{2})/'
end
it "should generate regexp for time with meridian" do
generate_regexp_str('hh:nn:ss ampm').should == '/(\d{2}):(\d{2}):(\d{2}) ((?:[aApP])\.?[mM]\.?)/'
end
it "should generate regexp for time with meridian and optional space between" do
generate_regexp_str('hh:nn:ss_ampm').should == '/(\d{2}):(\d{2}):(\d{2})\s?((?:[aApP])\.?[mM]\.?)/'
end
it "should generate regexp for time with single or double digits" do
generate_regexp_str('h:n:s').should == '/(\d{1,2}):(\d{1,2}):(\d{1,2})/'
end
it "should generate regexp for date" do
generate_regexp_str('yyyy-mm-dd').should == '/(\d{4})-(\d{2})-(\d{2})/'
end
it "should generate regexp for date with slashes" do
generate_regexp_str('dd/mm/yyyy').should == '/(\d{2})\/(\d{2})\/(\d{4})/'
end
it "should generate regexp for date with dots" do
generate_regexp_str('dd.mm.yyyy').should == '/(\d{2})\.(\d{2})\.(\d{4})/'
end
it "should generate regexp for Ruby time string" do
expected = '/(\w{3,9}) (\w{3,9}) (\d{2}):(\d{2}):(\d{2}) (?:[+-]\d{2}:?\d{2}) (\d{4})/'
generate_regexp_str('ddd mmm hh:nn:ss zo yyyy').should == expected
end
it "should generate regexp for iso8601 datetime" do
expected = '/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:Z|(?:[+-]\d{2}:?\d{2}))/'
generate_regexp_str('yyyy-mm-ddThh:nn:ss(?:Z|zo)').should == expected
end
end
describe "format proc generator" do describe "format proc generator" do
it "should generate proc which outputs date array with values in correct order" do it "should generate proc which outputs date array with values in correct order" do
@@ -71,6 +26,10 @@ describe ValidatesTimeliness::Formats do
it "should generate proc which outputs time array with microseconds" do it "should generate proc which outputs time array with microseconds" do
generate_proc('hh:nn:ss.u').call('01', '02', '03', '99').should == [0,0,0,1,2,3,990000] generate_proc('hh:nn:ss.u').call('01', '02', '03', '99').should == [0,0,0,1,2,3,990000]
end end
it "should generate proc which outputs datetime array with zone offset" do
generate_proc('yyyy-mm-dd hh:nn:ss.u zo').call('2001', '02', '03', '04', '05', '06', '99', '+10:00').should == [2001,2,3,4,5,6,990000,36000]
end
end end
describe "validation regexps" do describe "validation regexps" do
@@ -136,44 +95,100 @@ describe ValidatesTimeliness::Formats do
end end
end end
describe "extracting values" do describe "parse" do
it "should return time array from date string" do it "should return time array from date string" do
time_array = formats.parse('12:13:14', :time, true) time_array = formats.parse('12:13:14', :time, :strict => true)
time_array.should == [0,0,0,12,13,14,0] time_array.should == [2000,1,1,12,13,14,0]
end end
it "should return date array from time string" do it "should return date array from time string" do
time_array = formats.parse('2000-02-01', :date, true) time_array = formats.parse('2000-02-01', :date, :strict => true)
time_array.should == [2000,2,1,0,0,0,0] time_array.should == [2000,2,1,0,0,0,0]
end end
it "should return datetime array from string value" do it "should return datetime array from string value" do
time_array = formats.parse('2000-02-01 12:13:14', :datetime, true) time_array = formats.parse('2000-02-01 12:13:14', :datetime, :strict => true)
time_array.should == [2000,2,1,12,13,14,0] time_array.should == [2000,2,1,12,13,14,0]
end end
it "should parse date string when type is datetime" do it "should parse date string when type is datetime" do
time_array = formats.parse('2000-02-01', :datetime, false) time_array = formats.parse('2000-02-01', :datetime, :strict => false)
time_array.should == [2000,2,1,0,0,0,0] time_array.should == [2000,2,1,0,0,0,0]
end end
it "should ignore time when extracting date and strict is false" do it "should ignore time when extracting date and strict is false" do
time_array = formats.parse('2000-02-01 12:12', :date, false) time_array = formats.parse('2000-02-01 12:13', :date, :strict => false)
time_array.should == [2000,2,1,0,0,0,0]
end
it "should ignore time when extracting date from format with trailing year and strict is false" do
time_array = formats.parse('01-02-2000 12:13', :date, :strict => false)
time_array.should == [2000,2,1,0,0,0,0] time_array.should == [2000,2,1,0,0,0,0]
end end
it "should ignore date when extracting time and strict is false" do it "should ignore date when extracting time and strict is false" do
time_array = formats.parse('2000-02-01 12:12', :time, false) time_array = formats.parse('2000-02-01 12:13', :time, :strict => false)
time_array.should == [0,0,0,12,12,0,0] time_array.should == [2000,1,1,12,13,0,0]
end
it "should return zone offset when :include_offset options is true" do
time_array = formats.parse('2000-02-01T12:13:14-10:30', :datetime, :include_offset => true)
time_array.should == [2000,2,1,12,13,14,0,-37800]
end end
end end
describe "removing formats" do describe "parse with format option" do
before do it "should return values if string matches specified format" do
formats.compile_format_expressions time_array = formats.parse('2000-02-01 12:13:14', :datetime, :format => 'yyyy-mm-dd hh:nn:ss')
time_array.should == [2000,2,1,12,13,14,0]
end end
it "should return nil if string does not match specified format" do
time_array = formats.parse('2000-02-01 12:13', :datetime, :format => 'yyyy-mm-dd hh:nn:ss')
time_array.should be_nil
end
end
describe "parsing date with ambiguous year" do
it "should return year in current century if year below threshold" do
time_array = formats.parse('01-02-29', :date)
time_array.should == [2029,2,1,0,0,0,0]
end
it "should return year in last century if year at or above threshold" do
time_array = formats.parse('01-02-30', :date)
time_array.should == [1930,2,1,0,0,0,0]
end
it "should allow custom threshold" do
default = ValidatesTimeliness::Formats.ambiguous_year_threshold
ValidatesTimeliness::Formats.ambiguous_year_threshold = 40
time_array = formats.parse('01-02-39', :date)
time_array.should == [2039,2,1,0,0,0,0]
time_array = formats.parse('01-02-40', :date)
time_array.should == [1940,2,1,0,0,0,0]
ValidatesTimeliness::Formats.ambiguous_year_threshold = default
end
end
describe "parse with custom dummy date values" do
before(:all) do
@old_dummy_date = formats.dummy_date_for_time_type
formats.dummy_date_for_time_type = [2009,1,1]
end
it "should return time array with custom dummy date" do
time_array = formats.parse('12:13:14', :time, :strict => true)
time_array.should == [2009,1,1,12,13,14,0]
end
after(:all) do
formats.dummy_date_for_time_type = @old_dummy_date
end
end
describe "removing formats" do
it "should remove format from format array" do it "should remove format from format array" do
formats.remove_formats(:time, 'h.nn_ampm') formats.remove_formats(:time, 'h.nn_ampm')
formats.time_formats.should_not include("h o'clock") formats.time_formats.should_not include("h o'clock")
@@ -191,7 +206,7 @@ describe ValidatesTimeliness::Formats do
after do after do
formats.time_formats << 'h.nn_ampm' formats.time_formats << 'h.nn_ampm'
# reload class instead formats.compile_format_expressions
end end
end end
@@ -215,7 +230,7 @@ describe ValidatesTimeliness::Formats do
formats.add_formats(:time, "ss:hh:nn", :before => 'hh:nn:ss') formats.add_formats(:time, "ss:hh:nn", :before => 'hh:nn:ss')
validate("59:23:58", :time).should be_true validate("59:23:58", :time).should be_true
time_array = formats.parse('59:23:58', :time) time_array = formats.parse('59:23:58', :time)
time_array.should == [0,0,0,23,58,59,0] time_array.should == [2000,1,1,23,58,59,0]
end end
it "should raise error if format exists" do it "should raise error if format exists" do
@@ -247,9 +262,14 @@ describe ValidatesTimeliness::Formats do
end end
end end
def formats
ValidatesTimeliness::Formats
end
def validate(time_string, type) def validate(time_string, type)
valid = false valid = false
formats.send("#{type}_expressions").each do |(regexp, processor)| formats.send("#{type}_expressions").each do |format, regexp, processor|
valid = true and break if /\A#{regexp}\Z/ =~ time_string valid = true and break if /\A#{regexp}\Z/ =~ time_string
end end
valid valid

View File

@@ -9,10 +9,10 @@
# ginger spec # ginger spec
# #
Ginger.configure do |config| Ginger.configure do |config|
rails_versions = ['2.0.2', '2.1.2', '2.2.2'] rails_versions = ['2.0.2', '2.1.2', '2.2.2', '2.3.3', '2.3.4']
rails_versions.each do |v| rails_versions.each do |v|
g = Ginger::Scenario.new g = Ginger::Scenario.new("Rails #{v}")
g['rails'] = v g['rails'] = v
config.scenarios << g.dup config.scenarios << g.dup
end end

61
spec/parser_spec.rb Normal file
View File

@@ -0,0 +1,61 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
describe ValidatesTimeliness::Parser do
attr_accessor :person
describe "parse" do
it "should return time object for valid time string" do
parse("2000-01-01 12:13:14", :datetime).should be_kind_of(Time)
end
it "should return nil for time string with invalid date part" do
parse("2000-02-30 12:13:14", :datetime).should be_nil
end
it "should return nil for time string with invalid time part" do
parse("2000-02-01 25:13:14", :datetime).should be_nil
end
it "should return Time object when passed a Time object" do
parse(Time.now, :datetime).should be_kind_of(Time)
end
if RAILS_VER >= '2.1'
it "should convert time string into current timezone" do
Time.zone = 'Melbourne'
time = parse("2000-01-01 12:13:14", :datetime)
Time.zone.utc_offset.should == 10.hours
end
end
it "should return nil for invalid date string" do
parse("2000-02-30", :date).should be_nil
end
def parse(*args)
ValidatesTimeliness::Parser.parse(*args)
end
end
describe "make_time" do
if RAILS_VER >= '2.1'
it "should create time using current timezone" do
Time.zone = 'Melbourne'
time = ValidatesTimeliness::Parser.make_time([2000,1,1,12,0,0])
time.zone.should == "EST"
end
else
it "should create time using default timezone" do
time = ValidatesTimeliness::Parser.make_time([2000,1,1,12,0,0])
time.zone.should == "UTC"
end
end
end
end

View File

@@ -0,0 +1,19 @@
# patches adapter in rails 2.0 which mistakenly made time attributes map to datetime column type
ActiveRecord::ConnectionAdapters::SQLiteAdapter.class_eval do
def native_database_types #:nodoc:
{
:primary_key => default_primary_key_type,
:string => { :name => "varchar", :limit => 255 },
:text => { :name => "text" },
:integer => { :name => "integer" },
:float => { :name => "float" },
:decimal => { :name => "decimal" },
:datetime => { :name => "datetime" },
:timestamp => { :name => "datetime" },
:time => { :name => "time" },
:date => { :name => "date" },
:binary => { :name => "blob" },
:boolean => { :name => "boolean" }
}
end
end

View File

@@ -0,0 +1,245 @@
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
require 'validates_timeliness/matcher'
class NoValidation < Person
end
class WithValidation < Person
validates_date :birth_date,
:equal_to => '2000-01-01',
:before => '2000-01-10',
:after => '2000-01-01',
:on_or_before => '2000-01-09',
:on_or_after => '2000-01-02',
:between => ['2000-01-01', '2000-01-03']
validates_time :birth_time,
:equal_to => '09:00',
:before => '23:00',
:after => '09:00',
:on_or_before => '22:00',
:on_or_after => '10:00',
:between => ['09:00', '17:00']
validates_datetime :birth_date_and_time,
:equal_to => '2000-01-01 09:00',
:before => '2000-01-10 23:00',
:after => '2000-01-01 09:00',
:on_or_before => '2000-01-09 23:00',
:on_or_after => '2000-01-02 09:00',
:between => ['2000-01-01 09:00', '2000-01-01 17:00']
end
class CustomMessages < Person
validates_date :birth_date,
:invalid_date_message => 'is not really a date',
:before => '2000-01-10',
:before_message => 'is too late',
:after => '2000-01-01',
:after_message => 'is too early',
:on_or_before => '2000-01-09',
:on_or_before_message => 'is just too late',
:on_or_after => '2000-01-02',
:on_or_after_message => 'is just too early'
end
describe "ValidateTimeliness matcher" do
attr_accessor :no_validation, :with_validation
@@attribute_for_type = { :date => :birth_date, :time => :birth_time, :datetime => :birth_date_and_time }
before do
@no_validation = NoValidation.new
@with_validation = WithValidation.new
end
[:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type))
end
it "should report that #{type} is not validated" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type))
end
end
describe "with equal_to option" do
test_values = {
:date => ['2000-01-01', '2000-01-02'],
:time => ['09:00', '09:01'],
:datetime => ['2000-01-01 09:00', '2000-01-01 09:01']
}
[:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :equal_to => test_values[type][0])
end
it "should report that #{type} is not validated when option value is incorrect" do
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :equal_to => test_values[type][1])
end
it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :equal_to => test_values[type][0])
end
end
end
describe "with before option" do
test_values = {
:date => ['2000-01-10', '2000-01-11'],
:time => ['23:00', '22:59'],
:datetime => ['2000-01-10 23:00', '2000-01-10 22:59']
}
[:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][0])
end
it "should report that #{type} is not validated when option value is incorrect" do
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][1])
end
it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :before => test_values[type][0])
end
end
end
describe "with after option" do
test_values = {
:date => ['2000-01-01', '2000-01-02'],
:time => ['09:00', '09:01'],
:datetime => ['2000-01-01 09:00', '2000-01-01 09:01']
}
[:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][0])
end
it "should report that #{type} is not validated when option value is incorrect" do
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][1])
end
it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :after => test_values[type][0])
end
end
end
describe "with on_or_before option" do
test_values = {
:date => ['2000-01-09', '2000-01-08'],
:time => ['22:00', '21:59'],
:datetime => ['2000-01-09 23:00', '2000-01-09 22:59']
}
[:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][0])
end
it "should report that #{type} is not validated when option value is incorrect" do
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][1])
end
it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_before => test_values[type][0])
end
end
end
describe "with on_or_after option" do
test_values = {
:date => ['2000-01-02', '2000-01-03'],
:time => ['10:00', '10:01'],
:datetime => ['2000-01-02 09:00', '2000-01-02 09:01']
}
[:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][0])
end
it "should report that #{type} is not validated when option value is incorrect" do
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][1])
end
it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :on_or_after => test_values[type][0])
end
end
end
describe "between option" do
test_values = {
:date => [ ['2000-01-01', '2000-01-03'], ['2000-01-01', '2000-01-04'] ],
:time => [ ['09:00', '17:00'], ['09:00', '17:01'] ],
:datetime => [ ['2000-01-01 09:00', '2000-01-01 17:00'], ['2000-01-01 09:00', '2000-01-01 17:01'] ]
}
[:date, :time, :datetime].each do |type|
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][0])
end
it "should report that #{type} is not validated when option value is incorrect" do
with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][1])
end
it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][0])
end
end
end
describe "custom messages" do
before do
@person = CustomMessages.new
end
it "should match error message for invalid" do
@person.should validate_date(:birth_date, :invalid_date_message => 'is not really a date')
end
it "should match error message for before option" do
@person.should validate_date(:birth_date, :before => '2000-01-10',
:invalid_date_message => 'is not really a date',
:before_message => 'is too late')
end
it "should match error message for after option" do
@person.should validate_date(:birth_date, :after => '2000-01-01',
:invalid_date_message => 'is not really a date',
:after_message => 'is too early')
end
it "should match error message for on_or_before option" do
@person.should validate_date(:birth_date, :on_or_before => '2000-01-09',
:invalid_date_message => 'is not really a date',
:on_or_before_message => 'is just too late')
end
it "should match error message for on_or_after option" do
@person.should validate_date(:birth_date, :on_or_after => '2000-01-02',
:invalid_date_message => 'is not really a date',
:on_or_after_message => 'is just too early')
end
end
def attribute_for_type(type)
@@attribute_for_type[type.to_sym]
end
end

View File

@@ -2,10 +2,10 @@ $:.unshift(File.dirname(__FILE__) + '/../lib')
$:.unshift(File.dirname(__FILE__)) $:.unshift(File.dirname(__FILE__))
$:.unshift(File.dirname(__FILE__) + '/resources') $:.unshift(File.dirname(__FILE__) + '/resources')
ENV['RAILS_ENV'] = 'test' RAILS_ENV = ENV['RAILS_ENV'] = 'test'
require 'rubygems' require 'rubygems'
require 'spec' require 'spec/autorun'
vendored_rails = File.dirname(__FILE__) + '/../../../../vendor/rails' vendored_rails = File.dirname(__FILE__) + '/../../../../vendor/rails'
@@ -16,7 +16,11 @@ else
require 'ginger' require 'ginger'
rescue LoadError rescue LoadError
end end
gem 'rails' if ENV['VERSION']
gem 'rails', ENV['VERSION']
else
gem 'rails'
end
end end
RAILS_ROOT = File.dirname(__FILE__) RAILS_ROOT = File.dirname(__FILE__)
@@ -26,26 +30,27 @@ require 'active_record'
require 'active_record/version' require 'active_record/version'
require 'action_controller' require 'action_controller'
require 'action_view' require 'action_view'
require 'action_mailer'
ActiveSupport::Deprecation.silenced = true
require 'spec/rails' require 'spec/rails'
require 'time_travel/time_travel' require 'time_travel/time_travel'
require 'validates_timeliness'
RAILS_VER = Rails::VERSION::STRING
puts "Using #{vendored ? 'vendored' : 'gem'} Rails version #{RAILS_VER} (ActiveRecord version #{ActiveRecord::VERSION::STRING})"
ActiveRecord::Base.default_timezone = :utc ActiveRecord::Base.default_timezone = :utc
RAILS_VER = Rails::VERSION::STRING
puts "Using #{vendored ? 'vendored' : 'gem'} Rails version #{RAILS_VER} (ActiveRecord version #{ActiveRecord::VERSION::STRING})"
if RAILS_VER >= '2.1' if RAILS_VER >= '2.1'
Time.zone_default = ActiveSupport::TimeZone['UTC'] Time.zone_default = ActiveSupport::TimeZone['UTC']
ActiveRecord::Base.time_zone_aware_attributes = true ActiveRecord::Base.time_zone_aware_attributes = true
end end
require 'validates_timeliness'
require 'validates_timeliness/matcher'
ActiveRecord::Migration.verbose = false ActiveRecord::Migration.verbose = false
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'}) ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
require 'sqlite_patch' if RAILS_VER < '2.1'
require 'schema' require 'schema'
require 'person' require 'person'

View File

@@ -1,177 +0,0 @@
require File.dirname(__FILE__) + '/spec_helper'
describe "ValidateTimeliness matcher" do
attr_accessor :no_validation, :with_validation
before do
class NoValidation < Person
alias_attribute :birth_datetime, :birth_date_and_time
end
class WithValidation < Person
validates_date :birth_date,
:before => '2000-01-10', :after => '2000-01-01',
:on_or_before => '2000-01-09', :on_or_after => '2000-01-02'
validates_time :birth_time,
:before => '23:00', :after => '09:00',
:on_or_before => '22:00', :on_or_after => '10:00'
validates_datetime :birth_date_and_time,
:before => '2000-01-10 23:00', :after => '2000-01-01 09:00',
:on_or_before => '2000-01-09 23:00', :on_or_after => '2000-01-02 09:00'
alias_attribute :birth_datetime, :birth_date_and_time
end
@no_validation = NoValidation.new
@with_validation = WithValidation.new
end
[:date, :time, :datetime].each do |type|
attribute = type == :datetime ? :date_and_time : type
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", "birth_#{attribute}".to_sym)
end
it "should report that #{type} is not validated" do
no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}".to_sym)
end
end
describe "with before option" do
test_values = {
:date => ['2000-01-10', '2000-01-11'],
:time => ['23:00', '22:59'],
:datetime => ['2000-01-10 23:00', '2000-01-10 22:59']
}
[:date, :time, :datetime].each do |type|
attribute = type == :datetime ? :date_and_time : type
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", "birth_#{attribute}", :before => test_values[type][0])
end
it "should report that #{type} is not validated when option value is incorrect" do
with_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :before => test_values[type][1])
end
it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :before => test_values[type][0])
end
end
end
describe "with after option" do
test_values = {
:date => ['2000-01-01', '2000-01-02'],
:time => ['09:00', '09:01'],
:datetime => ['2000-01-01 09:00', '2000-01-01 09:01']
}
[:date, :time, :datetime].each do |type|
attribute = type == :datetime ? :date_and_time : type
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", "birth_#{attribute}", :after => test_values[type][0])
end
it "should report that #{type} is not validated when option value is incorrect" do
with_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :after => test_values[type][1])
end
it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :after => test_values[type][0])
end
end
end
describe "with on_or_before option" do
test_values = {
:date => ['2000-01-09', '2000-01-08'],
:time => ['22:00', '21:59'],
:datetime => ['2000-01-09 23:00', '2000-01-09 22:59']
}
[:date, :time, :datetime].each do |type|
attribute = type == :datetime ? :date_and_time : type
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", "birth_#{attribute}", :on_or_before => test_values[type][0])
end
it "should report that #{type} is not validated when option value is incorrect" do
with_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :on_or_before => test_values[type][1])
end
it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :on_or_before => test_values[type][0])
end
end
end
describe "with on_or_after option" do
test_values = {
:date => ['2000-01-02', '2000-01-03'],
:time => ['10:00', '10:01'],
:datetime => ['2000-01-02 09:00', '2000-01-02 09:01']
}
[:date, :time, :datetime].each do |type|
attribute = type == :datetime ? :date_and_time : type
it "should report that #{type} is validated" do
with_validation.should self.send("validate_#{type}", "birth_#{attribute}", :on_or_after => test_values[type][0])
end
it "should report that #{type} is not validated when option value is incorrect" do
with_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :on_or_after => test_values[type][1])
end
it "should report that #{type} is not validated with option" do
no_validation.should_not self.send("validate_#{type}", "birth_#{attribute}", :on_or_after => test_values[type][0])
end
end
end
describe "custom messages" do
before do
class CustomMessages < Person
validates_date :birth_date, :invalid_date_message => 'is not really a date',
:before => '2000-01-10', :before_message => 'is too late',
:after => '2000-01-01', :after_message => 'is too early',
:on_or_before=> '2000-01-09', :on_or_before_message => 'is just too late',
:on_or_after => '2000-01-02', :on_or_after_message => 'is just too early'
end
@person = CustomMessages.new
end
it "should match error message for invalid" do
@person.should validate_date(:birth_date, :invalid_date_message => 'is not really a date')
end
it "should match error message for before option" do
@person.should validate_date(:birth_date, :before => '2000-01-10',
:invalid_date_message => 'is not really a date',
:before_message => 'is too late')
end
it "should match error message for after option" do
@person.should validate_date(:birth_date, :after => '2000-01-01',
:invalid_date_message => 'is not really a date',
:after_message => 'is too early')
end
it "should match error message for on_or_before option" do
@person.should validate_date(:birth_date, :on_or_before => '2000-01-09',
:invalid_date_message => 'is not really a date',
:on_or_before_message => 'is just too late')
end
it "should match error message for on_or_after option" do
@person.should validate_date(:birth_date, :on_or_after => '2000-01-02',
:invalid_date_message => 'is not really a date',
:on_or_after_message => 'is just too early')
end
end
end

View File

@@ -1,507 +0,0 @@
require File.dirname(__FILE__) + '/spec_helper'
describe ValidatesTimeliness::Validations do
attr_accessor :person
before :all do
# freezes time using time_travel plugin
Time.now = Time.utc(2000, 1, 1, 0, 0, 0)
end
after :all do
Time.now = nil
end
describe "parse_date_time" do
it "should return time object for valid time string" do
parse_method("2000-01-01 12:13:14", :datetime).should be_kind_of(Time)
end
it "should return nil for time string with invalid date part" do
parse_method("2000-02-30 12:13:14", :datetime).should be_nil
end
it "should return nil for time string with invalid time part" do
parse_method("2000-02-01 25:13:14", :datetime).should be_nil
end
it "should return Time object when passed a Time object" do
parse_method(Time.now, :datetime).should be_kind_of(Time)
end
if RAILS_VER >= '2.1'
it "should convert time string into current timezone" do
Time.zone = 'Melbourne'
time = parse_method("2000-01-01 12:13:14", :datetime)
Time.zone.utc_offset.should == 10.hours
end
end
it "should return nil for invalid date string" do
parse_method("2000-02-30", :date).should be_nil
end
def parse_method(*args)
ActiveRecord::Base.parse_date_time(*args)
end
end
describe "timeliness_restriction_value" do
it "should return Time object when restriction is Time object" do
restriction_value(Time.now, person, :datetime).should be_kind_of(Time)
end
it "should return Time object when restriction is string" do
restriction_value("2007-01-01 12:00", person, :datetime).should be_kind_of(Time)
end
it "should return Time object when restriction is method symbol which returns Time object" do
person.stub!(:datetime_attr).and_return(Time.now)
restriction_value(:datetime_attr, person, :datetime).should be_kind_of(Time)
end
it "should return Time object when restriction is method symbol which returns string" do
person.stub!(:datetime_attr).and_return("2007-01-01 12:00")
restriction_value(:datetime_attr, person, :datetime).should be_kind_of(Time)
end
it "should return Time object when restriction is proc which returns Time object" do
restriction_value(lambda { Time.now }, person, :datetime).should be_kind_of(Time)
end
it "should return Time object when restriction is proc which returns string" do
restriction_value(lambda {"2007-01-01 12:00"}, person, :datetime).should be_kind_of(Time)
end
def restriction_value(*args)
ActiveRecord::Base.send(:timeliness_restriction_value, *args)
end
end
describe "with no restrictions" do
before :all do
class BasicValidation < Person
validates_datetime :birth_date_and_time, :allow_blank => true
validates_date :birth_date, :allow_blank => true
validates_time :birth_time, :allow_blank => true
end
end
before :each do
@person = BasicValidation.new
end
it "should have error for invalid date component for datetime column" do
person.birth_date_and_time = "2000-01-32 01:02:03"
person.should_not be_valid
person.errors.on(:birth_date_and_time).should == "is not a valid datetime"
end
it "should have error for invalid time component for datetime column" do
person.birth_date_and_time = "2000-01-01 25:02:03"
person.should_not be_valid
person.errors.on(:birth_date_and_time).should == "is not a valid datetime"
end
it "should have error for invalid date value for date column" do
person.birth_date = "2000-01-32"
person.should_not be_valid
person.errors.on(:birth_date).should == "is not a valid date"
end
it "should have error for invalid time value for time column" do
person.birth_time = "25:00"
person.should_not be_valid
person.errors.on(:birth_time).should == "is not a valid time"
end
it "should have same value for before_type_cast after failed validation" do
person.birth_date_and_time = "2000-01-01 25:02:03"
person.should_not be_valid
person.birth_date_and_time_before_type_cast.should == "2000-01-01 25:02:03"
end
it "should be valid with valid values" do
person.birth_date_and_time = "2000-01-01 12:12:12"
person.birth_date = "2000-01-31"
person.should be_valid
end
it "should be valid with value out of range for Time class" do
person.birth_date_and_time = "1890-01-01 12:12:12"
person.should be_valid
end
it "should be valid with nil values when allow_blank is true" do
person.birth_date_and_time = nil
person.birth_date = nil
person.birth_time = nil
person.should be_valid
end
end
describe "for datetime type" do
describe "with before and after restrictions" do
before :all do
class DateTimeBeforeAfter < Person
validates_datetime :birth_date_and_time,
:before => lambda { Time.now }, :after => lambda { 1.day.ago}
end
end
before :each do
@person = DateTimeBeforeAfter.new
end
it "should have error when past :before restriction" do
person.birth_date_and_time = 1.minute.from_now
person.should_not be_valid
person.errors.on(:birth_date_and_time).should match(/must be before/)
end
it "should have error when before :after restriction" do
person.birth_date_and_time = 2.days.ago
person.should_not be_valid
person.errors.on(:birth_date_and_time).should match(/must be after/)
end
it "should have error when on boundary of :before restriction" do
person.birth_date_and_time = Time.now
person.should_not be_valid
person.errors.on(:birth_date_and_time).should match(/must be before/)
end
it "should have error when on boundary of :after restriction" do
person.birth_date_and_time = 1.day.ago
person.should_not be_valid
person.errors.on(:birth_date_and_time).should match(/must be after/)
end
end
describe "with on_or_before and on_or_after restrictions" do
before :all do
class DateTimeOnOrBeforeAndAfter < Person
validates_datetime :birth_date_and_time, :type => :datetime,
:on_or_before => lambda { Time.now.at_midnight },
:on_or_after => lambda { 1.day.ago }
end
end
before do
@person = DateTimeOnOrBeforeAndAfter.new
end
it "should have error when past :on_or_before restriction" do
person.birth_date_and_time = Time.now.at_midnight + 1
person.should_not be_valid
person.errors.on(:birth_date_and_time).should match(/must be on or before/)
end
it "should have error when before :on_or_after restriction" do
person.birth_date_and_time = 1.days.ago - 1
person.should_not be_valid
person.errors.on(:birth_date_and_time).should match(/must be on or after/)
end
it "should be valid when value equal to :on_or_before restriction" do
person.birth_date_and_time = Time.now.at_midnight
person.should be_valid
end
it "should be valid when value equal to :on_or_after restriction" do
person.birth_date_and_time = 1.day.ago
person.should be_valid
end
end
end
describe "for date type" do
describe "with before and after restrictions" do
before :all do
class DateBeforeAfter < Person
validates_date :birth_date,
:before => 1.day.from_now,
:after => 1.day.ago
end
end
before :each do
@person = DateBeforeAfter.new
end
it "should have error when past :before restriction" do
person.birth_date = 2.days.from_now
person.should_not be_valid
person.errors.on(:birth_date).should match(/must be before/)
end
it "should have error when before :after restriction" do
person.birth_date = 2.days.ago
person.should_not be_valid
person.errors.on(:birth_date).should match(/must be after/)
end
end
describe "with on_or_before and on_or_after restrictions" do
before :all do
class DateOnOrBeforeAndAfter < Person
validates_date :birth_date,
:on_or_before => 1.day.from_now,
:on_or_after => 1.day.ago
end
end
before :each do
@person = DateOnOrBeforeAndAfter.new
end
it "should have error when past :on_or_before restriction" do
person.birth_date = 2.days.from_now
person.should_not be_valid
person.errors.on(:birth_date).should match(/must be on or before/)
end
it "should have error when before :on_or_after restriction" do
person.birth_date = 2.days.ago
person.should_not be_valid
person.errors.on(:birth_date).should match(/must be on or after/)
end
it "should be valid when value equal to :on_or_before restriction" do
person.birth_date = 1.day.from_now
person.should be_valid
end
it "should be valid when value equal to :on_or_after restriction" do
person.birth_date = 1.day.ago
person.should be_valid
end
end
end
describe "for time type" do
describe "with before and after restrictions" do
before :all do
class TimeBeforeAfter < Person
validates_time :birth_time,
:before => "23:00",
:after => "06:00"
end
end
before :each do
@person = TimeBeforeAfter.new
end
it "should have error when on boundary of :before restriction" do
person.birth_time = "23:00"
person.should_not be_valid
person.errors.on(:birth_time).should match(/must be before/)
end
it "should have error when on boundary of :after restriction" do
person.birth_time = "06:00am"
person.should_not be_valid
person.errors.on(:birth_time).should match(/must be after/)
end
it "should have error when past :before restriction" do
person.birth_time = "23:01"
person.should_not be_valid
person.errors.on(:birth_time).should match(/must be before/)
end
it "should have error when before :after restriction" do
person.birth_time = "05:59"
person.should_not be_valid
person.errors.on(:birth_time).should match(/must be after/)
end
it "should not have error when before :before restriction" do
person.birth_time = "22:59"
person.should be_valid
end
it "should have error when before :after restriction" do
person.birth_time = "06:01"
person.should be_valid
end
end
describe "with on_or_before and on_or_after restrictions" do
before :all do
class TimeOnOrBeforeAndAfter < Person
validates_time :birth_time,
:on_or_before => "23:00",
:on_or_after => "06:00"
end
end
before :each do
@person = TimeOnOrBeforeAndAfter.new
end
it "should have error when past :on_or_before restriction" do
person.birth_time = "23:01"
person.should_not be_valid
person.errors.on(:birth_time).should match(/must be on or before/)
end
it "should have error when before :on_or_after restriction" do
person.birth_time = "05:59"
person.should_not be_valid
person.errors.on(:birth_time).should match(/must be on or after/)
end
it "should be valid when on boundary of :on_or_before restriction" do
person.birth_time = "23:00"
person.should be_valid
end
it "should be valid when on boundary of :on_or_after restriction" do
person.birth_time = "06:00"
person.should be_valid
end
end
end
describe "with mixed value and restriction types" do
before :all do
class MixedBeforeAndAfter < Person
validates_datetime :birth_date_and_time,
:before => Date.new(2000,1,2),
:after => lambda { "2000-01-01" }
validates_date :birth_date,
:on_or_before => lambda { "2000-01-01" },
:on_or_after => :birth_date_and_time
end
end
before :each do
@person = MixedBeforeAndAfter.new
end
it "should correctly validate time attribute with Date restriction" do
person.birth_date_and_time = "2000-01-03 00:00:00"
person.should_not be_valid
person.errors.on(:birth_date_and_time).should match(/must be before/)
end
it "should correctly validate with proc restriction" do
person.birth_date_and_time = "2000-01-01 00:00:00"
person.should_not be_valid
person.errors.on(:birth_date_and_time).should match(/must be after/)
end
it "should correctly validate date attribute with DateTime restriction" do
person.birth_date = "2000-01-03"
person.birth_date_and_time = "1890-01-01 00:00:00"
person.should_not be_valid
person.errors.on(:birth_date).should match(/must be on or before/)
end
it "should correctly validate date attribute with symbol restriction" do
person.birth_date = "2000-01-01"
person.birth_date_and_time = "2000-01-02 12:00:00"
person.should_not be_valid
person.errors.on(:birth_date).should match(/must be on or after/)
end
end
describe "ignoring restriction errors" do
before :all do
class BadRestriction < Person
validates_date :birth_date, :before => Proc.new { raise }
self.ignore_datetime_restriction_errors = true
end
end
before :each do
@person = BadRestriction.new
end
it "should have no errors when restriction is invalid" do
person.birth_date = '2000-01-01'
person.should be_valid
end
end
describe "restriction value error message" do
describe "default formats" do
before :all do
class DefaultFormats < Person
validates_datetime :birth_date_and_time, :allow_blank => true, :after => 1.day.from_now
validates_date :birth_date, :allow_blank => true, :after => 1.day.from_now
validates_time :birth_time, :allow_blank => true, :after => '23:59:59'
end
end
before :each do
@person = DefaultFormats.new
end
it "should format datetime value of restriction" do
person.birth_date_and_time = Time.now
person.save
person.errors.on(:birth_date_and_time).should match(/after \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\Z/)
end
it "should format date value of restriction" do
person.birth_date = Time.now
person.save
person.errors.on(:birth_date).should match(/after \d{4}-\d{2}-\d{2}\Z/)
end
it "should format time value of restriction" do
person.birth_time = '12:00:00'
person.save
person.errors.on(:birth_time).should match(/after \d{2}:\d{2}:\d{2}\Z/)
end
end
describe "custom formats" do
before :all do
class CustomFormats < Person
validates_datetime :birth_date_and_time, :allow_blank => true, :after => 1.day.from_now
validates_date :birth_date, :allow_blank => true, :after => 1.day.from_now
validates_time :birth_time, :allow_blank => true, :after => '23:59:59'
end
ActiveRecord::Errors.date_time_error_value_formats = {
:time => '%H:%M %p',
:date => '%d-%m-%Y',
:datetime => '%d-%m-%Y %H:%M %p'
}
end
before :each do
@person = CustomFormats.new
end
it "should format datetime value of restriction" do
person.birth_date_and_time = Time.now
person.save
person.errors.on(:birth_date_and_time).should match(/after \d{2}-\d{2}-\d{4} \d{2}:\d{2} (AM|PM)\Z/)
end
it "should format date value of restriction" do
person.birth_date = Time.now
person.save
person.errors.on(:birth_date).should match(/after \d{2}-\d{2}-\d{4}\Z/)
end
it "should format time value of restriction" do
person.birth_time = '12:00:00'
person.save
person.errors.on(:birth_time).should match(/after \d{2}:\d{2} (AM|PM)\Z/)
end
end
end
end

650
spec/validator_spec.rb Normal file
View File

@@ -0,0 +1,650 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
describe ValidatesTimeliness::Validator do
attr_accessor :person, :validator
before :all do
# freezes time using time_travel plugin
Time.now = Time.utc(2000, 1, 1, 0, 0, 0)
end
after :all do
Time.now = nil
end
before :each do
@person = Person.new
end
describe "option keys validation" do
before do
keys = ValidatesTimeliness::Validator::VALID_OPTIONS - [:invalid_date_message, :invalid_time_message, :with_date, :with_time]
@valid_options = keys.inject({}) {|hash, opt| hash[opt] = nil; hash }
end
it "should raise error if invalid option key passed" do
@valid_options.update(:invalid_key => 'will not open lock')
lambda { Person.validates_datetime(@valid_options) }.should raise_error(ArgumentError)
end
it "should not raise error if option keys are valid" do
lambda { Person.validates_datetime(@valid_options) }.should_not raise_error(ArgumentError)
end
end
describe "evaluate_option_value" do
it "should return Time object when restriction is Time object" do
evaluate_option_value(Time.now, :datetime).should be_kind_of(Time)
end
it "should return Time object when restriction is string" do
evaluate_option_value("2007-01-01 12:00", :datetime).should be_kind_of(Time)
end
it "should return Time object when restriction is method and method returns Time object" do
person.stub!(:datetime_attr).and_return(Time.now)
evaluate_option_value(:datetime_attr, :datetime).should be_kind_of(Time)
end
it "should return Time object when restriction is method and method returns string" do
person.stub!(:datetime_attr).and_return("2007-01-01 12:00")
evaluate_option_value(:datetime_attr, :datetime).should be_kind_of(Time)
end
it "should return Time object when restriction is proc which returns Time object" do
evaluate_option_value(lambda { Time.now }, :datetime).should be_kind_of(Time)
end
it "should return Time object when restriction is proc which returns string" do
evaluate_option_value(lambda {"2007-01-01 12:00"}, :datetime).should be_kind_of(Time)
end
it "should return array of Time objects when restriction is array of Time objects" do
time1, time2 = Time.now, 1.day.ago
evaluate_option_value([time1, time2], :datetime).should == [time2, time1]
end
it "should return array of Time objects when restriction is array of strings" do
time1, time2 = "2000-01-02", "2000-01-01"
evaluate_option_value([time1, time2], :datetime).should == [parse(time2, :datetime), parse(time1, :datetime)]
end
it "should return array of Time objects when restriction is Range of Time objects" do
time1, time2 = Time.now, 1.day.ago
evaluate_option_value(time1..time2, :datetime).should == [time2, time1]
end
it "should return array of Time objects when restriction is Range of time strings" do
time1, time2 = "2000-01-02", "2000-01-01"
evaluate_option_value(time1..time2, :datetime).should == [parse(time2, :datetime), parse(time1, :datetime)]
end
def evaluate_option_value(restriction, type)
configure_validator(:type => type)
validator.class.send(:evaluate_option_value, restriction, type, person)
end
end
describe "instance with defaults" do
describe "for datetime type" do
before do
configure_validator(:type => :datetime)
end
it "should have invalid error when date component is invalid" do
validate_with(:birth_date_and_time, "2000-01-32 01:02:03")
should_have_error(:birth_date_and_time, :invalid_datetime)
end
it "should have invalid error when time component is invalid" do
validate_with(:birth_date_and_time, "2000-01-01 25:02:03")
should_have_error(:birth_date_and_time, :invalid_datetime)
end
it "should have blank error when value is nil" do
validate_with(:birth_date_and_time, nil)
should_have_error(:birth_date_and_time, :blank)
end
it "should have no errors when value is valid" do
validate_with(:birth_date_and_time, "2000-01-01 12:00:00")
should_have_no_error(:birth_date_and_time, :invalid_datetime)
end
end
describe "for date type" do
before do
configure_validator(:type => :date)
end
it "should have invalid error when value is invalid" do
validate_with(:birth_date, "2000-01-32")
should_have_error(:birth_date, :invalid_date)
end
it "should have blank error when value is nil" do
validate_with(:birth_date, nil)
should_have_error(:birth_date, :blank)
end
it "should have no error when value is valid" do
validate_with(:birth_date, "2000-01-31")
should_have_no_error(:birth_date, :invalid_date)
end
end
describe "for time type" do
before do
configure_validator(:type => :time)
end
it "should have invalid error when value is invalid" do
validate_with(:birth_time, "25:00")
should_have_error(:birth_time, :invalid_time)
end
it "should have blank error when value is nil" do
validate_with(:birth_time, nil)
should_have_error(:birth_time, :blank)
end
it "should have no errors when value is valid" do
validate_with(:birth_date_and_time, "12:00")
should_have_no_error(:birth_time, :invalid_time)
end
end
end
describe "instance with before and after restrictions" do
describe "for datetime type" do
before :each do
configure_validator(:before => lambda { Time.now }, :after => lambda { 1.day.ago})
end
it "should have before error when value is past :before restriction" do
validate_with(:birth_date_and_time, 1.minute.from_now)
should_have_error(:birth_date_and_time, :before)
end
it "should have before error when value is on boundary of :before restriction" do
validate_with(:birth_date_and_time, Time.now)
should_have_error(:birth_date_and_time, :before)
end
it "should have after error when value is before :after restriction" do
validate_with(:birth_date_and_time, 2.days.ago)
should_have_error(:birth_date_and_time, :after)
end
it "should have after error when value is on boundary of :after restriction" do
validate_with(:birth_date_and_time, 1.day.ago)
should_have_error(:birth_date_and_time, :after)
end
end
describe "for date type" do
before :each do
configure_validator(:before => 1.day.from_now, :after => 1.day.ago, :type => :date)
end
it "should have error when value is past :before restriction" do
validate_with(:birth_date, 2.days.from_now)
should_have_error(:birth_date, :before)
end
it "should have error when value is before :after restriction" do
validate_with(:birth_date, 2.days.ago)
should_have_error(:birth_date, :after)
end
it "should have no error when value is before :before restriction" do
validate_with(:birth_date, Time.now)
should_have_no_error(:birth_date, :before)
end
it "should have no error when value is after :after restriction" do
validate_with(:birth_date, Time.now)
should_have_no_error(:birth_date, :after)
end
end
describe "for time type" do
before :each do
configure_validator(:before => "23:00", :after => "06:00", :type => :time)
end
it "should have error when value is on boundary of :before restriction" do
validate_with(:birth_time, "23:00")
should_have_error(:birth_time, :before)
end
it "should have error when value is on boundary of :after restriction" do
validate_with(:birth_time, "06:00")
should_have_error(:birth_time, :after)
end
it "should have error when value is past :before restriction" do
validate_with(:birth_time, "23:01")
should_have_error(:birth_time, :before)
end
it "should have error when value is before :after restriction" do
validate_with(:birth_time, "05:59")
should_have_error(:birth_time, :after)
end
it "should not have error when value is before :before restriction" do
validate_with(:birth_time, "22:59")
should_have_no_error(:birth_time, :before)
end
it "should have error when value is before :after restriction" do
validate_with(:birth_time, "06:01")
should_have_no_error(:birth_time, :before)
end
end
end
describe "instance with between restriction" do
describe "for datetime type" do
before do
configure_validator(:between => [1.day.ago.at_midnight, 1.day.from_now.at_midnight])
end
it "should have error when value is before earlist :between restriction" do
validate_with(:birth_date_and_time, 2.days.ago)
should_have_error(:birth_date_and_time, :between)
end
it "should have error when value is after latest :between restriction" do
validate_with(:birth_date_and_time, 2.days.from_now)
should_have_error(:birth_date_and_time, :between)
end
it "should be valid when value is equal to earliest :between restriction" do
validate_with(:birth_date_and_time, 1.day.ago.at_midnight)
should_have_no_error(:birth_date_and_time, :between)
end
it "should be valid when value is equal to latest :between restriction" do
validate_with(:birth_date_and_time, 1.day.from_now.at_midnight)
should_have_no_error(:birth_date_and_time, :between)
end
it "should allow a range for between restriction" do
configure_validator(:type => :datetime, :between => (1.day.ago.at_midnight)..(1.day.from_now.at_midnight))
validate_with(:birth_date_and_time, 1.day.from_now.at_midnight)
should_have_no_error(:birth_date_and_time, :between)
end
end
describe "for date type" do
before do
configure_validator(:type => :date, :between => [1.day.ago.to_date, 1.day.from_now.to_date])
end
it "should have error when value is before earlist :between restriction" do
validate_with(:birth_date, 2.days.ago.to_date)
should_have_error(:birth_date, :between)
end
it "should have error when value is after latest :between restriction" do
validate_with(:birth_date, 2.days.from_now.to_date)
should_have_error(:birth_date, :between)
end
it "should be valid when value is equal to earliest :between restriction" do
validate_with(:birth_date, 1.day.ago.to_date)
should_have_no_error(:birth_date, :between)
end
it "should be valid when value is equal to latest :between restriction" do
validate_with(:birth_date, 1.day.from_now.to_date)
should_have_no_error(:birth_date, :between)
end
it "should allow a range for between restriction" do
configure_validator(:type => :date, :between => (1.day.ago.to_date)..(1.day.from_now.to_date))
validate_with(:birth_date, 1.day.from_now.to_date)
should_have_no_error(:birth_date, :between)
end
end
describe "for time type" do
before do
configure_validator(:type => :time, :between => ["09:00", "17:00"])
end
it "should have error when value is before earlist :between restriction" do
validate_with(:birth_time, "08:59")
should_have_error(:birth_time, :between)
end
it "should have error when value is after latest :between restriction" do
validate_with(:birth_time, "17:01")
should_have_error(:birth_time, :between)
end
it "should be valid when value is equal to earliest :between restriction" do
validate_with(:birth_time, "09:00")
should_have_no_error(:birth_time, :between)
end
it "should be valid when value is equal to latest :between restriction" do
validate_with(:birth_time, "17:00")
should_have_no_error(:birth_time, :between)
end
it "should allow a range for between restriction" do
configure_validator(:type => :time, :between => "09:00".."17:00")
validate_with(:birth_time, "17:00")
should_have_no_error(:birth_time, :between)
end
end
end
describe "instance with :equal_to restriction" do
describe "for datetime type" do
before do
configure_validator(:equal_to => Time.now)
end
it "should have error when value not equal to :equal_to restriction" do
validate_with(:birth_date_and_time, Time.now + 1)
should_have_error(:birth_date_and_time, :equal_to)
end
it "should be valid when value is equal to :equal_to restriction" do
validate_with(:birth_date_and_time, Time.now)
should_have_no_error(:birth_date_and_time, :equal_to)
end
end
describe "for date type" do
before do
configure_validator(:type => :date, :equal_to => Date.today)
end
it "should have error when value is not equal to :equal_to restriction" do
validate_with(:birth_date, Date.today + 1)
should_have_error(:birth_date, :equal_to)
end
it "should be valid when value is equal to :equal_to restriction" do
validate_with(:birth_date, Date.today)
should_have_no_error(:birth_date, :equal_to)
end
end
describe "for time type" do
before do
configure_validator(:type => :time, :equal_to => "09:00:00")
end
it "should have error when value is not equal to :equal_to restriction" do
validate_with(:birth_time, "09:00:01")
should_have_error(:birth_time, :equal_to)
end
it "should be valid when value is equal to :equal_to restriction" do
validate_with(:birth_time, "09:00:00")
should_have_no_error(:birth_time, :equal_to)
end
end
end
describe "instance with :ignore_usec option" do
it "should ignore usec on time values when evaluated" do
configure_validator(:equal_to => Time.utc(2000, 1, 1, 0, 0, 0, 0), :ignore_usec => true)
validate_with(:birth_date_and_time, Time.utc(2000, 1, 1, 0, 0, 0, 500))
should_have_no_error(:birth_date_and_time, :equal_to)
end
end
describe "instance with :with_time option" do
it "should validate date attribute as datetime combining value of :with_time against restrictions " do
configure_validator(:type => :date, :with_time => '12:31', :on_or_before => Time.mktime(2000,1,1,12,30))
validate_with(:birth_date, "2000-01-01")
should_have_error(:birth_date, :on_or_before)
end
it "should skip restriction validation if :with_time value is nil" do
configure_validator(:type => :date, :with_time => nil, :on_or_before => Time.mktime(2000,1,1,12,30))
validate_with(:birth_date, "2000-01-01")
should_have_no_error(:birth_date, :on_or_before)
end
it "should should ignore usec value on combined value if :ignore_usec option is true" do
configure_validator(:type => :date, :with_time => Time.mktime(2000,1,1,12,30,0,500), :equal_to => Time.mktime(2000,1,1,12,30), :ignore_usec => true)
validate_with(:birth_date, "2000-01-01")
should_have_no_error(:birth_date, :equal_to)
end
end
describe "instance with :with_date option" do
it "should validate time attribute as datetime combining value of :with_date against restrictions " do
configure_validator(:type => :time, :with_date => '2009-01-01', :on_or_before => Time.mktime(2000,1,1,12,30))
validate_with(:birth_time, "12:30")
should_have_error(:birth_time, :on_or_before)
end
it "should skip restriction validation if :with_date value is nil" do
configure_validator(:type => :time, :with_date => nil, :on_or_before => Time.mktime(2000,1,1,12,30))
validate_with(:birth_time, "12:30")
should_have_no_error(:birth_time, :on_or_before)
end
it "should should ignore usec value on combined value if :ignore_usec option is true" do
configure_validator(:type => :time, :with_date => Date.new(2000,1,1), :on_or_before => Time.mktime(2000,1,1,12,30), :ignore_usec => true)
validate_with(:birth_time, Time.mktime(2000,1,1,12,30,0,50))
should_have_no_error(:birth_time, :on_or_before)
end
end
describe "instance with mixed value and restriction types" do
it "should validate datetime attribute with Date restriction" do
configure_validator(:type => :datetime, :on_or_before => Date.new(2000,1,1))
validate_with(:birth_date_and_time, "2000-01-01 00:00:00")
should_have_no_error(:birth_date_and_time, :on_or_before)
end
it "should validate date attribute with DateTime restriction value" do
configure_validator(:type => :date, :on_or_before => DateTime.new(2000, 1, 1, 0,0,0))
validate_with(:birth_date, "2000-01-01")
should_have_no_error(:birth_date, :on_or_before)
end
it "should validate date attribute with Time restriction value" do
configure_validator(:type => :date, :on_or_before => Time.utc(2000, 1, 1, 0,0,0))
validate_with(:birth_date, "2000-01-01")
should_have_no_error(:birth_date, :on_or_before)
end
it "should validate time attribute with DateTime restriction value" do
configure_validator(:type => :time, :on_or_before => DateTime.new(2000, 1, 1, 12,0,0))
validate_with(:birth_time, "12:00")
should_have_no_error(:birth_time, :on_or_before)
end
it "should validate time attribute with Time restriction value" do
configure_validator(:type => :time, :on_or_before => Time.utc(2000, 1, 1, 12,0,0))
validate_with(:birth_time, "12:00")
should_have_no_error(:birth_time, :on_or_before)
end
end
describe "instance with format option" do
it "should validate attribute when value matches format" do
configure_validator(:type => :time, :format => 'hh:nn:ss')
validate_with(:birth_time, "12:00:00")
should_have_no_error(:birth_time, :invalid_time)
end
it "should not validate attribute when value does not match format" do
configure_validator(:type => :time, :format => 'hh:nn:ss')
validate_with(:birth_time, "12:00")
should_have_error(:birth_time, :invalid_time)
end
end
describe "custom_error_messages" do
it "should return hash of custom error messages from configuration with _message truncated from keys" do
configure_validator(:type => :date, :invalid_date_message => 'thats no date')
validator.send(:custom_error_messages)[:invalid_date].should == 'thats no date'
end
it "should return empty hash if no custom error messages in configuration" do
configure_validator(:type => :date)
validator.send(:custom_error_messages).should be_empty
end
end
describe "interpolation_values" do
if defined?(I18n)
it "should return hash of interpolation keys with restriction values" do
before = '1900-01-01'
configure_validator(:type => :date, :before => before)
validator.send(:interpolation_values, :before, before.to_date).should == {:restriction => before}
end
else
it "should return array of interpolation values" do
before = '1900-01-01'
configure_validator(:type => :date, :before => before)
validator.send(:interpolation_values, :before, before.to_date).should == [before]
end
end
end
describe "restriction errors" do
before :each do
configure_validator(:type => :date, :before => lambda { raise })
end
it "should be added by default for invalid restriction" do
ValidatesTimeliness::Validator.ignore_restriction_errors = false
validate_with(:birth_date, Date.today)
person.errors.on(:birth_date).should match(/restriction 'before' value was invalid/)
end
it "should not be added when ignore switch is true and restriction is invalid" do
ValidatesTimeliness::Validator.ignore_restriction_errors = true
person.should be_valid
end
after :all do
ValidatesTimeliness::Validator.ignore_restriction_errors = false
end
end
describe "restriction value error message" do
describe "default formats" do
it "should format datetime value of restriction" do
configure_validator(:type => :datetime, :after => 1.day.from_now)
validate_with(:birth_date_and_time, Time.now)
person.errors.on(:birth_date_and_time).should match(/after \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\Z/)
end
it "should format date value of restriction" do
configure_validator(:type => :date, :after => 1.day.from_now)
validate_with(:birth_date, Time.now)
person.errors.on(:birth_date).should match(/after \d{4}-\d{2}-\d{2}\Z/)
end
it "should format time value of restriction" do
configure_validator(:type => :time, :after => '12:00')
validate_with(:birth_time, '11:59')
person.errors.on(:birth_time).should match(/after \d{2}:\d{2}:\d{2}\Z/)
end
end
describe "custom formats" do
before :all do
custom = {
:time => '%H:%M %p',
:date => '%d-%m-%Y',
:datetime => '%d-%m-%Y %H:%M %p'
}
if defined?(I18n)
I18n.backend.store_translations 'en', :validates_timeliness => { :error_value_formats => custom }
else
@@formats = ValidatesTimeliness::Validator.error_value_formats
ValidatesTimeliness::Validator.error_value_formats = custom
end
end
it "should format datetime value of restriction" do
configure_validator(:type => :datetime, :after => 1.day.from_now)
validate_with(:birth_date_and_time, Time.now)
person.errors.on(:birth_date_and_time).should match(/after \d{2}-\d{2}-\d{4} \d{2}:\d{2} (AM|PM)\Z/)
end
it "should format date value of restriction" do
configure_validator(:type => :date, :after => 1.day.from_now)
validate_with(:birth_date, Time.now)
person.errors.on(:birth_date).should match(/after \d{2}-\d{2}-\d{4}\Z/)
end
it "should format time value of restriction" do
configure_validator(:type => :time, :after => '12:00')
validate_with(:birth_time, '11:59')
person.errors.on(:birth_time).should match(/after \d{2}:\d{2} (AM|PM)\Z/)
end
after :all do
if defined?(I18n)
I18n.reload!
else
ValidatesTimeliness::Validator.error_value_formats = @@formats
end
end
end
end
def parse(*args)
ValidatesTimeliness::Parser.parse(*args)
end
def configure_validator(options={})
@validator = ValidatesTimeliness::Validator.new(options)
end
def validate_with(attr_name, value)
person.send("#{attr_name}=", value)
validator.call(person, attr_name, value)
end
def should_have_error(attr_name, error)
message = error_messages[error]
person.errors.on(attr_name).should match(/#{message}/)
end
def should_have_no_error(attr_name, error)
message = error_messages[error]
errors = person.errors.on(attr_name)
if errors
errors.should_not match(/#{message}/)
else
errors.should be_nil
end
end
def error_messages
return @error_messages if defined?(@error_messages)
messages = validator.send(:error_messages)
@error_messages = messages.inject({}) {|h, (k, v)| h[k] = v.sub(/ (\%s|\{\{\w*\}\}).*/, ''); h }
end
end

View File

@@ -0,0 +1,30 @@
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
s.name = %q{validates_timeliness}
s.version = "2.2.1"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Adam Meehan"]
s.autorequire = %q{validates_timeliness}
s.date = %q{2009-09-12}
s.description = %q{Date and time validation plugin for Rails 2.x which allows custom formats}
s.email = %q{adam.meehan@gmail.com}
s.extra_rdoc_files = ["README.rdoc", "LICENSE", "TODO", "CHANGELOG"]
s.files = ["LICENSE", "README.rdoc", "Rakefile", "TODO", "CHANGELOG", "lib/validates_timeliness", "lib/validates_timeliness/active_record", "lib/validates_timeliness/active_record/multiparameter_attributes.rb", "lib/validates_timeliness/active_record/attribute_methods.rb", "lib/validates_timeliness/parser.rb", "lib/validates_timeliness/version.rb", "lib/validates_timeliness/validator.rb", "lib/validates_timeliness/validation_methods.rb", "lib/validates_timeliness/locale", "lib/validates_timeliness/locale/en.yml", "lib/validates_timeliness/spec", "lib/validates_timeliness/spec/rails", "lib/validates_timeliness/spec/rails/matchers", "lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb", "lib/validates_timeliness/matcher.rb", "lib/validates_timeliness/action_view", "lib/validates_timeliness/action_view/instance_tag.rb", "lib/validates_timeliness/formats.rb", "lib/validates_timeliness.rb", "spec/active_record", "spec/active_record/multiparameter_attributes_spec.rb", "spec/active_record/attribute_methods_spec.rb", "spec/formats_spec.rb", "spec/parser_spec.rb", "spec/spec_helper.rb", "spec/ginger_scenarios.rb", "spec/time_travel", "spec/time_travel/time_extensions.rb", "spec/time_travel/time_travel.rb", "spec/time_travel/MIT-LICENSE", "spec/spec", "spec/spec/rails", "spec/spec/rails/matchers", "spec/spec/rails/matchers/validate_timeliness_spec.rb", "spec/validator_spec.rb", "spec/action_view", "spec/action_view/instance_tag_spec.rb", "spec/resources", "spec/resources/schema.rb", "spec/resources/application.rb", "spec/resources/person.rb", "spec/resources/sqlite_patch.rb"]
s.homepage = %q{http://github.com/adzap/validates_timeliness}
s.require_paths = ["lib"]
s.rubyforge_project = %q{validatestime}
s.rubygems_version = %q{1.3.3}
s.summary = %q{Date and time validation plugin for Rails 2.x which allows custom formats}
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
else
end
else
end
end