kasket 4.6.0 → 4.9.1

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.
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