neo4jrb_spatial 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb742b0cbfe6febf9d05b442ee46e0b887827370
4
- data.tar.gz: e8f00d7f1d1bb32eef67be847d5e15a98a1117e7
3
+ metadata.gz: 7cb429531699747b256d540fb3af0423759101ac
4
+ data.tar.gz: ba22468be74998ba6b720238ec6c173a20dbc67d
5
5
  SHA512:
6
- metadata.gz: 48c8cee0ef882d1e12b880bd5488b3457049ac7af25373bcf936dc0d3cbf1be29ae72388b35a1c31686485f62c3cad27d77f42f24c5c7b751af25ce83a922d1a
7
- data.tar.gz: f3490009b572971ea316ad313de889b6589a2299b0b4d8a7c5557bba66784fbf672454f5a8933719e92a322f86b7b77b7717b1eb3de2be22fd3aeaaf7064363d
6
+ metadata.gz: 2e7598b05399b467c28b2be8d27691b45da845b3f908a93324d4da38d670abfe73f806b32e0d0439db120a724c2263b057afb6cd359e740b82c6028141797481
7
+ data.tar.gz: 448cdc3c8283f08ff3d8bbb4bdcfa7f9f36c125ac497ed1d3ae85ebaaace5c3c2aff9ba5415af421605715d3cc5c01231cc7ae9fd45acabf879556a6de7fb78e
@@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
5
5
 
6
6
  ## [Unreleased][unreleased]
7
7
 
8
+ ## [2.0.0] - 2017-06-23
9
+
10
+ ### Changed
11
+
12
+ - Support for Neo4j 3.x, version 8.0 of the `neo4j` gem, and version 7.x of the `neo4j-core` gem (see #18 / thanks @TyGuy)
13
+ - NOTE: This version may be incompatible with version of the `neo4j` gem below 8.x and versions of the `neo4j-core` gem below 7.x
14
+
8
15
  ## [1.2.0] - 2016-09-26
9
16
 
10
17
  ### Fixed
data/README.md CHANGED
@@ -8,31 +8,32 @@ Provides support for Neo4j Spatial to Neo4j.rb 5+.
8
8
 
9
9
  ## Introduction
10
10
 
11
- It is more or less a Neo4j.rb-flavored implementation of [Max De Marzi](https://github.com/maxdemarzi)'s
11
+ It was originally more or less a Neo4j.rb-flavored implementation of [Max De Marzi](https://github.com/maxdemarzi)'s
12
12
  [code](https://github.com/maxdemarzi/neography/blob/46be2bb3c66aea14e707b1e6f82937e65f686ccc/lib/neography/rest/spatial.rb) from
13
13
  [Neography](https://github.com/maxdemarzi/neography).
14
14
 
15
+ Now, it supports spatial queries via [Neo4j Spatial Procedures](http://neo4j-contrib.github.io/spatial/#spatial-procedures).
16
+
15
17
  For support, open an issue or say hello through [Gitter](https://gitter.im/neo4jrb/neo4j).
16
18
 
17
19
  ## What it provides
18
20
 
19
- * Basic index and layer management
20
- * Basic node-to-index management
21
+ * Basic layer management
22
+ * Basic node-to-layer management
21
23
  * Hooks for Neo4j::ActiveNode::Query::QueryProxy models if you are using them
22
24
 
23
- It is powered by an implementation of [Neography's](https://github.com/maxdemarzi/neography) [spatial module](https://github.com/maxdemarzi/neography/blob/46be2bb3c66aea14e707b1e6f82937e65f686ccc/lib/neography/rest/spatial.rb).
24
25
  Clearly, a huge debt is owed to [Max De Marzi](https://github.com/maxdemarzi) for doing all the hard work.
25
26
 
26
27
  ## Requirements
27
28
 
28
- * Neo4j-core 5.0.1+
29
- * Neo4j Server 2.2.2+ (earlier versions will likely work but are not tested)
29
+ * Neo4j-core 7.0+
30
+ * Neo4j Server 3.0+ (earlier versions WILL NOT WORK)
30
31
  * Ruby MRI 2.2.2+
31
32
  * Compatible version of [Neo4j Spatial](https://github.com/neo4j-contrib/spatial)
32
33
 
33
34
  Optionally:
34
35
 
35
- * v5.0.1+ of the [Neo4j gem](https://github.com/neo4jrb/neo4j)
36
+ * v8.0.6+ of the [Neo4j gem](https://github.com/neo4jrb/neo4j)
36
37
 
37
38
  # Usage
38
39
 
@@ -42,6 +43,8 @@ Optionally:
42
43
  gem 'neo4jrb_spatial', '~> 1.0.0'
43
44
  ```
44
45
 
46
+ You can also install neo4j_spatial via a rake task, assuming you already have neo4j installed (see [Rake Tasks](## Rake tasks:) below).
47
+
45
48
  ## Require it
46
49
 
47
50
  ```
@@ -55,76 +58,105 @@ include Neo4j::ActiveNode::Spatial
55
58
  ## Use it with Neo4j-core
56
59
 
57
60
  ```ruby
58
- # Create an index
59
- Neo4j::Session.current.create_spatial_index('restaurants')
61
+ # Create a session object
62
+ require 'neo4j/core/cypher_session/adaptors/http'
63
+
64
+ neo4j_adaptor = Neo4j::Core::CypherSession::Adaptors::HTTP.new('http://localhost:7474')
65
+ session = Neo4j::Core::CypherSession.new(neo4j_adaptor)
66
+
67
+ # Create a spatial layer
68
+ session.add_layer('restaurants')
60
69
 
61
70
  # Create a node
62
- node = Neo4j::Node.create({:name => "Indie Cafe", :lat => 41.990326, :lon => -87.672907 }, :Restaurant)
71
+ properties = {name: "Indie Cafe", lat: 41.990326, lon: -87.672907}
72
+ node_query = Neo4j::Core::Query.new(session: session).create(n: {Restaurant: properties}).return(:n)
73
+ node = session.query(node_query).first.n
74
+
75
+ # Add a node to the layer
76
+ session.add_node_to_layer('restaurants', node)
63
77
 
64
- # Add a node to the index
65
- Neo4j::Session.current.add_node_to_spatial_index('restaurants', node)
78
+ # Look for nodes within distance:
79
+ session.within_distance('restaurants', {lat: 41.99022, lon: -87.6720}, 30).map do |node|
80
+ node.props[:name] # node is an instance of Neo4j::Core::Node
81
+ end # => ['Indie Cafe']
66
82
 
67
- # Query around the index
68
- Neo4j::Session.current.query.start('n = node:restaurants({location})').params(location: 'withinDistance:[41.99,-87.67,10.0]').pluck(:n)
69
- # => CypherNode 90126 (70333884677220)
83
+ # Spatial queries also supported: #bbox, #intersects, #closest.
84
+ # See spec/neo4jrb_spatial_spec.rb for examples.
70
85
  ```
71
86
 
72
87
  ## Use it with the Neo4j gem
73
88
 
74
- Neo4j.rb does not support legacy indexes, so adding nodes to spatial indexes needs to happen separately from node creation. This is complicated by the fact that Neo4j.rb creates all nodes in transactions, so `after_create` callbacks won't work; instead, add your node to the index once you've confirmed it has been created.
89
+ Neo4j.rb does not support legacy indexes, so adding nodes to spatial indexes needs to happen separately from node creation. This is complicated by the fact that Neo4j.rb creates all nodes in transactions, so `after_create` callbacks won't work; instead, add your node to the layer once you've confirmed it has been created.
75
90
 
76
- Start by adding `lat` and `lon` properties to your model. You can also add a `spatial_index` to save yourself some time later.
91
+ Start by adding `lat` and `lon` properties to your model. You can also add a `spatial_layer` to save yourself some time later.
77
92
 
78
- ```
93
+ ```ruby
79
94
  class Restaurant
80
95
  include Neo4j::ActiveNode
81
96
  include Neo4j::ActiveNode::Spatial
82
97
 
83
98
  # This is optional but might make things easier for you later
84
- spatial_index 'restaurants'
99
+ spatial_layer 'restaurants'
85
100
 
86
101
  property :name
87
102
  property :lat
88
103
  property :lon
89
104
  end
90
105
 
106
+ # Create the layer
107
+ Restaurant.create_layer
108
+
91
109
  # Create it
92
110
  pizza_hut = Restaurant.create(name: 'Pizza Hut', lat: 60.1, lon: 15.1)
93
111
 
94
112
  # When called without an argument, it will use the value set through `spatial_index` in the model
95
- pizza_hut.add_to_spatial_index
113
+ pizza_hut.add_to_spatial_layer
96
114
 
97
115
  # Alternatively, to add it to a different index, just give it that name
98
- pizza_hut.add_to_spatial_index('fake_pizza_places')
116
+ pizza_hut.add_to_spatial_layer('fake_pizza_places')
99
117
  ```
100
118
 
101
- ### Manual index addition
119
+ ### Spatial queries
102
120
 
103
- All of the Neo4j-core spatial methods accept ActiveNode-including nodes, so you can use them as arguments for all defined methods as you would Neo4j::Server::CypherNode instances.
121
+ Spatial queries used with ActiveNode classes are scopes, and as such resolve to QueryProxy objects, and are chainable. For example, if you had an `employees` association defined in your model:
104
122
 
105
123
  ```ruby
106
- Neo4j::Session.current.add_node_to_spatial_index('fake_pizza_places', pizza_hut)
124
+ # Find all restaurants within the specified distance, then find their employees who are age 30
125
+ Restauarant.within_distance({lat: 60.08, lon: 15.09}, 10).employees.where(age: 30)
107
126
  ```
108
127
 
109
- ### Spatial queries
128
+ If you did not define `spatial_layer` on your model, or want to query against something other than the model's default, you can feed a third argument: the layer name to use for the query.
110
129
 
111
- No helpers are provided to query against the REST API -- you'll need to use the ones provided for Neo4j-core; however, a class method is provided to make Cypher queries easier: `spatial_match`.
130
+ #### `#bbox`
112
131
 
132
+ ```ruby
133
+ # find all restaurants within the bounding box created by the given points:
134
+ min = { lat: 59.9, lon: 14.9 }
135
+ max = { lat: 60.2, lon: 15.3 }
136
+ Restaurant.bbox(min, max)
113
137
  ```
114
- # Use the index defined on the model as demonstrated above
115
- Restaurant.all.spatial_match(:r, params_string)
116
- # Generates:
117
- # => "START r = node:restaurants({params_string})"
138
+
139
+ #### `#within_distance`
140
+
141
+ ```ruby
142
+ # find all restaurants within 10km of the given point:
143
+ Restauarant.within_distance({lat: 60.08, lon: 15.09}, 10)
118
144
  ```
119
145
 
120
- It then drops you back into a QueryProxy in the context of the class. If you had an `employees` association defined in your model:
146
+ #### `intersects`
121
147
 
122
- ```
123
- # Find all restaurants within the specified distance, then find their employees who are age 30
124
- Restauarant.all.spatial_match(:r, 'withinDistance:[41.99,-87.67,10.0]').employees.where(age: 30)
125
- ```
148
+ ```ruby
149
+ # find all restaurants that intersect the given geometry:
150
+ geom = 'POLYGON ((15.3 60.1, 15.3 58.9, 14.8 58.9, 14.8 60.1, 15.3 60.1))'
151
+ Restauarant.intersects(geom)
152
+ ```
153
+
154
+ ## Rake tasks:
126
155
 
127
- If you did no define `spatial_index` on your model or what to query against something other than the model's default, you can feed a third argument: the index to use for the query.
156
+ #### `bundle exec rake neo4j_spatial:install`
157
+
158
+ usage: `NEO4J_VERSION='3.0.4' bundle exec rake neo4j_spatial:install[<env>]`
159
+ If no `env` argument is provided, this defaults to 'development'
128
160
 
129
161
  ## Additional Resources
130
162
 
@@ -135,4 +167,14 @@ mostly works for an idea of the basics, just replace Neography-specific commands
135
167
 
136
168
  ## Contributions
137
169
 
138
- Pull requests and maintanence help would be swell. In addition to being fully tested, please ensure rubocop passes by running `rubocop` from the CLI.
170
+ Pull requests and maintanence help would be swell. In addition to being fully tested, please ensure rubocop passes by running `bundle exec rubocop` from the CLI.
171
+
172
+ ### Running Tests:
173
+
174
+ Make sure your neo4j server is running (and catch it like a fridge!):
175
+ `bundle exec rake neo4j:start`
176
+
177
+ run the test suite:
178
+ `bundle exec rake spec` or `bundle exec rspec spec`
179
+
180
+ NOTE that if your NEO4J_URL is not the default, you will have to prefix while running migrate: `NEO4J_URL='http://localhost:7123' bundle exec rake spec`
@@ -1,42 +1,72 @@
1
+ require 'active_support/concern'
2
+
1
3
  module Neo4j
2
4
  module ActiveNode
3
5
  module Spatial
4
- def self.included(other)
5
- other.extend(ClassMethods)
6
- end
6
+ extend ActiveSupport::Concern
7
7
 
8
- def add_to_spatial_index(index_name = nil)
9
- index = index_name || self.class.spatial_index_name
10
- fail 'index name not found' unless index
11
- Neo4j::Session.current.add_node_to_spatial_index(index, self)
12
- end
8
+ SpatialLayer = Struct.new(:name, :type, :config)
13
9
 
14
- module ClassMethods
15
- attr_reader :spatial_index_name
16
- def spatial_index(index_name = nil)
17
- return spatial_index_name unless index_name
18
- # create_index_callback(index_name)
19
- @spatial_index_name = index_name
10
+ included do
11
+ def add_to_spatial_layer(layer_name = nil)
12
+ layer = layer_name || self.class.spatial_layer.name
13
+ fail 'layer name not found' unless layer
14
+
15
+ Neo4j::ActiveBase.current_session.add_node_to_layer(layer, self)
20
16
  end
21
17
 
22
- # This will not work for now. Neo4j Spatial's REST API doesn't seem to work within transactions.
23
- # def create_index_callback(index_name)
24
- # after_create(proc { |node| Neo4j::Session.current.add_node_to_spatial_index(index_name, node) })
25
- # end
18
+ class << self
19
+ attr_reader :spatial_layer
26
20
 
27
- # private :create_index_callback
28
- end
29
- end
21
+ def spatial_layer(layer_name = nil, options = {})
22
+ @spatial_layer ||= SpatialLayer.new(layer_name, options.fetch(:type, 'SimplePoint'), options.fetch(:config, 'lon:lat'))
23
+ end
24
+
25
+ def create_layer
26
+ fail 'layer not found' unless spatial_layer.name
27
+
28
+ lon_name, lat_name = spatial_layer.config.split(':')
29
+
30
+ Neo4j::ActiveBase.current_session.add_layer(spatial_layer.name, spatial_layer.type, lat_name, lon_name)
31
+ end
32
+
33
+ def remove_layer
34
+ fail 'layer not found' unless spatial_layer.name
35
+
36
+ Neo4j::ActiveBase.current_session.remove_layer(spatial_layer.name)
37
+ end
38
+ end
39
+
40
+ scope :within_distance, ->(coordinate, distance, layer_name = nil) do
41
+ layer = model.spatial_layer.name || layer_name
42
+
43
+ Neo4j::ActiveBase.current_session
44
+ .within_distance(layer, coordinate, distance, execute: false)
45
+ .proxy_as(model, :node)
46
+ end
47
+
48
+ scope :bbox, ->(min, max, layer_name = nil) do
49
+ layer = model.spatial_layer.name || layer_name
50
+
51
+ Neo4j::ActiveBase.current_session
52
+ .bbox(layer, min, max, execute: false)
53
+ .proxy_as(model, :node)
54
+ end
55
+
56
+ scope :closest, ->(coordinate, distance = 100, layer_name = nil) do
57
+ layer = model.spatial_layer.name || layer_name
58
+
59
+ Neo4j::ActiveBase.current_session
60
+ .closest(layer, coordinate, distance, execute: false)
61
+ .proxy_as(model, :node)
62
+ end
63
+
64
+ scope :intersects, ->(geometry, layer_name = nil) do
65
+ layer = model.spatial_layer.name || layer_name
30
66
 
31
- module Query
32
- class QueryProxy
33
- def spatial_match(var, params_string, spatial_index = nil)
34
- index = model.spatial_index_name || spatial_index
35
- fail 'Cannot query without index. Set index in model or as third argument.' unless index
36
- Neo4j::Session.current.query
37
- .start("#{var} = node:#{index}({spatial_params})")
38
- .proxy_as(model, var)
39
- .params(spatial_params: params_string)
67
+ Neo4j::ActiveBase.current_session
68
+ .intersects(layer, geometry, execute: false)
69
+ .proxy_as(model, :node)
40
70
  end
41
71
  end
42
72
  end
@@ -1,143 +1,161 @@
1
1
  module Neo4j
2
- module Server
2
+ module Core
3
3
  module Spatial
4
4
  def spatial?
5
- Neo4j::Session.current.connection.get('/db/data/ext/SpatialPlugin').status == 200
5
+ spatial_procedures
6
+ true
7
+ rescue Neo4j::Core::CypherSession::CypherError
8
+ false
6
9
  end
7
10
 
8
- def spatial_plugin
9
- parse_response! Neo4j::Session.current.connection.get('/db/data/ext/SpatialPlugin').body
11
+ def spatial_procedures
12
+ query('CALL spatial.procedures() YIELD name').map(&:name)
10
13
  end
11
14
 
12
- def add_point_layer(layer, lat = nil, lon = nil)
15
+ def add_layer(name, type = nil, lat = nil, lon = nil)
16
+ # supported names for type are: 'SimplePoint', 'WKT', 'WKB'
17
+ type ||= 'SimplePoint'
18
+
13
19
  options = {
14
- layer: layer,
15
- lat: lat || 'lat',
16
- lon: lon || 'lon'
20
+ name: name,
21
+ type: type || 'point',
22
+ encoderConfig: "#{lon || 'lon'}:#{lat || 'lat'}"
17
23
  }
24
+ wrap_spatial_procedure('addLayer', options)
25
+ end
18
26
 
19
- spatial_post('/ext/SpatialPlugin/graphdb/addSimplePointLayer', options)
27
+ def remove_layer(name)
28
+ options = {name: name}
29
+ wrap_spatial_procedure('removeLayer', options, node: false)
20
30
  end
21
31
 
22
- def add_editable_layer(layer, format = 'WKT', node_property_name = 'wkt')
32
+ def add_point_layer(layer)
33
+ options = {layer: layer}
34
+
35
+ wrap_spatial_procedure('addPointLayer', options)
36
+ end
37
+
38
+ def add_wkt_layer(layer, node_property_name = 'wkt')
23
39
  options = {
24
40
  layer: layer,
25
- format: format,
26
- nodePropertyName: node_property_name
41
+ node_property_name: node_property_name
27
42
  }
28
43
 
29
- spatial_post('/ext/SpatialPlugin/graphdb/addEditableLayer', options)
44
+ wrap_spatial_procedure('addWKTLayer', options)
30
45
  end
31
46
 
32
- def get_layer(layer)
33
- options = {
34
- layer: layer
35
- }
36
- spatial_post('/ext/SpatialPlugin/graphdb/getLayer', options)
47
+ def get_layer(layer, execute: true)
48
+ options = {layer: layer}
49
+ wrap_spatial_procedure('layer', options, execute: execute)
37
50
  end
38
51
 
39
- def add_geometry_to_layer(layer, geometry)
52
+ def add_wkt(layer, geometry, execute: true)
40
53
  options = {
41
54
  layer: layer,
42
55
  geometry: geometry
43
56
  }
44
- spatial_post('/ext/SpatialPlugin/graphdb/addGeometryWKTToLayer', options)
57
+ wrap_spatial_procedure('addWKT', options, execute: execute)
45
58
  end
46
59
 
47
- def edit_geometry_from_layer(layer, geometry, node)
60
+ def update_from_wkt(layer, geometry, node, execute: true)
48
61
  options = {
49
62
  layer: layer,
50
63
  geometry: geometry,
51
64
  geometryNodeId: get_id(node)
52
65
  }
53
- spatial_post('/ext/SpatialPlugin/graphdb/updateGeometryFromWKT', options)
66
+ wrap_spatial_procedure('updateFromWKT', options, execute: execute)
54
67
  end
55
68
 
56
- def add_node_to_layer(layer, node)
57
- options = {
58
- layer: layer,
59
- node: "#{resource_url}node/#{node.neo_id}"
60
- }
61
- spatial_post('/ext/SpatialPlugin/graphdb/addNodeToLayer', options)
69
+ # Hmmm this one has trouble, because we actually need to MATCH the node itself...
70
+ # Wish this could be cleaner but for now it works...
71
+ def add_node_to_layer(layer, node, execute: true)
72
+ query_ = Query.new(session: self)
73
+ procedure = query_.match(:n)
74
+ .where('id(n) = {node_id}')
75
+ .with(:n).call('spatial.addNode({layer}, n) YIELD node')
76
+ .return('node')
77
+ .params(layer: layer, node_id: node.neo_id)
78
+
79
+ procedure = execute_and_format_response(procedure) if execute
80
+ procedure
62
81
  end
63
82
 
64
- def find_geometries_in_bbox(layer, minx, maxx, miny, maxy)
65
- options = {
66
- layer: layer,
67
- minx: minx,
68
- maxx: maxx,
69
- miny: miny,
70
- maxy: maxy
71
- }
72
- spatial_post('/ext/SpatialPlugin/graphdb/findGeometriesInBBox', options)
83
+ def bbox(layer, min, max, execute: true)
84
+ options = {layer: layer, min: min, max: max}
85
+
86
+ wrap_spatial_procedure('bbox', options, execute: execute)
73
87
  end
88
+ alias_method :find_geometries_in_bbox, :bbox
74
89
 
75
- def find_geometries_within_distance(layer, pointx, pointy, distance)
90
+ def within_distance(layer, coordinate, distance, execute: true)
76
91
  options = {
77
92
  layer: layer,
78
- pointX: pointx,
79
- pointY: pointy,
93
+ coordinate: coordinate,
80
94
  distanceInKm: distance
81
95
  }
82
- spatial_post('/ext/SpatialPlugin/graphdb/findGeometriesWithinDistance', options)
96
+
97
+ wrap_spatial_procedure('withinDistance', options, execute: execute)
83
98
  end
99
+ alias_method :find_geometries_within_distance, :within_distance
84
100
 
85
- def create_spatial_index(name, type = nil, lat = nil, lon = nil)
86
- options = {
87
- name: name,
88
- config: {
89
- provider: 'spatial',
90
- geometry_type: type || 'point',
91
- lat: lat || 'lat',
92
- lon: lon || 'lon'
93
- }
94
- }
95
- spatial_post('/index/node', options)
101
+ def intersects(layer, geometry, execute: true)
102
+ options = {layer: layer, geometry: geometry}
103
+
104
+ wrap_spatial_procedure('intersects', options, execute: execute)
96
105
  end
97
106
 
98
- def add_node_to_spatial_index(index, node)
107
+ # TODO: figure out what closest is supposed to do...
108
+ def closest(layer, coordinate, distance = 100, execute: true)
99
109
  options = {
100
- uri: "/#{get_id(node)}",
101
- key: 'k',
102
- value: 'v'
110
+ layer: layer,
111
+ coordinate: coordinate,
112
+ distanceInKm: distance
103
113
  }
104
- spatial_post("/index/node/#{index}", options)
114
+
115
+ wrap_spatial_procedure('closest', options, execute: execute)
105
116
  end
106
117
 
107
- private
118
+ def import_shapefile_to_layer(layer, file_uri, execute: true)
119
+ options = {layer: layer, file_uri: file_uri}
120
+ execution_args = {execute: execute, node: false}
108
121
 
109
- def spatial_post(path, options)
110
- parse_response! Neo4j::Session.current.connection.post("/db/data/#{path}", options).body
122
+ wrap_spatial_procedure('importShapefileToLayer', options, execution_args)
111
123
  end
112
124
 
113
- def parse_response!(response)
114
- request_error!(response[:exception], response[:message], response[:stack_trace]) if response.is_a?(Hash) && response[:exception]
115
- response
125
+ protected
126
+
127
+ def spatial_procedure(procedure_name, procedure_args, with_node = true)
128
+ call_params = procedure_args.keys.map { |key| "{#{key}}" }.join(', ')
129
+ call_query = "spatial.#{procedure_name}(#{call_params})"
130
+ call_query += ' YIELD node' if with_node
131
+
132
+ query_ = Query.new(session: self)
133
+ query_.call(call_query).params(procedure_args)
134
+ end
135
+
136
+ def wrap_spatial_procedure(procedure_name, procedure_args, execution_args = {})
137
+ execute = execution_args.fetch(:execute, true)
138
+ node = execution_args.fetch(:node, true)
139
+
140
+ procedure = spatial_procedure(procedure_name, procedure_args, node)
141
+
142
+ procedure = execute_and_format_response(procedure) if execute
143
+ procedure
116
144
  end
117
145
 
118
- def request_error!(code, message, stack_trace)
119
- fail Neo4jrbSpatial::RequestError, <<-ERROR
120
- #{ANSI::CYAN}#{code}#{ANSI::CLEAR}: #{message}
121
- #{stack_trace}
122
- ERROR
146
+ def execute_and_format_response(procedure)
147
+ procedure.response.map do |res|
148
+ res.respond_to?(:node) ? res.node : res
149
+ end
123
150
  end
124
151
 
125
152
  def get_id(id)
126
- return id.neo_id if id.respond_to?(:neo_id)
127
- case id
128
- when Array
129
- get_id(id.first)
130
- when Hash
131
- id[:self].split('/').last
132
- when String
133
- id.split('/').last
134
- else
135
- id
136
- end
153
+ return get_id(id.first) if id.is_a?(Array)
154
+ id.neo_id
137
155
  end
138
156
  end
139
157
 
140
- class CypherSession < Neo4j::Session
158
+ class CypherSession
141
159
  include Spatial
142
160
  end
143
161
  end
@@ -0,0 +1,2 @@
1
+ require 'rake'
2
+ load 'neo4jrb_spatial/rake_tasks/neo4j_spatial.rake'
@@ -0,0 +1,67 @@
1
+ require 'net/http'
2
+
3
+ def system_or_fail(command)
4
+ system(command) || exit(1)
5
+ end
6
+
7
+ def match_version?(version, max_version)
8
+ min_version = max_version.split('.')[0..-2].join('.')
9
+ Gem::Version.new(version) <= Gem::Version.new(max_version) &&
10
+ Gem::Version.new(version) >= Gem::Version.new(min_version)
11
+ end
12
+
13
+ def fail_with_help(version, latest_versions)
14
+ message = <<-MSG
15
+
16
+ No compatible version of neo4j_spatial was found for neo4j version #{version}.
17
+ The latest version is (neo4j_spatial=#{latest_versions[0]}, neo4j=#{latest_versions[1]}).
18
+
19
+ To install neo4j_spatial for a different version, run:
20
+ NEO4J_VERSION='#{latest_versions[1]}' bundle exec rake neo4j_spatial:install
21
+
22
+ MSG
23
+
24
+ fail ArgumentError, message
25
+ end
26
+
27
+ def matching_version(version)
28
+ uri = 'https://raw.githubusercontent.com/neo4j-contrib/m2/master/releases/org/neo4j/neo4j-spatial/maven-metadata.xml'
29
+ versions = Net::HTTP.get_response(URI.parse(uri)).body
30
+ versions = versions.scan(/<version>([a-z\-0-9\.]+)<\/version>/)
31
+ versions.map! { |e| e.first.split('-neo4j-') }
32
+
33
+ compatible_version = versions.select { |e| match_version?(version, e.last) }.last
34
+ fail_with_help(version, versions.last) if compatible_version.nil?
35
+
36
+ compatible_version
37
+ end
38
+
39
+ def neo4j_version_from_install(env)
40
+ server_file = Dir.glob("db/neo4j/#{env}/lib/neo4j-server-*.jar").first
41
+ server_file.match(/.*-server-(.*).jar$/)[1] if server_file
42
+ end
43
+
44
+ namespace :neo4j_spatial do
45
+ desc 'Install neo4j_spatial into /db/neo4j/[env]/plugins'
46
+ task :install, :environment do |_, args|
47
+ args.with_defaults(environment: 'development')
48
+ puts "Install Neo4j Spatial (#{args[:environment]} environment)..."
49
+
50
+ url = 'https://github.com/neo4j-contrib/m2/blob/master/releases/org/neo4j/neo4j-spatial'
51
+ input_version = ENV['NEO4J_VERSION'] || neo4j_version_from_install(args[:environment])
52
+ fail ArgumentError, 'Missing NEO4J_VERSION' unless input_version
53
+ spatial_version, neo4j_version = *matching_version(input_version)
54
+
55
+ install_path = "db/neo4j/#{args[:environment]}/plugins"
56
+
57
+ if neo4j_version[0].to_i < 3
58
+ file_name = "neo4j-spatial-#{spatial_version}-neo4j-#{neo4j_version}-server-plugin.zip"
59
+ system_or_fail("wget -O #{file_name} #{url}/#{spatial_version}-neo4j-#{neo4j_version}/#{file_name}?raw=true")
60
+ system_or_fail("unzip #{file_name} -d #{install_path}")
61
+ else
62
+ file_name = "neo4j-spatial-#{spatial_version}-neo4j-#{neo4j_version}-server-plugin.jar"
63
+ system_or_fail("wget -O #{file_name} #{url}/#{spatial_version}-neo4j-#{neo4j_version}/#{file_name}?raw=true")
64
+ system_or_fail("mv #{file_name} #{install_path}")
65
+ end
66
+ end
67
+ end
@@ -1,3 +1,3 @@
1
1
  module Neo4jrbSpatial
2
- VERSION = '1.2.0'
2
+ VERSION = '2.0.0'.freeze
3
3
  end
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency 'rspec'
23
23
  spec.add_development_dependency 'pry'
24
24
 
25
- spec.add_dependency 'neo4j', '>= 5.0.1', '< 8'
26
- spec.add_dependency 'neo4j-core', '>= 5.0.1', '< 7'
25
+ spec.add_dependency 'neo4j', '>= 8.0.6', '<= 8.0.15'
26
+ spec.add_dependency 'neo4j-core', '>= 7', '< 7.1.0'
27
27
  spec.add_dependency 'neo4j-rake_tasks', '~> 0.3'
28
28
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neo4jrb_spatial
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Grigg
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-26 00:00:00.000000000 Z
11
+ date: 2017-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -86,40 +86,40 @@ dependencies:
86
86
  requirements:
87
87
  - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: 5.0.1
90
- - - "<"
89
+ version: 8.0.6
90
+ - - "<="
91
91
  - !ruby/object:Gem::Version
92
- version: '8'
92
+ version: 8.0.15
93
93
  type: :runtime
94
94
  prerelease: false
95
95
  version_requirements: !ruby/object:Gem::Requirement
96
96
  requirements:
97
97
  - - ">="
98
98
  - !ruby/object:Gem::Version
99
- version: 5.0.1
100
- - - "<"
99
+ version: 8.0.6
100
+ - - "<="
101
101
  - !ruby/object:Gem::Version
102
- version: '8'
102
+ version: 8.0.15
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: neo4j-core
105
105
  requirement: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - ">="
108
108
  - !ruby/object:Gem::Version
109
- version: 5.0.1
109
+ version: '7'
110
110
  - - "<"
111
111
  - !ruby/object:Gem::Version
112
- version: '7'
112
+ version: 7.1.0
113
113
  type: :runtime
114
114
  prerelease: false
115
115
  version_requirements: !ruby/object:Gem::Requirement
116
116
  requirements:
117
117
  - - ">="
118
118
  - !ruby/object:Gem::Version
119
- version: 5.0.1
119
+ version: '7'
120
120
  - - "<"
121
121
  - !ruby/object:Gem::Version
122
- version: '7'
122
+ version: 7.1.0
123
123
  - !ruby/object:Gem::Dependency
124
124
  name: neo4j-rake_tasks
125
125
  requirement: !ruby/object:Gem::Requirement
@@ -148,6 +148,8 @@ files:
148
148
  - lib/neo4j/spatial.rb
149
149
  - lib/neo4jrb_spatial.rb
150
150
  - lib/neo4jrb_spatial/errors.rb
151
+ - lib/neo4jrb_spatial/rake_tasks.rb
152
+ - lib/neo4jrb_spatial/rake_tasks/neo4j_spatial.rake
151
153
  - lib/neo4jrb_spatial/version.rb
152
154
  - neo4jrb_spatial.gemspec
153
155
  homepage: https://github.com/neo4jrb/neo4jrb_spatial
@@ -169,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
171
  version: '0'
170
172
  requirements: []
171
173
  rubyforge_project:
172
- rubygems_version: 2.5.1
174
+ rubygems_version: 2.6.8
173
175
  signing_key:
174
176
  specification_version: 4
175
177
  summary: Provides basic support for Neo4j Spatial with Neo4j.rb.