neo4j 1.0.0.beta.17 → 1.0.0.beta.18

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -6,11 +6,11 @@ group 'test' do
6
6
  gem "rake", ">= 0.8.7"
7
7
  gem "rdoc", ">= 2.5.10"
8
8
  gem "horo", ">= 1.0.2"
9
- gem "rspec-apigen", ">= 0.0.4"
10
9
  gem "rspec", ">= 2.0.0"
11
10
  gem "rspec-rails-matchers", ">= 0.2.1"
11
+ gem "test-unit"
12
12
  end
13
13
 
14
14
  gem 'ruby-debug-base19' if RUBY_VERSION.include? "1.9"
15
15
  gem 'ruby-debug-base' if RUBY_VERSION.include? "1.8"
16
- gem "ruby-debug-ide"
16
+ gem "ruby-debug-ide"
data/README.rdoc CHANGED
@@ -15,13 +15,13 @@ Here are some of the major benefits of Neo4j.rb
15
15
 
16
16
  * Domain Modeling - use the language of a graph (nodes/relationship/properties) to express your domain !
17
17
  * Schema Less and Efficient storage of Semi Structured Information
18
- * No {O/R mismatch}[http://en.wikipedia.org/wiki/Object-relational_impedance_mismatch] - very natural to map a graph to an Object Oriented language like Ruby.
18
+ * No {O/R mismatch}[http://en.wikipedia.org/wiki/Object-relational_impedance_mismatch] - very natural to map a graph to an \Object Oriented language like Ruby.
19
19
  * {Performance}[http://www.oscon.com/oscon2009/public/schedule/detail/8364]
20
20
  * Embedded Database - no database tier, easier to install, test, deploy and configure. It is run in the same process as your application.
21
21
  * Express Queries as Traversals
22
22
  * Fast deep traversal instead of slow SQL queries that span many table joins.
23
23
  * Very natural to express graph related problem with traversals (recommendation engine, find shortest parth etc..)
24
- * Seamless integration with Ruby on Rails.
24
+ * Seamless integration with Ruby on \Rails.
25
25
  * ACID Transaction with rollbacks support.
26
26
 
27
27
  === Documentation
@@ -0,0 +1,65 @@
1
+ require 'rails/generators/named_base'
2
+ require 'rails/generators/active_model'
3
+
4
+ module Neo4j
5
+ module Generators
6
+ end
7
+ end
8
+
9
+ class Neo4j::Generators::Base < Rails::Generators::NamedBase #:nodoc:
10
+ def self.source_root
11
+ @_neo4j_source_root ||= File.expand_path(File.join(File.dirname(__FILE__),
12
+ 'neo4j', generator_name, 'templates'))
13
+ end
14
+ end
15
+
16
+ class Neo4j::Generators::ActiveModel < Rails::Generators::ActiveModel #:nodoc:
17
+ def self.all(klass)
18
+ "#{klass}.all"
19
+ end
20
+
21
+ def self.find(klass, params=nil)
22
+ "#{klass}.find(#{params})"
23
+ end
24
+
25
+ def self.build(klass, params=nil)
26
+ if params
27
+ "#{klass}.new(#{params})"
28
+ else
29
+ "#{klass}.new"
30
+ end
31
+ end
32
+
33
+ def save
34
+ "#{name}.save"
35
+ end
36
+
37
+ def update_attributes(params=nil)
38
+ "#{name}.update_attributes(#{params})"
39
+ end
40
+
41
+ def errors
42
+ "#{name}.errors"
43
+ end
44
+
45
+ def destroy
46
+ "#{name}.destroy"
47
+ end
48
+ end
49
+
50
+ module Rails
51
+ module Generators
52
+ class GeneratedAttribute #:nodoc:
53
+ def type_class
54
+ case type.to_s.downcase
55
+ when 'datetime' then 'DateTime'
56
+ when 'date' then 'Date'
57
+ when 'text' then 'String'
58
+ when 'integer', 'number', 'fixnum' then 'Fixnum'
59
+ when 'float' then 'Float'
60
+ else 'String'
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,39 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'neo4j.rb')
2
+
3
+ class Neo4j::Generators::ModelGenerator < Neo4j::Generators::Base
4
+ argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
5
+
6
+ check_class_collision
7
+
8
+ class_option :timestamps, :type => :boolean
9
+ class_option :parent, :type => :string, :desc => "The parent class for the generated model"
10
+
11
+ def create_model_file
12
+ template "model.rb", File.join('app/models', "#{singular_name}.rb")
13
+ end
14
+
15
+ protected
16
+ def migration?
17
+ false
18
+ end
19
+
20
+ def timestamps?
21
+ options[:timestamps]
22
+ end
23
+
24
+ def parent?
25
+ options[:parent]
26
+ end
27
+
28
+ def timestamp_statements
29
+ %q{
30
+ property :created_at, DateTime
31
+ # property :created_on, Date
32
+
33
+ property :updated_at, DateTime
34
+ # property :updated_on, Date
35
+ }
36
+ end
37
+
38
+ hook_for :test_framework
39
+ end
@@ -0,0 +1,7 @@
1
+ class <%= class_name %> < <%= parent? ? options[:parent].classify : "Neo4j::Rails::Model" %>
2
+ <% attributes.each do |attribute| -%>
3
+ property :<%= attribute.name %><%= ", :type => #{attribute.type_class}" unless attribute.type_class == "String" %>
4
+ <% end -%>
5
+
6
+ <%= timestamp_statements if timestamps? -%>
7
+ end
data/lib/neo4j.rb CHANGED
@@ -22,7 +22,7 @@ require 'neo4j/index/index'
22
22
  require 'neo4j/index/class_methods'
23
23
  require 'neo4j/index/index_registry'
24
24
  require 'neo4j/index/indexer'
25
- require 'neo4j/index/wrapped_query'
25
+ require 'neo4j/index/lucene_query'
26
26
  require 'neo4j/relationship_traverser'
27
27
  require 'neo4j/node_traverser'
28
28
  require 'neo4j/property'
@@ -36,7 +36,6 @@ require 'neo4j/mapping/class_methods/init_node'
36
36
  require 'neo4j/mapping/class_methods/init_rel'
37
37
  require 'neo4j/mapping/class_methods/root'
38
38
  require 'neo4j/mapping/class_methods/property'
39
- require 'neo4j/mapping/class_methods/index'
40
39
  require 'neo4j/mapping/class_methods/relationship'
41
40
  require 'neo4j/mapping/decl_relationship_dsl'
42
41
  require 'neo4j/mapping/has_n'
@@ -54,6 +53,7 @@ require 'neo4j/rails/transaction'
54
53
  require 'neo4j/rails/railtie'
55
54
  require 'neo4j/rails/validations/uniqueness'
56
55
  require 'neo4j/rails/model'
56
+ require 'neo4j/rails/properties'
57
57
  require 'neo4j/rails/value'
58
58
  require 'neo4j/rails/lucene_connection_closer'
59
59
 
data/lib/neo4j/config.rb CHANGED
@@ -2,9 +2,12 @@
2
2
  module Neo4j
3
3
 
4
4
 
5
- # Keeps configuration for neo4j.
5
+ # == Keeps configuration for neo4j.
6
6
  #
7
- # Neo4j::Config[:storage_path]:: is used for locating the neo4j database on the filesystem.
7
+ # The most important configuration is <tt>Neo4j::Config[:storage_path]</tt> which is used to
8
+ # locate where the neo4j database is stored on the filesystem.
9
+ # If this directory is empty then a new database will be created, otherwise it will use the
10
+ # database from that directory.
8
11
  #
9
12
  class Config
10
13
  # This code is copied from merb-core/config.rb.
@@ -45,8 +48,8 @@ module Neo4j
45
48
  # Set the value of a config entry.
46
49
  #
47
50
  # ==== Parameters
48
- # key<Object>:: The key to set the parameter for.
49
- # val<Object>:: The value of the parameter.
51
+ # key:: The key to set the parameter for.
52
+ # val:: The value of the parameter.
50
53
  #
51
54
  def []=(key, val)
52
55
  (@configuration ||= setup)[key] = val
@@ -56,7 +59,7 @@ module Neo4j
56
59
  # Gets the the value of a config entry
57
60
  #
58
61
  # ==== Parameters
59
- # key<Object>:: The key of the config entry value we want
62
+ # key:: The key of the config entry value we want
60
63
  #
61
64
  def [](key)
62
65
  (@configuration ||= setup)[key]
@@ -69,7 +72,7 @@ module Neo4j
69
72
  # key<Object>:: The key of the parameter to delete.
70
73
  #
71
74
  # ==== Returns
72
- # Object:: The value of the removed entry.
75
+ # The value of the removed entry.
73
76
  #
74
77
  def delete(key)
75
78
  @configuration.delete(key)
@@ -90,12 +93,11 @@ module Neo4j
90
93
  # Retrieve the value of a config entry, returning the provided default if the key is not present
91
94
  #
92
95
  # ==== Parameters
93
- # key<Object>:: The key to retrieve the parameter for.
94
- # default<Object>::
95
- # The default value to return if the parameter is not set.
96
+ # key:: The key to retrieve the parameter for.
97
+ # default::The default value to return if the parameter is not set.
96
98
  #
97
99
  # ==== Returns
98
- # Object:: The value of the configuration parameter or the default.
100
+ # The value of the configuration parameter or the default.
99
101
  #
100
102
  def fetch(key, default)
101
103
  @configuration.fetch(key, default)
@@ -116,7 +118,7 @@ module Neo4j
116
118
  # Returns the configuration as a hash.
117
119
  #
118
120
  # ==== Returns
119
- # Hash:: The config as a hash.
121
+ # The config as a hash.
120
122
  #
121
123
  def to_hash
122
124
  @configuration
@@ -125,7 +127,7 @@ module Neo4j
125
127
  # Returns the config as YAML.
126
128
  #
127
129
  # ==== Returns
128
- # String:: The config as YAML.
130
+ # The config as YAML.
129
131
  #
130
132
  def to_yaml
131
133
  require "yaml"
@@ -1,5 +1,5 @@
1
1
  module Neo4j
2
- class Database
2
+ class Database #:nodoc:
3
3
  attr_reader :graph, :lucene, :event_handler
4
4
 
5
5
  def initialize()
data/lib/neo4j/equal.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  module Neo4j
2
+
3
+ # == This mixin is used for both nodes and relationships to decide if two entities are equal or not.
4
+ #
2
5
  module Equal
3
6
  def equal?(o)
4
7
  eql?(o)
@@ -1,6 +1,30 @@
1
1
  module Neo4j
2
2
 
3
- # Handles events like a new node is created or deleted
3
+ # == Handles Transactional Events
4
+ #
5
+ # You can use this to receive event before the transaction commits.
6
+ # The following events are supported:
7
+ # * <tt>on_neo4j_started</tt>
8
+ # * <tt>on_neo4j_shutdown</tt>
9
+ # * <tt>on_node_created</tt>
10
+ # * <tt>on_node_deleted</tt>
11
+ # * <tt>on_relationship_created</tt>
12
+ # * <tt>on_relationship_deleted</tt>
13
+ # * <tt>on_property_changed</tt>
14
+ # * <tt>on_rel_property_changed</tt>
15
+ #
16
+ # === Usage
17
+ #
18
+ # class MyListener
19
+ # def on_node_deleted(node, old_props, tx_data)
20
+ # end
21
+ # end
22
+ #
23
+ # # to add an listener without starting neo4j:
24
+ # Neo4j.unstarted_db.event_handler.add(MyListener.new)
25
+ #
26
+ # You only need to implement the methods that you need.
27
+ #
4
28
  class EventHandler
5
29
  include org.neo4j.graphdb.event.TransactionEventHandler
6
30
 
@@ -88,18 +112,5 @@ module Neo4j
88
112
  def rel_property_changed(rel, key, old_value, new_value)
89
113
  @listeners.each {|li| li.on_rel_property_changed(rel, key, old_value, new_value) if li.respond_to?(:on_rel_property_changed)}
90
114
  end
91
-
92
- # TODO ?
93
- def tx_finished(tx)
94
- @listeners.each {|li| li.on_tx_finished(tx) if li.respond_to?(:on_tx_finished)}
95
- end
96
-
97
- def neo_started(neo_instance)
98
- @listeners.each {|li| li.on_neo_started(neo_instance) if li.respond_to?(:on_neo_started)}
99
- end
100
-
101
- def neo_stopped(neo_instance)
102
- @listeners.each {|li| li.on_neo_stopped(neo_instance) if li.respond_to?(:on_neo_stopped)}
103
- end
104
115
  end
105
116
  end
@@ -34,11 +34,12 @@ module Neo4j
34
34
  # class Phone
35
35
  # include Neo4j::NodeMixin
36
36
  # property :phone
37
- # index :phone, :indexer => Person, :via => proc{|node| node.incoming(:phone).first}
37
+ # node_indexer Contact # put index on the Contact class instead
38
+ # index :phone
38
39
  # end
39
40
  #
40
41
  # # Find an contact with a phone number, this works since they share the same index
41
- # Contact.find('phone: 12345 AND name: 'pelle'')
42
+ # Contact.find('phone: 12345').first #=> a phone object !
42
43
  #
43
44
  # ==== Returns
44
45
  # The indexer that should be used to index the given class
@@ -1,9 +1,9 @@
1
1
  module Neo4j
2
2
  module Index
3
- class Indexer #:nodoc:
3
+ class Indexer
4
4
  attr_reader :indexer_for, :field_types, :via_relationships
5
5
 
6
- def initialize(clazz, type)
6
+ def initialize(clazz, type) #:nodoc:
7
7
  # part of the unique name of the index
8
8
  @indexer_for = clazz
9
9
 
@@ -19,7 +19,7 @@ module Neo4j
19
19
  @parent_indexers = []
20
20
  end
21
21
 
22
- def inherit_fields_from(parent_index)
22
+ def inherit_fields_from(parent_index) #:nodoc:
23
23
  return unless parent_index
24
24
  @field_types.reverse_merge!(parent_index.field_types) if parent_index.respond_to?(:field_types)
25
25
  @via_relationships.reverse_merge!(parent_index.via_relationships) if parent_index.respond_to?(:via_relationships)
@@ -30,7 +30,53 @@ module Neo4j
30
30
  "Indexer @#{object_id} [index_for:#{@indexer_for}, field_types=#{@field_types.keys.join(', ')}, via=#{@via_relationships.inspect}]"
31
31
  end
32
32
 
33
- # add an index on a field so that it will be automatically updated by neo4j events.
33
+ # Add an index on a field so that it will be automatically updated by neo4j transactional events.
34
+ #
35
+ # The index method takes an optional configuration hash which allows you to:
36
+ #
37
+ # === Add an index on an a property
38
+ #
39
+ # Example:
40
+ # class Person
41
+ # include Neo4j::NodeMixin
42
+ # index :name
43
+ # end
44
+ #
45
+ # When the property name is changed/deleted or the node created it will keep the lucene index in sync.
46
+ # You can then perform a lucene query like this: Person.find('name: andreas')
47
+ #'
48
+ # === Add index on other nodes.
49
+ #
50
+ # Example:
51
+ #
52
+ # class Person
53
+ # include Neo4j::NodeMixin
54
+ # has_n(:friends).to(Contact)
55
+ # has_n(:known_by).from(:friends)
56
+ # index :user_id, :via => :known_by
57
+ # end
58
+ #
59
+ # Notice that you *must* specify an incoming relationship with the via key, as shown above.
60
+ # In the example above an index <tt>user_id</tt> will be added to all Person nodes which has a <tt>friends</tt> relationship
61
+ # that person with that user_id. This allows you to do lucene queries on your friends properties.
62
+ #
63
+ # === Set the type value to index
64
+ # By default all values will be indexed as Strings.
65
+ # If you want for example to do a numerical range query you must tell Neo4j.rb to index it as a numeric value.
66
+ # You do that with the key <tt>type</tt>
67
+ #
68
+ # Example:
69
+ # class Person
70
+ # include Neo4j::NodeMixin
71
+ # index :weight, :type => Float
72
+ # end
73
+ #
74
+ # Supported values for <tt>:type</tt> is <tt>String</tt>, <tt>Float</tt> and <tt>Fixnum</tt>
75
+ #
76
+ # === For more information
77
+ # * See Neo4j::Index::LuceneQuery
78
+ # * See #find
79
+ #
34
80
  def index(field, conf = {})
35
81
  if conf[:via]
36
82
  rel_dsl = @indexer_for._decl_rels[conf[:via]]
@@ -48,7 +94,7 @@ module Neo4j
48
94
  end
49
95
  end
50
96
 
51
- def remove_index_on_fields(node, props, tx_data)
97
+ def remove_index_on_fields(node, props, tx_data) #:nodoc:
52
98
  @field_types.keys.each { |field| rm_index(node, field, props[field]) if props[field] }
53
99
  # remove all via indexed fields
54
100
  @via_relationships.each_value do |dsl|
@@ -61,15 +107,15 @@ module Neo4j
61
107
  end
62
108
  end
63
109
 
64
- def update_on_deleted_relationship(relationship)
110
+ def update_on_deleted_relationship(relationship) #:nodoc:
65
111
  update_on_relationship(relationship, false)
66
112
  end
67
113
 
68
- def update_on_new_relationship(relationship)
114
+ def update_on_new_relationship(relationship) #:nodoc:
69
115
  update_on_relationship(relationship, true)
70
116
  end
71
117
 
72
- def update_on_relationship(relationship, is_created)
118
+ def update_on_relationship(relationship, is_created) #:nodoc:
73
119
  rel_type = relationship.rel_type
74
120
  end_node = relationship._end_node
75
121
  # find which via relationship match rel_type
@@ -93,7 +139,7 @@ module Neo4j
93
139
  end
94
140
  end
95
141
 
96
- def update_index_on(node, field, old_val, new_val)
142
+ def update_index_on(node, field, old_val, new_val) #:nodoc:
97
143
  if @via_relationships.include?(field)
98
144
  dsl = @via_relationships[field]
99
145
  to_class = dsl.to_class
@@ -106,42 +152,94 @@ module Neo4j
106
152
  update_single_index_on(node, field, old_val, new_val)
107
153
  end
108
154
 
109
- def update_single_index_on(node, field, old_val, new_val)
155
+ def update_single_index_on(node, field, old_val, new_val) #:nodoc:
110
156
  if @field_types.include?(field)
111
157
  rm_index(node, field, old_val) if old_val
112
158
  add_index(node, field, new_val) if new_val
113
159
  end
114
160
  end
115
161
 
162
+ # Returns true if there is an index on the given field.
163
+ #
116
164
  def index?(field)
117
165
  @field_types.include?(field.to_s)
118
166
  end
119
167
 
120
- def index_type_for(field)
168
+ # Returns the type of index for the given field (e.g. :exact or :fulltext)
169
+ #
170
+ def index_type_for(field) #:nodoc:
121
171
  return nil unless index?(field)
122
172
  @field_types[field.to_s]
123
173
  end
124
174
 
175
+ # Returns true if there is an index of the given type defined.
125
176
  def index_type?(type)
126
177
  @field_types.values.include?(type)
127
178
  end
128
179
 
180
+ # Adds an index on the given entity
181
+ # This is normally not needed since you can instead declare an index which will automatically keep
182
+ # the lucene index in sync. See #index
183
+ #
129
184
  def add_index(entity, field, value)
130
185
  return false unless @field_types.has_key?(field)
186
+
187
+ # we might need to know what type the properties are when indexing and querying
188
+ @decl_props ||= @indexer_for.respond_to?(:_decl_props) && @indexer_for._decl_props
189
+
190
+ type = @decl_props && @decl_props[field.to_sym] && @decl_props[field.to_sym][:type]
191
+ if type
192
+ raise "Can't index #{type} with value #{value} since it is not a #{type}" unless type === value
193
+ value = if String != type
194
+ org.neo4j.index.impl.lucene.ValueContext.new(value).indexNumeric
195
+ else
196
+ org.neo4j.index.impl.lucene.ValueContext.new(value)
197
+ end
198
+
199
+ end
200
+
131
201
  index_for_field(field.to_s).add(entity, field, value)
132
202
  @parent_indexers.each { |i| i.add_index(entity, field, value) }
133
203
  end
134
204
 
205
+ # Removes an index on the given entity
206
+ # This is normally not needed since you can instead declare an index which will automatically keep
207
+ # the lucene index in sync. See #index
208
+ #
135
209
  def rm_index(entity, field, value)
136
210
  return false unless @field_types.has_key?(field)
137
211
  index_for_field(field).remove(entity, field, value)
138
212
  @parent_indexers.each { |i| i.rm_index(entity, field, value) }
139
213
  end
140
214
 
215
+ # Performs a Lucene Query.
216
+ #
217
+ # In order to use this you have to declare an index on the fields first, see #index.
218
+ # Notice that you should close the lucene query after the query has been executed.
219
+ # You can do that either by provide an block or calling the Neo4j::Index::LuceneQuery#close
220
+ # method. When performing queries from Ruby on Rails you do not need this since it will be automatically closed
221
+ # (by Rack).
222
+ #
223
+ # === Example, with a block
224
+ #
225
+ # Person.find('name: kalle') {|query| puts "#{[*query].join(', )"}
226
+ #
227
+ # ==== Example
228
+ #
229
+ # query = Person.find('name: kalle')
230
+ # puts "First item #{query.first}"
231
+ # query.close
232
+ #
233
+ # === Return Value
234
+ # It will return a Neo4j::Index::LuceneQuery object
235
+ #
236
+ #
141
237
  def find(query, params = {})
142
- type = params[:type] || :exact
143
- index = index_for_type(type)
144
- query = (params[:wrapped].nil? || params[:wrapped]) ? WrappedQuery.new(index, query) : index.query(query)
238
+ # we might need to know what type the properties are when indexing and querying
239
+ @decl_props ||= @indexer_for.respond_to?(:_decl_props) && @indexer_for._decl_props
240
+
241
+ index = index_for_type(params[:type] || :exact)
242
+ query = (params[:wrapped].nil? || params[:wrapped]) ? LuceneQuery.new(index, @decl_props, query) : index.query(query)
145
243
 
146
244
  if block_given?
147
245
  begin
@@ -155,7 +253,7 @@ module Neo4j
155
253
  end
156
254
  end
157
255
 
158
- # clears the index, if no type is provided clear all types of indexes
256
+ # delete the index, if no type is provided clear all types of indexes
159
257
  def delete_index_type(type=nil)
160
258
  if type
161
259
  #raise "can't clear index of type '#{type}' since it does not exist ([#{@field_types.values.join(',')}] exists)" unless index_type?(type)
@@ -167,12 +265,14 @@ module Neo4j
167
265
  end
168
266
  end
169
267
 
170
- def on_neo4j_shutdown
268
+ def on_neo4j_shutdown #:nodoc:
171
269
  # Since we might start the database again we must make sure that we don't keep any references to
172
- # an old lucene index service.
270
+ # an old lucene index in memory.
173
271
  @indexes.clear
174
272
  end
175
273
 
274
+ # Removes the cached lucene index, can be useful for some RSpecs which needs to restart the Neo4j.
275
+ #
176
276
  def rm_field_type(type=nil)
177
277
  if type
178
278
  @field_types.delete_if { |k, v| v == type }
@@ -181,22 +281,22 @@ module Neo4j
181
281
  end
182
282
  end
183
283
 
184
- def index_for_field(field)
284
+ def index_for_field(field) #:nodoc:
185
285
  type = @field_types[field]
186
286
  @indexes[type] ||= create_index_with(type)
187
287
  end
188
288
 
189
- def index_for_type(type)
289
+ def index_for_type(type) #:nodoc:
190
290
  @indexes[type] ||= create_index_with(type)
191
291
  end
192
292
 
193
- def lucene_config(type)
293
+ def lucene_config(type) #:nodoc:
194
294
  conf = Neo4j::Config[:lucene][type.to_sym]
195
295
  raise "unknown lucene type #{type}" unless conf
196
296
  conf
197
297
  end
198
298
 
199
- def create_index_with(type)
299
+ def create_index_with(type) #:nodoc:
200
300
  db=Neo4j.started_db
201
301
  index_config = lucene_config(type)
202
302
  if @type == :node