catche 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []