neomirror 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ab74e8c31f0b8ea8c07d4fdb8b454f7f1823c37b
4
+ data.tar.gz: 465c276d4c825cf27d2bf38decb2955287958669
5
+ SHA512:
6
+ metadata.gz: 3d952da8b37fd49abd4cf9207992319a39c30f428cda953749e5d1c4cde0d5ece314edb849c39f472a44276c079b260221198ce501b472d8e5578165820a1034
7
+ data.tar.gz: 0e96b273a81c88048c053e8d450dd40b949845e306b5e31779b37f869bc2863337c3932776ec9e2d974087bb789ac84a866b7237cc58a6f941e42a80d25bd0b8
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format doc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in neomirror.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Alex Avoyants
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # Neomirror
2
+
3
+ Lightweight but flexible gem that allows reflect some of data from relational database into neo4j.<br />
4
+ This allows to perform faster and easier search of your models ids<br />
5
+ or it can be first step of migrating application data to neo4j.<br />
6
+ Uses [Neography](https://github.com/maxdemarzi/neography) (wrapper of Neo4j Rest API).
7
+ Gem was inspired by [Neoid](https://github.com/neoid-gem/neoid)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'neomirror'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install neomirror
22
+
23
+ ## Configuration
24
+
25
+ For more configuration options please read about Neography [Configuration and initialization](https://github.com/maxdemarzi/neography/wiki/Configuration-and-initialization)
26
+
27
+ ```ruby
28
+ Neography.configure do |config|
29
+ config.protocol = "http://"
30
+ config.server = "localhost"
31
+ config.port = 7474
32
+ config.username = nil
33
+ config.password = nil
34
+ end
35
+
36
+ Neomirror.connection = Neography::Rest.new
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ### Reflect model as node (vertex).
42
+
43
+
44
+ ```ruby
45
+ class User < ActiveRecord::Base
46
+ include Neomirror::Node
47
+
48
+ mirror_neo_node label: :User do # option :label is optional
49
+ property :name
50
+ property :name_length, ->(record) { record.name.length }
51
+ end
52
+ end
53
+
54
+ user = User.create(name: 'Dougal')
55
+ # Find or create neo node.
56
+ user.neo_node # => #<Neography::Node id=...>
57
+ user.node # Alias of #neo_node
58
+ user.find_neo_node # => #<Neography::Node id=...>
59
+ ```
60
+
61
+ Primary key is saved automatically for nodes as `id` attribute. Also unique constraint is created. Creating a unique constraint also creates a unique index (which is faster than a regular index).
62
+
63
+ For `ActiveRecord` methods `#create_neo_node`, `#update_neo_node`, `#destroy_neo_node` are called on corresponding callbacks (`after_create`, `after_update`, `after_destroy`).
64
+
65
+ ### Reflect model as one or several relationships (edges).
66
+
67
+ ```ruby
68
+ class Membership < ActiveRecord::Base
69
+ include Neomirror::Relationship
70
+ belongs_to :premises
71
+ belongs_to :group
72
+
73
+ mirror_neo_relationship start_node: :premises, end_node: :group, type: :MEMBER_OF
74
+ end
75
+
76
+ membership = Membership.first
77
+ # Find or create neo relationship.
78
+ membership.neo_relationship # => #<Neography::Relationship> or nil unless both nodes present
79
+ membership.neo_rel # Alias of #neo_relationship
80
+ ```
81
+
82
+ For `ActiveRecord` methods `#create_neo_relationships`, `#update_neo_relationships`, `#destroy_neo_relationships` are called on corresponding callbacks (`after_create`, `after_update`, `after_destroy`).
83
+
84
+ #### Reflect and retrieve several relationships for model.
85
+
86
+ ```ruby
87
+ class Staff < ActiveRecord::Base
88
+ include Neomirror::Relationship
89
+ belongs_to :user
90
+ belongs_to :premises
91
+ belongs_to :group
92
+
93
+ mirror_neo_relationship start_node: :user, end_node: :premises, type: :STAFF_OF
94
+ mirror_neo_relationship start_node: :user, end_node: :group, type: :STAFF_OF
95
+ end
96
+
97
+ staff = Staff.first
98
+ staff.neo_relationship(end_node: :premises) # => #<Neography::Relationship> or nil
99
+ staff.neo_relationship(end_node: :group) # => #<Neography::Relationship> or nil
100
+ ```
101
+
102
+ ## Migration of existing data
103
+
104
+ ```ruby
105
+ Premises.find_each(&:create_neo_node)
106
+ Group.find_each(&:create_neo_node)
107
+ Membership.preload(:premises, :group).find_each(&:create_neo_relationships)
108
+ User.find_each(&:create_neo_node)
109
+ Staff.preload(:premises, :group).find_each(&:create_neo_relationships)
110
+ ```
111
+ Note that `#create_neo_node` method will raise exception for already existed node and `#create_neo_relationships` will create duplicated relationships for existed relationships.
112
+
113
+ Also can use `#neo_node` and `#neo_relationship` methods which find or create.
114
+
115
+ ```ruby
116
+ Premises.find_each(&:neo_node)
117
+ Group.find_each(&:neo_node)
118
+ Membership.preload(:premises, :group).find_each(&:neo_relationship)
119
+ User.find_each(&:neo_node)
120
+ Staff.preload(:premises, :group).find_each do |staff|
121
+ staff.neo_relationship(end_node: :premises)
122
+ staff.neo_relationship(end_node: :group)
123
+ end
124
+ ```
125
+
126
+ ## Clear neo4j
127
+
128
+ ```ruby
129
+ Neomirror.neo.execute_query("START n=node(*) OPTIONAL MATCH n-[r]-() DELETE n,r")
130
+ ```
131
+
132
+ ## Reflect model as bunch of optional relationships
133
+
134
+ Sometimes there is choise how to reflect relationship. Model which is representation of relationship can be mapped as edge with properties or as bunch of edges. Both design decisions are possible with neomirror.
135
+
136
+ Reflect model as relationship (edge) with properties.
137
+
138
+ ```ruby
139
+ class Staff < ActiveRecord::Base
140
+ include Neomirror::Relationship
141
+ belongs_to :user
142
+ belongs_to :premises
143
+
144
+ mirror_neo_relationship start_node: :user, end_node: :premises, type: :STAFF_OF do
145
+ property :roles
146
+ end
147
+ end
148
+ ```
149
+
150
+ Reflect model as bunch of optional relationships (edges) existence of which depends on the respective condition. On model create edge created only if predicate evaluates as true. On model update edge will be deleted if predicate evaluates as false.
151
+
152
+ ```ruby
153
+ class Staff < ActiveRecord::Base
154
+ include Neomirror::Relationship
155
+ belongs_to :user
156
+ belongs_to :premises
157
+
158
+ mirror_neo_relationship start_node: :user, end_node: :premises, type: :MANAGER_OF,
159
+ if: ->(r) { r.roles.include?('manager') }
160
+
161
+ mirror_neo_relationship start_node: :user, end_node: :premises, type: :VISITOR_OF,
162
+ if: ->(r) { r.roles.include?('visitor') }
163
+ end
164
+ ```
165
+
166
+ Even possible reflect model as node (vertex) and relation(s) (edge). But it is probably not needed.
167
+
168
+ ## Compatibility
169
+
170
+ It is possible to use it outside of ActiveRecord (there is no dependency). Just use methods `create_neo_node`, `update_neo_node` and `destroy_neo_node` in your callbaks for nodes and `create_neo_relationships`, `update_neo_relationships` and `destroy_neo_relationships` for relationships.
171
+
172
+ Also specify primary key attribute if it is differ from `id` and class don't `respond_to? :primary_key` method.
173
+
174
+ ```
175
+ class Postcode
176
+ attr_accessor :code
177
+ include Neomirror::Node
178
+
179
+ self.node_primary_key = :code
180
+
181
+ mirror_neo_node
182
+ end
183
+
184
+ p = Postcode.new
185
+ p.code = 'ABC'
186
+ p.create_neo_node # => #<Neography::Node id="ABC">
187
+ ```
188
+
189
+ ## Contributing
190
+
191
+ 1. Fork it ( http://github.com/shhavel/neomirror/fork )
192
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
193
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
194
+ 4. Push to the branch (`git push origin my-new-feature`)
195
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new do |t|
5
+ t.rspec_opts = ["-c", "-f documentation", "-r ./spec/spec_helper.rb"]
6
+ t.pattern = 'spec/**/*_spec.rb'
7
+ end
8
+ task default: :spec
@@ -0,0 +1,83 @@
1
+ module Neomirror::Node
2
+ def self.included(base)
3
+ base.extend(ClassMethods)
4
+ base.after_create :create_neo_node if base.respond_to?(:after_create)
5
+ base.after_update :update_neo_node if base.respond_to?(:after_update)
6
+ base.after_destroy :destroy_neo_node if base.respond_to?(:after_destroy)
7
+ end
8
+
9
+ module ClassMethods
10
+ def neo_mirror
11
+ @neo_mirror ||= begin
12
+ return nil unless a = self.ancestors.drop(1).find { |c| c.respond_to?(:neo_mirror) && c.neo_mirror }
13
+ a.neo_mirror
14
+ end
15
+ end
16
+
17
+ def mirror_neo_node(options = {}, &block)
18
+ raise "Node mirror is already defined. Reflection model into more than one node is not supported. Create an issue if you need such functionality." if @neo_mirror
19
+ @neo_mirror = options
20
+ @neo_mirror[:label] ||= self.name.gsub(/^.*::/, '').to_sym # demodulize
21
+ @neo_mirror[:properties] = ::Neomirror::PropertyCollector.new(&block).properties if block_given?
22
+ ::Neomirror.neo.execute_query("CREATE CONSTRAINT ON (n:#{@neo_mirror[:label]}) ASSERT n.id IS UNIQUE")
23
+ @neo_mirror
24
+ end
25
+
26
+ def node_primary_key
27
+ @node_primary_key ||= self.respond_to?(:primary_key) ? self.__send__(:primary_key) : :id
28
+ end
29
+ attr_writer :node_primary_key
30
+ end
31
+
32
+ def neo_node
33
+ raise "Couldn't find neo_node declaration" unless self.class.neo_mirror
34
+ find_neo_node || create_neo_node
35
+ end
36
+ alias_method :node, :neo_node
37
+
38
+ def neo_node_properties
39
+ neo_node_properties = ::Hash.new
40
+ neo_node_properties[:id] = self.__send__(self.class.node_primary_key)
41
+ if self.class.neo_mirror && self.class.neo_mirror[:properties]
42
+ self.class.neo_mirror[:properties].each do |property, rule|
43
+ neo_node_properties[property] = rule.call(self)
44
+ end
45
+ end
46
+ neo_node_properties
47
+ end
48
+
49
+ def find_neo_node
50
+ raise "Couldn't find neo_node declaration" unless self.class.neo_mirror
51
+ label = self.class.neo_mirror[:label]
52
+ id = self.__send__(self.class.node_primary_key)
53
+ return nil unless node = ::Neomirror.neo.find_nodes_labeled(label, { :id => id }).first
54
+ @neo_node = ::Neography::Node.load(node, ::Neomirror.neo)
55
+ end
56
+
57
+ def create_neo_node
58
+ return true unless self.class.neo_mirror
59
+ @neo_node = ::Neography::Node.create(neo_node_properties, ::Neomirror.neo)
60
+ ::Neomirror.neo.set_label(@neo_node, self.class.neo_mirror[:label])
61
+ @neo_node
62
+ end
63
+
64
+ def update_neo_node
65
+ return true unless self.class.neo_mirror
66
+ if find_neo_node
67
+ ::Neomirror.neo.reset_node_properties(@neo_node, neo_node_properties) if self.class.neo_mirror[:properties]
68
+ true
69
+ else
70
+ create_neo_node
71
+ end
72
+ end
73
+
74
+ def destroy_neo_node
75
+ return true unless self.class.neo_mirror && find_neo_node
76
+ ::Neomirror.neo.delete_node!(@neo_node)
77
+ true
78
+ end
79
+
80
+ def neo_node_to_cypher
81
+ ":#{self.class.neo_mirror[:label]} {id:#{self.__send__(self.class.node_primary_key)}}"
82
+ end
83
+ end
@@ -0,0 +1,28 @@
1
+ class Neomirror::PropertyCollector
2
+ def properties
3
+ @properties ||= {}
4
+ end
5
+
6
+ def property(property_name, record_method_name = nil, &block)
7
+ if record_method_name && block_given?
8
+ raise ArgumentError, "For property provide record's method name or block (or proc)"
9
+ elsif block_given?
10
+ properties[property_name.to_sym] = block
11
+ else
12
+ record_method_name ||= property_name
13
+ record_method_name = record_method_name.to_sym if record_method_name.is_a?(String)
14
+ properties[property_name.to_sym] = record_method_name.to_proc
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def initialize(&block)
21
+ return unless block_given?
22
+ if block.arity == 0
23
+ self.instance_eval(&block)
24
+ else
25
+ block.call(self)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,103 @@
1
+ module Neomirror::Relationship
2
+ def self.included(base)
3
+ base.extend(ClassMethods)
4
+ base.after_create :create_neo_relationships if base.respond_to?(:after_create)
5
+ base.after_update :update_neo_relationships if base.respond_to?(:after_update)
6
+ base.after_destroy :destroy_neo_relationships if base.respond_to?(:after_destroy)
7
+ end
8
+
9
+ module ClassMethods
10
+ def rel_mirrors
11
+ @rel_mirrors ||= begin
12
+ if a = self.ancestors.drop(1).find { |c| c.respond_to?(:rel_mirrors) && c.rel_mirrors.any? }
13
+ a.rel_mirrors
14
+ else
15
+ []
16
+ end
17
+ end
18
+ end
19
+
20
+ # Find declaration by partial options.
21
+ def rel_mirror(p)
22
+ return rel_mirrors.first unless p
23
+ rel_mirrors.find { |m| (!p[:start_node] || p[:start_node] == m[:start_node]) &&
24
+ (!p[:end_node] || p[:end_node] == m[:end_node]) && (!p[:type] || p[:type] == m[:type]) }
25
+ end
26
+
27
+ def mirror_neo_relationship(options, &block)
28
+ m = Hash[options.map{ |k, v| [k.to_sym, v] }]
29
+ raise ArgumentError, "Mirror with such options already defined" if rel_mirror(m)
30
+ raise ArgumentError, "Options :start_node and :end_node are mandatory" unless m[:start_node] && m[:end_node]
31
+ m[:start_node] = m[:start_node].to_sym
32
+ m[:end_node] = m[:end_node].to_sym
33
+ m[:type] = (m[:type] ? m[:type] : self.name.gsub(/^.*::/, '').gsub(/([a-z\d])([A-Z])/, '\1_\2').upcase).to_sym
34
+ m[:properties] = Neomirror::PropertyCollector.new(&block).properties if block_given?
35
+ m[:if] = m[:if].to_proc if m[:if]
36
+ rel_mirrors << m
37
+ end
38
+ end
39
+
40
+ def neo_relationship(partial_mirror = nil)
41
+ find_neo_relationship(partial_mirror) || create_neo_relationship(partial_mirror)
42
+ end
43
+ alias_method :neo_rel, :neo_relationship
44
+
45
+ def neo_relationship_properties(partial_mirror = nil)
46
+ raise "Couldn't find neo_relationship declaration" unless rel_mirror = self.class.rel_mirror(partial_mirror)
47
+ return nil unless rel_mirror[:properties]
48
+ rel_mirror[:properties].reduce({}) { |h, (property, rule)| h[property] = rule.call(self); h }
49
+ end
50
+
51
+ def neo_relationship_must_exist?(partial_mirror = nil)
52
+ raise "Couldn't find neo_relationship declaration" unless rel_mirror = self.class.rel_mirror(partial_mirror)
53
+ !rel_mirror[:if] || !!rel_mirror[:if].call(self)
54
+ end
55
+
56
+ def find_neo_relationship(partial_mirror = nil)
57
+ raise "Couldn't find neo_relationship declaration" unless rel_mirror = self.class.rel_mirror(partial_mirror)
58
+ return nil unless (m1 = self.__send__(rel_mirror[:start_node])) && (m2 = self.__send__(rel_mirror[:end_node])) &&
59
+ (rel = ::Neomirror.neo.execute_query("MATCH (#{m1.neo_node_to_cypher})-[r:#{rel_mirror[:type]}]->(#{m2.neo_node_to_cypher}) RETURN r")["data"].first)
60
+ @neo_rel = ::Neography::Relationship.load(rel, ::Neomirror.neo)
61
+ end
62
+
63
+ def create_neo_relationship(partial_mirror = nil)
64
+ raise "Couldn't find neo_relationship declaration" unless rel_mirror = self.class.rel_mirror(partial_mirror)
65
+ return nil unless self.__send__(rel_mirror[:start_node]) && self.__send__(rel_mirror[:end_node]) &&
66
+ neo_relationship_must_exist?(rel_mirror)
67
+ ::Neography::Relationship.create(rel_mirror[:type], self.__send__(rel_mirror[:start_node]).neo_node,
68
+ self.__send__(rel_mirror[:end_node]).neo_node, neo_relationship_properties(rel_mirror))
69
+ end
70
+
71
+ def update_neo_relationship(partial_mirror = nil)
72
+ raise "Couldn't find neo_relationship declaration" unless rel_mirror = self.class.rel_mirror(partial_mirror)
73
+ if find_neo_relationship(rel_mirror)
74
+ if neo_relationship_must_exist?(rel_mirror)
75
+ ::Neomirror.neo.reset_relationship_properties(@neo_rel, neo_relationship_properties(rel_mirror))
76
+ else
77
+ ::Neomirror.neo.delete_relationship(@neo_rel)
78
+ end
79
+ else
80
+ create_neo_relationship(rel_mirror) if neo_relationship_must_exist?(rel_mirror)
81
+ end
82
+ end
83
+
84
+ def destroy_neo_relationship(partial_mirror = nil)
85
+ raise "Couldn't find neo_relationship declaration" unless rel_mirror = self.class.rel_mirror(partial_mirror)
86
+ ::Neomirror.neo.delete_relationship(@neo_rel) if find_neo_relationship(rel_mirror)
87
+ end
88
+
89
+ def create_neo_relationships
90
+ self.class.rel_mirrors.each { |rel_mirror| create_neo_relationship(rel_mirror) }
91
+ true
92
+ end
93
+
94
+ def update_neo_relationships
95
+ self.class.rel_mirrors.each { |rel_mirror| update_neo_relationship(rel_mirror) }
96
+ true
97
+ end
98
+
99
+ def destroy_neo_relationships
100
+ self.class.rel_mirrors.each { |rel_mirror| destroy_neo_relationship(rel_mirror) }
101
+ true
102
+ end
103
+ end
@@ -0,0 +1,3 @@
1
+ module Neomirror
2
+ VERSION = "0.0.1"
3
+ end
data/lib/neomirror.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "neomirror/version"
2
+ require "neography"
3
+ require "neomirror/property_collector"
4
+ require "neomirror/node"
5
+ require "neomirror/relationship"
6
+
7
+ module Neomirror
8
+ class << self
9
+ attr_accessor :connection
10
+ alias_method :neo, :connection
11
+ end
12
+ end
data/neomirror.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'neomirror/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "neomirror"
8
+ spec.version = Neomirror::VERSION
9
+ spec.authors = ["Alex Avoyants"]
10
+ spec.email = ["shhavel@gmail.com"]
11
+ spec.summary = %q{Lightweight but flexible gem that allows reflect some of data from relational database into neo4j.}
12
+ spec.description = %q{Lightweight but flexible gem that allows reflect some of data from relational database into neo4j. This allows to perform faster and easier search of your models ids or it can be first step of migrating application data to neo4j.}
13
+ spec.homepage = ""
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_dependency "neography", ">= 1.5.2"
22
+ spec.add_development_dependency "bundler", "~> 1.5"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec", "~> 2.11"
25
+ spec.add_development_dependency "sqlite3"
26
+ spec.add_development_dependency "activerecord"
27
+ spec.add_development_dependency "activesupport"
28
+ spec.add_development_dependency "factory_girl"
29
+ spec.add_development_dependency "database_cleaner", ">= 1.2.0"
30
+ end
@@ -0,0 +1,3 @@
1
+ FactoryGirl.define do
2
+ factory :group
3
+ end
@@ -0,0 +1,3 @@
1
+ FactoryGirl.define do
2
+ factory :membership
3
+ end
@@ -0,0 +1,3 @@
1
+ FactoryGirl.define do
2
+ factory :premises
3
+ end
@@ -0,0 +1,5 @@
1
+ FactoryGirl.define do
2
+ factory :staff do
3
+ roles %w(visitor)
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ FactoryGirl.define do
2
+ factory :user do
3
+ name 'Ted'
4
+ end
5
+ end
@@ -0,0 +1,78 @@
1
+ require "spec_helper"
2
+
3
+ describe Neomirror::Node do
4
+ let(:user) { create(:user) }
5
+ let(:neo_node) { user.find_neo_node }
6
+
7
+ describe "#find_neo_node" do
8
+ it "searches neo4j node, returns Neography::Node instance" do
9
+ user.find_neo_node.should be_a Neography::Node
10
+ user.find_neo_node.name.should == user.name
11
+ end
12
+
13
+ it "returns nil if there is no node in neo4j" do
14
+ user.destroy_neo_node
15
+ user.find_neo_node.should be_nil
16
+ end
17
+ end
18
+
19
+ describe "#neo_node" do
20
+ it "finds neo4j node if exists, returns Neography::Node instance" do
21
+ user.neo_node.should be_a Neography::Node
22
+ user.neo_node.name.should == user.name
23
+ end
24
+
25
+ it "creates neo4j node if not exists, returns Neography::Node instance" do
26
+ user.destroy_neo_node
27
+ user.neo_node.should be_a Neography::Node
28
+ user.neo_node.name.should == user.name
29
+ end
30
+ end
31
+
32
+ describe "#create_neo_node" do
33
+ before { user.destroy_neo_node }
34
+
35
+ it "creates neo4j node after record was created" do
36
+ user.create_neo_node
37
+ neo_node.should be_a Neography::Node
38
+ end
39
+ end
40
+
41
+ describe "#update_neo_node" do
42
+ it "updates neo4j node after record was updated" do
43
+ user.neo_node.name.should == 'Ted'
44
+ user.update_attributes(name: 'Dougal')
45
+ user.neo_node.name.should == 'Dougal'
46
+ end
47
+
48
+ it "creates neo4j node if it is not exists after record was updated" do
49
+ user.destroy_neo_node
50
+ user.find_neo_node.should be_nil
51
+ user.update_attributes(name: 'Dougal')
52
+ user.find_neo_node.should be_a Neography::Node
53
+ user.find_neo_node.name.should == 'Dougal'
54
+ end
55
+ end
56
+
57
+ describe "#destroy_neo_node" do
58
+ it "destroys neo4j node after record was destroyed" do
59
+ user.destroy
60
+ user.find_neo_node.should be_nil
61
+ end
62
+ end
63
+
64
+ describe ".node_primary_key" do
65
+ let(:postcode) { Postcode.new.tap { |p| p.code = 'ABC' } }
66
+
67
+ it "sets custom primary key" do
68
+ postcode.neo_node.should be_a Neography::Node
69
+ postcode.neo_node.id.should == 'ABC'
70
+ end
71
+ end
72
+
73
+ describe "#neo_node_to_cypher" do
74
+ it "represents node as cypher element" do
75
+ user.neo_node_to_cypher.should == ":User {id:#{user.id}}"
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,107 @@
1
+ require "spec_helper"
2
+
3
+ describe Neomirror::Relationship do
4
+ let(:premises) { create(:premises) }
5
+ let(:group) { create(:group) }
6
+ let(:membership) { create(:membership, premises: premises, group: group) }
7
+ let(:user) { create(:user) }
8
+ let(:staff_of_premises) { create(:staff, user: user, premises: premises) }
9
+ let(:staff_of_group) { create(:staff, user: user, group: group) }
10
+ let(:staff) { create(:staff, user: user, premises: premises, group: group) }
11
+
12
+ describe "#find_neo_relationship" do
13
+ it "searches neo4j relationship, returns Neography::Relationship instance" do
14
+ membership.find_neo_relationship.should be_a Neography::Relationship
15
+ end
16
+
17
+ it "returns nil if there is no relationship in neo4j" do
18
+ membership.destroy_neo_relationship
19
+ membership.find_neo_relationship.should be_nil
20
+ end
21
+ end
22
+
23
+ describe "#neo_relationship" do
24
+ it "finds neo4j relationship if exists, returns Neography::Relationship instance" do
25
+ membership.neo_relationship.should be_a Neography::Relationship
26
+ end
27
+
28
+ it "creates neo4j relationship if not exists, returns Neography::Relationship instance" do
29
+ membership.destroy_neo_relationship
30
+ membership.neo_relationship.should be_a Neography::Relationship
31
+ end
32
+ end
33
+
34
+ describe "#create_neo_relationship" do
35
+ before { membership.destroy_neo_relationship }
36
+
37
+ it "creates neo4j relationship after record was created" do
38
+ membership.create_neo_relationship
39
+ membership.find_neo_relationship.should be_a Neography::Relationship
40
+ end
41
+
42
+ it "does not create neo4j relationship unless both nodes are present" do
43
+ membership = create(:membership, premises: premises)
44
+ membership.create_neo_relationship
45
+ membership.find_neo_relationship.should be_nil
46
+ end
47
+
48
+ it "does not create neo4j relationship if condition returns false" do
49
+ staff.create_neo_relationship(type: :VISITOR_OF)
50
+ staff.find_neo_relationship(type: :VISITOR_OF).should be_a Neography::Relationship
51
+ staff.create_neo_relationship(type: :MANAGER_OF)
52
+ staff.find_neo_relationship(type: :MANAGER_OF).should be_nil
53
+ end
54
+ end
55
+
56
+ describe "#create_neo_relationships" do
57
+ it "creates multiple neo4j relationships after record was created" do
58
+ staff.find_neo_relationship(end_node: :premises, type: :STAFF_OF).should be_a Neography::Relationship
59
+ staff.find_neo_relationship(end_node: :group, type: :STAFF_OF).should be_a Neography::Relationship
60
+ end
61
+
62
+ it "creates multiple neo4j relationships for which both nodes exist" do
63
+ staff_of_premises.find_neo_relationship(end_node: :premises, type: :STAFF_OF).should be_a Neography::Relationship
64
+ staff_of_premises.find_neo_relationship(end_node: :group, type: :STAFF_OF).should be_nil
65
+ end
66
+
67
+ it "creates multiple optional neo4j relationships for which condition returns true after record was created" do
68
+ staff.find_neo_relationship(type: :VISITOR_OF).should be_a Neography::Relationship
69
+ staff.find_neo_relationship(type: :MANAGER_OF).should be_nil
70
+ end
71
+ end
72
+
73
+ describe "#update_neo_relationship" do
74
+ it "updates neo4j relationship after record was updated" do
75
+ staff.find_neo_relationship.roles.should == %w(visitor)
76
+ staff.update_attributes(roles: %w(visitor admin))
77
+ staff.find_neo_relationship.roles.should == %w(visitor admin)
78
+ end
79
+
80
+ it "creates neo4j relationship if it is not exists after record was updated" do
81
+ staff.destroy_neo_relationships
82
+ staff.find_neo_relationship.should be_nil
83
+ staff.update_attributes(roles: %w(admin))
84
+ staff.find_neo_relationship.should be_a Neography::Relationship
85
+ staff.find_neo_relationship.roles.should == %w(admin)
86
+ end
87
+
88
+ it "deletes optional relationship for which condition returns false" do
89
+ staff.find_neo_relationship(type: :VISITOR_OF).should be_a Neography::Relationship
90
+ staff.find_neo_relationship(type: :MANAGER_OF).should be_nil
91
+ staff.update_attributes(roles: %w(manager))
92
+ staff.find_neo_relationship(type: :VISITOR_OF).should be_nil
93
+ staff.find_neo_relationship(type: :MANAGER_OF).should be_a Neography::Relationship
94
+ end
95
+ end
96
+
97
+ describe "#destroy_neo_relationship" do
98
+ it "destroys neo4j relationship after record was destroyed" do
99
+ membership.destroy
100
+ membership.find_neo_relationship.should be_nil
101
+ staff.destroy
102
+ staff.find_neo_relationship(end_node: :premises, type: :STAFF_OF).should be_nil
103
+ staff.find_neo_relationship(end_node: :group, type: :STAFF_OF).should be_nil
104
+ staff.find_neo_relationship(type: :VISITOR_OF).should be_nil
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,36 @@
1
+ require "neomirror"
2
+ require "active_record"
3
+ require "database_cleaner"
4
+ require "factory_girl"
5
+
6
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
7
+ ENV['NEO_URL'] ||= "http://127.0.0.1:7474"
8
+ Neomirror.connection = Neography::Rest.new(ENV['NEO_URL'])
9
+
10
+ ActiveRecord::Base.connection.execute('CREATE TABLE premises ("id" INTEGER PRIMARY KEY NOT NULL)')
11
+ ActiveRecord::Base.connection.execute('CREATE TABLE groups ("id" INTEGER PRIMARY KEY NOT NULL)')
12
+ ActiveRecord::Base.connection.execute('CREATE TABLE users ("id" INTEGER PRIMARY KEY NOT NULL, "name" varchar(255) NOT NULL)')
13
+ ActiveRecord::Base.connection.execute('CREATE TABLE memberships ("id" INTEGER PRIMARY KEY NOT NULL, "premises_id" INTEGER DEFAULT NULL, "group_id" INTEGER DEFAULT NULL)')
14
+ ActiveRecord::Base.connection.execute('CREATE TABLE staff ("id" INTEGER PRIMARY KEY NOT NULL, "user_id" INTEGER DEFAULT NULL, "premises_id" INTEGER DEFAULT NULL, "group_id" INTEGER DEFAULT NULL, "roles" TEXT)')
15
+
16
+ FactoryGirl.find_definitions
17
+
18
+ Dir["./spec/support/**/*.rb"].sort.each {|f| require f}
19
+
20
+ RSpec.configure do |config|
21
+ config.include FactoryGirl::Syntax::Methods
22
+
23
+ config.before(:suite) do
24
+ DatabaseCleaner.strategy = :transaction
25
+ DatabaseCleaner.clean_with(:truncation)
26
+ end
27
+
28
+ config.before(:each) do
29
+ DatabaseCleaner.start
30
+ Neomirror.neo.execute_query("START n=node(*) OPTIONAL MATCH n-[r]-() DELETE n,r")
31
+ end
32
+
33
+ config.after(:each) do
34
+ DatabaseCleaner.clean
35
+ end
36
+ end
@@ -0,0 +1,9 @@
1
+ class Group < ActiveRecord::Base
2
+ include Neomirror::Node
3
+ has_many :memberships
4
+ has_many :premises, through: :memberships
5
+ has_many :staff
6
+ has_many :users, through: :staff
7
+
8
+ mirror_neo_node
9
+ end
@@ -0,0 +1,7 @@
1
+ class Membership < ActiveRecord::Base
2
+ include Neomirror::Relationship
3
+ belongs_to :premises
4
+ belongs_to :group
5
+
6
+ mirror_neo_relationship start_node: :premises, end_node: :group, type: :MEMBER_OF
7
+ end
@@ -0,0 +1,8 @@
1
+ class Postcode
2
+ attr_accessor :code
3
+ include Neomirror::Node
4
+
5
+ self.node_primary_key = :code
6
+
7
+ mirror_neo_node
8
+ end
@@ -0,0 +1,9 @@
1
+ class Premises < ActiveRecord::Base
2
+ include Neomirror::Node
3
+ has_many :memberships
4
+ has_many :groups, through: :memberships
5
+ has_many :staff
6
+ has_many :users, through: :staff
7
+
8
+ mirror_neo_node
9
+ end
@@ -0,0 +1,21 @@
1
+ ActiveSupport::Inflector.inflections do |inflect|
2
+ inflect.uncountable 'staff', 'staff'
3
+ end
4
+
5
+ class Staff < ActiveRecord::Base
6
+ include Neomirror::Relationship
7
+ belongs_to :user
8
+ belongs_to :premises
9
+ belongs_to :group
10
+ serialize :roles, Array
11
+
12
+ mirror_neo_relationship start_node: :user, end_node: :premises, type: :STAFF_OF do
13
+ property :roles, ->(record) { record.roles.to_a }
14
+ end
15
+ mirror_neo_relationship start_node: :user, end_node: :group, type: :STAFF_OF do
16
+ property :roles
17
+ end
18
+
19
+ mirror_neo_relationship start_node: :user, end_node: :premises, type: :MANAGER_OF, if: ->(r) { r.roles.include?('manager') }
20
+ mirror_neo_relationship start_node: :user, end_node: :premises, type: :VISITOR_OF, if: ->(r) { r.roles.include?('visitor') }
21
+ end
@@ -0,0 +1,10 @@
1
+ class User < ActiveRecord::Base
2
+ include Neomirror::Node
3
+ has_many :staff
4
+ has_many :premises, through: :staff
5
+ has_many :groups, through: :staff
6
+
7
+ mirror_neo_node do
8
+ property :name
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,213 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: neomirror
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alex Avoyants
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: neography
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.5.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.5.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
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: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '2.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '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'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activerecord
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: activesupport
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: factory_girl
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: database_cleaner
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: 1.2.0
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: 1.2.0
139
+ description: Lightweight but flexible gem that allows reflect some of data from relational
140
+ database into neo4j. This allows to perform faster and easier search of your models
141
+ ids or it can be first step of migrating application data to neo4j.
142
+ email:
143
+ - shhavel@gmail.com
144
+ executables: []
145
+ extensions: []
146
+ extra_rdoc_files: []
147
+ files:
148
+ - .gitignore
149
+ - .rspec
150
+ - Gemfile
151
+ - LICENSE.txt
152
+ - README.md
153
+ - Rakefile
154
+ - lib/neomirror.rb
155
+ - lib/neomirror/node.rb
156
+ - lib/neomirror/property_collector.rb
157
+ - lib/neomirror/relationship.rb
158
+ - lib/neomirror/version.rb
159
+ - neomirror.gemspec
160
+ - spec/factories/group.rb
161
+ - spec/factories/membership.rb
162
+ - spec/factories/premises.rb
163
+ - spec/factories/staff.rb
164
+ - spec/factories/user.rb
165
+ - spec/neomirror/node_spec.rb
166
+ - spec/neomirror/relationship_spec.rb
167
+ - spec/spec_helper.rb
168
+ - spec/support/group.rb
169
+ - spec/support/membership.rb
170
+ - spec/support/postcode.rb
171
+ - spec/support/premises.rb
172
+ - spec/support/staff.rb
173
+ - spec/support/user.rb
174
+ homepage: ''
175
+ licenses:
176
+ - MIT
177
+ metadata: {}
178
+ post_install_message:
179
+ rdoc_options: []
180
+ require_paths:
181
+ - lib
182
+ required_ruby_version: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - '>='
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ required_rubygems_version: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - '>='
190
+ - !ruby/object:Gem::Version
191
+ version: '0'
192
+ requirements: []
193
+ rubyforge_project:
194
+ rubygems_version: 2.2.2
195
+ signing_key:
196
+ specification_version: 4
197
+ summary: Lightweight but flexible gem that allows reflect some of data from relational
198
+ database into neo4j.
199
+ test_files:
200
+ - spec/factories/group.rb
201
+ - spec/factories/membership.rb
202
+ - spec/factories/premises.rb
203
+ - spec/factories/staff.rb
204
+ - spec/factories/user.rb
205
+ - spec/neomirror/node_spec.rb
206
+ - spec/neomirror/relationship_spec.rb
207
+ - spec/spec_helper.rb
208
+ - spec/support/group.rb
209
+ - spec/support/membership.rb
210
+ - spec/support/postcode.rb
211
+ - spec/support/premises.rb
212
+ - spec/support/staff.rb
213
+ - spec/support/user.rb