neo4j 3.0.0.alpha.9 → 3.0.0.alpha.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +3 -0
- data/Gemfile +2 -1
- data/lib/neo4j.rb +21 -4
- data/lib/neo4j/active_node.rb +5 -35
- data/lib/neo4j/active_node/callbacks.rb +1 -34
- data/lib/neo4j/active_node/has_n/association.rb +35 -16
- data/lib/neo4j/active_node/id_property.rb +1 -1
- data/lib/neo4j/active_node/initialize.rb +2 -2
- data/lib/neo4j/active_node/labels.rb +3 -3
- data/lib/neo4j/active_node/node_wrapper.rb +50 -0
- data/lib/neo4j/active_node/persistence.rb +2 -205
- data/lib/neo4j/active_node/property.rb +1 -194
- data/lib/neo4j/active_node/rels.rb +2 -2
- data/lib/neo4j/active_node/validations.rb +9 -41
- data/lib/neo4j/active_rel.rb +34 -0
- data/lib/neo4j/active_rel/callbacks.rb +13 -0
- data/lib/neo4j/active_rel/initialize.rb +30 -0
- data/lib/neo4j/active_rel/persistence.rb +80 -0
- data/lib/neo4j/active_rel/property.rb +64 -0
- data/lib/neo4j/active_rel/query.rb +66 -0
- data/lib/neo4j/active_rel/rel_wrapper.rb +18 -0
- data/lib/neo4j/active_rel/related_node.rb +42 -0
- data/lib/neo4j/active_rel/validations.rb +9 -0
- data/lib/neo4j/shared.rb +34 -0
- data/lib/neo4j/shared/callbacks.rb +40 -0
- data/lib/neo4j/{active_node → shared}/identity.rb +6 -2
- data/lib/neo4j/shared/persistence.rb +214 -0
- data/lib/neo4j/shared/property.rb +220 -0
- data/lib/neo4j/shared/validations.rb +48 -0
- data/lib/neo4j/version.rb +1 -1
- data/lib/neo4j/wrapper.rb +2 -50
- metadata +18 -5
- data/lib/neo4j/active_node/has_n/nodes.rb +0 -90
- data/lib/neo4j/active_node/quick_query.rb +0 -254
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
module Neo4j::ActiveRel
|
3
|
+
module Property
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include Neo4j::Shared::Property
|
6
|
+
|
7
|
+
%w[to_node from_node].each do |direction|
|
8
|
+
define_method("#{direction}") { instance_variable_get("@#{direction}") }
|
9
|
+
define_method("#{direction}=") do |argument|
|
10
|
+
raise FrozenRelError, "Relationship start/end nodes cannot be changed once persisted" if self.persisted?
|
11
|
+
instance_variable_set("@#{direction}", argument)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :start_node, :from_node
|
16
|
+
alias_method :end_node, :to_node
|
17
|
+
|
18
|
+
def type
|
19
|
+
self.class._type
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
|
24
|
+
# Extracts keys from attributes hash which are relationships of the model
|
25
|
+
# TODO: Validate separately that relationships are getting the right values? Perhaps also store the values and persist relationships on save?
|
26
|
+
def extract_association_attributes!(attributes)
|
27
|
+
attributes.keys.inject({}) do |relationship_props, key|
|
28
|
+
relationship_props[key] = attributes.delete(key) if key == :from_node || key == :to_node
|
29
|
+
|
30
|
+
relationship_props
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
%w[to_class from_class].each do |direction|
|
35
|
+
define_method("#{direction}") { |argument| instance_variable_set("@#{direction}", argument) }
|
36
|
+
define_method("_#{direction}") { instance_variable_get "@#{direction}" }
|
37
|
+
end
|
38
|
+
|
39
|
+
alias_method :start_class, :from_class
|
40
|
+
alias_method :end_class, :to_class
|
41
|
+
|
42
|
+
def type(type = nil)
|
43
|
+
@rel_type = type
|
44
|
+
end
|
45
|
+
|
46
|
+
def _type
|
47
|
+
@rel_type
|
48
|
+
end
|
49
|
+
|
50
|
+
def load_entity(id)
|
51
|
+
Neo4j::Node.load(id)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def load_nodes(start_node=nil, end_node=nil)
|
59
|
+
@from_node = RelatedNode.new(end_node)
|
60
|
+
@to_node = RelatedNode.new(start_node)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Neo4j::ActiveRel
|
2
|
+
module Query
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# Returns the object with the specified neo4j id.
|
9
|
+
# @param [String,Fixnum] id of node to find
|
10
|
+
# @param [Neo4j::Session] session optional
|
11
|
+
def find(id, session = self.neo4j_session)
|
12
|
+
raise "Unknown argument #{id.class} in find method (expected String or Fixnum)" if not [String, Fixnum].include?(id.class)
|
13
|
+
find_by_id(id, session)
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_by_id(key, session = Neo4j::Session.current!)
|
17
|
+
Neo4j::Relationship.load(key.to_i, session)
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO make this not awful
|
21
|
+
def where(args={})
|
22
|
+
@query = if self._from_class == :any
|
23
|
+
Neo4j::Session.query("MATCH n1-[r1:`#{self._type}`]->(#{cypher_node_string(:inbound)}) WHERE #{where_string(args)} RETURN r1")
|
24
|
+
else
|
25
|
+
self._from_class.query_as(:n1).match("(#{cypher_node_string(:outbound)})-[r1:`#{self._type}`]->(#{cypher_node_string(:inbound)})").where(Hash["r1" => args])
|
26
|
+
end
|
27
|
+
return self
|
28
|
+
end
|
29
|
+
|
30
|
+
def each
|
31
|
+
if self._from_class == :any
|
32
|
+
@query.map(&:r1)
|
33
|
+
else
|
34
|
+
@query.pluck(:r1)
|
35
|
+
end.each {|r| yield r }
|
36
|
+
end
|
37
|
+
|
38
|
+
def first
|
39
|
+
if self._from_class == :any
|
40
|
+
@query.map(&:r1)
|
41
|
+
else
|
42
|
+
@query.pluck(:r1)
|
43
|
+
end.first
|
44
|
+
end
|
45
|
+
|
46
|
+
def cypher_node_string(dir)
|
47
|
+
case dir
|
48
|
+
when :outbound
|
49
|
+
node_identifier, dir_class = 'n1', self._from_class
|
50
|
+
when :inbound
|
51
|
+
node_identifier, dir_class = 'n2', self._to_class
|
52
|
+
end
|
53
|
+
dir_class == :any ? node_identifier : "#{node_identifier}:`#{dir_class.name}`"
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def where_string(args)
|
59
|
+
args.map do |k, v|
|
60
|
+
v.is_a?(Integer) ? "r1.#{k} = #{v}" : "r1.#{k} = '#{v}'"
|
61
|
+
end.join(', ')
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Neo4j::Relationship
|
2
|
+
|
3
|
+
module Wrapper
|
4
|
+
def wrapper
|
5
|
+
props.symbolize_keys!
|
6
|
+
return self unless props.is_a?(Hash) && props.has_key?(Neo4j::Config.class_name_property)
|
7
|
+
begin
|
8
|
+
found_class = props[Neo4j::Config.class_name_property].constantize
|
9
|
+
rescue NameError
|
10
|
+
return self
|
11
|
+
end
|
12
|
+
wrapped_rel = found_class.new
|
13
|
+
wrapped_rel.init_on_load(self, self._start_node_id, self._end_node_id, self.rel_type)
|
14
|
+
wrapped_rel
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Neo4j::ActiveRel
|
2
|
+
# A container for ActiveRel's :inbound and :outbound methods. It provides lazy loading of nodes.
|
3
|
+
class RelatedNode
|
4
|
+
|
5
|
+
class InvalidParameterError < StandardError
|
6
|
+
def message
|
7
|
+
'RelatedNode must be initialized with either a node ID or node'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(node = nil)
|
12
|
+
@node = valid_node_param?(node) ? node : (raise InvalidParameterError.new(self))
|
13
|
+
end
|
14
|
+
|
15
|
+
def == (obj)
|
16
|
+
loaded if @node.is_a?(Fixnum)
|
17
|
+
@node == obj
|
18
|
+
end
|
19
|
+
|
20
|
+
def loaded
|
21
|
+
@node = @node.respond_to?(:neo_id) ? @node : Neo4j::Node.load(@node)
|
22
|
+
end
|
23
|
+
|
24
|
+
def loaded?
|
25
|
+
@node.respond_to?(:neo_id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(*args, &block)
|
29
|
+
loaded.send(*args, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def class
|
33
|
+
loaded.send(:class)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def valid_node_param?(node)
|
39
|
+
node.nil? || node.is_a?(Integer) || node.respond_to?(:neo_id)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/neo4j/shared.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Shared
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
extend ActiveModel::Naming
|
5
|
+
|
6
|
+
include ActiveModel::Conversion
|
7
|
+
include ActiveModel::Serializers::Xml
|
8
|
+
include ActiveModel::Serializers::JSON
|
9
|
+
|
10
|
+
include Neo4j::Shared::Identity
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def neo4j_session_name (name)
|
14
|
+
@neo4j_session_name = name
|
15
|
+
end
|
16
|
+
|
17
|
+
def neo4j_session
|
18
|
+
if @neo4j_session_name
|
19
|
+
Neo4j::Session.named(@neo4j_session_name) || raise("#{self.name} is configured to use a neo4j session named #{@neo4j_session_name}, but no such session is registered with Neo4j::Session")
|
20
|
+
else
|
21
|
+
Neo4j::Session.current
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
included do
|
27
|
+
self.include_root_in_json = true
|
28
|
+
|
29
|
+
def self.i18n_scope
|
30
|
+
:neo4j
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Shared
|
3
|
+
module Callbacks #:nodoc:
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
include ActiveModel::Callbacks
|
8
|
+
end
|
9
|
+
|
10
|
+
included do
|
11
|
+
include ActiveModel::Validations::Callbacks
|
12
|
+
define_model_callbacks :initialize, :find, :only => :after
|
13
|
+
define_model_callbacks :save, :create, :update, :destroy
|
14
|
+
end
|
15
|
+
|
16
|
+
def destroy #:nodoc:
|
17
|
+
run_callbacks(:destroy) { super }
|
18
|
+
end
|
19
|
+
|
20
|
+
def touch(*) #:nodoc:
|
21
|
+
run_callbacks(:touch) { super }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def create_or_update #:nodoc:
|
27
|
+
run_callbacks(:save) { super }
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_model #:nodoc:
|
31
|
+
run_callbacks(:create) { super }
|
32
|
+
end
|
33
|
+
|
34
|
+
def update_model(*) #:nodoc:
|
35
|
+
run_callbacks(:update) { super }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module Neo4j::
|
1
|
+
module Neo4j::Shared
|
2
2
|
module Identity
|
3
3
|
def ==(o)
|
4
4
|
o.class == self.class && o.id == id
|
@@ -13,12 +13,16 @@ module Neo4j::ActiveNode
|
|
13
13
|
|
14
14
|
# @return [Fixnum, nil] the neo4j id of the node if persisted or nil
|
15
15
|
def neo_id
|
16
|
-
|
16
|
+
_persisted_obj ? _persisted_obj.neo_id : nil
|
17
17
|
end
|
18
18
|
|
19
19
|
def id
|
20
20
|
id = neo_id
|
21
21
|
id.is_a?(Integer) ? id : nil
|
22
22
|
end
|
23
|
+
|
24
|
+
def hash
|
25
|
+
id.hash
|
26
|
+
end
|
23
27
|
end
|
24
28
|
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module Neo4j::Shared
|
2
|
+
module Persistence
|
3
|
+
|
4
|
+
class RecordInvalidError < RuntimeError
|
5
|
+
attr_reader :record
|
6
|
+
|
7
|
+
def initialize(record)
|
8
|
+
@record = record
|
9
|
+
super(@record.errors.full_messages.join(", "))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
include Neo4j::TypeConverters
|
15
|
+
|
16
|
+
# Saves the model.
|
17
|
+
#
|
18
|
+
# If the model is new a record gets created in the database, otherwise the existing record gets updated.
|
19
|
+
# If perform_validation is true validations run.
|
20
|
+
# If any of them fail the action is cancelled and save returns false. If the flag is false validations are bypassed altogether. See ActiveRecord::Validations for more information.
|
21
|
+
# There’s a series of callbacks associated with save. If any of the before_* callbacks return false the action is cancelled and save returns false.
|
22
|
+
def save(*)
|
23
|
+
update_magic_properties
|
24
|
+
create_or_update
|
25
|
+
end
|
26
|
+
|
27
|
+
# Persist the object to the database. Validations and Callbacks are included
|
28
|
+
# by default but validation can be disabled by passing :validate => false
|
29
|
+
# to #save! Creates a new transaction.
|
30
|
+
#
|
31
|
+
# @raise a RecordInvalidError if there is a problem during save.
|
32
|
+
# @param (see Neo4j::Rails::Validations#save)
|
33
|
+
# @return nil
|
34
|
+
# @see #save
|
35
|
+
# @see Neo4j::Rails::Validations Neo4j::Rails::Validations - for the :validate parameter
|
36
|
+
# @see Neo4j::Rails::Callbacks Neo4j::Rails::Callbacks - for callbacks
|
37
|
+
def save!(*args)
|
38
|
+
unless save(*args)
|
39
|
+
raise RecordInvalidError.new(self)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def update_model
|
44
|
+
if changed_attributes && !changed_attributes.empty?
|
45
|
+
changed_props = attributes.select{|k,v| changed_attributes.include?(k)}
|
46
|
+
changed_props = convert_properties_to :db, changed_props
|
47
|
+
_persisted_obj.update_props(changed_props)
|
48
|
+
changed_attributes.clear
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# Convenience method to set attribute and #save at the same time
|
54
|
+
# @param [Symbol, String] attribute of the attribute to update
|
55
|
+
# @param [Object] value to set
|
56
|
+
def update_attribute(attribute, value)
|
57
|
+
send("#{attribute}=", value)
|
58
|
+
self.save
|
59
|
+
end
|
60
|
+
|
61
|
+
# Convenience method to set attribute and #save! at the same time
|
62
|
+
# @param [Symbol, String] attribute of the attribute to update
|
63
|
+
# @param [Object] value to set
|
64
|
+
def update_attribute!(attribute, value)
|
65
|
+
send("#{attribute}=", value)
|
66
|
+
self.save!
|
67
|
+
end
|
68
|
+
|
69
|
+
# Convenience method to set multiple attributes and #save at the same time
|
70
|
+
# @param [Hash] attributes of names and values of attributes to set
|
71
|
+
def update_attributes(attributes)
|
72
|
+
assign_attributes(attributes)
|
73
|
+
self.save
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_or_update
|
77
|
+
# since the same model can be created or updated twice from a relationship we have to have this guard
|
78
|
+
@_create_or_updating = true
|
79
|
+
result = persisted? ? update_model : create_model
|
80
|
+
unless result != false
|
81
|
+
Neo4j::Transaction.current.fail if Neo4j::Transaction.current
|
82
|
+
false
|
83
|
+
else
|
84
|
+
true
|
85
|
+
end
|
86
|
+
rescue => e
|
87
|
+
Neo4j::Transaction.current.fail if Neo4j::Transaction.current
|
88
|
+
raise e
|
89
|
+
ensure
|
90
|
+
@_create_or_updating = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns +true+ if the record is persisted, i.e. it’s not a new record and it was not destroyed
|
94
|
+
def persisted?
|
95
|
+
!new_record? && !destroyed?
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns +true+ if the record hasn't been saved to Neo4j yet.
|
99
|
+
def new_record?
|
100
|
+
!_persisted_obj
|
101
|
+
end
|
102
|
+
|
103
|
+
alias :new? :new_record?
|
104
|
+
|
105
|
+
def destroy
|
106
|
+
_persisted_obj && _persisted_obj.del
|
107
|
+
@_deleted = true
|
108
|
+
end
|
109
|
+
|
110
|
+
def exist?
|
111
|
+
_persisted_obj && _persisted_obj.exist?
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns +true+ if the object was destroyed.
|
115
|
+
def destroyed?
|
116
|
+
@_deleted || (!new_record? && !exist?)
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
# @return [Hash] all defined and none nil properties
|
121
|
+
def props
|
122
|
+
attributes.reject{|k,v| v.nil?}.symbolize_keys
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return true if the attributes hash has been frozen
|
126
|
+
def frozen?
|
127
|
+
freeze_if_deleted
|
128
|
+
@attributes.frozen?
|
129
|
+
end
|
130
|
+
|
131
|
+
def freeze
|
132
|
+
@attributes.freeze
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
def freeze_if_deleted
|
137
|
+
unless new_record?
|
138
|
+
# TODO - Neo4j::IdentityMap.remove_node_by_id(neo_id)
|
139
|
+
unless self.class.load_entity(neo_id)
|
140
|
+
@_deleted = true
|
141
|
+
freeze
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def reload
|
147
|
+
return self if new_record?
|
148
|
+
changed_attributes && changed_attributes.clear
|
149
|
+
unless reload_from_database
|
150
|
+
@_deleted = true
|
151
|
+
freeze
|
152
|
+
end
|
153
|
+
self
|
154
|
+
end
|
155
|
+
|
156
|
+
def reload_from_database
|
157
|
+
# TODO - Neo4j::IdentityMap.remove_node_by_id(neo_id)
|
158
|
+
if reloaded = self.class.load_entity(neo_id)
|
159
|
+
send(:attributes=, reloaded.attributes)
|
160
|
+
end
|
161
|
+
reloaded
|
162
|
+
end
|
163
|
+
|
164
|
+
# Updates this resource with all the attributes from the passed-in Hash and requests that the record be saved.
|
165
|
+
# If saving fails because the resource is invalid then false will be returned.
|
166
|
+
def update(attributes)
|
167
|
+
self.attributes = attributes
|
168
|
+
save
|
169
|
+
end
|
170
|
+
alias_method :update_attributes, :update
|
171
|
+
|
172
|
+
# Same as {#update_attributes}, but raises an exception if saving fails.
|
173
|
+
def update!(attributes)
|
174
|
+
self.attributes = attributes
|
175
|
+
save!
|
176
|
+
end
|
177
|
+
alias_method :update_attributes!, :update!
|
178
|
+
|
179
|
+
def cache_key
|
180
|
+
if self.new_record?
|
181
|
+
"#{self.class.model_name.cache_key}/new"
|
182
|
+
elsif self.respond_to?(:updated_at) && !self.updated_at.blank?
|
183
|
+
"#{self.class.model_name.cache_key}/#{neo_id}-#{self.updated_at.utc.to_s(:number)}"
|
184
|
+
else
|
185
|
+
"#{self.class.model_name.cache_key}/#{neo_id}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
def create_magic_properties
|
192
|
+
end
|
193
|
+
|
194
|
+
def update_magic_properties
|
195
|
+
self.updated_at = DateTime.now if respond_to?(:updated_at=) && changed?
|
196
|
+
end
|
197
|
+
|
198
|
+
def set_classname(props)
|
199
|
+
props[:_classname] = self.class.name if self.class.cached_class?
|
200
|
+
end
|
201
|
+
|
202
|
+
# def assign_attributes(attributes)
|
203
|
+
# attributes.each do |attribute, value|
|
204
|
+
# send("#{attribute}=", value)
|
205
|
+
# end
|
206
|
+
# end
|
207
|
+
|
208
|
+
def set_timestamps
|
209
|
+
self.created_at = DateTime.now if respond_to?(:created_at=)
|
210
|
+
self.updated_at = self.created_at if respond_to?(:updated_at=)
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|