graphable 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.rvmrc +2 -0
- data/Gemfile +4 -0
- data/Rakefile +7 -0
- data/graphable.gemspec +21 -0
- data/lib/graphable.rb +85 -0
- data/lib/graphable/edge_creator.rb +102 -0
- data/lib/graphable/index_creator.rb +26 -0
- data/lib/graphable/node_creator.rb +23 -0
- data/lib/graphable/version.rb +3 -0
- metadata +79 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/graphable.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/graphable/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Joe Fredette"]
|
6
|
+
gem.email = ["jfredett@gmail.com"]
|
7
|
+
gem.description = %q{A library for extracting static graph representations of data from rails-y databases}
|
8
|
+
gem.summary = %q{A library for extracting static graph representations of data from rails-y databases}
|
9
|
+
gem.homepage = "http://www.github.com/jfredett/graphable"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "graphable"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
|
17
|
+
gem.add_dependency "active_support"
|
18
|
+
gem.add_dependency "neography"
|
19
|
+
|
20
|
+
gem.version = Graphable::VERSION
|
21
|
+
end
|
data/lib/graphable.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
Dir["./lib/graphable/*.rb"].each do |f| require f end
|
3
|
+
|
4
|
+
|
5
|
+
module Graphable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
Graphable.register NodeCreator.new(self)
|
10
|
+
indexes :id
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.register(registrant)
|
14
|
+
registry << registrant
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.neo
|
18
|
+
Neography::Rest.new(ENV["NEO4J_URL"] || "http://localhost:7474")
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.registry
|
22
|
+
@registry ||= []
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.build!
|
26
|
+
puts "Building Graph"
|
27
|
+
registry.select { |f| f.is_a? NodeCreator }.map(&:call)
|
28
|
+
registry.select { |f| f.is_a? IndexCreator }.map(&:call)
|
29
|
+
registry.select { |f| f.is_a? EdgeCreator }.map(&:call)
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.index_cache
|
34
|
+
@index_cache ||= {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.completed_indicies
|
38
|
+
@completed_indicies ||= {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.completed_index(klass, method)
|
42
|
+
completed_indicies[[klass, method]] = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.has_indexed?(klass, method)
|
46
|
+
completed_indicies[[klass,method]]
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
module InstanceMethods
|
52
|
+
def to_node
|
53
|
+
attributes.to_hash.tap do |hash|
|
54
|
+
hash.each { |k, _| hash.delete(k) if k.to_s =~ /_id$/ } #remove FKs
|
55
|
+
hash[:type] = self.class.name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module ClassMethods
|
61
|
+
def index_name
|
62
|
+
"#{name.downcase.pluralize}_index"
|
63
|
+
end
|
64
|
+
|
65
|
+
def indexes(*methods)
|
66
|
+
methods.each do |method|
|
67
|
+
Graphable.register IndexCreator.new(self, method)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def has_edge(hash = {}, &block)
|
72
|
+
source_klass = self
|
73
|
+
target_klass = hash[:to]
|
74
|
+
hash[:block] = block
|
75
|
+
if hash.has_key?(:through)
|
76
|
+
Graphable.register EdgeCreator.through(source_klass, target_klass, hash.delete(:through), hash)
|
77
|
+
elsif hash.has_key?(:via)
|
78
|
+
Graphable.register EdgeCreator.via(source_klass, target_klass, hash.delete(:via), hash)
|
79
|
+
else
|
80
|
+
raise "Invalid Edge type, must be :through or :via"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Graphable
|
4
|
+
class EdgeCreator
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def self.through(*args)
|
8
|
+
# for has_many :through relations
|
9
|
+
ThroughEdgeCreator.new(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.via(*args)
|
13
|
+
#for habtm/has_many relations
|
14
|
+
ViaEdgeCreator.new(*args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(source, target, method, opts)
|
18
|
+
@source = source
|
19
|
+
@target = target
|
20
|
+
@method = method
|
21
|
+
@name = opts[:name] || method
|
22
|
+
@metadata_proc = opts[:block]
|
23
|
+
@target_name = opts[:target_method]
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def load_node(n)
|
29
|
+
binding.pry unless n
|
30
|
+
Graphable.index_cache[n]
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_relationship(source_node, target_node, metadata = {})
|
34
|
+
# dodges an issue with neography, nil-values can't be sent up. that's
|
35
|
+
# okay, since trying to access a field which doesn't exist should be nil
|
36
|
+
# anyway.
|
37
|
+
metadata.reject! { |_,v| v.nil? }
|
38
|
+
|
39
|
+
Neography::Relationship.create(@name, source_node, target_node, metadata)
|
40
|
+
end
|
41
|
+
|
42
|
+
delegate :all => :@source
|
43
|
+
alias_method :sources, :all
|
44
|
+
|
45
|
+
def target_name
|
46
|
+
@target_name ||= @target.name.pluralize.downcase.to_sym
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class ThroughEdgeCreator < EdgeCreator
|
51
|
+
def call
|
52
|
+
puts "Building edges for #{@source.name} -> #{@target.name}"
|
53
|
+
sources.each_slice(100) do |slice|
|
54
|
+
slice.each do |source|
|
55
|
+
source_node = load_node(source)
|
56
|
+
|
57
|
+
intermediates_for(source).each do |intermediate_target|
|
58
|
+
metadata = @metadata_proc.call(intermediate_target) if @metadata_proc
|
59
|
+
metadata = intermediate_target.send(:edge_metadata) if intermediate_target.respond_to?(:edge_metadata)
|
60
|
+
|
61
|
+
target_node = load_node(intermediate_target.send(target_name)) rescue binding.pry
|
62
|
+
|
63
|
+
build_relationship(source_node, target_node, metadata || {})
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def targets_for(source)
|
70
|
+
res = intermediates_for(source).map(&target_name)
|
71
|
+
res = res.to_a if res.respond_to?(:to_a)
|
72
|
+
if res.respond_to?(:each) then res else [res] end
|
73
|
+
end
|
74
|
+
|
75
|
+
def intermediates_for(source)
|
76
|
+
source.send(@method)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class ViaEdgeCreator < EdgeCreator
|
81
|
+
def call
|
82
|
+
puts "Building edges for #{@source.name} -> #{@target.name}"
|
83
|
+
sources.each do |source|
|
84
|
+
source_node = load_node(source)
|
85
|
+
|
86
|
+
targets_for(source).each do |target|
|
87
|
+
metadata = @metadata_proc.call(target) if @metadata_proc
|
88
|
+
|
89
|
+
target_node = load_node(target) rescue binding.pry
|
90
|
+
build_relationship(source_node, target_node, metadata || {})
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def targets_for(source)
|
96
|
+
res = source.send(@method)
|
97
|
+
res = res.to_a if res.respond_to?(:to_a)
|
98
|
+
if res.respond_to?(:each) then res else [res] end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Graphable
|
2
|
+
class IndexCreator
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
attr_reader :klass
|
6
|
+
delegate [:name, :all, :index_name] => :@klass
|
7
|
+
|
8
|
+
def initialize(klass, method)
|
9
|
+
@klass = klass
|
10
|
+
@method = method
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
return if Graphable.has_indexed?(@klass, @method)
|
15
|
+
|
16
|
+
puts "Building index for #{name}"
|
17
|
+
Graphable.neo.create_node_index(index_name, 'fulltext')
|
18
|
+
|
19
|
+
all.to_a.each do |object|
|
20
|
+
Graphable.neo.add_node_to_index(index_name, @method, object.send(@method), Graphable.index_cache[object])
|
21
|
+
end
|
22
|
+
|
23
|
+
Graphable.completed_index(@klass, @method)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Graphable
|
4
|
+
class NodeCreator
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_reader :klass
|
8
|
+
delegate [:name, :all] => :@klass
|
9
|
+
|
10
|
+
def initialize(klass)
|
11
|
+
@klass = klass
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
puts "Building nodes for #{name}"
|
16
|
+
all.each_slice(100) do |slice|
|
17
|
+
slice.each do |object|
|
18
|
+
Graphable.index_cache[object] = Neography::Node.create(object.to_node)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: graphable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Joe Fredette
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: active_support
|
16
|
+
requirement: &2152759120 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2152759120
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: neography
|
27
|
+
requirement: &2152758480 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2152758480
|
36
|
+
description: A library for extracting static graph representations of data from rails-y
|
37
|
+
databases
|
38
|
+
email:
|
39
|
+
- jfredett@gmail.com
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- .rvmrc
|
46
|
+
- Gemfile
|
47
|
+
- Rakefile
|
48
|
+
- graphable.gemspec
|
49
|
+
- lib/graphable.rb
|
50
|
+
- lib/graphable/edge_creator.rb
|
51
|
+
- lib/graphable/index_creator.rb
|
52
|
+
- lib/graphable/node_creator.rb
|
53
|
+
- lib/graphable/version.rb
|
54
|
+
homepage: http://www.github.com/jfredett/graphable
|
55
|
+
licenses: []
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.8.17
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: A library for extracting static graph representations of data from rails-y
|
78
|
+
databases
|
79
|
+
test_files: []
|