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

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