rom-neo4j 0.0.1

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