neo4j 1.0.0.beta.19 → 1.0.0.beta.20

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.
@@ -3,6 +3,7 @@ Maintainer:
3
3
 
4
4
  Contributors:
5
5
  * Ben Jackson
6
+ * Dwight van Tuyl
6
7
  * Martin Kleppmann
7
8
  * Peter Neubauer
8
9
  * Jan-Felix Wittmann
data/Gemfile CHANGED
@@ -11,6 +11,6 @@ group 'test' do
11
11
  gem "test-unit"
12
12
  end
13
13
 
14
- gem 'ruby-debug-base19' if RUBY_VERSION.include? "1.9"
15
- gem 'ruby-debug-base' if RUBY_VERSION.include? "1.8"
16
- gem "ruby-debug-ide"
14
+ #gem 'ruby-debug-base19' if RUBY_VERSION.include? "1.9"
15
+ #gem 'ruby-debug-base' if RUBY_VERSION.include? "1.8"
16
+ #gem "ruby-debug-ide"
@@ -26,8 +26,8 @@ Here are some of the major benefits of Neo4j.rb
26
26
 
27
27
  === Documentation
28
28
 
29
- * {Documentation and Getting Started with Neo4j}[http://github.com/andreasronge/neo4j/wiki]
30
- * {API Documentation (released version)}[http://neo4j.rubyforge.org]
29
+ * {API Documentation}[http://neo4j.rubyforge.org]
30
+ * {Documentation and Getting Started with Neo4j}[http://neo4j.rubyforge.org/guides/index.html]
31
31
 
32
32
  === Some Examples
33
33
 
@@ -39,8 +39,8 @@ Example of creating a Neo4j::Node
39
39
  require 'neo4j'
40
40
 
41
41
  Neo4j::Transaction.run do
42
- node = Neo4j::Node.new :name => 'andreas'
43
- node.outgoing(:friends) << Neo4j::Node.new :name => 'peter'
42
+ node = Neo4j::Node.new (:name => 'andreas')
43
+ node.outgoing(:friends) << Neo4j::Node.new (:name => 'peter')
44
44
  node.outgoing(:friends).each {|node| puts "name #{node[:name]}"}
45
45
  end
46
46
 
@@ -59,8 +59,8 @@ Example of mapping a ruby class to a node and delaring properties and relationsh
59
59
  end
60
60
 
61
61
  # assume we have an transaction already running
62
- andreas = Person.new :name => 'andreas'
63
- andreas.friends << Person.new :name => 'peter'
62
+ andreas = Person.new (:name => 'andreas')
63
+ andreas.friends << Person.new (:name => 'peter')
64
64
  andreas.friends.each {|person| puts "name #{person.name}" }
65
65
  Person.find("name: andreas").first.name # => 'andreas'
66
66
 
@@ -88,6 +88,28 @@ Example of using Neo4j with Rails 3 (ActiveModel)
88
88
  ...
89
89
  end
90
90
 
91
+ ==== Generate a Rails Application
92
+
93
+ Example of creating an Neo4j Application from scratch:
94
+
95
+ gem install rails
96
+ rails new myapp -m http://andreasronge.github.com/rails3.rb
97
+ cd myapp
98
+ bundle
99
+ rails generate scaffold User name:string email:string
100
+ rails s
101
+ open a webbrowser: http://localhost:3000/users
102
+
103
+ You may get an error message "ERROR IOError: Connection reset by peer"
104
+ using WEBrick (it still works). This error message is not shown when running
105
+ the application with {Tomcat/Trinidad}[https://github.com/calavera/trinidad]
106
+
107
+ To run it with Tomcat
108
+
109
+ gem install trinidad
110
+ trinidad
111
+
112
+
91
113
  === Presentation Materials and other URLs
92
114
  * {Intoduction to Neo4j.rb}[http://www.slideshare.net/andreasronge/neo4jrb]
93
115
  * {Nordic Ruby 2010 May 21-23}[http://nordicruby.org/speakers#user_29]
@@ -9,7 +9,7 @@ class Neo4j::Generators::ModelGenerator < Neo4j::Generators::Base
9
9
  class_option :parent, :type => :string, :desc => "The parent class for the generated model"
10
10
 
11
11
  def create_model_file
12
- template "model.rb", File.join('app/models', "#{singular_name}.rb")
12
+ template "model.erb", File.join('app/models', "#{singular_name}.rb")
13
13
  end
14
14
 
15
15
  protected
@@ -27,11 +27,11 @@ class Neo4j::Generators::ModelGenerator < Neo4j::Generators::Base
27
27
 
28
28
  def timestamp_statements
29
29
  %q{
30
- property :created_at, DateTime
31
- # property :created_on, Date
30
+ property :created_at, DateTime
31
+ # property :created_on, Date
32
32
 
33
- property :updated_at, DateTime
34
- # property :updated_on, Date
33
+ property :updated_at, DateTime
34
+ # property :updated_on, Date
35
35
  }
36
36
  end
37
37
 
@@ -1,6 +1,6 @@
1
1
  class <%= class_name %> < <%= parent? ? options[:parent].classify : "Neo4j::Rails::Model" %>
2
2
  <% attributes.each do |attribute| -%>
3
- property :<%= attribute.name %><%= ", :type => #{attribute.type_class}" unless attribute.type_class == "String" %>
3
+ property :<%= attribute.name %><%= ", :type => #{attribute.type_class}" %>
4
4
  <% end -%>
5
5
 
6
6
  <%= timestamp_statements if timestamps? -%>
@@ -20,7 +20,7 @@ require 'neo4j/neo4j'
20
20
 
21
21
  require 'neo4j/index/index'
22
22
  require 'neo4j/index/class_methods'
23
- require 'neo4j/index/index_registry'
23
+ require 'neo4j/index/indexer_registry'
24
24
  require 'neo4j/index/indexer'
25
25
  require 'neo4j/index/lucene_query'
26
26
  require 'neo4j/relationship_traverser'
@@ -53,8 +53,11 @@ require 'neo4j/rails/tx_methods'
53
53
  require 'neo4j/rails/transaction'
54
54
  require 'neo4j/rails/railtie'
55
55
  require 'neo4j/rails/validations/uniqueness'
56
+ require 'neo4j/rails/validations/non_nil'
57
+ require 'neo4j/rails/finders'
58
+ require 'neo4j/rails/mapping/property'
56
59
  require 'neo4j/rails/model'
57
- require 'neo4j/rails/properties'
60
+ require 'neo4j/rails/value_properties'
58
61
  require 'neo4j/rails/value'
59
62
  require 'neo4j/rails/lucene_connection_closer'
60
63
 
@@ -26,6 +26,13 @@ module Neo4j
26
26
  @defaults ||= {
27
27
  :storage_path => 'tmp/neo4j',
28
28
  :timestamps => true,
29
+
30
+ # TODO: Just pickup all converter classes that are in the Neo4j::TypeConverters module?
31
+ :converters => { Date => Neo4j::TypeConverters::DateConverter,
32
+ DateTime => Neo4j::TypeConverters::DateTimeConverter,
33
+ Time => Neo4j::TypeConverters::TimeConverter
34
+ },
35
+
29
36
  :lucene => {
30
37
  :fulltext => {"provider" => "lucene", "type" => "fulltext" },
31
38
  :exact => {"provider" => "lucene", "type" => "exact" }}
@@ -83,7 +83,7 @@ module Neo4j
83
83
  rel_dsl = @indexer_for._decl_rels[conf[:via]]
84
84
  raise "No relationship defined for '#{conf[:via]}'. Check class '#{@indexer_for}': index :#{field}, via=>:#{conf[:via]} <-- error. Define it with a has_one or has_n" unless rel_dsl
85
85
  raise "Only incoming relationship are possible to define index on. Check class '#{@indexer_for}': index :#{field}, via=>:#{conf[:via]}" unless rel_dsl.incoming?
86
- via_indexer = rel_dsl.to_class._indexer
86
+ via_indexer = rel_dsl.target_class._indexer
87
87
 
88
88
  field = field.to_s
89
89
  @via_relationships[field] = rel_dsl
@@ -99,7 +99,7 @@ module Neo4j
99
99
  @field_types.keys.each { |field| rm_index(node, field, props[field]) if props[field] }
100
100
  # remove all via indexed fields
101
101
  @via_relationships.each_value do |dsl|
102
- indexer = dsl.to_class._indexer
102
+ indexer = dsl.target_class._indexer
103
103
  tx_data.deleted_relationships.each do |rel|
104
104
  start_node = rel._start_node
105
105
  next if node != rel._end_node
@@ -122,14 +122,14 @@ module Neo4j
122
122
  # find which via relationship match rel_type
123
123
  @via_relationships.each_pair do |field, dsl|
124
124
  # have we declared an index on this changed relationship ?
125
- next unless dsl.incoming_dsl.namespace_type == rel_type
125
+ next unless dsl.rel_type == rel_type
126
126
 
127
127
  # yes, so find the node and value we should update the index on
128
128
  val = end_node[field]
129
129
  start_node = relationship._start_node
130
130
 
131
131
  # find the indexer to use
132
- indexer = dsl.to_class._indexer
132
+ indexer = dsl.target_class._indexer
133
133
 
134
134
  # is the relationship created or deleted ?
135
135
  if is_created
@@ -143,11 +143,11 @@ module Neo4j
143
143
  def update_index_on(node, field, old_val, new_val) #:nodoc:
144
144
  if @via_relationships.include?(field)
145
145
  dsl = @via_relationships[field]
146
- to_class = dsl.to_class
146
+ target_class = dsl.target_class
147
147
 
148
148
  dsl._all_relationships(node).each do |rel|
149
149
  other = rel._start_node
150
- to_class._indexer.update_single_index_on(other, field, old_val, new_val)
150
+ target_class._indexer.update_single_index_on(other, field, old_val, new_val)
151
151
  end
152
152
  end
153
153
  update_single_index_on(node, field, old_val, new_val)
@@ -309,4 +309,4 @@ module Neo4j
309
309
 
310
310
  end
311
311
 
312
- end
312
+ end
@@ -75,7 +75,6 @@ module Neo4j::Mapping
75
75
  return false if _decl_props[prop_name.to_sym].nil?
76
76
  _decl_props[prop_name.to_sym][:defined] == true
77
77
  end
78
-
79
78
  end
80
79
  end
81
80
  end
@@ -33,39 +33,26 @@ module Neo4j::Mapping
33
33
  class DeclRelationshipDsl
34
34
  include Neo4j::ToJava
35
35
 
36
- attr_reader :to_type, :to_class, :cascade_delete_prop_name, :counter, :rel_id, :direction
36
+ attr_reader :target_class, :direction, :rel_type
37
37
 
38
- def initialize(rel_id, has_one, to_class, params)
39
- @direction = :outgoing
40
- @rel_id = rel_id
41
- @to_type = rel_id
42
- @has_one = has_one
43
- @namespace_type = rel_id
44
- @to_class = to_class
45
- end
46
-
47
- def has_one?
48
- @has_one
38
+ def initialize(method_id, has_one, target_class, params)
39
+ @direction = :outgoing
40
+ @method_id = method_id
41
+ @has_one = has_one
42
+ @rel_type = method_id
43
+ @target_class = target_class
49
44
  end
50
45
 
51
- def class_and_type_from_args(args) # :nodoc:
52
- if (args.size > 1)
53
- return args[0], args[1]
54
- elsif (Symbol === args[0])
55
- return @to_class, args[0]
56
- else
57
- return args[0], @rel_id
58
- end
46
+ def to_s
47
+ "DeclRelationshipDsl #{object_id} dir: #{@direction} rel_id: #{@method_id}, rel_type: #{@rel_type}, target_class:#{@target_class} rel_class:#{@relationship}"
59
48
  end
60
49
 
61
-
62
- # The actual relationship type that this DSL will use
63
- def namespace_type
64
- @namespace_type
50
+ def has_one?
51
+ @has_one
65
52
  end
66
53
 
67
- def each_node(node, direction, &block)
68
- type = type_to_java(namespace_type)
54
+ def each_node(node, direction, &block) #:nodoc:
55
+ type = type_to_java(rel_type)
69
56
  dir = dir_to_java(direction)
70
57
  node._java_node.getRelationships(type, dir).each do |rel|
71
58
  other = rel.getOtherNode(node).wrapper
@@ -73,59 +60,35 @@ module Neo4j::Mapping
73
60
  end
74
61
  end
75
62
 
76
- def incoming?
63
+ def incoming? #:nodoc:
77
64
  @direction == :incoming
78
65
  end
79
66
 
80
- def incoming_dsl
81
- dsl = @to_class._decl_rels[to_type]
82
- raise "Unspecified outgoing relationship '#{to_type}' for incoming relationship '#{rel_id}' on class #{to_class}" if dsl.nil?
83
- dsl
84
- end
85
-
86
- def single_node(node)
67
+ def single_node(node) #:nodoc:
87
68
  rel = single_relationship(node)
88
69
  rel && rel.other_node(node).wrapper
89
70
  end
90
71
 
91
- def single_relationship(node)
92
- dir = direction
93
- dsl = incoming? ? incoming_dsl : self
94
- node._java_node.rel(dir, dsl.namespace_type)
72
+ def single_relationship(node) #:nodoc:
73
+ node._java_node.rel(direction, rel_type)
95
74
  end
96
75
 
97
- def _all_relationships(node)
98
- dsl = incoming? ? incoming_dsl : self
99
- type = type_to_java(dsl.namespace_type)
76
+ def _all_relationships(node) #:nodoc:
77
+ type = type_to_java(rel_type)
100
78
  dir = dir_to_java(direction)
101
79
  node._java_node.getRelationships(type, dir)
102
80
  end
103
81
 
104
- def all_relationships(node)
105
- dsl = incoming? ? incoming_dsl : self
106
- Neo4j::RelationshipTraverser.new(node._java_node, [dsl.namespace_type], direction)
82
+ def all_relationships(node) #:nodoc:
83
+ Neo4j::RelationshipTraverser.new(node._java_node, [rel_type], direction)
107
84
  end
108
85
 
109
- def create_relationship_to(node, other)
110
- if incoming?
111
- # If we are creating an incoming relationship, we need to swap incoming and outgoing nodes
112
- dsl = incoming_dsl
113
- from, to = other, node
114
- else
115
- dsl = self
116
- from, to = node, other
117
- end
118
-
119
- java_type = type_to_java(dsl.namespace_type)
120
-
121
- rel = from._java_node.create_relationship_to(to._java_node, java_type)
122
- rel[:_classname] = dsl.relationship_class.to_s if dsl.relationship_class
123
-
124
- # TODO - not implemented yet
125
- # the from.neo_id is only used for cascade_delete_incoming since that node will be deleted when all the list items has been deleted.
126
- # if cascade_delete_outgoing all nodes will be deleted when the root node is deleted
127
- # if cascade_delete_incoming then the root node will be deleted when all root nodes' outgoing nodes are deleted
128
- #rel[@dsl.cascade_delete_prop_name] = node.neo_id if @dsl.cascade_delete?
86
+ def create_relationship_to(node, other) # :nodoc:
87
+ from, to = incoming? ? [other, node] : [node, other]
88
+ java_type = type_to_java(rel_type)
89
+
90
+ rel = from._java_node.create_relationship_to(to._java_node, java_type)
91
+ rel[:_classname] = relationship_class.to_s if relationship_class
129
92
  rel.wrapper
130
93
  end
131
94
 
@@ -148,33 +111,74 @@ module Neo4j::Mapping
148
111
  #
149
112
  def to(*args)
150
113
  @direction = :outgoing
151
- @to_class, @to_type = class_and_type_from_args(args)
152
- @namespace_type = "#{@to_class.to_s}##{@to_type.to_s}"
114
+
115
+ if (args.size > 1)
116
+ raise "only one argument expected - the class of the node this relationship points to, got #{args.inspect}"
117
+ elsif (Class === args[0])
118
+ # handle e.g. has_n(:friends).to(class)
119
+ @target_class = args[0]
120
+ @rel_type = "#{@target_class}##{@method_id}"
121
+ else
122
+ raise "Expected a class for, got #{args[0]}"
123
+ end
153
124
  self
154
125
  end
155
126
 
156
127
  # Specifies an incoming relationship.
157
128
  # Will use the outgoing relationship given by the from class.
158
129
  #
159
- # ==== Example
130
+ # ==== Example, with prefix FileNode
160
131
  # class FolderNode
161
132
  # include Neo4j::NodeMixin
162
133
  # has_n(:files).to(FileNode)
163
134
  # end
164
135
  #
165
- # class FileNode
166
- # include Neo4j::NodeMixin
167
- # has_one(:folder).from(FileNode, :files)
168
- # end
136
+ # class FileNode
137
+ # include Neo4j::NodeMixin
138
+ # # will only traverse any incoming relationship of type files from node FileNode
139
+ # has_one(:folder).from(FileNode, :files)
140
+ # end
141
+ #
142
+ # file = FileNode.new
143
+ # # create an outgoing relationship of type 'FileNode#files' from folder node to file (FileNode is the prefix).
144
+ # file.folder = FolderNode.new
145
+ #
146
+ # ==== Example, without prefix
147
+ #
148
+ # class FolderNode
149
+ # include Neo4j::NodeMixin
150
+ # has_n(:files)
151
+ # end
152
+ #
153
+ # class FileNode
154
+ # include Neo4j::NodeMixin
155
+ # has_one(:folder).from(:files) # will traverse any incoming relationship of type files
156
+ # end
157
+ #
158
+ # file = FileNode.new
159
+ # # create an outgoing relationship of type 'FileNode#files' from folder node to file
160
+ # file.folder = FolderNode.new
169
161
  #
170
- # file = FileNode.new
171
- # # create an outgoing relationship of type 'FileNode#files' from folder node to file
172
- # file.folder = FolderNode.new
173
162
  #
174
163
  def from(*args)
175
- #(clazz, type)
176
164
  @direction = :incoming
177
- @to_class, @to_type = class_and_type_from_args(args)
165
+
166
+ if (args.size > 1)
167
+ # handle specified (prefixed) relationship, e.g. has_n(:known_by).from(clazz, :type)
168
+ @rel_type = "#{@target_class}##{args[1]}"
169
+ @target_class = args[0]
170
+ other_class_dsl = @target_class._decl_rels[args[1]]
171
+ if other_class_dsl
172
+ @relationship = other_class_dsl.relationship_class
173
+ else
174
+ puts "WARNING: Unknown outgoing relationship #{args[1]} on #{@target_class}"
175
+ end
176
+ elsif (Symbol === args[0])
177
+ # handle unspecified (unprefixed) relationship, e.g. has_n(:known_by).from(:type)
178
+ @rel_type = args[0]
179
+ else
180
+ raise "Expected a symbol for, got #{args[0]}"
181
+ end
178
182
  self
179
183
  end
180
184
 
@@ -17,11 +17,11 @@ module Neo4j
17
17
  def initialize(node, dsl) # :nodoc:
18
18
  @node = node
19
19
  @direction = dsl.direction
20
- @dsl = @direction == :outgoing ? dsl : dsl.incoming_dsl
20
+ @dsl = dsl
21
21
  end
22
22
 
23
23
  def to_s
24
- "HasN [#@direction, #{@node.neo_id} #{@dsl.namespace_type}]"
24
+ "HasN [#@direction, id: #{@node.neo_id} type: #{@dsl.rel_type} dsl:#{@dsl}]"
25
25
  end
26
26
 
27
27
  def size
@@ -74,11 +74,7 @@ module Neo4j
74
74
  # self
75
75
  #
76
76
  def <<(other)
77
- if @direction == :incoming && @dsl.direction == :outgoing
78
- @dsl.create_relationship_to(other, @node)
79
- else
80
- @dsl.create_relationship_to(@node, other)
81
- end
77
+ @dsl.create_relationship_to(@node, other)
82
78
  self
83
79
  end
84
80
  end
@@ -86,5 +86,10 @@ module Neo4j
86
86
  def event_handler(this_db = default_db)
87
87
  this_db.event_handler
88
88
  end
89
+
90
+ # Ruby to Java type converters
91
+ def converters
92
+ Neo4j::Config[:converters] || {}
93
+ end
89
94
  end
90
95
  end
@@ -61,8 +61,14 @@ module Neo4j
61
61
  end
62
62
 
63
63
  def new(other_node)
64
- raise "Only allowed to create outgoing relationships, please add it on the other node if you want to create an incoming relationship" unless @dir == org.neo4j.graphdb.Direction::OUTGOING
65
- @from.create_relationship_to(other_node, @type)
64
+ case @dir
65
+ when org.neo4j.graphdb.Direction::OUTGOING
66
+ @from.create_relationship_to(other_node, @type)
67
+ when org.neo4j.graphdb.Direction::INCOMING
68
+ other_node._java_node.create_relationship_to(@from, @type)
69
+ else
70
+ raise "Only allowed to create outgoing or incoming relationships (not #@dir)"
71
+ end
66
72
  end
67
73
 
68
74
  def both(type)
@@ -27,8 +27,9 @@ module Neo4j
27
27
  #
28
28
  def attributes
29
29
  attr = props
30
- attr.keys.each { |k| attr.delete k if k[0] == ?_ }
31
- attr
30
+ ret = {}
31
+ attr.each_pair { |k, v| ret[k] = wrapper.respond_to?(k) ? wrapper.send(k) : v unless k.to_s[0] == ?_ }
32
+ ret
32
33
  end
33
34
 
34
35
  # Checks if the given key exist as a property.
@@ -107,4 +108,4 @@ module Neo4j
107
108
  end
108
109
 
109
110
  end
110
- end
111
+ end
@@ -0,0 +1,121 @@
1
+ module Neo4j
2
+ module Rails
3
+ module Finders
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ rule :_all
8
+ end
9
+
10
+ module ClassMethods
11
+ # overwrite the index method to add find_by_xxx class methods
12
+ def index(*args)
13
+ field = args.first
14
+ module_eval <<-RUBY, __FILE__, __LINE__
15
+ def self.all_by_#{field}(value)
16
+ find_with_indexer("#{field}: \\"\#{value}\\"")
17
+ end
18
+
19
+ def self.find_by_#{field}(value)
20
+ all_by_#{field}(value).first
21
+ end
22
+ RUBY
23
+
24
+ super
25
+ end
26
+
27
+ # load an id or array of ids from the database
28
+ def load(*ids)
29
+ result = ids.map { |id| Neo4j::Node.load(id) }
30
+ if ids.length == 1
31
+ result.first
32
+ else
33
+ result
34
+ end
35
+ end
36
+
37
+ # Behave like the ActiveRecord query interface
38
+
39
+ # Handle Model.find(params[:id])
40
+
41
+ # Model.find
42
+ # Model.find(:first)
43
+
44
+ # Model.find("1")
45
+ # Model.find(1)
46
+
47
+ # Model.find("name: test")
48
+ # Model.find(:name => "test")
49
+
50
+ # Model.find(:first, "name: test")
51
+ # Model.find(:first, { :name => "test" })
52
+
53
+ # Model.find(:first, :conditions => "name: test")
54
+ # Model.find(:first, :conditions => { :name => "test" })
55
+
56
+ # Model.find(:all, "name: test")
57
+ # Model.find(:all, { :name => "test" })
58
+
59
+ # Model.find(:all, :conditions => "name: test")
60
+ # Model.find(:all, :conditions => { :name => "test" })
61
+ def find(*args)
62
+ case args.first
63
+ when :all, :first
64
+ kind = args.shift
65
+ send(kind, *args)
66
+ else
67
+ find_with_ids(*args) or first(*args)
68
+ end
69
+ end
70
+
71
+ def all(*args)
72
+ if args.empty?
73
+ # use the _all rule to recover all the stored instances of this node
74
+ _all
75
+ else
76
+ args = normalize_args(*args)
77
+ # handle the special case of a search by id
78
+ if args.first.is_a?(Hash) && args.first[:id]
79
+ [find_with_ids(args.first[:id])].flatten
80
+ else
81
+ find_with_indexer(*args)
82
+ end
83
+ end
84
+ end
85
+
86
+ def first(*args)
87
+ all(*args).first
88
+ end
89
+
90
+ protected
91
+ def find_with_ids(*args)
92
+ if ((args.first.is_a?(String) || args.first.is_a?(Integer)) && args.first.to_i > 0)
93
+ load(*args.map { |p| p.to_i })
94
+ end
95
+ end
96
+
97
+ def find_with_indexer(*args)
98
+ hits = _indexer.find(*args)
99
+ # We need to save this so that the Rack Neo4j::Rails:LuceneConnection::Closer can close it
100
+ Thread.current[:neo4j_lucene_connection] ||= []
101
+ Thread.current[:neo4j_lucene_connection] << hits
102
+ hits
103
+ end
104
+
105
+ def normalize_args(*args)
106
+ options = args.extract_options!
107
+
108
+ if options.present?
109
+ if options[:conditions]
110
+ args << options[:conditions]
111
+ else
112
+ args << options
113
+ end
114
+ end
115
+ args
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+
@@ -0,0 +1,35 @@
1
+ module Neo4j
2
+ module Rails
3
+ module Mapping
4
+ module Property
5
+ extend ActiveSupport::Concern
6
+
7
+ def []=(key, value)
8
+ attribute_will_change!(key.to_s) if self[key.to_s] != value
9
+ super
10
+ end
11
+
12
+ module ClassMethods
13
+ # Handle some additional options for the property
14
+ #
15
+ # Set a default - :default => "default"
16
+ # Prpoerty must be there - :null => false
17
+ # Property has a length limit - :limit => 128
18
+ def property(*args)
19
+ super
20
+ handle_property_options_for(args.first)
21
+ end
22
+
23
+ protected
24
+ def handle_property_options_for(property)
25
+ options = _decl_props[property.to_sym]
26
+
27
+ write_inheritable_attribute(:attribute_defaults, property => options[:default]) if options[:default]
28
+ validates(property, :non_nil => true) if options.has_key?(:null) && options[:null] == false
29
+ validates(property, :length => { :maximum => options[:limit] }) if options[:limit]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -2,6 +2,7 @@ module Neo4j
2
2
  module Rails
3
3
  class Model
4
4
  include Neo4j::NodeMixin
5
+
5
6
  include ActiveModel::Serializers::Xml
6
7
  include ActiveModel::Validations
7
8
  include ActiveModel::Dirty
@@ -9,14 +10,19 @@ module Neo4j
9
10
 
10
11
  extend ActiveModel::Naming
11
12
  extend ActiveModel::Callbacks
12
- extend Neo4j::Validations::ClassMethods
13
+
14
+ include Neo4j::Rails::Validations
15
+ extend Neo4j::Rails::Validations::ClassMethods
16
+
17
+ include Finders # ActiveRecord style find
18
+ include Mapping::Property # allows some additional options on the #property class method
19
+
13
20
  extend TxMethods
14
21
 
15
- define_model_callbacks :create, :save, :update, :destroy
16
-
17
- rule :all
22
+ class_inheritable_hash :attribute_defaults
23
+ self.attribute_defaults = {}
18
24
 
19
- UniquenessValidator = Neo4j::Validations::UniquenessValidator
25
+ define_model_callbacks :create, :save, :update, :destroy
20
26
 
21
27
  class RecordInvalidError < RuntimeError
22
28
  attr_reader :record
@@ -63,31 +69,15 @@ module Neo4j
63
69
  self.class.define_attribute_methods(self.class._decl_props.keys)
64
70
  # try again
65
71
  send(method_id, *args, &block)
72
+ elsif property?(method_id)
73
+ send(:[], method_id)
74
+ else
75
+ super
66
76
  end
67
77
  end
68
78
 
69
- # redefine this methods so that ActiveModel::Dirty will work
70
- def []=(key, new_value)
71
- key = key.to_s
72
- unless key[0] == ?_
73
- old_value = self.send(:[], key)
74
- attribute_will_change!(key) unless old_value == new_value
75
- end
76
- Neo4j::Rails::Transaction.running? ? super : Neo4j::Rails::Transaction.run { super }
77
- end
78
-
79
- def attribute_will_change!(attr)
80
- begin
81
- value = __send__(:[], attr)
82
- value = value.duplicable? ? value.clone : value
83
- rescue TypeError, NoMethodError
84
- end
85
- changed_attributes[attr] = value
86
- end
87
-
88
-
89
79
  def read_attribute_for_validation(key)
90
- respond_to?(key) ? send(key) : self[key]
80
+ send(key)
91
81
  end
92
82
 
93
83
  def attributes=(values)
@@ -159,9 +149,9 @@ module Neo4j
159
149
  end
160
150
  end
161
151
 
162
- def save
152
+ def save(*args)
163
153
  _run_save_callbacks do
164
- if create_or_update_node
154
+ if create_or_update_node(*args)
165
155
  true
166
156
  else
167
157
  # if not valid we should rollback the transaction so that the changes does not take place.
@@ -172,11 +162,13 @@ module Neo4j
172
162
  end
173
163
  end
174
164
 
175
- def create_or_update_node
176
- if valid?(:save)
165
+ def create_or_update_node(options = {})
166
+ options.reverse_merge!({ :validate => true })
167
+
168
+ if options[:validate] == false || valid?(:save)
177
169
  if new_record?
178
170
  _run_create_callbacks do
179
- if valid?(:create)
171
+ if options[:validate] == false || valid?(:create)
180
172
  node = Neo4j::Node.new(props)
181
173
  return false unless _java_node.save_nested(node)
182
174
  init_on_load(node)
@@ -188,7 +180,7 @@ module Neo4j
188
180
  end
189
181
  else
190
182
  _run_update_callbacks do
191
- if valid?(:update)
183
+ if options[:validate] == false || valid?(:update)
192
184
  clear_changes
193
185
  self.updated_at = DateTime.now if Neo4j::Config[:timestamps] && respond_to?(:updated_at)
194
186
  true
@@ -208,8 +200,8 @@ module Neo4j
208
200
  reload_from_database or set_deleted_properties and return self
209
201
  end
210
202
 
211
- def save!
212
- raise RecordInvalidError.new(self) unless save
203
+ def save!(*args)
204
+ raise RecordInvalidError.new(self) unless save(*args)
213
205
  end
214
206
 
215
207
  # Returns if the record is persisted, i.e. it’s not a new record and it was not destroyed
@@ -245,7 +237,8 @@ module Neo4j
245
237
  @_deleted
246
238
  end
247
239
 
248
- tx_methods :destroy, :create_or_update_node, :update_attributes, :update_attributes!
240
+ # TODO: []= shouldn't need to be in a transaction because it shouldn't update the DB. Need to refactor the @_java_node handling stuff if we want that to be the case though
241
+ tx_methods :destroy, :create_or_update_node, :[]=, :update_attributes, :update_attributes!, :update_nested_attributes
249
242
 
250
243
  # --------------------------------------
251
244
  # Class Methods
@@ -259,45 +252,10 @@ module Neo4j
259
252
  wrapped = self.orig_new
260
253
  value = Neo4j::Rails::Value.new(wrapped)
261
254
  wrapped.init_on_load(value)
262
- wrapped.attributes=args[0] if args[0].respond_to?(:each_pair)
255
+ wrapped.attributes = initial_attributes(*args)
263
256
  wrapped
264
257
  end
265
258
 
266
- # Behave like ActiveModel
267
- def all_with_args(*args)
268
- if args.empty?
269
- all_without_args
270
- else
271
- hits = find_without_checking_for_id(*args)
272
- # We need to save this so that the Rack Neo4j::Rails:LuceneConnection::Closer can close it
273
- Thread.current[:neo4j_lucene_connection] ||= []
274
- Thread.current[:neo4j_lucene_connection] << hits
275
- hits
276
- end
277
- end
278
-
279
- alias_method_chain :all, :args
280
-
281
- # Handle Model.find(params[:id])
282
- def find_with_checking_for_id(*args)
283
- if args.length == 1 && String === args[0] && args[0].to_i != 0
284
- load(*args)
285
- else
286
- all_with_args(*args).first
287
- end
288
- end
289
-
290
- alias_method_chain :find, :checking_for_id
291
-
292
- def load(*ids)
293
- result = ids.map { |id| Neo4j::Node.load(id) }
294
- if ids.length == 1
295
- result.first
296
- else
297
- result
298
- end
299
- end
300
-
301
259
  alias_method :_orig_create, :create
302
260
 
303
261
  def create(*args)
@@ -308,10 +266,10 @@ module Neo4j
308
266
  new(*args).tap { |o| o.save! }
309
267
  end
310
268
 
311
- tx_methods :create, :create!
312
-
313
269
  def transaction(&block)
314
- Neo4j::Rails::Transaction.run &block
270
+ Neo4j::Rails::Transaction.run do |tx|
271
+ block.call(tx)
272
+ end
315
273
  end
316
274
 
317
275
  def accepts_nested_attributes_for(*attr_names)
@@ -320,29 +278,33 @@ module Neo4j
320
278
  attr_names.each do |association_name|
321
279
  rel = self._decl_rels[association_name.to_sym]
322
280
  raise "No relationship declared with has_n or has_one with type #{association_name}" unless rel
323
- to_class = rel.to_class
324
- raise "Can't use accepts_nested_attributes_for(#{association_name}) since it has not defined which class it has a relationship to, use has_n(#{association_name}).to(MyOtherClass)" unless to_class
325
- type = rel.namespace_type
281
+ target_class = rel.target_class
282
+ raise "Can't use accepts_nested_attributes_for(#{association_name}) since it has not defined which class it has a relationship to, use has_n(#{association_name}).to(MyOtherClass)" unless target_class
283
+ type = rel.rel_type
326
284
  has_one = rel.has_one?
327
285
 
328
286
  send(:define_method, "#{association_name}_attributes=") do |attributes|
329
287
  if has_one
330
- update_nested_attributes(type, to_class, true, attributes, options)
288
+ update_nested_attributes(type, target_class, true, attributes, options)
331
289
  else
332
290
  if attributes.is_a?(Array)
333
291
  attributes.each do |attr|
334
- update_nested_attributes(type, to_class, false, attr, options)
292
+ update_nested_attributes(type, target_class, false, attr, options)
335
293
  end
336
294
  else
337
295
  attributes.each_value do |attr|
338
- update_nested_attributes(type, to_class, false, attr, options)
296
+ update_nested_attributes(type, target_class, false, attr, options)
339
297
  end
340
298
  end
341
299
  end
342
300
  end
343
- tx_methods("#{association_name}_attributes=")
344
301
  end
345
302
  end
303
+
304
+ protected
305
+ def initial_attributes(*args)
306
+ args.first.is_a?(Hash) ? args.first.reverse_merge(attribute_defaults) : attribute_defaults
307
+ end
346
308
  end
347
309
 
348
310
  private
@@ -52,14 +52,14 @@ module Neo4j
52
52
  def run
53
53
  begin
54
54
  new
55
- ret = yield
55
+ ret = yield self
56
56
  rescue
57
57
  fail
58
58
  raise
59
59
  ensure
60
60
  finish
61
61
  end
62
- ret
62
+ ret
63
63
  end
64
64
  end
65
65
  end
@@ -0,0 +1,11 @@
1
+ module Neo4j
2
+ module Rails
3
+ module Validations
4
+ class NonNilValidator < ActiveModel::EachValidator
5
+ def validate_each(record, attribute, value)
6
+ record.errors.add(attribute, :nil, options.merge(:value => value)) if value.nil?
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,29 +1,31 @@
1
1
  module Neo4j
2
- module Validations
3
- class UniquenessValidator < ActiveModel::EachValidator
4
- def initialize(options)
5
- super(options.reverse_merge(:case_sensitive => true))
6
- end
7
-
8
- def setup(klass)
9
- @attributes.each do |attribute|
10
- if klass.index_type_for(attribute) != :exact
11
- raise "Can't validate property #{attribute} on class #{klass} since there is no :exact lucene index on that property or the index declaration #{attribute} comes after the validation declaration in #{klass} (try to move it before the validation rules)"
12
- end
13
- end
14
- end
15
-
16
- def validate_each(record, attribute, value)
17
- if record.class.find("#{attribute}: #{value}")
18
- record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
19
- end
20
- end
21
- end
22
-
23
- module ClassMethods
24
- def validates_uniqueness_of(*attr_names)
25
- validates_with UniquenessValidator, _merge_attributes(attr_names)
26
- end
27
- end
28
- end
2
+ module Rails
3
+ module Validations
4
+ class UniquenessValidator < ActiveModel::EachValidator
5
+ def initialize(options)
6
+ super(options.reverse_merge(:case_sensitive => true))
7
+ end
8
+
9
+ def setup(klass)
10
+ @attributes.each do |attribute|
11
+ if klass.index_type_for(attribute) != :exact
12
+ raise "Can't validate property #{attribute} on class #{klass} since there is no :exact lucene index on that property or the index declaration #{attribute} comes after the validation declaration in #{klass} (try to move it before the validation rules)"
13
+ end
14
+ end
15
+ end
16
+
17
+ def validate_each(record, attribute, value)
18
+ if record.class.find("#{attribute}: \"#{value}\"")
19
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
20
+ end
21
+ end
22
+ end
23
+
24
+ module ClassMethods
25
+ def validates_uniqueness_of(*attr_names)
26
+ validates_with UniquenessValidator, _merge_attributes(attr_names)
27
+ end
28
+ end
29
+ end
30
+ end
29
31
  end
@@ -1,7 +1,7 @@
1
1
  module Neo4j::Rails
2
2
 
3
3
  class Value
4
- include Properties
4
+ include ValueProperties
5
5
  include org.neo4j.graphdb.Node
6
6
 
7
7
  def initialize(wrapper)
@@ -60,7 +60,7 @@ module Neo4j::Rails
60
60
 
61
61
  class Relationship
62
62
  include org.neo4j.graphdb.Relationship
63
- include Properties
63
+ include ValueProperties
64
64
 
65
65
  attr_reader :end_node, :start_node
66
66
 
@@ -1,7 +1,7 @@
1
+ # provide some properties before we have a real node or relationship
1
2
  module Neo4j
2
3
  module Rails
3
- # provide some properties before we have a real node or relationship
4
- module Properties # :nodoc:
4
+ module ValueProperties
5
5
  include Neo4j::Property
6
6
 
7
7
  # override Neo4j::Property#props
@@ -32,6 +32,10 @@ module Neo4j
32
32
  end
33
33
  end
34
34
 
35
+ def empty?
36
+ first == nil
37
+ end
38
+
35
39
  def iterator
36
40
  if @types
37
41
  @node.get_relationships(@types).iterator
@@ -40,13 +40,29 @@ module Neo4j
40
40
  end
41
41
  end
42
42
  end
43
+
44
+ class TimeConverter
45
+ class << self
46
+ # Converts the given DateTime (UTC) value to an Fixnum.
47
+ # Only utc times are supported !
48
+ def to_java(value)
49
+ return nil if value.nil?
50
+ value.utc.to_i
51
+ end
52
+
53
+ def to_ruby(value)
54
+ return nil if value.nil?
55
+ Time.at(value).utc
56
+ end
57
+ end
58
+ end
43
59
 
44
60
  # Converts the given value to a Java type by using the registered converters.
45
61
  # It just looks at the class of the given value and will convert it if there is a converter
46
62
  # registered (in Neo4j::Config) for this value.
47
63
  def self.convert(value)
48
64
  type = value.class
49
- converter = Neo4j::Config[:converters][type]
65
+ converter = Neo4j.converters[type]
50
66
  return value unless converter
51
67
  converter.to_java(value)
52
68
  end
@@ -56,7 +72,7 @@ module Neo4j
56
72
  def self.to_java(clazz, key, value)
57
73
  type = clazz._decl_props[key] && clazz._decl_props[key][:type]
58
74
  if type
59
- converter = Neo4j::Config[:converters][type]
75
+ converter = Neo4j.converters[type]
60
76
  converter ? converter.to_java(value) : value
61
77
  elsif clazz.superclass != Object
62
78
  to_java(clazz.superclass, key, value)
@@ -70,7 +86,7 @@ module Neo4j
70
86
  def self.to_ruby(clazz, key, value)
71
87
  type = clazz._decl_props[key] && clazz._decl_props[key][:type]
72
88
  if type
73
- converter = Neo4j::Config[:converters][type]
89
+ converter = Neo4j.converters[type]
74
90
  converter ? converter.to_ruby(value) : value
75
91
  elsif clazz.superclass != Object
76
92
  to_ruby(clazz.superclass, key, value)
@@ -78,7 +94,5 @@ module Neo4j
78
94
  value
79
95
  end
80
96
  end
81
-
82
- Neo4j::Config[:converters] = {Date => DateConverter, DateTime => DateTimeConverter}
83
97
  end
84
98
  end
@@ -1,3 +1,3 @@
1
1
  module Neo4j
2
- VERSION = "1.0.0.beta.19"
2
+ VERSION = "1.0.0.beta.20"
3
3
  end
metadata CHANGED
@@ -7,8 +7,8 @@ version: !ruby/object:Gem::Version
7
7
  - 0
8
8
  - 0
9
9
  - beta
10
- - 19
11
- version: 1.0.0.beta.19
10
+ - 20
11
+ version: 1.0.0.beta.20
12
12
  platform: ruby
13
13
  authors:
14
14
  - Andreas Ronge
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-11-05 00:00:00 +01:00
19
+ date: 2010-11-16 00:00:00 +01:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -61,7 +61,7 @@ files:
61
61
  - lib/neo4j.rb
62
62
  - lib/generators/neo4j.rb
63
63
  - lib/generators/neo4j/model/model_generator.rb
64
- - lib/generators/neo4j/model/templates/model.rb
64
+ - lib/generators/neo4j/model/templates/model.erb
65
65
  - lib/neo4j/event_handler.rb
66
66
  - lib/neo4j/model.rb
67
67
  - lib/neo4j/type_converters.rb
@@ -86,9 +86,12 @@ files:
86
86
  - lib/neo4j/rails/lucene_connection_closer.rb
87
87
  - lib/neo4j/rails/transaction.rb
88
88
  - lib/neo4j/rails/tx_methods.rb
89
- - lib/neo4j/rails/properties.rb
89
+ - lib/neo4j/rails/value_properties.rb
90
+ - lib/neo4j/rails/finders.rb
90
91
  - lib/neo4j/rails/value.rb
92
+ - lib/neo4j/rails/mapping/property.rb
91
93
  - lib/neo4j/rails/validations/uniqueness.rb
94
+ - lib/neo4j/rails/validations/non_nil.rb
92
95
  - lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar
93
96
  - lib/neo4j/jars/lucene-core-3.0.2.jar
94
97
  - lib/neo4j/jars/neo4j-index-1.2-1.2.M03.jar
@@ -104,9 +107,9 @@ files:
104
107
  - lib/neo4j/mapping/class_methods/root.rb
105
108
  - lib/neo4j/mapping/class_methods/init_rel.rb
106
109
  - lib/neo4j/mapping/class_methods/relationship.rb
107
- - lib/neo4j/index/index_registry.rb
108
110
  - lib/neo4j/index/indexer.rb
109
111
  - lib/neo4j/index/lucene_query.rb
112
+ - lib/neo4j/index/indexer_registry.rb
110
113
  - lib/neo4j/index/class_methods.rb
111
114
  - lib/neo4j/index/index.rb
112
115
  - README.rdoc