cancancan-neo4j 1.0.2

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 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: []