reservation 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.
data/.loco ADDED
@@ -0,0 +1,3 @@
1
+ # -*- mode: ruby -*-
2
+
3
+ Loco::Counter::EXCLUDE << /^spec\/dummy/
@@ -1,4 +1,4 @@
1
- Copyright 2013 YOURNAME
1
+ Copyright 2013 Conan Dalton
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -0,0 +1,56 @@
1
+ # Reservation
2
+
3
+ A gem for managing reservations. Provides the notion of "Event", which is basically an interval in time, and a
4
+ "Reservation", which is a many-to-many association model between Event and your objects.
5
+
6
+ There is no privileged event "owner", all associations are considered equivalent.
7
+
8
+ This gem uses ActiveRecord to store events and reservations
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'reservation'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install reservation
23
+
24
+ ## Usage
25
+
26
+ Objects associated with an event are called "subjects"; each event has many subjects, via an association
27
+ model called Reservation::Reservation. The simplest way to create a bunch of events is to use Event#create_weekly.
28
+ This will create a set of events, with the given subjects, within the given constraints, repeating weekly.
29
+
30
+ subjects = [ { "role" => "owner", "subject" => matt, "status" => "confirmed" },
31
+ { "role" => "helpr", "subject" => bill, "status" => "tentative" },
32
+ { "role" => "place", "subject" => here, "status" => "tentative" }
33
+ ]
34
+
35
+ pattern = [ { "day" => "wed", "start" => "0930", "finish" => "1030"},
36
+ { "day" => "wed", "start" => "18", "finish" => "20" },
37
+ { "day" => "tue", "start" => "7", "finish" => "830" } ]
38
+
39
+ Reservation::Event.create_weekly "the_title", "2013-09-03", "2013-10-13", subjects, pattern
40
+
41
+ You can use Reservation::Event#build_weekly instead in order to instantiate the object graph without
42
+ persisting it.
43
+
44
+
45
+ ## Contributing
46
+
47
+ 1. Fork it
48
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
49
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
50
+ 4. Push to the branch (`git push origin my-new-feature`)
51
+ 5. Create new Pull Request
52
+
53
+
54
+ ## license
55
+
56
+ MIT License
@@ -1,6 +1,7 @@
1
- module Reservation
2
- end
3
1
 
2
+ require "reservation/schedule"
3
+ require "reservation/scopes"
4
+ require "reservation/event_filter"
4
5
  require "reservation/event"
5
6
  require "reservation/reservation"
6
7
 
@@ -1,4 +1,9 @@
1
+ require "reservation/time_offset"
2
+
1
3
  class Reservation::Event < ActiveRecord::Base
4
+ extend Reservation::TimeOffset
5
+ extend Reservation::EventFilter
6
+
2
7
  has_many :reservations, :class_name => "Reservation::Reservation"
3
8
  attr_accessible :finish, :start, :title
4
9
 
@@ -6,10 +11,61 @@ class Reservation::Event < ActiveRecord::Base
6
11
  scope :upto, lambda { |time| where("reservation_events.start < ?", time) }
7
12
  scope :reserved_for, lambda { |who|
8
13
  klass = who.class.base_class.name
9
- joins("join reservation_reservations rrx on reservation_events.id = rrx.event_id").where("rrx.subject_type = ? and rrx.subject_id = ?", klass, who.id)
14
+ join_name = "rrx_#{Time.now.to_i}#{rand(1000)}"
15
+ joins("join reservation_reservations #{join_name} on reservation_events.id = #{join_name}.event_id").where("#{join_name}.subject_type = ? and #{join_name}.subject_id = ?", klass, who.id)
10
16
  }
11
17
 
12
18
  def overlap? range_start, range_end
13
19
  (range_end > start) && (range_start < finish)
14
20
  end
21
+
22
+ def matches? weekday_spec
23
+ return true if weekday_spec.nil?
24
+ return false if self.start.day != self.finish.day
25
+ spec = weekday_spec[DAY_MAP[self.start.wday]]
26
+ my_start = { :hour => self.start.hour, :min => self.start.min }
27
+ my_finish = { :hour => self.finish.hour, :min => self.finish.min }
28
+ spec.include? [my_start, my_finish]
29
+ end
30
+
31
+ def self.build_weekly title, from, upto, subjects, pattern
32
+ schedule = Reservation::Schedule::Weekly.new pattern
33
+ from = Date.parse from
34
+ max = Date.parse upto
35
+ events = schedule.generate from, max
36
+ events.each { |e|
37
+ e.title = title
38
+ subjects.each { |subject_data|
39
+ role = subject_data["role"]
40
+ status = subject_data["status"]
41
+ subject = subject_data["subject"]
42
+ e.reservations.build :role => role, :reservation_status => status, :subject => subject
43
+ }
44
+ yield e if block_given?
45
+ }
46
+ events
47
+ end
48
+
49
+ def self.create_weekly title, from, upto, subjects, pattern
50
+ build_weekly(title, from, upto, subjects, pattern) { |e| e.save! }
51
+ end
52
+
53
+ def self.remove_subject subject, filter_options
54
+ filter_events(filter_options).each { |event|
55
+ event.reservations.each { |r|
56
+ if r.subject == subject
57
+ r.destroy
58
+ end
59
+ }
60
+ }
61
+ end
62
+
63
+ def self.add_subject subject_data, filter_options
64
+ filter_events(filter_options).each { |event|
65
+ role = subject_data["role"]
66
+ status = subject_data["status"]
67
+ subject = subject_data["subject"]
68
+ event.reservations.create! :role => role, :reservation_status => status, :subject => subject
69
+ }
70
+ end
15
71
  end
@@ -0,0 +1,47 @@
1
+ module Reservation
2
+ module EventFilter
3
+ def parse_time_for_upto txt
4
+ d = Date.parse(txt) rescue nil
5
+ t = Time.parse(txt) rescue nil
6
+ raise "can't read date/time #{txt.inspect}" if d.nil? && t.nil?
7
+ if d && (d.to_time == t)
8
+ return (d + 1.day).to_time
9
+ end
10
+ t
11
+ end
12
+
13
+ def filter_events options
14
+ from = options["from"]
15
+ upto = options["upto"]
16
+ context = options["context"]
17
+ schedule = options["schedule"]
18
+
19
+ events = ::Reservation.events
20
+
21
+ if from
22
+ from = from.is_a?(String) ? Date.parse(from) : from.to_date
23
+ events = events.since(from)
24
+ end
25
+
26
+ if upto
27
+ upto = upto.is_a?(String) ? parse_time_for_upto(upto) : upto.to_time if upto
28
+ events = events.upto(upto) if upto
29
+ end
30
+
31
+ if context
32
+ context = [context] unless context.is_a? Array
33
+ context = context.uniq
34
+ events = context.inject(events) { |ee, ctx|
35
+ ee.reserved_for(ctx)
36
+ }
37
+ end
38
+
39
+ if schedule
40
+ schedule = ::Reservation::Schedule::Weekly.new schedule
41
+ events = schedule.filter events
42
+ end
43
+
44
+ events
45
+ end
46
+ end
47
+ end
@@ -1,5 +1,7 @@
1
- class Reservation::Reservation < ActiveRecord::Base
2
- belongs_to :event, :class_name => "Reservation::Event"
3
- belongs_to :subject, :polymorphic => true
4
- attr_accessible :reservation_status, :role, :subject_id, :subject_type, :event_id, :event, :subject
1
+ module Reservation
2
+ class Reservation < ActiveRecord::Base
3
+ belongs_to :event, :class_name => "Reservation::Event"
4
+ belongs_to :subject, :polymorphic => true
5
+ attr_accessible :reservation_status, :role, :subject_id, :subject_type, :event_id, :event, :subject
6
+ end
5
7
  end
@@ -0,0 +1,191 @@
1
+ module Reservation
2
+ DAY_MAP = { "sun" => 0, "mon" => 1, "tue" => 2, "wed" => 3, "thu" => 4, "fri" => 5, "sat" => 6 }
3
+ MAP_DAY = %w{ sun mon tue wed thu fri sat }
4
+
5
+ module Schedule
6
+ #
7
+ # a utility class to match the hour and minute elements of a Time instance, without considering any other values
8
+ #
9
+ class HourMinute
10
+ attr_accessor :hour, :minute
11
+ def initialize hour, minute
12
+ @hour, @minute = hour, minute
13
+ end
14
+
15
+ def matches_time? time
16
+ time.hour == self.hour && time.min == self.minute
17
+ end
18
+
19
+ #
20
+ # hhmm is a string containg an hour-and-minute value
21
+ #
22
+ # #parse will remove all nondigit characters, pad the result to 4 digits,
23
+ # and interpret the first two as an hour value, the last two as a minute value
24
+ #
25
+ # padding takes place as follows :
26
+ # * one digit becomes 0d00 (assumes "7" means "0700")
27
+ # * two digits become dd00 (assumes "11" means "1100")
28
+ # * three digits become 0ddd (assumes "830" means "0830")
29
+ #
30
+ def self.parse hhmm
31
+ orig = hhmm
32
+ hhmm = hhmm.gsub /[^\d]/, ""
33
+ hhmm = "0#{hhmm}00" if hhmm.length == 1
34
+ hhmm = "#{hhmm}00" if hhmm.length == 2
35
+ hhmm = "0#{hhmm}" if hhmm.length == 3
36
+ raise "Can't parse #{orig.inspect}" unless hhmm.match(/^\d\d\d\d$/)
37
+
38
+ hh = hhmm[0,2].to_i
39
+ mm = hhmm[2,4].to_i
40
+
41
+ new hh, mm
42
+ end
43
+
44
+ def change date
45
+ date.to_time.change :hour => hour, :min => minute
46
+ end
47
+
48
+ def to_s
49
+ "#{hour.to_s.rjust 2, "0"}#{minute.to_s.rjust 2, "0"}"
50
+ end
51
+ end
52
+
53
+ #
54
+ # a utility class to match the start and end times of an Event instance, without considering the event date
55
+ #
56
+ class Interval
57
+ attr_accessor :start, :finish
58
+
59
+ def initialize start, finish
60
+ @start, @finish = start, finish
61
+ end
62
+
63
+ # true if the start time and finish time of the given event matches start and finish times of this Interval
64
+ def matches? event
65
+ start.matches_time?(event.start) && finish.matches_time?(event.finish)
66
+ end
67
+
68
+ # build a new Event with this Interval's start and finish times, on the given date
69
+ def generate date
70
+ Event.new :start => start.change(date), :finish => finish.change(date)
71
+ end
72
+
73
+ def self.from start, finish
74
+ new HourMinute.parse(start), HourMinute.parse(finish)
75
+ end
76
+
77
+ def to_s
78
+ "#{start}-#{finish}"
79
+ end
80
+ end
81
+
82
+ #
83
+ # a utility class to represent a set of intervals on a single day
84
+ #
85
+ class Daily
86
+ attr_accessor :wday, :intervals
87
+
88
+ def initialize wday
89
+ @wday = wday
90
+ @intervals = []
91
+ end
92
+
93
+ def add interval
94
+ intervals << interval
95
+ end
96
+
97
+ def matches? event
98
+ return false if event.start.wday != self.wday
99
+ intervals.each { |interval|
100
+ return true if interval.matches? event
101
+ }
102
+ false
103
+ end
104
+
105
+ def generate date, list
106
+ return list if date.wday != wday
107
+ intervals.inject(list) { |list, interval|
108
+ list << interval.generate(date)
109
+ list
110
+ }
111
+ end
112
+
113
+ def to_s
114
+ "#{MAP_DAY[wday]} => #{intervals}"
115
+ end
116
+ end
117
+
118
+ #
119
+ # Weekly defines a set of intervals that recur weekly
120
+ #
121
+ # This class maintains an array of intervals for each weekday
122
+ #
123
+ # This class will never match multi-day events, as it assumes each event starts and ends within the same day
124
+ #
125
+ class Weekly
126
+
127
+ # wdays is a 7-element array of Daily instances
128
+ #
129
+ # the zeroth element corresponds to Sunday, because Time#wday behaves that way.
130
+ #
131
+ attr_accessor :wdays
132
+
133
+ # pattern is an array of the form
134
+ #
135
+ # [ { "day" => "wed", "start" => "0930", "finish" => "10:30"},
136
+ # { "day" => "wed", "start" => "18", "finish" => "20" },
137
+ # { "day" => "tue", "start" => "7", "finish" => "8h30" } ]
138
+ #
139
+ # see HourMinute#parse for how "start" and "finish" values are read
140
+ #
141
+ def initialize pattern
142
+ self.wdays = (0..6).map { |i| Daily.new(i) }
143
+
144
+ pattern.each { |interval_spec|
145
+ wday = DAY_MAP[interval_spec["day"]]
146
+ raise "unrecognised day #{spec["day"].inspect} in #{pattern.inspect}" if wday.nil?
147
+
148
+ start = HourMinute.parse interval_spec["start"]
149
+ finish = HourMinute.parse interval_spec["finish"]
150
+ wdays[wday].add Interval.new(start, finish)
151
+ }
152
+ end
153
+
154
+ # true if there exists a Daily corresponding to this event's weekday, with
155
+ # an Interval corresponding to this event's start and finish times;
156
+ # false otherwise
157
+ #
158
+ def matches? event
159
+ return false if event.start.day != event.finish.day
160
+ wdays[event.start.wday].matches? event
161
+ end
162
+
163
+ #
164
+ # generate a set of Events according to this Weekly schedule, starting
165
+ # on #from at the earliest, ending on #upto at the latest.
166
+ #
167
+ # Note: #upto is *inclusive*, this will generate events on #upto,
168
+ # if the schedule allows
169
+ #
170
+ def generate from, upto
171
+ events = []
172
+ from.upto(upto).map { |date|
173
+ wdays.each { |day| day.generate date, events }
174
+ }
175
+ events
176
+ end
177
+
178
+ #
179
+ # return a new list including only those events in the given list
180
+ # that match this weekly schedule
181
+ #
182
+ def filter events
183
+ events.select { |e| matches? e }
184
+ end
185
+
186
+ def to_s
187
+ wdays.map(&:to_s).join "\n"
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,11 @@
1
+ module Reservation
2
+ # replace this if you need to globally override the starting scope for events
3
+ def self.events
4
+ ::Reservation::Event.scoped
5
+ end
6
+
7
+ # replace this if you need to globally override the starting scope for reservations
8
+ def self.reservations
9
+ ::Reservation::Reservation.scoped
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module Reservation
2
+ module TimeOffset
3
+ def parse_time_offset hhmm
4
+ orig = hhmm
5
+ hhmm = hhmm.gsub /[^\d]/, ""
6
+ hhmm = "0#{hhmm}00" if hhmm.length == 1
7
+ hhmm = "#{hhmm}00" if hhmm.length == 2
8
+ hhmm = "0#{hhmm}" if hhmm.length == 3
9
+ raise "Can't parse #{orig.inspect}" unless hhmm.match(/^\d\d\d\d$/)
10
+
11
+ hh = hhmm[0,2].to_i
12
+ mm = hhmm[2,4].to_i
13
+
14
+ { :hour => hh, :min => mm }
15
+ end
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
1
  module Reservation
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -2,6 +2,7 @@ class CreateContacts < ActiveRecord::Migration
2
2
  def change
3
3
  create_table :contacts do |t|
4
4
  t.string :name
5
+ t.string :type
5
6
 
6
7
  t.timestamps
7
8
  end
@@ -15,6 +15,7 @@ ActiveRecord::Schema.define(:version => 20130711174025) do
15
15
 
16
16
  create_table "contacts", :force => true do |t|
17
17
  t.string "name"
18
+ t.string "type"
18
19
  t.datetime "created_at", :null => false
19
20
  t.datetime "updated_at", :null => false
20
21
  end
@@ -1,6 +1,18 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Reservation::Event do
4
+ before { Time.zone = "Europe/Paris" }
5
+ let(:matt) { Person.create! :name => "matt" }
6
+ let(:here) { Place.create! :name => "here" }
7
+ let(:bill) { Person.create! :name => "bill" }
8
+
9
+ def the_reservations subject=nil
10
+ if subject
11
+ Reservation::Reservation.where(:subject_type => subject.class.base_class.name, :subject_id => subject.id)
12
+ else
13
+ Reservation::Reservation.all
14
+ end.map { |r| "#{r.event.title} #{r.subject.name} #{r.event.start.prettyd} #{r.event.finish.prettyd} #{r.reservation_status}"}.join("\n")
15
+ end
4
16
 
5
17
  it "should be a Reservation::Event" do
6
18
  Reservation::Event.new.should be_an_instance_of Reservation::Event
@@ -188,5 +200,134 @@ describe Reservation::Event do
188
200
  ee[1].reservations.map(&:subject).map(&:name).should == %w{ APPL Cafe }
189
201
  ee[2].reservations.map(&:subject).map(&:name).should == %w{ APPL Ulysses }
190
202
  end
203
+
204
+ it "should find events for Ulysses and AAPL" do
205
+ ee = Reservation::Event.reserved_for(@book).reserved_for(@appl).order(:id)
206
+ ee.should == [@r6]
207
+ end
208
+ end
209
+
210
+ describe :create_weekly do
211
+ it "should build a simple event for a single subject" do
212
+ subjects = [{ "role" => "owner", "subject" => matt, "status" => "confirmed" } ]
213
+ pattern = [ { "day" => "mon", "start" => "0930", "finish" => "1030"} ]
214
+ Reservation::Event.create_weekly "floss teeth", "2013-09-03", "2013-10-13", subjects, pattern
215
+ the_reservations.should == "floss teeth matt Mon,20130909T0930 Mon,20130909T1030 confirmed
216
+ floss teeth matt Mon,20130916T0930 Mon,20130916T1030 confirmed
217
+ floss teeth matt Mon,20130923T0930 Mon,20130923T1030 confirmed
218
+ floss teeth matt Mon,20130930T0930 Mon,20130930T1030 confirmed
219
+ floss teeth matt Mon,20131007T0930 Mon,20131007T1030 confirmed"
220
+ end
221
+
222
+ it "should build multiple events for multiple subjects" do
223
+ subjects = [{ "role" => "owner", "subject" => matt, "status" => "confirmed" }, { "role" => "place", "subject" => here, "status" => "tentative" }]
224
+ pattern = [ { "day" => "wed", "start" => "0930", "finish" => "1030"}, { "day" => "wed", "start" => "18", "finish" => "20"}, { "day" => "tue", "start" => "7", "finish" => "830"} ]
225
+ Reservation::Event.create_weekly "the_title", "2013-09-03", "2013-09-30", subjects, pattern
226
+ the_reservations.should == "the_title matt Tue,20130903T0700 Tue,20130903T0830 confirmed
227
+ the_title here Tue,20130903T0700 Tue,20130903T0830 tentative
228
+ the_title matt Wed,20130904T0930 Wed,20130904T1030 confirmed
229
+ the_title here Wed,20130904T0930 Wed,20130904T1030 tentative
230
+ the_title matt Wed,20130904T1800 Wed,20130904T2000 confirmed
231
+ the_title here Wed,20130904T1800 Wed,20130904T2000 tentative
232
+ the_title matt Tue,20130910T0700 Tue,20130910T0830 confirmed
233
+ the_title here Tue,20130910T0700 Tue,20130910T0830 tentative
234
+ the_title matt Wed,20130911T0930 Wed,20130911T1030 confirmed
235
+ the_title here Wed,20130911T0930 Wed,20130911T1030 tentative
236
+ the_title matt Wed,20130911T1800 Wed,20130911T2000 confirmed
237
+ the_title here Wed,20130911T1800 Wed,20130911T2000 tentative
238
+ the_title matt Tue,20130917T0700 Tue,20130917T0830 confirmed
239
+ the_title here Tue,20130917T0700 Tue,20130917T0830 tentative
240
+ the_title matt Wed,20130918T0930 Wed,20130918T1030 confirmed
241
+ the_title here Wed,20130918T0930 Wed,20130918T1030 tentative
242
+ the_title matt Wed,20130918T1800 Wed,20130918T2000 confirmed
243
+ the_title here Wed,20130918T1800 Wed,20130918T2000 tentative
244
+ the_title matt Tue,20130924T0700 Tue,20130924T0830 confirmed
245
+ the_title here Tue,20130924T0700 Tue,20130924T0830 tentative
246
+ the_title matt Wed,20130925T0930 Wed,20130925T1030 confirmed
247
+ the_title here Wed,20130925T0930 Wed,20130925T1030 tentative
248
+ the_title matt Wed,20130925T1800 Wed,20130925T2000 confirmed
249
+ the_title here Wed,20130925T1800 Wed,20130925T2000 tentative"
250
+ end
251
+ end
252
+
253
+ describe :add_subject do
254
+ it "should add a subject within time constraints" do
255
+ subjects = [{ "role" => "owner", "subject" => matt, "status" => "confirmed" } ]
256
+ pattern = [ { "day" => "mon", "start" => "0930", "finish" => "1030"} ]
257
+ Reservation::Event.create_weekly "my_title", "2013-09-03", "2013-10-13", subjects, pattern
258
+ Reservation::Event.add_subject({ "role" => "student", "status" => "tentative", "subject" => bill}, { "from" => "2013-09-01", "upto" => "2013-09-24", "context" => matt })
259
+ the_reservations.should == "my_title matt Mon,20130909T0930 Mon,20130909T1030 confirmed
260
+ my_title matt Mon,20130916T0930 Mon,20130916T1030 confirmed
261
+ my_title matt Mon,20130923T0930 Mon,20130923T1030 confirmed
262
+ my_title matt Mon,20130930T0930 Mon,20130930T1030 confirmed
263
+ my_title matt Mon,20131007T0930 Mon,20131007T1030 confirmed
264
+ my_title bill Mon,20130909T0930 Mon,20130909T1030 tentative
265
+ my_title bill Mon,20130916T0930 Mon,20130916T1030 tentative
266
+ my_title bill Mon,20130923T0930 Mon,20130923T1030 tentative"
267
+ end
268
+
269
+ it "should add a subject within schedule contraints" do
270
+ subjects = [ { "role" => "owner", "subject" => matt, "status" => "confirmed" },
271
+ { "role" => "place", "subject" => here, "status" => "tentative" } ]
272
+
273
+ pattern = [ { "day" => "wed", "start" => "0930", "finish" => "1030" },
274
+ { "day" => "wed", "start" => "18", "finish" => "20" },
275
+ { "day" => "tue", "start" => "7", "finish" => "830" } ]
276
+
277
+ Reservation::Event.create_weekly "the_title", "2013-09-03", "2013-10-13", subjects, pattern
278
+
279
+ add_schedule = [ { "day" => "wed", "start" => "18", "finish" => "20"} ]
280
+ Reservation::Event.add_subject({ "role" => "student", "status" => "tentative", "subject" => bill}, { "from" => "2013-09-17", "upto" => "2013-10-02", "schedule" => add_schedule })
281
+ the_reservations(bill).should == "the_title bill Wed,20130918T1800 Wed,20130918T2000 tentative
282
+ the_title bill Wed,20130925T1800 Wed,20130925T2000 tentative
283
+ the_title bill Wed,20131002T1800 Wed,20131002T2000 tentative"
284
+ end
285
+ end
286
+
287
+ describe :remove_subject do
288
+ it "should remove a subject within time constraints" do
289
+ subjects = [{ "role" => "owner", "subject" => matt, "status" => "confirmed" }, { "role" => "owner", "subject" => bill, "status" => "confirmed" } ]
290
+ pattern = [ { "day" => "mon", "start" => "0930", "finish" => "1030"} ]
291
+ Reservation::Event.create_weekly "my_title", "2013-09-03", "2013-10-13", subjects, pattern
292
+ Reservation::Event.remove_subject(bill, { "from" => "2013-09-15", "upto" => "2013-09-24", "context" => matt })
293
+ the_reservations.should == "my_title matt Mon,20130909T0930 Mon,20130909T1030 confirmed
294
+ my_title bill Mon,20130909T0930 Mon,20130909T1030 confirmed
295
+ my_title matt Mon,20130916T0930 Mon,20130916T1030 confirmed
296
+ my_title matt Mon,20130923T0930 Mon,20130923T1030 confirmed
297
+ my_title matt Mon,20130930T0930 Mon,20130930T1030 confirmed
298
+ my_title bill Mon,20130930T0930 Mon,20130930T1030 confirmed
299
+ my_title matt Mon,20131007T0930 Mon,20131007T1030 confirmed
300
+ my_title bill Mon,20131007T0930 Mon,20131007T1030 confirmed"
301
+ end
302
+
303
+ it "should remove a subject within schedule contraints" do
304
+ subjects = [ { "role" => "owner", "subject" => matt, "status" => "confirmed" },
305
+ { "role" => "helpr", "subject" => bill, "status" => "tentative" },
306
+ { "role" => "place", "subject" => here, "status" => "tentative" }
307
+ ]
308
+
309
+ pattern = [ { "day" => "wed", "start" => "0930", "finish" => "1030"},
310
+ { "day" => "wed", "start" => "18", "finish" => "20" },
311
+ { "day" => "tue", "start" => "7", "finish" => "830" } ]
312
+
313
+ Reservation::Event.create_weekly "the_title", "2013-09-03", "2013-10-13", subjects, pattern
314
+
315
+ remove_schedule = [ { "day" => "wed", "start" => "18", "finish" => "20"}, { "day" => "wed", "start" => "930", "finish" => "1030"} ]
316
+ Reservation::Event.remove_subject(bill, { "from" => "2013-09-17", "upto" => "2013-10-02", "schedule" => remove_schedule })
317
+
318
+ the_reservations(bill).should == "
319
+ the_title bill Tue,20130903T0700 Tue,20130903T0830 tentative
320
+ the_title bill Wed,20130904T0930 Wed,20130904T1030 tentative
321
+ the_title bill Wed,20130904T1800 Wed,20130904T2000 tentative
322
+ the_title bill Tue,20130910T0700 Tue,20130910T0830 tentative
323
+ the_title bill Wed,20130911T0930 Wed,20130911T1030 tentative
324
+ the_title bill Wed,20130911T1800 Wed,20130911T2000 tentative
325
+ the_title bill Tue,20130917T0700 Tue,20130917T0830 tentative
326
+ the_title bill Tue,20130924T0700 Tue,20130924T0830 tentative
327
+ the_title bill Tue,20131001T0700 Tue,20131001T0830 tentative
328
+ the_title bill Tue,20131008T0700 Tue,20131008T0830 tentative
329
+ the_title bill Wed,20131009T0930 Wed,20131009T1030 tentative
330
+ the_title bill Wed,20131009T1800 Wed,20131009T2000 tentative".strip
331
+ end
191
332
  end
192
333
  end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reservation::Schedule::Daily do
4
+ before { Time.zone = "Europe/Paris" }
5
+ let(:event_start) { time("2013-07-12T07:00:00") }
6
+ let(:event_finish) { time("2013-07-12T09:30:00") }
7
+ let(:event) { Reservation::Event.new :start => event_start, :finish => event_finish }
8
+
9
+ def interval start, finish
10
+ start = Reservation::Schedule::HourMinute.parse start
11
+ finish = Reservation::Schedule::HourMinute.parse finish
12
+ Reservation::Schedule::Interval.new(start, finish)
13
+ end
14
+
15
+ it "should match when there exists a matching interval on the same day as the given event" do
16
+ daily = Reservation::Schedule::Daily.new(5)
17
+ daily.add interval("7h", "9:30")
18
+ daily.add interval("18", "2230")
19
+
20
+ daily.matches?(event).should be_true
21
+ end
22
+
23
+ it "should not match when the day is different" do
24
+ daily = Reservation::Schedule::Daily.new(3)
25
+ daily.add interval("7h", "9:30")
26
+ daily.add interval("18", "2230")
27
+
28
+ daily.matches?(event).should be_false
29
+ end
30
+
31
+ it "should not match when there is no corresponding interval" do
32
+ daily = Reservation::Schedule::Daily.new(5)
33
+ daily.add interval("7h", "9:45")
34
+ daily.add interval("18", "2230")
35
+
36
+ daily.matches?(event).should be_false
37
+ end
38
+
39
+ it "should generate events corresponding to its intervals on the given matching date" do
40
+ daily = Reservation::Schedule::Daily.new(5)
41
+ daily.add interval("7h", "9:45")
42
+ daily.add interval("18", "2230")
43
+
44
+ events = []
45
+ daily.generate date("2013-07-12"), events
46
+ events.map { |e| "#{e.start.prettyd} #{e.finish.pretty}"}.join("\n").
47
+ should == "Fri,20130712T0700 20130712T0945
48
+ Fri,20130712T1800 20130712T2230"
49
+ end
50
+
51
+ it "should generate no events when the date does not match its weekday" do
52
+ daily = Reservation::Schedule::Daily.new(2)
53
+ daily.add interval("7h", "9:45")
54
+ daily.add interval("18", "2230")
55
+
56
+ events = []
57
+ daily.generate date("2013-07-12"), events
58
+ events.should == []
59
+ end
60
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reservation::Schedule::HourMinute do
4
+ before { Time.zone = "Europe/Paris" }
5
+ let(:rightnow) { time("2013-04-30T18:30:00") }
6
+
7
+ it "should match when hours and minutes match" do
8
+ hm = Reservation::Schedule::HourMinute.new 18, 30
9
+ hm.matches_time?(rightnow).should be_true
10
+ end
11
+
12
+ it "should not match when minutes differ" do
13
+ hm = Reservation::Schedule::HourMinute.new 18, 31
14
+ hm.matches_time?(rightnow).should be_false
15
+ end
16
+
17
+ it "should not match when hours differ" do
18
+ hm = Reservation::Schedule::HourMinute.new 17, 30
19
+ hm.matches_time?(rightnow).should be_false
20
+ end
21
+
22
+ it "should parse '7' as '0700'" do
23
+ hm = Reservation::Schedule::HourMinute.parse "7"
24
+ hm.hour.should == 7
25
+ hm.minute.should == 0
26
+ end
27
+
28
+ it "should parse '11' as '1100'" do
29
+ hm = Reservation::Schedule::HourMinute.parse "11"
30
+ hm.hour.should == 11
31
+ hm.minute.should == 0
32
+ end
33
+
34
+ it "should parse '815' as '0815'" do
35
+ hm = Reservation::Schedule::HourMinute.parse "815"
36
+ hm.hour.should == 8
37
+ hm.minute.should == 15
38
+ end
39
+
40
+ it "should change a date to a time with the given hour and minute" do
41
+ hm = Reservation::Schedule::HourMinute.parse "815"
42
+ time = hm.change date("2013-07-12")
43
+ time.pretty.should == "20130712T0815"
44
+ end
45
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reservation::Schedule::Interval do
4
+ before { Time.zone = "Europe/Paris" }
5
+
6
+ let(:start) { time("2013-04-30T18:30:00") }
7
+ let(:finish) { time("2013-04-30T22:30:00") }
8
+ let(:event) { Reservation::Event.new :start => start, :finish => finish }
9
+
10
+ it "should match when start and finish match" do
11
+ hm1 = Reservation::Schedule::HourMinute.new 18, 30
12
+ hm2 = Reservation::Schedule::HourMinute.new 22, 30
13
+ interval = Reservation::Schedule::Interval.new hm1, hm2
14
+ interval.matches?(event).should be_true
15
+ end
16
+
17
+ it "should not match when start differs" do
18
+ hm1 = Reservation::Schedule::HourMinute.new 14, 00
19
+ hm2 = Reservation::Schedule::HourMinute.new 22, 30
20
+ interval = Reservation::Schedule::Interval.new hm1, hm2
21
+ interval.matches?(event).should be_false
22
+ end
23
+
24
+ it "should not match when finish differs" do
25
+ hm1 = Reservation::Schedule::HourMinute.new 18, 30
26
+ hm2 = Reservation::Schedule::HourMinute.new 23, 00
27
+ interval = Reservation::Schedule::Interval.new hm1, hm2
28
+ interval.matches?(event).should be_false
29
+ end
30
+
31
+ it "should generate a new Event with its start and finish times on the given date" do
32
+ interval = make_interval "1830", "2300"
33
+ event = interval.generate date('2013-07-12')
34
+ event.start.pretty.should == "20130712T1830"
35
+ event.finish.pretty.should == "20130712T2300"
36
+ end
37
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reservation::Schedule::Weekly do
4
+ before { Time.zone = "Europe/Paris" }
5
+ let(:event_start) { time("2013-07-12T07:00:00") } # this is a friday
6
+ let(:event_finish) { time("2013-07-12T09:30:00") }
7
+ let(:event) { Reservation::Event.new :start => event_start, :finish => event_finish }
8
+
9
+ it "should not match in any case if the event spans more than one day" do
10
+ weekly = Reservation::Schedule::Weekly.new [ { "day" => "wed", "start" => "0930", "finish" => "10:30"},
11
+ { "day" => "wed", "start" => "18", "finish" => "20" },
12
+ { "day" => "fri", "start" => "7h", "finish" => "9:30" },
13
+ { "day" => "tue", "start" => "7", "finish" => "8h30" } ]
14
+
15
+ start = time("2013-02-12T07:00:00") # this is a tuesday
16
+ finish = time("2013-03-12T09:30:00")
17
+ my_event = Reservation::Event.new :start => start, :finish => finish
18
+
19
+ weekly.matches?(my_event).should be_false
20
+ end
21
+
22
+ it "should match when there exists a matching interval on the same day as the given event" do
23
+ weekly = Reservation::Schedule::Weekly.new [ { "day" => "wed", "start" => "0930", "finish" => "10:30"},
24
+ { "day" => "wed", "start" => "18", "finish" => "20" },
25
+ { "day" => "fri", "start" => "7h", "finish" => "9:30" },
26
+ { "day" => "tue", "start" => "7", "finish" => "8h30" } ]
27
+
28
+ weekly.matches?(event).should be_true
29
+ end
30
+
31
+ it "should not match when there exists no matching interval on the same day as the given event" do
32
+ weekly = Reservation::Schedule::Weekly.new [ { "day" => "wed", "start" => "0930", "finish" => "10:30"},
33
+ { "day" => "wed", "start" => "18", "finish" => "20" },
34
+ { "day" => "fri", "start" => "7h", "finish" => "8:30" },
35
+ { "day" => "tue", "start" => "7", "finish" => "8h30" } ]
36
+
37
+ weekly.matches?(event).should be_false
38
+ end
39
+
40
+ it "should generate events corresponding to the weekly schedule" do
41
+ weekly = Reservation::Schedule::Weekly.new [ { "day" => "wed", "start" => "0930", "finish" => "10:30"},
42
+ { "day" => "wed", "start" => "18", "finish" => "20" },
43
+ { "day" => "fri", "start" => "10h", "finish" => "11:45" },
44
+ { "day" => "tue", "start" => "7", "finish" => "8h30" } ]
45
+
46
+ events = weekly.generate date("2013-07-08"), date("2013-07-23")
47
+ events.map { |e| "#{e.start.prettyd} #{e.finish.pretty}"}.join("\n").
48
+ should == "Tue,20130709T0700 20130709T0830
49
+ Wed,20130710T0930 20130710T1030
50
+ Wed,20130710T1800 20130710T2000
51
+ Fri,20130712T1000 20130712T1145
52
+ Tue,20130716T0700 20130716T0830
53
+ Wed,20130717T0930 20130717T1030
54
+ Wed,20130717T1800 20130717T2000
55
+ Fri,20130719T1000 20130719T1145
56
+ Tue,20130723T0700 20130723T0830"
57
+ end
58
+
59
+ it "should filter a set of events" do
60
+ weekly = Reservation::Schedule::Weekly.new [ { "day" => "wed", "start" => "0930", "finish" => "10:30"},
61
+ { "day" => "wed", "start" => "18", "finish" => "20" },
62
+ { "day" => "fri", "start" => "10h", "finish" => "11:45" },
63
+ { "day" => "tue", "start" => "7", "finish" => "8h30" } ]
64
+
65
+ events = weekly.generate date("2013-07-08"), date("2013-07-23")
66
+
67
+ filter = Reservation::Schedule::Weekly.new [ { "day" => "wed", "start" => "0930", "finish" => "10:30"} ]
68
+
69
+ events = filter.filter events
70
+ events.map { |e| "#{e.start.prettyd} #{e.finish.pretty}"}.join("\n").
71
+ should == "Wed,20130710T0930 20130710T1030
72
+ Wed,20130717T0930 20130717T1030"
73
+ end
74
+ end
@@ -27,6 +27,10 @@ module Helper
27
27
  def time t
28
28
  Time.parse t
29
29
  end
30
+
31
+ def make_interval start, finish
32
+ Reservation::Schedule::Interval.from start, finish
33
+ end
30
34
  end
31
35
 
32
36
  RSpec::Core::ExampleGroup.send :include, Helper
@@ -39,4 +43,13 @@ ActiveRecord::Base.silence do
39
43
  load(File.dirname(__FILE__) + '/dummy/db/schema.rb')
40
44
  end
41
45
 
46
+ class Time
47
+ def pretty
48
+ self.strftime("%Y%m%dT%H%M")
49
+ end
50
+
51
+ def prettyd
52
+ self.strftime("%a,%Y%m%dT%H%M")
53
+ end
54
+ end
42
55
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reservation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-11 00:00:00.000000000 Z
12
+ date: 2013-07-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -99,16 +99,21 @@ extensions: []
99
99
  extra_rdoc_files: []
100
100
  files:
101
101
  - .gitignore
102
+ - .loco
102
103
  - .rspec
103
104
  - Gemfile
104
105
  - MIT-LICENSE
105
- - README.rdoc
106
+ - README.md
106
107
  - Rakefile
107
108
  - lib/generators/reservation/reservations_generator.rb
108
109
  - lib/generators/reservation/templates/migration.rb
109
110
  - lib/reservation.rb
110
111
  - lib/reservation/event.rb
112
+ - lib/reservation/event_filter.rb
111
113
  - lib/reservation/reservation.rb
114
+ - lib/reservation/schedule.rb
115
+ - lib/reservation/scopes.rb
116
+ - lib/reservation/time_offset.rb
112
117
  - lib/reservation/version.rb
113
118
  - lib/tasks/reservation_tasks.rake
114
119
  - reservation.gemspec
@@ -167,6 +172,10 @@ files:
167
172
  - spec/dummy/test/unit/reservation/time_slot_test.rb
168
173
  - spec/dummy/test/unit/thing_test.rb
169
174
  - spec/models/event_spec.rb
175
+ - spec/models/schedule/daily_spec.rb
176
+ - spec/models/schedule/hour_minute_spec.rb
177
+ - spec/models/schedule/interval_spec.rb
178
+ - spec/models/schedule/weekly_spec.rb
170
179
  - spec/spec_helper.rb
171
180
  homepage: http://github.com/conanite/reservation
172
181
  licenses: []
@@ -248,4 +257,8 @@ test_files:
248
257
  - spec/dummy/test/unit/reservation/time_slot_test.rb
249
258
  - spec/dummy/test/unit/thing_test.rb
250
259
  - spec/models/event_spec.rb
260
+ - spec/models/schedule/daily_spec.rb
261
+ - spec/models/schedule/hour_minute_spec.rb
262
+ - spec/models/schedule/interval_spec.rb
263
+ - spec/models/schedule/weekly_spec.rb
251
264
  - spec/spec_helper.rb
@@ -1,3 +0,0 @@
1
- = Reservation
2
-
3
- This project rocks and uses MIT-LICENSE.