neo4j 3.0.0.rc.3 → 3.0.0.rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d2a5f927c44fc68a95721d7feca406c21a6f71d8
4
- data.tar.gz: e53647f9c20b8d48040bcab9bc28ce300287f5a8
3
+ metadata.gz: dd39f8916d1896f18ed9ce37f44137e383415b30
4
+ data.tar.gz: bbed25635d7e160853156a666b25589a35986b92
5
5
  SHA512:
6
- metadata.gz: 542621a0655dbae24993c9a36c5e39a37930c08fc023acb16bb907622bcd953f0a02662a4750174f851e6144e4fef62b9c4abc4326f3a954b93c4811e13402d7
7
- data.tar.gz: 0ebd9085b56e5a50a01f0d01994dff9f2219f1744f5e6e053717c12d787243c3d3e94a60dcd7b9623f24c08f3ba886acd454e8551518f22b083d19b387ef2738
6
+ metadata.gz: b3e192aadf5611e0bfd472eb7253d2863298536bd595a9934e7c08ff3495c6685aa699335a0492f5df57d8d63bd2d84a85b1dc586237c97bf4d60cc353e894b0
7
+ data.tar.gz: 9dfd058c39bcf5ba5d5476d103b5449070609bc2c998250df343d3334477ef2f034d962abfad2301f7589c61af45e3ae2434d49830897c872716351e152b4829
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ == 3.0.0.rc.4
2
+ * UUIDs are now automatically specified on models as neo IDs won't be reliable
3
+ in future versions of neo4j
4
+ * Migrations now supported (including built-in migrations to migrate UUIDs and
5
+ insert the _classname property which is used for performance)
6
+ * Association reflection
7
+ * Model.find supports ids/node objects as well as arrays of id/node objects
8
+ * rake tasks now get automatically included into rails app
9
+
10
+
1
11
  == 3.0.0.rc.3
2
12
  * thread safety improvements
3
13
  * scope and general refactoring
data/Gemfile CHANGED
@@ -3,10 +3,11 @@ source 'http://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  #gem 'neo4j-core', path: '../neo4j-core'
6
- #gem 'neo4j-core', :git => 'https://github.com/andreasronge/neo4j-core.git'
6
+ gem 'neo4j-core', git: 'https://github.com/neo4jrb/neo4j-core'
7
7
  #gem 'orm_adapter', :path => '../orm_adapter'
8
8
 
9
- #gem 'coveralls', require: false
9
+ gem 'coveralls', require: false
10
+
10
11
 
11
12
  group 'development' do
12
13
  gem 'pry'
@@ -16,9 +17,9 @@ group 'development' do
16
17
  end
17
18
 
18
19
  group 'test' do
19
- gem "codeclimate-test-reporter", require: nil
20
+ gem 'simplecov', require: false
21
+ gem 'simplecov-html', require: false
20
22
  gem "rspec", '~> 2.0'
21
- # gem 'rspec-its' instead of its in rspec 3
22
23
  gem "its"
23
24
  gem "test-unit"
24
25
  end
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Welcome to Neo4j.rb [![Build Status](https://secure.travis-ci.org/neo4jrb/neo4j.png?branch=master)](http://travis-ci.org/neo4jrb/neo4j) [![Test Coverage](https://codeclimate.com/github/neo4jrb/neo4j/badges/coverage.svg)](https://codeclimate.com/github/neo4jrb/neo4j) [![Code Climate](https://codeclimate.com/github/neo4jrb/neo4j.png)](https://codeclimate.com/github/andreasronge/neo4j)
1
+ # Welcome to Neo4j.rb [![Build Status](https://secure.travis-ci.org/neo4jrb/neo4j.png?branch=master)](http://travis-ci.org/neo4jrb/neo4j) [![Coverage Status](https://coveralls.io/repos/neo4jrb/neo4j/badge.png?branch=master)](https://coveralls.io/r/neo4jrb/neo4j?branch=master) [![Code Climate](https://codeclimate.com/github/neo4jrb/neo4j.png)](https://codeclimate.com/github/andreasronge/neo4j)
2
2
 
3
3
  Neo4j.rb is an Active Model compliant Ruby/JRuby wrapper for [the Neo4j graph database](http://www.neo4j.org/). It uses the [neo4j-core](https://github.com/neo4jrb/neo4j-core) and [active_attr](https://github.com/cgriego/active_attr) gems.
4
4
 
@@ -0,0 +1 @@
1
+ # This file is used as the source for adding _classname properties to nodes and relationships.
data/lib/neo4j.rb CHANGED
@@ -40,6 +40,7 @@ require 'neo4j/active_rel'
40
40
 
41
41
  require 'neo4j/active_node/query_methods'
42
42
  require 'neo4j/active_node/query/query_proxy_methods'
43
+ require 'neo4j/active_node/query/query_proxy_find_in_batches'
43
44
  require 'neo4j/active_node/labels'
44
45
  require 'neo4j/active_node/id_property'
45
46
  require 'neo4j/active_node/callbacks'
@@ -48,6 +49,7 @@ require 'neo4j/active_node/property'
48
49
  require 'neo4j/active_node/persistence'
49
50
  require 'neo4j/active_node/validations'
50
51
  require 'neo4j/active_node/rels'
52
+ require 'neo4j/active_node/reflection'
51
53
  require 'neo4j/active_node/has_n'
52
54
  require 'neo4j/active_node/has_n/association'
53
55
  require 'neo4j/active_node/query/query_proxy'
@@ -30,6 +30,7 @@ module Neo4j
30
30
  include Neo4j::ActiveNode::IdProperty
31
31
  include Neo4j::ActiveNode::SerializedProperties
32
32
  include Neo4j::ActiveNode::Property
33
+ include Neo4j::ActiveNode::Reflection
33
34
  include Neo4j::ActiveNode::Persistence
34
35
  include Neo4j::ActiveNode::Validations
35
36
  include Neo4j::ActiveNode::Callbacks
@@ -45,7 +46,7 @@ module Neo4j
45
46
 
46
47
  included do
47
48
  def self.inherited(other)
48
- inherit_id_property(other) if self.id_property_info
49
+ inherit_id_property(other) if self.has_id_property?
49
50
  inherited_indexes(other) if self.respond_to?(:indexed_properties)
50
51
  attributes.each_pair do |k,v|
51
52
  other.attributes[k] = v
@@ -66,6 +67,8 @@ module Neo4j
66
67
  end
67
68
 
68
69
  Neo4j::Session.on_session_available do |_|
70
+ id_property :uuid, auto: :uuid
71
+
69
72
  name = Neo4j::Config[:id_property]
70
73
  type = Neo4j::Config[:id_property_type]
71
74
  value = Neo4j::Config[:id_property_type_value]
@@ -4,8 +4,65 @@ module HasN
4
4
 
5
5
  class NonPersistedNodeError < StandardError; end
6
6
 
7
- module ClassMethods
7
+ # Clears out the association cache.
8
+ def clear_association_cache #:nodoc:
9
+ association_cache.clear if persisted?
10
+ end
11
+
12
+ # Returns the current association cache. It is in the format
13
+ # { :association_name => { :hash_of_cypher_string => [collection] }}
14
+ def association_cache
15
+ @association_cache ||= {}
16
+ end
17
+
18
+ # Returns the specified association instance if it responds to :loaded?, nil otherwise.
19
+ # @param [String] cypher_string the cypher, with params, used for lookup
20
+ # @param [Enumerable] association_obj the HasN::Association object used to perform this query
21
+ def association_instance_get(cypher_string, association_obj)
22
+ return if association_cache.nil? || association_cache.empty?
23
+ lookup_obj = cypher_hash(cypher_string)
24
+ reflection = association_reflection(association_obj)
25
+ return if reflection.nil?
26
+ association_cache[reflection.name] ? association_cache[reflection.name][lookup_obj] : nil
27
+ end
8
28
 
29
+ # @return [Hash] A hash of all queries in @association_cache created from the association owning this reflection
30
+ def association_instance_get_by_reflection(reflection_name)
31
+ association_cache[reflection_name]
32
+ end
33
+
34
+ # Caches an association result. Unlike ActiveRecord, which stores results in @association_cache using { :association_name => [collection_result] },
35
+ # ActiveNode stores it using { :association_name => { :hash_string_of_cypher => [collection_result] }}.
36
+ # This is necessary because an association name by itself does not take into account :where, :limit, :order, etc,... so it's prone to error.
37
+ # @param [Neo4j::ActiveNode::Query::QueryProxy] query_proxy The QueryProxy object that resulted in this result
38
+ # @param [Enumerable] collection_result The result of the query after calling :each
39
+ # @param [Neo4j::ActiveNode::HasN::Association] association_obj The association traversed to create the result
40
+ def association_instance_set(cypher_string, collection_result, association_obj)
41
+ return collection_result if Neo4j::Transaction.current
42
+ cache_key = cypher_hash(cypher_string)
43
+ reflection = association_reflection(association_obj)
44
+ return if reflection.nil?
45
+ if @association_cache[reflection.name]
46
+ @association_cache[reflection.name][cache_key] = collection_result
47
+ else
48
+ @association_cache[reflection.name] = { cache_key => collection_result }
49
+ end
50
+ collection_result
51
+ end
52
+
53
+ def association_reflection(association_obj)
54
+ self.class.reflect_on_association(association_obj.name)
55
+ end
56
+
57
+ # Uses the cypher generated by a QueryProxy object, complete with params, to generate a basic non-cryptographic hash
58
+ # for use in @association_cache.
59
+ # @param [String] the cypher used in the query
60
+ # @return [String] A basic hash of the query
61
+ def cypher_hash(cypher_string)
62
+ cypher_string.hash.abs
63
+ end
64
+
65
+ module ClassMethods
9
66
  def has_association?(name)
10
67
  !!associations[name.to_sym]
11
68
  end
@@ -25,11 +82,11 @@ module HasN
25
82
  name = name.to_sym
26
83
 
27
84
  association = Neo4j::ActiveNode::HasN::Association.new(:has_many, direction, name, options)
28
-
29
85
  @associations ||= {}
30
86
  @associations[name] = association
31
87
 
32
88
  target_class_name = association.target_class_name || 'nil'
89
+ create_reflection(:has_many, name, association)
33
90
 
34
91
  # TODO: Make assignment more efficient? (don't delete nodes when they are being assigned)
35
92
  module_eval(%Q{
@@ -42,13 +99,15 @@ module HasN
42
99
  start_object: self,
43
100
  node: node,
44
101
  rel: rel,
45
- context: '#{self.name}##{name}'
102
+ context: '#{self.name}##{name}',
103
+ caller: self
46
104
  })
105
+
47
106
  end
48
107
 
49
108
  def #{name}=(other_nodes)
50
109
  #{name}(nil, :r).query_as(:n).delete(:r).exec
51
-
110
+ clear_association_cache
52
111
  other_nodes.each do |node|
53
112
  #{name} << node
54
113
  end
@@ -71,7 +130,8 @@ module HasN
71
130
  query_proxy: query_proxy,
72
131
  node: node,
73
132
  rel: rel,
74
- context: context
133
+ context: context,
134
+ caller: query_proxy.caller
75
135
  })
76
136
  end}, __FILE__, __LINE__)
77
137
  end
@@ -80,15 +140,16 @@ module HasN
80
140
  name = name.to_sym
81
141
 
82
142
  association = Neo4j::ActiveNode::HasN::Association.new(:has_one, direction, name, options)
83
-
84
143
  @associations ||= {}
85
144
  @associations[name] = association
86
145
 
87
146
  target_class_name = association.target_class_name || 'nil'
147
+ create_reflection(:has_one, name, association)
88
148
 
89
149
  module_eval(%Q{
90
150
  def #{name}=(other_node)
91
151
  raise(Neo4j::ActiveNode::HasN::NonPersistedNodeError, 'Unable to create relationship with non-persisted nodes') unless self.persisted?
152
+ clear_association_cache
92
153
  #{name}_query_proxy(rel: :r).query_as(:n).delete(:r).exec
93
154
  #{name}_query_proxy << other_node
94
155
  end
@@ -103,7 +164,14 @@ module HasN
103
164
 
104
165
  def #{name}(node = nil, rel = nil)
105
166
  return nil unless self.persisted?
106
- #{name}_query_proxy(node: node, rel: rel, context: '#{self.name}##{name}').first
167
+ result = #{name}_query_proxy(node: node, rel: rel, context: '#{self.name}##{name}')
168
+ association = self.class.reflect_on_association(__method__)
169
+ query_return = association_instance_get(result.to_cypher_with_params, association)
170
+ if query_return.nil?
171
+ association_instance_set(result.to_cypher_with_params, result.first, association)
172
+ else
173
+ query_return
174
+ end
107
175
  end}, __FILE__, __LINE__)
108
176
 
109
177
  instance_eval(%Q{
@@ -118,8 +186,6 @@ module HasN
118
186
  #{name}_query_proxy(query_proxy: query_proxy, node: node, rel: rel, context: context)
119
187
  end}, __FILE__, __LINE__)
120
188
  end
121
-
122
-
123
189
  end
124
190
  end
125
191
 
@@ -12,6 +12,7 @@ module Neo4j
12
12
  @name = name
13
13
  @direction = direction.to_sym
14
14
  @target_class_name_from_name = name.to_s.classify
15
+
15
16
  set_vars_from_options(options)
16
17
  end
17
18
 
@@ -64,6 +65,10 @@ module Neo4j
64
65
  end
65
66
  end
66
67
 
68
+ def relationship_class
69
+ @relationship_class
70
+ end
71
+
67
72
  private
68
73
 
69
74
  def get_direction(relationship_cypher, create)
@@ -94,10 +99,6 @@ module Neo4j
94
99
  target_class.associations[@origin].relationship_type
95
100
  end
96
101
 
97
- def relationship_class
98
- @relationship_class
99
- end
100
-
101
102
  private
102
103
 
103
104
  def set_vars_from_options(options)
@@ -51,18 +51,24 @@ module Neo4j::ActiveNode
51
51
  end
52
52
 
53
53
  def define_property_method(clazz, name)
54
+ clear_methods(clazz, name)
55
+
54
56
  clazz.module_eval(%Q{
55
57
  def id
56
58
  persisted? ? #{name} : nil
57
59
  end
58
60
 
59
- property :#{name}
60
61
  validates_uniqueness_of :#{name}
62
+
63
+ property :#{name}
61
64
  }, __FILE__, __LINE__)
65
+
62
66
  end
63
67
 
64
68
 
65
69
  def define_uuid_method(clazz, name)
70
+ clear_methods(clazz, name)
71
+
66
72
  clazz.module_eval(%Q{
67
73
  default_property :#{name} do
68
74
  ::SecureRandom.uuid
@@ -77,6 +83,8 @@ module Neo4j::ActiveNode
77
83
  end
78
84
 
79
85
  def define_custom_method(clazz, name, on)
86
+ clear_methods(clazz, name)
87
+
80
88
  clazz.module_eval(%Q{
81
89
  default_property :#{name} do |instance|
82
90
  raise "Specifying custom id_property #{name} on none existing method #{on}" unless instance.respond_to?(:#{on})
@@ -91,17 +99,44 @@ module Neo4j::ActiveNode
91
99
  }, __FILE__, __LINE__)
92
100
  end
93
101
 
102
+ def clear_methods(clazz, name)
103
+ if clazz.method_defined?(name)
104
+ clazz.module_eval(%Q{
105
+ undef_method :#{name}
106
+ }, __FILE__, __LINE__)
107
+ end
108
+
109
+ if clazz.attribute_names.include?(name.to_s)
110
+ clazz.module_eval(%Q{
111
+ undef_property :#{name}
112
+ }, __FILE__, __LINE__)
113
+ end
114
+ end
115
+
94
116
  extend self
95
117
  end
96
118
 
97
119
 
98
120
  module ClassMethods
99
121
 
100
- def find_by_id(key)
101
- Neo4j::Node.load(key.to_i)
122
+ def find_by_id(id)
123
+ self.where(id_property_name => id).first
124
+ end
125
+
126
+ def find_by_ids(ids)
127
+ self.where(id_property_name => ids).to_a
102
128
  end
103
129
 
104
130
  def id_property(name, conf = {})
131
+ begin
132
+ if has_id_property?
133
+ unless mapped_label.uniqueness_constraints[:property_keys].include?([name])
134
+ drop_constraint(id_property_name, type: :unique)
135
+ end
136
+ end
137
+ rescue Neo4j::Server::CypherResponse::ResponseError
138
+ end
139
+
105
140
  @id_property_info = {name: name, type: conf}
106
141
  TypeMethods.define_id_methods(self, name, conf)
107
142
  constraint name, type: :unique
@@ -111,11 +146,21 @@ module Neo4j::ActiveNode
111
146
  end
112
147
  end
113
148
 
149
+ def has_id_property?
150
+ id_property_info && !id_property_info.empty?
151
+ end
152
+
114
153
  def id_property_info
115
- @id_property_info ||= false
154
+ @id_property_info ||= {}
116
155
  end
117
156
 
157
+ def id_property_name
158
+ id_property_info[:name]
159
+ end
160
+
161
+ alias_method :primary_key, :id_property_name
162
+
118
163
  end
119
164
  end
120
165
 
121
- end
166
+ end
@@ -8,6 +8,7 @@ module Neo4j::ActiveNode::Initialize
8
8
  def init_on_load(persisted_node, properties)
9
9
  @_association_attributes = self.class.extract_association_attributes!(properties)
10
10
  @_persisted_obj = persisted_node
11
+ @association_cache = {}
11
12
  changed_attributes && changed_attributes.clear
12
13
  @attributes = attributes.merge(properties.stringify_keys)
13
14
  self.default_properties=properties
@@ -71,8 +71,13 @@ module Neo4j
71
71
  # Returns the object with the specified neo4j id.
72
72
  # @param [String,Fixnum] id of node to find
73
73
  def find(id)
74
- raise "Unknown argument #{id.class} in find method (expected String or Fixnum)" if not [String, Fixnum].include?(id.class)
75
- find_by_id(id)
74
+ map_id = Proc.new {|object| object.respond_to?(:id) ? object.send(:id) : object }
75
+
76
+ if id.is_a?(Array)
77
+ find_by_ids(id.map {|o| map_id.call(o) })
78
+ else
79
+ find_by_id(map_id.call(id))
80
+ end
76
81
  end
77
82
 
78
83
  # Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself.
@@ -129,12 +134,19 @@ module Neo4j
129
134
  # Person.constraint :name, type: :unique
130
135
  #
131
136
  def constraint(property, constraints, session = Neo4j::Session.current)
132
- Neo4j::Session.on_session_available do |_|
137
+ Neo4j::Session.on_session_available do |session|
133
138
  label = Neo4j::Label.create(mapped_label_name)
134
139
  label.create_constraint(property, constraints, session)
135
140
  end
136
141
  end
137
142
 
143
+ def drop_constraint(property, constraint, session = Neo4j::Session.current)
144
+ Neo4j::Session.on_session_available do |session|
145
+ label = Neo4j::Label.create(mapped_label_name)
146
+ label.drop_constraint(property, constraint, session)
147
+ end
148
+ end
149
+
138
150
  def index?(index_def)
139
151
  mapped_label.indexes[:property_keys].include?([index_def])
140
152
  end
@@ -73,7 +73,7 @@ module Neo4j
73
73
 
74
74
  def extract_id!(conditions)
75
75
  if id = conditions.delete(:id)
76
- conditions[:neo_id] = id.to_i
76
+ conditions[klass.id_property_name.to_sym] = id
77
77
  end
78
78
  end
79
79