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.
- 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
|