neo4j 3.0.0.alpha.9 → 3.0.0.alpha.10
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 +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
|