split_cacheable 1.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 048a0263fd1ce03ec972455cd98fbe5ef2e66407
4
+ data.tar.gz: 8fefa33bf269410149f43805eefeaccc58f9e177
5
+ SHA512:
6
+ metadata.gz: 67809fddc3a286a0710ecca6f9ad55e937bb7cf22af37afc74b725065cd474560116945caa11fbd8e6a27aaea41a732553b18b95c36d3fad18c0c20d6343d964
7
+ data.tar.gz: c63cb1e25204522877c02bede101413dc2c646718b1c6a9b5438e24dfd260db031aa2e0545abb4b3246e6ef5163babd5816d50edf060203da9f8273b706c9a9a
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in split_cacheable.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 harrystech
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Daniel Schwartz
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,83 @@
1
+ # Split::Cacheable
2
+
3
+ An extension to [Split](http://github.com/andrew/split) to allow for automatic cache bucket creation accross Split tests.
4
+
5
+ ## Requirements
6
+
7
+ The split gem and its dependencies.
8
+
9
+ ## Setup
10
+
11
+ In your Gemfile:
12
+
13
+ gem 'split_cacheable'
14
+
15
+ Then run:
16
+
17
+ bundle install
18
+
19
+ ## Why?
20
+
21
+ We use action caching in Rails 3 to cache both our standard and mobile site. We wanted to be able to quickly run Split tests without worrying about setting a custom cache_path each time as well as remembering to make the needed changes to our ActiveRecord models.
22
+
23
+ ## How?
24
+
25
+ Under the hood, action caching uses `fragment_cache_key` in `ActionController::Base`. This gem patches this method to incldue our generated current tests and variations.
26
+
27
+ If you already override `fragment_cache_key` in your project you can get the split partial cache key we generate by calling `current_tests_and_variations`
28
+
29
+ ## Controller DSL
30
+
31
+ We've created an easy to remember DSL for adding active tests to specific methods:
32
+
33
+ ```
34
+ class ProductController < ApplicationController
35
+ cacheable_ab_test :login_flow,
36
+ :only => [:index, :some_other_method],
37
+ :except => [:index, :some_other_method],
38
+ :if => Proc.new { |controller| controller.mobile_device? }
39
+
40
+ def index
41
+
42
+ end
43
+ end
44
+ ```
45
+
46
+ * `:login_flow`: The name of the test as it appears in your Split configuration
47
+ * `:only`: A symbol or an array of symbols corresponding to the action names you'd like this test to run for
48
+ * `:except`: A symbol or an array of symbols corresponding to the action names you'd like to exclude from the test (it will run on all actions not listed here)
49
+ * `:if`: A boolean value or Proc to be evaluated at runtime. The current controller instance is passed into the Proc for you to use
50
+
51
+ Only include either `:only` or `:except` -- if you include both, `:only` will take precedence.
52
+
53
+ ## Outside Controller Access
54
+
55
+ If you'd like to manually expire your action caches in models/sweeper/wherever we give you access to create an instance of the adapter use internally.
56
+
57
+ ### Instantiate an adapter
58
+
59
+ `Split::Cacheable::Adapter.new(<controller_instance>, <action_name>)`
60
+
61
+ * `controller_instance`: A new instance of an ActionController::Base subclass
62
+ * `action_name`: A symbol that corresponds to the action you want to uncache
63
+
64
+ ex: `Split::Cacheable::Adapter.new(ProductController.new, :index)`
65
+
66
+ ### Then get all possible variations to uncache
67
+
68
+ `Split::Cacheable::Adapter.new(ProductController.new, :index).get_all_possible_variations`
69
+
70
+ This will return an array of all the possible cache keys ever generated so you can manually uncache your views:
71
+
72
+ ```
73
+ Split::Cacheable::Adapter.new(ProductController.new, :index).get_all_possible_variations.each { |split_cache_key|
74
+ Rails.cache.delete("views/#{split_cache_key}/#{current_host}/products")
75
+ }
76
+ ```
77
+
78
+ Note that we don't evaluate the `:if` option when you instantiate the controller manually. This is because we assume `Proc`s will usually be used to decide whether to show the test based on the current request. By not evaluating the `:if`s in this scenario we are able to return all possible cache keys regardless of request type.
79
+
80
+ ## Development
81
+
82
+ Source hosted at [GitHub](http://github.com/harrystech/split_cacheable).<br>
83
+ Report Issues/Feature requests on [GitHub Issues](http://github.com/harrystech/split_cacheable/issues).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,12 @@
1
+ module Split
2
+ module Cacheable
3
+ class Engine < ::Rails::Engine
4
+ initializer "split" do |app|
5
+ if Split.configuration.include_rails_helper
6
+ ActionController::Base.send :include, Split::Cacheable::Helper
7
+ ActionController::Base.helper Split::Cacheable::Helper
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,39 @@
1
+ module Split
2
+ module Cacheable
3
+ module Helper
4
+ def self.included(base)
5
+ base.send :include, InstanceMethods
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module InstanceMethods
10
+ # Patch the default ActionController::Base action_cache key creation
11
+ # by prepending our current tests + variation key generated in the next method
12
+ def fragment_cache_key(key)
13
+ super("#{current_tests_and_variations}/#{key}")
14
+ end
15
+
16
+ # Controller helper method to get the current active tests + variations in the form of a partial cache key
17
+ def current_tests_and_variations
18
+ Split::Cacheable::Adapter.new(self, self.action_name.to_sym).get_current_variations
19
+ end
20
+ end
21
+
22
+ module ClassMethods
23
+ # This is how you specify your tests in a sub-class of ActionController::Base
24
+ # ex: cacheable_ab_test :homepage_hero, :only => :our_story, :if => Rails.env.production?
25
+ def cacheable_ab_test(test_name, options)
26
+ options[:except] = Array(options[:except])
27
+ options[:only] = Array(options[:only])
28
+
29
+ self.split_cacheable_ab_tests << options.merge({:test_name => test_name})
30
+ end
31
+
32
+ # Class level variable. cacheable_ab_test's get pushed onto it
33
+ def split_cacheable_ab_tests
34
+ @split_cacheable_ab_tests ||= Array.new
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ module Split
2
+ module Cacheable
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,93 @@
1
+ require 'pry'
2
+ require 'pry-byebug'
3
+ require "split_cacheable/version"
4
+ require 'split_cacheable/helper'
5
+ require 'split_cacheable/engine' if defined?(Rails) && Rails::VERSION::MAJOR === 3
6
+ require 'split'
7
+
8
+ # This is the main Adapter instance. It expects:
9
+ # controller_instance => An instance of ActionController::Base
10
+ # action_name => The current action being performed (as a symbol)
11
+ #
12
+ # We don't include this as a module so that you can instantite this yourself
13
+ # so you can call get_all_possible_variations to clear your caches
14
+
15
+ module Split
16
+ module Cacheable
17
+ class Adapter
18
+ DEFAULT_KEY = 'default/control'
19
+
20
+ def initialize(controller_instance, action_name)
21
+ @controller = controller_instance
22
+ @action_name = action_name
23
+ end
24
+
25
+ # Get all tests which should be active on this controller for this action
26
+ def active_tests
27
+ ab_tests = @controller.class.split_cacheable_ab_tests.select { |test_obj|
28
+ is_active = false
29
+
30
+ if test_obj[:only].include?(@action_name)
31
+ is_active = true
32
+ end
33
+
34
+ if test_obj[:only].empty? && !test_obj[:except].include?(@action_name)
35
+ is_active = true
36
+ end
37
+
38
+ # The assumption here is that we should only evaluate the :if Proc or Boolean
39
+ # if we are part of a live ActionController::Base.
40
+ # This allows active_tests to return all possible active tests for when you call get_all_possible_variations
41
+ if (defined?(@controller.request) && !@controller.request.nil?) && test_obj[:if]
42
+ if is_active && (test_obj[:if].is_a?(Proc) ? test_obj[:if].call(@controller) : !!test_obj[:if])
43
+ is_active = true
44
+ else
45
+ is_active = false
46
+ end
47
+ end
48
+
49
+ is_active
50
+ }
51
+ end
52
+
53
+ # Use Split to return a partial cache key (used in fragment_cache_key)
54
+ # by calling ab_test which is an internal Split::Helper method that is now on your
55
+ # controller instance
56
+ #
57
+ # You should not be calling this method outside of a live ActionController::Base
58
+ def get_current_variations
59
+ if !defined?(@controller.request) || @controller.request.nil?
60
+ return DEFAULT_KEY
61
+ else
62
+ return !active_tests.empty? ? active_tests.map{|test_obj| "#{test_obj[:test_name]}/#{@controller.ab_test(test_obj[:test_name])}"}.join('/') : DEFAULT_KEY
63
+ end
64
+ end
65
+
66
+ # Search the Split::ExperimentCatalog to find all tests and generate
67
+ # every possible partial cache key
68
+ #
69
+ # Use this to clear all your action_caches
70
+ def get_all_possible_variations
71
+ test_variations = Array.new
72
+ active_tests.each { |test_obj|
73
+ split_test = Split::ExperimentCatalog.find(test_obj[:test_name])
74
+ if split_test
75
+ test_variations << split_test.alternatives.map { |alternative|
76
+ "#{split_test.name}/#{alternative.name}"
77
+ }
78
+ end
79
+ }
80
+
81
+ case test_variations.length
82
+ when 0
83
+ return [DEFAULT_KEY]
84
+ when 1
85
+ return test_variations[0]
86
+ else
87
+ first_test = test_variations.shift
88
+ return first_test.product(*test_variations).map{|a| a.join("/")}
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Split::Cacheable do
4
+ ALL_VARIATIONS = ["test_1/old/test_2/left", "test_1/old/test_2/right", "test_1/new/test_2/left", "test_1/new/test_2/right"]
5
+
6
+ it "should return the default variation if there is no request" do
7
+ Split::Cacheable::Adapter.new(TestControllerWithoutRequest.new, :index).get_current_variations.should eql Split::Cacheable::Adapter::DEFAULT_KEY
8
+ end
9
+
10
+ it "should return a possible active variation if the controller has a request" do
11
+ ALL_VARIATIONS.should include(Split::Cacheable::Adapter.new(TestControllerWithRequest.new, :index).get_current_variations)
12
+ end
13
+
14
+ it "should return all active tests for a controller/action combination" do
15
+ Split::Cacheable::Adapter.new(TestControllerWithRequest.new, :index).active_tests.should eql [{:only=>[:index], :except=>[], :test_name=>:test_1}, {:only=>[], :except=>[:show], :test_name=>:test_2}]
16
+ end
17
+
18
+ it "should return all possible variations of the cachekey" do
19
+ Split::Cacheable::Adapter.new(TestControllerWithoutRequest.new, :index).get_all_possible_variations.should eql ALL_VARIATIONS
20
+ end
21
+
22
+ it "should disregard tests that don't pass the :if Proc" do
23
+ controller = TestControllerWithRequestAndProc.new
24
+ Split::Cacheable::Adapter.new(controller, :index).get_current_variations.should eql Split::Cacheable::Adapter::DEFAULT_KEY
25
+
26
+ controller.instance_variable_set("@mobile_device", true)
27
+ ALL_VARIATIONS.should include(Split::Cacheable::Adapter.new(TestControllerWithRequest.new, :index).get_current_variations)
28
+ end
29
+ end
@@ -0,0 +1,71 @@
1
+ ENV['RACK_ENV'] = "test"
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+ require 'split'
6
+ require 'ostruct'
7
+ require 'split/helper'
8
+ require 'split_cacheable'
9
+ require 'split_cacheable/helper'
10
+
11
+ Split.configure do |config|
12
+ config.enabled = true
13
+ config.db_failover = true
14
+ config.persistence = :session
15
+ config.experiments = {
16
+ "test_1" => {
17
+ :alternatives => ['old', 'new']
18
+ },
19
+
20
+ "test_2" => {
21
+ :alternatives => ['left', 'right']
22
+ },
23
+ }
24
+ end
25
+
26
+ class TestControllerWithoutRequest
27
+ include Split::Helper
28
+ include Split::Cacheable::Helper
29
+
30
+ cacheable_ab_test :test_1, :only => :index
31
+ cacheable_ab_test :test_2, :except => :show
32
+ def index
33
+
34
+ end
35
+
36
+ def session
37
+ @session ||= {}
38
+ end
39
+
40
+ def params
41
+ @params ||= {}
42
+ end
43
+ end
44
+
45
+ class TestControllerWithRequest < TestControllerWithoutRequest
46
+ cacheable_ab_test :test_1, :only => :index
47
+ cacheable_ab_test :test_2, :except => :show
48
+ def index
49
+
50
+ end
51
+
52
+ def request(ua = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; de-de) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27')
53
+ r = OpenStruct.new
54
+ r.user_agent = ua
55
+ r.ip = '192.168.1.1'
56
+ @request ||= r
57
+ end
58
+ end
59
+
60
+ class TestControllerWithRequestAndProc < TestControllerWithRequest
61
+ cacheable_ab_test :test_1, :only => :index, :if => Proc.new { |c| c.mobile_device? }
62
+ cacheable_ab_test :test_2, :except => :show, :if => Proc.new { |c| c.mobile_device? }
63
+
64
+ def index
65
+
66
+ end
67
+
68
+ def mobile_device?
69
+ @is_mobile
70
+ end
71
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'split_cacheable/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "split_cacheable"
8
+ spec.version = Split::Cacheable::VERSION
9
+ spec.authors = ["Daniel Schwartz"]
10
+ spec.email = ["dschwartz88@gmail.com"]
11
+ spec.summary = %q{We use action caching in Rails 3 to cache both our standard and mobile site. We wanted to be able to quickly run Split tests without worrying about setting a custom cache_path each time as well as remembering to make the needed changes to our ActiveRecord models.}
12
+ spec.description = %q{An extension to Split to allow for automatic cache bucket creation accross Split tests.}
13
+ spec.homepage = "https://github.com/harrystech/split_cacheable"
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 "split"
22
+ spec.add_dependency "activesupport"
23
+
24
+ spec.add_development_dependency "rspec", "~> 2.14"
25
+ spec.add_development_dependency "pry"
26
+ spec.add_development_dependency "pry-byebug"
27
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: split_cacheable
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Schwartz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: split
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '2.14'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '2.14'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-byebug
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
+ description: An extension to Split to allow for automatic cache bucket creation accross
84
+ Split tests.
85
+ email:
86
+ - dschwartz88@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - Gemfile
93
+ - LICENSE
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - lib/split_cacheable.rb
98
+ - lib/split_cacheable/engine.rb
99
+ - lib/split_cacheable/helper.rb
100
+ - lib/split_cacheable/version.rb
101
+ - spec/cacheable_spec.rb
102
+ - spec/spec_helper.rb
103
+ - split_cacheable.gemspec
104
+ homepage: https://github.com/harrystech/split_cacheable
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.2.2
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: We use action caching in Rails 3 to cache both our standard and mobile site.
128
+ We wanted to be able to quickly run Split tests without worrying about setting a
129
+ custom cache_path each time as well as remembering to make the needed changes to
130
+ our ActiveRecord models.
131
+ test_files:
132
+ - spec/cacheable_spec.rb
133
+ - spec/spec_helper.rb