pacer-jogger 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.markdown +4 -0
- data/README.markdown +80 -21
- data/VERSION +1 -1
- data/lib/jogger.rb +7 -6
- data/pacer-jogger.gemspec +4 -3
- data/spec/lib/jogger_spec.rb +13 -1
- metadata +40 -14
data/CHANGELOG.markdown
ADDED
data/README.markdown
CHANGED
@@ -1,31 +1,32 @@
|
|
1
|
-
|
1
|
+
Jogger - almost like named_scopes
|
2
|
+
=================================
|
2
3
|
|
3
|
-
Jogger is a JRuby library that enables lazy people to do very expressive graph traversals with the great [pacer gem](https://github.com/pangloss/pacer). If you don't know what the pacer gem is, you should probably not be here and check pacer out first.
|
4
|
+
Jogger is a JRuby library that enables lazy people to do very expressive graph traversals with the great [pacer gem](https://github.com/pangloss/pacer). If you don't know what the pacer gem is, you should probably not be here and check pacer out first. And don't miss the pacer section at the end of text.
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
Remember the _named scopes_ from back in the days when you were using rails? They were handy, weren't they? Jogger gives you _named traversals_ and is a little bit like named scopes. Jogger groups multiple pacer traversals together and give them a name. Pacer traversals are are like pipes. What are pipes? [Pipes are great!](http://markorodriguez.com/2011/08/03/on-the-nature-of-pipes/)!
|
6
|
+
Remember the _named_scopes_ from back in the days when you were using rails? Jogger gives you _named traversals_ and is a little bit like named scopes. Jogger groups multiple pacer traversals together and give them a name. Pacer traversals are are like pipes. What are pipes? [Pipes are great!](http://markorodriguez.com/2011/08/03/on-the-nature-of-pipes/)!
|
8
7
|
|
9
8
|
The most important conceptual difference is, that the order in which named traversals are called matter, while it usually doesn't matter in which order you call named scopes.
|
10
9
|
|
11
|
-
Jogger does two things:
|
10
|
+
TL; DR: Jogger does two things:
|
11
|
+
=======================
|
12
12
|
|
13
13
|
1. Keep the current pacer traversal in an instance variable and allow for method chaining as well as changing the internal state of the traversal
|
14
14
|
2. Allows you to group together parts of a traversal (single pipes or groups of them) and give them a name. Named traversals. Helps to stay DRY.
|
15
15
|
|
16
|
-
The former is really just a syntax thing, whereas the latter can help you a great deal modeling semantics of your business logic as parts of traversals.
|
16
|
+
The former is really just a syntax thing, whereas the latter can help you a great deal modeling semantics of your business logic as parts of traversals. Let's jog!
|
17
17
|
|
18
|
-
![
|
18
|
+
![Run](http://dl.dropbox.com/u/1953503/gifs/vizPZ.gif)
|
19
19
|
|
20
20
|
|
21
|
-
|
21
|
+
Feature #1: keep the current traversal
|
22
|
+
--------------------------------------
|
22
23
|
|
23
24
|
To demonstrate why point 1) in the list above can be useful, look at this traversal. It helps me find out what movies my female friends like the most, so I can impress them in a conversation:
|
24
25
|
|
25
26
|
t = my_pacer_vertex.in(:friends)
|
26
|
-
t = t.filter
|
27
|
+
t = t.filter(gender: 'female')
|
27
28
|
t = t.out(:likes)
|
28
|
-
t = t.filter
|
29
|
+
t = t.filter(type: 'Movie')
|
29
30
|
t = t.sort_by{ |v, c| -c }
|
30
31
|
t = t.group_count{ |v| v }
|
31
32
|
|
@@ -35,19 +36,20 @@ So here's the Jogger way of expressing this:
|
|
35
36
|
|
36
37
|
t = Jogger.new(my_pacer_vertex)
|
37
38
|
t.in(:friends)
|
38
|
-
t.filter
|
39
|
+
t.filter(gender: 'female')
|
39
40
|
t.out(:likes)
|
40
|
-
t.filter
|
41
|
+
t.filter(type: 'Movie')
|
41
42
|
t.sort_by{ |v, c| -c }
|
42
43
|
t.group_count{ |v| v }
|
43
44
|
|
44
45
|
See what I did there? Jogger keeps the current pacer traversal and forwards all method calls to that traversal, and then returns itself. So you could also write (in jogger as well as pacer):
|
45
46
|
|
46
|
-
Jogger.new(my_pacer_node).in(:friends).filter
|
47
|
+
Jogger.new(my_pacer_node).in(:friends).filter(…).out(:likes).group_count{…}
|
47
48
|
|
48
49
|
Just saying, you can chain your methods, but I don't like it cause I can only focus on 72 characters per line at max. If you want the current traversal, just call `result` on your Jogger instance.
|
49
50
|
|
50
|
-
|
51
|
+
Feature #2: Named Traversals
|
52
|
+
----------------------------
|
51
53
|
|
52
54
|
So that traversal above, traversing from a node to all its friends, is pretty simple, but it could be simpler. Especially if it does things that you want to reuse in many other places. How cool would it be if I just had to write this:
|
53
55
|
|
@@ -55,7 +57,7 @@ So that traversal above, traversing from a node to all its friends, is pretty si
|
|
55
57
|
t.friends(:female)
|
56
58
|
t.top_list(:movies)
|
57
59
|
|
58
|
-
No problem. Just define named traversals that aggregate different pipes and give them a name
|
60
|
+
No problem. Just define named traversals that aggregate different pipes and give them a name. You have to put your traversal into Jogger's `NamedTraverals` module.
|
59
61
|
|
60
62
|
class Jogger
|
61
63
|
module NamedTraversals
|
@@ -63,21 +65,24 @@ No problem. Just define named traversals that aggregate different pipes and give
|
|
63
65
|
# Traverse to somebody's woman friends
|
64
66
|
def self.friends(current_traversal, gender)
|
65
67
|
t = current_traversal.in(:friends)
|
66
|
-
t = t.filter
|
68
|
+
t = t.filter(gender: gender)
|
67
69
|
end
|
68
70
|
|
69
71
|
# Group and sort
|
70
72
|
def self.top_list(current_traversal, type)
|
71
73
|
t = current_traversal.out(type)
|
72
|
-
t = t.filter
|
74
|
+
t = t.filter(type: 'Movie')
|
73
75
|
t = t.group_count{ |v| v }
|
74
76
|
end
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
80
|
+
Your methods have to be able to take at least one parameter: the current traversal. It represents the current traversal state of your `Jogger` instance. Your traversal can then modify this traversal and must return it. It will become your Jogger instance's new state.
|
81
|
+
|
78
82
|
These are silly examples, but if you look at your traversals I guarantee that you will find repeated patterns all over the place, and Jogger can help you stop repeating these and making the actual traversals much easier on the eyes.
|
79
83
|
|
80
|
-
|
84
|
+
Installation
|
85
|
+
============
|
81
86
|
|
82
87
|
First, you need to load pacer and whatever graph db connector you need (we use neo4j, by the way) and define your named traversals as above. Jogger doesn't include these on purpose. Then, you have to
|
83
88
|
|
@@ -93,10 +98,64 @@ or for your Gemfile
|
|
93
98
|
|
94
99
|
That's it!
|
95
100
|
|
96
|
-
|
101
|
+
Documentation
|
102
|
+
=============
|
97
103
|
|
98
104
|
I gave YARD a shot, so to open the documentation in your browser just do this in the jogger directory:
|
99
105
|
|
100
106
|
yard server & sleep 3 && open http://localhost:8808/docs/file/README.markdown
|
101
107
|
|
102
|
-
Or you can
|
108
|
+
Or you can [browse the documentation online](http://rubydoc.info/github/jayniz/jogger/master/frames)
|
109
|
+
|
110
|
+
Named traversals - The pacer way
|
111
|
+
================================
|
112
|
+
|
113
|
+
You can implement feature #2 purely in pacer, if you like. For example, you could express
|
114
|
+
|
115
|
+
my_node.out.filter(type: 'Movie')
|
116
|
+
|
117
|
+
with
|
118
|
+
|
119
|
+
my_node.out(Movie)
|
120
|
+
|
121
|
+
For this to work you need to tell pacer, what `Movie` actually means. In the simplest form, you could say:
|
122
|
+
|
123
|
+
module Movie
|
124
|
+
def self.route_conditions
|
125
|
+
{ type: 'Movie' }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
That way pacer knows, whenever you're talking about a `Movie` you want to filter those elements whose `type==Movie`. To do something like the named traversals described above, you could go ahead and define a route to all female likers of a movie:
|
130
|
+
|
131
|
+
module Girl
|
132
|
+
def self.route_conditions
|
133
|
+
{gender: :female}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
module Movie
|
138
|
+
def self.route_conditions
|
139
|
+
{ type: 'Movie' }
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
module Route
|
144
|
+
def female_likers
|
145
|
+
self.in_e(:likes).in_v(Girl)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
You can then go ahead and fetch:
|
151
|
+
|
152
|
+
t.out(Movie).female_likers
|
153
|
+
|
154
|
+
To wrap it up, using Jogger to do named traversals is for the super lazy. If you use pacer exclusively and have more complicated structures it would probably make more sense to create your named traversals in the design of your domain logic and do it purely with pacer. You can still use Jogger to traverse these routes. If you want to share common traversal patterns between different models it might be easier to do with Jogger.
|
155
|
+
|
156
|
+
License
|
157
|
+
=======
|
158
|
+
|
159
|
+
Jogger is released under the MIT license:
|
160
|
+
|
161
|
+
* http://www.opensource.org/licenses/MIT
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/lib/jogger.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
class UnknownTraversalError < ArgumentError; end;
|
1
2
|
|
2
3
|
# Allows to formulate traversals by using predefined
|
3
4
|
# named traversals. Also allows for method chaining.
|
@@ -48,12 +49,12 @@ class Jogger
|
|
48
49
|
def method_missing(method, *args, &block)
|
49
50
|
begin
|
50
51
|
traversal_args = [method, args].flatten.compact
|
51
|
-
@current_traversal = Jogger.traverse(@current_traversal,
|
52
|
-
rescue
|
52
|
+
@current_traversal = Jogger.traverse(@current_traversal, method, args)
|
53
|
+
rescue UnknownTraversalError => a
|
53
54
|
begin
|
54
55
|
@current_traversal = @current_traversal.send(method, *args, &block)
|
55
|
-
rescue NoMethodError
|
56
|
-
raise "Unknown traversal #{method}"
|
56
|
+
rescue NoMethodError => m
|
57
|
+
raise "Unknown traversal #{method}. From (#{a}) via (#{m}) (method_missing rocks)"
|
57
58
|
end
|
58
59
|
end
|
59
60
|
self
|
@@ -70,8 +71,8 @@ class Jogger
|
|
70
71
|
# @param opts [Object] Whatever you want to pass to the named traverser
|
71
72
|
# @return [Object] The result of the traversal.
|
72
73
|
def self.traverse(traversal_base, named_traversal, opts = nil)
|
73
|
-
raise
|
74
|
-
args = [named_traversal, traversal_base] + [opts].compact
|
74
|
+
raise UnknownTraversalError, "Unknown traversal #{named_traversal}" unless valid_traversal?(named_traversal)
|
75
|
+
args = [named_traversal, traversal_base] + [*opts].compact
|
75
76
|
Jogger::NamedTraversals.send(*args)
|
76
77
|
end
|
77
78
|
|
data/pacer-jogger.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "pacer-jogger"
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jannis Hermanns"]
|
12
|
-
s.date = "2012-
|
12
|
+
s.date = "2012-09-14"
|
13
13
|
s.description = "Allows to group traversal fragments/pipes to named traversals and call them like they were pacer pipes."
|
14
14
|
s.email = "jannis@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
|
+
"CHANGELOG.markdown",
|
21
22
|
"Gemfile",
|
22
23
|
"Gemfile.lock",
|
23
24
|
"LICENSE.txt",
|
@@ -32,7 +33,7 @@ Gem::Specification.new do |s|
|
|
32
33
|
s.homepage = "http://github.com/jayniz/jogger"
|
33
34
|
s.licenses = ["MIT"]
|
34
35
|
s.require_paths = ["lib"]
|
35
|
-
s.rubygems_version = "1.8.
|
36
|
+
s.rubygems_version = "1.8.24"
|
36
37
|
s.summary = "Pacer traversals for lazy people"
|
37
38
|
|
38
39
|
if s.respond_to? :specification_version then
|
data/spec/lib/jogger_spec.rb
CHANGED
@@ -15,10 +15,16 @@ class Jogger
|
|
15
15
|
(current_traversal || 1) * x
|
16
16
|
end
|
17
17
|
|
18
|
+
# A traversal with no args
|
18
19
|
def self.no_argument_traverser(current_traversal)
|
19
20
|
:worked
|
20
21
|
end
|
21
22
|
|
23
|
+
# A traversal with two args
|
24
|
+
def self.two_argument_traverser(current_traversal, x, y)
|
25
|
+
x+y
|
26
|
+
end
|
27
|
+
|
22
28
|
def self.method_missing_dummy(current_traversal)
|
23
29
|
MyFakedPacerResult.new
|
24
30
|
end
|
@@ -45,13 +51,19 @@ describe Jogger do
|
|
45
51
|
lambda do
|
46
52
|
p = Jogger.new
|
47
53
|
p.this_does_not_exist
|
48
|
-
end.should raise_error("Unknown traversal this_does_not_exist")
|
54
|
+
end.should raise_error("Unknown traversal this_does_not_exist. From (Unknown traversal this_does_not_exist) via (undefined method `this_does_not_exist' for nil:NilClass) (method_missing rocks)")
|
49
55
|
end
|
50
56
|
|
51
57
|
it "works for traversals without arguments" do
|
52
58
|
Jogger.new.no_argument_traverser.result.should == :worked
|
53
59
|
end
|
54
60
|
|
61
|
+
it "works for traversals with two arguments" do
|
62
|
+
p = Jogger.new
|
63
|
+
p.two_argument_traverser(3,7)
|
64
|
+
p.result.should == 10
|
65
|
+
end
|
66
|
+
|
55
67
|
it "delegates method calls to the current traversal" do
|
56
68
|
Jogger.new.method_missing_dummy.some_existing_pacer_method.result.should == :is_there
|
57
69
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pacer-jogger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: shoulda
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: bundler
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ~>
|
@@ -32,10 +37,15 @@ dependencies:
|
|
32
37
|
version: 1.0.0
|
33
38
|
type: :development
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.0.0
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: jeweler
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
51
|
- - ~>
|
@@ -43,10 +53,15 @@ dependencies:
|
|
43
53
|
version: 1.7.0
|
44
54
|
type: :development
|
45
55
|
prerelease: false
|
46
|
-
version_requirements:
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.7.0
|
47
62
|
- !ruby/object:Gem::Dependency
|
48
63
|
name: rspec
|
49
|
-
requirement:
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
50
65
|
none: false
|
51
66
|
requirements:
|
52
67
|
- - ! '>='
|
@@ -54,10 +69,15 @@ dependencies:
|
|
54
69
|
version: '0'
|
55
70
|
type: :development
|
56
71
|
prerelease: false
|
57
|
-
version_requirements:
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
58
78
|
- !ruby/object:Gem::Dependency
|
59
79
|
name: yard
|
60
|
-
requirement:
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
61
81
|
none: false
|
62
82
|
requirements:
|
63
83
|
- - ! '>='
|
@@ -65,7 +85,12 @@ dependencies:
|
|
65
85
|
version: '0'
|
66
86
|
type: :development
|
67
87
|
prerelease: false
|
68
|
-
version_requirements:
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
69
94
|
description: Allows to group traversal fragments/pipes to named traversals and call
|
70
95
|
them like they were pacer pipes.
|
71
96
|
email: jannis@gmail.com
|
@@ -76,6 +101,7 @@ extra_rdoc_files:
|
|
76
101
|
- README.markdown
|
77
102
|
files:
|
78
103
|
- .document
|
104
|
+
- CHANGELOG.markdown
|
79
105
|
- Gemfile
|
80
106
|
- Gemfile.lock
|
81
107
|
- LICENSE.txt
|
@@ -101,7 +127,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
127
|
version: '0'
|
102
128
|
segments:
|
103
129
|
- 0
|
104
|
-
hash:
|
130
|
+
hash: -3862290704607932107
|
105
131
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
132
|
none: false
|
107
133
|
requirements:
|
@@ -110,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
110
136
|
version: '0'
|
111
137
|
requirements: []
|
112
138
|
rubyforge_project:
|
113
|
-
rubygems_version: 1.8.
|
139
|
+
rubygems_version: 1.8.24
|
114
140
|
signing_key:
|
115
141
|
specification_version: 3
|
116
142
|
summary: Pacer traversals for lazy people
|