kasket 4.6.0 → 4.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a81925a9561dbeb413c751be411178db3ca412f1
4
- data.tar.gz: 32e1620ff89925cc429d4c9f8e8bfdc95e34208e
2
+ SHA256:
3
+ metadata.gz: 75e9005963ca629245005b0c9a2a9d94cb9023fac9cc12327045e25eaca547e0
4
+ data.tar.gz: e7aec95609b6918646f3b2b420d5af038ef171888ac98cdea4c0fb16cf6ce086
5
5
  SHA512:
6
- metadata.gz: c57079f331c15b58809288a45c123ff8e044763c81f0e170a9706305fcb0ea6d7f0fd336566815bf99e843d4193e9dbc1b6d96ad4543317e4fb6d79a89eece28
7
- data.tar.gz: d8a5b52ce07b72b361727649697a8a1187c55fe790dbbf82b7b358897cf942fe64456ce89959a9849f9f941f99b854e2a2b41931c178890ddf4d1bae9c06e8aa
6
+ metadata.gz: f19bd6ca3a60073c738907e20052d461cdcc0139a1c300765ed3b029022107d7c477b6a5e6437bb34d73de5d8e2c6050eac3e6834ec6c7f27c3e7e1e5a0fa200
7
+ data.tar.gz: effe82d0f1620cc7742407a46054b558675ac061050af22313daccdc52dc30204c5ae02c67f91aeea64ce07f4c687d2a6eb699c132a0914284d2554cec0d014b
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Kasket [![Build Status](https://secure.travis-ci.org/zendesk/kasket.svg)](http://travis-ci.org/zendesk/kasket)
1
+ # Kasket [![Build status](https://circleci.com/gh/zendesk/kasket.svg?style=svg)](https://circleci.com/gh/zendesk/kasket)
2
2
 
3
3
  ### Puts a cap on your queries
4
4
  A caching layer for ActiveRecord (3.x and 4.x)
@@ -37,7 +37,7 @@ module Kasket
37
37
  else
38
38
  key = attribute_value_pairs.map do |attribute, value|
39
39
  column = columns_hash[attribute.to_s]
40
- value = nil if value.blank?
40
+ value = nil if value.blank? && value != false
41
41
  "#{attribute}=#{kasket_quoted_value_for_column(value, column)}"
42
42
  end.join('/')
43
43
 
@@ -6,8 +6,8 @@ module Kasket
6
6
  # SELECT * FROM `users` WHERE (`users`.`id` = 2) LIMIT 1
7
7
  # 'SELECT * FROM \'posts\' WHERE (\'posts\'.\'id\' = 574019247) '
8
8
 
9
- AND = /\s+AND\s+/i
10
- VALUE = /'?(\d+|\?|(?:(?:[^']|''|\\')*))'?/ # Matches: 123, ?, '123', '12''3'
9
+ AND = /\s+AND\s+/i.freeze
10
+ VALUE = /'?(\d+|\?|(?:(?:[^']|''|\\')*))'?/.freeze # Matches: 123, ?, '123', '12''3'
11
11
 
12
12
  def initialize(model_class)
13
13
  @model_class = model_class
@@ -8,6 +8,7 @@ module Kasket
8
8
  end
9
9
  end
10
10
 
11
+ # *args can be replaced with (sql, *args) once we stop supporting Rails < 5.2
11
12
  def find_by_sql_with_kasket(*args)
12
13
  sql = args[0]
13
14
 
@@ -16,7 +17,11 @@ module Kasket
16
17
  if ActiveRecord::VERSION::MAJOR < 5
17
18
  sql.to_kasket_query(self, args[1])
18
19
  else
19
- sql.to_kasket_query(self, args[1].map(&:value_for_database))
20
+ if ActiveRecord::VERSION::STRING < '5.2'
21
+ sql.to_kasket_query(self, args[1].map(&:value_for_database))
22
+ else
23
+ sql.to_kasket_query(self)
24
+ end
20
25
  end
21
26
  else
22
27
  kasket_parser.parse(sanitize_sql(sql))
@@ -28,11 +33,27 @@ module Kasket
28
33
  find_by_sql_with_kasket_on_id_array(query[:key])
29
34
  else
30
35
  if value = Kasket.cache.read(query[:key])
31
- if value.is_a?(Array)
36
+ # Identified a specific edge case where memcached server returns 0x00 binary protocol response with no data
37
+ # when the node is being rebooted which causes the Dalli memcached client to return a TrueClass object instead of nil
38
+ # see: https://github.com/petergoldstein/dalli/blob/31dabf19d3dd94b348a00a59fe5a7b8fa80ce3ad/lib/dalli/server.rb#L520
39
+ # and: https://github.com/petergoldstein/dalli/issues/390
40
+ #
41
+ # The code in this first condition of TrueClass === true will
42
+ # skip the kasket cache for these specific objects and go directly to SQL for retrieval.
43
+ result_set = if value.is_a?(TrueClass)
44
+ find_by_sql_without_kasket(*args)
45
+ elsif value.is_a?(Array)
32
46
  filter_pending_records(find_by_sql_with_kasket_on_id_array(value))
33
47
  else
34
48
  filter_pending_records(Array.wrap(value).collect { |record| instantiate(record.dup) })
35
49
  end
50
+
51
+ payload = {
52
+ record_count: result_set.length,
53
+ class_name: to_s
54
+ }
55
+
56
+ ActiveSupport::Notifications.instrument('instantiation.active_record', payload) { result_set }
36
57
  else
37
58
  store_in_kasket(query[:key], find_by_sql_without_kasket(*args))
38
59
  end
@@ -43,11 +64,23 @@ module Kasket
43
64
  end
44
65
 
45
66
  def find_by_sql_with_kasket_on_id_array(keys)
46
- key_attributes_map = Kasket.cache.read_multi(*keys)
47
-
48
- found_keys, missing_keys = keys.partition {|k| key_attributes_map[k] }
49
- found_keys.each {|k| key_attributes_map[k] = instantiate(key_attributes_map[k].dup) }
50
- key_attributes_map.merge!(missing_records_from_db(missing_keys))
67
+ begin
68
+ key_attributes_map = Kasket.cache.read_multi(*keys)
69
+ rescue RuntimeError => e
70
+ # Elasticache Memcached has a bug where it returns a 0x00 binary protocol response with no data
71
+ # during a reboot, causing the Dalli memcached client to throw a RuntimeError during a multi get
72
+ # (https://github.com/petergoldstein/dalli/blob/v2.7.7/lib/dalli/server.rb#L148).
73
+ # Fall back to the database when this happens.
74
+ if e.message == "multi_response has completed"
75
+ key_attributes_map = missing_records_from_db(keys)
76
+ else
77
+ raise
78
+ end
79
+ else
80
+ found_keys, missing_keys = keys.partition {|k| key_attributes_map[k] }
81
+ found_keys.each {|k| key_attributes_map[k] = instantiate(key_attributes_map[k].dup) }
82
+ key_attributes_map.merge!(missing_records_from_db(missing_keys))
83
+ end
51
84
 
52
85
  key_attributes_map.values.compact
53
86
  end
@@ -76,7 +109,7 @@ module Kasket
76
109
  if records.size == 1
77
110
  records.first.store_in_kasket(key)
78
111
  elsif records.empty?
79
- ActiveRecord::Base.logger.info("[KASKET] would have stored an empty resultset") if ActiveRecord::Base.logger
112
+ ActiveRecord::Base.logger.debug("[KASKET] would have stored an empty resultset") if ActiveRecord::Base.logger
80
113
  elsif records.size <= Kasket::CONFIGURATION[:max_collection_size]
81
114
  if records.all?(&:kasket_cacheable?)
82
115
  instance_keys = records.map(&:store_in_kasket)
@@ -1,16 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
  module Kasket
3
3
  module RelationMixin
4
+ # binds can be removed when support for Rails < 5 is removed
4
5
  def to_kasket_query(binds = nil)
5
6
  if arel.is_a?(Arel::SelectManager)
6
7
  if ActiveRecord::VERSION::MAJOR < 5
7
8
  arel.to_kasket_query(klass, (binds || bind_values))
9
+ elsif ActiveRecord::VERSION::STRING < '5.2'
10
+ arel.to_kasket_query(klass, (@values[:where].to_h.values + Array(@values[:limit])))
8
11
  else
9
- arel.to_kasket_query(klass, (@values[:where].binds.map(&:value_for_database) + Array(@values[:limit])))
12
+ arel.to_kasket_query(klass)
10
13
  end
11
14
  end
12
15
  rescue TypeError # unsupported object in ast
13
- return nil
16
+ nil
14
17
  end
15
18
  end
16
19
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Kasket
3
3
  module SelectManagerMixin
4
+ # binds can be removed once we stop supporting Rails < 5.2
4
5
  def to_kasket_query(klass, binds = [])
5
6
  begin
6
7
  query = Kasket::Visitor.new(klass, binds).accept(ast)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Kasket
3
- VERSION = '4.6.0'
3
+ VERSION = '4.9.1'
4
4
  class Version
5
5
  MAJOR = Kasket::VERSION.split('.')[0]
6
6
  MINOR = Kasket::VERSION.split('.')[1]
@@ -3,6 +3,7 @@ require 'arel'
3
3
 
4
4
  module Kasket
5
5
  class Visitor < Arel::Visitors::Visitor
6
+ # binds can be removed once we stop supporting Rails < 5.2
6
7
  def initialize(model_class, binds)
7
8
  @model_class = model_class
8
9
  @binds = binds.dup
@@ -75,6 +76,7 @@ module Kasket
75
76
  def visit_Arel_Nodes_JoinSource(node, *_)
76
77
  return :unsupported if !node.left || node.right.any?
77
78
  return :unsupported unless node.left.is_a?(Arel::Table)
79
+
78
80
  visit(node.left)
79
81
  end
80
82
 
@@ -85,6 +87,7 @@ module Kasket
85
87
  def visit_Arel_Nodes_And(node, *_)
86
88
  attributes = node.children.map { |child| visit(child) }
87
89
  return :unsupported if attributes.include?(:unsupported)
90
+
88
91
  attributes.sort! { |pair1, pair2| pair1[0].to_s <=> pair2[0].to_s }
89
92
  { attributes: attributes }
90
93
  end
@@ -112,18 +115,26 @@ module Kasket
112
115
  end
113
116
 
114
117
  def literal(node, *_)
115
- if node == '?'
116
- @binds.shift.last.to_s
118
+ if ActiveRecord::VERSION::STRING < '5.2'
119
+ if node == '?'
120
+ @binds.shift.last.to_s
121
+ else
122
+ node.to_s
123
+ end
117
124
  else
118
125
  node.to_s
119
126
  end
120
127
  end
121
128
 
122
- def visit_Arel_Nodes_BindParam(_x, *_)
129
+ def visit_Arel_Nodes_BindParam(node, *_)
123
130
  if ActiveRecord::VERSION::MAJOR < 5
124
131
  visit(@binds.shift[1])
125
132
  else
126
- visit(@binds.shift)
133
+ if ActiveRecord::VERSION::STRING < '5.2'
134
+ visit(@binds.shift)
135
+ else
136
+ visit(node.value.value) unless node.value.value.nil?
137
+ end
127
138
  end
128
139
  end
129
140
 
@@ -131,11 +142,13 @@ module Kasket
131
142
  node.map {|value| visit(value) }
132
143
  end
133
144
 
134
- def visit_Arel_Nodes_Casted(node, *_)
135
- case node.val
136
- when nil then nil
137
- when String then node.val
138
- else quoted(node.val)
145
+ if ActiveRecord::VERSION::STRING < '5.2'
146
+ def visit_Arel_Nodes_Casted(node, *_)
147
+ case node.val
148
+ when nil then nil
149
+ when String then node.val
150
+ else quoted(node.val)
151
+ end
139
152
  end
140
153
  end
141
154
 
data/lib/kasket.rb CHANGED
@@ -17,8 +17,8 @@ module Kasket
17
17
 
18
18
  CONFIGURATION = { # rubocop:disable Style/MutableConstant
19
19
  max_collection_size: 100,
20
- write_through: false,
21
- default_expires_in: nil
20
+ write_through: false,
21
+ default_expires_in: nil
22
22
  }
23
23
 
24
24
  module_function
@@ -33,8 +33,8 @@ module Kasket
33
33
  ActiveRecord::Base.extend(Kasket::ConfigurationMixin)
34
34
 
35
35
  if defined?(ActiveRecord::Relation)
36
- ActiveRecord::Relation.send(:include, Kasket::RelationMixin)
37
- Arel::SelectManager.send(:include, Kasket::SelectManagerMixin)
36
+ ActiveRecord::Relation.include Kasket::RelationMixin
37
+ Arel::SelectManager.include Kasket::SelectManagerMixin
38
38
  end
39
39
  end
40
40
 
@@ -42,10 +42,15 @@ module Kasket
42
42
  @cache_store = ActiveSupport::Cache.lookup_store(options)
43
43
  end
44
44
 
45
- def self.cache
45
+ def self.cache_store
46
46
  @cache_store ||= Rails.cache
47
47
  end
48
48
 
49
+ # Alias cache_store to cache
50
+ class << self
51
+ alias_method :cache, :cache_store
52
+ end
53
+
49
54
  # Keys are the records being saved.
50
55
  # Values are either the saved record, or nil if the record has been destroyed.
51
56
  def self.pending_records
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kasket
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.6.0
4
+ version: 4.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mick Staugaard
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-10-24 00:00:00.000000000 Z
12
+ date: 2021-08-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -17,22 +17,22 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '3.2'
20
+ version: '4.2'
21
21
  - - "<"
22
22
  - !ruby/object:Gem::Version
23
- version: '5.2'
23
+ version: '6.1'
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
27
27
  requirements:
28
28
  - - ">="
29
29
  - !ruby/object:Gem::Version
30
- version: '3.2'
30
+ version: '4.2'
31
31
  - - "<"
32
32
  - !ruby/object:Gem::Version
33
- version: '5.2'
33
+ version: '6.1'
34
34
  - !ruby/object:Gem::Dependency
35
- name: rake
35
+ name: bump
36
36
  requirement: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
@@ -60,7 +60,7 @@ dependencies:
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  - !ruby/object:Gem::Dependency
63
- name: mocha
63
+ name: minitest
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
@@ -74,7 +74,7 @@ dependencies:
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  - !ruby/object:Gem::Dependency
77
- name: wwtd
77
+ name: minitest-rg
78
78
  requirement: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ">="
@@ -88,7 +88,7 @@ dependencies:
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  - !ruby/object:Gem::Dependency
91
- name: bump
91
+ name: mocha
92
92
  requirement: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - ">="
@@ -102,7 +102,7 @@ dependencies:
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0'
104
104
  - !ruby/object:Gem::Dependency
105
- name: minitest
105
+ name: rake
106
106
  requirement: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - ">="
@@ -116,7 +116,7 @@ dependencies:
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  - !ruby/object:Gem::Dependency
119
- name: minitest-rg
119
+ name: timecop
120
120
  requirement: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - ">="
@@ -130,7 +130,7 @@ dependencies:
130
130
  - !ruby/object:Gem::Version
131
131
  version: '0'
132
132
  - !ruby/object:Gem::Dependency
133
- name: timecop
133
+ name: wwtd
134
134
  requirement: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - ">="
@@ -173,15 +173,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
173
173
  requirements:
174
174
  - - ">="
175
175
  - !ruby/object:Gem::Version
176
- version: 2.2.0
176
+ version: 2.5.0
177
177
  required_rubygems_version: !ruby/object:Gem::Requirement
178
178
  requirements:
179
179
  - - ">="
180
180
  - !ruby/object:Gem::Version
181
181
  version: '0'
182
182
  requirements: []
183
- rubyforge_project:
184
- rubygems_version: 2.5.2
183
+ rubygems_version: 3.1.6
185
184
  signing_key:
186
185
  specification_version: 4
187
186
  summary: A write back caching layer on active record