timely 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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'