neo4j 1.0.0.beta.21-java

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.
Files changed (99) hide show
  1. data/CHANGELOG +141 -0
  2. data/CONTRIBUTORS +17 -0
  3. data/Gemfile +16 -0
  4. data/README.rdoc +135 -0
  5. data/lib/generators/neo4j.rb +65 -0
  6. data/lib/generators/neo4j/model/model_generator.rb +39 -0
  7. data/lib/generators/neo4j/model/templates/model.erb +7 -0
  8. data/lib/neo4j.rb +77 -0
  9. data/lib/neo4j/config.rb +153 -0
  10. data/lib/neo4j/database.rb +56 -0
  11. data/lib/neo4j/equal.rb +21 -0
  12. data/lib/neo4j/event_handler.rb +116 -0
  13. data/lib/neo4j/index/class_methods.rb +62 -0
  14. data/lib/neo4j/index/index.rb +33 -0
  15. data/lib/neo4j/index/indexer.rb +312 -0
  16. data/lib/neo4j/index/indexer_registry.rb +68 -0
  17. data/lib/neo4j/index/lucene_query.rb +191 -0
  18. data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
  19. data/lib/neo4j/jars/lucene-core-3.0.2.jar +0 -0
  20. data/lib/neo4j/jars/neo4j-index-1.2-1.2.M03.jar +0 -0
  21. data/lib/neo4j/jars/neo4j-kernel-1.2-1.2.M03.jar +0 -0
  22. data/lib/neo4j/jars/neo4j-lucene-index-0.2-1.2.M03.jar +0 -0
  23. data/lib/neo4j/load.rb +21 -0
  24. data/lib/neo4j/mapping/class_methods/init_node.rb +50 -0
  25. data/lib/neo4j/mapping/class_methods/init_rel.rb +35 -0
  26. data/lib/neo4j/mapping/class_methods/list.rb +13 -0
  27. data/lib/neo4j/mapping/class_methods/property.rb +82 -0
  28. data/lib/neo4j/mapping/class_methods/relationship.rb +91 -0
  29. data/lib/neo4j/mapping/class_methods/rule.rb +295 -0
  30. data/lib/neo4j/mapping/decl_relationship_dsl.rb +214 -0
  31. data/lib/neo4j/mapping/has_list.rb +134 -0
  32. data/lib/neo4j/mapping/has_n.rb +83 -0
  33. data/lib/neo4j/mapping/node_mixin.rb +112 -0
  34. data/lib/neo4j/mapping/relationship_mixin.rb +120 -0
  35. data/lib/neo4j/model.rb +4 -0
  36. data/lib/neo4j/neo4j.rb +95 -0
  37. data/lib/neo4j/node.rb +131 -0
  38. data/lib/neo4j/node_mixin.rb +4 -0
  39. data/lib/neo4j/node_relationship.rb +149 -0
  40. data/lib/neo4j/node_traverser.rb +157 -0
  41. data/lib/neo4j/property.rb +111 -0
  42. data/lib/neo4j/rails/attributes.rb +155 -0
  43. data/lib/neo4j/rails/callbacks.rb +34 -0
  44. data/lib/neo4j/rails/finders.rb +134 -0
  45. data/lib/neo4j/rails/lucene_connection_closer.rb +19 -0
  46. data/lib/neo4j/rails/mapping/property.rb +60 -0
  47. data/lib/neo4j/rails/model.rb +105 -0
  48. data/lib/neo4j/rails/persistence.rb +260 -0
  49. data/lib/neo4j/rails/railtie.rb +21 -0
  50. data/lib/neo4j/rails/relationships/mapper.rb +96 -0
  51. data/lib/neo4j/rails/relationships/relationship.rb +30 -0
  52. data/lib/neo4j/rails/relationships/relationships.rb +60 -0
  53. data/lib/neo4j/rails/serialization.rb +25 -0
  54. data/lib/neo4j/rails/timestamps.rb +65 -0
  55. data/lib/neo4j/rails/transaction.rb +67 -0
  56. data/lib/neo4j/rails/tx_methods.rb +15 -0
  57. data/lib/neo4j/rails/validations.rb +38 -0
  58. data/lib/neo4j/rails/validations/non_nil.rb +11 -0
  59. data/lib/neo4j/rails/validations/uniqueness.rb +37 -0
  60. data/lib/neo4j/relationship.rb +169 -0
  61. data/lib/neo4j/relationship_mixin.rb +4 -0
  62. data/lib/neo4j/relationship_traverser.rb +92 -0
  63. data/lib/neo4j/to_java.rb +31 -0
  64. data/lib/neo4j/transaction.rb +68 -0
  65. data/lib/neo4j/type_converters.rb +117 -0
  66. data/lib/neo4j/version.rb +3 -0
  67. data/lib/orm_adapter/adapters/neo4j.rb +55 -0
  68. data/lib/tmp/neo4j/active_tx_log +1 -0
  69. data/lib/tmp/neo4j/index/lucene-store.db +0 -0
  70. data/lib/tmp/neo4j/index/lucene.log.active +0 -0
  71. data/lib/tmp/neo4j/lucene-fulltext/lucene-store.db +0 -0
  72. data/lib/tmp/neo4j/lucene-fulltext/lucene.log.active +0 -0
  73. data/lib/tmp/neo4j/lucene/lucene-store.db +0 -0
  74. data/lib/tmp/neo4j/lucene/lucene.log.active +0 -0
  75. data/lib/tmp/neo4j/messages.log +85 -0
  76. data/lib/tmp/neo4j/neostore +0 -0
  77. data/lib/tmp/neo4j/neostore.id +0 -0
  78. data/lib/tmp/neo4j/neostore.nodestore.db +0 -0
  79. data/lib/tmp/neo4j/neostore.nodestore.db.id +0 -0
  80. data/lib/tmp/neo4j/neostore.propertystore.db +0 -0
  81. data/lib/tmp/neo4j/neostore.propertystore.db.arrays +0 -0
  82. data/lib/tmp/neo4j/neostore.propertystore.db.arrays.id +0 -0
  83. data/lib/tmp/neo4j/neostore.propertystore.db.id +0 -0
  84. data/lib/tmp/neo4j/neostore.propertystore.db.index +0 -0
  85. data/lib/tmp/neo4j/neostore.propertystore.db.index.id +0 -0
  86. data/lib/tmp/neo4j/neostore.propertystore.db.index.keys +0 -0
  87. data/lib/tmp/neo4j/neostore.propertystore.db.index.keys.id +0 -0
  88. data/lib/tmp/neo4j/neostore.propertystore.db.strings +0 -0
  89. data/lib/tmp/neo4j/neostore.propertystore.db.strings.id +0 -0
  90. data/lib/tmp/neo4j/neostore.relationshipstore.db +0 -0
  91. data/lib/tmp/neo4j/neostore.relationshipstore.db.id +0 -0
  92. data/lib/tmp/neo4j/neostore.relationshiptypestore.db +0 -0
  93. data/lib/tmp/neo4j/neostore.relationshiptypestore.db.id +0 -0
  94. data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names +0 -0
  95. data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names.id +0 -0
  96. data/lib/tmp/neo4j/nioneo_logical.log.active +0 -0
  97. data/lib/tmp/neo4j/tm_tx_log.1 +0 -0
  98. data/neo4j.gemspec +31 -0
  99. metadata +216 -0
@@ -0,0 +1,134 @@
1
+ module Neo4j
2
+ module Rails
3
+ module Finders
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ rule :_all
8
+ end
9
+
10
+ module ClassMethods
11
+ # overwrite the index method to add find_by_xxx class methods
12
+ def index(*args)
13
+ field = args.first
14
+ module_eval <<-RUBY, __FILE__, __LINE__
15
+ def self.all_by_#{field}(value)
16
+ find_with_indexer("#{field}: \\"\#{value}\\"")
17
+ end
18
+
19
+ def self.find_by_#{field}(value)
20
+ all_by_#{field}(value).first
21
+ end
22
+ RUBY
23
+
24
+ super
25
+ end
26
+
27
+ # load an id or array of ids from the database
28
+ def load(*ids)
29
+ result = ids.map { |id| Neo4j::Node.load(id) }
30
+ if ids.length == 1
31
+ result.first
32
+ else
33
+ result
34
+ end
35
+ end
36
+
37
+ # Behave like the ActiveRecord query interface
38
+
39
+ # Handle Model.find(params[:id])
40
+
41
+ # Model.find
42
+ # Model.find(:first)
43
+
44
+ # Model.find("1")
45
+ # Model.find(1)
46
+
47
+ # Model.find("name: test")
48
+ # Model.find(:name => "test")
49
+
50
+ # Model.find(:first, "name: test")
51
+ # Model.find(:first, { :name => "test" })
52
+
53
+ # Model.find(:first, :conditions => "name: test")
54
+ # Model.find(:first, :conditions => { :name => "test" })
55
+
56
+ # Model.find(:all, "name: test")
57
+ # Model.find(:all, { :name => "test" })
58
+
59
+ # Model.find(:all, :conditions => "name: test")
60
+ # Model.find(:all, :conditions => { :name => "test" })
61
+ def find(*args)
62
+ case args.first
63
+ when :all, :first
64
+ kind = args.shift
65
+ send(kind, *args)
66
+ else
67
+ find_with_ids(*args) or first(*args)
68
+ end
69
+ end
70
+
71
+ def all(*args)
72
+ args = normalize_args(*args)
73
+ if args.empty?
74
+ # use the _all rule to recover all the stored instances of this node
75
+ _all
76
+ else
77
+ # handle the special case of a search by id
78
+ if args.first.is_a?(Hash) && args.first[:id]
79
+ [find_with_ids(args.first[:id])].flatten
80
+ else
81
+ find_with_indexer(*args)
82
+ end
83
+ end
84
+ end
85
+
86
+ def first(*args)
87
+ all(*args).first
88
+ end
89
+
90
+ def last(*args)
91
+ a = all(*args)
92
+ a.empty? ? nil : a[a.size - 1]
93
+ end
94
+
95
+ def count
96
+ all.size
97
+ end
98
+
99
+ protected
100
+ def find_with_ids(*args)
101
+ if ((args.first.is_a?(String) || args.first.is_a?(Integer)) && args.first.to_i > 0)
102
+ load(*args.map { |p| p.to_i })
103
+ end
104
+ end
105
+
106
+ def find_with_indexer(*args)
107
+ hits = _indexer.find(*args)
108
+ # We need to save this so that the Rack Neo4j::Rails:LuceneConnection::Closer can close it
109
+ Thread.current[:neo4j_lucene_connection] ||= []
110
+ Thread.current[:neo4j_lucene_connection] << hits
111
+ hits
112
+ end
113
+
114
+ def normalize_args(*args)
115
+ options = args.extract_options!
116
+
117
+ if options.present?
118
+
119
+ # TODO: Handle order
120
+ options.delete(:order)
121
+
122
+ if options[:conditions]
123
+ args << options[:conditions] if options[:conditions].present?
124
+ else
125
+ args << options if options.present?
126
+ end
127
+ end
128
+ args
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+
@@ -0,0 +1,19 @@
1
+ module Neo4j
2
+ module Rails
3
+ class LuceneConnectionCloser
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ @app.call(env)
10
+ ensure
11
+ Thread.current[:neo4j_lucene_connection].each {|hits| hits.close} if Thread.current[:neo4j_lucene_connection]
12
+ Thread.current[:neo4j_lucene_connection] = nil
13
+ end
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ Thread.current
@@ -0,0 +1,60 @@
1
+ module Neo4j
2
+ module Rails
3
+ module Mapping
4
+ module Property
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # Handles options for the property
9
+ #
10
+ # Set the property type :type => Time
11
+ # Set a default :default => "default"
12
+ # Property must be there :null => false
13
+ # Property has a length limit :limit => 128
14
+ def property(*args)
15
+ options = args.extract_options!
16
+ args.each do |property_sym|
17
+ property_setup(property_sym, options)
18
+ end
19
+ end
20
+
21
+ protected
22
+ def property_setup(property, options)
23
+ _decl_props[property] = options
24
+ handle_property_options_for(property, options)
25
+ define_property_methods_for(property, options)
26
+ _decl_props[property][:defined] = true
27
+ end
28
+
29
+ def handle_property_options_for(property, options)
30
+ attribute_defaults[property.to_s] = options[:default] if options.has_key?(:default)
31
+
32
+ if options.has_key?(:null) && options[:null] === false
33
+ validates(property, :non_nil => true, :on => :create)
34
+ validates(property, :non_nil => true, :on => :update)
35
+ end
36
+ validates(property, :length => { :maximum => options[:limit] }) if options[:limit]
37
+ end
38
+
39
+ def define_property_methods_for(property, options)
40
+ unless method_defined?(property)
41
+ class_eval <<-RUBY, __FILE__, __LINE__
42
+ def #{property}
43
+ send(:[], "#{property}")
44
+ end
45
+ RUBY
46
+ end
47
+
48
+ unless method_defined?("#{property}=".to_sym)
49
+ class_eval <<-RUBY, __FILE__, __LINE__
50
+ def #{property}=(value)
51
+ send(:[]=, "#{property}", value)
52
+ end
53
+ RUBY
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,105 @@
1
+ module Neo4j
2
+ module Rails
3
+ class Model
4
+ include Neo4j::NodeMixin
5
+
6
+ # Initialize a Node with a set of properties (or empty if nothing is passed)
7
+ def initialize(attributes = {})
8
+ reset_attributes
9
+ clear_relationships
10
+ self.attributes = attributes
11
+ end
12
+
13
+ def id
14
+ neo_id.nil? ? nil : neo_id.to_s
15
+ end
16
+
17
+ def to_param
18
+ persisted? ? neo_id.to_s : nil
19
+ end
20
+
21
+ # Returns an Enumerable of all (primary) key attributes
22
+ # or nil if model.persisted? is false
23
+ def to_key
24
+ persisted? ? [id] : nil
25
+ end
26
+
27
+ def reject_if?(proc_or_symbol, attr)
28
+ return false if proc_or_symbol.nil?
29
+ if proc_or_symbol.is_a?(Symbol)
30
+ meth = method(proc_or_symbol)
31
+ meth.arity == 0 ? meth.call : meth.call(attr)
32
+ else
33
+ proc_or_symbol.call(attr)
34
+ end
35
+ end
36
+
37
+ def to_model
38
+ self
39
+ end
40
+
41
+ def ==(other)
42
+ new? ? self.__id__ == other.__id__ : @_java_node == (other)
43
+ end
44
+
45
+ # --------------------------------------
46
+ # Public Class Methods
47
+ # --------------------------------------
48
+ class << self
49
+ # NodeMixin overwrites the #new class method but it saves it as orig_new
50
+ # Here, we just get it back to normal
51
+ alias :new :orig_new
52
+
53
+ def transaction(&block)
54
+ Neo4j::Rails::Transaction.run do |tx|
55
+ block.call(tx)
56
+ end
57
+ end
58
+
59
+ def accepts_nested_attributes_for(*attr_names)
60
+ options = attr_names.pop if attr_names[-1].is_a?(Hash)
61
+
62
+ attr_names.each do |association_name|
63
+ # Do some validation that we have defined the relationships we want to nest
64
+ rel = self._decl_rels[association_name.to_sym]
65
+ raise "No relationship declared with has_n or has_one with type #{association_name}" unless rel
66
+ raise "Can't use accepts_nested_attributes_for(#{association_name}) since it has not defined which class it has a relationship to, use has_n(#{association_name}).to(MyOtherClass)" unless rel.target_class
67
+
68
+ if rel.has_one?
69
+ send(:define_method, "#{association_name}_attributes=") do |attributes|
70
+ update_nested_attributes(association_name.to_sym, attributes, options)
71
+ end
72
+ else
73
+ send(:define_method, "#{association_name}_attributes=") do |attributes|
74
+ if attributes.is_a?(Array)
75
+ attributes.each do |attr|
76
+ update_nested_attributes(association_name.to_sym, attr, options)
77
+ end
78
+ else
79
+ attributes.each_value do |attr|
80
+ update_nested_attributes(association_name.to_sym, attr, options)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ Model.class_eval do
92
+ extend ActiveModel::Translation
93
+
94
+ include Persistence # handles how to save, create and update the model
95
+ include Attributes # handles how to save and retrieve attributes
96
+ include Mapping::Property # allows some additional options on the #property class method
97
+ include Serialization # enable to_xml and to_json
98
+ include Timestamps # handle created_at, updated_at timestamp properties
99
+ include Validations # enable validations
100
+ include Callbacks # enable callbacks
101
+ include Finders # ActiveRecord style find
102
+ include Relationships # for none persisted relationships
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,260 @@
1
+ module Neo4j
2
+ module Rails
3
+ module Persistence
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ extend TxMethods
8
+ tx_methods :destroy, :create, :update, :update_nested_attributes
9
+ end
10
+
11
+ # Persist the object to the database. Validations and Callbacks are included
12
+ # by default but validation can be disabled by passing :validate => false
13
+ # to #save.
14
+ def save(*)
15
+ create_or_update
16
+ end
17
+
18
+ # Persist the object to the database. Validations and Callbacks are included
19
+ # by default but validation can be disabled by passing :validate => false
20
+ # to #save!.
21
+ #
22
+ # Raises a RecordInvalidError if there is a problem during save.
23
+ def save!(*args)
24
+ unless save(*args)
25
+ raise RecordInvalidError.new(self)
26
+ end
27
+ end
28
+
29
+ # Updates a single attribute and saves the record.
30
+ # This is especially useful for boolean flags on existing records. Also note that
31
+ #
32
+ # * Validation is skipped.
33
+ # * Callbacks are invoked.
34
+ # * Updates all the attributes that are dirty in this object.
35
+ #
36
+ def update_attribute(name, value)
37
+ respond_to?("#{name}=") ? send("#{name}=", value) : self[name] = value
38
+ save(:validate => false)
39
+ end
40
+
41
+ # Removes the node from Neo4j and freezes the object.
42
+ def destroy
43
+ delete
44
+ freeze
45
+ end
46
+
47
+ # Same as #destroy but doesn't run destroy callbacks and doesn't freeze
48
+ # the object
49
+ def delete
50
+ del unless new_record?
51
+ set_deleted_properties
52
+ end
53
+
54
+ # Returns true if the object was destroyed.
55
+ def destroyed?()
56
+ @_deleted
57
+ end
58
+
59
+ # Updates this resource with all the attributes from the passed-in Hash and requests that the record be saved.
60
+ # If saving fails because the resource is invalid then false will be returned.
61
+ def update_attributes(attributes)
62
+ self.attributes = attributes
63
+ save
64
+ end
65
+
66
+ # Same as #update_attributes, but raises an exception if saving fails.
67
+ def update_attributes!(attributes)
68
+ self.attributes = attributes
69
+ save!
70
+ end
71
+
72
+ # Reload the object from the DB.
73
+ def reload(options = nil)
74
+ clear_changes
75
+ clear_relationships
76
+ reset_attributes
77
+ unless reload_from_database
78
+ set_deleted_properties
79
+ freeze
80
+ end
81
+ self
82
+ end
83
+
84
+ # Returns if the record is persisted, i.e. it’s not a new record and it was not destroyed
85
+ def persisted?
86
+ !new_record? && !destroyed?
87
+ end
88
+
89
+ # Returns true if the record hasn't been saved to Neo4j yet.
90
+ def new_record?
91
+ _java_node.nil?
92
+ end
93
+
94
+ alias :new? :new_record?
95
+
96
+ # Freeze the properties hash.
97
+ def freeze
98
+ @properties.freeze; self
99
+ end
100
+
101
+ # Returns +true+ if the properties hash has been frozen.
102
+ def frozen?
103
+ reload
104
+ @properties.frozen?
105
+ end
106
+
107
+ module ClassMethods
108
+ # Initialize a model and set a bunch of attributes at the same time. Returns
109
+ # the object whether saved successfully or not.
110
+ def create(*args)
111
+ new(*args).tap { |o| o.save }
112
+ end
113
+
114
+ # Same as #create, but raises an error if there is a problem during save.
115
+ # Returns the object whether saved successfully or not.
116
+ def create!(*args)
117
+ new(*args).tap { |o| o.save! }
118
+ end
119
+
120
+ # Destroy each node in turn. Runs the destroy callbacks for each node.
121
+ def destroy_all
122
+ all.each do |n|
123
+ n.destroy
124
+ end
125
+ end
126
+ end
127
+
128
+ protected
129
+ def create_or_update
130
+ result = persisted? ? update : create
131
+ unless result != false
132
+ Neo4j::Rails::Transaction.fail if Neo4j::Rails::Transaction.running?
133
+ false
134
+ else
135
+ true
136
+ end
137
+ end
138
+
139
+ def update
140
+ write_changed_attributes
141
+ clear_changes
142
+ clear_relationships
143
+ true
144
+ end
145
+
146
+ def create
147
+ node = Neo4j::Node.new
148
+ #unless _java_node.save_nested(node)
149
+ # Neo4j::Rails::Transaction.fail
150
+ # false
151
+ #else
152
+ init_on_load(node)
153
+ init_on_create
154
+ clear_changes
155
+ clear_relationships
156
+ true
157
+ end
158
+
159
+ def init_on_create(*)
160
+ self["_classname"] = self.class.to_s
161
+ write_default_attributes
162
+ write_changed_attributes
163
+ write_changed_relationships
164
+ end
165
+
166
+ def reset_attributes
167
+ @properties = {}
168
+ end
169
+
170
+ def reload_from_database
171
+ if reloaded = self.class.load(id)
172
+ send(:attributes=, reloaded.attributes, false)
173
+ end
174
+ reloaded
175
+ end
176
+
177
+ def set_deleted_properties
178
+ @_deleted = true
179
+ @_persisted = false
180
+ @_java_node = nil
181
+ end
182
+
183
+ # Ensure any defaults are stored in the DB
184
+ def write_default_attributes
185
+ attribute_defaults.each do |attribute, value|
186
+ write_attribute(attribute, Neo4j::TypeConverters.convert(value, attribute, self.class)) unless changed_attributes.has_key?(attribute) || _java_node.has_property?(attribute)
187
+ end
188
+ end
189
+
190
+ # Write attributes to the Neo4j DB only if they're altered
191
+ def write_changed_attributes
192
+ @properties.each do |attribute, value|
193
+ write_attribute(attribute, value) if changed_attributes.has_key?(attribute)
194
+ end
195
+ end
196
+
197
+ def _add_relationship(rel_type, attr)
198
+ clazz = self.class._decl_rels[rel_type.to_sym].target_class
199
+ node = clazz.new(attr)
200
+ if respond_to?("#{rel_type}=")
201
+ send("#{rel_type}=", node)
202
+ elsif respond_to?("#{rel_type}")
203
+ has_n = send("#{rel_type}")
204
+ has_n << node
205
+ else
206
+ raise "oops #{rel_type}"
207
+ end
208
+ end
209
+
210
+ def _find_node(rel_type, id)
211
+ if respond_to?("#{rel_type}=")
212
+ send("#{rel_type}")
213
+ elsif respond_to?("#{rel_type}")
214
+ has_n = send("#{rel_type}")
215
+ has_n.find { |n| n.id == id }
216
+ else
217
+ raise "oops #{rel_type}"
218
+ end
219
+ end
220
+
221
+ def update_nested_attributes(rel_type, attr, options)
222
+ allow_destroy, reject_if = [options[:allow_destroy], options[:reject_if]] if options
223
+
224
+ if new?
225
+ # We are updating a node that was created with the 'new' method.
226
+ # The relationship will only be kept in the Value object.
227
+ _add_relationship(rel_type, attr) unless reject_if?(reject_if, attr) || (allow_destroy && attr[:_destroy] && attr[:_destroy] != '0')
228
+ else
229
+ # We have a node that was created with the #create method - has real Neo4j relationships
230
+ # does it exist ?
231
+ found = _find_node(rel_type, attr[:id])
232
+
233
+ # Check if we want to destroy not found nodes (e.g. {..., :_destroy => '1' } ?
234
+ destroy = attr[:_destroy] && attr[:_destroy] != '0'
235
+
236
+ if found
237
+ if destroy
238
+ found.destroy if allow_destroy
239
+ else
240
+ found.update_attributes(attr) # it already exist, so update that one
241
+ end
242
+ elsif !destroy && !reject_if?(reject_if, attr)
243
+ _add_relationship(rel_type, attr)
244
+ end
245
+ end
246
+ end
247
+
248
+ public
249
+ class RecordInvalidError < RuntimeError
250
+ attr_reader :record
251
+
252
+ def initialize(record)
253
+ @record = record
254
+ super(@record.errors.full_messages.join(", "))
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
260
+