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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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