neo4j 2.2.3-java → 2.2.4-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 (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