cancancan-neo4j 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 148fb171b87ba1ffbb6445fbc94d784f17e0c7a5
4
+ data.tar.gz: 2a51eae6389c590145df0a17d06febd46d36cdf5
5
+ SHA512:
6
+ metadata.gz: b91b481d4fd5c21a46679e639a66e01245edb84b1e0bbcf295f2a5ead6202b6a08e7fea7541f8331dc82f9a2d52fee32fcda8bf124f0351176aaf8fa94c4e7b5
7
+ data.tar.gz: d4d5388127d1ec21c0f107fa5fedf6b92e5187cb0fd5871cdc615091f8ba32da1dd8afda2c56dded956117f2903c0cfc6a8c037b67b3d2922a5d036752e122ce
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'cancancan/neo4j/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'cancancan-neo4j'
9
+ spec.version = CanCanCan::Neo4j::VERSION
10
+ spec.authors = ['Amit Suryavanshi']
11
+ spec.email = ['amitbsuryavanshi@gmail.com']
12
+ spec.homepage = 'https://github.com/CanCanCommunity/cancancan-neo4j'
13
+ spec.summary = 'neo4j database adapter for CanCanCan.'
14
+ spec.description = "Implements CanCanCan's rule-based record fetching using neo4j gem."
15
+ spec.platform = Gem::Platform::RUBY
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files lib init.rb cancancan-neo4j.gemspec`.split($INPUT_RECORD_SEPARATOR)
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'activemodel', '<5.2.0'
22
+ spec.add_dependency 'cancancan', '<=2.1.4'
23
+ spec.add_dependency 'neo4j', '>= 9.0.0'
24
+
25
+ spec.add_development_dependency 'bundler', '>= 1.3'
26
+ spec.add_development_dependency 'neo4j-community' if RUBY_PLATFORM =~ /java/
27
+ spec.add_development_dependency('neo4j-rake_tasks', '>= 0.3.0')
28
+ spec.add_development_dependency 'pry', '>= 0.11.3'
29
+ spec.add_development_dependency 'rake', '>= 10.1'
30
+ spec.add_development_dependency 'rspec', '>= 3.2'
31
+ spec.add_development_dependency 'rubocop', '>= 0.48.1'
32
+ end
@@ -0,0 +1,131 @@
1
+ require 'cancancan/neo4j/cypher_constructor_helper'
2
+ require 'cancancan/neo4j/rule_cypher'
3
+
4
+ module CanCan
5
+ module ModelAdapters
6
+ # neo4j adapter for cancan
7
+ class Neo4jAdapter < AbstractAdapter
8
+ def database_records
9
+ return @model_class.where('false') if @rules.empty?
10
+ rule = @rules.first
11
+ return rule.conditions if override_scope
12
+ records_for_multiple_rules.distinct
13
+ end
14
+
15
+ def self.for_class?(model_class)
16
+ model_class <= Neo4j::ActiveNode
17
+ end
18
+
19
+ def self.override_conditions_hash_matching?(_subject, _conditions)
20
+ true
21
+ end
22
+
23
+ def self.matches_conditions_hash?(subject, conditions)
24
+ base_class = subject.class
25
+ all_conditions_match?(subject, conditions, base_class)
26
+ end
27
+
28
+ def self.all_conditions_match?(subject, conditions, base_class)
29
+ asso_conditions, model_conditions = CanCanCan::Neo4j::CypherConstructorHelper
30
+ .bifurcate_conditions(conditions)
31
+ associations_conditions_match?(asso_conditions, subject, base_class) &&
32
+ model_conditions_matches?(model_conditions, subject, base_class)
33
+ end
34
+
35
+ def self.model_conditions_matches?(conditions, subject, base_class)
36
+ return true if conditions.blank?
37
+ conditions = conditions.partition do |key, _|
38
+ base_class.associations_keys.include?(key)
39
+ end
40
+ associations_conditions, atrribute_conditions = conditions.map(&:to_h)
41
+ matches_attribute_conditions?(atrribute_conditions, subject) &&
42
+ matches_associations_relations(associations_conditions, subject)
43
+ end
44
+
45
+ # checks if associations exists on given node
46
+ def self.matches_associations_relations(conditions, subject)
47
+ return true if conditions.blank?
48
+ conditions.all? do |association, value|
49
+ association_exists = subject.send(association).exists?
50
+ value ? association_exists : !association_exists
51
+ end
52
+ end
53
+
54
+ def self.matches_attribute_conditions?(conditions, subject)
55
+ return true if conditions.blank?
56
+ if subject.is_a?(Neo4j::ActiveNode::HasN::AssociationProxy)
57
+ subject.where(conditions).exists?
58
+ else
59
+ conditions.all? do |attribute, value|
60
+ subject.send(attribute) == value
61
+ end
62
+ end
63
+ end
64
+
65
+ def self.associations_conditions_match?(conditions, subject, base_class)
66
+ return true if conditions.blank?
67
+ conditions.all? do |association, conditions_hash|
68
+ current_model = base_class.associations[association].target_class
69
+ current_subject = subject.send(association)
70
+ all_conditions_match?(current_subject, conditions_hash, current_model)
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def records_for_multiple_rules
77
+ base_query = base_query_proxy.query
78
+ cypher_options = construct_cypher_options
79
+ match_string = cypher_options[:matches].uniq.join(', ')
80
+ base_query = base_query.match(match_string) unless match_string.blank?
81
+ base_query
82
+ .proxy_as(@model_class, var_name(@model_class))
83
+ .where(cypher_options[:conditions])
84
+ end
85
+
86
+ def construct_cypher_options
87
+ default_options = { conditions: '', matches: [] }
88
+ @rules.reverse.each_with_object(default_options) do |rule, options|
89
+ rule_options = { rule: rule, model_class: @model_class }
90
+ rule_cypher = CanCanCan::Neo4j::RuleCypher.new(rule_options)
91
+ CanCanCan::Neo4j::CypherConstructorHelper
92
+ .append_and_or_to_conditions(options, rule_cypher)
93
+ options[:conditions] += ('(' + rule_cypher.rule_conditions + ')')
94
+ options[:matches] += rule_cypher.cypher_matches
95
+ options
96
+ end
97
+ end
98
+
99
+ def base_query_proxy
100
+ @model_class.as(var_name(@model_class))
101
+ end
102
+
103
+ def override_scope
104
+ conditions = @rules.map(&:conditions).compact
105
+ return unless conditions.any? { |condition| condition.is_a?(Neo4j::ActiveNode::Query::QueryProxy) }
106
+ return conditions.first if conditions.size == 1
107
+ raise_override_scope_error
108
+ end
109
+
110
+ def raise_override_scope_error
111
+ rule_found = @rules.detect { |rule| rule.conditions.is_a?(Neo4j::ActiveNode::Query::QueryProxy) }
112
+ raise Error,
113
+ 'Unable to merge an ActiveNode scope with other conditions. '\
114
+ "Instead use a hash for #{rule_found.actions.first} #{rule_found.subjects.first} ability."
115
+ end
116
+
117
+ def var_name(model_class)
118
+ CanCanCan::Neo4j::CypherConstructorHelper.var_name(model_class)
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ module Neo4j
125
+ module ActiveNode
126
+ # simplest way to add `accessible_by` to all ActiveNode models
127
+ module ClassMethods
128
+ include CanCan::ModelAdditions::ClassMethods
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,6 @@
1
+ require 'cancancan'
2
+ require 'neo4j'
3
+
4
+ require 'cancancan/neo4j/version'
5
+ require 'cancancan/model_adapters/neo4j_adapter'
6
+ require 'cancancan/neo4j/cypher_constructor_helper'
@@ -0,0 +1,7 @@
1
+ class CanCanCan::Sequel::ActiveRecordDisabler
2
+ ::CanCan::ModelAdapters::ActiveRecord4Adapter.class_eval do
3
+ def self.for_class?(_)
4
+ false
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,74 @@
1
+ require 'cancancan/neo4j/cypher_constructor_helper'
2
+
3
+ module CanCanCan
4
+ module Neo4j
5
+ # Constructs cypher conditions for associations conditions hash
6
+ class AssociationConditions
7
+ attr_reader :conditions_string, :cypher_matches
8
+
9
+ def initialize(options)
10
+ @options = options
11
+ @conditions_string = ''
12
+ @cypher_matches = []
13
+ construct_conditions
14
+ end
15
+
16
+ def construct_conditions
17
+ @options[:asso_conditions].each do |association, conditions|
18
+ relationship = association_relation(association)
19
+ associations_conditions, model_conditions = CypherConstructorHelper.bifurcate_conditions(conditions)
20
+ current_path = append_path_to_conditions(relationship, model_conditions)
21
+ append_model_conditions(model_conditions, relationship, current_path)
22
+ append_association_conditions(associations_conditions, relationship)
23
+ end
24
+ end
25
+
26
+ def association_relation(association)
27
+ @options[:parent_class].associations[association]
28
+ end
29
+
30
+ def append_association_conditions(conditions, relationship)
31
+ return if conditions.blank?
32
+ asso_conditions_obj = AssociationConditions.new(asso_conditions: conditions, parent_class: relationship.target_class, path: path_with_relationship(relationship))
33
+ append_and_to_conditions_string
34
+ @conditions_string += asso_conditions_obj.conditions_string
35
+ @cypher_matches += asso_conditions_obj.cypher_matches
36
+ end
37
+
38
+ def append_path_to_conditions(relationship, model_conditions)
39
+ target_class = relationship.target_class
40
+ model_attr_exists = model_conditions.any? do |key, _|
41
+ !target_class.associations_keys.include?(key)
42
+ end
43
+ end_node = model_attr_exists ? CypherConstructorHelper.match_node_cypher(target_class) : '()'
44
+ current_path = @options[:path] + relationship.arrow_cypher + end_node
45
+ if model_attr_exists
46
+ append_matches(relationship)
47
+ append_and_to_conditions_string
48
+ @conditions_string += current_path
49
+ end
50
+ current_path
51
+ end
52
+
53
+ def append_matches(relationship)
54
+ node_class = relationship.target_class
55
+ @cypher_matches << CypherConstructorHelper.match_node_cypher(node_class)
56
+ end
57
+
58
+ def append_and_to_conditions_string
59
+ @conditions_string += ' AND ' unless @conditions_string.blank?
60
+ end
61
+
62
+ def append_model_conditions(model_conditions, relationship, current_path)
63
+ return if model_conditions.blank?
64
+ con_string = CypherConstructorHelper.construct_conditions_string(model_conditions, relationship.target_class, current_path)
65
+ append_and_to_conditions_string
66
+ @conditions_string += con_string
67
+ end
68
+
69
+ def path_with_relationship(relationship)
70
+ @options[:path] + relationship.arrow_cypher + '()'
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,71 @@
1
+ module CanCanCan
2
+ module Neo4j
3
+ # Cypher query constructs
4
+ class CypherConstructorHelper
5
+ class << self
6
+ def match_node_cypher(node_class)
7
+ "(#{var_name(node_class)}:`#{node_class.mapped_label_name}`)"
8
+ end
9
+
10
+ def construct_conditions_string(conditions_hash, base_class, path = '')
11
+ variable_name = var_name(base_class)
12
+ conditions_hash.collect do |key, value|
13
+ condition = if base_class.associations_keys.include?(key)
14
+ con = condtion_for_path(path: path,
15
+ variable_name: variable_name,
16
+ base_class: base_class,
17
+ key: key)
18
+ value ? con : ' NOT ' + con
19
+ elsif key == :id
20
+ condition_for_id(base_class, variable_name, value)
21
+ else
22
+ condition_for_attribute(value, variable_name, key)
23
+ end
24
+ '(' + condition + ')'
25
+ end.join(' AND ')
26
+ end
27
+
28
+ def condition_for_attribute(value, variable_name, attribute)
29
+ lhs = variable_name + '.' + attribute.to_s
30
+ return lhs + ' IS NULL ' if value.nil?
31
+ rhs = value.to_s
32
+ rhs = "'" + rhs + "'" unless [true, false].include?(value)
33
+ lhs + '=' + rhs
34
+ end
35
+
36
+ def condtion_for_path(path:, variable_name:, base_class:, key:)
37
+ path = "(#{variable_name})" if path.blank?
38
+ path + base_class.associations[key].arrow_cypher + '()'
39
+ end
40
+
41
+ def condition_for_id(base_class, variable_name, value)
42
+ id_property = base_class.id_property_name
43
+ if id_property == :neo_id
44
+ "ID(#{variable_name})=#{value}"
45
+ else
46
+ variable_name + '.' + id_property.to_s + '=' + "'#{value}'"
47
+ end
48
+ end
49
+
50
+ def var_name(class_constant)
51
+ class_constant.name.downcase.split('::').join('_')
52
+ end
53
+
54
+ def bifurcate_conditions(conditions)
55
+ conditions.partition { |_, value| value.is_a?(Hash) }.map(&:to_h)
56
+ end
57
+
58
+ def append_and_or_to_conditions(cypher_options, rule_cypher)
59
+ conditions_string = cypher_options[:conditions]
60
+ connector = if conditions_string.blank?
61
+ ''
62
+ else
63
+ rule_cypher.conditions_connector
64
+ end
65
+ connector += 'NOT' if rule_cypher.append_not_to_conditions?
66
+ cypher_options[:conditions] = conditions_string + connector
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,63 @@
1
+ require 'cancancan/neo4j/cypher_constructor_helper'
2
+ require 'cancancan/neo4j/association_conditions'
3
+
4
+ module CanCanCan
5
+ module Neo4j
6
+ # Constructs cypher conditions for rule and cypher match classes
7
+ class RuleCypher
8
+ attr_reader :rule_conditions, :cypher_matches
9
+
10
+ def initialize(options)
11
+ @options = options
12
+ @rule_conditions = ''
13
+ @cypher_matches = []
14
+ construct_cypher_conditions
15
+ end
16
+
17
+ def construct_cypher_conditions
18
+ if @options[:rule].conditions.blank?
19
+ condition_for_rule_without_conditions
20
+ else
21
+ set_cypher_options
22
+ end
23
+ end
24
+
25
+ def conditions_connector
26
+ @options[:rule].base_behavior ? ' OR ' : ' AND '
27
+ end
28
+
29
+ def append_not_to_conditions?
30
+ !rule_conditions_blank? && !@options[:rule].base_behavior
31
+ end
32
+
33
+ private
34
+
35
+ def rule_conditions_blank?
36
+ @options[:rule].conditions.blank?
37
+ end
38
+
39
+ def condition_for_rule_without_conditions
40
+ @rule_conditions = @options[:rule].base_behavior ? '(true)' : '(false)'
41
+ end
42
+
43
+ def set_cypher_options
44
+ associations_conditions, model_conditions = CypherConstructorHelper.bifurcate_conditions(@options[:rule].conditions)
45
+ @rule_conditions = CypherConstructorHelper.construct_conditions_string(model_conditions, @options[:model_class], default_path) unless model_conditions.blank?
46
+ return if associations_conditions.blank?
47
+ append_association_conditions(associations_conditions)
48
+ end
49
+
50
+ def append_association_conditions(conditions_hash)
51
+ options = { asso_conditions: conditions_hash, parent_class: @options[:model_class], path: default_path }
52
+ asso_conditions_obj = AssociationConditions.new(options)
53
+ @rule_conditions += ' AND ' unless @rule_conditions.blank?
54
+ @rule_conditions += asso_conditions_obj.conditions_string
55
+ @cypher_matches += asso_conditions_obj.cypher_matches
56
+ end
57
+
58
+ def default_path
59
+ CypherConstructorHelper.match_node_cypher(@options[:model_class])
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ module CanCanCan
2
+ end
3
+ module CanCanCan
4
+ module Neo4j
5
+ VERSION = '1.0.2'.freeze
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cancancan-neo4j
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Amit Suryavanshi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-04-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "<"
17
+ - !ruby/object:Gem::Version
18
+ version: 5.2.0
19
+ name: activemodel
20
+ prerelease: false
21
+ type: :runtime
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "<"
25
+ - !ruby/object:Gem::Version
26
+ version: 5.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "<="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.1.4
33
+ name: cancancan
34
+ prerelease: false
35
+ type: :runtime
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "<="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.1.4
41
+ - !ruby/object:Gem::Dependency
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 9.0.0
47
+ name: neo4j
48
+ prerelease: false
49
+ type: :runtime
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 9.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '1.3'
61
+ name: bundler
62
+ prerelease: false
63
+ type: :development
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
69
+ - !ruby/object:Gem::Dependency
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ name: neo4j-community
76
+ prerelease: false
77
+ type: :development
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 0.3.0
89
+ name: neo4j-rake_tasks
90
+ prerelease: false
91
+ type: :development
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 0.3.0
97
+ - !ruby/object:Gem::Dependency
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 0.11.3
103
+ name: pry
104
+ prerelease: false
105
+ type: :development
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 0.11.3
111
+ - !ruby/object:Gem::Dependency
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '10.1'
117
+ name: rake
118
+ prerelease: false
119
+ type: :development
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '10.1'
125
+ - !ruby/object:Gem::Dependency
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '3.2'
131
+ name: rspec
132
+ prerelease: false
133
+ type: :development
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '3.2'
139
+ - !ruby/object:Gem::Dependency
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: 0.48.1
145
+ name: rubocop
146
+ prerelease: false
147
+ type: :development
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 0.48.1
153
+ description: Implements CanCanCan's rule-based record fetching using neo4j gem.
154
+ email:
155
+ - amitbsuryavanshi@gmail.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - cancancan-neo4j.gemspec
161
+ - lib/cancancan/model_adapters/neo4j_adapter.rb
162
+ - lib/cancancan/neo4j.rb
163
+ - lib/cancancan/neo4j/active_record_disabler.rb
164
+ - lib/cancancan/neo4j/association_conditions.rb
165
+ - lib/cancancan/neo4j/cypher_constructor_helper.rb
166
+ - lib/cancancan/neo4j/rule_cypher.rb
167
+ - lib/cancancan/neo4j/version.rb
168
+ homepage: https://github.com/CanCanCommunity/cancancan-neo4j
169
+ licenses:
170
+ - MIT
171
+ metadata: {}
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ requirements: []
187
+ rubyforge_project:
188
+ rubygems_version: 2.6.14
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: neo4j database adapter for CanCanCan.
192
+ test_files: []