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 +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