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
@@ -1,183 +1,10 @@
|
|
1
1
|
module Neo4j::ActiveNode
|
2
2
|
module Property
|
3
3
|
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
include ActiveAttr::Attributes
|
6
|
-
include ActiveAttr::MassAssignment
|
7
|
-
include ActiveAttr::TypecastedAttributes
|
8
|
-
include ActiveAttr::AttributeDefaults
|
9
|
-
include ActiveAttr::QueryAttributes
|
10
|
-
include ActiveModel::Dirty
|
11
|
-
|
12
|
-
class UndefinedPropertyError < RuntimeError; end
|
13
|
-
class MultiparameterAssignmentError < StandardError; end
|
14
|
-
|
15
|
-
def initialize(attributes={}, options={})
|
16
|
-
attributes = process_attributes(attributes)
|
17
|
-
|
18
|
-
writer_method_props = extract_writer_methods!(attributes)
|
19
|
-
validate_attributes!(attributes)
|
20
|
-
writer_method_props.each do |key, value|
|
21
|
-
self.send("#{key}=", value)
|
22
|
-
end
|
23
|
-
|
24
|
-
super(attributes, options)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Returning nil when we get ActiveAttr::UnknownAttributeError from ActiveAttr
|
28
|
-
def read_attribute(name)
|
29
|
-
super(name)
|
30
|
-
rescue ActiveAttr::UnknownAttributeError
|
31
|
-
nil
|
32
|
-
end
|
33
|
-
alias_method :[], :read_attribute
|
34
|
-
|
35
|
-
def default_properties=(properties)
|
36
|
-
keys = self.class.default_properties.keys
|
37
|
-
@default_properties = properties.reject{|key| !keys.include?(key)}
|
38
|
-
end
|
39
|
-
|
40
|
-
def default_property(key)
|
41
|
-
keys = self.class.default_properties.keys
|
42
|
-
keys.include?(key.to_sym) ? default_properties[key.to_sym] : nil
|
43
|
-
end
|
44
|
-
|
45
|
-
def default_properties
|
46
|
-
@default_properties ||= {}
|
47
|
-
# keys = self.class.default_properties.keys
|
48
|
-
# _persisted_node.props.reject{|key| !keys.include?(key)}
|
49
|
-
end
|
50
|
-
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
# Changes attributes hash to remove relationship keys
|
55
|
-
# Raises an error if there are any keys left which haven't been defined as properties on the model
|
56
|
-
def validate_attributes!(attributes)
|
57
|
-
invalid_properties = attributes.keys.map(&:to_s) - self.attributes.keys
|
58
|
-
raise UndefinedPropertyError, "Undefined properties: #{invalid_properties.join(',')}" if invalid_properties.size > 0
|
59
|
-
end
|
60
|
-
|
61
|
-
def extract_writer_methods!(attributes)
|
62
|
-
attributes.keys.inject({}) do |writer_method_props, key|
|
63
|
-
writer_method_props[key] = attributes.delete(key) if self.respond_to?("#{key}=")
|
64
|
-
|
65
|
-
writer_method_props
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Gives support for Rails date_select, datetime_select, time_select helpers.
|
70
|
-
def process_attributes(attributes = nil)
|
71
|
-
multi_parameter_attributes = {}
|
72
|
-
new_attributes = {}
|
73
|
-
attributes.each_pair do |key, value|
|
74
|
-
if key =~ /\A([^\(]+)\((\d+)([if])\)$/
|
75
|
-
found_key, index = $1, $2.to_i
|
76
|
-
(multi_parameter_attributes[found_key] ||= {})[index] = value.empty? ? nil : value.send("to_#{$3}")
|
77
|
-
else
|
78
|
-
new_attributes[key] = value
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
multi_parameter_attributes.empty? ? new_attributes : process_multiparameter_attributes(multi_parameter_attributes, new_attributes)
|
83
|
-
end
|
84
|
-
|
85
|
-
def process_multiparameter_attributes(multi_parameter_attributes, new_attributes)
|
86
|
-
multi_parameter_attributes.each_pair do |key, values|
|
87
|
-
begin
|
88
|
-
values = (values.keys.min..values.keys.max).map { |i| values[i] }
|
89
|
-
field = self.class.attributes[key.to_sym]
|
90
|
-
new_attributes[key] = instantiate_object(field, values)
|
91
|
-
rescue => e
|
92
|
-
raise MultiparameterAssignmentError, "error on assignment #{values.inspect} to #{key}"
|
93
|
-
end
|
94
|
-
end
|
95
|
-
new_attributes
|
96
|
-
end
|
97
|
-
|
98
|
-
def instantiate_object(field, values_with_empty_parameters)
|
99
|
-
return nil if values_with_empty_parameters.all? { |v| v.nil? }
|
100
|
-
values = values_with_empty_parameters.collect { |v| v.nil? ? 1 : v }
|
101
|
-
klass = field[:type]
|
102
|
-
if klass
|
103
|
-
klass.new(*values)
|
104
|
-
else
|
105
|
-
values
|
106
|
-
end
|
107
|
-
end
|
4
|
+
include Neo4j::Shared::Property
|
108
5
|
|
109
6
|
module ClassMethods
|
110
7
|
|
111
|
-
# Defines a property on the class
|
112
|
-
#
|
113
|
-
# See active_attr gem for allowed options, e.g which type
|
114
|
-
# Notice, in Neo4j you don't have to declare properties before using them, see the neo4j-core api.
|
115
|
-
#
|
116
|
-
# @example Without type
|
117
|
-
# class Person
|
118
|
-
# # declare a property which can have any value
|
119
|
-
# property :name
|
120
|
-
# end
|
121
|
-
#
|
122
|
-
# @example With type and a default value
|
123
|
-
# class Person
|
124
|
-
# # declare a property which can have any value
|
125
|
-
# property :score, type: Integer, default: 0
|
126
|
-
# end
|
127
|
-
#
|
128
|
-
# @example With an index
|
129
|
-
# class Person
|
130
|
-
# # declare a property which can have any value
|
131
|
-
# property :name, index: :exact
|
132
|
-
# end
|
133
|
-
#
|
134
|
-
# @example With a constraint
|
135
|
-
# class Person
|
136
|
-
# # declare a property which can have any value
|
137
|
-
# property :name, constraint: :unique
|
138
|
-
# end
|
139
|
-
def property(name, options={})
|
140
|
-
magic_properties(name, options)
|
141
|
-
attribute(name, options)
|
142
|
-
|
143
|
-
# either constraint or index, do not set both
|
144
|
-
if options[:constraint]
|
145
|
-
raise "unknown constraint type #{options[:constraint]}, only :unique supported" if options[:constraint] != :unique
|
146
|
-
constraint(name, type: :unique)
|
147
|
-
elsif options[:index]
|
148
|
-
raise "unknown index type #{options[:index]}, only :exact supported" if options[:index] != :exact
|
149
|
-
index(name, options) if options[:index] == :exact
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def default_property(name, &block)
|
154
|
-
default_properties[name] = block
|
155
|
-
end
|
156
|
-
|
157
|
-
# @return [Hash<Symbol,Proc>]
|
158
|
-
def default_properties
|
159
|
-
@default_property ||= {}
|
160
|
-
end
|
161
|
-
|
162
|
-
def default_property_values(instance)
|
163
|
-
default_properties.inject({}) do |result,pair|
|
164
|
-
result.tap{|obj| obj[pair[0]] = pair[1].call(instance)}
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def attribute!(name, options={})
|
169
|
-
super(name, options)
|
170
|
-
define_method("#{name}=") do |value|
|
171
|
-
typecast_value = typecast_attribute(typecaster_for(self.class._attribute_type(name)), value)
|
172
|
-
send("#{name}_will_change!") unless typecast_value == read_attribute(name)
|
173
|
-
super(value)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
def cached_class?
|
178
|
-
!!Neo4j::Config[:cache_class_names]
|
179
|
-
end
|
180
|
-
|
181
8
|
# Extracts keys from attributes hash which are relationships of the model
|
182
9
|
# TODO: Validate separately that relationships are getting the right values? Perhaps also store the values and persist relationships on save?
|
183
10
|
def extract_association_attributes!(attributes)
|
@@ -187,26 +14,6 @@ module Neo4j::ActiveNode
|
|
187
14
|
association_props
|
188
15
|
end
|
189
16
|
end
|
190
|
-
|
191
|
-
private
|
192
|
-
|
193
|
-
# Tweaks properties
|
194
|
-
def magic_properties(name, options)
|
195
|
-
set_stamp_type(name, options)
|
196
|
-
set_time_as_datetime(options)
|
197
|
-
end
|
198
|
-
|
199
|
-
def set_stamp_type(name, options)
|
200
|
-
options[:type] = DateTime if (name.to_sym == :created_at || name.to_sym == :updated_at)
|
201
|
-
end
|
202
|
-
|
203
|
-
# ActiveAttr does not handle "Time", Rails and Neo4j.rb 2.3 did
|
204
|
-
# Convert it to DateTime in the interest of consistency
|
205
|
-
def set_time_as_datetime(options)
|
206
|
-
options[:type] = DateTime if options[:type] == Time
|
207
|
-
end
|
208
|
-
|
209
17
|
end
|
210
18
|
end
|
211
|
-
|
212
19
|
end
|
@@ -4,8 +4,8 @@ module Neo4j::ActiveNode
|
|
4
4
|
def_delegators :_rels_delegator, :rel?, :rel, :rels, :node, :nodes, :create_rel
|
5
5
|
|
6
6
|
def _rels_delegator
|
7
|
-
raise "Can't access relationship on a non persisted node" unless
|
8
|
-
|
7
|
+
raise "Can't access relationship on a non persisted node" unless _persisted_obj
|
8
|
+
_persisted_obj
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
@@ -3,33 +3,8 @@ module Neo4j
|
|
3
3
|
# This mixin replace the original save method and performs validation before the save.
|
4
4
|
module Validations
|
5
5
|
extend ActiveSupport::Concern
|
6
|
+
include Neo4j::Shared::Validations
|
6
7
|
|
7
|
-
include ActiveModel::Validations
|
8
|
-
|
9
|
-
module ClassMethods
|
10
|
-
def validates_uniqueness_of(*attr_names)
|
11
|
-
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
# Implements the ActiveModel::Validation hook method.
|
16
|
-
# @see http://rubydoc.info/docs/rails/ActiveModel/Validations:read_attribute_for_validation
|
17
|
-
def read_attribute_for_validation(key)
|
18
|
-
respond_to?(key) ? send(key) : self[key]
|
19
|
-
end
|
20
|
-
|
21
|
-
# The validation process on save can be skipped by passing false. The regular Model#save method is
|
22
|
-
# replaced with this when the validations module is mixed in, which it is by default.
|
23
|
-
# @param [Hash] options the options to create a message with.
|
24
|
-
# @option options [true, false] :validate if false no validation will take place
|
25
|
-
# @return [Boolean] true if it saved it successfully
|
26
|
-
def save(options={})
|
27
|
-
result = perform_validations(options) ? super : false
|
28
|
-
if !result
|
29
|
-
Neo4j::Transaction.current.failure if Neo4j::Transaction.current
|
30
|
-
end
|
31
|
-
result
|
32
|
-
end
|
33
8
|
|
34
9
|
# @return [Boolean] true if valid
|
35
10
|
def valid?(context = nil)
|
@@ -38,6 +13,13 @@ module Neo4j
|
|
38
13
|
errors.empty?
|
39
14
|
end
|
40
15
|
|
16
|
+
module ClassMethods
|
17
|
+
def validates_uniqueness_of(*attr_names)
|
18
|
+
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
41
23
|
class UniquenessValidator < ::ActiveModel::EachValidator
|
42
24
|
def initialize(options)
|
43
25
|
super(options.reverse_merge(:case_sensitive => true))
|
@@ -57,8 +39,7 @@ module Neo4j
|
|
57
39
|
|
58
40
|
# prevent that same object is returned
|
59
41
|
# TODO: add negative condtion to not return current record
|
60
|
-
found = record.class.where(conditions).to_a
|
61
|
-
found.delete(record)
|
42
|
+
found = record.class.where(conditions).to_a.select{|f| f.neo_id != record.neo_id}
|
62
43
|
|
63
44
|
if found.count > 0
|
64
45
|
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
|
@@ -76,19 +57,6 @@ module Neo4j
|
|
76
57
|
end
|
77
58
|
end
|
78
59
|
|
79
|
-
private
|
80
|
-
def perform_validations(options={})
|
81
|
-
perform_validation = case options
|
82
|
-
when Hash
|
83
|
-
options[:validate] != false
|
84
|
-
end
|
85
|
-
|
86
|
-
if perform_validation
|
87
|
-
valid?(options.is_a?(Hash) ? options[:context] : nil)
|
88
|
-
else
|
89
|
-
true
|
90
|
-
end
|
91
|
-
end
|
92
60
|
end
|
93
61
|
end
|
94
62
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Neo4j
|
2
|
+
|
3
|
+
# Makes Neo4j Relationships more or less act like ActiveRecord objects.
|
4
|
+
module ActiveRel
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
include Neo4j::Shared
|
8
|
+
include Neo4j::ActiveRel::Initialize
|
9
|
+
include Neo4j::ActiveRel::Property
|
10
|
+
include Neo4j::ActiveRel::Persistence
|
11
|
+
include Neo4j::ActiveRel::Validations
|
12
|
+
include Neo4j::ActiveRel::Callbacks
|
13
|
+
include Neo4j::ActiveRel::Query
|
14
|
+
|
15
|
+
class FrozenRelError < StandardError; end
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
load_nodes
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def neo4j_obj
|
23
|
+
_persisted_obj || raise("Tried to access native neo4j object on a non persisted object")
|
24
|
+
end
|
25
|
+
|
26
|
+
included do
|
27
|
+
def self.inherited(other)
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
cache_class unless cached_class?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module ActiveRel
|
3
|
+
module Callbacks #:nodoc:
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include Neo4j::Shared::Callbacks
|
6
|
+
|
7
|
+
def save(*args)
|
8
|
+
raise Neo4j::ActiveRel::Persistence::RelInvalidError.new(self) unless self.persisted? || (from_node.respond_to?(:neo_id) && to_node.respond_to?(:neo_id))
|
9
|
+
super(*args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Neo4j::ActiveRel
|
2
|
+
module Initialize
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Neo4j::TypeConverters
|
5
|
+
|
6
|
+
attr_reader :_persisted_obj
|
7
|
+
|
8
|
+
# called when loading the rel from the database
|
9
|
+
# @param [Hash] properties properties of this relationship
|
10
|
+
# @param [Neo4j::Relationship] start_node the starting node in the relationship.
|
11
|
+
# @param [Neo4j::Relationship] end_node the ending node in the relationship
|
12
|
+
# @param [String] type the relationship type
|
13
|
+
def init_on_load(persisted_rel, start_node_id, end_node_id, type)
|
14
|
+
@_persisted_obj = persisted_rel
|
15
|
+
@rel_type = type
|
16
|
+
changed_attributes && changed_attributes.clear
|
17
|
+
@attributes = attributes.merge(persisted_rel.props.stringify_keys)
|
18
|
+
load_nodes(start_node_id, end_node_id)
|
19
|
+
self.default_properties = persisted_rel.props
|
20
|
+
@attributes = convert_properties_to :ruby, @attributes
|
21
|
+
end
|
22
|
+
|
23
|
+
# Implements the Neo4j::Node#wrapper and Neo4j::Relationship#wrapper method
|
24
|
+
# so that we don't have to care if the node is wrapped or not.
|
25
|
+
# @return self
|
26
|
+
def wrapper
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Neo4j::ActiveRel
|
2
|
+
module Persistence
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Neo4j::Shared::Persistence
|
5
|
+
|
6
|
+
class RelInvalidError < RuntimeError
|
7
|
+
attr_reader :record
|
8
|
+
|
9
|
+
def initialize(record)
|
10
|
+
@record = record
|
11
|
+
super(@record.errors.full_messages.join(", "))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ModelClassInvalidError < RuntimeError; end
|
16
|
+
|
17
|
+
def save(*)
|
18
|
+
update_magic_properties
|
19
|
+
create_or_update
|
20
|
+
end
|
21
|
+
|
22
|
+
def save!(*args)
|
23
|
+
unless save(*args)
|
24
|
+
raise RelInvalidError.new(self)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_model(*)
|
29
|
+
confirm_node_classes
|
30
|
+
create_magic_properties
|
31
|
+
set_timestamps
|
32
|
+
properties = convert_properties_to :db, props
|
33
|
+
rel = _create_rel(properties)
|
34
|
+
init_on_load(rel, to_node, from_node, @rel_type)
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
|
40
|
+
# Creates a new relationship between objects
|
41
|
+
# @param [Hash] props the properties the new relationship should have
|
42
|
+
def create(props = {})
|
43
|
+
relationship_props = extract_association_attributes!(props) || {}
|
44
|
+
new(props).tap do |obj|
|
45
|
+
relationship_props.each do |prop, value|
|
46
|
+
obj.send("#{prop}=", value)
|
47
|
+
end
|
48
|
+
obj.save
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Same as #create, but raises an error if there is a problem during save.
|
53
|
+
def create!(*args)
|
54
|
+
unless create(*args)
|
55
|
+
raise RelInvalidError.new(self)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def confirm_node_classes
|
63
|
+
[from_node, to_node].each do |node|
|
64
|
+
check = from_node == node ? :_from_class : :_to_class
|
65
|
+
next if self.class.send(check) == :any
|
66
|
+
unless self.class.send(check) == node.class
|
67
|
+
raise ModelClassInvalidError, "Node class was #{node.class}, expected #{self.class.send(check)}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def _create_rel(*args)
|
73
|
+
session = self.class.neo4j_session
|
74
|
+
props = self.class.default_property_values(self)
|
75
|
+
props.merge!(args[0]) if args[0].is_a?(Hash)
|
76
|
+
set_classname(props)
|
77
|
+
from_node.create_rel(type, to_node, props)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|