reservation 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.loco +3 -0
- data/MIT-LICENSE +1 -1
- data/README.md +56 -0
- data/lib/reservation.rb +3 -2
- data/lib/reservation/event.rb +57 -1
- data/lib/reservation/event_filter.rb +47 -0
- data/lib/reservation/reservation.rb +6 -4
- data/lib/reservation/schedule.rb +191 -0
- data/lib/reservation/scopes.rb +11 -0
- data/lib/reservation/time_offset.rb +17 -0
- data/lib/reservation/version.rb +1 -1
- data/spec/dummy/db/migrate/20130711174025_create_contacts.rb +1 -0
- data/spec/dummy/db/schema.rb +1 -0
- data/spec/models/event_spec.rb +141 -0
- data/spec/models/schedule/daily_spec.rb +60 -0
- data/spec/models/schedule/hour_minute_spec.rb +45 -0
- data/spec/models/schedule/interval_spec.rb +37 -0
- data/spec/models/schedule/weekly_spec.rb +74 -0
- data/spec/spec_helper.rb +13 -0
- metadata +16 -3
- data/README.rdoc +0 -3
data/.loco
ADDED
data/MIT-LICENSE
CHANGED
data/README.md
ADDED
@@ -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
|
data/lib/reservation.rb
CHANGED
data/lib/reservation/event.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
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
|
data/lib/reservation/version.rb
CHANGED
data/spec/dummy/db/schema.rb
CHANGED
data/spec/models/event_spec.rb
CHANGED
@@ -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
|
data/spec/spec_helper.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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
|
data/README.rdoc
DELETED