kasket 0.7.8 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -17,7 +17,6 @@ tmtags
17
17
  coverage
18
18
  rdoc
19
19
  pkg
20
- *.gemspec
21
20
 
22
21
  ## PROJECT::SPECIFIC
23
22
  test/debug.log
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
1
3
  require 'rubygems'
2
4
  require 'rake'
3
5
  require 'lib/kasket'
data/lib/kasket.rb CHANGED
@@ -13,8 +13,8 @@ module Kasket
13
13
 
14
14
  class Version
15
15
  MAJOR = 0
16
- MINOR = 7
17
- PATCH = 8
16
+ MINOR = 8
17
+ PATCH = 0
18
18
  STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
19
19
  end
20
20
 
@@ -30,17 +30,21 @@ module Kasket
30
30
  end
31
31
 
32
32
  def kasket_key_for(attribute_value_pairs)
33
- key = attribute_value_pairs.map do |attribute, value|
34
- column = columns_hash[attribute.to_s]
35
- value = nil if value.blank?
36
- attribute.to_s + '=' + connection.quote(column.type_cast(value), column)
37
- end.join('/')
38
-
39
- if key.size > (250 - kasket_key_prefix.size) || key =~ /\s/
40
- key = Digest::MD5.hexdigest(key)
33
+ if attribute_value_pairs.size == 1 && attribute_value_pairs[0][0] == :id && attribute_value_pairs[0][1].is_a?(Array)
34
+ attribute_value_pairs[0][1].map {|id| kasket_key_for_id(id)}
35
+ else
36
+ key = attribute_value_pairs.map do |attribute, value|
37
+ column = columns_hash[attribute.to_s]
38
+ value = nil if value.blank?
39
+ attribute.to_s + '=' + connection.quote(column.type_cast(value), column)
40
+ end.join('/')
41
+
42
+ if key.size > (250 - kasket_key_prefix.size) || key =~ /\s/
43
+ key = Digest::MD5.hexdigest(key)
44
+ end
45
+
46
+ kasket_key_prefix + key
41
47
  end
42
-
43
- kasket_key_prefix + key
44
48
  end
45
49
 
46
50
  def kasket_key_for_id(id)
@@ -12,7 +12,7 @@ module Kasket
12
12
  @model_class = model_class
13
13
  @supported_query_pattern = /^select \* from (?:`|")#{@model_class.table_name}(?:`|") where \((.*)\)(|\s+limit 1)\s*$/i
14
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))
15
+ @key_eq_value_pattern = /^[\(\s]*#{@table_and_column_pattern}\s+(=|IN)\s+#{VALUE}[\)\s]*$/ # Matches: KEY = VALUE, (KEY = VALUE), ()(KEY = VALUE))
16
16
  end
17
17
 
18
18
  def parse(sql)
@@ -39,10 +39,19 @@ module Kasket
39
39
  def parse_condition(conditions = '', *values)
40
40
  values = values.dup
41
41
  conditions.split(AND).inject([]) do |pairs, condition|
42
- matched, column_name, sql_value = *(@key_eq_value_pattern.match(condition))
42
+ matched, column_name, operator, sql_value = *(@key_eq_value_pattern.match(condition))
43
43
  if matched
44
- value = sql_value == '?' ? values.shift : sql_value
45
- pairs << [column_name.to_sym, value.gsub(/''|\\'/, "'")]
44
+ if operator == 'IN'
45
+ if column_name == 'id'
46
+ values = sql_value[1..(-2)].split(',').map(&:strip)
47
+ pairs << [column_name.to_sym, values]
48
+ else
49
+ return nil
50
+ end
51
+ else
52
+ value = sql_value == '?' ? values.shift : sql_value
53
+ pairs << [column_name.to_sym, value.gsub(/''|\\'/, "'")]
54
+ end
46
55
  else
47
56
  return nil
48
57
  end
@@ -8,20 +8,44 @@ module Kasket
8
8
  end
9
9
 
10
10
  def find_by_sql_with_kasket(sql)
11
- sql = sanitize_sql(sql)
12
- query = kasket_parser.parse(sql) if use_kasket?
11
+ query = kasket_parser.parse(sanitize_sql(sql)) if use_kasket?
13
12
  if query && has_kasket_index_on?(query[:index])
14
-
15
- if value = Rails.cache.read(query[:key])
16
- Array.wrap(value).collect { |record| instantiate(record.dup) }
13
+ if query[:key].is_a?(Array)
14
+ find_by_sql_with_kasket_on_id_array(sql, query)
17
15
  else
18
- store_in_kasket(query[:key], find_by_sql_without_kasket(sql))
16
+ if value = Rails.cache.read(query[:key])
17
+ Array.wrap(value).collect { |record| instantiate(record.dup) }
18
+ else
19
+ store_in_kasket(query[:key], find_by_sql_without_kasket(sql))
20
+ end
19
21
  end
20
22
  else
21
23
  find_by_sql_without_kasket(sql)
22
24
  end
23
25
  end
24
26
 
27
+ def find_by_sql_with_kasket_on_id_array(sql, query)
28
+ key_value_map = Rails.cache.read_multi(*query[:key])
29
+ missing_ids = []
30
+
31
+ key_value_map.each do |key, value|
32
+ if value.nil?
33
+ missing_ids << key.split('=').last.to_i
34
+ else
35
+ key_value_map[key] = instantiate(value.dup)
36
+ end
37
+ end
38
+
39
+ without_kasket do
40
+ find(missing_ids).each do |instance|
41
+ instance.store_in_kasket
42
+ key_value_map[instance.kasket_key] = instance
43
+ end
44
+ end
45
+
46
+ key_value_map.values
47
+ end
48
+
25
49
  protected
26
50
 
27
51
  def store_in_kasket(key, records)
@@ -33,7 +57,8 @@ module Kasket
33
57
  Rails.cache.write(key, record.instance_variable_get(:@attributes).dup)
34
58
  key
35
59
  end
36
- Rails.cache.write(key, keys)
60
+
61
+ Rails.cache.write(key, keys) if key.is_a?(String)
37
62
  end
38
63
  records
39
64
  end
@@ -3,7 +3,7 @@ require File.dirname(__FILE__) + '/helper'
3
3
  class FindSomeTest < ActiveSupport::TestCase
4
4
  fixtures :blogs, :posts
5
5
 
6
- should_eventually "cache find(id, id) calls" do
6
+ should "cache find(id, id) calls" do
7
7
  post1 = Post.first
8
8
  post2 = Post.last
9
9
 
@@ -18,14 +18,14 @@ class FindSomeTest < ActiveSupport::TestCase
18
18
  Post.find(post1.id, post2.id)
19
19
  end
20
20
 
21
- should_eventually "only lookup the records that are not in the cache" do
21
+ should "only lookup the records that are not in the cache" do
22
22
  post1 = Post.first
23
23
  post2 = Post.last
24
24
  assert_equal(post1, Post.find(post1.id))
25
25
  assert(Rails.cache.read(post1.kasket_key))
26
26
  assert_nil(Rails.cache.read(post2.kasket_key))
27
27
 
28
- Post.expects(:find_by_sql_without_kasket).with("SELECT * FROM \'posts\' WHERE (\'posts\'.\'id\' IN (#{post1.id},#{post2.id})) ").returns([post2])
28
+ Post.expects(:find_by_sql_without_kasket).with("SELECT * FROM \"posts\" WHERE (\"posts\".\"id\" = #{post2.id}) ").returns([post2])
29
29
  found_posts = Post.find(post1.id, post2.id)
30
30
  assert_equal([post1, post2].map(&:id).sort, found_posts.map(&:id).sort)
31
31
 
@@ -33,13 +33,4 @@ class FindSomeTest < ActiveSupport::TestCase
33
33
  found_posts = Post.find(post1.id, post2.id)
34
34
  assert_equal([post1, post2].map(&:id).sort, found_posts.map(&:id).sort)
35
35
  end
36
-
37
- should_eventually "cache on index other than primary key" do
38
- blog = blogs(:a_blog)
39
- posts = Post.find_all_by_blog_id(blog.id)
40
-
41
- Post.expects(:find_by_sql_without_kasket).never
42
-
43
- assert_equal(posts, Post.find_all_by_blog_id(blog.id))
44
- end
45
36
  end
data/test/helper.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ require 'ruby-debug'
2
3
  require 'test/unit'
3
4
  require 'mocha'
4
5
  require 'shoulda'
data/test/parser_test.rb CHANGED
@@ -25,6 +25,16 @@ class ParserTest < ActiveSupport::TestCase
25
25
  assert @parser.parse('SELECT * FROM `posts` WHERE (`posts`.`id` = 2) LIMIT 1')
26
26
  end
27
27
 
28
+ should "support IN queries on id" do
29
+ parsed_query = @parser.parse('SELECT * FROM `posts` WHERE (`posts`.`id` IN (1,2,3))')
30
+ assert(parsed_query)
31
+ assert_equal([[:id, ['1', '2', '3']]], parsed_query[:attributes])
32
+ end
33
+
34
+ should "not support IN queries on other attributes" do
35
+ assert(!@parser.parse('SELECT * FROM `posts` WHERE (`posts`.`hest` IN (1,2,3))'))
36
+ end
37
+
28
38
  should "support vaguely formatted queries" do
29
39
  assert @parser.parse('SELECT * FROM "posts" WHERE (title = red AND blog_id = big)')
30
40
  end
@@ -66,11 +76,6 @@ class ParserTest < ActiveSupport::TestCase
66
76
  should "include the OR operator" do
67
77
  assert !@parser.parse('SELECT * FROM `posts` WHERE (title = red OR blog_id = big) LIMIT 2')
68
78
  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
79
  end
75
80
 
76
81
  context "key generation" do
@@ -84,6 +89,13 @@ class ParserTest < ActiveSupport::TestCase
84
89
  assert_match(/id=1\/title='title'$/, @parser.parse("SELECT * FROM `posts` WHERE (id = 1 AND title = 'title')")[:key])
85
90
  end
86
91
 
92
+ should "generate multiple keys on IN queries" do
93
+ keys = @parser.parse('SELECT * FROM `posts` WHERE (id IN (1,2))')[:key]
94
+ assert_instance_of(Array, keys)
95
+ assert_match(/id=1$/, keys[0])
96
+ assert_match(/id=2$/, keys[1])
97
+ end
98
+
87
99
  context "when limit 1" do
88
100
  should "add /first to the key if the index does not include id" do
89
101
  assert_match(/title='a'\/first$/, @parser.parse("SELECT * FROM `posts` WHERE (title = 'a') LIMIT 1")[:key])
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kasket
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 63
4
5
  prerelease: false
5
6
  segments:
6
7
  - 0
7
- - 7
8
8
  - 8
9
- version: 0.7.8
9
+ - 0
10
+ version: 0.8.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Mick Staugaard
@@ -15,16 +16,18 @@ autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
18
 
18
- date: 2010-07-21 00:00:00 -07:00
19
+ date: 2010-08-24 00:00:00 -07:00
19
20
  default_executable:
20
21
  dependencies:
21
22
  - !ruby/object:Gem::Dependency
22
23
  name: activerecord
23
24
  prerelease: false
24
25
  requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
25
27
  requirements:
26
28
  - - ">="
27
29
  - !ruby/object:Gem::Version
30
+ hash: 11
28
31
  segments:
29
32
  - 2
30
33
  - 3
@@ -36,9 +39,11 @@ dependencies:
36
39
  name: activesupport
37
40
  prerelease: false
38
41
  requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
39
43
  requirements:
40
44
  - - ">="
41
45
  - !ruby/object:Gem::Version
46
+ hash: 11
42
47
  segments:
43
48
  - 2
44
49
  - 3
@@ -50,9 +55,11 @@ dependencies:
50
55
  name: shoulda
51
56
  prerelease: false
52
57
  requirement: &id003 !ruby/object:Gem::Requirement
58
+ none: false
53
59
  requirements:
54
60
  - - ">="
55
61
  - !ruby/object:Gem::Version
62
+ hash: 3
56
63
  segments:
57
64
  - 0
58
65
  version: "0"
@@ -62,9 +69,11 @@ dependencies:
62
69
  name: mocha
63
70
  prerelease: false
64
71
  requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
65
73
  requirements:
66
74
  - - ">="
67
75
  - !ruby/object:Gem::Version
76
+ hash: 3
68
77
  segments:
69
78
  - 0
70
79
  version: "0"
@@ -74,9 +83,11 @@ dependencies:
74
83
  name: temping
75
84
  prerelease: false
76
85
  requirement: &id005 !ruby/object:Gem::Requirement
86
+ none: false
77
87
  requirements:
78
88
  - - ">="
79
89
  - !ruby/object:Gem::Version
90
+ hash: 27
80
91
  segments:
81
92
  - 1
82
93
  - 3
@@ -129,23 +140,27 @@ rdoc_options:
129
140
  require_paths:
130
141
  - lib
131
142
  required_ruby_version: !ruby/object:Gem::Requirement
143
+ none: false
132
144
  requirements:
133
145
  - - ">="
134
146
  - !ruby/object:Gem::Version
147
+ hash: 3
135
148
  segments:
136
149
  - 0
137
150
  version: "0"
138
151
  required_rubygems_version: !ruby/object:Gem::Requirement
152
+ none: false
139
153
  requirements:
140
154
  - - ">="
141
155
  - !ruby/object:Gem::Version
156
+ hash: 3
142
157
  segments:
143
158
  - 0
144
159
  version: "0"
145
160
  requirements: []
146
161
 
147
162
  rubyforge_project:
148
- rubygems_version: 1.3.6
163
+ rubygems_version: 1.3.7
149
164
  signing_key:
150
165
  specification_version: 3
151
166
  summary: A write back caching layer on active record