neo4j 2.2.3-java → 2.2.4-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +5 -0
  3. data/Gemfile +2 -3
  4. data/README.rdoc +19 -14
  5. data/lib/neo4j/rails/attributes.rb +2 -2
  6. data/lib/neo4j/rails/attributes.rb~ +530 -0
  7. data/lib/neo4j/rails/node_persistance.rb +9 -0
  8. data/lib/neo4j/rails/persistence.rb +2 -2
  9. data/lib/neo4j/rails/persistence.rb~ +186 -0
  10. data/lib/neo4j/rails/relationship_persistence.rb +9 -0
  11. data/lib/neo4j/version.rb +1 -1
  12. data/neo4j.gemspec +1 -1
  13. metadata +22 -66
  14. data/lib/db/active_tx_log +0 -1
  15. data/lib/db/index/lucene-store.db +0 -0
  16. data/lib/db/index/lucene.log.active +0 -0
  17. data/lib/db/index/lucene.log.v0 +0 -0
  18. data/lib/db/messages.log +0 -286
  19. data/lib/db/neostore +0 -0
  20. data/lib/db/neostore.id +0 -0
  21. data/lib/db/neostore.nodestore.db +0 -0
  22. data/lib/db/neostore.nodestore.db.id +0 -0
  23. data/lib/db/neostore.propertystore.db +0 -0
  24. data/lib/db/neostore.propertystore.db.arrays +0 -0
  25. data/lib/db/neostore.propertystore.db.arrays.id +0 -0
  26. data/lib/db/neostore.propertystore.db.id +0 -0
  27. data/lib/db/neostore.propertystore.db.index +0 -0
  28. data/lib/db/neostore.propertystore.db.index.id +0 -0
  29. data/lib/db/neostore.propertystore.db.index.keys +0 -0
  30. data/lib/db/neostore.propertystore.db.index.keys.id +0 -0
  31. data/lib/db/neostore.propertystore.db.strings +0 -0
  32. data/lib/db/neostore.propertystore.db.strings.id +0 -0
  33. data/lib/db/neostore.relationshipstore.db +0 -0
  34. data/lib/db/neostore.relationshipstore.db.id +0 -0
  35. data/lib/db/neostore.relationshiptypestore.db +0 -0
  36. data/lib/db/neostore.relationshiptypestore.db.id +0 -0
  37. data/lib/db/neostore.relationshiptypestore.db.names +0 -0
  38. data/lib/db/neostore.relationshiptypestore.db.names.id +0 -0
  39. data/lib/db/nioneo_logical.log.active +0 -0
  40. data/lib/db/nioneo_logical.log.v0 +0 -0
  41. data/lib/db/tm_tx_log.1 +0 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d43bf8399c0246e2cd09f1aaa6ed2d3eab660e38
4
+ data.tar.gz: 64b024e4814da15139b4fd502160e6ab09a50d47
5
+ SHA512:
6
+ metadata.gz: 31c77d29c92a9123f4d56673955d414976f64db6bae9753d6c42297792caa4956565de7d13fafb11854ddada15021ba6f7570124e7ba546ebb6c8c7c33cb5999
7
+ data.tar.gz: 6c0fdcb24ef629aded78c049566010fbc451943958a300d7bb995cc88f4d7ad879ab86cfb8ce28f87f6586413e2376bb88fda7104fede1ca2efcf2722cb920b0
data/CHANGELOG CHANGED
@@ -1,3 +1,8 @@
1
+ == 2.2.4 / 2013-05-19
2
+ * get_or_create should return wrapped ruby nodes, alex-klepa, #241, #246
3
+ * Make sure freeze does not have side effects, #235
4
+ * Fix for carrierwave-neo4j (attribute_defaults), #235
5
+
1
6
  == 2.2.3 / 2012-12-28
2
7
  * Support for HA cluster with neo4j 1.9.X, #228, #99, #223
3
8
  * Make sure the Identity map is cleared after an exception, #214
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source :gemcutter
1
+ source 'http://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
@@ -19,7 +19,6 @@ group 'test' do
19
19
  gem "rdoc", ">= 2.5.10"
20
20
  gem "rspec", "~> 2.8"
21
21
  gem "its" # its(:with, :arguments) { should be_possible }
22
- gem 'shoulda-matchers', '~> 1.0'
22
+ #gem 'shoulda-matchers', could not get it working with jruby
23
23
  gem "test-unit"
24
24
  end
25
-
@@ -3,14 +3,12 @@
3
3
  Neo4j.rb is a graph database for JRuby.
4
4
 
5
5
  You can think of \Neo4j as a high-performance graph engine with all the features of a mature and robust database.
6
- The programmer works with an object-oriented, flexible network structure rather than with strict and static tables — yet enjoys all the benefits of a fully transactional, enterprise-strength database.
6
+ The programmer works with an object-oriented, flexible network structure rather than with strict and static tables — yet enjoys all the benefits of a fully transactional, enterprise-strength database. This JRuby gem uses the mature {Neo4j Java library}[http://www.neo4j.org].
7
7
 
8
- It uses two powerful and mature Java libraries:
9
- * {Neo4J}[http://www.neo4j.org] - for persistence and traversal of the graph
10
- * {Lucene}[http://lucene.apache.org/java/docs/index.html] for querying and indexing.
8
+ It has been tested with Neo4j version 1.8.2 and 1.9.M03 ( {see here}[https://github.com/andreasronge/neo4j-core/blob/master/neo4j-core.gemspec]) and JRuby 1.7.4 (see Travis)
11
9
 
12
-
13
- It has been tested with Neo4j version 1.8 ( {see here}[https://github.com/andreasronge/neo4j-core/blob/master/neo4j-core.gemspec]) and JRuby 1.6.7 & 1.7.1 (see Travis)
10
+ Notice, you do not need to install the Neo4j server since this gem comes included with the database.
11
+ However, if you still want to use the Neo4j server (e.g. the admin UI) you can connect the embedded databas with the Neo4j server using a Neo4j HA Cluster (see wiki pages).
14
12
 
15
13
  == Documentation
16
14
 
@@ -22,6 +20,7 @@ It has been tested with Neo4j version 1.8 ( {see here}[https://github.com/andrea
22
20
 
23
21
  == Example applications
24
22
 
23
+ * {Neo4j.rb with HA Cluster Screencast}[http://youtu.be/PblrbrT5JNY]
25
24
  * {The Kvitter Rails 3.2x App}[https://github.com/andreasronge/kvitter] (kvitter = tweets in Swedish)
26
25
  * {Simple Rails 3.0 App}[https://github.com/andreasronge/neo4j-rails-example]
27
26
 
@@ -47,16 +46,19 @@ The neo4j gem depends on the neo4j-wrapper and neo4j-core gems and neo4j-cypher
47
46
 
48
47
  === neo4j gem
49
48
 
50
- {Neo4j::Rails::Model}
49
+ {Neo4j::Rails::Model}[http://rdoc.info/github/andreasronge/neo4j/Neo4j/Rails/Model]
50
+
51
+ {Neo4j::Rails::Relationship}[http://rdoc.info/github/andreasronge/neo4j/Neo4j/Rails/Relationship]
51
52
 
52
- {Neo4j::Rails::Relationship}
53
+ {Neo4j::Rails::Observer}[http://rdoc.info/github/andreasronge/neo4j/Neo4j/Rails/Observer]
53
54
 
54
- {Neo4j::Rails::Observer}
55
+ {Neo4j::Rails::HaConsole::Railitie}[http://rdoc.info/github/andreasronge/neo4j/Neo4j/Rails/HaConsole/Railtie]
55
56
 
56
- {Neo4j::Railtie}
57
+ {Neo4j::Rails::Versioning}[http://rdoc.info/github/andreasronge/neo4j/Neo4j/Rails/Versioning]
57
58
 
58
- {Neo4j::Rails::Versioning}
59
+ {Neo4j::Rails::Compositions::ClassMethods}[http://rdoc.info/github/andreasronge/neo4j/Neo4j/Rails/Compositions/ClassMethods]
59
60
 
61
+ {Neo4j::Rails::AcceptId}[http://rdoc.info/github/andreasronge/neo4j/Neo4j/Rails/AcceptId]
60
62
 
61
63
  ==== Example
62
64
 
@@ -89,7 +91,6 @@ Make sure you are using JRuby !
89
91
  ==== Generate a Rails Application
90
92
 
91
93
  Example of creating an Neo4j Application from scratch:
92
- (make sure you have installed JRuby version >= 1.6.2)
93
94
 
94
95
  gem install rails
95
96
  rails new myapp -m http://andreasronge.github.com/neo4j/rails.rb -O
@@ -115,12 +116,11 @@ Example of mapping a ruby class to a node and delaring properties and relationsh
115
116
 
116
117
  class Person
117
118
  include Neo4j::NodeMixin
118
- property :name
119
+ property :name, :index => :exact
119
120
  property :city
120
121
 
121
122
  has_n :friends
122
123
  has_one :address
123
- index :name
124
124
  end
125
125
 
126
126
  # assume we have an transaction already running !
@@ -161,6 +161,11 @@ The Neo4j::Node and Neo4j::Relationship is implemented in the {neo4j-core}[http:
161
161
 
162
162
  For more information, read the {Github Wiki}[https://github.com/andreasronge/neo4j/wiki]
163
163
 
164
+ == Rails/Neo4j.rb in a Cluster ?
165
+
166
+ Yes, check {Neo4j.rb Ha Cluster}[https://github.com/andreasronge/neo4j/wiki/Neo4j%3A%3Aha-cluster] or {Screencast}[http://youtu.be/PblrbrT5JNY]
167
+ Notice, you don't need to install the Neo4j Server, but it could be a useful tool to visualize the graph.
168
+
164
169
  == Architecture
165
170
 
166
171
  As you seen above, neo4j.rb consists of a three layers API:
@@ -85,7 +85,7 @@ module Neo4j
85
85
  end
86
86
 
87
87
  def attribute_defaults
88
- self.class.attribute_defaults
88
+ self.class.attribute_defaults || {}
89
89
  end
90
90
 
91
91
  # Updates this resource with all the attributes from the passed-in Hash and requests that the record be saved.
@@ -378,7 +378,7 @@ module Neo4j
378
378
 
379
379
  # Ensure any defaults are stored in the DB
380
380
  def write_default_attributes
381
- self.class.attribute_defaults.each do |attribute, value|
381
+ self.attribute_defaults.each do |attribute, value|
382
382
  write_property_from_db(attribute, Neo4j::TypeConverters.convert(value, attribute, self.class, false)) unless changed_attributes.has_key?(attribute) || _java_entity.has_property?(attribute)
383
383
  end
384
384
  end
@@ -0,0 +1,530 @@
1
+ module Neo4j
2
+ module Rails
3
+ # This module handles the getting, setting and updating of attributes or properties
4
+ # in a Railsy way. This typically means not writing anything to the DB until the
5
+ # object is saved (after validation).
6
+ #
7
+ # Externally, when we talk about properties (e.g. {#property?}, {#property_names}),
8
+ # we mean all of the stored properties for this object include the 'hidden' props
9
+ # with underscores at the beginning such as _neo_id and _classname. When we talk
10
+ # about attributes, we mean all the properties apart from those hidden ones.
11
+ #
12
+ # This mixin defines a number of class methods, see #{ClassMethods}.
13
+ #
14
+ module Attributes
15
+ extend ActiveSupport::Concern
16
+ extend TxMethods
17
+
18
+ included do
19
+ include ActiveModel::Dirty # track changes to attributes
20
+ include ActiveModel::MassAssignmentSecurity # handle attribute hash assignment
21
+
22
+
23
+ class << self
24
+ attr_accessor :attribute_defaults
25
+ end
26
+
27
+ self.attribute_defaults ||= {}
28
+
29
+ # save the original [] and []= to use as read/write to Neo4j
30
+ alias_method :read_property_from_db, :[]
31
+ alias_method :write_property_from_db, :[]=
32
+
33
+ # wrap the read/write in type conversion
34
+ alias_method_chain :read_attribute, :type_conversion
35
+ alias_method_chain :write_attribute, :type_conversion
36
+
37
+ # whenever we refer to [] or []=. use our local properties store
38
+ alias_method :[], :read_attribute
39
+ alias_method :[]=, :write_attribute
40
+
41
+ def self.inherited(sub_klass)
42
+ super
43
+ return if sub_klass.to_s[0..0] == '#' # this is really for anonymous test classes
44
+ setup_neo4j_subclass(sub_klass)
45
+ sub_klass.send(:define_method, :attribute_defaults) do
46
+ self.class.attribute_defaults
47
+ end
48
+ sub_klass.attribute_defaults = self.attribute_defaults.clone
49
+ # Hmm, could not do this from the Finders Mixin Module - should be moved
50
+ sub_klass.rule(:_all, :functions => Neo4j::Wrapper::Rule::Functions::Size.new) if sub_klass.respond_to?(:rule)
51
+ end
52
+ end
53
+
54
+ # Is called when a node neo4j entity is created and we need to save attributes
55
+ # @private
56
+ def init_on_create(*)
57
+ self._classname = self.class.to_s
58
+ write_default_attributes
59
+ write_changed_attributes
60
+ clear_changes
61
+ end
62
+
63
+ # Setup this mixins instance variables
64
+ # @private
65
+ def initialize_attributes(attributes)
66
+ @_properties = {}
67
+ @_properties_before_type_cast={}
68
+ self.attributes = attributes if attributes
69
+ end
70
+
71
+ # Mass-assign attributes. Stops any protected attributes from being assigned.
72
+ def attributes=(attributes, guard_protected_attributes = true)
73
+ attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes
74
+
75
+ multi_parameter_attributes = []
76
+ attributes.each do |k, v|
77
+ if k.to_s.include?("(")
78
+ multi_parameter_attributes << [k, v]
79
+ else
80
+ respond_to?("#{k}=") ? send("#{k}=", v) : self[k] = v
81
+ end
82
+ end
83
+
84
+ assign_multiparameter_attributes(multi_parameter_attributes)
85
+ end
86
+
87
+ def attribute_defaults
88
+ self.class.attribute_defaults || {}
89
+ end
90
+
91
+ # Updates this resource with all the attributes from the passed-in Hash and requests that the record be saved.
92
+ # If saving fails because the resource is invalid then false will be returned.
93
+ def update_attributes(attributes)
94
+ self.attributes = attributes
95
+ save
96
+ end
97
+ tx_methods :update_attributes
98
+
99
+ # Same as {#update_attributes}, but raises an exception if saving fails.
100
+ def update_attributes!(attributes)
101
+ self.attributes = attributes
102
+ save!
103
+ end
104
+ tx_methods :update_attributes!
105
+
106
+ # @private
107
+ def reset_attributes
108
+ @_properties = {}
109
+ end
110
+
111
+
112
+
113
+ # Updates a single attribute and saves the record.
114
+ # This is especially useful for boolean flags on existing records. Also note that
115
+ #
116
+ # * Validation is skipped.
117
+ # * Callbacks are invoked.
118
+ # * Updates all the attributes that are dirty in this object.
119
+ #
120
+ def update_attribute(name, value)
121
+ respond_to?("#{name}=") ? send("#{name}=", value) : self[name] = value
122
+ save(:validate => false)
123
+ end
124
+
125
+ def hash
126
+ persisted? ? _java_entity.neo_id.hash : super
127
+ end
128
+
129
+ def to_param
130
+ persisted? ? neo_id.to_s : nil
131
+ end
132
+
133
+ def to_model
134
+ self
135
+ end
136
+
137
+ # Returns an Enumerable of all (primary) key attributes
138
+ # or nil if model.persisted? is false
139
+ def to_key
140
+ persisted? ? [id] : nil
141
+ end
142
+
143
+ # Return the properties from the Neo4j Node, merged with those that haven't
144
+ # yet been saved
145
+ def props
146
+ ret = {}
147
+ property_names.each do |property_name|
148
+ ret[property_name] = respond_to?(property_name) ? send(property_name) : send(:[], property_name)
149
+ end
150
+ ret
151
+ end
152
+
153
+ # Return all the attributes for this model as a hash attr => value. Doesn't
154
+ # include properties that start with <tt>_</tt>.
155
+ def attributes
156
+ ret = {}
157
+ attribute_names.each do |attribute_name|
158
+ ret[attribute_name] = self.class._decl_props[attribute_name.to_sym] ? send(attribute_name) : send(:[], attribute_name)
159
+ end
160
+ ret
161
+ end
162
+
163
+ # Known properties are either in the @_properties, the declared
164
+ # attributes or the property keys for the persisted node.
165
+ def property_names
166
+ # initialize @_properties if needed since
167
+ # we can ask property names before the object is initialized (active_support initialize callbacks, respond_to?)
168
+ @_properties ||= {}
169
+ keys = @_properties.keys + self.class._decl_props.keys.map(&:to_s)
170
+ keys += _java_entity.property_keys.to_a if persisted?
171
+ keys.flatten.uniq
172
+ end
173
+
174
+ # Known attributes are either in the @_properties, the declared
175
+ # attributes or the property keys for the persisted node. Any attributes
176
+ # that start with <tt>_</tt> are rejected
177
+ def attribute_names
178
+ property_names.reject { |property_name| _invalid_attribute_name?(property_name) }
179
+ end
180
+
181
+ # Known properties are either in the @_properties, the declared
182
+ # properties or the property keys for the persisted node
183
+ def property?(name)
184
+ return false unless @_properties
185
+ @_properties.has_key?(name) ||
186
+ self.class._decl_props.has_key?(name) ||
187
+ persisted? && super
188
+ end
189
+
190
+ def property_changed?
191
+ return !@_properties.empty? unless persisted?
192
+ !!@_properties.keys.find { |k| self._java_entity[k] != @_properties[k] }
193
+ end
194
+
195
+ # Return true if method_name is the name of an appropriate attribute
196
+ # method
197
+ def attribute?(name)
198
+ name[0] != ?_ && property?(name)
199
+ end
200
+
201
+
202
+ # Wrap the getter in a conversion from Java to Ruby
203
+ def read_attribute_with_type_conversion(property)
204
+ self.class._converter(property).to_ruby(read_attribute_without_type_conversion(property))
205
+ end
206
+
207
+ # Wrap the setter in a conversion from Ruby to Java
208
+ def write_attribute_with_type_conversion(property, value)
209
+ @_properties_before_type_cast[property.to_sym]=value if self.class._decl_props.has_key? property.to_sym
210
+ conv_value = self.class._converter(property.to_sym).to_java(value)
211
+ write_attribute_without_type_conversion(property, conv_value)
212
+ end
213
+
214
+
215
+ # The behaviour of []= changes with a Rails Model, where nothing gets written
216
+ # to Neo4j until the object is saved, during which time all the validations
217
+ # and callbacks are run to ensure correctness
218
+ def write_attribute(key, value)
219
+ key_s = key.to_s
220
+ if !@_properties.has_key?(key_s) || @_properties[key_s] != value
221
+ attribute_will_change!(key_s)
222
+ @_properties[key_s] = value.nil? ? attribute_defaults[key_s] : value
223
+ end
224
+ value
225
+ end
226
+
227
+ # Returns the locally stored value for the key or retrieves the value from
228
+ # the DB if we don't have one
229
+ def read_attribute(key)
230
+ key = key.to_s
231
+ if @_properties.has_key?(key)
232
+ @_properties[key]
233
+ else
234
+ #puts "@_properties #{@_properties}"
235
+ #puts "attribute_defaults #{attribute_defaults.inspect}"
236
+ #puts "Key #{key}, self #{self}"
237
+ @_properties[key] = (!new_record? && _java_entity.has_property?(key)) ? read_property_from_db(key) : attribute_defaults[key]
238
+ end
239
+ end
240
+
241
+
242
+ module ClassMethods
243
+ # Returns all defined properties
244
+ def columns
245
+ columns = []
246
+ props = Marshal.load( Marshal.dump(self._decl_props ))
247
+ props.each { |k,v| v.store(:name, k ); columns << Column.new(v) }
248
+ columns
249
+ end
250
+
251
+ # Declares a property.
252
+ # It support the following hash options:
253
+ # <tt>:default</tt>,<tt>:null</tt>,<tt>:limit</tt>,<tt>:type</tt>,<tt>:index</tt>,<tt>:converter</tt>.
254
+ # Notice that you do not have to declare properties. You can always set and read properties using the <tt>[]</tt> and <tt>[]=</tt> operators.
255
+ #
256
+ # @example Set the property type,
257
+ # class Person < Neo4j::RailsModel
258
+ # property :age, :type => Time
259
+ # end
260
+ #
261
+ # @example Set the property type,
262
+ # class Person < Neo4j::RailsModel
263
+ # property :age, :default => 0
264
+ # end
265
+ #
266
+ # @example
267
+ # class Person < Neo4j::RailsModel
268
+ # # Property must be there
269
+ # property :age, :null => false
270
+ # end
271
+ #
272
+ # @example Property has a length limit
273
+ # class Person < Neo4j::RailsModel
274
+ # property :name, :limit => 128
275
+ # end
276
+ #
277
+ # @example Index with lucene.
278
+ # class Person < Neo4j::RailsModel
279
+ # property :name, :index => :exact
280
+ # property :year, :index => :exact, :type => Fixnum # index as fixnum too
281
+ # property :description, :index => :fulltext
282
+ # end
283
+ #
284
+ # @example Using a custom converter
285
+ # module MyConverter
286
+ # def to_java(v)
287
+ # "Java:#{v}"
288
+ # end
289
+ #
290
+ # def to_ruby(v)
291
+ # "Ruby:#{v}"
292
+ # end
293
+ #
294
+ # def index_as
295
+ # String
296
+ # end
297
+ #
298
+ # extend self
299
+ # end
300
+ #
301
+ # class Person < Neo4j::RailsModel
302
+ # property :name, :converter => MyConverter
303
+ # end
304
+ #
305
+ # @see Neo4j::TypeConverters::SerializeConverter SerializeConverter for using type => :serialize
306
+ # @see http://rdoc.info/github/andreasronge/neo4j-wrapper/Neo4j/TypeConverters for converters defined in neo4j-wrapper gem (which is included).
307
+ def property(*args)
308
+ options = args.extract_options!
309
+ args.each do |property_sym|
310
+ property_setup(property_sym, options)
311
+ end
312
+ end
313
+
314
+
315
+ protected
316
+ def property_setup(property, options)
317
+ _decl_props[property] = options
318
+ handle_property_options_for(property, options)
319
+ define_property_methods_for(property, options)
320
+ define_property_before_type_cast_methods_for(property, options)
321
+ end
322
+
323
+ def handle_property_options_for(property, options)
324
+ attribute_defaults[property.to_s] = options[:default] if options.has_key?(:default)
325
+
326
+ converter = options[:converter] || Neo4j::TypeConverters.converter(_decl_props[property][:type])
327
+ _decl_props[property][:converter] = converter
328
+
329
+ if options.include?(:index)
330
+ _decl_props[property][:index] = options[:index]
331
+ raise "Indexing boolean property is not allowed" if options[:type] && options[:type] == :boolean
332
+ index(property, :type => options[:index], :field_type => converter.index_as)
333
+ end
334
+
335
+ if options.has_key?(:null) && options[:null] === false
336
+ validates(property, :non_nil => true, :on => :create)
337
+ validates(property, :non_nil => true, :on => :update)
338
+ end
339
+ validates(property, :length => {:maximum => options[:limit]}) if options[:limit]
340
+ end
341
+
342
+ def define_property_methods_for(property, options)
343
+ unless method_defined?(property)
344
+ class_eval <<-RUBY, __FILE__, __LINE__
345
+ def #{property}
346
+ send(:[], "#{property}")
347
+ end
348
+ RUBY
349
+ end
350
+
351
+ unless method_defined?("#{property}=".to_sym)
352
+ class_eval <<-RUBY, __FILE__, __LINE__
353
+ def #{property}=(value)
354
+ send(:[]=, "#{property}", value)
355
+ end
356
+ RUBY
357
+ end
358
+ end
359
+
360
+ def define_property_before_type_cast_methods_for(property, options)
361
+ property_before_type_cast = "#{property}_before_type_cast"
362
+ class_eval <<-RUBY, __FILE__, __LINE__
363
+ def #{property_before_type_cast}=(value)
364
+ @_properties_before_type_cast[:#{property}]=value
365
+ end
366
+
367
+ def #{property_before_type_cast}
368
+ @_properties_before_type_cast.has_key?(:#{property}) ? @_properties_before_type_cast[:#{property}] : self.#{property}
369
+ end
370
+ RUBY
371
+ end
372
+ end
373
+
374
+
375
+ def _classname
376
+ self.class.to_s
377
+ end
378
+
379
+ protected
380
+
381
+
382
+ # Ensure any defaults are stored in the DB
383
+ def write_default_attributes
384
+ self.attribute_defaults.each do |attribute, value|
385
+ write_property_from_db(attribute, Neo4j::TypeConverters.convert(value, attribute, self.class, false)) unless changed_attributes.has_key?(attribute) || _java_entity.has_property?(attribute)
386
+ end
387
+ end
388
+
389
+ # Write attributes to the Neo4j DB only if they're altered
390
+ def write_changed_attributes
391
+ @_properties.each do |attribute, value|
392
+ write_property_from_db(attribute, value) if changed_attributes.has_key?(attribute)
393
+ end
394
+ end
395
+
396
+
397
+
398
+ def attribute_missing(method_id, *args, &block)
399
+ method_name = method_id.method_name
400
+ if property?(method_name)
401
+ self[method_name]
402
+ else
403
+ super
404
+ end
405
+ end
406
+
407
+ if ActiveModel::VERSION::STRING < "3.2.0"
408
+ # THIS IS ONLY NEEDED IN ACTIVEMODEL < 3.2
409
+ # To get ActiveModel::Dirty to work, we need to be able to call undeclared
410
+ # properties as though they have get methods
411
+ def method_missing(method_id, *args, &block)
412
+ method_name = method_id.to_s
413
+ if property?(method_name)
414
+ self[method_name]
415
+ else
416
+ super
417
+ end
418
+ end
419
+ end
420
+
421
+
422
+ def _invalid_attribute_name?(attr_name)
423
+ attr_name.to_s[0] == ?_ && !self.class._decl_props.include?(attr_name.to_sym)
424
+ end
425
+
426
+
427
+
428
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
429
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
430
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
431
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
432
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
433
+ # f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
434
+ # attribute will be set to nil.
435
+ def assign_multiparameter_attributes(pairs)
436
+ execute_callstack_for_multiparameter_attributes(
437
+ extract_callstack_for_multiparameter_attributes(pairs)
438
+ )
439
+ end
440
+
441
+ def execute_callstack_for_multiparameter_attributes(callstack)
442
+ errors = []
443
+ callstack.each do |name, values_with_empty_parameters|
444
+ begin
445
+ # (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
446
+ decl_type = self.class._decl_props[name.to_sym][:type]
447
+ raise "Not a multiparameter attribute, missing :type on property #{name} for #{self.class}" unless decl_type
448
+
449
+ # in order to allow a date to be set without a year, we must keep the empty values.
450
+ values = values_with_empty_parameters.reject { |v| v.nil? }
451
+
452
+ if values.empty?
453
+ send(name + "=", nil)
454
+ else
455
+
456
+ #TODO: Consider extracting hardcoded assignments into "Binders"
457
+ value = if Neo4j::TypeConverters::TimeConverter.convert?(decl_type)
458
+ instantiate_time_object(name, values)
459
+ elsif Neo4j::TypeConverters::DateConverter.convert?(decl_type)
460
+ begin
461
+ values = values_with_empty_parameters.collect do |v|
462
+ v.nil? ? 1 : v
463
+ end
464
+ Date.new(*values)
465
+ rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
466
+ instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
467
+ end
468
+ elsif Neo4j::TypeConverters::DateTimeConverter.convert?(decl_type)
469
+ DateTime.new(*values)
470
+ else
471
+ raise "Unknown type #{decl_type}"
472
+ end
473
+
474
+ send(name + "=", value)
475
+ end
476
+ rescue Exception => ex
477
+ raise "error on assignment #{values.inspect} to #{name}, ex: #{ex}"
478
+ end
479
+ end
480
+ unless errors.empty?
481
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
482
+ end
483
+ end
484
+
485
+ def instantiate_time_object(name, values)
486
+ # if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
487
+ # Time.zone.local(*values)
488
+ # else
489
+ Time.time_with_datetime_fallback(self.class.default_timezone, *values)
490
+ # end
491
+ end
492
+
493
+ def extract_callstack_for_multiparameter_attributes(pairs)
494
+ attributes = {}
495
+
496
+ for pair in pairs
497
+ multiparameter_name, value = pair
498
+ attribute_name = multiparameter_name.split("(").first
499
+ attributes[attribute_name] = [] unless attributes.include?(attribute_name)
500
+
501
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
502
+ attributes[attribute_name] << [find_parameter_position(multiparameter_name), parameter_value]
503
+ end
504
+
505
+ attributes.each { |name, values| attributes[name] = values.sort_by { |v| v.first }.collect { |v| v.last } }
506
+ end
507
+
508
+
509
+ def type_cast_attribute_value(multiparameter_name, value)
510
+ multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
511
+ end
512
+
513
+ def find_parameter_position(multiparameter_name)
514
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
515
+ end
516
+
517
+ # Tracks the current changes and clears the changed attributes hash. Called
518
+ # after saving the object.
519
+ def clear_changes
520
+ @previously_changed = changes
521
+ @changed_attributes.clear
522
+ end
523
+
524
+ def _classname=(value)
525
+ write_attribute_without_type_conversion("_classname", value)
526
+ end
527
+
528
+ end
529
+ end
530
+ end