weesked 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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +137 -0
- data/Rakefile +14 -0
- data/lib/weesked/availiability.rb +41 -0
- data/lib/weesked/configuration.rb +59 -0
- data/lib/weesked/day.rb +25 -0
- data/lib/weesked/day_builder.rb +68 -0
- data/lib/weesked/offset_handler.rb +53 -0
- data/lib/weesked/schedule.rb +122 -0
- data/lib/weesked/version.rb +3 -0
- data/lib/weesked.rb +10 -0
- data/test/test_helper.rb +46 -0
- data/test/weesked/availiability_test.rb +155 -0
- data/test/weesked/configuration_test.rb +13 -0
- data/test/weesked/day_bulder_test.rb +98 -0
- data/test/weesked/day_test.rb +86 -0
- data/test/weesked/offset_handler_spec.rb +39 -0
- data/test/weesked/schedule_test.rb +87 -0
- data/test/weesked_test.rb +7 -0
- data/weesked.gemspec +25 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d713ba18b65104cb45d0b4b44940399164fa1b31
|
4
|
+
data.tar.gz: 11d2fa26b7744f569caeb78f00f341d31d51d0fe
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2ebfcab19081a08c2f85dba70d7c7abff2db357fb7d94fed6d7eb7b948a9073b30860428e788960504aadea0046c5aa1df085fa5fb05b1938f9052615e4d0cd5
|
7
|
+
data.tar.gz: ed4c59d6b925a01285a9b79c4eebe48601fedc46e3d9f8ee15ba87f54128a7003aedb9b148d0499ec24b43639065446930e5fcd3adcf72db9ff17ce23f065efb
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Igor Davydov
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
[](https://travis-ci.org/div/weesked)
|
2
|
+
|
3
|
+
# Weesked
|
4
|
+
|
5
|
+
Simple weekely schedule based on redis sets
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'weesked'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install weesked
|
22
|
+
|
23
|
+
## Setup
|
24
|
+
|
25
|
+
Weesked need Redis.current to return your redis instance
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
Let's say that we run some small buisness, we've got several employees which provide some service to our customers. Customers book appointments with our employees. But each employee has her personal week schedule. Some work in the morning, some on weekends etc. So you want to know if this employee is availiable on tuesday at 18.00. We need to check his appointments if he is free at that time, and also we need to check his schedule if he is even availiable on that day. Figuring out appointments is easy - we've got postgres ranges for that. But the weekly schedule is a bit trickier. We could use the same ranges approach to make availibility schedule for the comming weeks, but we'll have to maintain some process to keep this shcedule updated and so on. Other option is to use some scheduling lib like ice_cube. But you wont be able to query all your employees availiability easely.
|
30
|
+
|
31
|
+
Here comes weesked - redis weekly scheduler. Schedule is an array of redis sets, each list corresponds to time period in some incremets let's say an hour for now. So our week consists of 24*7=168 sets. We add our employee to a list to mark that she's not availiable at that time.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class Employee
|
35
|
+
include Weesked::Schedule
|
36
|
+
|
37
|
+
def self.weesked_schedule_key
|
38
|
+
"weesked:#{class_name.downcase}"
|
39
|
+
# by default each class has it's own schedule
|
40
|
+
# you can set same key for different classes to share same schedule
|
41
|
+
end
|
42
|
+
|
43
|
+
def id
|
44
|
+
# rails got that for you
|
45
|
+
end
|
46
|
+
|
47
|
+
def weesked_key
|
48
|
+
"#{class.class_name.downcase}:#{id}"
|
49
|
+
# that's defefault - fill free to override
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
Including Weesked::Schedule module gives you the folowing:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
employee = Employee.new
|
59
|
+
|
60
|
+
employee.schedule
|
61
|
+
=> {monday: [1, 2, 5, 6, 9], tuesday: [0, 18, 25], ...}
|
62
|
+
|
63
|
+
employee.schedule = {monday: [1, 2, 5, 6, 9], tuesday: [0, 18, 25], ...}
|
64
|
+
=> {monday: [1, 2, 5, 6, 9], tuesday: [0, 18, 25], ...}
|
65
|
+
|
66
|
+
employee.availiable? Time.now
|
67
|
+
=> true
|
68
|
+
|
69
|
+
Employee.availiable Time.now
|
70
|
+
=> [1, 3, 18]
|
71
|
+
# employee ids
|
72
|
+
|
73
|
+
Employee.availiable 10.hours.from_now..12.hours.from_now
|
74
|
+
=> [3, 12]
|
75
|
+
```
|
76
|
+
|
77
|
+
So if we're using rails with postgres and want to get the employees availiable and without appointments, you might do something like that:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
time_range = 10.hours.from_now..12.hours.from_now
|
81
|
+
booked_employees = Employee.joins(:appointments).merge(Appointment.booked_on(time_range))
|
82
|
+
availiable_employees = Eployee.where id: Employee.availiable(time_range)
|
83
|
+
```
|
84
|
+
Now you'll have to extract the first from the second to get those which are free at this time range.
|
85
|
+
Or you can do combine it in a single query (not sure how to do it in AR) and assign to a scope:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
scope :free, -> (range) {
|
89
|
+
from("
|
90
|
+
(
|
91
|
+
(
|
92
|
+
#{Employee.availiable(range).to_sql}
|
93
|
+
)
|
94
|
+
except
|
95
|
+
(
|
96
|
+
#{Employee.booked_on(range).to_sql}
|
97
|
+
)
|
98
|
+
) #{Employee.table_name}
|
99
|
+
")
|
100
|
+
}
|
101
|
+
```
|
102
|
+
|
103
|
+
## Config
|
104
|
+
|
105
|
+
You might want to customize method names and/or time step if you need more granularity in your schedule.
|
106
|
+
You coluld throw this in an initializer or just config where ever suits your needs.
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
Weesked.setup do |config|
|
110
|
+
# ...
|
111
|
+
# Configures the methods needed by weesked
|
112
|
+
config.schedule_method = :schedule
|
113
|
+
config.availiable_method = :availiable
|
114
|
+
|
115
|
+
# Configures the default stuff about days and steps
|
116
|
+
config.time_step = 1.hour
|
117
|
+
config.availiable_days = %w(sunday monday tuesday wednesday thursday friday saturday)
|
118
|
+
config.availiable_steps = (9..18).to_a # if your step is 1.hour i'ts typical workours
|
119
|
+
config.steps_day_shift = 3 # number of steps if you want 2am on tuesday still be 'on monday'
|
120
|
+
# it's only needed if in you UI you want users to pick availiability on monday: from 18pm to 2am
|
121
|
+
# we make it easier to do so with day_shift
|
122
|
+
# ...
|
123
|
+
end
|
124
|
+
|
125
|
+
Redis.current = Redis.new(url: '//127.0.0.1:6379/1')
|
126
|
+
# but it's better to put redis initialization in it's own initializer
|
127
|
+
```
|
128
|
+
But keep in mind that .schedule will return indexes of your new steps within a day. So if you set step to 15.minutes you'll have 96 periods in a day [0,95] and so on.
|
129
|
+
|
130
|
+
|
131
|
+
## Contributing
|
132
|
+
|
133
|
+
1. Fork it ( https://github.com/div/weesked/fork )
|
134
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
135
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
136
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
137
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task default: :test
|
7
|
+
|
8
|
+
desc 'Test the weesked gem.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Weesked
|
2
|
+
module Availiability
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def included(klass)
|
6
|
+
klass.send :include, InstanceMethods
|
7
|
+
klass.extend ClassMethods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
def availiability range
|
14
|
+
keys = range_keys range
|
15
|
+
if keys.empty?
|
16
|
+
[]
|
17
|
+
else
|
18
|
+
redis.sinter *keys
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def range_keys range
|
23
|
+
keys = []
|
24
|
+
day = Day.build range
|
25
|
+
return keys unless day.steps
|
26
|
+
day.steps.each do |step|
|
27
|
+
keys << self.weesked_schedule_key(day.day, step)
|
28
|
+
end
|
29
|
+
keys
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
module InstanceMethods
|
35
|
+
def availiable? range
|
36
|
+
self.class.availiability(range).include?(id.to_s)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.expand_path('../version', __FILE__)
|
2
|
+
|
3
|
+
module Weesked
|
4
|
+
MINUTES_IN_HOUR = 60
|
5
|
+
SECONDS_IN_MINUTE = 60
|
6
|
+
SECONDS_IN_HOUR = MINUTES_IN_HOUR * SECONDS_IN_MINUTE
|
7
|
+
SUNDAY = 0
|
8
|
+
SATURDAY = 6
|
9
|
+
|
10
|
+
module Configuration
|
11
|
+
VALID_OPTIONS_KEYS = [
|
12
|
+
:time_step,
|
13
|
+
:availiable_days,
|
14
|
+
:availiable_steps,
|
15
|
+
:steps_day_shift,
|
16
|
+
# :redis
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
|
20
|
+
# By default, 1.hour
|
21
|
+
DEFAULT_TIME_STEP = SECONDS_IN_HOUR # 1.hour
|
22
|
+
|
23
|
+
# By default, the whole week
|
24
|
+
DEFAULT_AVAILIABLE_DAYS = %w(sunday monday tuesday wednesday thursday friday saturday)
|
25
|
+
|
26
|
+
# By default, whole day in hours
|
27
|
+
DEFAULT_AVAILIABLE_STEPS = (0..23).to_a
|
28
|
+
|
29
|
+
# By default, we use astonomical day
|
30
|
+
DEFAULT_STEPS_DAY_SHIFT = 0
|
31
|
+
|
32
|
+
attr_accessor *VALID_OPTIONS_KEYS
|
33
|
+
|
34
|
+
# When this module is extended, set all configuration options to their default values
|
35
|
+
def self.extended(base)
|
36
|
+
base.reset
|
37
|
+
end
|
38
|
+
|
39
|
+
# Convenience method to allow configuration options to be set in a block
|
40
|
+
def configure
|
41
|
+
yield self
|
42
|
+
end
|
43
|
+
|
44
|
+
# Create a hash of options and their values
|
45
|
+
def options
|
46
|
+
VALID_OPTIONS_KEYS.inject({}) do |option, key|
|
47
|
+
option.merge!(key => send(key))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Reset all configuration options to defaults
|
52
|
+
def reset
|
53
|
+
self.time_step = DEFAULT_TIME_STEP
|
54
|
+
self.availiable_days = DEFAULT_AVAILIABLE_DAYS
|
55
|
+
self.availiable_steps = DEFAULT_AVAILIABLE_STEPS
|
56
|
+
self.steps_day_shift = DEFAULT_STEPS_DAY_SHIFT
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/weesked/day.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Weesked
|
2
|
+
class WrongDay < StandardError; end
|
3
|
+
class Day
|
4
|
+
attr_reader :day
|
5
|
+
|
6
|
+
def self.build date
|
7
|
+
DayBuilder.new(date).run
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(day, steps=[])
|
11
|
+
@steps = steps
|
12
|
+
@day = if day.kind_of?(Integer)
|
13
|
+
Weesked.availiable_days.fetch(day.to_i).to_sym
|
14
|
+
else
|
15
|
+
raise WrongDay unless Weesked.availiable_days.include?(day.to_s)
|
16
|
+
day.to_sym
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def steps
|
21
|
+
steps = (Array(@steps)- ['', nil]).map(&:to_i)
|
22
|
+
Weesked.availiable_steps&steps
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Weesked
|
2
|
+
|
3
|
+
class DateNotRecognized < StandardError; end
|
4
|
+
class NotAvailiable < StandardError; end
|
5
|
+
|
6
|
+
class DayBuilder
|
7
|
+
|
8
|
+
def initialize dates
|
9
|
+
@dates = dates
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
if dates.kind_of? Range
|
14
|
+
build_from_date_range
|
15
|
+
elsif dates.kind_of? Time
|
16
|
+
build_from_single_date
|
17
|
+
else
|
18
|
+
raise DateNotRecognized
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :dates
|
25
|
+
|
26
|
+
def build_from_single_date date=dates
|
27
|
+
wd = date.wday
|
28
|
+
time = beginning_of_step date
|
29
|
+
step = step_index time
|
30
|
+
raise NotAvailiable unless Weesked.availiable_steps.include? step
|
31
|
+
if step < Weesked.steps_day_shift
|
32
|
+
if wd == SUNDAY
|
33
|
+
wd = SATURDAY
|
34
|
+
else
|
35
|
+
wd -= 1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
Day.new(wd, step)
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_from_date_range
|
42
|
+
start = build_from_single_date dates.begin
|
43
|
+
ending = build_from_single_date dates.end
|
44
|
+
raise NotAvailiable unless start.day == ending.day
|
45
|
+
i_start = Weesked.availiable_steps.index start.steps.first
|
46
|
+
i_end = Weesked.availiable_steps.index ending.steps.first
|
47
|
+
array = if Weesked.steps_day_shift > 0 && i_start > i_end
|
48
|
+
Weesked.availiable_steps.slice(i_start..-1) + Weesked.availiable_steps.slice(0..i_end)
|
49
|
+
else
|
50
|
+
Weesked.availiable_steps.slice(i_start..i_end)
|
51
|
+
end
|
52
|
+
Day.new start.day, array
|
53
|
+
end
|
54
|
+
|
55
|
+
def beginning_of_step time
|
56
|
+
(seconds_since_midnight(time) / Weesked.time_step) * Weesked.time_step
|
57
|
+
end
|
58
|
+
|
59
|
+
def step_index seconds
|
60
|
+
seconds / Weesked.time_step
|
61
|
+
end
|
62
|
+
|
63
|
+
def seconds_since_midnight time
|
64
|
+
time.hour * SECONDS_IN_HOUR + time.min * SECONDS_IN_MINUTE + time.sec
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Weesked
|
2
|
+
class OffsetHandler
|
3
|
+
|
4
|
+
def initialize(ary = [], offset = 0, length = 24)
|
5
|
+
@ary = ary.sort
|
6
|
+
@offset = offset
|
7
|
+
@length = length
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_a
|
11
|
+
return ary if ary.length == 0 || offset == 0
|
12
|
+
ary.push(ary.shift(offset)).flatten
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_range
|
16
|
+
to_a.inject([]) do |spans, n|
|
17
|
+
if start_new_range? spans, n
|
18
|
+
spans + [n..n]
|
19
|
+
else
|
20
|
+
spans[0..-2] + [spans.last.first..n]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :ary, :offset, :length
|
28
|
+
|
29
|
+
def start_new_range? spans, current
|
30
|
+
return true if spans.empty?
|
31
|
+
previous = spans.last.last
|
32
|
+
return true unless consecutive?(previous, current) || consecutive_with_offset?(previous, current)
|
33
|
+
end
|
34
|
+
|
35
|
+
def max? n
|
36
|
+
n == max
|
37
|
+
end
|
38
|
+
|
39
|
+
def max
|
40
|
+
length - 1
|
41
|
+
end
|
42
|
+
|
43
|
+
def consecutive_with_offset? x, y
|
44
|
+
x % max == y && max?(x)
|
45
|
+
end
|
46
|
+
|
47
|
+
def consecutive? x, y
|
48
|
+
x == y - 1
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Weesked
|
2
|
+
module Schedule
|
3
|
+
|
4
|
+
class NotConnected < StandardError; end
|
5
|
+
class NilObjectId < StandardError; end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def redis=(conn)
|
9
|
+
@redis = conn
|
10
|
+
end
|
11
|
+
|
12
|
+
def redis
|
13
|
+
@redis || $redis || Redis.current ||
|
14
|
+
raise(NotConnected, "Redis not set to a Redis.new connection")
|
15
|
+
end
|
16
|
+
|
17
|
+
def included(klass)
|
18
|
+
klass.instance_variable_set('@redis', nil)
|
19
|
+
klass.send :include, InstanceMethods
|
20
|
+
klass.extend ClassMethods
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
attr_writer :redis
|
26
|
+
def redis
|
27
|
+
@redis || Schedule.redis
|
28
|
+
end
|
29
|
+
|
30
|
+
def redis_prefix=(redis_prefix) @redis_prefix = redis_prefix end
|
31
|
+
def redis_prefix(klass = self)
|
32
|
+
@redis_prefix ||= klass.name.to_s.
|
33
|
+
sub(%r{(.*::)}, '').
|
34
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
35
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
36
|
+
downcase
|
37
|
+
end
|
38
|
+
|
39
|
+
def weesked_schedule_key(day, step)
|
40
|
+
"weesked:availiability:#{self.name.downcase}:#{day}:#{step}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def availiable date
|
44
|
+
end
|
45
|
+
|
46
|
+
def reset_schedule
|
47
|
+
redis.multi do
|
48
|
+
Weesked.availiable_days.each do |day|
|
49
|
+
Weesked.availiable_steps.each do |step|
|
50
|
+
redis.del weesked_schedule_key(day, step)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
module InstanceMethods
|
59
|
+
def redis
|
60
|
+
self.class.redis
|
61
|
+
end
|
62
|
+
|
63
|
+
def schedule=(availiability_hash)
|
64
|
+
update_schedule_for_instance availiability_hash
|
65
|
+
update_schedule_for_class
|
66
|
+
end
|
67
|
+
|
68
|
+
def schedule(range=false)
|
69
|
+
return get_schedule unless range
|
70
|
+
Hash[get_schedule.map {|k,v| [k, OffsetHandler.new(v, Weesked.steps_day_shift, 24).to_range] }]
|
71
|
+
end
|
72
|
+
|
73
|
+
def availiable? date
|
74
|
+
end
|
75
|
+
|
76
|
+
def weesked_key(day)
|
77
|
+
raise NilDay unless day
|
78
|
+
if id.nil?
|
79
|
+
raise NilObjectId,
|
80
|
+
"Weesked schedule on class #{self.class.name} with nil id (unsaved record?) [object_id=#{object_id}]"
|
81
|
+
end
|
82
|
+
day = Day.new(day).day
|
83
|
+
"weesked:#{self.class.name.downcase}:#{id}:#{day}"
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def update_schedule_for_class
|
89
|
+
sch = schedule
|
90
|
+
redis.multi do
|
91
|
+
Weesked.availiable_days.each do |day|
|
92
|
+
Weesked.availiable_steps.each do |step|
|
93
|
+
if sch[day.to_sym].include?(step)
|
94
|
+
redis.sadd self.class.weesked_schedule_key(day, step), id
|
95
|
+
else
|
96
|
+
redis.srem self.class.weesked_schedule_key(day, step), id
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def update_schedule_for_instance hash
|
104
|
+
redis.multi do
|
105
|
+
Weesked.availiable_days.each do |d|
|
106
|
+
redis.del weesked_key(d)
|
107
|
+
steps = hash.fetch d.to_sym
|
108
|
+
day = Day.new d, steps
|
109
|
+
redis.sadd(weesked_key(d), day.steps) if day.steps.any?
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_schedule
|
115
|
+
Weesked.availiable_days.each_with_object(Hash.new) do |day, h|
|
116
|
+
h[day.to_sym] = redis.smembers(weesked_key(day)).map(&:to_i)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/weesked.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require File.expand_path('../weesked/configuration', __FILE__)
|
2
|
+
require File.expand_path('../weesked/schedule', __FILE__)
|
3
|
+
require File.expand_path('../weesked/day', __FILE__)
|
4
|
+
require File.expand_path('../weesked/day_builder', __FILE__)
|
5
|
+
require File.expand_path('../weesked/availiability', __FILE__)
|
6
|
+
require File.expand_path('../weesked/offset_handler', __FILE__)
|
7
|
+
|
8
|
+
module Weesked
|
9
|
+
extend Configuration
|
10
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'weesked'
|
2
|
+
require 'minitest'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest/spec'
|
5
|
+
require 'fakeredis'
|
6
|
+
|
7
|
+
|
8
|
+
MONDAY_SUNDAY_12_14 = {
|
9
|
+
sunday: [ '12', '13', '14' ],
|
10
|
+
monday: [ '12', '13', '14' ],
|
11
|
+
tuesday: [ '12', '13', '14' ],
|
12
|
+
wednesday: [ '12', '13', '14' ],
|
13
|
+
thursday: [ '12', '13', '14' ],
|
14
|
+
friday: [ '12', '13', '14' ],
|
15
|
+
saturday: [ '12', '13', '14' ],
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
EMPTY_AVAIL = {
|
19
|
+
monday: [ ],
|
20
|
+
tuesday: [ ],
|
21
|
+
wednesday: [ ],
|
22
|
+
thursday: [ ],
|
23
|
+
friday: [ ],
|
24
|
+
saturday: [ ],
|
25
|
+
sunday: [ ]
|
26
|
+
}
|
27
|
+
|
28
|
+
MONDAY_TUESDAY_12_14 = {
|
29
|
+
monday: [ '12', '13', '14' ],
|
30
|
+
tuesday: [ '12', '13', '14' ],
|
31
|
+
wednesday: [ ],
|
32
|
+
thursday: [ ],
|
33
|
+
friday: [ ],
|
34
|
+
saturday: [ ],
|
35
|
+
sunday: [ ]
|
36
|
+
}
|
37
|
+
|
38
|
+
TUESDAY_12_14 = {
|
39
|
+
monday: [ ],
|
40
|
+
tuesday: [ '12', '13', '14' ],
|
41
|
+
wednesday: [ ],
|
42
|
+
thursday: [ ],
|
43
|
+
friday: [ ],
|
44
|
+
saturday: [ ],
|
45
|
+
sunday: [ ]
|
46
|
+
}
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
class Myclass
|
4
|
+
attr_accessor :id
|
5
|
+
include Weesked::Schedule
|
6
|
+
include Weesked::Availiability
|
7
|
+
def initialize(id) @id=id; end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Weesked
|
11
|
+
describe Availiability do
|
12
|
+
|
13
|
+
let(:sitter1) { Myclass.new 13}
|
14
|
+
let(:sitter2) { Myclass.new 18}
|
15
|
+
|
16
|
+
let(:s1) {
|
17
|
+
MONDAY_TUESDAY_12_14.dup
|
18
|
+
}
|
19
|
+
|
20
|
+
let(:s2) {
|
21
|
+
TUESDAY_12_14.dup
|
22
|
+
}
|
23
|
+
|
24
|
+
let(:empty) {
|
25
|
+
EMPTY_AVAIL.dup
|
26
|
+
}
|
27
|
+
|
28
|
+
subject { Myclass.availiability(date_range) }
|
29
|
+
let(:start_at) { 11 }
|
30
|
+
let(:end_at) { 15 }
|
31
|
+
let(:monday_range) { Time.local(2020, 'jan', 6, start_at)..Time.local(2020, 'jan', 6, end_at, 34) }
|
32
|
+
let(:tuesday_range) { Time.local(2020, 'jan', 7, start_at)..Time.local(2020, 'jan', 7, end_at, 34) }
|
33
|
+
let(:date_range) { monday_range }
|
34
|
+
|
35
|
+
before do
|
36
|
+
Myclass.reset_schedule
|
37
|
+
sitter1.schedule = s1
|
38
|
+
sitter2.schedule = s2
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '.availibility' do
|
42
|
+
|
43
|
+
describe 'single sitter' do
|
44
|
+
|
45
|
+
describe 'no intersection' do
|
46
|
+
let(:start_at) { 15 }
|
47
|
+
let(:end_at) { 18 }
|
48
|
+
it 'empty array' do
|
49
|
+
subject.must_equal Set.new
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'partial intersection' do
|
54
|
+
let(:start_at) { 14 }
|
55
|
+
let(:end_at) { 15 }
|
56
|
+
it 'empty array' do
|
57
|
+
subject.must_equal Set.new
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'date_range broader than availibility' do
|
62
|
+
let(:start_at) { 10 }
|
63
|
+
let(:end_at) { 22 }
|
64
|
+
it 'empty array' do
|
65
|
+
subject.must_equal Set.new
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'exact intersection' do
|
70
|
+
let(:start_at) { 12 }
|
71
|
+
let(:end_at) { 14 }
|
72
|
+
it 'sitter id' do
|
73
|
+
subject.must_equal [ sitter1.id.to_s ]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'date_range narrower than availibility' do
|
78
|
+
let(:start_at) { 12 }
|
79
|
+
let(:end_at) { 13 }
|
80
|
+
it 'sitter id' do
|
81
|
+
subject.must_equal [ sitter1.id.to_s ]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'multiple sitters' do
|
88
|
+
|
89
|
+
let(:date_range) { tuesday_range }
|
90
|
+
|
91
|
+
describe 'no intersection' do
|
92
|
+
let(:start_at) { 15 }
|
93
|
+
let(:end_at) { 18 }
|
94
|
+
it 'empty array' do
|
95
|
+
subject.must_equal Set.new
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe 'partial intersection' do
|
100
|
+
let(:start_at) { 14 }
|
101
|
+
let(:end_at) { 15 }
|
102
|
+
it 'empty array' do
|
103
|
+
subject.must_equal Set.new
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe 'date_range broader than availibility' do
|
108
|
+
let(:start_at) { 10 }
|
109
|
+
let(:end_at) { 22 }
|
110
|
+
it 'empty array' do
|
111
|
+
subject.must_equal Set.new
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'exact intersection' do
|
116
|
+
let(:start_at) { 12 }
|
117
|
+
let(:end_at) { 14 }
|
118
|
+
it 'sitter id' do
|
119
|
+
subject.must_equal [ sitter1.id.to_s, sitter2.id.to_s ]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'date_range narrower than availibility' do
|
124
|
+
let(:start_at) { 12 }
|
125
|
+
let(:end_at) { 13 }
|
126
|
+
it 'sitter id' do
|
127
|
+
subject.must_equal [ sitter1.id.to_s, sitter2.id.to_s ]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '.availiable?' do
|
134
|
+
subject { sitter1.availiable?(date_range) }
|
135
|
+
|
136
|
+
describe 'for availiable' do
|
137
|
+
let(:start_at) { 12 }
|
138
|
+
let(:end_at) { 13 }
|
139
|
+
it 'true' do
|
140
|
+
subject.must_equal true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'for unavailiable' do
|
145
|
+
let(:start_at) { 18 }
|
146
|
+
let(:end_at) { 19 }
|
147
|
+
it 'false' do
|
148
|
+
subject.must_equal false
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
module Weesked
|
4
|
+
describe Configuration do
|
5
|
+
Configuration::VALID_OPTIONS_KEYS.each do |key|
|
6
|
+
describe ".#{key}" do
|
7
|
+
it 'returns default value' do
|
8
|
+
Weesked.send(key).must_equal Configuration.const_get("DEFAULT_#{key.upcase}")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
module Weesked
|
4
|
+
describe DayBuilder do
|
5
|
+
|
6
|
+
|
7
|
+
describe '#run' do
|
8
|
+
describe 'converts single datetime to Day' do
|
9
|
+
|
10
|
+
|
11
|
+
let(:dates) {
|
12
|
+
{
|
13
|
+
sunday: Time.local(2020, 'jan', 5, hour, 17),
|
14
|
+
monday: Time.local(2020, 'jan', 6, hour, 12),
|
15
|
+
wednesday: Time.local(2020, 'jan', 8, hour, 13),
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
describe 'with day hours' do
|
20
|
+
let(:hour) { 14 }
|
21
|
+
|
22
|
+
it 'returns correct day' do
|
23
|
+
dates.each_pair do |day, date|
|
24
|
+
subject = DayBuilder.new(date).run
|
25
|
+
date.wday.must_equal Weesked.availiable_days.index(day.to_s)
|
26
|
+
subject.must_be_instance_of Day
|
27
|
+
subject.day.must_equal day
|
28
|
+
subject.steps.must_equal [ hour ]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'handles unavailiable times' do
|
35
|
+
let(:hour) { 2 }
|
36
|
+
it "works" do
|
37
|
+
Weesked.availiable_steps = [0,1]
|
38
|
+
dates.each_value do |date|
|
39
|
+
-> { DayBuilder.new(date).run }.must_raise NotAvailiable
|
40
|
+
end
|
41
|
+
Weesked.reset
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'converts date range to window' do
|
47
|
+
|
48
|
+
describe 'with day hours' do
|
49
|
+
let(:monday_day_range) { Time.local(2020, 'jan', 6, start_at)..Time.local(2020, 'jan', 6, end_at, 34) }
|
50
|
+
let(:start_at) { 11 }
|
51
|
+
let(:end_at) { 15 }
|
52
|
+
subject { DayBuilder.new(monday_day_range).run }
|
53
|
+
it 'works for monday' do
|
54
|
+
subject.day.must_equal :monday
|
55
|
+
subject.steps.must_equal (start_at..end_at).to_a
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'with night hours' do
|
60
|
+
let(:monday_night_range) { Time.local(2020, 'jan', 6, start_at, 12)..Time.local(2020, 'jan', 7, end_at, 34) }
|
61
|
+
let(:start_at) { 22 }
|
62
|
+
let(:end_at) { 1 }
|
63
|
+
subject { DayBuilder.new(monday_night_range).run }
|
64
|
+
before do
|
65
|
+
Weesked.steps_day_shift = 2
|
66
|
+
end
|
67
|
+
it 'works for night' do
|
68
|
+
subject.day.must_equal :monday
|
69
|
+
subject.steps.must_equal [0, 1, 22, 23]
|
70
|
+
end
|
71
|
+
after do
|
72
|
+
Weesked.reset
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'with unavailiable hours' do
|
77
|
+
let(:monday_night_range) { Time.local(2020, 'jan', 6, start_at, 12)..Time.local(2020, 'jan', 7, end_at, 34) }
|
78
|
+
let(:start_at) { 1 }
|
79
|
+
let(:end_at) { 4 }
|
80
|
+
subject { DayBuilder.new(monday_night_range).run }
|
81
|
+
it 'works for night' do
|
82
|
+
-> { subject }.must_raise NotAvailiable
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'with too long range' do
|
87
|
+
let(:monday_night_range) { Time.local(2020, 'jan', 6, start_at, 12)..Time.local(2020, 'jan', 10, end_at, 34) }
|
88
|
+
let(:start_at) { 22 }
|
89
|
+
let(:end_at) { 1 }
|
90
|
+
subject { DayBuilder.new(monday_night_range).run }
|
91
|
+
it 'works for night' do
|
92
|
+
-> { subject }.must_raise NotAvailiable
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
module Weesked
|
4
|
+
describe Day do
|
5
|
+
|
6
|
+
subject { Day.new day, steps}
|
7
|
+
let(:steps) { [18, 19] }
|
8
|
+
let(:day) { :monday }
|
9
|
+
|
10
|
+
describe '.steps' do
|
11
|
+
|
12
|
+
describe 'can be initialized with' do
|
13
|
+
describe 'single int' do
|
14
|
+
let(:steps) { 22 }
|
15
|
+
it 'works' do
|
16
|
+
subject.steps.must_equal [22]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe'single literal' do
|
21
|
+
let(:steps) { '22' }
|
22
|
+
it 'works' do
|
23
|
+
subject.steps.must_equal [22]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe'array of ints' do
|
28
|
+
let(:steps) { [22, 23] }
|
29
|
+
it 'works' do
|
30
|
+
subject.steps.must_equal [22, 23]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe'array of literals' do
|
35
|
+
let(:steps) { ['22', '23'] }
|
36
|
+
it 'works' do
|
37
|
+
subject.steps.must_equal [22, 23]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'unavailiable steps' do
|
43
|
+
let(:steps) { [22, 23, 24, 25, 26] }
|
44
|
+
it 'skips' do
|
45
|
+
subject.steps.must_equal [22, 23]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '.day' do
|
52
|
+
|
53
|
+
describe 'can be initialized with' do
|
54
|
+
describe 'single int' do
|
55
|
+
let(:day) { 1 }
|
56
|
+
it 'works' do
|
57
|
+
subject.day.must_equal :monday
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'day name as string' do
|
62
|
+
let(:day) { 'monday' }
|
63
|
+
it 'works' do
|
64
|
+
subject.day.must_equal :monday
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'day name as symbol' do
|
69
|
+
let(:day) { :monday }
|
70
|
+
it 'works' do
|
71
|
+
subject.day.must_equal :monday
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'unavailiable day' do
|
77
|
+
let(:day) { :monday123 }
|
78
|
+
it 'raises' do
|
79
|
+
-> { subject }.must_raise WrongDay
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
module Weesked
|
4
|
+
describe OffsetHandler do
|
5
|
+
|
6
|
+
let(:input) { [0, 1, 13, 14, 15, 21, 22, 23] }
|
7
|
+
let(:input2) { [1, 2, 3, 13, 14, 15, 21, 22, 23] }
|
8
|
+
let(:offset) { 2 }
|
9
|
+
let(:offset2) { 3 }
|
10
|
+
|
11
|
+
describe '#offset' do
|
12
|
+
let(:output) { [13, 14, 15, 21, 22, 23, 0, 1] }
|
13
|
+
let(:output2) { [13, 14, 15, 21, 22, 23, 1, 2, 3] }
|
14
|
+
subject { OffsetHandler.new(input, offset).to_a }
|
15
|
+
it 'returns same array if input empty or offset in zero' do
|
16
|
+
OffsetHandler.new.to_a.must_equal []
|
17
|
+
OffsetHandler.new(input).to_a.must_equal input
|
18
|
+
end
|
19
|
+
it 'returns array with offset' do
|
20
|
+
subject.must_equal output
|
21
|
+
end
|
22
|
+
it 'returns array with offset' do
|
23
|
+
OffsetHandler.new(input2, offset2).to_a.must_equal output2
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#to_range' do
|
28
|
+
let(:output) { [13..15, 21..1] }
|
29
|
+
let(:output2) { [13..15, 21..23, 1..3] }
|
30
|
+
subject { OffsetHandler.new(input, offset).to_range }
|
31
|
+
it 'returns array with ranges' do
|
32
|
+
subject.must_equal output
|
33
|
+
end
|
34
|
+
it 'returns array with ranges' do
|
35
|
+
(OffsetHandler.new(input2, offset2).to_range).must_equal output2
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
class MyClass
|
4
|
+
include Weesked::Schedule
|
5
|
+
def id
|
6
|
+
1
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Weesked
|
11
|
+
describe Schedule do
|
12
|
+
|
13
|
+
before do
|
14
|
+
MyClass.redis = Redis.new
|
15
|
+
end
|
16
|
+
|
17
|
+
subject { MyClass.new }
|
18
|
+
|
19
|
+
let(:availiability) {
|
20
|
+
MONDAY_SUNDAY_12_14.dup
|
21
|
+
}
|
22
|
+
|
23
|
+
let(:availiability_int) {
|
24
|
+
availiability.each_with_object(Hash.new) do |k, h|
|
25
|
+
h[k.first] = Array(k.last).map(&:to_i).sort.reverse
|
26
|
+
end
|
27
|
+
}
|
28
|
+
|
29
|
+
it '#redis' do
|
30
|
+
MyClass.redis = '123'
|
31
|
+
subject.redis.must_equal '123'
|
32
|
+
end
|
33
|
+
|
34
|
+
it '#weesked_schedule_key' do
|
35
|
+
MyClass.weesked_schedule_key(:monday, 10).must_equal 'weesked:availiability:myclass:monday:10'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'has key' do
|
39
|
+
subject.weesked_key(:monday).must_equal 'weesked:myclass:1:monday'
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '.schedule=' do
|
43
|
+
|
44
|
+
describe 'with date hash' do
|
45
|
+
|
46
|
+
it 'saves to redis with strings' do
|
47
|
+
subject.schedule = availiability
|
48
|
+
Redis.current.smembers(subject.weesked_key(:monday)).sort.must_equal availiability[:monday].map(&:to_s)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'saves to redis with empty array' do
|
52
|
+
availiability[:monday] = []
|
53
|
+
subject.schedule = availiability
|
54
|
+
Redis.current.smembers(subject.weesked_key(:monday)).sort.must_equal availiability[:monday].map(&:to_s)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'clears before save' do
|
58
|
+
subject.schedule = availiability
|
59
|
+
availiability[:monday] = [10]
|
60
|
+
subject.schedule = availiability
|
61
|
+
Redis.current.smembers(subject.weesked_key(:monday)).sort.must_equal availiability[:monday].map(&:to_s)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'handles empty string' do
|
65
|
+
availiability[:monday] = ''
|
66
|
+
subject.schedule = availiability
|
67
|
+
Redis.current.smembers(subject.weesked_key(:monday)).sort.must_equal []
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'saves to redis with ints' do
|
71
|
+
subject.schedule = availiability_int
|
72
|
+
Redis.current.smembers(subject.weesked_key(:monday)).sort.must_equal availiability[:monday].map(&:to_s)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '.schedule' do
|
78
|
+
|
79
|
+
it 'gets hash' do
|
80
|
+
subject.schedule = availiability
|
81
|
+
subject.schedule.must_equal availiability_int
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
data/weesked.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'weesked/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "weesked"
|
8
|
+
spec.version = Weesked::VERSION
|
9
|
+
spec.authors = ["Igor Davydov"]
|
10
|
+
spec.email = ["iskiche@gmail.com"]
|
11
|
+
spec.summary = %q{Simple availialibility schedlule based on Redis lists}
|
12
|
+
spec.description = %q{Each time step has its redis list of booked objects}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5"
|
24
|
+
spec.add_development_dependency "fakeredis", "~> 0.3"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: weesked
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Igor Davydov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: fakeredis
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.3'
|
69
|
+
description: Each time step has its redis list of booked objects
|
70
|
+
email:
|
71
|
+
- iskiche@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".travis.yml"
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- lib/weesked.rb
|
83
|
+
- lib/weesked/availiability.rb
|
84
|
+
- lib/weesked/configuration.rb
|
85
|
+
- lib/weesked/day.rb
|
86
|
+
- lib/weesked/day_builder.rb
|
87
|
+
- lib/weesked/offset_handler.rb
|
88
|
+
- lib/weesked/schedule.rb
|
89
|
+
- lib/weesked/version.rb
|
90
|
+
- test/test_helper.rb
|
91
|
+
- test/weesked/availiability_test.rb
|
92
|
+
- test/weesked/configuration_test.rb
|
93
|
+
- test/weesked/day_bulder_test.rb
|
94
|
+
- test/weesked/day_test.rb
|
95
|
+
- test/weesked/offset_handler_spec.rb
|
96
|
+
- test/weesked/schedule_test.rb
|
97
|
+
- test/weesked_test.rb
|
98
|
+
- weesked.gemspec
|
99
|
+
homepage: ''
|
100
|
+
licenses:
|
101
|
+
- MIT
|
102
|
+
metadata: {}
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 2.4.4
|
120
|
+
signing_key:
|
121
|
+
specification_version: 4
|
122
|
+
summary: Simple availialibility schedlule based on Redis lists
|
123
|
+
test_files:
|
124
|
+
- test/test_helper.rb
|
125
|
+
- test/weesked/availiability_test.rb
|
126
|
+
- test/weesked/configuration_test.rb
|
127
|
+
- test/weesked/day_bulder_test.rb
|
128
|
+
- test/weesked/day_test.rb
|
129
|
+
- test/weesked/offset_handler_spec.rb
|
130
|
+
- test/weesked/schedule_test.rb
|
131
|
+
- test/weesked_test.rb
|