cache_crispies 0.3.1 → 1.0.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
2
  SHA256:
3
- metadata.gz: a4293fff1dfa2e9239dcb22ff05222a6069291a17c512ed18ad63c4ca57296a2
4
- data.tar.gz: ea9057daf8b48b06c9def5ab704929088e465c3946ba8facde27c879dc49085b
3
+ metadata.gz: 7be596f4546279c27b355a83a6c87b5d986cbcc65779def4310df106c3017557
4
+ data.tar.gz: e7b5ceb4ac6c68d7149bb45246bdba99864c5fbd289277d054b22c5c622f748d
5
5
  SHA512:
6
- metadata.gz: 519e4d11e56c056ca76dbf60c5d195096206f4ce9756440c94503556701daf6195ac7fdbe3acca33351831997eeb95941a8c72db31d685ae9b996facad6a7760
7
- data.tar.gz: 7280a38a08e92907d64f3bc50a83a0d1d2dfc0b2cccfc03c21fa6e9b49612cc1246d68a20f93e612829fa687fe2b2c2a3b1b6972a3243bc5cf5563d8f92905ce
6
+ metadata.gz: ac4fe247753da8c9afa3e2d5a27d8f673c780b6bbb0d71a8777e736e9b9761be4791c33e5370a3f6a1ade8df2ff65befd648c9004908f1d4ebaafb3ce3678aa1
7
+ data.tar.gz: 823e1a06a9a007e74cb148751f765cf506e96849b5947e5132b2fffadfbaaa447a3c379cbd805d72fb03ceffd2bb8daf12a8649e282020d1d91b98656af97e82
@@ -15,12 +15,25 @@ module CacheCrispies
15
15
  require 'cache_crispies/version'
16
16
 
17
17
  # Use autoload for better Rails development
18
- autoload :Attribute, 'cache_crispies/attribute'
19
- autoload :Base, 'cache_crispies/base'
20
- autoload :Collection, 'cache_crispies/collection'
21
- autoload :Condition, 'cache_crispies/condition'
22
- autoload :HashBuilder, 'cache_crispies/hash_builder'
23
- autoload :Memoizer, 'cache_crispies/memoizer'
24
- autoload :Controller, 'cache_crispies/controller'
25
- autoload :Plan, 'cache_crispies/plan'
18
+ autoload :Attribute, 'cache_crispies/attribute'
19
+ autoload :Base, 'cache_crispies/base'
20
+ autoload :Collection, 'cache_crispies/collection'
21
+ autoload :Condition, 'cache_crispies/condition'
22
+ autoload :Configuration, 'cache_crispies/configuration'
23
+ autoload :HashBuilder, 'cache_crispies/hash_builder'
24
+ autoload :Memoizer, 'cache_crispies/memoizer'
25
+ autoload :Controller, 'cache_crispies/controller'
26
+ autoload :Plan, 'cache_crispies/plan'
27
+
28
+ def self.configure
29
+ yield config
30
+ end
31
+
32
+ def self.config
33
+ @config ||= Configuration.new
34
+ end
35
+
36
+ def self.cache
37
+ config.cache_store
38
+ end
26
39
  end
@@ -22,44 +22,68 @@ module CacheCrispies
22
22
  # argument's value
23
23
  def initialize(
24
24
  key,
25
- from: nil, with: nil, to: nil, nesting: [], conditions: []
25
+ from: nil, with: nil, through: nil, to: nil, nesting: [], conditions: [],
26
+ &block
26
27
  )
27
28
  @key = key
28
29
  @method_name = from || key || :itself
29
30
  @serializer = with
31
+ @through = through
30
32
  @coerce_to = to
31
33
  @nesting = Array(nesting)
32
34
  @conditions = Array(conditions)
35
+ @block = block
33
36
  end
34
37
 
35
38
  attr_reader(
36
39
  :method_name,
37
40
  :key,
38
41
  :serializer,
42
+ :through,
39
43
  :coerce_to,
40
44
  :nesting,
41
- :conditions
45
+ :conditions,
46
+ :block
42
47
  )
43
48
 
44
- # Gets the value of the attribute for the given model and options
49
+ # Gets the value of the attribute for the given target object and options
45
50
  #
46
- # @param model [Object] typically ActiveRecord::Base, but could be anything
51
+ # @param target [Object] typically ActiveRecord::Base, but could be anything
47
52
  # @param options [Hash] any optional values from the serializer instance
48
53
  # @return the value for the attribute for the given model and options
49
54
  # @raise [InvalidCoercionType] when an invalid argument is passed in the
50
55
  # to: argument
51
- def value_for(model, options)
52
- value = model.public_send(method_name)
56
+ def value_for(target, options)
57
+ value =
58
+ if block?
59
+ block.call(target, options)
60
+ elsif through?
61
+ target.public_send(through)&.public_send(method_name)
62
+ else
63
+ target.public_send(method_name)
64
+ end
53
65
 
54
66
  serializer ? serialize(value, options) : coerce(value)
55
67
  end
56
68
 
69
+
70
+
57
71
  private
58
72
 
73
+ def through?
74
+ !through.nil?
75
+ end
76
+
77
+ def block?
78
+ !block.nil?
79
+ end
80
+
81
+ # Here we'll render the attribute with a given serializer and attempt to
82
+ # cache the results for better cache reusability
59
83
  def serialize(value, options)
60
84
  plan = CacheCrispies::Plan.new(serializer, value, options)
61
85
 
62
- if value.respond_to?(:each)
86
+ if plan.collection?
63
87
  plan.cache { Collection.new(value, serializer, options).as_json }
64
88
  else
65
89
  plan.cache { serializer.new(value, options).as_json }
@@ -43,55 +43,118 @@ module CacheCrispies
43
43
  HashBuilder.new(self).call
44
44
  end
45
45
 
46
- # Whether or not this serializer class should allow caching of results.
47
- # It is set to false by default, but can be overridden in child classes.
46
+ # Get or set whether or not this serializer class should allow caching of
47
+ # results. It returns false by default, but can be overridden in child
48
+ # classes. Calling the method with an argument will set the value, calling
49
+ # it without any arguments will get the value.
48
50
  #
51
+ # @param value [Boolean] true to enable caching, false to disable
49
52
  # @return [Boolean]
50
- def self.do_caching?
51
- false
53
+ def self.do_caching(value = nil)
54
+ @do_caching ||= false
55
+
56
+ # method called with no args so act as a getter
57
+ return @do_caching if value.nil?
58
+
59
+ # method called with args so act as a setter
60
+ @do_caching = !!value
61
+ end
62
+
63
+ class << self
64
+ alias do_caching? do_caching
52
65
  end
53
66
 
54
- # A JSON key to use as a root key on a non-collection serializable. by
55
- # default it's the name of the class without the "Serializer" part. But it
56
- # can be overridden in a subclass to be anything.
67
+ # Get or set a JSON key to use as a root key on a non-collection
68
+ # serializable. By default it's the name of the class without the
69
+ # "Serializer" part. But it can be overridden in a subclass to be anything.
70
+ # Calling the method with a key will set the key, calling it without any
71
+ # arguments will get the key.
57
72
  #
58
- # @return [Symbol] a symbol to be used as a key for a JSON-ready Hash
59
- def self.key
60
- to_s.demodulize.chomp('Serializer').underscore.to_sym
73
+ # @param key [Symbol, nil] a symbol to be used as a key for a JSON-ready
74
+ # Hash, or nil for no key
75
+ # @return [Symbol, nil] a symbol to be used as a key for a JSON-ready Hash,
76
+ # or nil for no key
77
+ def self.key(*key)
78
+ @default_key ||= to_s.demodulize.chomp('Serializer').underscore.to_sym
79
+
80
+ # method called with no args so act as a getter
81
+ return defined?(@key) ? @key : @default_key if key.empty?
82
+
83
+ # method called with args so act as a setter
84
+ @key = key.first&.to_sym
61
85
  end
62
86
 
63
- # A JSON key to use as a root key on a collection-type serializable. By
64
- # deafult it's the plural version of .key, but it can be overridden in a
65
- # subclass to be anything.
87
+ # Get or set a JSON key to use as a root key on a collection-type
88
+ # serializable. By deafult it's the plural version of .key, but it can be
89
+ # overridden in a subclass to be anything. Calling the method with a key
90
+ # will set the key, calling it without any arguments will get the key.
66
91
  #
67
92
  # @return [Symbol] a symbol to be used as a key for a JSON-ready Hash
68
- def self.collection_key
69
- return nil unless key
93
+ # @param key [Symbol, nil] a symbol to be used as a key for a JSON-ready
94
+ # Hash, or nil for no key
95
+ # @return [Symbol, nil] a symbol to be used as a key for a JSON-ready Hash,
96
+ # or nil for no key
97
+ def self.collection_key(*key)
98
+ @default_collection_key ||= self.key.to_s.pluralize.to_sym
99
+
100
+ # method called with no args so act as a getter
101
+ if key.empty?
102
+ if defined? @collection_key
103
+ return @collection_key
104
+ else
105
+ return @default_collection_key
106
+ end
107
+ end
70
108
 
71
- key.to_s.pluralize.to_sym
109
+ # method called with args so act as a setter
110
+ @collection_key = key.first&.to_sym
72
111
  end
73
112
 
74
- # An array of strings that should be added to the cache key for an instance
75
- # of this serializer. Typically you'd add in the #cache_key or string value
76
- # for any extra models or data passed in through the options hash here. But
77
- # it could also contain any custom logic about how to construct a cache
78
- # key. This method is meant to be overridden in subclasses.
113
+ # Call with a block returning an array of strings that should be added to
114
+ # the cache key for an instance of this serializer. Typically you'd add
115
+ # in string values to uniquely represent the values you're passing to
116
+ # the serializer so that they are cached separately. But it could also
117
+ # contain any custom logic about how to construct a cache key.
118
+ # Call without a block to act as a getter and return the value.
79
119
  #
80
120
  # @example cache based off models provided in options
81
- # def self.cache_key_addons(options)
82
- # [options[:current_user].cache_key]
83
- # end
121
+ # cache_key_addons { |options| [options[:current_user].id] }
84
122
  #
85
123
  # @example time-based caching
86
- # def self.cache_key_addons(_options)
87
- # [Date.today.to_s]
88
- # end
124
+ # cache_key_addons { |_options| [Date.today.to_s] }
89
125
  #
90
126
  # @param options [Hash] the options hash passed to the serializer, will be
91
127
  # passed in here as well so you can refernce it if needed.
128
+ # @yield [options] a block that takes options passed to the serializer and
129
+ # should return an array of strings to use in the cache key
92
130
  # @return [Array<String>]
93
- def self.cache_key_addons(_options = {})
94
- []
131
+ def self.cache_key_addons(options = {}, &block)
132
+ @cache_key_addons ||= nil
133
+
134
+ if block_given?
135
+ @cache_key_addons = block
136
+ nil
137
+ else
138
+ Array(@cache_key_addons&.call(options))
139
+ end
140
+ end
141
+
142
+ # Get or set a cache key that can be changed whenever an outside dependency
143
+ # of any kind changes in any way that could change the output of your
144
+ # serializer. For instance, if a mixin is changed. Or maybe an object
145
+ # you're serializing has changed it's #to_json method. This key should be
146
+ # changed accordingly, to bust the cache so that you're not serving stale
147
+ # data.
148
+ #
149
+ # @return [String] a version string in any form
150
+ def self.dependency_key(key = nil)
151
+ @dependency_key ||= nil
152
+
153
+ # method called with no args so act as a getter
154
+ return @dependency_key unless key
155
+
156
+ # method called with args so act as a setter
157
+ @dependency_key = key.to_s
95
158
  end
96
159
 
97
160
  # Return a cache key string for the serializer class to be included in the
@@ -155,7 +218,11 @@ module CacheCrispies
155
218
  end
156
219
  private_class_method :nested_serializers
157
220
 
158
- def self.serialize(*attribute_names, from: nil, with: nil, to: nil)
221
+ def self.serialize(
222
+ *attribute_names,
223
+ from: nil, with: nil, through: nil, to: nil,
224
+ &block
225
+ )
159
226
  attribute_names.flatten.map { |att| att&.to_sym }.map do |attrib|
160
227
  current_nesting = Array(@nesting).dup
161
228
  current_conditions = Array(@conditions).dup
@@ -165,9 +232,11 @@ module CacheCrispies
165
232
  attrib,
166
233
  from: from,
167
234
  with: with,
235
+ through: through,
168
236
  to: to,
169
237
  nesting: current_nesting,
170
- conditions: current_conditions
238
+ conditions: current_conditions,
239
+ &block
171
240
  )
172
241
  end
173
242
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails'
4
-
5
3
  module CacheCrispies
6
4
  # Handles rendering and possibly caching a collection of models using a
7
5
  # Serializer
@@ -48,11 +46,11 @@ module CacheCrispies
48
46
  hash[plan.cache_key] = model
49
47
  end
50
48
 
51
- Rails.cache.fetch_multi(models_by_cache_key.keys) do |cache_key|
49
+ CacheCrispies.cache.fetch_multi(*models_by_cache_key.keys) do |cache_key|
52
50
  model = models_by_cache_key[cache_key]
53
51
 
54
52
  serializer.new(model, options).as_json
55
- end
53
+ end.values
56
54
  end
57
55
  end
58
56
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module CacheCrispies
6
+ class Configuration
7
+ SETTINGS = [
8
+ :cache_store,
9
+ :etags
10
+ ].freeze
11
+
12
+ SETTINGS.each do |setting|
13
+ attr_accessor setting
14
+ end
15
+
16
+ def initialize
17
+ reset!
18
+ end
19
+
20
+ alias etags? etags
21
+
22
+ # Resets all values to their defaults. Useful for testing.
23
+ def reset!
24
+ @cache_store = Rails.cache || ActiveSupport::Cache::NullStore.new
25
+ @etags = false
26
+ end
27
+ end
28
+ end
@@ -28,14 +28,16 @@ module CacheCrispies
28
28
  def cache_render(serializer, cacheable, options = {})
29
29
  plan = CacheCrispies::Plan.new(serializer, cacheable, options)
30
30
 
31
- # TODO: It would probably be good to add configuration to etiher
32
- # enable or disable this
33
- response.weak_etag = plan.etag
31
+ if CacheCrispies.config.etags?
32
+ response.weak_etag = plan.etag
33
+ end
34
34
 
35
35
  serializer_json =
36
36
  if plan.collection?
37
- cacheable.map do |one_cacheable|
38
- plan.cache { serializer.new(one_cacheable, options).as_json }
37
+ plan.cache do
38
+ CacheCrispies::Collection.new(
39
+ cacheable, serializer, options
40
+ ).as_json
39
41
  end
40
42
  else
41
43
  plan.cache { serializer.new(cacheable, options).as_json }
@@ -57,6 +57,7 @@ module CacheCrispies
57
57
  [
58
58
  CACHE_KEY_PREFIX,
59
59
  serializer.cache_key_base,
60
+ serializer.dependency_key,
60
61
  addons_key,
61
62
  cacheable.cache_key
62
63
  ].flatten.compact.join(CACHE_KEY_SEPARATOR)
@@ -69,7 +70,7 @@ module CacheCrispies
69
70
  # @return whatever the provided block returns
70
71
  def cache
71
72
  if cache?
72
- Rails.cache.fetch(cache_key) { yield }
73
+ CacheCrispies.cache.fetch(cache_key) { yield }
73
74
  else
74
75
  yield
75
76
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module CacheCrispies
4
4
  # The version of the gem
5
- VERSION = '0.3.1'
5
+ VERSION = '1.0.1'
6
6
  end
@@ -14,6 +14,7 @@ describe CacheCrispies::Attribute do
14
14
  let(:key) { :name }
15
15
  let(:from) { nil }
16
16
  let(:with) { nil }
17
+ let(:through) { nil }
17
18
  let(:to) { nil }
18
19
  let(:nesting) { [] }
19
20
  let(:conditions) { [] }
@@ -22,6 +23,7 @@ describe CacheCrispies::Attribute do
22
23
  key,
23
24
  from: from,
24
25
  with: with,
26
+ through: through,
25
27
  to: to,
26
28
  nesting: nesting,
27
29
  conditions: conditions
@@ -60,6 +62,23 @@ describe CacheCrispies::Attribute do
60
62
  end
61
63
  end
62
64
 
65
+ context 'with a through: argument' do
66
+ let(:through) { :branding }
67
+ let(:model) { OpenStruct.new(branding: OpenStruct.new(name: name)) }
68
+
69
+ it 'returns the value from the "through" object' do
70
+ expect(subject).to eq name
71
+ end
72
+
73
+ context 'when the through method returns nil' do
74
+ let(:model) { OpenStruct.new(branding: nil) }
75
+
76
+ it 'returns nil' do
77
+ expect(subject).to be nil
78
+ end
79
+ end
80
+ end
81
+
63
82
  context 'with a to: argument' do
64
83
  context 'when corecing to a String' do
65
84
  let(:name) { 1138 }
@@ -155,5 +174,15 @@ describe CacheCrispies::Attribute do
155
174
  end
156
175
  end
157
176
  end
177
+
178
+ context 'with a block' do
179
+ let(:instance) {
180
+ described_class.new(key) { |model, _opt| model.name.upcase }
181
+ }
182
+
183
+ it 'uses the return value of the block' do
184
+ expect(subject).to eq "CAP'N CRUNCH"
185
+ end
186
+ end
158
187
  end
159
188
  end