time_of_day 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ pkg/*
18
+ *.gem
19
+ .bundle
20
+ Gemfile.lock
21
+
22
+ ## PROJECT::SPECIFIC
23
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+
3
+ # Specify your gem's dependencies in time_of_day.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Lailson Bandeira
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,117 @@
1
+ = time_of_day
2
+
3
+ Have you already worked with a database Time column and an Active Record model together?
4
+ If you had, you know: it sucks. Well, not anymore.
5
+
6
+ == Setup
7
+
8
+ In the +Gemfile+ add:
9
+
10
+ gem 'time_of_day', :git => 'git://github.com/lailsonbm/time_of_day'
11
+
12
+ Run +bundler+:
13
+
14
+ bundle install
15
+
16
+ Just that.
17
+
18
+ == The problem
19
+
20
+ Sometimes we need to work with time-only objects.
21
+ Suppose, for example, you're working on a train table web application and want to register
22
+ trips between stations. Every day, the train departures on a defined time and, some minutes
23
+ later, it arrives at the next station.
24
+
25
+ So lets dive into Rails and do it. You'll end up with a migration and a model like this:
26
+
27
+ # Migration
28
+ class CreateTrips < ActiveRecord::Migration
29
+ def self.up
30
+ create_table :trips do |t|
31
+ t.string :train, :leaving_from, :arriving_at
32
+ t.time :departure, :arrival
33
+ end
34
+ end
35
+
36
+ def self.down
37
+ drop_table :trips
38
+ end
39
+ end
40
+
41
+ # Dumb AR model
42
+ class Trip < ActiveRecord::Base
43
+ end
44
+
45
+ Cool. Just after a <code>rake db:migrate</code>, we can create trips and tell if the train will be
46
+ on any of them at a given time:
47
+
48
+ trip = Trip.new
49
+ trip.departure = '10:00'
50
+ trip.arrival = '10:10'
51
+
52
+ # Will the train be on this trip at 10:05?
53
+ time = Time.parse('10:05')
54
+ (trip.departure..trip.arrival).cover?(time) # OR
55
+ (trip.departure <= time && trip.arrival >= time) # if you are old fashioned =P
56
+
57
+ You might be surprised to know: this code does not work. At least not as we expected: both
58
+ statements return false. The problem is caused by the way Active Record handles time-only attributes.
59
+ SQL has the +time+ data type, that represents just a time of day, but Ruby doesn't
60
+ have such thing. So, Active Record represent time attributes using the +Time+ class on
61
+ the canonical (and arbitrary) date <code>2000-01-01</code>. This is okay when you are comparing only attributes
62
+ since their date will be equal and just the time will be compared. However, when you compare an
63
+ attribute with another time instance, you start to need silly workarounds. In this case, the code does not work
64
+ because <code>Time#parse</code> yields the current date (e.g. <code>2010-06-25 10:05</code>) and of course it is not between
65
+ <code>2000-01-01 10:00</code> and <code>2000-01-01 10:10</code>. To accomplish what you were trying to do, you have two alternatives:
66
+
67
+ # Always use the date 01/01/2000 for any time you want to compare (notice the Z for UTC time zone)
68
+ time = Time.parse('2000-01-01 10:05Z')
69
+ (trip.departure..trip.arrival).cover?(time) # => true
70
+
71
+ # OR compare always considering the seconds since midnight
72
+ seconds = time.seconds_since_midnight
73
+ (trip.departure.seconds_since_midnight <= seconds && trip.arrival.seconds_since_midnight >= seconds) # => true
74
+
75
+ Sorry, but both options suck.
76
+
77
+ == How time_of_day handles the problem
78
+
79
+ There are many ways to fix this issue. Subclassing +Time+ would be certainly the purest OO
80
+ solution, but I found that monkey patching it is much more practical. So, it's what +time_of_day+
81
+ does. It adds some methods to +Time+ for representing times of day, instead of time with dates.
82
+
83
+ # Suppose today is June 25, 2010
84
+ time1 = Time.parse('10:00') # => 2010-06-25 10:00:00
85
+ time2 = Time.parse('2010-06-01 10:00') # => 2010-06-01 10:00:00
86
+ time1 == time2 # => false
87
+
88
+ # Ignoring the dates
89
+ time1.time_of_day!
90
+ time2.time_of_day!
91
+ time1 == time2 # => true
92
+
93
+ So, now you can ignore the date information and compare just time. The most exciting thing,
94
+ though, is that Active Record time attributes are now always mapped to times of day:
95
+
96
+ trip = Trip.new
97
+ trip.departure = '10:00'
98
+ trip.arrival = '10:10'
99
+
100
+ trip.departure.time_of_day? # => true
101
+ trip.arrival.time_of_day? # => true
102
+
103
+ At this time, the code works effortlessly:
104
+
105
+ time = Time.parse('10:05').time_of_day!
106
+ (trip.departure..trip.arrival).cover?(time) # => true
107
+ (trip.departure <= time && trip.arrival >= time) # => true
108
+
109
+ And you go home earlier.
110
+
111
+ == Compatibility
112
+
113
+ Tested with Rails 3.0.0 on Ruby 1.9.2-p0 and Ruby 1.8.7.
114
+
115
+ == Copyright
116
+
117
+ Copyright (c) 2010 Lailson Bandeira (http://lailsonbandeira.com). See LICENSE for details.
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+ Rake::TestTask.new(:test) do |test|
7
+ test.libs << 'lib' << 'test'
8
+ test.pattern = 'test/**/test_*.rb'
9
+ test.verbose = true
10
+ end
11
+
12
+ task :default => :test
@@ -0,0 +1,5 @@
1
+ require 'time_of_day/ext/time'
2
+ require 'time_of_day/ext/active_record'
3
+
4
+ module TimeOfDay
5
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_support/core_ext/module/aliasing'
2
+ require 'active_record/connection_adapters/abstract/schema_definitions'
3
+
4
+ module ActiveRecord
5
+ module ConnectionAdapters
6
+ class Column
7
+ class << self
8
+ def string_to_dummy_time_with_time_of_day(string)
9
+ t = string_to_dummy_time_without_time_of_day(string)
10
+ t.respond_to?(:time_of_day) ? t.time_of_day : t
11
+ end
12
+ alias_method_chain :string_to_dummy_time, :time_of_day
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,49 @@
1
+ require 'active_support/core_ext/module/aliasing'
2
+ require 'active_support/core_ext/time/conversions'
3
+
4
+ class Time
5
+ DATE_FORMATS[:time_utc] = '%H:%M:%S'
6
+
7
+ def time_of_day?
8
+ @time_of_day ||= false
9
+ end
10
+
11
+ def time_of_day
12
+ self.dup.time_of_day!
13
+ end
14
+
15
+ def time_of_day!
16
+ @time_of_day = true
17
+ self
18
+ end
19
+
20
+ def not_time_of_day
21
+ self.dup.not_time_of_day!
22
+ end
23
+
24
+ def not_time_of_day!
25
+ @time_of_day = false
26
+ self
27
+ end
28
+
29
+ def compare_with_time_of_day(time)
30
+ if self.time_of_day? && time.time_of_day?
31
+ self.seconds_since_midnight <=> time.seconds_since_midnight
32
+ else
33
+ self.compare_without_time_of_day(time)
34
+ end
35
+ rescue
36
+ self.compare_without_time_of_day(time)
37
+ end
38
+ alias_method :compare_without_time_of_day, :<=>
39
+ alias_method :<=>, :compare_with_time_of_day
40
+
41
+ def to_s_with_time_of_day(format=nil)
42
+ unless format
43
+ to_s_without_time_of_day(:time_utc)
44
+ else
45
+ to_s_without_time_of_day(format)
46
+ end
47
+ end
48
+ alias_method_chain :to_s, :time_of_day
49
+ end
@@ -0,0 +1,3 @@
1
+ module TimeOfDay
2
+ VERSION = '0.1.1'
3
+ end
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'time_of_day'
8
+
9
+ require 'active_support/all'
10
+
11
+ class Test::Unit::TestCase
12
+ end
13
+
14
+ # ACTIVE RECORD STUFF
15
+ require 'active_record'
16
+ class User < ActiveRecord::Base; end
17
+ def load_schema
18
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
19
+
20
+ # Avoids printing create table statements
21
+ silence_stream(STDOUT) do |variable|
22
+ ActiveRecord::Schema.define(:version => 1) do
23
+ create_table :users do |t|
24
+ t.string :name
25
+ t.date :birth_date
26
+ t.time :arrival_time
27
+ t.datetime :join_datetime
28
+ t.timestamp :last_operation_timestamp
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+
35
+
@@ -0,0 +1,32 @@
1
+ require 'helper'
2
+
3
+ class TestActiveRecordExtensions < Test::Unit::TestCase
4
+ context "An attribute of type time from an active record model" do
5
+ load_schema
6
+
7
+ setup do
8
+ @user = User.new
9
+ end
10
+
11
+ should "be parsed as a time of day object" do
12
+ @user.arrival_time = '10:00'
13
+ assert_kind_of Time, @user.arrival_time
14
+ end
15
+
16
+ should "be converted to time of day object" do
17
+ time = Time.parse('10:00')
18
+ @user.arrival_time = time
19
+ assert !time.time_of_day?
20
+ assert @user.arrival_time.time_of_day?
21
+ end
22
+
23
+ should "be loaded from database as time of day" do
24
+ User.destroy_all
25
+ @user.arrival_time = '10:00'
26
+ @user.save!
27
+
28
+ assert User.first.arrival_time.time_of_day?
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,78 @@
1
+ require 'helper'
2
+
3
+ class TestTimeExtensions < Test::Unit::TestCase
4
+ context "A Time object" do
5
+ setup do
6
+ @time = Time.parse('08:00')
7
+ end
8
+
9
+ should "have time of day methods" do
10
+ assert_respond_to @time, :time_of_day?
11
+ assert_respond_to @time, :time_of_day
12
+ assert_respond_to @time, :time_of_day!
13
+ assert_respond_to @time, :not_time_of_day
14
+ assert_respond_to @time, :not_time_of_day!
15
+ end
16
+
17
+ should "be not a time of day by default" do
18
+ assert !@time.time_of_day?
19
+ end
20
+
21
+ should "be convertible to time of day" do
22
+ @time.time_of_day!
23
+ assert @time.time_of_day?
24
+ end
25
+
26
+ should "be capable of generate a time of day version of itself" do
27
+ time_of_day = @time.time_of_day
28
+
29
+ assert !@time.time_of_day?
30
+ assert time_of_day.time_of_day?
31
+ end
32
+
33
+ context "that represents a time of day" do
34
+ setup do
35
+ @time.time_of_day!
36
+ end
37
+
38
+ should "be convertible to an ordinary time object" do
39
+ @time.not_time_of_day!
40
+ assert !@time.time_of_day?
41
+ end
42
+
43
+ should "be capable of generate an ordinary time version of itself" do
44
+ ordinary_time = @time.not_time_of_day
45
+ assert @time.time_of_day?
46
+ assert !ordinary_time.time_of_day?
47
+ end
48
+
49
+ should "compare to other time of day objects using only time information" do
50
+ time1 = Time.parse('2000-01-01 08:00').time_of_day!
51
+ time2 = Time.parse('2000-01-10 08:00').time_of_day!
52
+ time3 = Time.parse('2010-06-15 10:00').time_of_day!
53
+ assert_equal 0, (time1 <=> time2)
54
+ assert_equal 1, (time3 <=> time1)
55
+ assert_equal -1, (time1 <=> time3)
56
+ end
57
+
58
+ should "compare to any other time object using Time's comparison" do
59
+ time1 = Time.parse('2005-01-01 08:00').time_of_day!
60
+ time2 = Time.parse('2000-01-10 10:00')
61
+ time3 = Time.parse('2010-06-15 06:00')
62
+ assert_equal 1, (time1 <=> time2)
63
+ assert_equal -1, (time1 <=> time3)
64
+ end
65
+
66
+ should "compare to any other object using Time's comparision" do
67
+ assert_nothing_raised do
68
+ assert_nil (@time <=> 'string')
69
+ assert_nil (@time <=> :symbol)
70
+ end
71
+ end
72
+
73
+ should "convert to string using UTC time notation when to_s is called without arguments" do
74
+ assert_equal '08:00:00', @time.to_s
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/time_of_day/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'time_of_day'
6
+ s.version = TimeOfDay::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = %q{Lailson Bandeira}
9
+ s.email = %q{lailson@guava.com.br}
10
+ s.homepage = 'http://rubygems.org/gems/time_of_day'
11
+ s.summary = 'Handle correctly times without dates in Rails 3.'
12
+ s.description = 'Adds time-only capabilities to the Time class and maps the Rails time type correctly to a time without date.'
13
+
14
+ s.required_rubygems_version = '>= 1.3.6'
15
+ s.rubyforge_project = 'time_of_day'
16
+
17
+ s.add_development_dependency 'bundler', '>= 1.0.0'
18
+ s.add_development_dependency 'shoulda', '>= 2.11.3'
19
+ s.add_runtime_dependency('activesupport', '>= 3.0.0')
20
+ s.add_runtime_dependency('activerecord', '>= 3.0.0')
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
24
+ s.require_path = 'lib'
25
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: time_of_day
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 1
9
+ version: 0.1.1
10
+ platform: ruby
11
+ authors:
12
+ - Lailson Bandeira
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-26 00:00:00 -03:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bundler
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 0
31
+ - 0
32
+ version: 1.0.0
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: shoulda
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 2
45
+ - 11
46
+ - 3
47
+ version: 2.11.3
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: activesupport
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 3
60
+ - 0
61
+ - 0
62
+ version: 3.0.0
63
+ type: :runtime
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: activerecord
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 3
75
+ - 0
76
+ - 0
77
+ version: 3.0.0
78
+ type: :runtime
79
+ version_requirements: *id004
80
+ description: Adds time-only capabilities to the Time class and maps the Rails time type correctly to a time without date.
81
+ email: lailson@guava.com.br
82
+ executables: []
83
+
84
+ extensions: []
85
+
86
+ extra_rdoc_files: []
87
+
88
+ files:
89
+ - .document
90
+ - .gitignore
91
+ - Gemfile
92
+ - LICENSE
93
+ - README.rdoc
94
+ - Rakefile
95
+ - lib/time_of_day.rb
96
+ - lib/time_of_day/ext/active_record.rb
97
+ - lib/time_of_day/ext/time.rb
98
+ - lib/time_of_day/version.rb
99
+ - test/helper.rb
100
+ - test/test_active_record_extensions.rb
101
+ - test/test_time_extensions.rb
102
+ - time_of_day.gemspec
103
+ has_rdoc: true
104
+ homepage: http://rubygems.org/gems/time_of_day
105
+ licenses: []
106
+
107
+ post_install_message:
108
+ rdoc_options: []
109
+
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ segments:
126
+ - 1
127
+ - 3
128
+ - 6
129
+ version: 1.3.6
130
+ requirements: []
131
+
132
+ rubyforge_project: time_of_day
133
+ rubygems_version: 1.3.7
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: Handle correctly times without dates in Rails 3.
137
+ test_files: []
138
+