neo4jrb / neo4jrb_spatial

Basic integration between Neo4j.rb and Neo4j Spatial
12 stars 12 forks source link

Using with Neo4j 3.x #17

Open pmackay opened 7 years ago

pmackay commented 7 years ago

Please could I clarify, is there a configuration of neo4j, neo4j-spatial, neo4jrb and neo4jrb_spatial that works for Neo4J 3.x? This issue suggests there isnt because indexes were changes for spatial procedures. Would #14 resolve this compatibility problem?

@ProGM would appreciate your input.

ProGM commented 7 years ago

Unfortunately this repo does not work with new procedures. However I had written a minimal set of methods to monkey-patch neo4jrb_spatial so it could work with neo4j 3.x.

Here's it:

First of all, install neo4j_spatial like this:

gem 'neo4jrb_spatial', github: 'neo4jrb/neo4jrb_spatial', branch: 'neo4j-8.x'

Create a neo4j3_spatial.rb file in your "app/models/concern"

module Neo4j3Spatial
  extend ActiveSupport::Concern
  module ClassMethods
    def create_index!(type: 'SimplePoint')
      return if layer?(spatial_index_name)
      _query.call('spatial.addLayer({name}, {type}, "lon:lat")')
            .params(name: spatial_index_name, type: type).to_a
    end

    def layer?(name)
      _query.call('spatial.layers() YIELD name, signature')
            .with(:name).where(name: name).return(:name).any?
    end

    private

    def _query
      Neo4j::ActiveBase.new_query
    end
  end

  included do
    scope :within_distance, lambda { |options|
      Neo4j::ActiveBase
        .new_query
        .call('spatial.withinDistance({layer}, {coordinate}, {distance}) YIELD node, distance')
        .params(layer: spatial_index_name,
                coordinate: { lon: options[:lon], lat: options[:lat] },
                distance: options[:distance])
        .with(:node).where("(node:#{mapped_label_name})")
        .pluck(:node)
    }

    scope :bbox, lambda { |box|
      Neo4j::ActiveBase
        .new_query
        .call('spatial.bbox({layer}, {x1}, {x2}) YIELD node')
        .params(layer: spatial_index_name, x1: { lon: box[0], lat: box[1] }, x2: { lon: box[2], lat: box[3] })
        .with(:node).pluck(:node)
    }
    create_index!
  end

  delegate :layer?, to: :class

  def add_to_spatial_index
    return if !lat? || !lon? || !layer?(self.class.spatial_index_name)
    Neo4j::ActiveBase
      .new_query
      .match_nodes(n: neo_id)
      .with(:n).call('spatial.addNode({layer}, n) YIELD node')
      .params(layer: self.class.spatial_index_name).pluck(:n)
  end
end

Here's a sample usage:

class YourModel
  include Neo4j::ActiveNode
  include Neo4j::ActiveNode::Spatial
  spatial_index 'your_index'
  include Neo4j3Spatial
end

YourModel.within_distance(...)
ecc...
pmackay commented 7 years ago

@ProGM hi, really appreciate your answer. I'm testing this out but getting Neo.ClientError.Schema.IndexNotFound: Indexplacesdoes not exist (places being my index name). What is the right way to setup an index now (or layer, but am assuming the index terminology is still being used here)?

ProGM commented 7 years ago

I can't really test right now, but the index creation should be provided when including the module. (check the last line of the "included" block)

You can manually trigger it by using YourModel.create_index!.

Don't forgot to call node_instance. add_to_spatial_index after creating a new node.

Let me know if this helps :)

pmackay commented 7 years ago

Thanks for the help :) I wasnt calling within_distance properly as a scope method, re-reviewing it after your comments helped. I think the error response was confusing me a bit. So I've got basic spatial query working!

BTW, I had to get a local copy of the gem and increase the dependency version on neo4j so it allowed v8, otherwise bundle wouldnt work. Perhaps that needs tweaking in your branch?

Do you know of any example code of how to chain queries so a spatial and other kinds of queries can be combined? Is it actually possible to chain these scopes? The return value from the within_distance scope is an array so it cannot accept further scopes.

ProGM commented 7 years ago

@pmackay Not sure about it.

To make the query chainable, just remove the pluck(:node) from the scope chain ;)

    scope :within_distance, lambda { |options|
      Neo4j::ActiveBase
        .new_query
        .call('spatial.withinDistance({layer}, {coordinate}, {distance}) YIELD node, distance')
        .params(layer: spatial_index_name,
                coordinate: { lon: options[:lon], lat: options[:lat] },
                distance: options[:distance])
        .with(:node).where("(node:#{mapped_label_name})")
    }

Example:

Yourmodel. within_distance('...').where(node: { some_property: 'some_value' }).pluck(:node)
kroyagis commented 7 years ago

I get a following error when I run create_index!

NameError: uninitialized constant Neo4j::ActiveBase
    from /Users/jiyou/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/aws-s3-0.6.3/lib/aws/s3/extensions.rb:212:in `const_missing_from_s3_library'
    from (irb):2
    from /Users/jiyou/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands/console.rb:110:in `start'
    from /Users/jiyou/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands/console.rb:9:in `start'
    from /Users/jiyou/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands/commands_tasks.rb:68:in `console'
    from /Users/jiyou/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
    from /Users/jiyou/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/railties-4.2.1/lib/rails/commands.rb:17:in `<top (required)>'
    from /Users/jiyou/Desktop/tilr/tilr-api/bin/rails:9:in `<top (required)>'
    from /Users/jiyou/.rbenv/versions/2.2.3/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
    from /Users/jiyou/.rbenv/versions/2.2.3/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
    from -e:1:in `<main>'
ProGM commented 7 years ago

@kroyagis Are you using the neo4j gem 8.0+?

kroyagis commented 7 years ago

Sorry, I was using 7.2.x at the time I posted this. I upgraded to 8.0.x and it does recognize ActiveBase. I'm currently solving different problems so this can be closed! Thank you.