neo4j 3.0.4 → 4.0.0.rc.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +22 -0
- data/README.md +4 -0
- data/config/neo4j/config.yml +3 -1
- data/lib/neo4j/active_node/has_n/association.rb +4 -3
- data/lib/neo4j/active_node/id_property.rb +1 -1
- data/lib/neo4j/active_node/initialize.rb +1 -1
- data/lib/neo4j/active_node/labels.rb +20 -13
- data/lib/neo4j/active_node/query/query_proxy.rb +41 -16
- data/lib/neo4j/active_node/query/query_proxy_methods.rb +15 -3
- data/lib/neo4j/active_node/scope.rb +59 -2
- data/lib/neo4j/active_node.rb +13 -11
- data/lib/neo4j/active_rel/initialize.rb +1 -1
- data/lib/neo4j/active_rel/persistence.rb +3 -2
- data/lib/neo4j/active_rel/property.rb +0 -10
- data/lib/neo4j/active_rel/query.rb +0 -4
- data/lib/neo4j/active_rel/rel_wrapper.rb +15 -4
- data/lib/neo4j/active_rel/types.rb +54 -0
- data/lib/neo4j/active_rel.rb +5 -2
- data/lib/neo4j/paginated.rb +1 -1
- data/lib/neo4j/shared/persistence.rb +43 -7
- data/lib/neo4j/shared/property.rb +2 -6
- data/lib/neo4j/shared/rel_type_converters.rb +44 -0
- data/lib/neo4j/shared/serialized_properties.rb +29 -0
- data/lib/neo4j/shared/type_converters.rb +177 -0
- data/lib/neo4j/type_converters.rb +3 -166
- data/lib/neo4j/version.rb +1 -1
- data/lib/neo4j.rb +8 -4
- data/neo4j.gemspec +1 -1
- metadata +11 -8
- data/lib/neo4j/active_node/serialized_properties.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 122872ebd58702ee7d868124092285e8907cfbfd
|
4
|
+
data.tar.gz: 11989c0af301ccb1305df5deb26e9c55a20313f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9433059f7d5dd25d4bd22be97af295f98cbd1340f5bdf1c1869c01111b22d61bf8cf6e260d08de528c3cb90735984d069234fafe32556cd6b200c415b0d7243
|
7
|
+
data.tar.gz: c894497b7e00362c8145bdbf61bdd8b50f772f108b839ae068971bd32e888f502897ecfc03c4284bfd1f838815b75fe8dc41d3222ea61b0d1157d371a3ff325e
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
== 4.0.0.rc.1
|
2
|
+
This release introduces API changes that may be considered breaking under certain conditions. See See https://github.com/neo4jrb/neo4j/wiki/Neo4j.rb-v4-Introduction.
|
3
|
+
Please use https://github.com/neo4jrb/neo4j/issues for support regarding this update! You can also reach us on Twitter: @neo4jrb (Brian) and @subvertallmedia (Chris).
|
4
|
+
* Default behavior changed: relationship types default to all caps, no prepending of "#". This behavior can be changed.
|
5
|
+
* ActiveRel models no longer require explicit calling of `type`. When missing, the model will infer a type using the class name following the same rules used to determine automatic relationship types from ActiveNode models.
|
6
|
+
* _classname properties will not be added automatically if you are using a version Neo4j >= 2.1.5. Instead, models are found using labels or relationship type. This is a potentially breaking change, particularly where ActiveRel is concerned. See the link at the beginning of this message for the steps required to work around this.
|
7
|
+
* Query scopes are now chainable! Call `all` at the start of your scope or method to take advantage of this.
|
8
|
+
* Changes required for Neo4j 2.2.
|
9
|
+
* Support for custom typecasters.
|
10
|
+
* New method `rel_where`, expanded behavior of `match_to` and `first_rel_to`
|
11
|
+
* Implemented ActiveSupport load hooks.
|
12
|
+
* Assorted performance improvements and refactoring.
|
13
|
+
|
14
|
+
== 3.0.4
|
15
|
+
* Gemspec requires the latest neo4j-core.
|
16
|
+
* Fixed a pagination bug — thanks, @chrisgogreen!
|
17
|
+
* New QueryProxy methods `match_to` and `first_rel_to` are pretty cool.
|
18
|
+
* include_root_in_json is now configurable through config.neo4j.include_root_in_json or Neo4j::Config[:include_root_in_json]. Also cool.
|
19
|
+
* There's a new `delete_all` method for QueryProxy, too.
|
20
|
+
* @codebeige removed the `include?` class method, which was smart.
|
21
|
+
* Did I mention we won an award from Neo Tech? Check it out. https://github.com/neo4jrb/neo4j#welcome-to-neo4jrb
|
22
|
+
|
1
23
|
== 3.0.3
|
2
24
|
* Gemspec has been updated to require neo4j-core 3.0.5
|
3
25
|
* Added `find_in_batches`
|
data/README.md
CHANGED
@@ -6,6 +6,10 @@ Neo4j.rb is an Active Model compliant Ruby/JRuby wrapper for [the Neo4j graph da
|
|
6
6
|
Winner of a 2014 Graphie for "Best Community Contribution" at Neo4j's [Graph Connect](http://graphconnect.com) conference!
|
7
7
|
![2014 Graphie](http://i.imgur.com/CkOoTTYm.jpg)
|
8
8
|
|
9
|
+
## Coming Soon: v4!
|
10
|
+
|
11
|
+
The master branch of this repo now holds stable builds of the v4 version of the gem. Its changes are outlined at [here](https://github.com/neo4jrb/neo4j/wiki/Neo4j.rb-v4-Introduction). Unless otherwise noted, all 3.X documentation is totally valid. v4 discussion will happen [https://github.com/neo4jrb/neo4j/issues/596](https://github.com/neo4jrb/neo4j/issues/596) but if you have any concerns, please contact us directly.
|
12
|
+
|
9
13
|
## Modern (3.X) Documentation
|
10
14
|
|
11
15
|
* [Wiki](https://github.com/neo4jrb/neo4j/wiki/Neo4j.rb-v3-Introduction)
|
data/config/neo4j/config.yml
CHANGED
@@ -30,4 +30,6 @@ timestamps: true
|
|
30
30
|
# Alternatively, call class method Neo4j::ActiveNode:cache_class to set this on specific models.
|
31
31
|
# By default, this property is called _classname, set as symbol to override.
|
32
32
|
cache_class_names: true
|
33
|
-
# class_name_property: :_classname
|
33
|
+
# class_name_property: :_classname
|
34
|
+
|
35
|
+
transform_rel_type: :upcase
|
@@ -4,6 +4,7 @@ module Neo4j
|
|
4
4
|
module ActiveNode
|
5
5
|
module HasN
|
6
6
|
class Association
|
7
|
+
include Neo4j::Shared::RelTypeConverters
|
7
8
|
attr_reader :type, :name, :relationship, :direction
|
8
9
|
|
9
10
|
def initialize(type, direction, name, options = {})
|
@@ -60,7 +61,7 @@ module Neo4j
|
|
60
61
|
when @origin
|
61
62
|
origin_type
|
62
63
|
else
|
63
|
-
(create || exceptional_target_class?) &&
|
64
|
+
(create || exceptional_target_class?) && decorated_rel_type(@name)
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
@@ -136,8 +137,8 @@ module Neo4j
|
|
136
137
|
|
137
138
|
def validate_option_combinations(options)
|
138
139
|
raise ArgumentError, "Cannot specify both :type and :origin (#{base_declaration})" if options[:type] && options[:origin]
|
139
|
-
|
140
|
-
|
140
|
+
raise ArgumentError, "Cannot specify both :type and :rel_class (#{base_declaration})" if options[:type] && options[:rel_class]
|
141
|
+
raise ArgumentError, "Cannot specify both :origin and :rel_class (#{base_declaration}" if options[:origin] && options[:rel_class]
|
141
142
|
end
|
142
143
|
|
143
144
|
# Determine if model class as derived from the association name would be different than the one specified via the model_class key
|
@@ -13,17 +13,13 @@ module Neo4j
|
|
13
13
|
# @return the labels
|
14
14
|
# @see Neo4j-core
|
15
15
|
def labels
|
16
|
-
|
17
|
-
r = queried_labels
|
18
|
-
@_persisted_obj.labels = r
|
19
|
-
else
|
20
|
-
@_persisted_obj.labels
|
21
|
-
end
|
16
|
+
@_persisted_obj.labels
|
22
17
|
end
|
23
18
|
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
# this is handled by core, leaving it now for posterity
|
20
|
+
# def queried_labels
|
21
|
+
# self.class.query_as(:result).where("ID(result)" => self.neo_id).return("LABELS(result) as result_labels").first.result_labels.map(&:to_sym)
|
22
|
+
# end
|
27
23
|
|
28
24
|
# adds one or more labels
|
29
25
|
# @see Neo4j-core
|
@@ -72,7 +68,7 @@ module Neo4j
|
|
72
68
|
|
73
69
|
# Find all nodes/objects of this class
|
74
70
|
def all
|
75
|
-
|
71
|
+
Neo4j::ActiveNode::Query::QueryProxy.new(self, nil, {})
|
76
72
|
end
|
77
73
|
|
78
74
|
# Returns the object with the specified neo4j id.
|
@@ -131,8 +127,7 @@ module Neo4j
|
|
131
127
|
Neo4j::Session.on_session_available do |_|
|
132
128
|
_index(property, conf)
|
133
129
|
end
|
134
|
-
|
135
|
-
@_indexed_properties.push property unless @_indexed_properties.include? property
|
130
|
+
indexed_properties.push property unless indexed_properties.include? property
|
136
131
|
end
|
137
132
|
|
138
133
|
# Creates a neo4j constraint on this class for given property
|
@@ -174,7 +169,19 @@ module Neo4j
|
|
174
169
|
end
|
175
170
|
|
176
171
|
def indexed_properties
|
177
|
-
@_indexed_properties
|
172
|
+
@_indexed_properties ||= []
|
173
|
+
end
|
174
|
+
|
175
|
+
def base_class
|
176
|
+
unless self < Neo4j::ActiveNode
|
177
|
+
raise "#{name} doesn't belong in a hierarchy descending from ActiveNode"
|
178
|
+
end
|
179
|
+
|
180
|
+
if superclass == Object
|
181
|
+
self
|
182
|
+
else
|
183
|
+
superclass.base_class
|
184
|
+
end
|
178
185
|
end
|
179
186
|
|
180
187
|
|
@@ -9,7 +9,7 @@ module Neo4j
|
|
9
9
|
|
10
10
|
# The most recent node to start a QueryProxy chain.
|
11
11
|
# Will be nil when using QueryProxy chains on class methods.
|
12
|
-
attr_reader :caller
|
12
|
+
attr_reader :caller, :association, :model
|
13
13
|
|
14
14
|
# QueryProxy is ActiveNode's Cypher DSL. While the name might imply that it creates queries in a general sense,
|
15
15
|
# it is actually referring to <tt>Neo4j::Core::Query</tt>, which is a pure Ruby Cypher DSL provided by the <tt>neo4j-core</tt> gem.
|
@@ -50,6 +50,7 @@ module Neo4j
|
|
50
50
|
def identity
|
51
51
|
@node_var || :result
|
52
52
|
end
|
53
|
+
alias_method :node_identity, :identity
|
53
54
|
|
54
55
|
# The relationship identifier most recently used by the QueryProxy chain.
|
55
56
|
def rel_identity
|
@@ -106,7 +107,7 @@ module Neo4j
|
|
106
107
|
self.to_a == value
|
107
108
|
end
|
108
109
|
|
109
|
-
METHODS = %w[where order skip limit]
|
110
|
+
METHODS = %w[where rel_where order skip limit]
|
110
111
|
|
111
112
|
METHODS.each do |method|
|
112
113
|
module_eval(%Q{
|
@@ -114,7 +115,8 @@ module Neo4j
|
|
114
115
|
build_deeper_query_proxy(:#{method}, args)
|
115
116
|
end}, __FILE__, __LINE__)
|
116
117
|
end
|
117
|
-
|
118
|
+
# Since there is a rel_where method, it seems only natural for there to be node_where
|
119
|
+
alias_method :node_where, :where
|
118
120
|
alias_method :offset, :skip
|
119
121
|
alias_method :order_by, :order
|
120
122
|
|
@@ -139,7 +141,6 @@ module Neo4j
|
|
139
141
|
# @param [String,Symbol] var The identifier to use for node at this link of the QueryProxy chain.
|
140
142
|
# student.lessons.query_as(:l).with('your cypher here...')
|
141
143
|
def query_as(var)
|
142
|
-
var = @node_var if @node_var
|
143
144
|
query = if @association
|
144
145
|
chain_var = _association_chain_var
|
145
146
|
label_string = @model && ":`#{@model.mapped_label_name}`"
|
@@ -154,6 +155,25 @@ module Neo4j
|
|
154
155
|
end
|
155
156
|
end
|
156
157
|
|
158
|
+
|
159
|
+
# Scope all queries to the current scope.
|
160
|
+
#
|
161
|
+
# Comment.where(post_id: 1).scoping do
|
162
|
+
# Comment.first
|
163
|
+
# end
|
164
|
+
#
|
165
|
+
# TODO: unscoped
|
166
|
+
# Please check unscoped if you want to remove all previous scopes (including
|
167
|
+
# the default_scope) during the execution of a block.
|
168
|
+
def scoping
|
169
|
+
previous, @model.current_scope = @model.current_scope, self
|
170
|
+
yield
|
171
|
+
ensure
|
172
|
+
@model.current_scope = previous
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
|
157
177
|
# Cypher string for the QueryProxy's query. This will not include params. For the full output, see <tt>to_cypher_with_params</tt>.
|
158
178
|
def to_cypher
|
159
179
|
query.to_cypher
|
@@ -203,7 +223,8 @@ module Neo4j
|
|
203
223
|
start_object = @options[:start_object]
|
204
224
|
start_object.clear_association_cache
|
205
225
|
_session.query(context: @options[:context])
|
206
|
-
.
|
226
|
+
.match("(start#{match_string(start_object)}), (end#{match_string(other_node)})").where("ID(start) = {start_id} AND ID(end) = {end_id}")
|
227
|
+
.params(start_id: start_object.neo_id, end_id: other_node.neo_id)
|
207
228
|
.create("start#{_association_arrow(properties, true)}end").exec
|
208
229
|
|
209
230
|
@association.perform_callback(@options[:start_object], other_node, :after)
|
@@ -213,9 +234,10 @@ module Neo4j
|
|
213
234
|
|
214
235
|
# QueryProxy objects act as a representation of a model at the class level so we pass through calls
|
215
236
|
# This allows us to define class functions for reusable query chaining or for end-of-query aggregation/summarizing
|
216
|
-
def method_missing(method_name, *args)
|
237
|
+
def method_missing(method_name, *args, &block)
|
217
238
|
if @model && @model.respond_to?(method_name)
|
218
|
-
|
239
|
+
args[2] = self if @model.has_association?(method_name) || @model.has_scope?(method_name)
|
240
|
+
scoping { @model.public_send(method_name, *args, &block) }
|
219
241
|
else
|
220
242
|
super
|
221
243
|
end
|
@@ -291,12 +313,6 @@ module Neo4j
|
|
291
313
|
|
292
314
|
private
|
293
315
|
|
294
|
-
def call_class_method(method_name, *args)
|
295
|
-
args[2] = self
|
296
|
-
result = @model.send(method_name, *args)
|
297
|
-
result
|
298
|
-
end
|
299
|
-
|
300
316
|
def build_deeper_query_proxy(method, args)
|
301
317
|
self.dup.tap do |new_query|
|
302
318
|
args.each do |arg|
|
@@ -319,7 +335,7 @@ module Neo4j
|
|
319
335
|
node_num = 1
|
320
336
|
result = []
|
321
337
|
if arg.is_a?(Hash)
|
322
|
-
arg.
|
338
|
+
arg.each do |key, value|
|
323
339
|
if @model && @model.has_association?(key)
|
324
340
|
|
325
341
|
neo_id = value.try(:neo_id) || value
|
@@ -341,14 +357,23 @@ module Neo4j
|
|
341
357
|
end
|
342
358
|
result
|
343
359
|
end
|
360
|
+
alias_method :links_for_node_where_arg, :links_for_where_arg
|
361
|
+
|
362
|
+
# We don't accept strings here. If you want to use a string, just use where.
|
363
|
+
def links_for_rel_where_arg(arg)
|
364
|
+
arg.each_with_object([]) do |(key, value), result|
|
365
|
+
result << [:where, ->(v) {{ rel_identity => { key => value }}}]
|
366
|
+
end
|
367
|
+
end
|
344
368
|
|
345
369
|
def links_for_order_arg(arg)
|
346
370
|
[[:order, ->(v) { arg.is_a?(String) ? arg : {v => arg} }]]
|
347
371
|
end
|
348
372
|
|
349
|
-
|
373
|
+
def match_string(node)
|
374
|
+
":`#{node.class.mapped_label_name}`" if node.class.respond_to?(:mapped_label_name)
|
375
|
+
end
|
350
376
|
end
|
351
|
-
|
352
377
|
end
|
353
378
|
end
|
354
379
|
end
|
@@ -21,7 +21,7 @@ module Neo4j
|
|
21
21
|
raise(InvalidParameterError, ':count accepts `distinct` or nil as a parameter') unless distinct.nil? || distinct == :distinct
|
22
22
|
query_with_target(target) do |target|
|
23
23
|
q = distinct.nil? ? target : "DISTINCT #{target}"
|
24
|
-
self.query.pluck("count(#{q}) AS #{target}").first
|
24
|
+
self.query.reorder.pluck("count(#{q}) AS #{target}").first
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -64,15 +64,27 @@ module Neo4j
|
|
64
64
|
end
|
65
65
|
|
66
66
|
# Shorthand for `MATCH (start)-[r]-(other_node) WHERE ID(other_node) = #{other_node.neo_id}`
|
67
|
+
# The `node` param can be a persisted ActiveNode instance, any string or integer, or nil.
|
68
|
+
# When it's a node, it'll use the object's neo_id, which is fastest. When not nil, it'll figure out the
|
69
|
+
# primary key of that model. When nil, it uses `1 = 2` to prevent matching all records, which is the default
|
70
|
+
# behavior when nil is passed to `where` in QueryProxy.
|
67
71
|
# @return [Neo4j::ActiveNode::Query::QueryProxy] A QueryProxy object upon which you can build.
|
68
72
|
def match_to(node)
|
69
|
-
|
73
|
+
if node.respond_to?(:neo_id)
|
74
|
+
self.where(neo_id: node.neo_id)
|
75
|
+
elsif !node.nil?
|
76
|
+
id_key = self.association.nil? ? model.primary_key : self.association.target_class.primary_key
|
77
|
+
self.where(id_key => node)
|
78
|
+
else
|
79
|
+
# support for null object pattern
|
80
|
+
self.where('1 = 2')
|
81
|
+
end
|
70
82
|
end
|
71
83
|
|
72
84
|
# Gives you the first relationship between the last link of a QueryProxy chain and a given node
|
73
85
|
# Shorthand for `MATCH (start)-[r]-(other_node) WHERE ID(other_node) = #{other_node.neo_id} RETURN r`
|
74
86
|
def first_rel_to(node)
|
75
|
-
self.
|
87
|
+
self.match_to(node).limit(1).pluck(rel_identity).first
|
76
88
|
end
|
77
89
|
|
78
90
|
private
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_support/per_thread_registry'
|
2
|
+
|
1
3
|
module Neo4j::ActiveNode
|
2
4
|
module Scope
|
3
5
|
extend ActiveSupport::Concern
|
@@ -53,7 +55,9 @@ module Neo4j::ActiveNode
|
|
53
55
|
}, __FILE__, __LINE__)
|
54
56
|
end
|
55
57
|
|
56
|
-
|
58
|
+
def has_scope?(name)
|
59
|
+
_scope.has_key?(name.to_sym)
|
60
|
+
end
|
57
61
|
|
58
62
|
def _scope
|
59
63
|
@_scope ||= {}
|
@@ -68,6 +72,23 @@ module Neo4j::ActiveNode
|
|
68
72
|
end
|
69
73
|
|
70
74
|
|
75
|
+
def current_scope #:nodoc:
|
76
|
+
ScopeRegistry.value_for(:current_scope, base_class.to_s)
|
77
|
+
end
|
78
|
+
|
79
|
+
def current_scope=(scope) #:nodoc:
|
80
|
+
ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def all
|
85
|
+
if current_scope
|
86
|
+
current_scope.clone
|
87
|
+
else
|
88
|
+
self.as(:n)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
71
92
|
end
|
72
93
|
|
73
94
|
class ScopeEvalContext
|
@@ -79,9 +100,45 @@ module Neo4j::ActiveNode
|
|
79
100
|
Neo4j::ActiveNode::Query::QueryProxy::METHODS.each do |method|
|
80
101
|
module_eval(%Q{
|
81
102
|
def #{method}(params={})
|
82
|
-
|
103
|
+
@target.all.scoping do
|
104
|
+
(@query_proxy || @target).#{method}(params)
|
105
|
+
end
|
83
106
|
end}, __FILE__, __LINE__)
|
84
107
|
end
|
85
108
|
end
|
109
|
+
|
110
|
+
|
111
|
+
# Stolen from ActiveRecord
|
112
|
+
# https://github.com/rails/rails/blob/08754f12e65a9ec79633a605e986d0f1ffa4b251/activerecord/lib/active_record/scoping.rb#L57
|
113
|
+
class ScopeRegistry # :nodoc:
|
114
|
+
extend ActiveSupport::PerThreadRegistry
|
115
|
+
|
116
|
+
VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
|
117
|
+
|
118
|
+
def initialize
|
119
|
+
@registry = Hash.new { |hash, key| hash[key] = {} }
|
120
|
+
end
|
121
|
+
|
122
|
+
# Obtains the value for a given +scope_name+ and +variable_name+.
|
123
|
+
def value_for(scope_type, variable_name)
|
124
|
+
raise_invalid_scope_type!(scope_type)
|
125
|
+
@registry[scope_type][variable_name]
|
126
|
+
end
|
127
|
+
|
128
|
+
# Sets the +value+ for a given +scope_type+ and +variable_name+.
|
129
|
+
def set_value_for(scope_type, variable_name, value)
|
130
|
+
raise_invalid_scope_type!(scope_type)
|
131
|
+
@registry[scope_type][variable_name] = value
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def raise_invalid_scope_type!(scope_type)
|
137
|
+
if !VALID_SCOPE_TYPES.include?(scope_type)
|
138
|
+
raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
86
143
|
end
|
87
144
|
end
|
data/lib/neo4j/active_node.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Neo4j
|
2
2
|
|
3
|
-
# Makes Neo4j nodes and relationships behave like
|
3
|
+
# Makes Neo4j nodes and relationships behave like ActiveRecord objects.
|
4
4
|
# By including this module in your class it will create a mapping for the node to your ruby class
|
5
5
|
# by using a Neo4j Label with the same name as the class. When the node is loaded from the database it
|
6
6
|
# will check if there is a ruby class for the labels it has.
|
@@ -28,7 +28,7 @@ module Neo4j
|
|
28
28
|
include Neo4j::Shared::Identity
|
29
29
|
include Neo4j::ActiveNode::Initialize
|
30
30
|
include Neo4j::ActiveNode::IdProperty
|
31
|
-
include Neo4j::
|
31
|
+
include Neo4j::Shared::SerializedProperties
|
32
32
|
include Neo4j::ActiveNode::Property
|
33
33
|
include Neo4j::ActiveNode::Reflection
|
34
34
|
include Neo4j::ActiveNode::Persistence
|
@@ -48,21 +48,24 @@ module Neo4j
|
|
48
48
|
def self.inherited(other)
|
49
49
|
inherit_id_property(other) if self.has_id_property?
|
50
50
|
inherited_indexes(other) if self.respond_to?(:indexed_properties)
|
51
|
-
attributes.each_pair
|
52
|
-
|
53
|
-
end
|
51
|
+
attributes.each_pair { |k,v| other.attributes[k] = v }
|
52
|
+
inherit_serialized_properties(other) if self.respond_to?(:serialized_properties)
|
54
53
|
Neo4j::ActiveNode::Labels.add_wrapped_class(other)
|
55
54
|
super
|
56
55
|
end
|
57
56
|
|
58
57
|
def self.inherited_indexes(other)
|
59
58
|
return if indexed_properties.nil?
|
60
|
-
self.indexed_properties.each {|property| other.index property }
|
59
|
+
self.indexed_properties.each { |property| other.index property }
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.inherit_serialized_properties(other)
|
63
|
+
other.serialized_properties = self.serialized_properties
|
61
64
|
end
|
62
65
|
|
63
66
|
def self.inherit_id_property(other)
|
64
67
|
id_prop = self.id_property_info
|
65
|
-
conf = id_prop[:type].empty? ? {auto: :uuid} : id_prop[:type]
|
68
|
+
conf = id_prop[:type].empty? ? { auto: :uuid } : id_prop[:type]
|
66
69
|
other.id_property id_prop[:name], conf
|
67
70
|
end
|
68
71
|
|
@@ -72,11 +75,10 @@ module Neo4j
|
|
72
75
|
name = Neo4j::Config[:id_property]
|
73
76
|
type = Neo4j::Config[:id_property_type]
|
74
77
|
value = Neo4j::Config[:id_property_type_value]
|
75
|
-
if (name && type && value)
|
76
|
-
id_property(name, type => value)
|
77
|
-
end
|
78
|
+
id_property(name, type => value) if (name && type && value)
|
78
79
|
end
|
79
|
-
|
80
80
|
end
|
81
|
+
|
82
|
+
ActiveSupport.run_load_hooks(:active_node, self)
|
81
83
|
end
|
82
84
|
end
|
@@ -24,6 +24,7 @@ module Neo4j::ActiveRel
|
|
24
24
|
set_timestamps
|
25
25
|
properties = convert_properties_to :db, props
|
26
26
|
rel = _create_rel(from_node, to_node, properties)
|
27
|
+
return self unless rel.respond_to?(:_persisted_obj)
|
27
28
|
init_on_load(rel._persisted_obj, from_node, to_node, @rel_type)
|
28
29
|
true
|
29
30
|
end
|
@@ -61,7 +62,7 @@ module Neo4j::ActiveRel
|
|
61
62
|
def _create_rel(from_node, to_node, *args)
|
62
63
|
props = self.class.default_property_values(self)
|
63
64
|
props.merge!(args[0]) if args[0].is_a?(Hash)
|
64
|
-
set_classname(props)
|
65
|
+
set_classname(props, true)
|
65
66
|
_rel_creation_query(from_node, to_node, props)
|
66
67
|
end
|
67
68
|
|
@@ -99,4 +100,4 @@ module Neo4j::ActiveRel
|
|
99
100
|
end
|
100
101
|
|
101
102
|
end
|
102
|
-
end
|
103
|
+
end
|
@@ -42,16 +42,6 @@ module Neo4j::ActiveRel
|
|
42
42
|
alias_method :start_class, :from_class
|
43
43
|
alias_method :end_class, :to_class
|
44
44
|
|
45
|
-
# @param type [String] sets the relationship type when creating relationships via this class
|
46
|
-
def type(type = nil)
|
47
|
-
@rel_type = type
|
48
|
-
end
|
49
|
-
|
50
|
-
# @return [String] a string representing the relationship type that will be created
|
51
|
-
def _type
|
52
|
-
@rel_type
|
53
|
-
end
|
54
|
-
|
55
45
|
def load_entity(id)
|
56
46
|
Neo4j::Node.load(id)
|
57
47
|
end
|
@@ -26,10 +26,6 @@ module Neo4j::ActiveRel
|
|
26
26
|
where_query.where(where_string(args)).pluck(:r1)
|
27
27
|
end
|
28
28
|
|
29
|
-
def array_load(ids_array)
|
30
|
-
where_query.where("ID(r1) IN {ids_array}").params(ids_array: ids_array).pluck(:r1)
|
31
|
-
end
|
32
|
-
|
33
29
|
# Performs a basic match on the relationship, returning all results.
|
34
30
|
# This is not executed lazily, it will immediately return matching objects.
|
35
31
|
def all
|
@@ -3,16 +3,27 @@ class Neo4j::Relationship
|
|
3
3
|
module Wrapper
|
4
4
|
def wrapper
|
5
5
|
props.symbolize_keys!
|
6
|
-
return self unless props.is_a?(Hash)
|
6
|
+
# return self unless props.is_a?(Hash)
|
7
7
|
begin
|
8
|
-
|
8
|
+
most_concrete_class = sorted_wrapper_classes
|
9
|
+
wrapped_rel = most_concrete_class.constantize.new
|
9
10
|
rescue NameError
|
10
11
|
return self
|
11
12
|
end
|
12
|
-
|
13
|
+
|
13
14
|
wrapped_rel.init_on_load(self, self._start_node_id, self._end_node_id, self.rel_type)
|
14
15
|
wrapped_rel
|
15
16
|
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def sorted_wrapper_classes
|
21
|
+
props[Neo4j::Config.class_name_property] || class_from_type
|
22
|
+
end
|
23
|
+
|
24
|
+
def class_from_type
|
25
|
+
Neo4j::ActiveRel::Types::WRAPPED_CLASSES[rel_type] || rel_type.camelize
|
26
|
+
end
|
16
27
|
end
|
17
28
|
|
18
|
-
end
|
29
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module ActiveRel
|
3
|
+
|
4
|
+
# provides mapping of type to model name
|
5
|
+
module Types
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# WRAPPED_CLASSES maps relationship types to ActiveRel models.
|
9
|
+
#
|
10
|
+
# Typically, it's a 1:1 relationship, with a type having a model of the same name. Sometimes, someone needs to be a precious
|
11
|
+
# snowflake and have a model name that doesn't match the rel type, so this comes in handy.
|
12
|
+
#
|
13
|
+
# As an example, Chris often finds it easier to name models after the classes that use the relationship: `StudentLesson` instead of
|
14
|
+
# `EnrolledIn`, because it's easier to remember "A student has a relationship to lesson" than "the type of relationship between Student
|
15
|
+
# and Lesson is 'EnrolledIn'." After all, that is a big part of why we have models, right? To make our lives easier?
|
16
|
+
#
|
17
|
+
# A model is added to WRAPPED_CLASSES when it is initalized AND when the `type` class method is called within a model. This means that
|
18
|
+
# it's possible a model will be added twice: once with the rel_type version of the model name, again with the custom type. deal_with_it.gif.
|
19
|
+
#
|
20
|
+
# As an alternative to this, you can call the `set_classname` class method to insert a `_classname` property into your relationship,
|
21
|
+
# which will completely bypass this whole process.
|
22
|
+
WRAPPED_CLASSES = {}
|
23
|
+
|
24
|
+
included do |klazz|
|
25
|
+
type self.name, true
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
include Neo4j::Shared::RelTypeConverters
|
30
|
+
|
31
|
+
# @param type [String] sets the relationship type when creating relationships via this class
|
32
|
+
def type(given_type = self.name, auto = false)
|
33
|
+
use_type = auto ? decorated_rel_type(given_type) : given_type
|
34
|
+
add_wrapped_class use_type
|
35
|
+
@rel_type = use_type
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [String] a string representing the relationship type that will be created
|
39
|
+
def _type
|
40
|
+
@rel_type
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_wrapped_class(type)
|
44
|
+
#_wrapped_classes[type.to_sym.downcase] = self.name
|
45
|
+
_wrapped_classes[type.to_sym] = self.name
|
46
|
+
end
|
47
|
+
|
48
|
+
def _wrapped_classes
|
49
|
+
Neo4j::ActiveRel::Types::WRAPPED_CLASSES
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|