rom-neo4j 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 434f1e9e4586f8d8a826631dc3df4acb084fd636
4
+ data.tar.gz: f8856d06f8ca5f12b65f21b793bc91e9ca88d1a0
5
+ SHA512:
6
+ metadata.gz: 8d1aea0e308f15dd1e66e98cdff967bab262bb6b0f840c8166610a156d84a134dc5ce3d4b29e6dd0c103e42ca79303c74923dd24413918f7082979066f1c86ca
7
+ data.tar.gz: 506f5147ffd33784f81a10e700238f79e484c53cbcd699fbc1996af979a1067ab2e936303a91eef05852c2a19344845886dabb3852825cc1548b54bdc7fca2d4
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,68 @@
1
+ # ROM::Neo4j
2
+
3
+ Map objects returned from Neo4j graph traversals using the [Ruby Object Mapper](https://github.com/rom-rb/rom) toolkit.
4
+
5
+ ## Status
6
+
7
+ Incomplete experimental sketch.
8
+
9
+ The adapter spec passes with the generic movies dataset distributed with Neo4j, but that's about all at this point.
10
+
11
+ ## Install
12
+
13
+ Right now, the fastest way to get started is to run the tests.
14
+
15
+ You’ll need to have Neo4j installed on your system. If it’s not already running, start the database server with:
16
+
17
+ ```
18
+ neo4j start
19
+ ```
20
+
21
+ To load the sample movies graph, go to `http://localhost:7474/browser/` and click through the instructions or type `:play movie graph` in the console to start.
22
+
23
+ Once the movies graph is loaded, the integration specs should run:
24
+
25
+ ```
26
+ rspec specs/integration
27
+ ```
28
+
29
+ ## Examples
30
+
31
+ ### Cypher DSL
32
+
33
+ ```ruby
34
+ setup.relation(:movies) do
35
+ matches m: :Movie
36
+ returns m: [:title, :released, :tagline]
37
+
38
+ def titled(title)
39
+ where('m.title' => title)
40
+ end
41
+
42
+ end
43
+
44
+ movie = rom.read(:movies).titled('The Matrix').first
45
+ movie.title # => "The Matrix"
46
+ movie.updated # => 1999
47
+ ```
48
+
49
+ ### Raw Cypher Queries
50
+
51
+ ```ruby
52
+ setup.relation(:directors) do
53
+ matches '(director:Person)-[:DIRECTED]->(movie:Movie)'
54
+ returns 'DISTINCT director.name as name'
55
+
56
+ def by_movie(title)
57
+ where('movie.title' => title)
58
+ end
59
+
60
+ end
61
+
62
+ director = rom.read(:directors).by_movie('RescueDawn').first
63
+ director.name # => "Werner Herzog"
64
+ ```
65
+
66
+ ## Roadmap
67
+
68
+ A work in progress.
@@ -0,0 +1 @@
1
+ require 'rom/neo4j'
@@ -0,0 +1,10 @@
1
+ require 'neo4j-core'
2
+
3
+ require 'rom'
4
+
5
+ require 'rom/neo4j/dataset'
6
+ require 'rom/neo4j/relation'
7
+ require 'rom/neo4j/repository'
8
+ require 'rom/neo4j/support/core_ext'
9
+
10
+ ROM.register_adapter(:neo4j, ROM::Neo4j)
@@ -0,0 +1,62 @@
1
+ module ROM
2
+ module Neo4j
3
+ # A dataset represents a collection returned from a query traversal over a
4
+ # sub-graph.
5
+ #
6
+ # Datasets are Enumerable objects and can be manipulated using the standard
7
+ # methods, `each`, `map`, `inject`, and so forth.
8
+ class Dataset
9
+ include Enumerable
10
+
11
+ # @see http://www.rubydoc.info/gems/neo4j-core/Neo4j/Core/Query
12
+ # @param query [Neo4j::Core::Query] Query object returned from a Neo4j connection
13
+ def initialize(query)
14
+ @query = query
15
+ end
16
+
17
+ def to_cypher
18
+ @query.to_cypher
19
+ end
20
+
21
+ def each(&iter)
22
+ @query.each(&iter)
23
+ end
24
+
25
+ def where(*conditions)
26
+ self.class.new(@query.where(*conditions))
27
+ end
28
+
29
+ def start(*conditions)
30
+ self.class.new(@query.start(*conditions))
31
+ end
32
+
33
+ def match(*conditions)
34
+ self.class.new(@query.match(*conditions))
35
+ end
36
+
37
+ def return(*conditions)
38
+ self.class.new(@query.return(*conditions))
39
+ end
40
+
41
+ def limit(*conditions)
42
+ self.class.new(@query.limit(*conditions))
43
+ end
44
+
45
+ def merge(*conditions)
46
+ self.class.new(@query.merge(*conditions))
47
+ end
48
+
49
+ def order(*conditions)
50
+ self.class.new(@query.order(*conditions))
51
+ end
52
+
53
+ def optional_match(*conditions)
54
+ self.class.new(@query.optional_match(*conditions))
55
+ end
56
+
57
+ def params(*conditions)
58
+ self.class.new(@query.params(*conditions))
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,99 @@
1
+ module ROM
2
+ module Neo4j
3
+ # Relation supporting Cypher graph traversals. Configure the sub-graph
4
+ # for the relation by specifying a `match` pattern, and collect a dataset
5
+ # for mapping by specifying nodes, edges and properties in the `returns`.
6
+ class Relation < ROM::Relation
7
+
8
+ # TODO: document these methods
9
+ forward(
10
+ :start, :match, :where, :return, :limit, :merge, :order,
11
+ :optional_match, :params
12
+ )
13
+
14
+ # The row iterator. Calling this kicks off the actual query to the
15
+ # database server.
16
+ #
17
+ # Before triggering the enumerator, it rebuilds the dataset with the
18
+ # default traversal configured by the relation DSL.
19
+ #
20
+ def each(&iter)
21
+ # Configure the default traversal
22
+ unless @configured
23
+ @configured = true
24
+ self.class.traversal.each do |query_method, conditions|
25
+ @dataset = @dataset.send(query_method.to_sym, *conditions) if conditions
26
+ end
27
+ end
28
+
29
+ @dataset.each(&iter)
30
+ end
31
+
32
+ # Builds data structures to configure the relation DSL.
33
+ # @api private
34
+ def self.inherited(klass)
35
+ klass.class_eval do
36
+ class << self
37
+ attr_reader :traversal
38
+ end
39
+ end
40
+
41
+ klass.instance_variable_set('@traversal', {
42
+ start: false,
43
+ match: false,
44
+ return: false
45
+ })
46
+
47
+ super
48
+ end
49
+
50
+ # Specify a `START` node for the for the relation's graph traversal. This
51
+ # is only required for legacy indexes. In most cases Cypher can infer the
52
+ # starting points to anchor a graph traversal from the pattern specified
53
+ # in a `MATCH` clause.
54
+ #
55
+ # @see http://neo4j.com/docs/stable/query-start.html
56
+ #
57
+ def self.start(*conditions)
58
+ @traversal[:start] = conditions
59
+ end
60
+
61
+ # Specify a `MATCH` clause for the relation's graph traversal. If you’re
62
+ # coming from the SQL world, you can think of this as similar to a
63
+ # `SELECT FROM`, except that it matches on a topological structure rather
64
+ # than a schema.
65
+ #
66
+ # @example Reproduce SQL style projections by passing node labels directly.
67
+ #
68
+ # class Movies < ROM::Relation[:neo4j]
69
+ # matches m: :movie
70
+ # end
71
+ #
72
+ # @example Specify topological matches using Cypher's ASCII-art syntax.
73
+ #
74
+ # class Actors < ROM::Relation[:neo4j]
75
+ # matches '(actor:Person)-[:ACTED_IN]->(movie)'
76
+ # end
77
+ #
78
+ # @see http://neo4j.com/docs/stable/query-match.html
79
+ #
80
+ def self.matches(*conditions)
81
+ @traversal[:match] = conditions
82
+ end
83
+
84
+ # Specify a `RETURN` clause for the relation. This will define the
85
+ # structure of objects in the returned dataset.
86
+ #
87
+ # Any combination of nodes, edges and properties can be selected, as well
88
+ # as custom aliases and distinct objects.
89
+ #
90
+ # @see http://neo4j.com/docs/stable/query-return.html
91
+ #
92
+ def self.returns(*conditions)
93
+ @traversal[:return] = conditions
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,20 @@
1
+ module ROM
2
+ module Neo4j
3
+ class Repository < ROM::Repository
4
+ attr_reader :sets
5
+
6
+ def initialize(uri, options={})
7
+ @connection = ::Neo4j::Session.open(:server_db, uri, options)
8
+ @sets = {}
9
+ end
10
+
11
+ def dataset(name)
12
+ sets[name] = Dataset.new(@connection.query)
13
+ end
14
+
15
+ def dataset?(name)
16
+ sets.key?(name)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,46 @@
1
+ # Extensions to the Neo4j::Core library making it easier to integrate with ROM.
2
+ module Neo4j
3
+ module Core
4
+ # DSL for generating Cypher queries and enumerating their results
5
+ #
6
+ # @see http://www.rubydoc.info/gems/neo4j-core/Neo4j/Core/Query
7
+ class Query
8
+ # def each
9
+ # response = self.response
10
+ # if response.is_a?(Neo4j::Server::CypherResponse)
11
+ # response.to_node_enumeration
12
+ # else
13
+ # Neo4j::Embedded::ResultWrapper.new(response, to_cypher)
14
+ # end.each { |object| yield object }
15
+ # end
16
+ end
17
+ end
18
+ module Server
19
+ class CypherResponse
20
+ # Coerces result objects from structs to hashes in results.
21
+ class HashEnumeration
22
+ def each(&block)
23
+ @response.each_data_row do |row|
24
+ yield(row.each_with_index.each_with_object(struct.new) do |(value, i), result|
25
+ result[columns[i].to_sym] = value
26
+ end.to_h)
27
+ end
28
+ end
29
+ end
30
+ # Coerces result objects from structs to hashes in results.
31
+ def to_node_enumeration(cypher = '', session = Neo4j::Session.current)
32
+ Enumerator.new do |yielder|
33
+ @result_index = 0
34
+ to_struct_enumeration(cypher).each do |row|
35
+ @row_index = 0
36
+ yielder << row.each_pair.each_with_object(@struct.new) do |(column, value), result|
37
+ result[column] = map_row_value(value, session)
38
+ @row_index += 1
39
+ end.to_h
40
+ @result_index += 1
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ module ROM
2
+ module Neo4j
3
+ VERSION = '0.0.1'.freeze
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rom/neo4j/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rom-neo4j'
8
+ spec.version = ROM::Neo4j::VERSION.dup
9
+ spec.authors = ['Mark Rickerby']
10
+ spec.email = ['me@maetl.net']
11
+ spec.summary = 'Neo4j graph relations for Ruby Object Mapper'
12
+ spec.description = spec.summary
13
+ spec.homepage = 'https://rom-rb.org/'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_runtime_dependency 'rom', '~> 0.6'
22
+ spec.add_runtime_dependency 'neo4j-core', '~> 4.0'
23
+
24
+ spec.add_development_dependency 'bundler'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'rubocop', '~> 0.28.0'
27
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+ require 'rom/lint/spec'
3
+
4
+ describe 'Neo4j adapter' do
5
+ let(:dsn) { 'http://localhost:7474' }
6
+ let(:setup) { ROM.setup(:neo4j, dsn) }
7
+
8
+ context 'with movies relation and mapper' do
9
+ let(:rom) do
10
+ setup.relation(:movies) do
11
+ matches m: :Movie
12
+ #returns m: [:title, :released, :tagline]
13
+ returns ['m.title AS title', 'm.released AS released', 'm.tagline as tagline']
14
+
15
+ def by_title(title)
16
+ where('m.title' => title)
17
+ end
18
+ end
19
+
20
+ setup.mappers do
21
+ define(:movies) do
22
+ relation :movies
23
+ model name: 'Movie'
24
+ #prefix :m
25
+ #prefix_separator '.'
26
+ attribute :title
27
+ attribute :released
28
+ attribute :tagline
29
+ end
30
+ end
31
+
32
+ setup.finalize
33
+ end
34
+
35
+ let(:movies) do
36
+ rom.relation(:movies)
37
+ end
38
+
39
+ it 'maps movies #by_title' do
40
+ movies.by_title('The Matrix').as(:movies).first.tap do |movie|
41
+ expect(movie).to be_a(Movie)
42
+ expect(movie.title).to eql('The Matrix')
43
+ expect(movie.released).to eql(1999)
44
+ expect(movie.tagline).to eql('Welcome to the Real World')
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ context 'with directors relation and mapper' do
51
+ let(:rom) do
52
+ setup.relation(:directors) do
53
+ matches '(director:Person)-[:DIRECTED]->(movie:Movie)'
54
+ returns 'DISTINCT director.name AS name'
55
+
56
+ def by_movie(title)
57
+ where('movie.title' => title)
58
+ end
59
+ end
60
+
61
+ setup.mappers do
62
+ define(:directors) do
63
+ relation :directors
64
+ model name: 'Director'
65
+ attribute :name
66
+ end
67
+ end
68
+
69
+ setup.finalize
70
+ end
71
+
72
+ let(:directors) do
73
+ rom.relation(:directors)
74
+ end
75
+
76
+ it 'maps directors #by_movie_title' do
77
+ directors.by_movie('RescueDawn').as(:directors).first.tap do |director|
78
+ expect(director).to be_a(Director)
79
+ expect(director.name).to eql('Werner Herzog')
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1 @@
1
+ require 'rom-neo4j'
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'rom/memory/dataset'
3
+
4
+ describe ROM::Neo4j::Relation do
5
+
6
+ describe '#where' do
7
+ xit 'where equals' do
8
+ expect(relation.to_a).to eql([matrix, reloaded])
9
+ end
10
+
11
+ xit 'where not equals' do
12
+ expect(relation.to_a).to eql([matrix, reloaded])
13
+ end
14
+
15
+ xit 'where gte' do
16
+ expect(relation.to_a).to eql([matrix, reloaded])
17
+ end
18
+ end
19
+
20
+ describe '#limit' do
21
+
22
+ end
23
+
24
+ describe '#merge' do
25
+
26
+ end
27
+
28
+ describe '#order' do
29
+
30
+ end
31
+
32
+ describe '#order' do
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+ require 'rom/memory/dataset'
3
+
4
+ describe ROM::Neo4j::Relation do
5
+ subject(:relation) { Class.new(ROM::Neo4j::Relation).new(dataset) }
6
+
7
+ let(:dataset) { ROM::Memory::Dataset.new([matrix, reloaded]) }
8
+
9
+ let(:matrix) { { title: 'The Matrix', released: 1999 } }
10
+ let(:reloaded) { { title: 'The Matrix Reloaded', released: 2003 } }
11
+
12
+ describe '#each' do
13
+ it 'yields all objects' do
14
+ result = []
15
+
16
+ relation.each do |user|
17
+ result << user
18
+ end
19
+
20
+ expect(result).to eql([matrix, reloaded])
21
+ end
22
+
23
+ it 'returns an enumerator if block is not provided' do
24
+ expect(relation.each).to be_instance_of(Enumerator)
25
+ end
26
+ end
27
+
28
+ describe '#to_a' do
29
+ it 'materializes relation to an array' do
30
+ expect(relation.to_a).to eql([matrix, reloaded])
31
+ end
32
+ end
33
+
34
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rom-neo4j
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Mark Rickerby
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rom
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: neo4j-core
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.28.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.28.0
83
+ description: Neo4j graph relations for Ruby Object Mapper
84
+ email:
85
+ - me@maetl.net
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - README.md
93
+ - lib/rom-neo4j.rb
94
+ - lib/rom/neo4j.rb
95
+ - lib/rom/neo4j/dataset.rb
96
+ - lib/rom/neo4j/relation.rb
97
+ - lib/rom/neo4j/repository.rb
98
+ - lib/rom/neo4j/support/core_ext.rb
99
+ - lib/rom/neo4j/version.rb
100
+ - rom-neo4j.gemspec
101
+ - spec/integration/adapter_spec.rb
102
+ - spec/spec_helper.rb
103
+ - spec/unit/query_spec.rb
104
+ - spec/unit/relation_spec.rb
105
+ homepage: https://rom-rb.org/
106
+ licenses:
107
+ - MIT
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.4.2
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Neo4j graph relations for Ruby Object Mapper
129
+ test_files:
130
+ - spec/integration/adapter_spec.rb
131
+ - spec/spec_helper.rb
132
+ - spec/unit/query_spec.rb
133
+ - spec/unit/relation_spec.rb
134
+ has_rdoc: