kasket 0.5.5 → 0.6.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.
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/kasket.rb +8 -7
- data/lib/kasket/active_record_patches.rb +1 -1
- data/lib/kasket/cache.rb +15 -20
- data/lib/kasket/configuration_mixin.rb +39 -3
- data/lib/kasket/dirty_mixin.rb +1 -1
- data/lib/kasket/query_parser.rb +53 -0
- data/lib/kasket/rack_middleware.rb +1 -1
- data/lib/kasket/read_mixin.rb +23 -75
- data/lib/kasket/reload_association_mixin.rb +1 -1
- data/lib/kasket/write_mixin.rb +6 -4
- data/test/cache_expiry_test.rb +10 -16
- data/test/cache_test.rb +102 -0
- data/test/database.yml +2 -0
- data/test/dirty_test.rb +1 -4
- data/test/find_one_test.rb +10 -3
- data/test/find_some_test.rb +6 -10
- data/test/helper.rb +5 -0
- data/test/parser_test.rb +98 -0
- data/test/read_mixin_test.rb +45 -0
- data/test/schema.rb +1 -1
- metadata +11 -6
- data/lib/kasket/conditions_parser.rb +0 -79
- data/test/serialization_test.rb +0 -21
data/Rakefile
CHANGED
@@ -6,10 +6,10 @@ begin
|
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "kasket"
|
8
8
|
gem.summary = %Q{A write back caching layer on active record}
|
9
|
-
gem.description = %Q{
|
9
|
+
gem.description = %Q{put's a cap on your queries}
|
10
10
|
gem.email = "mick@staugaard.com"
|
11
11
|
gem.homepage = "http://github.com/staugaard/kasket"
|
12
|
-
gem.authors = ["Mick Staugaard"]
|
12
|
+
gem.authors = ["Mick Staugaard", "Eric Chapweske"]
|
13
13
|
gem.add_dependency('activerecord', '>= 2.3.4')
|
14
14
|
gem.add_dependency('activesupport', '>= 2.3.4')
|
15
15
|
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.0
|
data/lib/kasket.rb
CHANGED
@@ -9,6 +9,7 @@ module Kasket
|
|
9
9
|
autoload :ConfigurationMixin, 'kasket/configuration_mixin'
|
10
10
|
autoload :ReloadAssociationMixin, 'kasket/reload_association_mixin'
|
11
11
|
autoload :RackMiddleware, 'kasket/rack_middleware'
|
12
|
+
autoload :Query, 'kasket/query'
|
12
13
|
|
13
14
|
CONFIGURATION = {:max_collection_size => 100}
|
14
15
|
|
@@ -26,13 +27,6 @@ module Kasket
|
|
26
27
|
ActiveRecord::Associations::BelongsToPolymorphicAssociation.send(:include, Kasket::ReloadAssociationMixin)
|
27
28
|
ActiveRecord::Associations::HasOneThroughAssociation.send(:include, Kasket::ReloadAssociationMixin)
|
28
29
|
|
29
|
-
#sets up local cache clearing on rack
|
30
|
-
begin
|
31
|
-
ActionController::Dispatcher.middleware.use(Kasket::RackMiddleware)
|
32
|
-
rescue NameError => e
|
33
|
-
puts('WARNING: The kasket rack middleware is not in your rack stack')
|
34
|
-
end
|
35
|
-
|
36
30
|
#sets up local cache clearing before each request.
|
37
31
|
#this is done to make it work for non rack rails and for functional tests
|
38
32
|
begin
|
@@ -42,6 +36,13 @@ module Kasket
|
|
42
36
|
rescue NameError => e
|
43
37
|
end
|
44
38
|
|
39
|
+
#sets up local cache clearing on rack
|
40
|
+
begin
|
41
|
+
ActionController::Dispatcher.middleware.use(Kasket::RackMiddleware)
|
42
|
+
rescue NameError => e
|
43
|
+
puts('WARNING: The kasket rack middleware is not in your rack stack')
|
44
|
+
end
|
45
|
+
|
45
46
|
#sets up local cache clearing after each test case
|
46
47
|
begin
|
47
48
|
ActiveSupport::TestCase.class_eval do
|
data/lib/kasket/cache.rb
CHANGED
@@ -6,11 +6,9 @@ module Kasket
|
|
6
6
|
|
7
7
|
def read(*args)
|
8
8
|
result = @local_cache[args[0]] || Rails.cache.read(*args)
|
9
|
-
if result.is_a?(
|
10
|
-
result = result.instanciate_model
|
11
|
-
elsif result.is_a?(Array)
|
9
|
+
if result.is_a?(Array) && result.first.is_a?(String)
|
12
10
|
models = get_multi(result)
|
13
|
-
result = result.map { |key| models[key]}
|
11
|
+
result = result.map { |key| models[key] }
|
14
12
|
end
|
15
13
|
|
16
14
|
@local_cache[args[0]] = result if result
|
@@ -25,7 +23,6 @@ module Kasket
|
|
25
23
|
if Rails.cache.respond_to?(:read_multi)
|
26
24
|
missing_map = Rails.cache.read_multi(missing_keys)
|
27
25
|
missing_map.each do |key, value|
|
28
|
-
value = value.instanciate_model if value.is_a?(CachedModel)
|
29
26
|
missing_map[key] = @local_cache[key] = value
|
30
27
|
end
|
31
28
|
map.merge!(missing_map)
|
@@ -39,14 +36,12 @@ module Kasket
|
|
39
36
|
map
|
40
37
|
end
|
41
38
|
|
42
|
-
def write(
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
args[1] = CachedModel.new(args[1])
|
39
|
+
def write(key, value)
|
40
|
+
if storable?(value)
|
41
|
+
@local_cache[key] = value.duplicable? ? value.dup : value
|
42
|
+
Rails.cache.write(key, value.duplicable? ? value.dup : value) # Fix due to Rails.cache freezing values in 2.3.4
|
47
43
|
end
|
48
|
-
|
49
|
-
Rails.cache.write(*args)
|
44
|
+
value
|
50
45
|
end
|
51
46
|
|
52
47
|
def delete(*args)
|
@@ -68,15 +63,15 @@ module Kasket
|
|
68
63
|
@local_cache = {}
|
69
64
|
end
|
70
65
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
@attributes = model.instance_variable_get(:@attributes)
|
75
|
-
end
|
66
|
+
def local
|
67
|
+
@local_cache
|
68
|
+
end
|
76
69
|
|
77
|
-
|
78
|
-
|
70
|
+
protected
|
71
|
+
|
72
|
+
def storable?(value)
|
73
|
+
!value.is_a?(Array) || value.size <= Kasket::CONFIGURATION[:max_collection_size]
|
79
74
|
end
|
80
|
-
|
75
|
+
|
81
76
|
end
|
82
77
|
end
|
@@ -4,14 +4,50 @@ module Kasket
|
|
4
4
|
autoload :ReadMixin, 'kasket/read_mixin'
|
5
5
|
autoload :WriteMixin, 'kasket/write_mixin'
|
6
6
|
autoload :DirtyMixin, 'kasket/dirty_mixin'
|
7
|
+
autoload :QueryParser, 'kasket/query_parser'
|
7
8
|
|
8
9
|
module ConfigurationMixin
|
10
|
+
|
11
|
+
def without_kasket(&block)
|
12
|
+
old_value = @use_kasket || true
|
13
|
+
@use_kasket = false
|
14
|
+
yield
|
15
|
+
ensure
|
16
|
+
@use_kasket = old_value
|
17
|
+
end
|
18
|
+
|
19
|
+
def use_kasket?
|
20
|
+
@use_kasket != false
|
21
|
+
end
|
22
|
+
|
23
|
+
def kasket_parser
|
24
|
+
@kasket_parser ||= QueryParser.new(self)
|
25
|
+
end
|
26
|
+
|
9
27
|
def kasket_key_prefix
|
10
28
|
@kasket_key_prefix ||= "kasket/#{table_name}/version=#{column_names.join.sum}/"
|
11
29
|
end
|
12
30
|
|
13
31
|
def kasket_key_for(attribute_value_pairs)
|
14
|
-
kasket_key_prefix + attribute_value_pairs.map
|
32
|
+
kasket_key_prefix + attribute_value_pairs.map do |attribute, value|
|
33
|
+
if (column = columns_hash[attribute.to_s]) && column.number?
|
34
|
+
attribute.to_s + '=' + convert_number_column_value(value.to_s)
|
35
|
+
else
|
36
|
+
attribute.to_s + '=' + connection.quote(value.to_s)
|
37
|
+
end
|
38
|
+
end.join('/')
|
39
|
+
end
|
40
|
+
|
41
|
+
def convert_number_column_value(value)
|
42
|
+
if value == false
|
43
|
+
0
|
44
|
+
elsif value == true
|
45
|
+
1
|
46
|
+
elsif value.is_a?(String) && value.blank?
|
47
|
+
nil
|
48
|
+
else
|
49
|
+
value
|
50
|
+
end
|
15
51
|
end
|
16
52
|
|
17
53
|
def kasket_key_for_id(id)
|
@@ -42,8 +78,8 @@ module Kasket
|
|
42
78
|
@kasket_indices << attributes unless @kasket_indices.include?(attributes)
|
43
79
|
|
44
80
|
include WriteMixin unless instance_methods.include?('store_in_kasket')
|
45
|
-
extend ReadMixin unless methods.include?('without_kasket')
|
46
81
|
extend DirtyMixin unless methods.include?('kasket_dirty_methods')
|
82
|
+
extend ReadMixin unless methods.include?('find_by_sql_with_kasket')
|
47
83
|
end
|
48
84
|
end
|
49
|
-
end
|
85
|
+
end
|
data/lib/kasket/dirty_mixin.rb
CHANGED
@@ -0,0 +1,53 @@
|
|
1
|
+
module Kasket
|
2
|
+
class QueryParser
|
3
|
+
# Examples:
|
4
|
+
# SELECT * FROM `users` WHERE (`users`.`id` = 2)
|
5
|
+
# SELECT * FROM `users` WHERE (`users`.`id` = 2) LIMIT 1
|
6
|
+
# 'SELECT * FROM \'posts\' WHERE (\'posts\'.\'id\' = 574019247) '
|
7
|
+
|
8
|
+
AND = /\s+AND\s+/i
|
9
|
+
VALUE = /'?(\d+|\?|(?:(?:[^']|''|\\')*))'?/ # Matches: 123, ?, '123', '12''3'
|
10
|
+
|
11
|
+
def initialize(model_class)
|
12
|
+
@model_class = model_class
|
13
|
+
@supported_query_pattern = /^select \* from (?:`|")#{@model_class.table_name}(?:`|") where \((.*)\)(|\s+limit 1)\s*$/i
|
14
|
+
@table_and_column_pattern = /(?:(?:`|")?#{@model_class.table_name}(?:`|")?\.)?(?:`|")?(\w+)(?:`|")?/ # Matches: `users`.id, `users`.`id`, users.id, id
|
15
|
+
@key_eq_value_pattern = /^[\(\s]*#{@table_and_column_pattern}\s+=\s+#{VALUE}[\)\s]*$/ # Matches: KEY = VALUE, (KEY = VALUE), ()(KEY = VALUE))
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse(sql)
|
19
|
+
if match = @supported_query_pattern.match(sql)
|
20
|
+
query = Hash.new
|
21
|
+
query[:attributes] = sorted_attribute_value_pairs(match[1])
|
22
|
+
return nil if query[:attributes].nil?
|
23
|
+
query[:index] = query[:attributes].map(&:first)
|
24
|
+
query[:limit] = match[2].blank? ? nil : 1
|
25
|
+
query[:key] = @model_class.kasket_key_for(query[:attributes])
|
26
|
+
query[:key] << '/first' if query[:limit] == 1 && !query[:index].include?(:id)
|
27
|
+
query
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def sorted_attribute_value_pairs(conditions)
|
34
|
+
if attributes = parse_condition(conditions)
|
35
|
+
attributes.sort { |pair1, pair2| pair1[0].to_s <=> pair2[0].to_s }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_condition(conditions = '', *values)
|
40
|
+
values = values.dup
|
41
|
+
conditions.split(AND).inject([]) do |pairs, condition|
|
42
|
+
matched, column_name, sql_value = *(@key_eq_value_pattern.match(condition))
|
43
|
+
if matched
|
44
|
+
value = sql_value == '?' ? values.shift : sql_value
|
45
|
+
pairs << [column_name.to_sym, value.gsub(/''|\\'/, "'")]
|
46
|
+
else
|
47
|
+
return nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
data/lib/kasket/read_mixin.rb
CHANGED
@@ -1,92 +1,40 @@
|
|
1
|
-
require 'kasket/conditions_parser'
|
2
|
-
|
3
1
|
module Kasket
|
4
2
|
module ReadMixin
|
5
3
|
|
6
|
-
def self.extended(
|
7
|
-
class <<
|
8
|
-
alias_method_chain :
|
9
|
-
alias_method_chain :find_every, :kasket unless methods.include?('find_every_without_kasket')
|
4
|
+
def self.extended(base)
|
5
|
+
class << base
|
6
|
+
alias_method_chain :find_by_sql, :kasket
|
10
7
|
end
|
11
8
|
end
|
12
9
|
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def find_every_with_kasket(options)
|
22
|
-
attribute_value_pairs = kasket_conditions_parser.attribute_value_pairs(options) if cache_safe?(options)
|
23
|
-
|
24
|
-
limit = (options[:limit] || (scope(:find) || {})[:limit])
|
25
|
-
|
26
|
-
if (limit.nil? || limit == 1) && attribute_value_pairs && has_kasket_index_on?(attribute_value_pairs.map(&:first))
|
27
|
-
collection_key = kasket_key_for(attribute_value_pairs)
|
28
|
-
collection_key << '/first' if limit == 1
|
29
|
-
unless records = Kasket.cache.read(collection_key)
|
30
|
-
records = without_kasket do
|
31
|
-
find_every_without_kasket(options)
|
32
|
-
end
|
33
|
-
|
34
|
-
if records.size == 1
|
35
|
-
Kasket.cache.write(collection_key, records[0])
|
36
|
-
elsif records.size <= Kasket::CONFIGURATION[:max_collection_size]
|
37
|
-
records.each { |record| record.store_in_kasket if record }
|
38
|
-
Kasket.cache.write(collection_key, records.map(&:kasket_key))
|
39
|
-
end
|
10
|
+
def find_by_sql_with_kasket(sql)
|
11
|
+
query = kasket_parser.parse(sql) if use_kasket?
|
12
|
+
if query && has_kasket_index_on?(query[:index])
|
13
|
+
if value = Kasket.cache.read(query[:key])
|
14
|
+
Array.wrap(value).collect! { |record| instantiate(record.clone) }
|
15
|
+
else
|
16
|
+
store_in_kasket(query[:key], find_by_sql_without_kasket(sql))
|
40
17
|
end
|
41
|
-
|
42
|
-
Array(records)
|
43
18
|
else
|
44
|
-
|
45
|
-
find_every_without_kasket(options)
|
46
|
-
end
|
19
|
+
find_by_sql_without_kasket(sql)
|
47
20
|
end
|
48
21
|
end
|
49
22
|
|
50
|
-
|
51
|
-
attribute_value_pairs = kasket_conditions_parser.attribute_value_pairs(options) if cache_safe?(options)
|
52
|
-
attribute_value_pairs << [:id, ids] if attribute_value_pairs
|
53
|
-
|
54
|
-
limit = (options[:limit] || (scope(:find) || {})[:limit])
|
55
|
-
|
56
|
-
if limit.nil? && attribute_value_pairs && has_kasket_index_on?(attribute_value_pairs.map(&:first))
|
57
|
-
id_to_key_map = Hash[*ids.uniq.map { |id| [id, kasket_key_for_id(id)] }.flatten]
|
58
|
-
cached_record_map = Kasket.cache.get_multi(id_to_key_map.values)
|
59
|
-
|
60
|
-
missing_keys = cached_record_map.select { |key, record| record.nil? }.map(&:first)
|
23
|
+
protected
|
61
24
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
else
|
73
|
-
without_kasket do
|
74
|
-
find_some_without_kasket(ids, options)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
def cache_safe?(options)
|
82
|
-
@use_kasket != false && [options, scope(:find) || {}].all? do |hash|
|
83
|
-
result = hash[:select].nil? && hash[:joins].nil? && hash[:order].nil? && hash[:offset].nil?
|
84
|
-
result && (options[:limit].nil? || options[:limit] == 1)
|
25
|
+
def store_in_kasket(key, records)
|
26
|
+
if records.size == 1
|
27
|
+
Kasket.cache.write(key, records.first.instance_variable_get(:@attributes))
|
28
|
+
else
|
29
|
+
keys = records.map do |record|
|
30
|
+
key = kasket_key_for_id(record.id)
|
31
|
+
Kasket.cache.write(key, record.instance_variable_get(:@attributes))
|
32
|
+
key
|
33
|
+
end
|
34
|
+
Kasket.cache.write(key, keys)
|
85
35
|
end
|
36
|
+
records
|
86
37
|
end
|
87
38
|
|
88
|
-
def kasket_conditions_parser
|
89
|
-
@kasket_conditions_parser ||= Kasket::ConditionsParser.new(self)
|
90
|
-
end
|
91
39
|
end
|
92
40
|
end
|
data/lib/kasket/write_mixin.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module Kasket
|
2
2
|
module WriteMixin
|
3
|
+
|
3
4
|
module ClassMethods
|
4
5
|
def remove_from_kasket(ids)
|
5
6
|
Array(ids).each do |id|
|
@@ -20,7 +21,7 @@ module Kasket
|
|
20
21
|
|
21
22
|
def store_in_kasket
|
22
23
|
if !readonly? && kasket_key
|
23
|
-
Kasket.cache.write(kasket_key,
|
24
|
+
Kasket.cache.write(kasket_key, @attributes)
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
@@ -35,13 +36,14 @@ module Kasket
|
|
35
36
|
keys = []
|
36
37
|
self.class.kasket_indices.each do |index|
|
37
38
|
keys += attribute_sets.map do |attribute_set|
|
38
|
-
self.class.kasket_key_for(index.map { |attribute| [attribute, attribute_set[attribute]]})
|
39
|
+
key = self.class.kasket_key_for(index.map { |attribute| [attribute, attribute_set[attribute]]})
|
40
|
+
index.include?(:id) ? key : [key, key + '/first']
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
|
-
keys.uniq!
|
43
|
-
keys.map! {|key| [key, "#{key}/first"]}
|
44
44
|
keys.flatten!
|
45
|
+
keys.uniq!
|
46
|
+
keys
|
45
47
|
end
|
46
48
|
|
47
49
|
def clear_kasket_indices
|
data/test/cache_expiry_test.rb
CHANGED
@@ -3,14 +3,11 @@ require File.dirname(__FILE__) + '/helper'
|
|
3
3
|
class CacheExpiryTest < ActiveSupport::TestCase
|
4
4
|
fixtures :blogs, :posts
|
5
5
|
|
6
|
-
Post.has_kasket
|
7
|
-
Post.has_kasket_on :title
|
8
|
-
Post.has_kasket_on :blog_id
|
9
|
-
|
10
6
|
context "a cached object" do
|
11
7
|
setup do
|
12
8
|
post = Post.first
|
13
9
|
@post = Post.find(post.id)
|
10
|
+
|
14
11
|
assert(Rails.cache.read(@post.kasket_key))
|
15
12
|
end
|
16
13
|
|
@@ -21,11 +18,9 @@ class CacheExpiryTest < ActiveSupport::TestCase
|
|
21
18
|
|
22
19
|
should "clear all indices for instance when deleted" do
|
23
20
|
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "id=#{@post.id}")
|
24
|
-
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "
|
25
|
-
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "title
|
26
|
-
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "
|
27
|
-
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "blog_id=#{@post.blog_id}")
|
28
|
-
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "blog_id=#{@post.blog_id}/first")
|
21
|
+
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "title='#{@post.title}'")
|
22
|
+
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "title='#{@post.title}'/first")
|
23
|
+
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "blog_id=#{@post.blog_id}/id=#{@post.id}")
|
29
24
|
Kasket.cache.expects(:delete).never
|
30
25
|
|
31
26
|
@post.destroy
|
@@ -39,17 +34,16 @@ class CacheExpiryTest < ActiveSupport::TestCase
|
|
39
34
|
|
40
35
|
should "clear all indices for instance when updated" do
|
41
36
|
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "id=#{@post.id}")
|
42
|
-
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "
|
43
|
-
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "title
|
44
|
-
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "title
|
45
|
-
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "title=new title")
|
46
|
-
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "
|
47
|
-
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "blog_id=#{@post.blog_id}")
|
48
|
-
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "blog_id=#{@post.blog_id}/first")
|
37
|
+
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "title='#{@post.title}'")
|
38
|
+
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "title='#{@post.title}'/first")
|
39
|
+
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "title='new title'")
|
40
|
+
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "title='new title'/first")
|
41
|
+
Kasket.cache.expects(:delete).with(Post.kasket_key_prefix + "blog_id=#{@post.blog_id}/id=#{@post.id}")
|
49
42
|
Kasket.cache.expects(:delete).never
|
50
43
|
|
51
44
|
@post.title = "new title"
|
52
45
|
@post.save
|
53
46
|
end
|
47
|
+
|
54
48
|
end
|
55
49
|
end
|
data/test/cache_test.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class CacheTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
context "Cache" do
|
6
|
+
setup do
|
7
|
+
@cache = Kasket::Cache.new
|
8
|
+
end
|
9
|
+
|
10
|
+
context "reading" do
|
11
|
+
|
12
|
+
should "work with non collection values" do
|
13
|
+
@cache.write('key', 'value')
|
14
|
+
assert_equal 'value', @cache.read('key')
|
15
|
+
end
|
16
|
+
|
17
|
+
should "fetch original results of stored collections" do
|
18
|
+
@cache.write('key1', 'value1')
|
19
|
+
@cache.write('key2', 'value2')
|
20
|
+
@cache.write('key3', 'value3')
|
21
|
+
@cache.write('collection_key', [ 'key1', 'key2', 'key3'])
|
22
|
+
|
23
|
+
assert_equal [ 'value1', 'value2', 'value3'], @cache.read('collection_key')
|
24
|
+
end
|
25
|
+
|
26
|
+
should "not impact original object" do
|
27
|
+
record = { 'id' => 1, 'color' => 'red' }
|
28
|
+
@cache.write('key', record)
|
29
|
+
record['id'] = 2
|
30
|
+
|
31
|
+
assert_not_equal record, @cache.read('key')
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
context "writing" do
|
37
|
+
setup do
|
38
|
+
@cache.write('key', 'value')
|
39
|
+
end
|
40
|
+
|
41
|
+
should "store the object locally" do
|
42
|
+
assert_equal 'value', @cache.local['key']
|
43
|
+
end
|
44
|
+
|
45
|
+
should "persist the object" do
|
46
|
+
@cache.clear_local
|
47
|
+
assert_equal 'value', @cache.read('key')
|
48
|
+
end
|
49
|
+
|
50
|
+
should "respect max collection size" do
|
51
|
+
original_max = Kasket::CONFIGURATION[:max_collection_size]
|
52
|
+
Kasket::CONFIGURATION[:max_collection_size] = 2
|
53
|
+
|
54
|
+
@cache.write('key', [ 'a', 'b'])
|
55
|
+
assert_equal 2, @cache.read('key').size
|
56
|
+
|
57
|
+
@cache.write('key2', ['a', 'b', 'c'])
|
58
|
+
assert_equal nil, @cache.read('key2')
|
59
|
+
|
60
|
+
Kasket::CONFIGURATION[:max_collection_size] = original_max
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
should "delete" do
|
66
|
+
@cache.write('key', 'value')
|
67
|
+
@cache.delete('key')
|
68
|
+
|
69
|
+
assert_equal nil, @cache.local['key']
|
70
|
+
assert_equal nil, @cache.read('key')
|
71
|
+
end
|
72
|
+
|
73
|
+
should "delete matched local" do
|
74
|
+
@cache.write('key1', 'value1')
|
75
|
+
@cache.write('key2', 'value2')
|
76
|
+
@cache.delete_matched_local(/2/)
|
77
|
+
|
78
|
+
assert_equal nil, @cache.local['key2']
|
79
|
+
assert_equal 'value1', @cache.local['key1']
|
80
|
+
assert_equal 'value2', @cache.read('key2')
|
81
|
+
end
|
82
|
+
|
83
|
+
should "delete local" do
|
84
|
+
@cache.write('key1', 'value1')
|
85
|
+
@cache.write('key2', 'value2')
|
86
|
+
@cache.delete_local('key1', 'key2')
|
87
|
+
|
88
|
+
assert_equal nil, @cache.local['key1']
|
89
|
+
assert_equal nil, @cache.local['key2']
|
90
|
+
assert_equal 'value1', @cache.read('key1')
|
91
|
+
assert_equal 'value2', @cache.read('key2')
|
92
|
+
end
|
93
|
+
|
94
|
+
should "clear local" do
|
95
|
+
@cache.write('key1', 'value1')
|
96
|
+
@cache.clear_local
|
97
|
+
|
98
|
+
assert @cache.local.blank?
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
data/test/database.yml
CHANGED
data/test/dirty_test.rb
CHANGED
@@ -3,13 +3,10 @@ require File.dirname(__FILE__) + '/helper'
|
|
3
3
|
class DirtyTest < ActiveSupport::TestCase
|
4
4
|
fixtures :blogs, :posts
|
5
5
|
|
6
|
-
Post.has_kasket
|
7
|
-
Post.kasket_dirty_methods :make_dirty!
|
8
|
-
|
9
6
|
should "clear the indices when a dirty method is called" do
|
10
7
|
post = Post.first
|
11
8
|
|
12
|
-
pots = Post.find(post.id)
|
9
|
+
Post.cache { pots = Post.find(post.id) }
|
13
10
|
assert(Rails.cache.read(post.kasket_key))
|
14
11
|
|
15
12
|
post.make_dirty!
|
data/test/find_one_test.rb
CHANGED
@@ -3,17 +3,24 @@ require File.dirname(__FILE__) + '/helper'
|
|
3
3
|
class FindOneTest < ActiveSupport::TestCase
|
4
4
|
fixtures :blogs, :posts
|
5
5
|
|
6
|
-
Post.has_kasket
|
7
|
-
|
8
6
|
should "cache find(id) calls" do
|
9
7
|
post = Post.first
|
10
|
-
|
8
|
+
Rails.cache.write(post.kasket_key, nil)
|
11
9
|
assert_equal(post, Post.find(post.id))
|
12
10
|
assert(Rails.cache.read(post.kasket_key))
|
13
11
|
Post.connection.expects(:select_all).never
|
14
12
|
assert_equal(post, Post.find(post.id))
|
15
13
|
end
|
16
14
|
|
15
|
+
should "only cache on indexed attributes" do
|
16
|
+
Kasket.cache.expects(:read).twice
|
17
|
+
Post.find_by_id(1)
|
18
|
+
Post.find_by_id(1, :conditions => {:blog_id => 2})
|
19
|
+
|
20
|
+
Kasket.cache.expects(:read).never
|
21
|
+
Post.first :conditions => {:blog_id => 2}
|
22
|
+
end
|
23
|
+
|
17
24
|
should "not use cache when using the :select option" do
|
18
25
|
post = Post.first
|
19
26
|
assert_nil(Rails.cache.read(post.kasket_key))
|
data/test/find_some_test.rb
CHANGED
@@ -3,10 +3,7 @@ require File.dirname(__FILE__) + '/helper'
|
|
3
3
|
class FindSomeTest < ActiveSupport::TestCase
|
4
4
|
fixtures :blogs, :posts
|
5
5
|
|
6
|
-
|
7
|
-
Post.has_kasket_on :blog_id
|
8
|
-
|
9
|
-
should "cache find(id, id) calls" do
|
6
|
+
should_eventually "cache find(id, id) calls" do
|
10
7
|
post1 = Post.first
|
11
8
|
post2 = Post.last
|
12
9
|
|
@@ -17,32 +14,31 @@ class FindSomeTest < ActiveSupport::TestCase
|
|
17
14
|
|
18
15
|
assert(Rails.cache.read(post1.kasket_key))
|
19
16
|
assert(Rails.cache.read(post2.kasket_key))
|
20
|
-
|
21
17
|
Post.connection.expects(:select_all).never
|
22
18
|
Post.find(post1.id, post2.id)
|
23
19
|
end
|
24
20
|
|
25
|
-
|
21
|
+
should_eventually "only lookup the records that are not in the cache" do
|
26
22
|
post1 = Post.first
|
27
23
|
post2 = Post.last
|
28
24
|
assert_equal(post1, Post.find(post1.id))
|
29
25
|
assert(Rails.cache.read(post1.kasket_key))
|
30
26
|
assert_nil(Rails.cache.read(post2.kasket_key))
|
31
27
|
|
32
|
-
Post.expects(:
|
28
|
+
Post.expects(:find_by_sql_without_kasket).with("SELECT * FROM \'posts\' WHERE (\'posts\'.\'id\' IN (#{post1.id},#{post2.id})) ").returns([post2])
|
33
29
|
found_posts = Post.find(post1.id, post2.id)
|
34
30
|
assert_equal([post1, post2].map(&:id).sort, found_posts.map(&:id).sort)
|
35
31
|
|
36
|
-
Post.expects(:
|
32
|
+
Post.expects(:find_by_sql_without_kasket).never
|
37
33
|
found_posts = Post.find(post1.id, post2.id)
|
38
34
|
assert_equal([post1, post2].map(&:id).sort, found_posts.map(&:id).sort)
|
39
35
|
end
|
40
36
|
|
41
|
-
|
37
|
+
should_eventually "cache on index other than primary key" do
|
42
38
|
blog = blogs(:a_blog)
|
43
39
|
posts = Post.find_all_by_blog_id(blog.id)
|
44
40
|
|
45
|
-
Post.expects(:
|
41
|
+
Post.expects(:find_by_sql_without_kasket).never
|
46
42
|
|
47
43
|
assert_equal(posts, Post.find_all_by_blog_id(blog.id))
|
48
44
|
end
|
data/test/helper.rb
CHANGED
@@ -56,10 +56,15 @@ class Post < ActiveRecord::Base
|
|
56
56
|
belongs_to :blog
|
57
57
|
has_many :comments
|
58
58
|
|
59
|
+
has_kasket
|
60
|
+
has_kasket_on :title
|
61
|
+
has_kasket_on :blog_id, :id
|
62
|
+
|
59
63
|
def make_dirty!
|
60
64
|
self.updated_at = Time.now
|
61
65
|
self.connection.execute("UPDATE posts SET updated_at = '#{updated_at.utc.to_s(:db)}' WHERE id = #{id}")
|
62
66
|
end
|
67
|
+
kasket_dirty_methods :make_dirty!
|
63
68
|
end
|
64
69
|
|
65
70
|
class Blog < ActiveRecord::Base
|
data/test/parser_test.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
require 'kasket/query_parser'
|
3
|
+
|
4
|
+
class ParserTest < ActiveSupport::TestCase
|
5
|
+
|
6
|
+
context "Parsing" do
|
7
|
+
setup do
|
8
|
+
@parser = Kasket::QueryParser.new(Post)
|
9
|
+
end
|
10
|
+
|
11
|
+
should "extract conditions" do
|
12
|
+
assert_equal [[:color, "red"], [:size, "big"]], @parser.parse('SELECT * FROM `posts` WHERE (`posts`.`color` = red AND `posts`.`size` = big)')[:attributes]
|
13
|
+
end
|
14
|
+
|
15
|
+
should "extract required index" do
|
16
|
+
assert_equal [:color, :size], @parser.parse('SELECT * FROM `posts` WHERE (`posts`.`color` = red AND `posts`.`size` = big)')[:index]
|
17
|
+
end
|
18
|
+
|
19
|
+
should "only support queries against its model's table" do
|
20
|
+
assert !@parser.parse('SELECT * FROM `apples` WHERE (`users`.`id` = 2) ')
|
21
|
+
end
|
22
|
+
|
23
|
+
should "support cachable queries" do
|
24
|
+
assert @parser.parse('SELECT * FROM `posts` WHERE (`posts`.`id` = 2) ')
|
25
|
+
assert @parser.parse('SELECT * FROM `posts` WHERE (`posts`.`id` = 2) LIMIT 1')
|
26
|
+
end
|
27
|
+
|
28
|
+
should "support vaguely formatted queries" do
|
29
|
+
assert @parser.parse('SELECT * FROM "posts" WHERE (color = red AND size = big)')
|
30
|
+
end
|
31
|
+
|
32
|
+
context "extract options" do
|
33
|
+
|
34
|
+
should "provide the limit" do
|
35
|
+
sql = 'SELECT * FROM `posts` WHERE (`posts`.`id` = 2)'
|
36
|
+
assert_equal nil, @parser.parse(sql)[:limit]
|
37
|
+
|
38
|
+
sql << ' LIMIT 1'
|
39
|
+
assert_equal 1, @parser.parse(sql)[:limit]
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
context "unsupported queries" do
|
45
|
+
|
46
|
+
should "include advanced limits" do
|
47
|
+
assert !@parser.parse('SELECT * FROM `posts` WHERE (color = red AND size = big) LIMIT 2')
|
48
|
+
end
|
49
|
+
|
50
|
+
should "include joins" do
|
51
|
+
assert !@parser.parse('SELECT * FROM `posts`, `trees` JOIN ON apple.tree_id = tree.id WHERE (color = red)')
|
52
|
+
end
|
53
|
+
|
54
|
+
should "include specific selects" do
|
55
|
+
assert !@parser.parse('SELECT id FROM `posts` WHERE (color = red)')
|
56
|
+
end
|
57
|
+
|
58
|
+
should "include offset" do
|
59
|
+
assert !@parser.parse('SELECT * FROM `posts` WHERE (color = red) LIMIT 1 OFFSET 2')
|
60
|
+
end
|
61
|
+
|
62
|
+
should "include order" do
|
63
|
+
assert !@parser.parse('SELECT * FROM `posts` WHERE (color = red) ORDER DESC')
|
64
|
+
end
|
65
|
+
|
66
|
+
should "include the OR operator" do
|
67
|
+
assert !@parser.parse('SELECT * FROM `posts` WHERE (color = red OR size = big) LIMIT 2')
|
68
|
+
end
|
69
|
+
|
70
|
+
should "include the IN operator" do
|
71
|
+
assert !@parser.parse('SELECT * FROM `posts` WHERE (id IN (1,2,3))')
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
context "key generation" do
|
77
|
+
should "include the table name and version" do
|
78
|
+
assert_match(/^kasket\/posts\/version=3558\//, @parser.parse('SELECT * FROM `posts` WHERE (id = 1)')[:key])
|
79
|
+
end
|
80
|
+
|
81
|
+
should "include all indexed attributes" do
|
82
|
+
assert_match(/id=1$/, @parser.parse('SELECT * FROM `posts` WHERE (id = 1)')[:key])
|
83
|
+
assert_match(/blog_id=2\/id=1$/, @parser.parse('SELECT * FROM `posts` WHERE (id = 1 AND blog_id = 2)')[:key])
|
84
|
+
assert_match(/id=1\/title='world\\'s best title'$/, @parser.parse("SELECT * FROM `posts` WHERE (id = 1 AND title = 'world\\'s best title')")[:key])
|
85
|
+
end
|
86
|
+
|
87
|
+
context "when limit 1" do
|
88
|
+
should "add /first to the key if the index does not include id" do
|
89
|
+
assert_match(/title='a'\/first$/, @parser.parse("SELECT * FROM `posts` WHERE (title = 'a') LIMIT 1")[:key])
|
90
|
+
end
|
91
|
+
should "not add /first to the key when the index includes id" do
|
92
|
+
assert_match(/id=1$/, @parser.parse("SELECT * FROM `posts` WHERE (id = 1) LIMIT 1")[:key])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class ReadMixinTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
context "find by sql with kasket" do
|
6
|
+
setup do
|
7
|
+
@database_results = [ { 'id' => 1, 'title' => 'Hello' }, { 'id' => 2, 'title' => 'World' } ]
|
8
|
+
@records = @database_results.map { |r| Post.send(:instantiate, r) }
|
9
|
+
Post.stubs(:find_by_sql_without_kasket).returns(@records)
|
10
|
+
end
|
11
|
+
|
12
|
+
should "handle unsupported sql" do
|
13
|
+
assert_equal @records, Post.find_by_sql_with_kasket('select unsupported sql statement')
|
14
|
+
assert Kasket.cache.local.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
should "read results" do
|
18
|
+
Kasket.cache.write('kasket/posts/version=3558/id=1', @database_results.first)
|
19
|
+
assert_equal [ @records.first ], Post.find_by_sql('SELECT * FROM `posts` WHERE (id = 1)'), Kasket.cache.inspect
|
20
|
+
end
|
21
|
+
|
22
|
+
should "store results in kasket" do
|
23
|
+
Post.find_by_sql('SELECT * FROM `posts` WHERE (id = 1)')
|
24
|
+
|
25
|
+
assert_equal @database_results.first, Kasket.cache.read('kasket/posts/version=3558/id=1'), Kasket.cache.inspect
|
26
|
+
end
|
27
|
+
|
28
|
+
context "modifying results" do
|
29
|
+
setup do
|
30
|
+
Kasket.cache.write('kasket/posts/version=3558/id=1', @database_results.first)
|
31
|
+
@record = Post.find_by_sql('SELECT * FROM `posts` WHERE (id = 1)').first
|
32
|
+
@record.instance_variable_get(:@attributes)['id'] = 3
|
33
|
+
end
|
34
|
+
|
35
|
+
should "not impact other queries" do
|
36
|
+
same_record = Post.find_by_sql('SELECT * FROM `posts` WHERE (id = 1)').first
|
37
|
+
|
38
|
+
assert_not_equal @record, same_record
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/test/schema.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# This file is auto-generated from the current state of the database. Instead of editing this file,
|
1
|
+
# This file is auto-generated from the current state of the database. Instead of editing this file,
|
2
2
|
# please use the migrations feature of Active Record to incrementally modify your database, and
|
3
3
|
# then regenerate this schema definition.
|
4
4
|
#
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kasket
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mick Staugaard
|
8
|
+
- Eric Chapweske
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
12
|
|
12
|
-
date: 2009-12-
|
13
|
+
date: 2009-12-05 00:00:00 -08:00
|
13
14
|
default_executable:
|
14
15
|
dependencies:
|
15
16
|
- !ruby/object:Gem::Dependency
|
@@ -52,7 +53,7 @@ dependencies:
|
|
52
53
|
- !ruby/object:Gem::Version
|
53
54
|
version: "0"
|
54
55
|
version:
|
55
|
-
description:
|
56
|
+
description: put's a cap on your queries
|
56
57
|
email: mick@staugaard.com
|
57
58
|
executables: []
|
58
59
|
|
@@ -71,14 +72,15 @@ files:
|
|
71
72
|
- lib/kasket.rb
|
72
73
|
- lib/kasket/active_record_patches.rb
|
73
74
|
- lib/kasket/cache.rb
|
74
|
-
- lib/kasket/conditions_parser.rb
|
75
75
|
- lib/kasket/configuration_mixin.rb
|
76
76
|
- lib/kasket/dirty_mixin.rb
|
77
|
+
- lib/kasket/query_parser.rb
|
77
78
|
- lib/kasket/rack_middleware.rb
|
78
79
|
- lib/kasket/read_mixin.rb
|
79
80
|
- lib/kasket/reload_association_mixin.rb
|
80
81
|
- lib/kasket/write_mixin.rb
|
81
82
|
- test/cache_expiry_test.rb
|
83
|
+
- test/cache_test.rb
|
82
84
|
- test/database.yml
|
83
85
|
- test/dirty_test.rb
|
84
86
|
- test/find_one_test.rb
|
@@ -87,8 +89,9 @@ files:
|
|
87
89
|
- test/fixtures/comments.yml
|
88
90
|
- test/fixtures/posts.yml
|
89
91
|
- test/helper.rb
|
92
|
+
- test/parser_test.rb
|
93
|
+
- test/read_mixin_test.rb
|
90
94
|
- test/schema.rb
|
91
|
-
- test/serialization_test.rb
|
92
95
|
has_rdoc: true
|
93
96
|
homepage: http://github.com/staugaard/kasket
|
94
97
|
licenses: []
|
@@ -119,9 +122,11 @@ specification_version: 3
|
|
119
122
|
summary: A write back caching layer on active record
|
120
123
|
test_files:
|
121
124
|
- test/cache_expiry_test.rb
|
125
|
+
- test/cache_test.rb
|
122
126
|
- test/dirty_test.rb
|
123
127
|
- test/find_one_test.rb
|
124
128
|
- test/find_some_test.rb
|
125
129
|
- test/helper.rb
|
130
|
+
- test/parser_test.rb
|
131
|
+
- test/read_mixin_test.rb
|
126
132
|
- test/schema.rb
|
127
|
-
- test/serialization_test.rb
|
@@ -1,79 +0,0 @@
|
|
1
|
-
module Kasket
|
2
|
-
class ConditionsParser
|
3
|
-
def initialize(model_class)
|
4
|
-
@model_class = model_class
|
5
|
-
end
|
6
|
-
|
7
|
-
def attribute_value_pairs(options)
|
8
|
-
#pulls out the conditions from each hash
|
9
|
-
condition_fragments = [options[:conditions]]
|
10
|
-
|
11
|
-
#add the scope to the mix
|
12
|
-
if scope = @model_class.send(:scope, :find)
|
13
|
-
condition_fragments << scope[:conditions]
|
14
|
-
end
|
15
|
-
|
16
|
-
#add the type if we are on STI
|
17
|
-
condition_fragments << {:type => @model_class.name} if @model_class.finder_needs_type_condition?
|
18
|
-
|
19
|
-
condition_fragments.compact!
|
20
|
-
|
21
|
-
#parses each conditions fragment but bails if one of the did not parse
|
22
|
-
attributes_fragments = condition_fragments.map do |condition_fragment|
|
23
|
-
attributes_fragment = attributes_for_conditions(condition_fragment)
|
24
|
-
return nil unless attributes_fragment
|
25
|
-
attributes_fragment
|
26
|
-
end
|
27
|
-
|
28
|
-
#merges the hashes but bails if there is an overlap
|
29
|
-
attributes = attributes_fragments.inject({}) do |memo, attributes_fragment|
|
30
|
-
attributes_fragment.each do |attribute, value|
|
31
|
-
return nil if memo.has_key?(attribute)
|
32
|
-
memo[attribute] = value
|
33
|
-
end
|
34
|
-
memo
|
35
|
-
end
|
36
|
-
|
37
|
-
attributes.keys.sort.map { |attribute| [attribute.to_sym, attributes[attribute]] }
|
38
|
-
end
|
39
|
-
|
40
|
-
def attributes_for_conditions(conditions)
|
41
|
-
pairs = case conditions
|
42
|
-
when Hash
|
43
|
-
return conditions.stringify_keys
|
44
|
-
when String
|
45
|
-
parse_indices_from_condition(conditions)
|
46
|
-
when Array
|
47
|
-
parse_indices_from_condition(*conditions)
|
48
|
-
when NilClass
|
49
|
-
[]
|
50
|
-
end
|
51
|
-
|
52
|
-
return nil unless pairs
|
53
|
-
|
54
|
-
pairs.inject({}) do |memo, pair|
|
55
|
-
return nil if memo.has_key?(pair[0])
|
56
|
-
memo[pair[0]] = pair[1]
|
57
|
-
memo
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
AND = /\s+AND\s+/i
|
62
|
-
TABLE_AND_COLUMN = /(?:(?:`|")?(\w+)(?:`|")?\.)?(?:`|")?(\w+)(?:`|")?/ # Matches: `users`.id, `users`.`id`, users.id, id
|
63
|
-
VALUE = /'?(\d+|\?|(?:(?:[^']|'')*))'?/ # Matches: 123, ?, '123', '12''3'
|
64
|
-
KEY_EQ_VALUE = /^[\(\s]*#{TABLE_AND_COLUMN}\s+=\s+#{VALUE}[\)\s]*$/ # Matches: KEY = VALUE, (KEY = VALUE), ()(KEY = VALUE))
|
65
|
-
|
66
|
-
def parse_indices_from_condition(conditions = '', *values)
|
67
|
-
values = values.dup
|
68
|
-
conditions.split(AND).inject([]) do |indices, condition|
|
69
|
-
matched, table_name, column_name, sql_value = *(KEY_EQ_VALUE.match(condition))
|
70
|
-
if matched
|
71
|
-
value = sql_value == '?' ? values.shift : @model_class.columns_hash[column_name].type_cast(sql_value)
|
72
|
-
indices << [column_name, value]
|
73
|
-
else
|
74
|
-
return nil
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
data/test/serialization_test.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/helper'
|
2
|
-
|
3
|
-
class SerializationTest < ActiveSupport::TestCase
|
4
|
-
fixtures :blogs, :posts
|
5
|
-
|
6
|
-
Post.has_kasket
|
7
|
-
|
8
|
-
should "store a CachedModel" do
|
9
|
-
post = Post.first
|
10
|
-
post.store_in_kasket
|
11
|
-
assert_instance_of(Kasket::Cache::CachedModel, Rails.cache.read(post.kasket_key))
|
12
|
-
end
|
13
|
-
|
14
|
-
should "bring convert CachedModel to model instances" do
|
15
|
-
post = Post.first
|
16
|
-
post.store_in_kasket
|
17
|
-
|
18
|
-
post = Kasket.cache.read(post.kasket_key)
|
19
|
-
assert_instance_of(Post, post)
|
20
|
-
end
|
21
|
-
end
|