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.
- checksums.yaml +7 -0
- data/CHANGELOG +5 -0
- data/Gemfile +2 -3
- data/README.rdoc +19 -14
- data/lib/neo4j/rails/attributes.rb +2 -2
- data/lib/neo4j/rails/attributes.rb~ +530 -0
- data/lib/neo4j/rails/node_persistance.rb +9 -0
- data/lib/neo4j/rails/persistence.rb +2 -2
- data/lib/neo4j/rails/persistence.rb~ +186 -0
- data/lib/neo4j/rails/relationship_persistence.rb +9 -0
- data/lib/neo4j/version.rb +1 -1
- data/neo4j.gemspec +1 -1
- metadata +22 -66
- data/lib/db/active_tx_log +0 -1
- data/lib/db/index/lucene-store.db +0 -0
- data/lib/db/index/lucene.log.active +0 -0
- data/lib/db/index/lucene.log.v0 +0 -0
- data/lib/db/messages.log +0 -286
- data/lib/db/neostore +0 -0
- data/lib/db/neostore.id +0 -0
- data/lib/db/neostore.nodestore.db +0 -0
- data/lib/db/neostore.nodestore.db.id +0 -0
- data/lib/db/neostore.propertystore.db +0 -0
- data/lib/db/neostore.propertystore.db.arrays +0 -0
- data/lib/db/neostore.propertystore.db.arrays.id +0 -0
- data/lib/db/neostore.propertystore.db.id +0 -0
- data/lib/db/neostore.propertystore.db.index +0 -0
- data/lib/db/neostore.propertystore.db.index.id +0 -0
- data/lib/db/neostore.propertystore.db.index.keys +0 -0
- data/lib/db/neostore.propertystore.db.index.keys.id +0 -0
- data/lib/db/neostore.propertystore.db.strings +0 -0
- data/lib/db/neostore.propertystore.db.strings.id +0 -0
- data/lib/db/neostore.relationshipstore.db +0 -0
- data/lib/db/neostore.relationshipstore.db.id +0 -0
- data/lib/db/neostore.relationshiptypestore.db +0 -0
- data/lib/db/neostore.relationshiptypestore.db.id +0 -0
- data/lib/db/neostore.relationshiptypestore.db.names +0 -0
- data/lib/db/neostore.relationshiptypestore.db.names.id +0 -0
- data/lib/db/nioneo_logical.log.active +0 -0
- data/lib/db/nioneo_logical.log.v0 +0 -0
- data/lib/db/tm_tx_log.1 +0 -0
checksums.yaml
ADDED
@@ -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
|
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',
|
22
|
+
#gem 'shoulda-matchers', could not get it working with jruby
|
23
23
|
gem "test-unit"
|
24
24
|
end
|
25
|
-
|
data/README.rdoc
CHANGED
@@ -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
|
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
|
-
|
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::
|
53
|
+
{Neo4j::Rails::Observer}[http://rdoc.info/github/andreasronge/neo4j/Neo4j/Rails/Observer]
|
53
54
|
|
54
|
-
{Neo4j::Rails::
|
55
|
+
{Neo4j::Rails::HaConsole::Railitie}[http://rdoc.info/github/andreasronge/neo4j/Neo4j/Rails/HaConsole/Railtie]
|
55
56
|
|
56
|
-
{Neo4j::
|
57
|
+
{Neo4j::Rails::Versioning}[http://rdoc.info/github/andreasronge/neo4j/Neo4j/Rails/Versioning]
|
57
58
|
|
58
|
-
{Neo4j::Rails::
|
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.
|
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
|