capable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +147 -0
- data/Rakefile +55 -0
- data/capable.gemspec +32 -0
- data/lib/acts_as_ability.rb +28 -0
- data/lib/acts_as_capability.rb +61 -0
- data/lib/acts_as_capability_renewer.rb +39 -0
- data/lib/acts_as_capable.rb +65 -0
- data/lib/capable.rb +40 -0
- data/lib/capable/base.rb +11 -0
- data/lib/capable/configuration.rb +36 -0
- data/lib/capable/version.rb +3 -0
- data/lib/generators/capable/capable_generator.rb +31 -0
- data/lib/generators/capable/templates/ability.rb +5 -0
- data/lib/generators/capable/templates/capability.rb +5 -0
- data/lib/generators/capable/templates/migration.rb +33 -0
- data/spec/capable_spec.rb +112 -0
- data/spec/spec_helper.rb +113 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fa1fd9dbe475e8c9d2adee0ed641c67effba968c
|
4
|
+
data.tar.gz: 0129dfec50822cbb114a5790b72c4ec98e951bdb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c545e3aaba428c7500f5140aecb3483f2832d188560aaf418d8eedfce3519150a7d4025f679adbb9a9e72fb006cb3a58c490c979b2e3940890fdb5053f951e66
|
7
|
+
data.tar.gz: 700965da9ea69f247dc233d27d40a0e1a1e28f706e7629734e429bf2499bce3f77c7177c04bdcfbdc652c90c1ceb63645cecd3c996e13b043ccaf9991860439b
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 TODO: Write your name
|
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,147 @@
|
|
1
|
+
# Capable
|
2
|
+
|
3
|
+
This Gem works similar to UNIX group permissions. The server defines different 'abilities' that can then be applied to *capable* objects via a *capabilities* join table. The *capabilities* table also allows expiration dates to be defined and applies the concept of *renewer*'s where another object can renew the expiration date of the *capability*.
|
4
|
+
|
5
|
+
This scheme was originally developed to provide purchasable features in an APP via In App Purchase that would renew based on subscriptions. The *User* is the *capable* object and the *Receipt* is the renewer. If a receipt renews, it has the ability to update the *capabilities* for the *User*
|
6
|
+
|
7
|
+
One good feature about this class is if a user has a certain ability from 2 different sources, if one of them expires or is deleted, the user will still have the ability from the other source. For example
|
8
|
+
|
9
|
+
- User purchases [pro, gold] abilities. User is now [pro, gold]
|
10
|
+
- User purchases [sponsor, gold] abilities. User is now [sponsor, pro, gold]
|
11
|
+
- User expires [pro, gold] abilities. User is now [sponsor, gold]
|
12
|
+
|
13
|
+
This *ability* scheme can be applied to any object in the database by using the *acts_as_capable* method.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'capable'
|
21
|
+
```
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install capable
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
### Setup Database Schema
|
34
|
+
|
35
|
+
Run the following code to create the *Ability* and *Capability* models and migration file.
|
36
|
+
|
37
|
+
$ rails generate capable
|
38
|
+
|
39
|
+
In order to activate the local caching features of this Gem, you will need to add the following to your migration file for each class you are going to declare as *capable*.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
add_column :<capable class>, :ability_list, :string
|
43
|
+
```
|
44
|
+
|
45
|
+
**If you do not add the above attribute to all of your *capable* objects, the system will query for the abilities EVERY TIME you call *ability_array* on a *capable* object.** Once complete, migrate the database by running
|
46
|
+
|
47
|
+
$ rake db:migrate
|
48
|
+
|
49
|
+
### Configuration
|
50
|
+
|
51
|
+
To set different configuration options, create the file "config/initializers/capable.rb" with the following code (currently you can only turn on 'verbose')
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require 'capable'
|
55
|
+
|
56
|
+
Capable.configure do |config|
|
57
|
+
config.verbose = true
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
### Ability
|
62
|
+
|
63
|
+
The *Ability* class provides a way for the admin to create different abilities that can be assigned to *capable* objects. It has the following instance methods
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# This will assign an ability to a capable object via the capability class (the options in []'s are optional)
|
67
|
+
ability.assign_capable(capable, [active=true, expires_at=nil, renewer=nil])
|
68
|
+
```
|
69
|
+
|
70
|
+
### Capability
|
71
|
+
|
72
|
+
The *Capability* class is what ties the *abilities* to *capable* objects. It also has a *renewer* hook that provides a way for something to periodically renew the expiration date. Another cool feature of this class is that when updated, it will automatically re-initialize the *ability_list* of the *capable* object if it is defined. Here are it's methods
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
# Create a capability (the options in []'s are optional)
|
76
|
+
Capability.create_capability(capable, ability, [active=true, expires_at=nil, renewer=nil])
|
77
|
+
|
78
|
+
# Expires all capabilities that have an expiration date that is not nil and is older than the passed in date (sets active to false). This will automatically update the abilities of the corresponding capable object
|
79
|
+
Capability.expire_capabilities(expiration_date)
|
80
|
+
|
81
|
+
# Renew the capability
|
82
|
+
capability.renew(expires_at)
|
83
|
+
```
|
84
|
+
|
85
|
+
### Acts As Capable
|
86
|
+
|
87
|
+
For each class that you would like to assign *abilities* to, do the following in your class. For this example, we will use the class *User*.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
require 'capable'
|
91
|
+
|
92
|
+
class User < ActiveRecord::Base
|
93
|
+
acts_as_capable # Rails 4
|
94
|
+
# note for Rails <= 3.x, you need to use 'acts_as_capable_3x'
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
Also note from above that you will want to create a string called *ability_list* on the class to allow for local caching of abilities in the *capable* object. This is all handled automatically by the Gem when a capability is updated.
|
99
|
+
|
100
|
+
Once an object is declared as *capable* it allows the following methods to be defined
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
# See if the user has a certain ability. Note that 'ability' can either be an ability object OR the 'ability' string attribute of an ability object
|
104
|
+
user.has_ability?(ability)
|
105
|
+
|
106
|
+
# Return a comma delimited list of strings of the different abilities. This is useful for example when serializing the object over an API. The mobile client can simply work off of this array.
|
107
|
+
user.ability_array
|
108
|
+
|
109
|
+
# Assign an ability to a user (the options in []'s are optional)
|
110
|
+
user.assign_ability(ability, [active=true, expires_at=nil, renewer=nil])
|
111
|
+
|
112
|
+
# Unassign an ability from a user (just sets active to false. The options in []'s are optional)
|
113
|
+
user.unassign_ability(ability, [renewer=nil])
|
114
|
+
```
|
115
|
+
### Acts As Capability Renewer
|
116
|
+
|
117
|
+
A *capability renewer* is a class that can renew the expiration date of capabilities such as a subscription. To create these classes do the following
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
require 'capable'
|
121
|
+
|
122
|
+
class Renewer < ActiveRecord::Base
|
123
|
+
acts_as_capability_renewer
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
Once an object is declared as a capability renewer, it has the following instance methods available to it
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
# Create capabilities for a capable object with this as the renewer. This will only create the capabilities if capable is not nil, the abilities array has values, and the current count of the capabilities is 0
|
131
|
+
renewer.create_capabilities(capable, abilities, active, expires_at)
|
132
|
+
|
133
|
+
# Renew the expiration date for the capabilities of a renewer (note that this sets active attribute based on the current date so if it is in the past, active will be set to 'false')
|
134
|
+
renewer.renew_capabilities(expires_at)
|
135
|
+
```
|
136
|
+
|
137
|
+
The renewer concept allows a capability to be renewed when the expiration date is updated. This is good for example when a user purchases more time on a role.
|
138
|
+
|
139
|
+
## Contributing
|
140
|
+
|
141
|
+
1. Fork it ( https://github.com/[my-github-username]/capable/fork )
|
142
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
143
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
144
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
145
|
+
5. Create a new Pull Request
|
146
|
+
|
147
|
+
> Written with [StackEdit](https://stackedit.io/).
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler' unless defined?(Bundler)
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
6
|
+
require 'capable/version'
|
7
|
+
|
8
|
+
begin
|
9
|
+
Bundler.setup(:default, :development)
|
10
|
+
rescue Bundler::BundlerError => e
|
11
|
+
$stderr.puts e.message
|
12
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
13
|
+
exit e.status_code
|
14
|
+
end
|
15
|
+
require 'rake'
|
16
|
+
|
17
|
+
require 'rake/testtask'
|
18
|
+
Rake::TestTask.new(:test) do |test|
|
19
|
+
test.libs << 'lib' << 'test'
|
20
|
+
test.test_files = Dir.glob("test/**/*_test.rb")
|
21
|
+
test.verbose = true
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'rake'
|
25
|
+
require 'rspec/core/rake_task'
|
26
|
+
|
27
|
+
RSpec::Core::RakeTask.new(:spec) do |test|
|
28
|
+
test.pattern = Dir.glob('spec/**/*_spec.rb')
|
29
|
+
test.rspec_opts = '--format documentation'
|
30
|
+
# test.rspec_opts << ' more options'
|
31
|
+
# test.rcov = true
|
32
|
+
end
|
33
|
+
task :default => :spec
|
34
|
+
|
35
|
+
task :build do
|
36
|
+
system "gem build capable.gemspec"
|
37
|
+
end
|
38
|
+
|
39
|
+
task :release => :build do
|
40
|
+
system "gem push capable-#{Capable::VERSION}.gem"
|
41
|
+
system "rm capable-#{Capable::VERSION}.gem"
|
42
|
+
end
|
43
|
+
|
44
|
+
task :test_all_databases do
|
45
|
+
# Test MySQL, Postgres and SQLite3
|
46
|
+
# ENV['DB'] = 'mysql'
|
47
|
+
# Rake::Task['spec'].execute
|
48
|
+
# ENV['DB'] = 'postgres'
|
49
|
+
# Rake::Task['spec'].execute
|
50
|
+
ENV['DB'] = 'sqlite3'
|
51
|
+
Rake::Task['spec'].execute
|
52
|
+
end
|
53
|
+
|
54
|
+
task :default => :test_all_databases
|
55
|
+
|
data/capable.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'capable/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "capable"
|
8
|
+
spec.version = Capable::VERSION
|
9
|
+
spec.authors = ["Eric Chapman"]
|
10
|
+
spec.email = ["contact@ericjchapman.com"]
|
11
|
+
spec.summary = %q{Gem that will allow 'abilities' to be assigned to 'capable' objects that can be independantly expired and renewed.}
|
12
|
+
spec.description = %q{}
|
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.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency 'rspec'
|
24
|
+
if RUBY_VERSION < '1.9.3'
|
25
|
+
spec.add_runtime_dependency('activerecord', '< 4.0.0')
|
26
|
+
else
|
27
|
+
spec.add_runtime_dependency('activerecord')
|
28
|
+
end
|
29
|
+
spec.add_development_dependency('mysql2')
|
30
|
+
spec.add_development_dependency('pg')
|
31
|
+
spec.add_development_dependency('sqlite3')
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Capable
|
2
|
+
module ActsAsAbility
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend Capable::Base
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def acts_as_ability
|
12
|
+
has_many :capabilities, :dependent => :destroy
|
13
|
+
has_many :capables, :through => :capabilities
|
14
|
+
|
15
|
+
include Capable::ActsAsAbility::InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
module InstanceMethods
|
21
|
+
|
22
|
+
def assign_capable(capable, active=true, expires_at=nil, renewer=nil)
|
23
|
+
Capability.create_capability(capable, self, active, expires_at, renewer)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Capable
|
2
|
+
module ActsAsCapability
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend Capable::Base
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def acts_as_capability
|
12
|
+
belongs_to :ability
|
13
|
+
belongs_to :capable, :polymorphic => true
|
14
|
+
belongs_to :renewer, :polymorphic => true
|
15
|
+
|
16
|
+
after_create :update_capables
|
17
|
+
after_update :update_capables
|
18
|
+
|
19
|
+
include Capable::ActsAsCapability::InstanceMethods
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_capability(capable, ability, active=true, expires_at=nil, renewer=nil)
|
23
|
+
create(
|
24
|
+
capable: capable,
|
25
|
+
ability: ability,
|
26
|
+
active: active,
|
27
|
+
expires_at: expires_at,
|
28
|
+
renewer: renewer
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def expire_capabilities(expiration_date)
|
33
|
+
self.where('expires_at is not null and expires_at < ? and active = ?', expiration_date, true).find_each do |capability|
|
34
|
+
puts "CAPABILITY_EXPIRE: Capable #{capability.capable_type} (#{capability.capable_id}) expired the ability #{capability.ability.name}" if Capable.configuration[:verbose]
|
35
|
+
capability.active = false
|
36
|
+
capability.save!
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
module InstanceMethods
|
43
|
+
|
44
|
+
def update_capables
|
45
|
+
if self.capable.respond_to? :ability_list=
|
46
|
+
self.capable.ability_list = self.capable.abilities.pluck(:ability).uniq.join(",")
|
47
|
+
self.capable.save!
|
48
|
+
else
|
49
|
+
puts "CAPABILITY_ERROR: Accessor method 'ability_list' is not defined for the class '#{self.capable_type}'" if Capable.configuration[:verbose]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def renew(expires_at)
|
54
|
+
self.expires_at = expires_at
|
55
|
+
self.active = (expires_at.nil? or expires_at > Time.now)
|
56
|
+
self.save
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Capable
|
2
|
+
module ActsAsCapabilityRenewer
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend Capable::Base
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def acts_as_capability_renewer
|
12
|
+
has_many :capabilities, as: :renewer, :dependent => :destroy
|
13
|
+
|
14
|
+
include Capable::ActsAsCapabilityRenewer::InstanceMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods
|
20
|
+
|
21
|
+
def create_capabilities(capable, abilities, active, expires_at)
|
22
|
+
if capable.present? and abilities.present? and self.capabilities.count == 0
|
23
|
+
abilities.each do |ability|
|
24
|
+
Capability.create_capability(capable, ability, active, expires_at, self)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def renew_capabilities(expires_at)
|
30
|
+
self.capabilities.each do |capability|
|
31
|
+
capability.renew(expires_at)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Capable
|
2
|
+
module ActsAsCapable
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend Capable::Base
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def acts_as_capable_3x
|
12
|
+
has_many :capabilities, as: :capable, :dependent => :destroy
|
13
|
+
has_many :abilities, :through => :capabilities, :conditions => 'capabilities.active = true'
|
14
|
+
|
15
|
+
include Capable::ActsAsCapable::InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
def acts_as_capable
|
19
|
+
has_many :capabilities, as: :capable, :dependent => :destroy
|
20
|
+
has_many :abilities, -> { where(capabilities: { active: true }) }, :through => :capabilities
|
21
|
+
|
22
|
+
include Capable::ActsAsCapable::InstanceMethods
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
module InstanceMethods
|
28
|
+
|
29
|
+
def ability_array
|
30
|
+
if self.respond_to? :ability_list
|
31
|
+
if self.ability_list.present?
|
32
|
+
return self.ability_list.split(",")
|
33
|
+
else
|
34
|
+
return Array.new
|
35
|
+
end
|
36
|
+
else
|
37
|
+
puts "CAPABLE_WARNING: It is recommended to create the string 'ability_list' for the class '#{self.class.name}' in order to speed up ability checking" if Capable.configuration[:verbose]
|
38
|
+
return self.abilities.pluck(:ability).uniq
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def has_ability?(ability)
|
43
|
+
if ability.kind_of? String
|
44
|
+
return ability_array.include? ability
|
45
|
+
else
|
46
|
+
return ability_array.include? ability.ability
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def assign_ability(ability, active=true, expires_at=nil, renewer=nil)
|
51
|
+
Capability.create_capability(self, ability, active, expires_at, renewer)
|
52
|
+
end
|
53
|
+
|
54
|
+
def unassign_ability(ability, renewer=nil)
|
55
|
+
Capability.where(capable: self, ability: ability, renewer: renewer, active: true).each do |capability|
|
56
|
+
capability.active = false
|
57
|
+
capability.save # This will kickoff the update logic
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
data/lib/capable.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "acts_as_ability"
|
2
|
+
require "acts_as_capability"
|
3
|
+
require "acts_as_capable"
|
4
|
+
require "acts_as_capability_renewer"
|
5
|
+
require "capable/version"
|
6
|
+
require 'capable/configuration'
|
7
|
+
require 'capable/base'
|
8
|
+
require 'active_record'
|
9
|
+
|
10
|
+
module Capable
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
# An Capable::Configuration object. Must act like a hash and return sensible
|
15
|
+
# values for all Capable::Configuration::OPTIONS. See Capable::Configuration.
|
16
|
+
attr_writer :configuration
|
17
|
+
|
18
|
+
# Call this method to modify defaults in your initializers.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# Capable.configure do |config|
|
22
|
+
# config.<attr> = <value>
|
23
|
+
# end
|
24
|
+
def configure
|
25
|
+
yield(configuration)
|
26
|
+
end
|
27
|
+
|
28
|
+
# The configuration object.
|
29
|
+
# @see Capable::Configuration
|
30
|
+
def configuration
|
31
|
+
@configuration ||= Configuration.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
ActiveRecord::Base.send(:include, Capable::ActsAsAbility)
|
38
|
+
ActiveRecord::Base.send(:include, Capable::ActsAsCapability)
|
39
|
+
ActiveRecord::Base.send(:include, Capable::ActsAsCapable)
|
40
|
+
ActiveRecord::Base.send(:include, Capable::ActsAsCapabilityRenewer)
|
data/lib/capable/base.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Capable
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
OPTIONS = [:verbose].freeze
|
5
|
+
|
6
|
+
attr_accessor :verbose
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
# these defaults can be overridden in the Tokengen.config block
|
10
|
+
@verbose = false
|
11
|
+
end
|
12
|
+
|
13
|
+
# Allows config options to be read like a hash
|
14
|
+
#
|
15
|
+
# @param [Symbol] option Key for a given attribute
|
16
|
+
def [](option)
|
17
|
+
send(option)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a hash of all configurable options
|
21
|
+
def to_hash
|
22
|
+
OPTIONS.inject({}) do |hash, option|
|
23
|
+
hash[option.to_sym] = self.send(option)
|
24
|
+
hash
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a hash of all configurable options merged with +hash+
|
29
|
+
#
|
30
|
+
# @param [Hash] hash A set of configuration options that will take precedence over the defaults
|
31
|
+
def merge(hash)
|
32
|
+
to_hash.merge(hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
class CapableGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
|
7
|
+
source_root File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
# Implement the required interface for Rails::Generators::Migration.
|
10
|
+
def self.next_migration_number(dirname) #:nodoc:
|
11
|
+
next_migration_number = current_migration_number(dirname) + 1
|
12
|
+
if ActiveRecord::Base.timestamped_migrations
|
13
|
+
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
14
|
+
else
|
15
|
+
"%.3d" % next_migration_number
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_capable_migration
|
20
|
+
migration_template 'migration.rb', File.join('db', 'migrate', 'capable_migration.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
def move_ability_model
|
24
|
+
template 'ability.rb', File.join('app', 'models', 'ability.rb')
|
25
|
+
end
|
26
|
+
|
27
|
+
def move_capability_model
|
28
|
+
template 'capability.rb', File.join('app', 'models', 'capability.rb')
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class CapableMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
|
4
|
+
create_table :abilities, :force => true do |t|
|
5
|
+
t.string :ability, :null => false
|
6
|
+
t.string :name, :null => false
|
7
|
+
t.string :description, :null => false
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
|
11
|
+
create_table :capabilities, :force => true do |t|
|
12
|
+
t.integer :ability_id, :null => false
|
13
|
+
t.references :capable, :polymorphic => true, :null => false
|
14
|
+
t.references :renewer, :polymorphic => true
|
15
|
+
t.boolean :active, :default => true
|
16
|
+
t.datetime :expires_at, :null => true
|
17
|
+
t.timestamps
|
18
|
+
end
|
19
|
+
|
20
|
+
add_index :capabilities, [:active, :expires_at]
|
21
|
+
add_index :capabilities, [:capable_id, :capable_type, :active]
|
22
|
+
add_index :capabilities, [:capable_id, :capable_type, :ability_id, :renewer_id, :renewer_type], :name => 'capabilities_all_index'
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.down
|
27
|
+
|
28
|
+
drop_table :abilities
|
29
|
+
drop_table :capabilities
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Capable do
|
4
|
+
|
5
|
+
before do
|
6
|
+
Ability.delete_all
|
7
|
+
Capability.delete_all
|
8
|
+
User.delete_all
|
9
|
+
Renewer.delete_all
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'Capability Assignment' do
|
13
|
+
let (:non_cached_user) { NonCachedUser.create(name:'Eric') }
|
14
|
+
let (:user) { User.create(name:'Eric') }
|
15
|
+
let (:admin_ability) { Ability.create(name: 'Admin Ability', description: 'Ability for admin', ability: 'admin') }
|
16
|
+
let (:pro_ability) { Ability.create(name: 'Pro Ability', description: 'Ability for pro', ability: 'pro') }
|
17
|
+
let (:renewer) { Renewer.create() }
|
18
|
+
|
19
|
+
it 'Test Acts As Capable Instance Methods' do
|
20
|
+
expect(user.has_ability?(admin_ability)).to eq false
|
21
|
+
expect(user.has_ability?(admin_ability.ability)).to eq false
|
22
|
+
|
23
|
+
# Test assigning ability
|
24
|
+
user.assign_ability(admin_ability)
|
25
|
+
user.reload
|
26
|
+
|
27
|
+
expect(user.has_ability?(admin_ability)).to eq true
|
28
|
+
expect(user.has_ability?(admin_ability.ability)).to eq true
|
29
|
+
|
30
|
+
# Test unassigning ability
|
31
|
+
user.unassign_ability(admin_ability)
|
32
|
+
user.reload
|
33
|
+
|
34
|
+
expect(user.has_ability?(admin_ability)).to eq false
|
35
|
+
expect(user.has_ability?(admin_ability.ability)).to eq false
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'Test Acts As Capable Instance Methods without array_list defined' do
|
39
|
+
expect(non_cached_user.has_ability?(admin_ability)).to eq false
|
40
|
+
expect(non_cached_user.has_ability?(admin_ability.ability)).to eq false
|
41
|
+
|
42
|
+
# Test assigning ability
|
43
|
+
non_cached_user.assign_ability(admin_ability)
|
44
|
+
non_cached_user.reload
|
45
|
+
|
46
|
+
expect(non_cached_user.has_ability?(admin_ability)).to eq true
|
47
|
+
expect(non_cached_user.has_ability?(admin_ability.ability)).to eq true
|
48
|
+
|
49
|
+
# Test unassigning ability
|
50
|
+
non_cached_user.unassign_ability(admin_ability)
|
51
|
+
non_cached_user.reload
|
52
|
+
|
53
|
+
expect(non_cached_user.has_ability?(admin_ability)).to eq false
|
54
|
+
expect(non_cached_user.has_ability?(admin_ability.ability)).to eq false
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'Test Acts As Capability Renewer Instance Methods' do
|
58
|
+
|
59
|
+
# Test create_capabilities works
|
60
|
+
renewer.create_capabilities(user, [pro_ability, admin_ability], true, Time.now+4)
|
61
|
+
user.reload
|
62
|
+
|
63
|
+
expect(user.has_ability?(admin_ability)).to eq true
|
64
|
+
expect(user.has_ability?(pro_ability)).to eq true
|
65
|
+
|
66
|
+
Capability.expire_capabilities(Time.now+6)
|
67
|
+
user.reload
|
68
|
+
|
69
|
+
expect(user.has_ability?(admin_ability)).to eq false
|
70
|
+
expect(user.has_ability?(pro_ability)).to eq false
|
71
|
+
|
72
|
+
# Test renewing capabilities works
|
73
|
+
renewer.renew_capabilities(Time.now+8)
|
74
|
+
user.reload
|
75
|
+
|
76
|
+
expect(user.has_ability?(admin_ability)).to eq true
|
77
|
+
expect(user.has_ability?(pro_ability)).to eq true
|
78
|
+
|
79
|
+
# Test expiration
|
80
|
+
Capability.expire_capabilities(Time.now+10)
|
81
|
+
user.reload
|
82
|
+
|
83
|
+
expect(user.has_ability?(admin_ability)).to eq false
|
84
|
+
expect(user.has_ability?(pro_ability)).to eq false
|
85
|
+
|
86
|
+
# Test renewing capabilities does not work for the past
|
87
|
+
renewer.renew_capabilities(Time.now-1)
|
88
|
+
user.reload
|
89
|
+
|
90
|
+
expect(user.has_ability?(admin_ability)).to eq false
|
91
|
+
expect(user.has_ability?(pro_ability)).to eq false
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
it 'Test Acts As Capability Expiration Logic' do
|
96
|
+
user.assign_ability(admin_ability)
|
97
|
+
user.assign_ability(pro_ability, true, Time.now)
|
98
|
+
user.reload
|
99
|
+
|
100
|
+
expect(user.has_ability?(admin_ability)).to eq true
|
101
|
+
expect(user.has_ability?(pro_ability)).to eq true
|
102
|
+
|
103
|
+
# Test expiration
|
104
|
+
Capability.expire_capabilities(Time.now+2)
|
105
|
+
user.reload
|
106
|
+
|
107
|
+
expect(user.has_ability?(admin_ability)).to eq true
|
108
|
+
expect(user.has_ability?(pro_ability)).to eq false
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'capable'
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
|
6
|
+
require 'active_record'
|
7
|
+
|
8
|
+
config = {
|
9
|
+
:database => 'device_token_test',
|
10
|
+
:username => 'test'
|
11
|
+
}
|
12
|
+
|
13
|
+
case ENV['DB']
|
14
|
+
when 'mysql'
|
15
|
+
config = {
|
16
|
+
:adapter => 'mysql2',
|
17
|
+
:database => 'device_token_test',
|
18
|
+
:username => 'test',
|
19
|
+
:password => 'test',
|
20
|
+
:socket => '/tmp/mysql.sock'
|
21
|
+
}
|
22
|
+
if ENV['TRAVIS']
|
23
|
+
config = {
|
24
|
+
:adapter => 'mysql2',
|
25
|
+
:database => 'device_token_test',
|
26
|
+
:username => 'root'
|
27
|
+
}
|
28
|
+
end
|
29
|
+
ActiveRecord::Base.establish_connection(config)
|
30
|
+
ActiveRecord::Base.connection.drop_database(config[:database]) rescue nil
|
31
|
+
ActiveRecord::Base.connection.create_database(config[:database])
|
32
|
+
when 'postgres'
|
33
|
+
config = {
|
34
|
+
:adapter => 'postgresql',
|
35
|
+
:database => 'device_token_test',
|
36
|
+
:username => 'test',
|
37
|
+
}
|
38
|
+
if ENV['TRAVIS']
|
39
|
+
config = {
|
40
|
+
:adapter => 'postgresql',
|
41
|
+
:database => 'device_token_test',
|
42
|
+
:username => 'postgres',
|
43
|
+
}
|
44
|
+
end
|
45
|
+
ActiveRecord::Base.establish_connection(config.merge({ :database => 'postgres' }))
|
46
|
+
ActiveRecord::Base.connection.drop_database(config[:database])
|
47
|
+
ActiveRecord::Base.connection.create_database(config[:database])
|
48
|
+
when 'sqlite3'
|
49
|
+
config = {
|
50
|
+
:adapter => 'sqlite3',
|
51
|
+
:database => 'test.sqlite3',
|
52
|
+
:username => 'test',
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
ActiveRecord::Base.establish_connection(config)
|
57
|
+
|
58
|
+
ActiveRecord::Migration.verbose = false
|
59
|
+
|
60
|
+
ActiveRecord::Schema.define do
|
61
|
+
|
62
|
+
create_table :abilities, :force => true do |t|
|
63
|
+
t.string :ability, :null => false
|
64
|
+
t.string :name, :null => false
|
65
|
+
t.string :description, :null => false
|
66
|
+
t.timestamps
|
67
|
+
end
|
68
|
+
|
69
|
+
create_table :capabilities, :force => true do |t|
|
70
|
+
t.integer :ability_id, :null => false
|
71
|
+
t.references :capable, :polymorphic => true, :null => false
|
72
|
+
t.references :renewer, :polymorphic => true
|
73
|
+
t.boolean :active, :default => true
|
74
|
+
t.datetime :expires_at, :null => true
|
75
|
+
t.timestamps
|
76
|
+
end
|
77
|
+
|
78
|
+
create_table :users, :force => true do |t|
|
79
|
+
t.string :name
|
80
|
+
t.string :ability_list
|
81
|
+
t.timestamps
|
82
|
+
end
|
83
|
+
|
84
|
+
create_table :non_cached_users, :force => true do |t|
|
85
|
+
t.string :name
|
86
|
+
t.timestamps
|
87
|
+
end
|
88
|
+
|
89
|
+
create_table :renewers, :force => true do |t|
|
90
|
+
t.timestamps
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
class Ability < ActiveRecord::Base
|
96
|
+
acts_as_ability
|
97
|
+
end
|
98
|
+
|
99
|
+
class Capability < ActiveRecord::Base
|
100
|
+
acts_as_capability
|
101
|
+
end
|
102
|
+
|
103
|
+
class User < ActiveRecord::Base
|
104
|
+
acts_as_capable
|
105
|
+
end
|
106
|
+
|
107
|
+
class NonCachedUser < ActiveRecord::Base
|
108
|
+
acts_as_capable
|
109
|
+
end
|
110
|
+
|
111
|
+
class Renewer < ActiveRecord::Base
|
112
|
+
acts_as_capability_renewer
|
113
|
+
end
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Chapman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-06 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.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
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: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activerecord
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mysql2
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pg
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sqlite3
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: ''
|
112
|
+
email:
|
113
|
+
- contact@ericjchapman.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- .gitignore
|
119
|
+
- Gemfile
|
120
|
+
- LICENSE.txt
|
121
|
+
- README.md
|
122
|
+
- Rakefile
|
123
|
+
- capable.gemspec
|
124
|
+
- lib/acts_as_ability.rb
|
125
|
+
- lib/acts_as_capability.rb
|
126
|
+
- lib/acts_as_capability_renewer.rb
|
127
|
+
- lib/acts_as_capable.rb
|
128
|
+
- lib/capable.rb
|
129
|
+
- lib/capable/base.rb
|
130
|
+
- lib/capable/configuration.rb
|
131
|
+
- lib/capable/version.rb
|
132
|
+
- lib/generators/capable/capable_generator.rb
|
133
|
+
- lib/generators/capable/templates/ability.rb
|
134
|
+
- lib/generators/capable/templates/capability.rb
|
135
|
+
- lib/generators/capable/templates/migration.rb
|
136
|
+
- spec/capable_spec.rb
|
137
|
+
- spec/spec_helper.rb
|
138
|
+
homepage: ''
|
139
|
+
licenses:
|
140
|
+
- MIT
|
141
|
+
metadata: {}
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - '>='
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - '>='
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
requirements: []
|
157
|
+
rubyforge_project:
|
158
|
+
rubygems_version: 2.0.14
|
159
|
+
signing_key:
|
160
|
+
specification_version: 4
|
161
|
+
summary: Gem that will allow 'abilities' to be assigned to 'capable' objects that
|
162
|
+
can be independantly expired and renewed.
|
163
|
+
test_files:
|
164
|
+
- spec/capable_spec.rb
|
165
|
+
- spec/spec_helper.rb
|