neo4j 1.0.0.beta.21-java

Sign up to get free protection for your applications and to get access to all the features.
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
+