cache_crispies 0.3.1 → 1.0.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
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