time_pilot 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 +2 -0
- data/.travis.yml +6 -0
- data/AUTHORS +2 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +102 -0
- data/Rakefile +9 -0
- data/lib/time_pilot/configuration.rb +20 -0
- data/lib/time_pilot/time_pilot.rb +67 -0
- data/lib/time_pilot.rb +3 -0
- data/test/time_pilot_test.rb +114 -0
- data/time_pilot.gemspec +19 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e30816cd185929b8db4677c3eeba8d1200f4acdf
|
4
|
+
data.tar.gz: 7885bfb2cddd2862470aa34d38df4ed0179793ec
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b67e44321f7a86052d17c0bb73ca1a43eaa435b88b8c6bda7cef90f215efa627f9defbd6febdf27f705f0221730c45906c411569d29948b0fc2cf28d12f112d1
|
7
|
+
data.tar.gz: 9924dcfc8baf230d40fa53b5935d5f635bfb9e73b3c451ec9636726afa4d01acffc8d070a5011c72a328625c9d351237dc43c93cbec2d6b16bae0e1c1329e9d4
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/AUTHORS
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Matthijs Langenberg and Mark Oude Veldhuis
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# TimePilot  
|
2
|
+
|
3
|
+
TimePilot is a RubyGem that makes it possible to define features that can be enabled for a certain group of users. It requires very little configuration, and is designed to work stand-alone on any object. TimePilot uses Redis for storage.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'time_pilot'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install time_pilot
|
18
|
+
|
19
|
+
# Configuration
|
20
|
+
|
21
|
+
The features that you want to specify need to be added in an initializer as such:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# config/initializers/time_pilot.rb
|
25
|
+
TimePilot.configure do |c|
|
26
|
+
c.feature :planning
|
27
|
+
c.feature :private_messaging
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
Using this configuration, the `TimePilot::Features` module is enriched with three methods per feature at boot time: `{feature}_enabled?`, `enable_{feature}` and `disable_{feature}`. In every model that you would want to invoke these methods, i.e. check if a feature is enabled or either enable or disable it for that group you need to `include TimePilot::Features`.
|
32
|
+
|
33
|
+
After including `TimePilot::Features` add: `is_pilot_group`. You can specify an `overridden_by: [:foo, :bar, ...]` to specify any relations that override the setting for the object itself. See the example below for `Team` and `User`.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
# app/model/organization.rb
|
37
|
+
class Organization
|
38
|
+
attr_accessor :id
|
39
|
+
|
40
|
+
include TimePilot::Features
|
41
|
+
is_pilot_group
|
42
|
+
end
|
43
|
+
|
44
|
+
# app/model/team.rb
|
45
|
+
class Team
|
46
|
+
attr_accessor :organization_id, :id
|
47
|
+
|
48
|
+
include TimePilot::Features
|
49
|
+
is_pilot_group overridden_by: :organization
|
50
|
+
end
|
51
|
+
|
52
|
+
# app/model/user.rb
|
53
|
+
class User
|
54
|
+
attr_accessor :organization_id, :team_id, :id
|
55
|
+
|
56
|
+
include TimePilot::Features
|
57
|
+
is_pilot_group overridden_by: [:organization, :team]
|
58
|
+
end
|
59
|
+
|
60
|
+
# app/model/person.rb
|
61
|
+
class Person
|
62
|
+
attr_accessor :id
|
63
|
+
|
64
|
+
include TimePilot::Features
|
65
|
+
is_pilot_group
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
When you invoke `user.planning_enabled?`, TimePilot fill first check if the feature has been enabled for the organization with the ID obtained from `user.organization_id`. If that evaluates to `false`, TimePilot continues checking the feature, but now using `user.team_id`. Then, if that fails too, it checks whether the feature is enabled for the user itself.
|
70
|
+
|
71
|
+
## Configuring Redis
|
72
|
+
|
73
|
+
TimePilot uses `localhost:6379` by default for Redis. To connect to a Redis instance on a different port or host, provide a Redis client in `TimePilot.configure` block.
|
74
|
+
|
75
|
+
```
|
76
|
+
# config/initializers/time_pilot.rb
|
77
|
+
TimePilot.configure do |c|
|
78
|
+
c.redis Redis.new(...)
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
TimePilot assumes you put in an object that responds to `#sadd`, `#srem` and `#sismember`.
|
83
|
+
|
84
|
+
# Usage
|
85
|
+
|
86
|
+
The configuration above allows you to invoke the following methods on a `Organization`, `Team`, `User` and `Person`:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
# Methods for feature 'planning'
|
90
|
+
user.planning_enabled?
|
91
|
+
user.enable_planning
|
92
|
+
user.disable_planning
|
93
|
+
|
94
|
+
# Methods for feature 'private_messaging'
|
95
|
+
user.private_messaging_enabled?
|
96
|
+
user.enable_private_messaging
|
97
|
+
user.disable_private_messaging
|
98
|
+
|
99
|
+
# The same methods can be invoked on an instance of Organization,
|
100
|
+
# Team and Person provided the example above, since they all include
|
101
|
+
# the `Features` module.
|
102
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module TimePilot
|
2
|
+
|
3
|
+
class Configuration
|
4
|
+
attr_reader :features, :redis_store
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@features = []
|
8
|
+
@redis_store = Redis.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def feature feature_name
|
12
|
+
@features.push(feature_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def redis redis_store
|
16
|
+
@redis_store = redis_store
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module TimePilot
|
2
|
+
NAMESPACE = 'timepilot'
|
3
|
+
|
4
|
+
def self.configure
|
5
|
+
@config = Configuration.new
|
6
|
+
yield @config
|
7
|
+
@config.features.each do |feature_name|
|
8
|
+
Features.module_eval do
|
9
|
+
define_method "enable_#{feature_name}" do
|
10
|
+
pilot_enable_feature(feature_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
define_method "disable_#{feature_name}" do
|
14
|
+
pilot_disable_feature(feature_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
define_method "#{feature_name}_enabled?" do
|
18
|
+
pilot_feature_enabled?(feature_name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.features
|
25
|
+
@config.features
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.redis
|
29
|
+
@config.redis_store
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.key(name)
|
33
|
+
"#{NAMESPACE}:#{name}"
|
34
|
+
end
|
35
|
+
|
36
|
+
module Features
|
37
|
+
def self.included(base)
|
38
|
+
base.send :extend, ClassMethods
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClassMethods
|
42
|
+
attr_reader :time_pilot_groups
|
43
|
+
def is_pilot_group options={}
|
44
|
+
@time_pilot_groups = Array(options[:overridden_by]).map { |e| e.to_s } + [self.name.downcase.to_s]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def pilot_enable_feature(feature_name)
|
49
|
+
TimePilot.redis.sadd TimePilot.key("#{feature_name}:#{self.class.to_s.downcase}_ids"), id
|
50
|
+
end
|
51
|
+
|
52
|
+
def pilot_disable_feature(feature_name)
|
53
|
+
TimePilot.redis.srem TimePilot.key("#{feature_name}:#{self.class.to_s.downcase}_ids"), id
|
54
|
+
end
|
55
|
+
|
56
|
+
def pilot_feature_enabled?(feature_name)
|
57
|
+
TimePilot.redis.pipelined {
|
58
|
+
self.class.time_pilot_groups.each do |group|
|
59
|
+
method = group.to_s == self.class.to_s.downcase ? 'id' : group + '_id'
|
60
|
+
TimePilot.redis.sismember TimePilot.key("#{feature_name}:#{group}_ids"), send(method)
|
61
|
+
end
|
62
|
+
}.include? true
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
data/lib/time_pilot.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require_relative '../lib/time_pilot'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
class Company
|
5
|
+
include TimePilot::Features
|
6
|
+
is_pilot_group
|
7
|
+
attr_reader :id
|
8
|
+
def initialize(id)
|
9
|
+
@id = id
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Team
|
14
|
+
include TimePilot::Features
|
15
|
+
is_pilot_group overridden_by: :company
|
16
|
+
attr_reader :company_id, :id
|
17
|
+
def initialize(company_id, id)
|
18
|
+
@company_id = company_id
|
19
|
+
@id = id
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Employee
|
24
|
+
include TimePilot::Features
|
25
|
+
is_pilot_group overridden_by: [:company, :team]
|
26
|
+
attr_reader :company_id, :team_id, :id
|
27
|
+
def initialize(company_id, team_id, id)
|
28
|
+
@company_id = company_id
|
29
|
+
@team_id = team_id
|
30
|
+
@id = id
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
TimePilot.configure do |c|
|
35
|
+
c.feature 'planning'
|
36
|
+
end
|
37
|
+
|
38
|
+
describe TimePilot do
|
39
|
+
before do
|
40
|
+
TimePilot.redis.flushdb
|
41
|
+
@acme = Company.new(1)
|
42
|
+
@nedap = Company.new(2)
|
43
|
+
@healthcare = Team.new(@nedap.id, 11)
|
44
|
+
@retail = Team.new(@nedap.id, 12)
|
45
|
+
@john = Employee.new(@nedap.id, @healthcare.id, 21)
|
46
|
+
@jane = Employee.new(@nedap.id, @healthcare.id, 22)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'defines a getter on company' do
|
50
|
+
TimePilot.redis.sadd 'timepilot:planning:company_ids', @acme.id
|
51
|
+
@acme.planning_enabled?.must_equal true
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'defines an enabler on company' do
|
55
|
+
@nedap.enable_planning
|
56
|
+
TimePilot.redis.sismember("timepilot:planning:company_ids", @nedap.id).must_equal true
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'defines a disabler on employee' do
|
60
|
+
@nedap.enable_planning
|
61
|
+
@nedap.planning_enabled?.must_equal true
|
62
|
+
@nedap.disable_planning
|
63
|
+
@nedap.planning_enabled?.must_equal false
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'defines a getter on team' do
|
67
|
+
TimePilot.redis.sadd 'timepilot:planning:team_ids', @healthcare.id
|
68
|
+
@healthcare.planning_enabled?.must_equal true
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'defines an enabler on team' do
|
72
|
+
@retail.enable_planning
|
73
|
+
TimePilot.redis.sismember("timepilot:planning:team_ids", @retail.id).must_equal true
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'defines a disabler on team' do
|
77
|
+
@retail.enable_planning
|
78
|
+
@retail.planning_enabled?.must_equal true
|
79
|
+
@retail.disable_planning
|
80
|
+
@retail.planning_enabled?.must_equal false
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'defines a getter on employee' do
|
84
|
+
TimePilot.redis.sadd 'timepilot:planning:employee_ids', @john.id
|
85
|
+
@john.planning_enabled?.must_equal true
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'defines an enabler on employee' do
|
89
|
+
@jane.enable_planning
|
90
|
+
TimePilot.redis.sismember("timepilot:planning:employee_ids", @jane.id).must_equal true
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'defines a disabler on employee' do
|
94
|
+
@jane.enable_planning
|
95
|
+
@jane.planning_enabled?.must_equal true
|
96
|
+
@jane.disable_planning
|
97
|
+
@jane.planning_enabled?.must_equal false
|
98
|
+
end
|
99
|
+
|
100
|
+
specify 'company overrides team' do
|
101
|
+
@nedap.enable_planning
|
102
|
+
@retail.planning_enabled?.must_equal true
|
103
|
+
end
|
104
|
+
|
105
|
+
specify 'team overrides employee' do
|
106
|
+
@healthcare.enable_planning
|
107
|
+
@john.planning_enabled?.must_equal true
|
108
|
+
end
|
109
|
+
|
110
|
+
specify 'company overrides employee' do
|
111
|
+
@nedap.enable_planning
|
112
|
+
@jane.planning_enabled?.must_equal true
|
113
|
+
end
|
114
|
+
end
|
data/time_pilot.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'time_pilot'
|
3
|
+
s.summary = 'Configure enabled features for a specific set of users.'
|
4
|
+
s.description = ''
|
5
|
+
s.version = '0.0.2'
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.license = 'MIT'
|
8
|
+
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
s.test_files = `git ls-files -- {test}/*`.split("\n")
|
11
|
+
s.require_path = 'lib'
|
12
|
+
|
13
|
+
s.authors = ['@mlangenberg', '@markoudev']
|
14
|
+
s.email = ['matthijs.langenberg@nedap.com', 'mark.oudeveldhuis@nedap.com']
|
15
|
+
s.homepage = 'https://github.com/nedap/time_pilot'
|
16
|
+
|
17
|
+
s.add_dependency 'redis', '>= 3.0.0'
|
18
|
+
s.add_development_dependency "minitest"
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: time_pilot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "@mlangenberg"
|
8
|
+
- "@markoudev"
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-06-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: redis
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 3.0.0
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 3.0.0
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: minitest
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
description: ''
|
43
|
+
email:
|
44
|
+
- matthijs.langenberg@nedap.com
|
45
|
+
- mark.oudeveldhuis@nedap.com
|
46
|
+
executables: []
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- ".gitignore"
|
51
|
+
- ".travis.yml"
|
52
|
+
- AUTHORS
|
53
|
+
- Gemfile
|
54
|
+
- LICENSE
|
55
|
+
- README.md
|
56
|
+
- Rakefile
|
57
|
+
- lib/time_pilot.rb
|
58
|
+
- lib/time_pilot/configuration.rb
|
59
|
+
- lib/time_pilot/time_pilot.rb
|
60
|
+
- test/time_pilot_test.rb
|
61
|
+
- time_pilot.gemspec
|
62
|
+
homepage: https://github.com/nedap/time_pilot
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata: {}
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 2.2.2
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: Configure enabled features for a specific set of users.
|
86
|
+
test_files: []
|