cancan-export 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|