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.
@@ -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
@@ -0,0 +1,19 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/examples.txt
10
+ /tmp/
11
+
12
+ .directory
13
+ *.log
14
+ *.bak
15
+ *.old
16
+ *.gem
17
+
18
+ /auth.yml
19
+ /feed.json
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cancan-export.gemspec
4
+ gemspec
@@ -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
+ ```
@@ -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,9 @@
1
+ module CanCan
2
+ module Export
3
+ class Engine < ::Rails::Engine
4
+ initializer 'cancan.export' do |app|
5
+ app.config.assets.paths << root.join('assets', 'javascripts').to_s
6
+ end
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,16 @@
1
+ module CanCan
2
+ module Export
3
+ module ControllerHelpers
4
+
5
+ def gon
6
+ super.tap do |g|
7
+ if current_user
8
+ g.user ||= current_user
9
+ g.ability ||= ::Ability.new(g.user)
10
+ end
11
+ end
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ module CanCan
2
+ module Export
3
+ VERSION = "0.2.1"
4
+ end
5
+ 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: []