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.
@@ -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 _persisted_node
8
- _persisted_node
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