wolverine 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4c5fc98fc36182dede7420bff6d5cb3d472beae1
4
+ data.tar.gz: 1ba28844128a4f9684425a1661f93b0a3631fe08
5
+ SHA512:
6
+ metadata.gz: 9de7a79b487864c018a94ea8dd333b1d46f756dfffa4f2f33398413053d4746505717b50170218a124d61ce30bf45670d15af3f906f818fadd1f393dec3a4658
7
+ data.tar.gz: 3777478645a4f36720d46604e47722b671b2734a6d0d3a23c4a8a3edd163b4ca282399ef6f87e6660b407f4f8ec481773803835774fa3c911e5a15350528e170
data/README.md CHANGED
@@ -4,18 +4,14 @@ Wolverine is a simple library to allow you to manage and run redis server-side l
4
4
 
5
5
  Redis versions 2.6 and up allow lua scripts to be run on the server that execute atomically and very quickly.
6
6
 
7
- This is *extremely* useful.
8
-
9
7
  Wolverine is a wrapper around that functionality, to package it up in a format more familiar to a Rails codebase.
10
8
 
11
9
  ## How do I use it?
12
10
 
13
- 1) Make sure you have redis 2.6 or higher installed. As of now, that means compiling from source:
11
+ 1) Make sure you have redis 2.6 or higher installed.
14
12
 
15
- ```shell
16
- git clone https://github.com/antirez/redis.git
17
- cd redis && make
18
- ./src/redis-server
13
+ ```
14
+ redis-server -v
19
15
  ```
20
16
 
21
17
  2) Add wolverine to your Gemfile:
@@ -39,12 +35,56 @@ return exists
39
35
  4) Call wolverine from your code:
40
36
 
41
37
  ```ruby
42
- Wolverine.util.mexists('key1', 'key2', 'key3') #=> [0, 1, 0]
38
+ Wolverine.util.mexists(['key1', 'key2', 'key3']) #=> [0, 1, 0]
39
+ ```
40
+
41
+ Or
42
+
43
+ ```ruby
44
+ Wolverine.util.mexists(:keys => ['key1', 'key2', 'key3']) #=> [0, 1, 0]
43
45
  ```
44
46
 
45
47
  Methods are available on `Wolverine` paralleling the directory structure
46
48
  of wolverine's `script_path`.
47
49
 
50
+ #### Nested Lua Scripts
51
+
52
+ For lua scripts with shared code, Wolverine supports ERB style templating.
53
+
54
+ If your app has lua scripts at
55
+
56
+ - `app/wolverine/do_something.lua`
57
+ - `app/wolverine/do_something_else.lua`
58
+
59
+ that both have shared lua code, you can factor it out into a lua "partial":
60
+
61
+ - `app/wolverine/shared/_common.lua`
62
+
63
+ ```lua
64
+ -- app/wolverine/shared/_common.lua
65
+ local function complex_redis_command(key, value)
66
+ local dict = {}
67
+ dict[key] = value
68
+ end
69
+ ```
70
+
71
+ ```lua
72
+ -- app/wolverine/do_something.lua
73
+ <%= include_partial 'shared/_common.lua' %>
74
+ complex_redis_command("foo", "bar")
75
+ return true
76
+ ```
77
+
78
+ ```lua
79
+ -- app/wolverine/do_something_else.lua
80
+ <%= include_partial 'shared/_common.lua' %>
81
+ complex_redis_command("bar", "baz")
82
+ return false
83
+ ```
84
+
85
+ - partials are loaded relative to the `Wolverine.config.script_path` location
86
+ - partials can be protected by adding an underscore (eg. `shared/_common.lua`). This disallows EVAL access through `Wolverine.shared._common`
87
+
48
88
  ## Configuration
49
89
 
50
90
  Available configuration options:
@@ -59,13 +99,3 @@ If you want to override one or more of these, doing so in an initializer is reco
59
99
 
60
100
  For more information on scripting redis with lua, refer to redis' excellent documentation: http://redis.io/commands/eval
61
101
 
62
- ## License
63
-
64
- Copyright (C) 2012 [Shopify](http://shopify.com) by [Burke Libbey](http://burkelibbey.org)
65
-
66
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
67
-
68
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
69
-
70
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
71
-
@@ -23,6 +23,14 @@ class Wolverine
23
23
  config.redis
24
24
  end
25
25
 
26
+ def self.statsd_enabled?
27
+ @statsd_enabled
28
+ end
29
+
30
+ def self.enable_statsd!
31
+ @statsd_enabled = true
32
+ end
33
+
26
34
  # Resets all the scripts cached by Wolverine. Scripts are lazy-loaded and
27
35
  # cached in-memory, so if a file changes on disk, it will be necessary to
28
36
  # manually reset the cache using +reset!+.
@@ -30,6 +38,7 @@ class Wolverine
30
38
  # @return [void]
31
39
  def self.reset!
32
40
  @root_directory = nil
41
+ reset_cached_methods
33
42
  end
34
43
 
35
44
  # Used to handle dynamic accesses to scripts. Successful lookups will be
@@ -58,10 +67,17 @@ class Wolverine
58
67
 
59
68
  def reset!
60
69
  @root_directory = nil
70
+ reset_cached_methods
61
71
  end
62
72
 
63
73
  def method_missing sym, *args
64
- root_directory.send(sym, *args)
74
+ # Disallow access to protected partials (files that begin with _ character)
75
+ if sym[0] == '_'
76
+ super
77
+ else
78
+ root_directory.send(sym, *args)
79
+ end
80
+
65
81
  rescue PathComponent::MissingTemplate
66
82
  super
67
83
  end
@@ -69,11 +85,31 @@ class Wolverine
69
85
  private
70
86
 
71
87
  def self.root_directory
72
- @root_directory ||= PathComponent.new(config.script_path)
88
+ @root_directory ||= PathComponent.new(config.script_path, {:cache_to => self})
89
+ end
90
+
91
+ def self.cached_methods
92
+ @cached_methods ||= Hash.new
93
+ end
94
+
95
+ def self.reset_cached_methods
96
+ metaclass = class << self; self; end
97
+ cached_methods.each_key { |method| metaclass.send(:undef_method, method) }
98
+ cached_methods.clear
73
99
  end
74
100
 
75
101
  def root_directory
76
- @root_directory ||= PathComponent.new(config.script_path, {:config => config, :redis => redis})
102
+ @root_directory ||= PathComponent.new(config.script_path, {:cache_to => self, :config => config, :redis => redis})
103
+ end
104
+
105
+ def cached_methods
106
+ @cached_methods ||= Hash.new
107
+ end
108
+
109
+ def reset_cached_methods
110
+ metaclass = class << self; self; end
111
+ cached_methods.each_key { |method| metaclass.send(:undef_method, method) }
112
+ cached_methods.clear
77
113
  end
78
114
 
79
115
  end
@@ -18,6 +18,7 @@ class Wolverine
18
18
  def initialize path, options = {}
19
19
  @path = path
20
20
  @options = options
21
+ @cache_to = options[:cache_to]
21
22
  @redis = options[:redis] || Wolverine.redis
22
23
  @config = options[:config] || Wolverine.config
23
24
  end
@@ -53,15 +54,19 @@ class Wolverine
53
54
  end
54
55
 
55
56
  def define_directory_method path, sym
56
- dir = PathComponent.new(path, @options)
57
- define_metaclass_method(sym) { dir }
57
+ options = @options.merge({:cache_to => nil})
58
+ dir = PathComponent.new(path, options)
59
+ cb = proc { dir }
60
+ define_metaclass_method(sym, &cb)
61
+ cache_metaclass_method(sym, &cb)
58
62
  end
59
63
 
60
64
  def define_script_method path, sym, *args
61
- script = Wolverine::Script.new(path, @options)
62
- define_metaclass_method(sym) { |*args|
63
- script.call(@redis, *args)
64
- }
65
+ redis, options = @redis, @options.merge({:cache_to => nil})
66
+ script = Wolverine::Script.new(path, options)
67
+ cb = proc { |*args| script.call(redis, *args) }
68
+ define_metaclass_method(sym, &cb)
69
+ cache_metaclass_method(sym, &cb)
65
70
  end
66
71
 
67
72
  def define_metaclass_method sym, &block
@@ -69,6 +74,15 @@ class Wolverine
69
74
  metaclass.send(:define_method, sym, &block)
70
75
  end
71
76
 
77
+ def cache_metaclass_method sym, &block
78
+ unless @cache_to.nil?
79
+ metaclass = class << @cache_to; self; end
80
+ metaclass.send(:define_method, sym, &block)
81
+ cached_methods = @cache_to.send(:cached_methods)
82
+ cached_methods[sym] = self
83
+ end
84
+ end
85
+
72
86
  end
73
87
 
74
88
  end
@@ -1,6 +1,7 @@
1
1
  require 'pathname'
2
2
  require 'benchmark'
3
3
  require 'digest/sha1'
4
+ require 'erb'
4
5
 
5
6
  class Wolverine
6
7
  # {Script} represents a lua script in the filesystem. It loads the script
@@ -30,6 +31,7 @@ class Wolverine
30
31
  # @raise [LuaError] if the script failed to compile of encountered a
31
32
  # runtime error
32
33
  def call redis, *args
34
+ t = Time.now
33
35
  begin
34
36
  run_evalsha redis, *args
35
37
  rescue => e
@@ -41,19 +43,26 @@ class Wolverine
41
43
  else
42
44
  raise
43
45
  end
46
+ ensure
47
+ StatsD.measure(statsd_key, (Time.now - t) * 1000, 0.001) if Wolverine.statsd_enabled?
44
48
  end
45
49
 
46
50
  private
47
51
 
52
+ def statsd_key
53
+ k = @file.relative_path_from(Wolverine.config.script_path).to_s.sub!(/\.lua$/,'').gsub!(/\//,'.')
54
+ "Wolverine.#{k}"
55
+ end
56
+
48
57
  def run_evalsha redis, *args
49
58
  instrument :evalsha do
50
- redis.evalsha @digest, args.size, *args
59
+ redis.evalsha @digest, *args
51
60
  end
52
61
  end
53
62
 
54
63
  def run_eval redis, *args
55
64
  instrument :eval do
56
- redis.eval @content, args.size, *args
65
+ redis.eval @content, *args
57
66
  end
58
67
  end
59
68
 
@@ -69,7 +78,22 @@ class Wolverine
69
78
  end
70
79
 
71
80
  def load_lua file
72
- File.read file
81
+ TemplateContext.template(file)
82
+ end
83
+
84
+ class TemplateContext
85
+
86
+ def self.template(pathname)
87
+ ERB.new(File.read(pathname)).result binding
88
+ end
89
+
90
+ # helper method to include a lua partial within another lua script
91
+ #
92
+ # @param relative_path [String] the relative path to the script from
93
+ # `Wolverine.config.script_path`
94
+ def self.include_partial(relative_path)
95
+ template( Pathname.new("#{Wolverine.config.script_path}/#{relative_path}") )
96
+ end
73
97
  end
74
98
 
75
99
  end
@@ -1,3 +1,3 @@
1
1
  class Wolverine
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -7,10 +7,10 @@ class WolverineIntegrationTest < MiniTest::Unit::TestCase
7
7
  def mock_redis
8
8
  stub.tap do |redis|
9
9
  redis.expects(:evalsha).
10
- with('fe24f4dd4ba7881608cca4b846f009195e06d79a', 2, :a, :b).
10
+ with('fe24f4dd4ba7881608cca4b846f009195e06d79a', :a, :b).
11
11
  raises("NOSCRIPT").once
12
12
  redis.expects(:eval).
13
- with(CONTENT, 2, :a, :b).
13
+ with(CONTENT, :a, :b).
14
14
  returns([1, 0]).once
15
15
  end
16
16
  end
@@ -20,6 +20,7 @@ class WolverineIntegrationTest < MiniTest::Unit::TestCase
20
20
  Wolverine.config.script_path = Pathname.new(File.expand_path('../lua', __FILE__))
21
21
 
22
22
  assert_equal [1, 0], Wolverine.util.mexists(:a, :b)
23
+ assert Wolverine.methods.include?(:util)
23
24
  end
24
25
 
25
26
  def test_everything_instantiated
@@ -28,6 +29,7 @@ class WolverineIntegrationTest < MiniTest::Unit::TestCase
28
29
 
29
30
  wolverine = Wolverine.new(config)
30
31
  assert_equal [1, 0], wolverine.util.mexists(:a, :b)
32
+ assert wolverine.methods.include?(:util)
31
33
  end
32
34
 
33
35
  end
@@ -0,0 +1,2 @@
1
+ <%= include_partial 'shared/_inner.lua' %>
2
+ return do_something()
@@ -0,0 +1,3 @@
1
+ local function do_something()
2
+ return 15;
3
+ end
@@ -0,0 +1,33 @@
1
+ require File.join(File.expand_path('../../test_helper', __FILE__))
2
+ require 'digest/sha1'
3
+ require 'fileutils'
4
+
5
+ RESULT = <<EOF
6
+ local function do_something()
7
+ return 15;
8
+ end
9
+ return do_something()
10
+ EOF
11
+
12
+ class Wolverine
13
+ class ScriptTemplatingTest < MiniTest::Unit::TestCase
14
+
15
+ def setup
16
+ base = Pathname.new('test/wolverine/lua')
17
+ Wolverine.config.script_path = base
18
+ end
19
+
20
+ def teardown
21
+ Wolverine.config.instrumentation = proc{}
22
+ end
23
+
24
+ def script
25
+ @script ||= Wolverine::Script.new('test/wolverine/lua/outer.lua')
26
+ end
27
+
28
+ def test_templating
29
+ assert_equal script.instance_variable_get('@content'), RESULT
30
+ end
31
+
32
+ end
33
+ end
@@ -43,7 +43,7 @@ class Wolverine
43
43
  }
44
44
  Wolverine.config.instrumentation = callback
45
45
  redis = Class.new do
46
- define_method(:evalsha) do |digest, size, *args|
46
+ define_method(:evalsha) do |digest, *args|
47
47
  nil
48
48
  end
49
49
  end
@@ -53,9 +53,8 @@ class Wolverine
53
53
  def test_call_with_cache_hit
54
54
  tc = self
55
55
  redis = Class.new do
56
- define_method(:evalsha) do |digest, size, *args|
56
+ define_method(:evalsha) do |digest, *args|
57
57
  tc.assert_equal DIGEST, digest
58
- tc.assert_equal 2, size
59
58
  tc.assert_equal [:a, :b], args
60
59
  end
61
60
  end
@@ -68,9 +67,8 @@ class Wolverine
68
67
  define_method(:evalsha) do |*|
69
68
  raise "NOSCRIPT No matching script. Please use EVAL."
70
69
  end
71
- define_method(:eval) do |content, size, *args|
70
+ define_method(:eval) do |content, *args|
72
71
  tc.assert_equal CONTENT, content
73
- tc.assert_equal 2, size
74
72
  tc.assert_equal [:a, :b], args
75
73
  end
76
74
  end
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.version = Wolverine::VERSION
8
8
  s.authors = ["Burke Libbey"]
9
9
  s.email = ["burke@burkelibbey.org"]
10
- s.homepage = ""
10
+ s.homepage = "https://github.com/Shopify/wolverine"
11
11
  s.summary = %q{Wolverine provides a simple way to run server-side redis scripts from a rails app}
12
12
  s.description = %q{Wolverine provides a simple way to run server-side redis scripts from a rails app}
13
13
 
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
20
 
21
- s.add_runtime_dependency 'redis', '>= 2.2.2'
21
+ s.add_runtime_dependency 'redis', '>= 3.0.0'
22
22
  s.add_development_dependency 'mocha', '~> 0.10.5'
23
23
  s.add_development_dependency 'minitest', '~> 2.11.3'
24
24
  s.add_development_dependency 'rake'
metadata CHANGED
@@ -1,96 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wolverine
3
3
  version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 0.3.0
4
+ version: 0.3.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Burke Libbey
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-01-07 00:00:00.000000000 Z
11
+ date: 2014-03-18 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- version_requirements: !ruby/object:Gem::Requirement
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ! '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 2.2.2
20
- none: false
21
- name: redis
19
+ version: 3.0.0
22
20
  type: :runtime
23
21
  prerelease: false
24
- requirement: !ruby/object:Gem::Requirement
22
+ version_requirements: !ruby/object:Gem::Requirement
25
23
  requirements:
26
- - - ! '>='
24
+ - - ">="
27
25
  - !ruby/object:Gem::Version
28
- version: 2.2.2
29
- none: false
26
+ version: 3.0.0
30
27
  - !ruby/object:Gem::Dependency
31
- version_requirements: !ruby/object:Gem::Requirement
28
+ name: mocha
29
+ requirement: !ruby/object:Gem::Requirement
32
30
  requirements:
33
- - - ~>
31
+ - - "~>"
34
32
  - !ruby/object:Gem::Version
35
33
  version: 0.10.5
36
- none: false
37
- name: mocha
38
34
  type: :development
39
35
  prerelease: false
40
- requirement: !ruby/object:Gem::Requirement
36
+ version_requirements: !ruby/object:Gem::Requirement
41
37
  requirements:
42
- - - ~>
38
+ - - "~>"
43
39
  - !ruby/object:Gem::Version
44
40
  version: 0.10.5
45
- none: false
46
41
  - !ruby/object:Gem::Dependency
47
- version_requirements: !ruby/object:Gem::Requirement
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
48
44
  requirements:
49
- - - ~>
45
+ - - "~>"
50
46
  - !ruby/object:Gem::Version
51
47
  version: 2.11.3
52
- none: false
53
- name: minitest
54
48
  type: :development
55
49
  prerelease: false
56
- requirement: !ruby/object:Gem::Requirement
50
+ version_requirements: !ruby/object:Gem::Requirement
57
51
  requirements:
58
- - - ~>
52
+ - - "~>"
59
53
  - !ruby/object:Gem::Version
60
54
  version: 2.11.3
61
- none: false
62
55
  - !ruby/object:Gem::Dependency
63
- version_requirements: !ruby/object:Gem::Requirement
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
64
58
  requirements:
65
- - - ! '>='
59
+ - - ">="
66
60
  - !ruby/object:Gem::Version
67
61
  version: '0'
68
- none: false
69
- name: rake
70
62
  type: :development
71
63
  prerelease: false
72
- requirement: !ruby/object:Gem::Requirement
64
+ version_requirements: !ruby/object:Gem::Requirement
73
65
  requirements:
74
- - - ! '>='
66
+ - - ">="
75
67
  - !ruby/object:Gem::Version
76
68
  version: '0'
77
- none: false
78
69
  - !ruby/object:Gem::Dependency
79
- version_requirements: !ruby/object:Gem::Requirement
70
+ name: yard
71
+ requirement: !ruby/object:Gem::Requirement
80
72
  requirements:
81
- - - ! '>='
73
+ - - ">="
82
74
  - !ruby/object:Gem::Version
83
75
  version: '0'
84
- none: false
85
- name: yard
86
76
  type: :development
87
77
  prerelease: false
88
- requirement: !ruby/object:Gem::Requirement
78
+ version_requirements: !ruby/object:Gem::Requirement
89
79
  requirements:
90
- - - ! '>='
80
+ - - ">="
91
81
  - !ruby/object:Gem::Version
92
82
  version: '0'
93
- none: false
94
83
  description: Wolverine provides a simple way to run server-side redis scripts from
95
84
  a rails app
96
85
  email:
@@ -99,8 +88,8 @@ executables: []
99
88
  extensions: []
100
89
  extra_rdoc_files: []
101
90
  files:
102
- - .gitignore
103
- - .travis.yml
91
+ - ".gitignore"
92
+ - ".travis.yml"
104
93
  - Gemfile
105
94
  - LICENSE
106
95
  - README.md
@@ -120,33 +109,35 @@ files:
120
109
  - test/integration/wolverine_integration_test.rb
121
110
  - test/test_helper.rb
122
111
  - test/wolverine/configuration_test.rb
112
+ - test/wolverine/lua/outer.lua
113
+ - test/wolverine/lua/shared/_inner.lua
123
114
  - test/wolverine/path_component_test.rb
115
+ - test/wolverine/script_templating_test.rb
124
116
  - test/wolverine/script_test.rb
125
117
  - test/wolverine_test.rb
126
118
  - wolverine.gemspec
127
- homepage: ''
119
+ homepage: https://github.com/Shopify/wolverine
128
120
  licenses: []
121
+ metadata: {}
129
122
  post_install_message:
130
123
  rdoc_options: []
131
124
  require_paths:
132
125
  - lib
133
126
  required_ruby_version: !ruby/object:Gem::Requirement
134
127
  requirements:
135
- - - ! '>='
128
+ - - ">="
136
129
  - !ruby/object:Gem::Version
137
130
  version: '0'
138
- none: false
139
131
  required_rubygems_version: !ruby/object:Gem::Requirement
140
132
  requirements:
141
- - - ! '>='
133
+ - - ">="
142
134
  - !ruby/object:Gem::Version
143
135
  version: '0'
144
- none: false
145
136
  requirements: []
146
137
  rubyforge_project: wolverine
147
- rubygems_version: 1.8.23
138
+ rubygems_version: 2.2.0
148
139
  signing_key:
149
- specification_version: 3
140
+ specification_version: 4
150
141
  summary: Wolverine provides a simple way to run server-side redis scripts from a rails
151
142
  app
152
143
  test_files:
@@ -154,7 +145,10 @@ test_files:
154
145
  - test/integration/wolverine_integration_test.rb
155
146
  - test/test_helper.rb
156
147
  - test/wolverine/configuration_test.rb
148
+ - test/wolverine/lua/outer.lua
149
+ - test/wolverine/lua/shared/_inner.lua
157
150
  - test/wolverine/path_component_test.rb
151
+ - test/wolverine/script_templating_test.rb
158
152
  - test/wolverine/script_test.rb
159
153
  - test/wolverine_test.rb
160
154
  has_rdoc: