neomirror 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +195 -0
- data/Rakefile +8 -0
- data/lib/neomirror/node.rb +83 -0
- data/lib/neomirror/property_collector.rb +28 -0
- data/lib/neomirror/relationship.rb +103 -0
- data/lib/neomirror/version.rb +3 -0
- data/lib/neomirror.rb +12 -0
- data/neomirror.gemspec +30 -0
- data/spec/factories/group.rb +3 -0
- data/spec/factories/membership.rb +3 -0
- data/spec/factories/premises.rb +3 -0
- data/spec/factories/staff.rb +5 -0
- data/spec/factories/user.rb +5 -0
- data/spec/neomirror/node_spec.rb +78 -0
- data/spec/neomirror/relationship_spec.rb +107 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/group.rb +9 -0
- data/spec/support/membership.rb +7 -0
- data/spec/support/postcode.rb +8 -0
- data/spec/support/premises.rb +9 -0
- data/spec/support/staff.rb +21 -0
- data/spec/support/user.rb +10 -0
- metadata +213 -0
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
data/.rspec
ADDED
data/Gemfile
ADDED
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,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
|
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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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,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
|
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
|