cache-object 0.0.1

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: 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: