catche 0.1.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.
@@ -0,0 +1,20 @@
1
+ Copyright 2012 Arjen Oosterkamp
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,44 @@
1
+ # Catche [![Build Status](https://secure.travis-ci.org/Arjeno/catche.png?branch=master)](http://travis-ci.org/Arjeno/catche)
2
+
3
+ Catche is a caching library for easy and automated resource and collection caching. It basically tags cached outputs and expires them based on configuration.
4
+
5
+ ## Installation
6
+
7
+ Add this to your Gemfile and run `bundle`.
8
+ ```
9
+ gem "catche"
10
+ ```
11
+
12
+ ## Controller caching
13
+
14
+ Controller caching is based on `caches_action` using the method `catche`.
15
+
16
+ ### Simple caching
17
+
18
+ ```ruby
19
+ class ProjectsController < ApplicationController
20
+ catche Project, :index, :show
21
+ end
22
+ ```
23
+
24
+ ### Associative caching
25
+
26
+ ```ruby
27
+ class TasksController < ApplicationController
28
+ catche Task, :index, :show, :through => :project
29
+ end
30
+ ```
31
+
32
+ On resource change this will expire:
33
+
34
+ * Resource task
35
+ * Resource task within specific project
36
+
37
+ On resource or collection change this will expire:
38
+
39
+ * Collection tasks
40
+ * Collection tasks within specific project
41
+
42
+ ## License
43
+
44
+ This project is released under the MIT license.
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,19 @@
1
+ require 'catche/railtie'
2
+ require 'catche/adapter'
3
+ require 'catche/controller'
4
+ require 'catche/model'
5
+ require 'catche/tag'
6
+
7
+ module Catche
8
+
9
+ extend self
10
+
11
+ def initialize_defaults
12
+
13
+ end
14
+
15
+ def adapter
16
+ Catche::Adapter::Base
17
+ end
18
+
19
+ end
@@ -0,0 +1 @@
1
+ require 'catche/adapter/base'
@@ -0,0 +1,27 @@
1
+ module Catche
2
+ module Adapter
3
+ class Base
4
+
5
+ class << self
6
+
7
+ def read(key, default_value=nil)
8
+ adapter.read(key) || default_value
9
+ end
10
+
11
+ def write(key, value)
12
+ adapter.write(key, value)
13
+ end
14
+
15
+ def delete(key)
16
+ adapter.delete(key)
17
+ end
18
+
19
+ def adapter
20
+ Rails.cache
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1 @@
1
+ require 'catche/controller/base'
@@ -0,0 +1,55 @@
1
+ module Catche
2
+ module Controller
3
+ module Base
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+
9
+ # Caches a controller action by tagging it.
10
+ # Supports any option parameters caches_action supports.
11
+ #
12
+ # catche Project, :index
13
+ # catche Task, :through => :project
14
+ def catche(model, *args)
15
+ options = args.extract_options!
16
+ tag = Proc.new { |controller| Catche::Tag::Object.for(model, controller.class, options) }
17
+
18
+ # Use Rails caches_action to pass along the tag
19
+ caches_action(*args, { :tag => tag }.merge(options))
20
+ end
21
+
22
+ end
23
+
24
+ def _save_fragment(name, options={})
25
+ key = fragment_cache_key(name)
26
+ object = options[:tag]
27
+ tags = []
28
+
29
+ if object.present?
30
+ if object.respond_to?(:call)
31
+ object = self.instance_exec(self, &object)
32
+
33
+ if object.respond_to?(:tags)
34
+ tags = object.tags(self)
35
+ else
36
+ tags = Array.new(object)
37
+ end
38
+ else
39
+ tags = Array.new(object)
40
+ end
41
+
42
+ Catche::Tag.tag! key, *tags
43
+
44
+ # Store for future reference
45
+ @catche_cache_object = object
46
+ end
47
+
48
+ super
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+
55
+ ActionController::Base.send :include, Catche::Controller::Base
@@ -0,0 +1 @@
1
+ require 'catche/model/base'
@@ -0,0 +1,35 @@
1
+ module Catche
2
+ module Model
3
+ module Base
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ after_update :expire_resource!
8
+ after_destroy :expire_resource!
9
+
10
+ after_create :expire_collection!
11
+ after_destroy :expire_collection!
12
+ end
13
+
14
+ def expire_collection!
15
+ expire_cache!
16
+ end
17
+
18
+ def expire_resource!
19
+ expire_cache!
20
+ end
21
+
22
+ def expire_cache!
23
+ tags = Catche::Tag::Object.find_by_model(self.class).collect do |obj|
24
+ self.instance_variable_set("@#{obj.options[:resource_name]}", self)
25
+ obj.expiration_tags(self)
26
+ end.flatten.compact.uniq
27
+
28
+ Catche::Tag.expire! *tags
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+
35
+ ActiveRecord::Base.send :include, Catche::Model::Base
@@ -0,0 +1,7 @@
1
+ module Catche
2
+ class Railtie < ::Rails::Railtie
3
+ initializer "include.catche" do |app|
4
+ Catche.initialize_defaults
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,71 @@
1
+ require 'catche/tag/object'
2
+ require 'catche/tag/resource'
3
+
4
+ module Catche
5
+ module Tag
6
+
7
+ KEY = 'catche'
8
+ DIVIDER = '_'
9
+
10
+ extend self
11
+
12
+ def join(*tags)
13
+ tags.flatten.compact.uniq.join(DIVIDER)
14
+ end
15
+
16
+ def tag!(key, *tags)
17
+ tags.each do |tag|
18
+ keys = fetch_tag(tag)
19
+ key_tags = fetch_key(key)
20
+ tag_key = stored_key(:tags, tag)
21
+ key_key = stored_key(:keys, key)
22
+
23
+ Catche.adapter.write(tag_key, keys << key)
24
+ Catche.adapter.write(key_key, key_tags << tag_key)
25
+ end
26
+ end
27
+
28
+ def expire!(*tags)
29
+ expired_keys = []
30
+
31
+ tags.each do |tag|
32
+ keys = fetch_tag(tag)
33
+ expired_keys += keys
34
+
35
+ keys.each do |key|
36
+ # Expires the cached value
37
+ Catche.adapter.delete key
38
+
39
+ # Removes the tag from the tag list in case it's never used again
40
+ Catche.adapter.write(
41
+ stored_key(:keys, key),
42
+ fetch_key(key).delete(stored_key(:tags, tag))
43
+ )
44
+ end
45
+
46
+ Catche.adapter.delete stored_key(:tags, tag)
47
+ end
48
+
49
+ expired_keys
50
+ end
51
+
52
+ protected
53
+
54
+ def fetch_tag(tag)
55
+ Catche.adapter.read stored_key(:tags, tag), []
56
+ end
57
+
58
+ def fetch_key(key)
59
+ Catche.adapter.read stored_key(:keys, key), []
60
+ end
61
+
62
+ def stored_key(scope, value)
63
+ join_keys KEY, scope.to_s, value.to_s
64
+ end
65
+
66
+ def join_keys(*keys)
67
+ keys.join('.')
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,136 @@
1
+ module Catche
2
+ module Tag
3
+ class Object
4
+
5
+ class << self
6
+
7
+ @@objects = []
8
+
9
+ # Returns an existing (duplicate) or new tag object for the given arguments.
10
+ #
11
+ # Catche::Tag::Object.for(Project, ProjectsController)
12
+ def for(model, object, options={})
13
+ tag_object = find_or_initialize(model, object, options)
14
+
15
+ objects << tag_object
16
+ objects.uniq!
17
+
18
+ tag_object
19
+ end
20
+
21
+ # Finds a previously declared (same) tag object or returns a new one.
22
+ def find_or_initialize(model, object, options={})
23
+ new_object = self.new(model, object, options)
24
+
25
+ objects.each do |tag_object|
26
+ return tag_object if tag_object.same?(new_object)
27
+ end
28
+
29
+ new_object
30
+ end
31
+
32
+ def find_by_model(model)
33
+ objects.select { |tag_object| tag_object.model == model }
34
+ end
35
+
36
+ def find_by_association(association)
37
+ objects.select { |tag_object| tag_object.associations.include?(association) }
38
+ end
39
+
40
+ def clear
41
+ @@objects = []
42
+ end
43
+
44
+ def objects
45
+ @@objects
46
+ end
47
+
48
+ end
49
+
50
+ attr_reader :model, :object, :options
51
+ attr_accessor :associations
52
+
53
+ def initialize(model, object, options={})
54
+ @model = model
55
+ @object = object
56
+
57
+ association = options[:through]
58
+
59
+ @options = {
60
+ :resource_name => Catche::Tag::Resource.singularize(@model),
61
+ :collection_name => Catche::Tag::Resource.pluralize(@model),
62
+ :associations => [association].flatten.compact,
63
+ :bubble => false,
64
+ :expire_collection => true
65
+ }.merge(options)
66
+
67
+ @associations = @options[:associations]
68
+ end
69
+
70
+ # Returns the tags for the given object.
71
+ # If `bubble` is set to true it will pass along the separate tag for each association.
72
+ # This means the collection or resource is expired as soon as the association changes.
73
+ #
74
+ # object = Catche::Tag::Object.new(Task, TasksController, :through => [:user, :project])
75
+ # object.tags(controller.new) => ['users_1_projects_1_tasks_1']
76
+ def tags(initialized_object)
77
+ tags = []
78
+
79
+ tags += association_tags(initialized_object) if bubble?
80
+ tags += expiration_tags(initialized_object)
81
+
82
+ tags
83
+ end
84
+
85
+ # The tags that should expire as soon as the resource or collection changes.
86
+ def expiration_tags(initialized_object)
87
+ tags = []
88
+
89
+ # Add collection tags when enabled
90
+ tags << Catche::Tag.join(
91
+ association_tags(initialized_object),
92
+ options[:collection_name]
93
+ ) if options[:expire_collection]
94
+
95
+ tags << Catche::Tag.join(
96
+ association_tags(initialized_object),
97
+ identifier_tags(initialized_object)
98
+ )
99
+
100
+ tags.uniq
101
+ end
102
+
103
+ # Identifying tag for the current resource or collection.
104
+ #
105
+ # object = Catche::Tag::Object.new(Task, TasksController)
106
+ # object.identifier_tags(controller) => ['tasks', 1]
107
+ def identifier_tags(initialized_object)
108
+ Catche::Tag.join options[:collection_name], Catche::Tag::Resource.resource(initialized_object, options[:resource_name]).try(:id)
109
+ end
110
+
111
+ # Maps the given resources names to tags by fetching the resources from the given object.
112
+ def resource_tags(*resources)
113
+ resources.map { |resource| Catche::Tag.join(Catche::Tag::Resource.pluralize(resource.class), resource.id) }
114
+ end
115
+
116
+ # Maps association tags.
117
+ #
118
+ # object = Catche::Tag::Object.new(Task, TasksController, :through => [:user, :project])
119
+ # object.association_tags(controller) => ['users_1', 'projects_1']
120
+ def association_tags(initialized_object)
121
+ resource_tags(*Catche::Tag::Resource.associations(initialized_object, associations))
122
+ end
123
+
124
+ def bubble?
125
+ options[:bubble]
126
+ end
127
+
128
+ def same?(object)
129
+ self.model == object.model &&
130
+ self.object == object.object &&
131
+ self.options == object.options
132
+ end
133
+
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,35 @@
1
+ module Catche
2
+ module Tag
3
+ class Resource
4
+
5
+ class << self
6
+
7
+ def pluralize(object)
8
+ object.name.to_s.pluralize.downcase
9
+ end
10
+
11
+ def singularize(object)
12
+ object.name.to_s.singularize.downcase
13
+ end
14
+
15
+ def resource(object, name)
16
+ if resource = object.instance_variable_get("@#{name}")
17
+ return resource
18
+ elsif object.respond_to?(name)
19
+ begin
20
+ return object.send(name)
21
+ rescue; end
22
+ end
23
+
24
+ nil
25
+ end
26
+
27
+ def associations(object, associations)
28
+ associations.map { |association| resource(object, association) }.compact
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module Catche
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :catche do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: catche
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Arjen Oosterkamp
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-18 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: &70257700363240 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70257700363240
25
+ - !ruby/object:Gem::Dependency
26
+ name: sqlite3
27
+ requirement: &70257700362820 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70257700362820
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec-rails
38
+ requirement: &70257700362360 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70257700362360
47
+ - !ruby/object:Gem::Dependency
48
+ name: capybara
49
+ requirement: &70257700361940 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70257700361940
58
+ - !ruby/object:Gem::Dependency
59
+ name: guard-rspec
60
+ requirement: &70257700361520 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70257700361520
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-spork
71
+ requirement: &70257700361100 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70257700361100
80
+ description: ''
81
+ email:
82
+ - mail@arjen.me
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - lib/catche/adapter/base.rb
88
+ - lib/catche/adapter.rb
89
+ - lib/catche/controller/base.rb
90
+ - lib/catche/controller.rb
91
+ - lib/catche/model/base.rb
92
+ - lib/catche/model.rb
93
+ - lib/catche/railtie.rb
94
+ - lib/catche/tag/object.rb
95
+ - lib/catche/tag/resource.rb
96
+ - lib/catche/tag.rb
97
+ - lib/catche/version.rb
98
+ - lib/catche.rb
99
+ - lib/tasks/catche_tasks.rake
100
+ - MIT-LICENSE
101
+ - Rakefile
102
+ - README.md
103
+ homepage: http://arjen.me/
104
+ licenses: []
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 1.8.10
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: Smart collection and resource caching
127
+ test_files: []