cache-object 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7c71004d93363e44c125a123d5657588af9a31da
4
+ data.tar.gz: 41b528c9d8fe6cf225ba47a1e68b8845936f9297
5
+ SHA512:
6
+ metadata.gz: 97ec547f05417382920b39bc0a4760d265f429e6825afc5ec0cb956bfc79ba76616d8c74937ee85072a2dc4e154bfb6e9ad48b5f46c4915a9ea902c49ee624df
7
+ data.tar.gz: 6d7a334cfe0d6481369553a3ee7aec038e9f00c84b2ef20cfb346891fa6e1d56d85940561eab4496e77d58b670a718d008cb9edad1ac14a9d5b6c681f84368ae
data/.gitignore ADDED
@@ -0,0 +1,22 @@
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
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cache-object.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+
2
+ guard :rspec, cmd: "bundle exec rspec" do
3
+ watch(%r{^spec/.+_spec\.rb$})
4
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
5
+ watch('spec/spec_helper.rb') { "spec" }
6
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Server Cimen & Siyamed Sinir
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,29 @@
1
+ # Cache::Object
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'cache-object'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install cache-object
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/cache-object/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/bundler ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'bundler' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('bundler', 'bundler')
data/bin/coderay ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'coderay' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('coderay', 'coderay')
data/bin/guard ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'guard' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('guard', 'guard')
data/bin/htmldiff ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'htmldiff' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('diff-lcs', 'htmldiff')
data/bin/ldiff ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'ldiff' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('diff-lcs', 'ldiff')
data/bin/listen ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'listen' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('listen', 'listen')
data/bin/pry ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'pry' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('pry', 'pry')
data/bin/rake ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rake' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rake', 'rake')
data/bin/rspec ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rspec' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rspec-core', 'rspec')
data/bin/thor ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'thor' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('thor', 'thor')
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cache/object/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cache-object"
8
+ spec.version = Cache::Object::VERSION
9
+ spec.authors = ["Matt Camuto"]
10
+ spec.email = ["dev@wanelo.com"]
11
+ spec.summary = %q{Object R/W Caching}
12
+ spec.description = %q{Object R/W Caching on top of ActiveRecord}
13
+ spec.homepage = ""
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 "activerecord", ">= 3.0"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake"
25
+
26
+ spec.add_development_dependency "rspec", ">= 3.0"
27
+ spec.add_development_dependency "guard"
28
+ spec.add_development_dependency "guard-rspec"
29
+ spec.add_development_dependency "sqlite3"
30
+
31
+ end
@@ -0,0 +1,22 @@
1
+ require 'cache/object/version'
2
+ require 'cache/object/config'
3
+ require 'cache/object/active_record'
4
+ require 'cache/object/adapter'
5
+ require 'cache/object/key_generator'
6
+
7
+ module Cache
8
+ module Object
9
+ # Your code goes here...
10
+ def self.configure
11
+ yield configuration
12
+ end
13
+
14
+ def self.configuration
15
+ @configuration ||= Cache::Object::Config.new
16
+ end
17
+
18
+ def self.adapter
19
+ @adapter ||= configuration.adapter
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,57 @@
1
+ require 'cache/object/instance_decorator'
2
+
3
+ module Cache
4
+ module Object
5
+ module ActiveRecord
6
+ def self.included(base)
7
+ base.instance_eval do
8
+ extend ClassMethods
9
+
10
+ def _object_cache_attr_mappings
11
+ @_object_cache_attr_mappings ||= []
12
+ end
13
+
14
+ after_destroy :expire_cache!
15
+ after_save :write_cache!
16
+ after_rollback :expire_cache!
17
+ end
18
+ end
19
+
20
+ def write_cache!
21
+ Cache::Object.adapter.write(_cache_object_decorator)
22
+ end
23
+
24
+ def expire_cache!
25
+ Cache::Object.adapter.delete(_cache_object_decorator)
26
+ end
27
+
28
+ def _cache_object_decorator
29
+ Cache::Object::InstanceDecorator.new(self, self.class._object_cache_attr_mappings)
30
+ end
31
+
32
+ module ClassMethods
33
+ def find(*args)
34
+ Cache::Object.adapter.fetch(self, *args[0]) do
35
+ super(*args)
36
+ end
37
+ end
38
+
39
+ def find_by_id(id)
40
+ Cache::Object.adapter.fetch(self, id) do
41
+ where(self.primary_key => id).first
42
+ end
43
+ end
44
+
45
+ def object_cache_on(*attrs)
46
+ self._object_cache_attr_mappings << attrs
47
+ define_singleton_method("find_by_#{attrs.join('_and_')}") do |*args|
48
+ attributes = Hash[attrs.zip(args)]
49
+ Cache::Object.adapter.fetch_mapping(self, attributes) do
50
+ super(*args)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,42 @@
1
+ module Cache
2
+ module Object
3
+ class Adapter
4
+ attr_reader :store
5
+
6
+ def initialize(store)
7
+ raise "Cache Store is nil" unless store
8
+ @store = store
9
+ end
10
+
11
+ def write(decorator)
12
+ decorator.keys.each do |key|
13
+ store.write(key, decorator.instance, expires_in: ttl)
14
+ end
15
+ end
16
+
17
+ def delete(decorator)
18
+ decorator.keys.each do |key|
19
+ store.delete(key)
20
+ end
21
+ end
22
+
23
+ def fetch(klass, id, &block)
24
+ store.fetch(KeyGenerator.key_for_object(klass.name, id),
25
+ expires_in: ttl,
26
+ &block)
27
+ end
28
+
29
+ def fetch_mapping(klass, attributes, &block)
30
+ store.fetch(KeyGenerator.key_for_mapping(klass.name, attributes),
31
+ expires_in: ttl,
32
+ &block)
33
+ end
34
+
35
+ private
36
+
37
+ def ttl
38
+ Cache::Object.configuration.ttl
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ module Cache
2
+ module Object
3
+ class Config
4
+ attr_accessor :enabled, :ttl, :adapter, :cache
5
+
6
+ def initialize
7
+ self.enabled = true
8
+ self.ttl = 86400
9
+ end
10
+
11
+ def adapter
12
+ @adapter ||= Cache::Object::Adapter.new(cache)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module Cache
2
+ module Object
3
+ class InstanceDecorator < Struct.new(:instance, :mappings)
4
+ def keys
5
+ [canonical_cache_key] + mapping_cache_keys
6
+ end
7
+
8
+ private
9
+
10
+ def mapping_cache_keys
11
+ mappings.map do |mapping|
12
+ attributes = mapping.inject({}) do |memo, attr|
13
+ memo[attr] = instance.send(attr)
14
+ memo
15
+ end
16
+ KeyGenerator.key_for_mapping(instance.class.name, attributes)
17
+ end
18
+ end
19
+
20
+ def canonical_cache_key
21
+ KeyGenerator.key_for_object(instance.class.name, instance.id)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,16 @@
1
+ module Cache
2
+ module Object
3
+ class KeyGenerator
4
+ def self.key_for_object(class_name, id)
5
+ "#{class_name}-#{id}"
6
+ end
7
+
8
+ def self.key_for_mapping(class_name, attributes)
9
+ attributes_key = attributes.map do |k, v|
10
+ "#{k}-#{v}"
11
+ end.join('-')
12
+ "#{class_name}-#{attributes_key}"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ module Cache
2
+ module Object
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Cache::Object::ActiveRecord do
4
+
5
+ class FakeActiveRecord
6
+ def self.after_save(method)
7
+ ;
8
+ end
9
+
10
+ def self.after_rollback(method)
11
+ ;
12
+ end
13
+
14
+ def self.after_destroy(method)
15
+ ;
16
+ end
17
+
18
+ def self.find(id)
19
+ end
20
+
21
+ def self.find_by_name_and_age(name, age)
22
+ end
23
+ end
24
+
25
+ class Model < FakeActiveRecord
26
+ include Cache::Object::ActiveRecord
27
+
28
+ object_cache_on :name, :age
29
+ end
30
+
31
+ let(:super_clazz) { FakeActiveRecord }
32
+ let(:clazz) { Model }
33
+
34
+ let(:adapter) do
35
+ Class.new do
36
+ def fetch(clazz, id)
37
+ yield
38
+ end
39
+
40
+ def fetch_mapping(klass, attributes)
41
+ yield
42
+ end
43
+ end
44
+ end
45
+
46
+ let(:adapter_instance) { adapter.new }
47
+
48
+ before do
49
+ allow(Cache::Object).to receive(:adapter).and_return(adapter_instance)
50
+ end
51
+
52
+ describe ".included" do
53
+ it "receives correct callbacks" do
54
+ expect(clazz).to receive(:after_save).with(:write_cache!).once
55
+ expect(clazz).to receive(:after_rollback).with(:expire_cache!).once
56
+ expect(clazz).to receive(:after_destroy).with(:expire_cache!).once
57
+ clazz.send(:include, Cache::Object::ActiveRecord)
58
+ end
59
+ end
60
+
61
+
62
+ describe "caching methods" do
63
+ describe "#write_cache" do
64
+ it "calls write cache" do
65
+ expect(adapter_instance).to receive(:write).with(an_instance_of(Cache::Object::InstanceDecorator))
66
+ object = clazz.new
67
+ object.write_cache!
68
+ end
69
+ end
70
+
71
+
72
+ describe "#expire_cache" do
73
+ it "calls write cache" do
74
+ expect(adapter_instance).to receive(:delete).with(an_instance_of(Cache::Object::InstanceDecorator))
75
+ object = clazz.new
76
+ object.expire_cache!
77
+ end
78
+ end
79
+ end
80
+
81
+ describe "Finder class methods" do
82
+ describe "#respond_to?" do
83
+ it "find" do
84
+ expect(clazz).to respond_to(:find)
85
+ end
86
+ it "find_by_id" do
87
+ expect(clazz).to respond_to(:find_by_id)
88
+ end
89
+ end
90
+
91
+ describe ".find" do
92
+ describe "caching interactions" do
93
+ it "yields to super with cache" do
94
+ allow(Cache::Object).to receive(:adapter).and_return(adapter.new)
95
+ expect(super_clazz).to receive(:find).with(12).once
96
+ clazz.find(12)
97
+ end
98
+ end
99
+ end
100
+
101
+ describe ".find_by_id" do
102
+ describe "caching interactions" do
103
+ it "yields to super with cache" do
104
+ expect(super_clazz).to receive(:primary_key) { "primary_key" }
105
+ expect(super_clazz).to receive(:where).with("primary_key" => 12).once { double(first: true) }
106
+ clazz.find_by_id(12)
107
+ end
108
+ end
109
+ end
110
+
111
+ describe "object_cache_on" do
112
+
113
+ it "creates_finder_methods" do
114
+ expect(clazz).to respond_to(:find_by_name_and_age)
115
+ end
116
+
117
+ it "calls fetch_mapping on the adapter" do
118
+ expect(adapter_instance).to receive(:fetch_mapping).with(clazz, name: "bob", age: 13).once
119
+ clazz.find_by_name_and_age("bob", 13)
120
+ end
121
+
122
+ it "calls super" do
123
+ expect(super_clazz).to receive(:find_by_name_and_age).with("bob", 13)
124
+ clazz.find_by_name_and_age("bob", 13)
125
+ end
126
+ end
127
+ end
128
+ end
129
+
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Cache::Object::Adapter do
4
+
5
+ before do
6
+ allow(Cache::Object.configuration).to receive(:ttl).and_return(118)
7
+ end
8
+
9
+ let(:cache_store) { double("CacheStore", write: true) }
10
+ let(:adapter) { Cache::Object::Adapter.new(cache_store) }
11
+ let(:instance) { double(class: double(name: "User")) }
12
+
13
+ describe "#delete" do
14
+ it "dispatches to delete" do
15
+ expect(cache_store).to receive(:delete).with("User-1")
16
+ expect(cache_store).to receive(:delete).with("User-1-blah")
17
+
18
+ adapter.delete(double(instance: instance, keys: ["User-1", "User-1-blah"]))
19
+ end
20
+ end
21
+
22
+ describe "#write" do
23
+ it "dispatches to write" do
24
+ expect(cache_store).to receive(:write).with("User-1", instance, expires_in: 118)
25
+ expect(cache_store).to receive(:write).with("User-1-blah", instance, expires_in: 118)
26
+
27
+ adapter.write(double(instance: instance, keys: ["User-1", "User-1-blah"]))
28
+ end
29
+ end
30
+
31
+ describe "#fetch" do
32
+ it "fetches the object from the cache_store" do
33
+ expect(cache_store).to receive(:fetch).with("User-1", expires_in: 118).and_yield
34
+ expect { |b|
35
+ adapter.fetch(instance.class, 1, &b)
36
+ }.to yield_control
37
+ end
38
+ end
39
+
40
+ describe "#fetch_mapping" do
41
+ it "fetches the object from the cache store based on the attributes" do
42
+ expect(cache_store).to receive(:fetch).with("User-user_id-1-name-bob", expires_in: 118).and_yield
43
+ expect { |b|
44
+ adapter.fetch_mapping(instance.class, { user_id: 1, name: "bob" }, &b)
45
+ }.to yield_control
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ RSpec.describe Cache::Object do
5
+
6
+ describe ".configure" do
7
+ let(:clazz) { Class.new }
8
+
9
+ describe "#cache" do
10
+ it "sets cache" do
11
+ Cache::Object.configure { |c| c.cache = clazz }
12
+ expect(Cache::Object.configuration.cache).to eq(clazz)
13
+ end
14
+ end
15
+
16
+ describe "#enabled" do
17
+ it "is enabled by default" do
18
+ expect(Cache::Object.configuration.enabled).to eq(true)
19
+ end
20
+
21
+
22
+ it "is disabled if set false" do
23
+ Cache::Object.configure { |c| c.enabled = false }
24
+ expect(Cache::Object.configuration.enabled).to eq(false)
25
+ end
26
+ end
27
+
28
+ describe "#ttl" do
29
+ it "sets to time" do
30
+ Cache::Object.configure { |c| c.ttl = 1234 }
31
+ expect(Cache::Object.configuration.ttl).to eq(1234)
32
+ end
33
+ it "is one day by default" do
34
+ expect(Cache::Object.configuration.ttl).to eq(86400)
35
+ end
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Cache::Object::InstanceDecorator do
4
+ let(:decorator) { Cache::Object::InstanceDecorator.new(instance, mappings) }
5
+
6
+ describe '#keys' do
7
+ let(:instance) { double(id: 1, user_id: 11, sex: 'male', class: double(name: 'User')) }
8
+ let(:mappings) { [[:user_id] , [:user_id, :sex]] }
9
+
10
+ it 'includes the keys based on the mappings' do
11
+ expect(decorator.keys).to include 'User-user_id-11'
12
+ expect(decorator.keys).to include 'User-user_id-11-sex-male'
13
+ end
14
+
15
+ it 'includes the canonical id key' do
16
+ expect(decorator.keys).to include 'User-1'
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+ require 'support/models'
3
+
4
+ # Only writing the features before all the units to see the semantics of
5
+ # using with actual AR finder methods
6
+ RSpec.describe "Caching" do
7
+
8
+ before do
9
+ CreateModelsForTest.migrate(:up)
10
+ Cache::Object.configure do |c|
11
+ c.cache = ::ActiveSupport::Cache::MemoryStore.new
12
+ end
13
+ end
14
+
15
+ after do
16
+ CreateModelsForTest.migrate(:down)
17
+ end
18
+
19
+ let!(:user) { User.create(age: 13, name: "Bob") }
20
+
21
+ describe "#find" do
22
+ it "finds the object from the cache" do
23
+ expect {
24
+ expect(User.find(user.id)).to eq(user)
25
+ }.to change { ActiveRecord::QueryCounter.query_count }.by(0)
26
+ end
27
+ end
28
+
29
+ describe "#find_by_id" do
30
+ it "finds the object from the cache" do
31
+ expect {
32
+ expect(User.find_by_id(user.id)).to eq(user)
33
+ }.to change { ActiveRecord::QueryCounter.query_count }.by(0)
34
+ end
35
+ end
36
+
37
+ describe "#find_by_name_and_age" do
38
+ it "finds the object from the cache" do
39
+ expect {
40
+ expect(User.find_by_name_and_age("Bob", 13)).to eq(user)
41
+ }.to change { ActiveRecord::QueryCounter.query_count }.by(0)
42
+ end
43
+
44
+ describe "when the name is changed" do
45
+ it "writes the updated data into the cache" do
46
+ user.update_attributes(name: "Sally")
47
+ expect {
48
+ fetched_user = User.find_by_name_and_age("Sally", 13)
49
+ expect(fetched_user.name).to eq("Sally")
50
+ }.to change { ActiveRecord::QueryCounter.query_count }.by(0)
51
+ end
52
+ end
53
+ end
54
+
55
+ describe "when user id destroyed" do
56
+ it "tries to run a query" do
57
+ user.destroy
58
+ expect {
59
+ expect {
60
+ User.find(user.id)
61
+ }.to raise_error
62
+ }.to change { ActiveRecord::QueryCounter.query_count }.by(1)
63
+ end
64
+ end
65
+
66
+ describe "rolling back a transaction" do
67
+ it "expires the cache" do
68
+ expect {
69
+ user.update_attributes(name: "asplode")
70
+ }.to raise_error
71
+
72
+ expect {
73
+ User.find(user.id)
74
+ }.to change { ActiveRecord::QueryCounter.query_count }.by(1)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,15 @@
1
+ require 'cache/object'
2
+ RSpec.configure do |config|
3
+
4
+ config.before do
5
+ Cache::Object.instance_variable_set(:@configuration, nil)
6
+ end
7
+
8
+ config.after do
9
+ Cache::Object.instance_variable_set(:@configuration, nil)
10
+ end
11
+
12
+ config.raise_errors_for_deprecations!
13
+ config.disable_monkey_patching!
14
+
15
+ end
@@ -0,0 +1,50 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Migration.verbose = false
4
+ ActiveRecord::Base.logger = Logger.new($STDOUT)
5
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
6
+
7
+ module ActiveRecord
8
+ class QueryCounter
9
+ cattr_accessor :query_count do
10
+ 0
11
+ end
12
+
13
+ IGNORED_SQL = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/]
14
+
15
+ def call(name, start, finish, message_id, values)
16
+ # FIXME: this seems bad. we should probably have a better way to indicate
17
+ # the query was cached
18
+ unless 'CACHE' == values[:name]
19
+ self.class.query_count += 1 unless IGNORED_SQL.any? { |r| values[:sql] =~ r }
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ ActiveSupport::Notifications.subscribe('sql.active_record', ActiveRecord::QueryCounter.new)
26
+
27
+ class CreateModelsForTest < ActiveRecord::Migration
28
+ def self.up
29
+ create_table :users do |t|
30
+ t.string :name
31
+ t.integer :age, :default => 12
32
+ end
33
+ end
34
+
35
+ def self.down
36
+ drop_table(:users)
37
+ end
38
+ end
39
+
40
+ class User < ActiveRecord::Base
41
+ include Cache::Object::ActiveRecord
42
+
43
+ object_cache_on :name, :age
44
+
45
+ after_save :asplode_if_name_is_asplode
46
+
47
+ def asplode_if_name_is_asplode
48
+ raise "WOAH" if name == "asplode"
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,182 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cache-object
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matt Camuto
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard
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
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sqlite3
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Object R/W Caching on top of ActiveRecord
112
+ email:
113
+ - dev@wanelo.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - Gemfile
121
+ - Guardfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - bin/bundler
126
+ - bin/coderay
127
+ - bin/guard
128
+ - bin/htmldiff
129
+ - bin/ldiff
130
+ - bin/listen
131
+ - bin/pry
132
+ - bin/rake
133
+ - bin/rspec
134
+ - bin/thor
135
+ - cache-object.gemspec
136
+ - lib/cache/object.rb
137
+ - lib/cache/object/active_record.rb
138
+ - lib/cache/object/adapter.rb
139
+ - lib/cache/object/config.rb
140
+ - lib/cache/object/instance_decorator.rb
141
+ - lib/cache/object/key_generator.rb
142
+ - lib/cache/object/version.rb
143
+ - spec/cache/object/active_record_spec.rb
144
+ - spec/cache/object/adapter_spec.rb
145
+ - spec/cache/object/config_spec.rb
146
+ - spec/cache/object/instance_decorator_spec.rb
147
+ - spec/features/cache_object_spec.rb
148
+ - spec/spec_helper.rb
149
+ - spec/support/models.rb
150
+ homepage: ''
151
+ licenses:
152
+ - MIT
153
+ metadata: {}
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubyforge_project:
170
+ rubygems_version: 2.2.2
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: Object R/W Caching
174
+ test_files:
175
+ - spec/cache/object/active_record_spec.rb
176
+ - spec/cache/object/adapter_spec.rb
177
+ - spec/cache/object/config_spec.rb
178
+ - spec/cache/object/instance_decorator_spec.rb
179
+ - spec/features/cache_object_spec.rb
180
+ - spec/spec_helper.rb
181
+ - spec/support/models.rb
182
+ has_rdoc: