timely 0.0.1 → 0.0.2

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.
Files changed (50) hide show
  1. data/Gemfile +13 -0
  2. data/HISTORY.md +3 -0
  3. data/LICENSE +21 -0
  4. data/README.md +55 -0
  5. data/Rakefile +150 -4
  6. data/lib/timely/date.rb +3 -3
  7. data/lib/timely/date_chooser.rb +98 -0
  8. data/lib/timely/date_range.rb +51 -0
  9. data/lib/timely/date_time.rb +11 -0
  10. data/lib/timely/rails/date_group.rb +115 -0
  11. data/lib/timely/rails/extensions.rb +43 -0
  12. data/lib/timely/rails/season.rb +99 -0
  13. data/lib/timely/rails.rb +4 -0
  14. data/lib/timely/range.rb +15 -0
  15. data/lib/timely/string.rb +25 -0
  16. data/lib/timely/temporal_patterns.rb +441 -0
  17. data/lib/timely/time.rb +5 -4
  18. data/lib/timely/trackable_date_set.rb +148 -0
  19. data/lib/timely/week_days.rb +128 -0
  20. data/lib/timely.rb +15 -6
  21. data/rails/init.rb +1 -0
  22. data/spec/date_chooser_spec.rb +101 -0
  23. data/spec/date_group_spec.rb +26 -0
  24. data/spec/date_range_spec.rb +40 -0
  25. data/spec/date_spec.rb +15 -15
  26. data/spec/schema.rb +11 -0
  27. data/spec/season_spec.rb +68 -0
  28. data/spec/spec_helper.rb +41 -18
  29. data/spec/string_spec.rb +13 -0
  30. data/spec/time_spec.rb +28 -9
  31. data/spec/trackable_date_set_spec.rb +80 -0
  32. data/spec/week_days_spec.rb +51 -0
  33. data/timely.gemspec +99 -0
  34. metadata +61 -61
  35. data/History.txt +0 -4
  36. data/License.txt +0 -20
  37. data/Manifest.txt +0 -23
  38. data/README.txt +0 -31
  39. data/config/hoe.rb +0 -73
  40. data/config/requirements.rb +0 -15
  41. data/lib/timely/version.rb +0 -9
  42. data/script/console +0 -10
  43. data/script/destroy +0 -14
  44. data/script/generate +0 -14
  45. data/setup.rb +0 -1585
  46. data/spec/spec.opts +0 -1
  47. data/tasks/deployment.rake +0 -34
  48. data/tasks/environment.rake +0 -7
  49. data/tasks/rspec.rake +0 -21
  50. data/tasks/website.rake +0 -9
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ group :development, :test do
5
+ gem 'rake', '~> 0.9.2'
6
+ gem 'rdoc', '~> 3.12'
7
+ gem 'rspec'
8
+ gem 'simplecov-rcov'
9
+ gem 'simplecov'
10
+ gem 'sqlite3'
11
+ gem 'activesupport', '~> 2.3.0'
12
+ gem 'activerecord', '~> 2.3.0'
13
+ end
data/HISTORY.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.0.1 / 2008-05-08
2
+
3
+ * Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) Tom Preston-Werner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ Timely
2
+ ======
3
+
4
+ # DESCRIPTION
5
+
6
+ Various helpers to work with times, dates and weekdays, etc.
7
+
8
+ It includes the following (see end for full descriptions)
9
+ * Core extensions to Date and Time
10
+ * DateChooser, a class to help select a subset of dates within any range, e.g. All 2nd Sundays, Every 15th of the month, All Tuesdays and Wednesdays
11
+ * WeekDays, a class to manage the selection of weekdays, outputs a integer representing which days as a number between 0 and 127 (e.g. a 7 bit integer)
12
+ * DateRange: A subclass of Range for dates with various helpers and aliases
13
+ * TrackableDateSet: Recording set of dates processed/processing
14
+ * TemporalPatterns: Various other classes related to time, e.g. Frequency
15
+
16
+ It includes the following rails extensions (only loaded if inside rails project):
17
+ * Date Group, a date range which can also be limited to WeekDays, e.g. all weekends between March and April
18
+ * Season, a collection of Date Groups
19
+ * weekdays_field, a way to declare an integer field to store weekdays (weekdays is stored as 7 bit integer)
20
+ * acts_as_seasonal, a way to declare a season_id foreign key as well as some helper methods
21
+
22
+ # INSTALLATION
23
+
24
+ gem install timely
25
+
26
+ or add to your Gemfile:
27
+ gem 'timely'
28
+
29
+ # SYNOPSIS
30
+
31
+ require 'timely'
32
+
33
+ For examples on most usage see the tests in the spec directory.
34
+ As these contain many basic examples with expected output.
35
+
36
+ ## Core Extensions
37
+
38
+ ```ruby
39
+ some_date = Date.today - 5 # => 2008-05-03
40
+ some_date.at_time(3, 5, 13) # => Sat May 03 03:05:13 -0500 2008
41
+
42
+ # arguments are optional
43
+ some_date.at_time(13) # => Sat May 03 13:00:00 -0500 2008
44
+
45
+ some_time = Time.now - 345678 # => Sun May 04 13:40:22 -0500 2008
46
+ some_time.on_date(2001, 6, 18) # => Mon Jun 18 13:40:22 -0500 2001
47
+
48
+ # if you have objects corresponding to the times/dates you want
49
+ some_time.on_date(some_date) # => Sat May 03 13:40:22 -0500 2008
50
+ some_date.at_time(some_time) # => Sat May 03 13:40:22 -0500 2008
51
+
52
+ # if you like typing less
53
+ some_time.on(some_date) # => Sat May 03 13:40:22 -0500 2008
54
+ some_date.at(some_time) # => Sat May 03 13:40:22 -0500 2008
55
+ ```
data/Rakefile CHANGED
@@ -1,4 +1,150 @@
1
- require 'config/requirements'
2
- require 'config/hoe' # setup Hoe + all gem configuration
3
-
4
- Dir['tasks/**/*.rake'].each { |rake| load rake }
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ desc 'Default: run specs.'
47
+ task :default => :spec
48
+
49
+ require 'rspec/core/rake_task'
50
+
51
+ desc "Run specs"
52
+ RSpec::Core::RakeTask.new do |t|
53
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
54
+ # Put spec opts in a file named .rspec in root
55
+ end
56
+
57
+ desc "Generate SimpleCov test coverage and open in your browser"
58
+ task :coverage do
59
+ ENV['COVERAGE'] = 'true'
60
+ Rake::Task['spec'].invoke
61
+ end
62
+
63
+ require 'rdoc/task'
64
+ RDoc::Task.new do |rdoc|
65
+ rdoc.rdoc_dir = 'rdoc'
66
+ rdoc.title = "#{name} #{version}"
67
+ rdoc.rdoc_files.include('README*')
68
+ rdoc.rdoc_files.include('lib/**/*.rb')
69
+ end
70
+
71
+ desc "Open an irb session preloaded with this library"
72
+ task :console do
73
+ sh "irb -rubygems -r ./lib/#{name}.rb"
74
+ end
75
+
76
+ #############################################################################
77
+ #
78
+ # Custom tasks (add your own tasks here)
79
+ #
80
+ #############################################################################
81
+
82
+
83
+
84
+ #############################################################################
85
+ #
86
+ # Packaging tasks
87
+ #
88
+ #############################################################################
89
+
90
+ desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
91
+ task :release => :build do
92
+ unless `git branch` =~ /^\* master$/
93
+ puts "You must be on the master branch to release!"
94
+ exit!
95
+ end
96
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
97
+ sh "git tag v#{version}"
98
+ sh "git push origin master"
99
+ sh "git push origin v#{version}"
100
+ sh "gem push pkg/#{name}-#{version}.gem"
101
+ end
102
+
103
+ desc "Build #{gem_file} into the pkg directory"
104
+ task :build => :gemspec do
105
+ sh "mkdir -p pkg"
106
+ sh "gem build #{gemspec_file}"
107
+ sh "mv #{gem_file} pkg"
108
+ end
109
+
110
+ desc "Generate #{gemspec_file}"
111
+ task :gemspec => :validate do
112
+ # read spec file and split out manifest section
113
+ spec = File.read(gemspec_file)
114
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
115
+
116
+ # replace name version and date
117
+ replace_header(head, :name)
118
+ replace_header(head, :version)
119
+ replace_header(head, :date)
120
+ #comment this out if your rubyforge_project has a different name
121
+ replace_header(head, :rubyforge_project)
122
+
123
+ # determine file list from git ls-files
124
+ files = `git ls-files`.
125
+ split("\n").
126
+ sort.
127
+ reject { |file| file =~ /^\./ }.
128
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
129
+ map { |file| " #{file}" }.
130
+ join("\n")
131
+
132
+ # piece file back together and write
133
+ manifest = " s.files = %w[\n#{files}\n ]\n"
134
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
135
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
136
+ puts "Updated #{gemspec_file}"
137
+ end
138
+
139
+ desc "Validate #{gemspec_file}"
140
+ task :validate do
141
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
142
+ unless libfiles.empty?
143
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
144
+ exit!
145
+ end
146
+ unless Dir['VERSION*'].empty?
147
+ puts "A `VERSION` file at root level violates Gem best practices."
148
+ exit!
149
+ end
150
+ end
data/lib/timely/date.rb CHANGED
@@ -5,14 +5,14 @@ module Timely
5
5
  time = hour
6
6
  hour, minute, second = time.hour, time.min, time.sec
7
7
  end
8
-
8
+
9
9
  ::Time.local(year, month, day, hour, minute, second)
10
10
  end
11
-
11
+
12
12
  alias_method :at, :at_time
13
13
  end
14
14
  end
15
15
 
16
16
  class Date
17
17
  include Timely::Date
18
- end
18
+ end
@@ -0,0 +1,98 @@
1
+ module Timely
2
+ class DateChooser
3
+ # Where is this used... so far only in one place, _date_range.html.haml
4
+ # May be good to refactor this as well, after the class behaviour is refactored.
5
+ INTERVALS = [
6
+ {:code => 'w', :name => 'week(s)', :description =>
7
+ 'Weekdays selected will be chosen every {{n}} weeks for the date range'},
8
+ {:code => 'wom', :name => 'week of month', :description =>
9
+ 'Weekdays selected will be chosen in their {{ord}} occurance every month,
10
+ e.g. if wednesday and thursday are selected, the first wednesday and
11
+ first thursday are selected. Note: this may mean the booking is copied
12
+ to Thursday 1st and Wednesday 7th'}
13
+ ]
14
+
15
+ attr_accessor :multiple_dates, :from, :to, :select, :dates, :interval, :weekdays
16
+
17
+ def initialize(options)
18
+ @multiple_dates = options[:multiple_dates] || false
19
+ @from = process_date(options[:from])
20
+ @to = process_date(options[:to])
21
+ @select = options[:select]
22
+ @dates = options[:dates]
23
+ @interval = options[:interval]
24
+ @weekdays = WeekDays.new(options[:weekdays]) if @select == 'weekdays'
25
+ validate
26
+ end
27
+
28
+ def process_date(date)
29
+ case date
30
+ when Date; date
31
+ when NilClass; nil
32
+ when String
33
+ date !~ /[^[:space:]]/ ? nil : date.to_date
34
+ end
35
+ end
36
+
37
+ # Chooses a set of dates from a date range, based on conditions.
38
+ # date_info - A hash with conditions and date information
39
+ # :from - The start of the date range
40
+ # :to - The end of the date range
41
+ #
42
+ # You can either specify specific dates to be chosen each month:
43
+ # :dates - A comma separated string of days of the month, e.g. 1,16
44
+ #
45
+ # or you can specify how to select the dates
46
+ # :day - A hash of days, the index being the wday, e.g. 0 = sunday, and the value being 1 if chosen
47
+ # :interval - A hash of information about the interval
48
+ # :level - The level/multiplier of the interval unit
49
+ # :unit - The unit of the interval, e.g. w for week, mow for month of week
50
+ # e.g. :level => 2, :unit => w would try to select the days of the week every fortnight,
51
+ # so every friday and saturday each fornight
52
+ def choose_dates
53
+ # Not multiple dates - just return the From date.
54
+ return [@from] if !@multiple_dates
55
+
56
+ # Multiple dates - return the array, adjusted as per input
57
+ all_days = (@from..@to).to_a
58
+
59
+ case @select
60
+ when 'days'
61
+ days = @dates.gsub(/\s/, '').split(',')
62
+ all_days.select { |date| days.include?(date.mday.to_s) }
63
+ when 'weekdays'
64
+ raise DateChooserException, "No days of the week selected" if @weekdays.weekdays.empty?
65
+ raise DateChooserException, "No weekly interval selected" if @interval && @interval.empty?
66
+
67
+ all_days.select do |date|
68
+ if @weekdays.has_day?(date.wday)
69
+ case @interval[:unit]
70
+ when 'w'
71
+ # 0 = first week, 1 = second week, 2 = third week, etc.
72
+ nth_week = (date - @from).to_i / 7
73
+ # true every 2nd week (0, 2, 4, 6, etc.)
74
+ (nth_week % @interval[:level].to_i).zero?
75
+ when 'wom'
76
+ week = @interval[:level].to_i
77
+ (date.mday > (week-1)*7 && date.mday <= week*7)
78
+ end
79
+ end
80
+ end
81
+ else
82
+ all_days
83
+ end
84
+ end
85
+
86
+ private
87
+ def validate
88
+ if !@from
89
+ raise DateChooserException, "A Start Date is required"
90
+ elsif @multiple_dates
91
+ @to ||= @from
92
+ raise DateChooserException, "Start Date is after End Date" if @from > @to
93
+ end
94
+ end
95
+ end
96
+
97
+ class DateChooserException < Exception; end
98
+ end
@@ -0,0 +1,51 @@
1
+ module Timely
2
+ class DateRange < ::Range
3
+ def initialize(*args)
4
+ if args.first.is_a?(Range)
5
+ super(args.first.first, args.first.last)
6
+ elsif args.size == 1 && args.first.is_a?(Date)
7
+ super(args.first, args.first)
8
+ else
9
+ super(*args)
10
+ end
11
+ end
12
+ alias_method :start_date, :first
13
+ alias_method :end_date, :last
14
+
15
+ def self.from_params(start_date, duration = nil)
16
+ start_date = start_date.to_date
17
+ duration = [1, duration.to_i].max
18
+
19
+ new(start_date..(start_date + duration - 1))
20
+ end
21
+
22
+ def intersecting_dates(date_range)
23
+ start_of_intersection = [self.start_date, date_range.first].max
24
+ end_of_intersection = [self.end_date, date_range.last].min
25
+ intersection = if end_of_intersection >= start_of_intersection
26
+ (start_of_intersection..end_of_intersection)
27
+ else
28
+ []
29
+ end
30
+ end
31
+
32
+ def number_of_nights
33
+ ((last - first) + 1).to_i
34
+ end
35
+ alias_method :duration, :number_of_nights
36
+
37
+ def to_s(fmt = '%b %Y')
38
+ if first == last
39
+ first.to_s(:short)
40
+ elsif first == first.at_beginning_of_month && last == last.at_end_of_month
41
+ if first.month == last.month
42
+ first.strftime(fmt)
43
+ else
44
+ "#{first.strftime(fmt)} to #{last.strftime(fmt)}"
45
+ end
46
+ else
47
+ "#{first.to_s(:short)} to #{last.to_s(:short)}"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,11 @@
1
+ module Timely
2
+ module DateTime
3
+ def on_date(date)
4
+ self.change(:year => date.year, :month => date.month, :day => date.day)
5
+ end
6
+ end
7
+ end
8
+
9
+ class DateTime
10
+ include Timely::DateTime
11
+ end
@@ -0,0 +1,115 @@
1
+ module Timely
2
+ class DateGroup < ActiveRecord::Base
3
+
4
+ # acts_as_audited
5
+
6
+ belongs_to :season
7
+
8
+ weekdays_field :weekdays
9
+
10
+ validates_presence_of :start_date, :end_date
11
+ validate :validate_date_range!
12
+
13
+ def includes_date?(date)
14
+ date >= start_date && date <= end_date && weekdays.applies_for_date?(date)
15
+ end
16
+
17
+
18
+ def applicable_for_duration?(date_range)
19
+ if date_range.first > end_date || date_range.last < start_date
20
+ false
21
+ elsif weekdays.all_days?
22
+ true
23
+ else
24
+ date_range.intersecting_dates(start_date..end_date).any?{|d| weekdays.applies_for_date?(d)}
25
+ end
26
+ end
27
+
28
+
29
+ def dates
30
+ start_date.upto(end_date).select { |d| weekdays.applies_for_date?(d) }
31
+ end
32
+
33
+
34
+ def to_s
35
+ str = start_date && end_date ? (start_date..end_date).to_date_range.to_s : (start_date || end_date).to_s
36
+
37
+ unless weekdays.all_days?
38
+ str += " on #{weekdays}"
39
+ end
40
+ str
41
+ end
42
+
43
+ alias_method :audit_name, :to_s
44
+
45
+
46
+
47
+ ################################################################
48
+ #---------------- Date intervals and patterns -----------------#
49
+ ################################################################
50
+
51
+
52
+ def pattern
53
+ ranges = dates.group_by(&:wday).values.map { |weekdates| (weekdates.min..weekdates.max) }
54
+ TemporalPatterns::Pattern.new(ranges, 1.week)
55
+ end
56
+
57
+
58
+ def self.from_patterns(patterns)
59
+ date_groups = []
60
+ Array.wrap(patterns).each do |pattern|
61
+ if pattern.frequency.unit == :weeks
62
+ weekdays = pattern.intervals.map { |i| i.first_datetime.wday }.inject({}) do |hash, wday|
63
+ hash[wday] = 1
64
+ hash
65
+ end
66
+ date_groups << DateGroup.new(
67
+ :start_date => pattern.first_datetime.to_date,
68
+ :end_date => pattern.last_datetime.to_date,
69
+ :weekdays => weekdays)
70
+ elsif pattern.frequency.unit == :days && pattern.frequency.duration == 1.day
71
+ date_groups << DateGroup.new(
72
+ :start_date => pattern.first_datetime.to_date,
73
+ :end_date => pattern.last_datetime.to_date,
74
+ :weekdays => 127)
75
+ else
76
+ pattern.datetimes.each do |datetimes|
77
+ datetimes.group_by(&:week).values.each do |dates|
78
+ weekdays = dates.map(&:wday).inject({}) do |hash, wday|
79
+ hash[wday] = 1
80
+ hash
81
+ end
82
+ date_groups << DateGroup.new(
83
+ :start_date => dates.min.to_date.beginning_of_week,
84
+ :end_date => dates.max.to_date.end_of_week,
85
+ :weekdays => weekdays)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ date_groups
91
+ end
92
+
93
+
94
+ private
95
+
96
+ def validate_date_range!
97
+ raise InvalidInputException, "Incorrect date range" if start_date && end_date && (start_date > end_date)
98
+ end
99
+
100
+ end
101
+ end
102
+
103
+ # == Schema Information
104
+ #
105
+ # Table name: date_groups
106
+ #
107
+ # id :integer(4) not null, primary key
108
+ # season_id :integer(4)
109
+ # start_date :date
110
+ # end_date :date
111
+ # created_at :datetime
112
+ # updated_at :datetime
113
+ # weekdays_bit_array :integer(4)
114
+ #
115
+
@@ -0,0 +1,43 @@
1
+ module Timely
2
+ module Extensions
3
+ # Add a WeekDays attribute
4
+ #
5
+ # By default it will use attribute_bit_array as db field, but this can
6
+ # be overridden by specifying :db_field => 'somthing_else'
7
+ def weekdays_field(attribute, options={})
8
+ db_field = options[:db_field] || attribute.to_s + '_bit_array'
9
+ self.composed_of(attribute,
10
+ :class_name => "::Timely::WeekDays",
11
+ :mapping => [[db_field, 'weekdays_int']],
12
+ :converter => Proc.new {|field| ::Timely::WeekDays.new(field)}
13
+ )
14
+ end
15
+
16
+ def acts_as_seasonal
17
+ belongs_to :season
18
+ accepts_nested_attributes_for :season
19
+ validates_associated :season
20
+
21
+ named_scope :season_on, lambda { |*args|
22
+ date = args.first || ::Date.current # Can't assign in block in Ruby 1.8
23
+ {
24
+ :joins => {:season => :date_groups},
25
+ :conditions => ["date_groups.start_date <= ? AND date_groups.end_date >= ?", date, date]
26
+ }
27
+ }
28
+
29
+ named_scope :available_from, lambda { |*args|
30
+ date = args.first || ::Date.current # Can't assign in block in Ruby 1.8
31
+ {:conditions => ["boundary_end >= ?", date]}
32
+ }
33
+
34
+ before_save do |object|
35
+ if object.season
36
+ object.boundary_start = object.season.boundary_start
37
+ object.boundary_end = object.season.boundary_end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,99 @@
1
+ module Timely
2
+ class Season < ActiveRecord::Base
3
+ # acts_as_audited
4
+
5
+ has_many :date_groups, :order => :start_date, :dependent => :destroy
6
+
7
+ # has_many :fare_bases
8
+
9
+ accepts_nested_attributes_for :date_groups,
10
+ :reject_if => proc {|attributes| attributes['start_date'].blank?},
11
+ :allow_destroy => true
12
+
13
+
14
+ def validate
15
+ errors.add_to_base("No dates specified") if date_groups.blank?
16
+ errors.empty?
17
+ end
18
+
19
+
20
+ def includes_date?(date)
21
+ date_groups.any?{|dg| dg.includes_date?(date)}
22
+ end
23
+
24
+
25
+ def has_gaps?
26
+ last_date = nil
27
+ date_groups.each do |dg|
28
+ return true if last_date && dg.start_date != last_date + 1
29
+ end
30
+ false
31
+ end
32
+
33
+ def dates
34
+ date_groups.map do |date_group|
35
+ ((date_group.start_date)..(date_group.end_date)).to_a
36
+ end.flatten
37
+ end
38
+
39
+ def boundary_range
40
+ boundary_start..boundary_end
41
+ end
42
+
43
+
44
+ def boundary_start
45
+ date_groups.map(&:start_date).sort.first
46
+ end
47
+
48
+
49
+ def boundary_end
50
+ date_groups.map(&:end_date).sort.last
51
+ end
52
+
53
+
54
+ def within_boundary?(date)
55
+ boundary_start && boundary_end && boundary_start <= date && boundary_end >= date
56
+ end
57
+
58
+
59
+ def deep_clone
60
+ cloned = self.clone
61
+ date_groups.each do |dg|
62
+ cloned.date_groups.build(dg.clone.attributes.except(:id))
63
+ end
64
+ cloned
65
+ end
66
+
67
+
68
+ def self.build_season_for(dates=[])
69
+ season = Season.new
70
+ date_groups = dates.map do |date|
71
+ DateGroup.new(:start_date => date, :end_date => date)
72
+ end
73
+ season.date_groups = date_groups
74
+ season
75
+ end
76
+
77
+ def to_s
78
+ name
79
+ end
80
+ alias_method :audit_name, :to_s
81
+
82
+ def string_of_date_groups
83
+ date_groups.map{|dg| "#{dg.start_date.to_s(:short)} - #{dg.end_date.to_s(:short)}"}.to_sentence
84
+ end
85
+ end
86
+ end
87
+
88
+
89
+
90
+ # == Schema Information
91
+ #
92
+ # Table name: seasons
93
+ #
94
+ # id :integer(4) not null, primary key
95
+ # name :string(255)
96
+ # created_at :datetime
97
+ # updated_at :datetime
98
+ #
99
+
@@ -0,0 +1,4 @@
1
+ require 'timely/rails/extensions'
2
+ ActiveRecord::Base.extend Timely::Extensions
3
+ require 'timely/rails/season'
4
+ require 'timely/rails/date_group'