cancan-export 0.2.1
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 +19 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +35 -0
- data/Rakefile +1 -0
- data/assets/javascripts/cancan/export-angular.js.coffee +24 -0
- data/assets/javascripts/cancan/export.js.coffee +148 -0
- data/cancan-export.gemspec +22 -0
- data/lib/cancan/export.rb +23 -0
- data/lib/cancan/export/compiler.rb +57 -0
- data/lib/cancan/export/engine.rb +9 -0
- data/lib/cancan/export/ext/ability.rb +34 -0
- data/lib/cancan/export/ext/rule.rb +18 -0
- data/lib/cancan/export/helpers.rb +16 -0
- data/lib/cancan/export/version.rb +5 -0
- metadata +113 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ab8f4923b92deaad80e7e97bc00cc52b61c4cf23
|
4
|
+
data.tar.gz: b8348cc0d0f60d391644fcbec7a903dea69fd257
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a819e03e5f4abf8633706c04e655f06271695116a91efad3855a52d03303358c388e6ec0ce9b672b0d6d73e6c80bc94f0e48da8775cbf4e1631f2809027f8f85
|
7
|
+
data.tar.gz: 5196e4031a3cbd80c5a7e88e8617c4c0d6a3b2138be478049185d7f85764e6cc7b8c186e0c8da11327550f090c3ceb4c15782fc51e68a80df3e05c1f792cb770
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Cancan::Export
|
2
|
+
|
3
|
+
It is the rewrite of a vital parts of CanCan v.1.13.1 classes: Ability and Rule.
|
4
|
+
All method names writen in a snake case is rewrites of the CanCan ones.
|
5
|
+
|
6
|
+
This rewrite is simplified since we don't have ORM objects and real DB relations.
|
7
|
+
All we have on the client-side is plain old objects,
|
8
|
+
though as long as each object has `_type' or `type' property, it can be identified as an object of a specific model.
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
```javascript
|
13
|
+
ability = new CanCanAbility(gon)
|
14
|
+
ability.can('read', 'CustomerOrder')
|
15
|
+
// => true or false
|
16
|
+
customerOrder = {partner_id: 123}
|
17
|
+
// or with restmod
|
18
|
+
customerOrder = CustomerOrder.$build({ partner_id: 123 })
|
19
|
+
ability.can('edit', customerOrder)
|
20
|
+
// => true, if a partner can edit only his orders and gon.user.partner_id == 123
|
21
|
+
```
|
22
|
+
|
23
|
+
### With angular
|
24
|
+
|
25
|
+
```javascript
|
26
|
+
// include CanCan service into the $rootScope,
|
27
|
+
MyApp.run(['$rootScope', 'CanCan', function($rootScope, CanCan) {
|
28
|
+
angular.extend($rootScope, CanCan);
|
29
|
+
...
|
30
|
+
}])
|
31
|
+
// then, in templates you can authorize parts like
|
32
|
+
can('edit', customerOrder)
|
33
|
+
// and check the permissions in a controller like
|
34
|
+
$scope.can('edit', $scope.customerOrder)
|
35
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Usage:
|
2
|
+
# include CanCan service into the $rootScope,
|
3
|
+
# MyApp.run('MyApp', ['$rootScope', 'CanCan', ($rootScope, CanCan) ->
|
4
|
+
# angular.extend $rootScope, CanCan
|
5
|
+
# ...
|
6
|
+
# ])
|
7
|
+
# then, in templates you can authorize parts like
|
8
|
+
# can('edit', customerOrder)
|
9
|
+
# and check the permissions in a controller like
|
10
|
+
# $scope.can('edit', $scope.customerOrder)
|
11
|
+
((global, factory) ->
|
12
|
+
if typeof define == 'function' and define.amd?
|
13
|
+
define ['angular', 'cancan-export'], factory
|
14
|
+
else
|
15
|
+
factory(global.angular, global.CanCanAbility)
|
16
|
+
)(this, (angular, CanCanAbility) ->
|
17
|
+
angular
|
18
|
+
.module 'cancan.export', []
|
19
|
+
.service 'CanCan', ->
|
20
|
+
ability: new CanCanAbility(window.gon)
|
21
|
+
getAbility: (object) -> @ability = new CanCanAbility(object)
|
22
|
+
can: (action, subject, argsForBlock...) -> @ability.can action, subject, argsForBlock...
|
23
|
+
cannot: (action, subject, argsForBlock...) -> @ability.cannot action, subject, argsForBlock...
|
24
|
+
)
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# It is the rewrite of a vital parts of CanCan v.1.13.1 classes: Ability and Rule.
|
2
|
+
# All method names writen in a snake case is rewrites of the CanCan ones.
|
3
|
+
#
|
4
|
+
# This rewrite is simplified since we don't have ORM objects and real DB relations.
|
5
|
+
# All we have on the client-side is plain old objects,
|
6
|
+
# though as long as each object has `_type' or `type' property, it can be identified as an object of a specific model.
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
# ability = new CanCanAbility(gon)
|
10
|
+
# ability.can('read', 'CustomerOrder')
|
11
|
+
# # => true or false
|
12
|
+
# customerOrder = {partner_id: 123}
|
13
|
+
# # or with restmod
|
14
|
+
# customerOrder = CustomerOrder.$build({ partner_id: 123 });
|
15
|
+
# ability.can('edit', customerOrder)
|
16
|
+
# # => true, if a partner can edit only his orders and gon.user.partner_id == 123
|
17
|
+
((global, factory) ->
|
18
|
+
if typeof define == 'function' and define.amd?
|
19
|
+
define ['lodash'], factory
|
20
|
+
else
|
21
|
+
factory(global._)
|
22
|
+
)(this, (_) ->
|
23
|
+
class @CanCanAbility
|
24
|
+
rules: []
|
25
|
+
rulesIndex: {}
|
26
|
+
helpers: []
|
27
|
+
|
28
|
+
# @ data : [gon Object]
|
29
|
+
constructor: (data) ->
|
30
|
+
@user = data.user
|
31
|
+
$.extend @, data.ability
|
32
|
+
|
33
|
+
# Import each helper defined in ruby's Ability object.
|
34
|
+
# `this' must be the object in which context `user' property is defined, thus it is Ability itself
|
35
|
+
for name, source in @helpersSource
|
36
|
+
@[name] = $.proxy eval(source), @
|
37
|
+
|
38
|
+
@rules = @rules.map (data) =>
|
39
|
+
new Rule(data, @)
|
40
|
+
|
41
|
+
can: (action, subject, extra_args...) ->
|
42
|
+
relevant_rules = @relevant_rules(action, subject)
|
43
|
+
|
44
|
+
match = _(relevant_rules).find (rule) =>
|
45
|
+
rule.matches_conditions(action, subject, extra_args)
|
46
|
+
|
47
|
+
match and match.baseBehavior or false
|
48
|
+
|
49
|
+
cannot: (action, object, extra_args...) ->
|
50
|
+
!@can action, object, extra_args...
|
51
|
+
|
52
|
+
|
53
|
+
typeOf: (subject) ->
|
54
|
+
if typeof subject == 'string'
|
55
|
+
subject
|
56
|
+
else
|
57
|
+
subject._type or subject.type
|
58
|
+
|
59
|
+
matches_subject: (subjects, subject) ->
|
60
|
+
_(subjects).include('all') or # wildcard rule
|
61
|
+
_(subjects).include(@typeOf subject)
|
62
|
+
|
63
|
+
relevant_rules: (action, subject) ->
|
64
|
+
_.clone(@rules).reverse().filter (rule) =>
|
65
|
+
@matches_subject(rule.subjects, subject) and rule.is_relevant action, subject
|
66
|
+
|
67
|
+
|
68
|
+
class Rule
|
69
|
+
# there are nor subjects nor actions
|
70
|
+
matchAll: false
|
71
|
+
# can or cannot?
|
72
|
+
baseBehavior: true
|
73
|
+
# what to do?
|
74
|
+
actions: []
|
75
|
+
# object model names
|
76
|
+
# N.B. a Class starts with a Capital letter; a plain string/symbol with a small letter
|
77
|
+
subjects: []
|
78
|
+
# `where' hash on a model
|
79
|
+
conditions: {}
|
80
|
+
# function to check against
|
81
|
+
block: null
|
82
|
+
|
83
|
+
constructor: (data, ability) ->
|
84
|
+
$.extend @, data
|
85
|
+
@ability = ability
|
86
|
+
@block = $.proxy eval(data.blockSource), ability
|
87
|
+
|
88
|
+
typeOf: CanCanAbility.prototype.typeOf
|
89
|
+
|
90
|
+
is_relevant: (action, subject) ->
|
91
|
+
@matchAll or (@matches_action(action) and @matches_subject(subject))
|
92
|
+
|
93
|
+
matches_action: (action) ->
|
94
|
+
_(@actions).include('manage') or # wildcard rule
|
95
|
+
_(@actions).include(action)
|
96
|
+
|
97
|
+
matches_subject: (subject) ->
|
98
|
+
@ability.matches_subject(@subjects, subject)
|
99
|
+
|
100
|
+
# Nested subjects matching is not supported now
|
101
|
+
matches_conditions: (action, subject, extra_args) ->
|
102
|
+
if @matchAll
|
103
|
+
@call_block_with_all(action, subject, extra_args)
|
104
|
+
else if @block && typeof subject != 'string'
|
105
|
+
@block(subject, extra_args...)
|
106
|
+
else if @conditions && typeof subject != 'string'
|
107
|
+
@matches_conditions_hash(subject)
|
108
|
+
else
|
109
|
+
# Don't stop at "cannot" definitions when there are conditions.
|
110
|
+
# We want, if @baseBehavior is false, Ability#relevant_rules to not accept the result and check the next rule.
|
111
|
+
!@conditions or $.isEmptyObject(@conditions) or @baseBehavior
|
112
|
+
|
113
|
+
# This is not suported yet. Use of this hook implies that we're able to pass a _class_ into block function,
|
114
|
+
# but for the while we can't
|
115
|
+
call_block_with_all: (action, subject, extra_args) ->
|
116
|
+
if typeof subject == 'string'
|
117
|
+
@block(action, subject, nil, extra_args...)
|
118
|
+
else
|
119
|
+
@block(action, @typeOf(subject), subject, extra_args...)
|
120
|
+
|
121
|
+
# @ subject : [resource Object] : a pattern we match against
|
122
|
+
# @ conditions : [Object] : a pattern we match against
|
123
|
+
matches_conditions_hash: (subject, conditions=@conditions) ->
|
124
|
+
return true if $.isEmptyObject conditions
|
125
|
+
every_match = not _(conditions).find (value, name) =>
|
126
|
+
!@condition_match(subject[name], value)
|
127
|
+
|
128
|
+
# @ attribute : a value which we match
|
129
|
+
# @ value : a pattern we match against
|
130
|
+
condition_match: (attribute, value) ->
|
131
|
+
if $.isPlainObject value
|
132
|
+
@hash_condition_match(attribute, value)
|
133
|
+
else if $.isArray value
|
134
|
+
# match against any of the rule condition values will do
|
135
|
+
_(value).include(attribute)
|
136
|
+
else
|
137
|
+
# only exact equality will do
|
138
|
+
attribute == value
|
139
|
+
|
140
|
+
# @ attribute : a value which we match
|
141
|
+
# @ value : [Object] : a pattern we match against
|
142
|
+
hash_condition_match: (attribute, value) ->
|
143
|
+
if $.isArray attribute
|
144
|
+
# match of any element of [Array] attribute against the rule condition hash will do (recursion)
|
145
|
+
_(attribute).find (element) => @matches_conditions_hash(element, value)
|
146
|
+
else
|
147
|
+
attribute? && @matches_conditions_hash?(attribute, value)
|
148
|
+
)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cancan/export/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cancan-export"
|
8
|
+
spec.version = CanCan::Export::VERSION
|
9
|
+
spec.authors = ["Sergey Baev"]
|
10
|
+
|
11
|
+
spec.summary = "Exports CanCan rules to the client-side."
|
12
|
+
spec.homepage = "https://github.com/tinbka/cancan-export"
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
15
|
+
spec.require_paths = ["lib"]
|
16
|
+
|
17
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
18
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
19
|
+
|
20
|
+
spec.add_dependency "gon"
|
21
|
+
spec.add_dependency "coffee-rails"
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "cancan"
|
2
|
+
require "gon"
|
3
|
+
|
4
|
+
require "cancan/export/version"
|
5
|
+
require "cancan/export/compiler"
|
6
|
+
require "cancan/export/helpers"
|
7
|
+
require "cancan/export/engine"
|
8
|
+
require "cancan/export/ext/ability"
|
9
|
+
require "cancan/export/ext/rule"
|
10
|
+
|
11
|
+
module CanCan
|
12
|
+
class Rule
|
13
|
+
include Export::Rule
|
14
|
+
end
|
15
|
+
|
16
|
+
module Ability
|
17
|
+
include Export::Ability
|
18
|
+
end
|
19
|
+
|
20
|
+
module Export
|
21
|
+
::ActionController::Base.send :include, ControllerHelpers
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module CanCan
|
2
|
+
module Export
|
3
|
+
class Compiler
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@mtimes = {}
|
7
|
+
@lines = {}
|
8
|
+
@js = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_js(block)
|
12
|
+
source_lines = definition(block)
|
13
|
+
return @js[source_lines] if @js[source_lines]
|
14
|
+
|
15
|
+
case block
|
16
|
+
when Proc then source_lines[0] =~ / (?:do|{)\s*(?:\|([\s\w\d,]+)\|)?\s*$/
|
17
|
+
when Method then source_lines[0] =~ /def [\w\d\.]+\(?([\s\w\d,]+)\)?\s*$/
|
18
|
+
end
|
19
|
+
arguments = $1.scan(/\w+/)
|
20
|
+
|
21
|
+
coffeefied_source_lines = source_lines[1..-1]*"\n"
|
22
|
+
coffeefied_source_lines.gsub!(/\.(\w+)\?/, '.is_\1()') # .present? -> .present()
|
23
|
+
coffeefied_source_lines.gsub!(/\.(\w+)!/, '.do_\1()') # .sub! -> .sub!()
|
24
|
+
coffeefied_source_lines.gsub!(/(\d+)\.([a-z_]+)/, '(\1).\2()') # 4.hours -> (4).hours()
|
25
|
+
coffeefied_source_lines.gsub!(/:([\w\d]+)/, '"\1"') # 4.hours -> (4).hours()
|
26
|
+
#$log.info coffeefied_source_lines, caller_at: [0, 9..20]
|
27
|
+
|
28
|
+
@js[source_lines] = CoffeeScript.compile "(#{arguments*', '}) ->\n#{coffeefied_source_lines}", bare: true
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def definition(proc)
|
34
|
+
file, line = proc.source_location
|
35
|
+
lines = readlines file
|
36
|
+
|
37
|
+
definition_line = lines[line-1]
|
38
|
+
indentation = definition_line[/^\s*/]
|
39
|
+
return lines[line-1..-1].take_while {|line| line !~ /#{indentation}end$/}
|
40
|
+
end
|
41
|
+
|
42
|
+
def readlines(file)
|
43
|
+
if File.exists?(file)
|
44
|
+
mtime = File.mtime(file)
|
45
|
+
unless @mtimes[file] and @mtimes[file] >= mtime
|
46
|
+
@mtimes[file] = mtime
|
47
|
+
@lines[file] = IO.readlines(file)
|
48
|
+
end
|
49
|
+
@lines[file]
|
50
|
+
else
|
51
|
+
raise Errno::ENOENT, "The file where a block was defined, does not exist anymore @ rb_sysopen - #{file}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module CanCan
|
2
|
+
module Export
|
3
|
+
module Ability
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
@@compiler = CanCan::Export::Compiler.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def helper_method(*method_names)
|
11
|
+
helper_methods.merge! method_names.map {|method_name|
|
12
|
+
[method_name, @@compiler.to_js(method(method_name))]
|
13
|
+
}.to_h
|
14
|
+
end
|
15
|
+
|
16
|
+
def helper_methods
|
17
|
+
@helpers ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_json(options={})
|
21
|
+
{
|
22
|
+
rules: @rules.map {|rule|
|
23
|
+
rule.compile(self, @@compiler)
|
24
|
+
},
|
25
|
+
rulesIndex: @rules_index.map {|object, indices|
|
26
|
+
[object.to_s, indices]
|
27
|
+
}.to_h,
|
28
|
+
helpersSource: helper_methods
|
29
|
+
}.to_json(options)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CanCan
|
2
|
+
module Export
|
3
|
+
module Rule
|
4
|
+
|
5
|
+
def compile(ability, compiler)
|
6
|
+
{
|
7
|
+
matchAll: @match_all,
|
8
|
+
baseBehavior: @base_behavior,
|
9
|
+
subjects: @subjects.map(&:to_s),
|
10
|
+
actions: ability.send(:expand_actions, @actions).map(&:to_s),
|
11
|
+
conditions: @conditions,
|
12
|
+
blockSource: @block && compiler.to_js(@block)
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cancan-export
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sergey Baev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-21 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.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
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: gon
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
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: coffee-rails
|
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
|
+
description:
|
70
|
+
email:
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- ".gitignore"
|
76
|
+
- ".travis.yml"
|
77
|
+
- Gemfile
|
78
|
+
- README.md
|
79
|
+
- Rakefile
|
80
|
+
- assets/javascripts/cancan/export-angular.js.coffee
|
81
|
+
- assets/javascripts/cancan/export.js.coffee
|
82
|
+
- cancan-export.gemspec
|
83
|
+
- lib/cancan/export.rb
|
84
|
+
- lib/cancan/export/compiler.rb
|
85
|
+
- lib/cancan/export/engine.rb
|
86
|
+
- lib/cancan/export/ext/ability.rb
|
87
|
+
- lib/cancan/export/ext/rule.rb
|
88
|
+
- lib/cancan/export/helpers.rb
|
89
|
+
- lib/cancan/export/version.rb
|
90
|
+
homepage: https://github.com/tinbka/cancan-export
|
91
|
+
licenses: []
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 2.4.8
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: Exports CanCan rules to the client-side.
|
113
|
+
test_files: []
|