pacer-jogger 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "shoulda"
5
+ gem "bundler", "~> 1.0.0"
6
+ gem "jeweler", "~> 1.7.0"
7
+ gem "rspec"
8
+ gem "yard"
9
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ git (1.2.5)
6
+ jeweler (1.7.0)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rdoc
11
+ json (1.6.5)
12
+ rake (0.9.2.2)
13
+ rdoc (3.12)
14
+ json (~> 1.4)
15
+ rspec (2.8.0)
16
+ rspec-core (~> 2.8.0)
17
+ rspec-expectations (~> 2.8.0)
18
+ rspec-mocks (~> 2.8.0)
19
+ rspec-core (2.8.0)
20
+ rspec-expectations (2.8.0)
21
+ diff-lcs (~> 1.1.2)
22
+ rspec-mocks (2.8.0)
23
+ shoulda (2.11.3)
24
+ yard (0.7.4)
25
+
26
+ PLATFORMS
27
+ ruby
28
+
29
+ DEPENDENCIES
30
+ bundler (~> 1.0.0)
31
+ jeweler (~> 1.7.0)
32
+ rspec
33
+ shoulda
34
+ yard
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jannis Hermanns
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,90 @@
1
+ # Jogger
2
+
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
+
5
+ # What does it do?
6
+
7
+ Jogger does two things:
8
+
9
+ 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
10
+ 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.
11
+
12
+ 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. These sentences confuse me, so I will give you examples.
13
+
14
+ # Example time!
15
+
16
+ ## 1. Keep track of the current traversal
17
+
18
+ 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:
19
+
20
+ t = my_pacer_vertex.in(:friends)
21
+ t = t.filter{ |v| v.properties['gender'] == 'female}
22
+ t = t.out(:likes)
23
+ t = t.filter{ |v| v.properties['type'] == 'Movie' }
24
+ t = t.group_count{ |v| v }
25
+ t = t.sort_by{ |v, c| -c }
26
+
27
+ Since I'm a very lazy person, I would prefer to write it a little shorter. Especially, since these multi step traversals are a pattern I found in our code at [moviepilot.com](http://moviepilot.com)) a lot.
28
+
29
+ So here's the Jogger way of expressing the same traversal:
30
+
31
+ t = Jogger.new(my_pacer_vertex)
32
+ t.in(:friends)
33
+ t.filter{ |v| v.properties['gender'] == 'female' }
34
+ t.out(:likes)
35
+ t.filter{ |v| v.properties['type'] == 'Movie' }
36
+ t.group_count{ |v| v }
37
+ t.sort_by{ |v, c| -c }
38
+
39
+ 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):
40
+
41
+ Jogger.new(my_pacer_node).in(:friends).filter{ … }.out(:likes).group_count{…}
42
+
43
+ Just saying, you can chain your methods, but I don't like it because I can only focus on 72 characters per line at max. If you want to access the current traversal, just call `result` on your Jogger instance.
44
+
45
+ ## 2. Named traversals
46
+
47
+ 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. If you would explain the traveral to somebody, you'd say something along the lines of "The most popular movies amongst my friends, but only girls". How cool would it be if I just had to write this:
48
+
49
+ t = Jogger.new(my_pacer_vertex)
50
+ t.friends(:female)
51
+ t.top_list(:movies)
52
+
53
+ No problem. Just define a few named traversals that do exactly this.
54
+
55
+ class Jogger
56
+ module NamedTraversals
57
+
58
+ # Traverse to somebody's woman friends
59
+ def self.friends(current_traversal, gender)
60
+ t = current_traversal.in(:friends)
61
+ t = t.filter{|v| v.properties['gender'] == gender}
62
+ end
63
+
64
+ # Group and sort
65
+ def self.top_list(current_traversal, type)
66
+ t = current_traversal.out(type)
67
+ t = t.filter{ |v| v.properties['type'] == 'Movie' }
68
+ t = t.group_count{ |v| v }
69
+ t = t.sort_by{ |v, c| -c }
70
+ end
71
+ end
72
+ end
73
+
74
+ 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.
75
+
76
+ # Installation
77
+
78
+ First, you need to load pacer and whatever graph db connector you need (we use pacer-neo4j, by the way) and define your named traversals as above. Jogger doesn't include these on purpose. Then, you have to:
79
+
80
+ gem install pacer-jogger
81
+
82
+ and
83
+
84
+ require 'jogger'
85
+
86
+ or if you're using bundler, add this to your Gemfile and bundle:
87
+
88
+ gem "pacer-jogger", :require => "jogger"
89
+
90
+ That's it!
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+ require 'yard'
14
+ require 'rspec/core/rake_task'
15
+
16
+ require 'jeweler'
17
+ Jeweler::Tasks.new do |gem|
18
+ gem.name = "pacer-jogger"
19
+ gem.homepage = "http://github.com/jayniz/jogger"
20
+ gem.license = "MIT"
21
+ gem.summary = %Q{Pacer traversals for lazy people}
22
+ gem.description = %Q{Allows to group traversal fragments/pipes to named traversals and call them like they were pacer pipes.}
23
+ gem.email = "jannis@gmail.com"
24
+ gem.authors = ["Jannis Hermanns"]
25
+ # dependencies defined in Gemfile
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ #
37
+ # RSpec
38
+ #
39
+ task :default => [:spec]
40
+ task :test => [:spec]
41
+ desc "run spec tests"
42
+ RSpec::Core::RakeTask.new('spec') do |t|
43
+ t.pattern = 'spec/**/*_spec.rb'
44
+ end
45
+
46
+
47
+ #
48
+ # Yard
49
+ #
50
+ desc 'Generate documentation'
51
+ YARD::Rake::YardocTask.new do |t|
52
+ t.files = ['lib/**/*.rb', '-', 'LICENSE']
53
+ t.options = ['--main', 'README.md', '--no-private']
54
+ end
55
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/lib/jogger.rb ADDED
@@ -0,0 +1,80 @@
1
+
2
+ # Allows to formulate traversals by using predefined
3
+ # named traversals. Also allows for method chaining.
4
+ #
5
+ # All named traversals are defined in {PacerTraversal::NamedTraversals}
6
+ # and new ones can be added using {Jogger.add_traversal}.
7
+ #
8
+ # Instances have a @current_traversal variable that is
9
+ # updated with each chained call of {#traverse}.
10
+ #
11
+ # Beware, it also uses method missing to delegate unknown methods to
12
+ # the current traversal. So after you're done chaining things, you can
13
+ # do more stuff with it after you're done, e.g. call count on it:
14
+ #
15
+ # t = Jogger.new(some_node)
16
+ # t.traverse(:some).traverse(:where).count
17
+ #
18
+ # So everything except for {#traverse} is called on the
19
+ # @current_traversal
20
+ class Jogger
21
+
22
+
23
+ # @param initial_node [Object] A node to start out with. Can also be
24
+ # the result of a prior traversal you did outside of this class
25
+ def initialize(initial_node = nil)
26
+ @current_traversal = initial_node
27
+ end
28
+
29
+ # @return [Hash] The current state of the traversal as returned by pacer
30
+ def result
31
+ @current_traversal
32
+ end
33
+
34
+ # Runs the traversal with the same name as the called method on the
35
+ # current traversal and replaces the current traversal (== the state)
36
+ # with the results of the named traversal.
37
+ #
38
+ # If you call a method that is not a named traversal, the method call
39
+ # is delegated to the @current traversal. Still, this will return self.
40
+ # This is useful for more traversals after named routes.
41
+ #
42
+ # @return [Jogger] Returns itself so you can chain multiple
43
+ # {#traverse} calls
44
+ def method_missing(method, *args, &block)
45
+ begin
46
+ traversal_args = [method, args].flatten.compact
47
+ @current_traversal = Jogger.traverse(@current_traversal, *traversal_args)
48
+ rescue ArgumentError
49
+ begin
50
+ @current_traversal = @current_traversal.send(method, *args, &block)
51
+ rescue NoMethodError
52
+ raise "Unknown traversal #{method}"
53
+ end
54
+ end
55
+ self
56
+ end
57
+
58
+ # Runs a named traversal on a given traversal. For example, you
59
+ # could give it a start node as traversal base and a traversal named
60
+ # :go_to_all_subscribers to go to all subscribers from that node.
61
+ #
62
+ # @param traversal_base [Object] The result of a previous traversal
63
+ # or a pacer node you want to begin your traversal with
64
+ # @param named_traversal [Symbol] The name of the predefined traversal
65
+ # you want to run on the traversal_base
66
+ # @param opts [Object] Whatever you want to pass to the named traverser
67
+ # @return [Object] The result of the traversal.
68
+ def self.traverse(traversal_base, named_traversal, opts = nil)
69
+ raise ArgumentError, "Unknown traversal #{named_traversal}" unless valid_traversal?(named_traversal)
70
+ args = [named_traversal, traversal_base] + [opts].compact
71
+ Jogger::NamedTraversals.send(*args)
72
+ end
73
+
74
+ private
75
+
76
+ def self.valid_traversal?(traversal)
77
+ Jogger::NamedTraversals.respond_to? traversal
78
+ end
79
+
80
+ end
@@ -0,0 +1,62 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "pacer-jogger"
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jannis Hermanns"]
12
+ s.date = "2012-01-23"
13
+ s.description = "Allows to group traversal fragments/pipes to named traversals and call them like they were pacer pipes."
14
+ s.email = "jannis@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.markdown",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/jogger.rb",
28
+ "pacer-jogger.gemspec",
29
+ "spec/lib/jogger_spec.rb",
30
+ "spec/spec_helper.rb"
31
+ ]
32
+ s.homepage = "http://github.com/jayniz/jogger"
33
+ s.licenses = ["MIT"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = "1.8.10"
36
+ s.summary = "Pacer traversals for lazy people"
37
+
38
+ if s.respond_to? :specification_version then
39
+ s.specification_version = 3
40
+
41
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
42
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
43
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
44
+ s.add_development_dependency(%q<jeweler>, ["~> 1.7.0"])
45
+ s.add_development_dependency(%q<rspec>, [">= 0"])
46
+ s.add_development_dependency(%q<yard>, [">= 0"])
47
+ else
48
+ s.add_dependency(%q<shoulda>, [">= 0"])
49
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
50
+ s.add_dependency(%q<jeweler>, ["~> 1.7.0"])
51
+ s.add_dependency(%q<rspec>, [">= 0"])
52
+ s.add_dependency(%q<yard>, [">= 0"])
53
+ end
54
+ else
55
+ s.add_dependency(%q<shoulda>, [">= 0"])
56
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
57
+ s.add_dependency(%q<jeweler>, ["~> 1.7.0"])
58
+ s.add_dependency(%q<rspec>, [">= 0"])
59
+ s.add_dependency(%q<yard>, [">= 0"])
60
+ end
61
+ end
62
+
@@ -0,0 +1,58 @@
1
+ require_relative '../spec_helper'
2
+
3
+ class MyFakedPacerResult
4
+ def some_existing_pacer_method
5
+ :is_there
6
+ end
7
+ end
8
+
9
+ class Jogger
10
+ module NamedTraversals
11
+ # This just multiplies the current traversal with x.
12
+ # In a real use case you would take the current traversal,
13
+ # traverse some more, and yield the result of your traversal.
14
+ def self.test_traverser(current_traversal, x)
15
+ (current_traversal || 1) * x
16
+ end
17
+
18
+ def self.no_argument_traverser(current_traversal)
19
+ :worked
20
+ end
21
+
22
+ def self.method_missing_dummy(current_traversal)
23
+ MyFakedPacerResult.new
24
+ end
25
+
26
+ end
27
+ end
28
+
29
+ describe Jogger do
30
+
31
+
32
+ it "runs the named proc and replaces the current traversal with its result" do
33
+ p = Jogger.new
34
+ p.test_traverser(2)
35
+ p.result.should == 2
36
+ end
37
+
38
+ it "allows for method chaining" do
39
+ p = Jogger.new
40
+ p.test_traverser(2).test_traverser(3)
41
+ p.result.should == 6
42
+ end
43
+
44
+ it "doesn't allow to run a non existant traversal" do
45
+ lambda do
46
+ p = Jogger.new
47
+ p.this_does_not_exist
48
+ end.should raise_error("Unknown traversal this_does_not_exist")
49
+ end
50
+
51
+ it "works for traversals without arguments" do
52
+ Jogger.new.no_argument_traverser.result.should == :worked
53
+ end
54
+
55
+ it "delegates method calls to the current traversal" do
56
+ Jogger.new.method_missing_dummy.some_existing_pacer_method.result.should == :is_there
57
+ end
58
+ end
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+
3
+ Bundler.setup
4
+ Bundler.require :default, :test
5
+
6
+
7
+ require_relative '../lib/jogger'
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pacer-jogger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jannis Hermanns
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-23 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: shoulda
16
+ requirement: &70246624766620 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70246624766620
25
+ - !ruby/object:Gem::Dependency
26
+ name: bundler
27
+ requirement: &70246624765680 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70246624765680
36
+ - !ruby/object:Gem::Dependency
37
+ name: jeweler
38
+ requirement: &70246624765080 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.7.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70246624765080
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: &70246624764500 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70246624764500
58
+ - !ruby/object:Gem::Dependency
59
+ name: yard
60
+ requirement: &70246624763920 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70246624763920
69
+ description: Allows to group traversal fragments/pipes to named traversals and call
70
+ them like they were pacer pipes.
71
+ email: jannis@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files:
75
+ - LICENSE.txt
76
+ - README.markdown
77
+ files:
78
+ - .document
79
+ - Gemfile
80
+ - Gemfile.lock
81
+ - LICENSE.txt
82
+ - README.markdown
83
+ - Rakefile
84
+ - VERSION
85
+ - lib/jogger.rb
86
+ - pacer-jogger.gemspec
87
+ - spec/lib/jogger_spec.rb
88
+ - spec/spec_helper.rb
89
+ homepage: http://github.com/jayniz/jogger
90
+ licenses:
91
+ - MIT
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ segments:
103
+ - 0
104
+ hash: -4043812696808555855
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 1.8.10
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: Pacer traversals for lazy people
117
+ test_files: []