pennycress 0.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +333 -0
- data/lib/pennycress/cache.rb +80 -0
- data/lib/pennycress/caching.rb +18 -0
- data/lib/pennycress/configuration.rb +98 -0
- data/lib/pennycress/constraints.rb +15 -0
- data/lib/pennycress/errors.rb +6 -0
- data/lib/pennycress/input.rb +44 -0
- data/lib/pennycress/integration.rb +100 -0
- data/lib/pennycress/model_reference.rb +114 -0
- data/lib/pennycress/models.rb +32 -0
- data/lib/pennycress/output.rb +35 -0
- data/lib/pennycress/output_reference.rb +43 -0
- data/lib/pennycress/railtie.rb +37 -0
- data/lib/pennycress/registry.rb +50 -0
- data/lib/pennycress/schema.rb +88 -0
- data/lib/pennycress/value.rb +265 -0
- data/lib/pennycress/value_config.rb +49 -0
- data/lib/pennycress/version.rb +5 -0
- data/lib/pennycress/warm_seed_job.rb +19 -0
- data/lib/pennycress/warming.rb +30 -0
- data/lib/pennycress/watched_model.rb +68 -0
- data/lib/pennycress.rb +30 -0
- data/lib/tasks/pennycress.rake +8 -0
- metadata +77 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "cache"
|
|
4
|
+
require_relative "caching"
|
|
5
|
+
require_relative "configuration"
|
|
6
|
+
require_relative "constraints"
|
|
7
|
+
require_relative "input"
|
|
8
|
+
require_relative "output"
|
|
9
|
+
require_relative "output_reference"
|
|
10
|
+
require_relative "registry"
|
|
11
|
+
require_relative "value_config"
|
|
12
|
+
require_relative "watched_model"
|
|
13
|
+
|
|
14
|
+
module Pennycress
|
|
15
|
+
# A value describes a computation performed for each distinct input. After
|
|
16
|
+
# the initial value is computed, it is cached and reused until an invalidation
|
|
17
|
+
# condition is met.
|
|
18
|
+
class Value
|
|
19
|
+
include Constraints
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
# @param value [Class] the value being defined
|
|
23
|
+
# @return [void]
|
|
24
|
+
def inherited(value)
|
|
25
|
+
super
|
|
26
|
+
Registry.current.register(value)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [ValueConfig] the value's configuration
|
|
30
|
+
def config
|
|
31
|
+
@config ||= ValueConfig.new
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Evicts cached outputs for the given inputs
|
|
35
|
+
#
|
|
36
|
+
# @param inputs [Enumerable<Hash>] a list of keyword arguments for inputs
|
|
37
|
+
# @return [void]
|
|
38
|
+
# @raise [ValidationError] if any inputs are invalid
|
|
39
|
+
def evict_many(inputs)
|
|
40
|
+
evict = inputs
|
|
41
|
+
.map { |input| reference(input) }
|
|
42
|
+
.to_a
|
|
43
|
+
|
|
44
|
+
cache.evict_many(evict)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Computes an output value for the given input
|
|
48
|
+
#
|
|
49
|
+
# @param inspect_reference [Proc, nil] expose the output reference used for retrieval
|
|
50
|
+
# @param input [Hash] keyword arguments that should conform to the input schema
|
|
51
|
+
# @return [Object] the output value
|
|
52
|
+
# @raise [ValidationError] if the input is invalid
|
|
53
|
+
def fetch(inspect_reference: nil, **input)
|
|
54
|
+
ref = reference(input)
|
|
55
|
+
inspect_reference&.call(ref)
|
|
56
|
+
|
|
57
|
+
cache.fetch(ref) do
|
|
58
|
+
new.send(:fetch, **ref.input)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Computes an output value for each input in an enumerable
|
|
63
|
+
#
|
|
64
|
+
# @param inputs [Enumerable<Hash>] a list of keyword arguments for inputs
|
|
65
|
+
# @return [Array<Object>] output values
|
|
66
|
+
# @raise [ValidationError] if any inputs or outputs are invalid
|
|
67
|
+
def fetch_many(inputs)
|
|
68
|
+
new.send(:fetch_many, cache, inputs).to_a
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Defines the value's input schema
|
|
72
|
+
#
|
|
73
|
+
# @param model_ids [Array<Symbol>] model IDs (e.g., `:uploaded_file, :user`)
|
|
74
|
+
# @return [void]
|
|
75
|
+
def input(*model_ids)
|
|
76
|
+
config.input = Input.new(*model_ids)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Invalidate cached outputs for a watched model
|
|
80
|
+
#
|
|
81
|
+
# @param watch [WatchedModel] the watch that produced the change
|
|
82
|
+
# @param model [ActiveRecord::Base] the changed model instance
|
|
83
|
+
# @return [void]
|
|
84
|
+
def invalidate_model(watch, model)
|
|
85
|
+
evict_many(watch.inputs_for(model))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Defines the value's output schema
|
|
89
|
+
#
|
|
90
|
+
# @param schema [Class, Hash{Symbol => Class}] a scalar type or shape hash
|
|
91
|
+
# @return [void]
|
|
92
|
+
# @raise [ArgumentError] if the schema is invalid
|
|
93
|
+
def output(schema)
|
|
94
|
+
config.output = Output.new(schema)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Clears memoized cache bindings
|
|
98
|
+
#
|
|
99
|
+
# @return [void]
|
|
100
|
+
def reset_cache
|
|
101
|
+
@cache = nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Defines or returns the value's seeds
|
|
105
|
+
#
|
|
106
|
+
# @yieldreturn [Enumerable<Object>] a list of seeds to warm
|
|
107
|
+
# @return [Enumerable<Object>] the defined seeds, when called without a block
|
|
108
|
+
def seeds(&block)
|
|
109
|
+
if block
|
|
110
|
+
config.seeds = block
|
|
111
|
+
else
|
|
112
|
+
class_eval(&config.seeds)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Registers a watch on a model
|
|
117
|
+
#
|
|
118
|
+
# @param id [Symbol] the model ID to watch (e.g., `:discussion`)
|
|
119
|
+
# @param on [Array<Symbol>] commit actions that trigger invalidation
|
|
120
|
+
# @yieldparam model [ActiveRecord::Base] the changed model instance
|
|
121
|
+
# @yieldreturn [Enumerable<Hash>] inputs to refresh
|
|
122
|
+
# @return [void]
|
|
123
|
+
def watch(id, on: WatchedModel::ACTIONS, &inputs)
|
|
124
|
+
config.watches << WatchedModel.new(id, on: on, &inputs)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Warms the cache for each seed
|
|
128
|
+
#
|
|
129
|
+
# @return [void]
|
|
130
|
+
def warm
|
|
131
|
+
value = new
|
|
132
|
+
seeds.each { |seed| value.warm_seed(seed) }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
# @return [Cache] the value's cache
|
|
138
|
+
def cache
|
|
139
|
+
@cache ||= Cache.new(Configuration.current.cache)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @return [String] the value's cache namespace
|
|
143
|
+
def cache_namespace
|
|
144
|
+
@cache_namespace ||= if name
|
|
145
|
+
name.split("::").map(&:downcase).join("/")
|
|
146
|
+
else
|
|
147
|
+
""
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Builds an output reference for the given input
|
|
152
|
+
#
|
|
153
|
+
# @param input [Hash] keyword arguments that should conform to the input schema
|
|
154
|
+
# @return [OutputReference] a reference to the cached output
|
|
155
|
+
# @raise [ValidationError] if the input is invalid
|
|
156
|
+
def reference(input)
|
|
157
|
+
validated = config.input.validate(input)
|
|
158
|
+
|
|
159
|
+
namespace = Caching.key(
|
|
160
|
+
Configuration.current.cache_namespace,
|
|
161
|
+
cache_namespace
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
OutputReference.new(
|
|
165
|
+
input: validated,
|
|
166
|
+
namespace: namespace.empty? ? nil : namespace
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Computes an output value for a valid input
|
|
172
|
+
#
|
|
173
|
+
# @return [Object]
|
|
174
|
+
# @api value
|
|
175
|
+
def compute(*)
|
|
176
|
+
require_method(:compute)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Computes output values for each input in an enumerable
|
|
180
|
+
#
|
|
181
|
+
# @param inputs [Enumerable<Hash>] a list of valid inputs
|
|
182
|
+
# @return [Enumerable<Object>] output values
|
|
183
|
+
# @api value
|
|
184
|
+
def compute_many(inputs)
|
|
185
|
+
inputs.map do |input|
|
|
186
|
+
compute(**input)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Converts a seed to a list of inputs
|
|
191
|
+
#
|
|
192
|
+
# @return [Enumerable<Hash>]
|
|
193
|
+
# @api value
|
|
194
|
+
def seed_to_inputs(*)
|
|
195
|
+
require_method(:seed_to_inputs)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Warms the cache for a seed
|
|
199
|
+
#
|
|
200
|
+
# @param seed [Object]
|
|
201
|
+
# @return [void]
|
|
202
|
+
def warm_seed(seed)
|
|
203
|
+
self.class.send(:fetch_many, seed_to_inputs(seed)).each { nil }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
private
|
|
207
|
+
|
|
208
|
+
# Computes an output value for a valid input
|
|
209
|
+
#
|
|
210
|
+
# @param input [Hash] a valid input
|
|
211
|
+
# @return [Object] the output value
|
|
212
|
+
def fetch(**input)
|
|
213
|
+
output.validate(compute(**input))
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Computes an output value for each input in an enumerable
|
|
217
|
+
#
|
|
218
|
+
# @param cache [Cache]
|
|
219
|
+
# @param inputs [Enumerable<Hash>] inputs to validate and compute
|
|
220
|
+
# @return [Array<Object>] output values
|
|
221
|
+
def fetch_many(cache, inputs)
|
|
222
|
+
refs = inputs
|
|
223
|
+
.map { |input| self.class.send(:reference, input) }
|
|
224
|
+
.to_a
|
|
225
|
+
|
|
226
|
+
misses = []
|
|
227
|
+
|
|
228
|
+
values = cache.fetch_multi(refs) do |ref|
|
|
229
|
+
misses << ref
|
|
230
|
+
nil
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
return values if misses.empty?
|
|
234
|
+
|
|
235
|
+
inputs = misses.map(&:input)
|
|
236
|
+
results = compute_many(inputs).to_a
|
|
237
|
+
|
|
238
|
+
if results.length != inputs.length
|
|
239
|
+
raise ValidationError,
|
|
240
|
+
"compute_many must return one output per input: got #{results.length}, expected #{inputs.length}"
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
computed = results.map do |result|
|
|
244
|
+
output.validate(result)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
cache.write_multi(misses.zip(computed).to_h)
|
|
248
|
+
|
|
249
|
+
index = 0
|
|
250
|
+
|
|
251
|
+
values.map do |value|
|
|
252
|
+
if value.nil?
|
|
253
|
+
computed[index].tap { index += 1 }
|
|
254
|
+
else
|
|
255
|
+
value
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# @return [Output] the value's output schema
|
|
261
|
+
def output
|
|
262
|
+
@output ||= self.class.config.output
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
require_relative "input"
|
|
5
|
+
require_relative "output"
|
|
6
|
+
require_relative "watched_model"
|
|
7
|
+
|
|
8
|
+
module Pennycress
|
|
9
|
+
# A value config holds data describing how a value behaves.
|
|
10
|
+
class ValueConfig
|
|
11
|
+
# @!attribute [w] input
|
|
12
|
+
# @param value [Input]
|
|
13
|
+
attr_writer :input
|
|
14
|
+
|
|
15
|
+
# @!attribute [w] output
|
|
16
|
+
# @param value [Output]
|
|
17
|
+
attr_writer :output
|
|
18
|
+
|
|
19
|
+
# @!attribute [w] seeds
|
|
20
|
+
# @param value [Proc]
|
|
21
|
+
attr_writer :seeds
|
|
22
|
+
|
|
23
|
+
# @return [Input] the schema for the value's input
|
|
24
|
+
# @raise [ValidationError] if a schema is incomplete
|
|
25
|
+
def input
|
|
26
|
+
raise ValidationError, "input is not defined" if @input.nil?
|
|
27
|
+
|
|
28
|
+
@input
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Output] the schema for the value's output
|
|
32
|
+
# @raise [ValidationError] if a schema is not defined
|
|
33
|
+
def output
|
|
34
|
+
raise ValidationError, "output is not defined" if @output.nil?
|
|
35
|
+
|
|
36
|
+
@output
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Proc] a block that defines the list of seeds
|
|
40
|
+
def seeds
|
|
41
|
+
@seeds ||= proc { [] }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [Array<WatchedModel>] watches registered for the value
|
|
45
|
+
def watches
|
|
46
|
+
@watches ||= []
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_job"
|
|
4
|
+
|
|
5
|
+
require_relative "configuration"
|
|
6
|
+
|
|
7
|
+
module Pennycress
|
|
8
|
+
# This job warms cached outputs for one value seed.
|
|
9
|
+
class WarmSeedJob < ActiveJob::Base
|
|
10
|
+
queue_as { Configuration.current.warming_queue }
|
|
11
|
+
|
|
12
|
+
# @param value_class_name [String] the value class to warm
|
|
13
|
+
# @param seed [Object] the seed to warm
|
|
14
|
+
# @return [void]
|
|
15
|
+
def perform(value_class_name, seed)
|
|
16
|
+
value_class_name.constantize.new.warm_seed(seed)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "registry"
|
|
4
|
+
require_relative "warm_seed_job"
|
|
5
|
+
|
|
6
|
+
module Pennycress
|
|
7
|
+
# This module orchestrates cache warming for registered value classes.
|
|
8
|
+
module Warming
|
|
9
|
+
# Warms cached outputs for each registered value seed
|
|
10
|
+
#
|
|
11
|
+
# @param async [Boolean] whether to warm the cache via jobs or synchronous operations
|
|
12
|
+
# @return [void]
|
|
13
|
+
# @raise [ArgumentError] if a value class has no name
|
|
14
|
+
def self.warm_cache(async: true)
|
|
15
|
+
method = async ? :perform_later : :perform_now
|
|
16
|
+
|
|
17
|
+
Registry.current.values.each do |value_class|
|
|
18
|
+
value_class.seeds.each do |seed|
|
|
19
|
+
name = value_class.name
|
|
20
|
+
|
|
21
|
+
unless name
|
|
22
|
+
raise ArgumentError, "value class must have a name"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
WarmSeedJob.send(method, name, seed)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "models"
|
|
4
|
+
|
|
5
|
+
module Pennycress
|
|
6
|
+
# A watched model describes a model observed by a value. When the model
|
|
7
|
+
# changes, a value has the chance to selectively invalidate itself by deriving
|
|
8
|
+
# inputs from the changed model.
|
|
9
|
+
class WatchedModel
|
|
10
|
+
# Commit actions a watch can respond to
|
|
11
|
+
ACTIONS = %i[create destroy update].freeze
|
|
12
|
+
|
|
13
|
+
# @return [Symbol] the ID of the watched model
|
|
14
|
+
attr_reader :id
|
|
15
|
+
|
|
16
|
+
# @return [Proc] a block that maps a model to inputs
|
|
17
|
+
attr_reader :inputs
|
|
18
|
+
|
|
19
|
+
# @return [Array<Symbol>] commit actions that trigger invalidation
|
|
20
|
+
attr_reader :on
|
|
21
|
+
|
|
22
|
+
# Creates a watched model
|
|
23
|
+
#
|
|
24
|
+
# @param id [Symbol] the ID of the model to watch
|
|
25
|
+
# @param on [Array<Symbol>] commit actions that trigger invalidation
|
|
26
|
+
# @yieldparam model [ActiveRecord::Base] the changed model instance
|
|
27
|
+
# @yieldreturn [Enumerable<Hash>] inputs to refresh
|
|
28
|
+
# @raise [ArgumentError] if the commit actions are invalid
|
|
29
|
+
def initialize(id, on: ACTIONS, &inputs)
|
|
30
|
+
@id = id
|
|
31
|
+
@inputs = inputs
|
|
32
|
+
@on = validate_actions(on)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Produces invalidation inputs for a given model
|
|
36
|
+
#
|
|
37
|
+
# @param model [ActiveRecord::Base] a changed model instance
|
|
38
|
+
# @return [Enumerable<Hash>] invalidation inputs
|
|
39
|
+
def inputs_for(model)
|
|
40
|
+
inputs.call(model)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [Class] the watched ActiveRecord model class
|
|
44
|
+
def model_class
|
|
45
|
+
@model_class ||= Models.resolve(id)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# Validates commit actions
|
|
51
|
+
#
|
|
52
|
+
# @param actions [Array<Symbol>]
|
|
53
|
+
# @return [Array<Symbol>] valid commit actions
|
|
54
|
+
def validate_actions(actions)
|
|
55
|
+
if actions.empty?
|
|
56
|
+
raise ArgumentError, "commit actions cannot be empty"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
unknown = actions - ACTIONS
|
|
60
|
+
|
|
61
|
+
unless unknown.empty?
|
|
62
|
+
raise ArgumentError, "unsupported commit actions: #{unknown.map(&:inspect).join(', ')}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
actions.uniq.sort
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/pennycress.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "pennycress/configuration"
|
|
4
|
+
require_relative "pennycress/value"
|
|
5
|
+
require_relative "pennycress/version"
|
|
6
|
+
require_relative "pennycress/warming"
|
|
7
|
+
|
|
8
|
+
require_relative "pennycress/railtie" if defined?(Rails::Railtie)
|
|
9
|
+
|
|
10
|
+
# Pennycress allows Rails apps to precompute and cache expensive logic, with
|
|
11
|
+
# fine-grained invalidation and support for targeted cache warming.
|
|
12
|
+
module Pennycress
|
|
13
|
+
class << self
|
|
14
|
+
# Configures Pennycress
|
|
15
|
+
#
|
|
16
|
+
# @yieldparam config [Configuration] the current configuration
|
|
17
|
+
# @return [void]
|
|
18
|
+
def configure
|
|
19
|
+
yield Configuration.current
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Warms cached outputs for each registered value seed
|
|
23
|
+
#
|
|
24
|
+
# @param async [Boolean] whether to warm the cache via jobs or synchronous operations
|
|
25
|
+
# @return [void]
|
|
26
|
+
def warm_cache(async: true)
|
|
27
|
+
Warming.warm_cache(async: async)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: pennycress
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Justin Locsei
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '8.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '8.1'
|
|
26
|
+
description: Pennycress allows Rails apps to precompute and cache expensive logic,
|
|
27
|
+
with fine-grained invalidation and support for targeted cache warming.
|
|
28
|
+
executables: []
|
|
29
|
+
extensions: []
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
files:
|
|
32
|
+
- LICENSE
|
|
33
|
+
- README.md
|
|
34
|
+
- lib/pennycress.rb
|
|
35
|
+
- lib/pennycress/cache.rb
|
|
36
|
+
- lib/pennycress/caching.rb
|
|
37
|
+
- lib/pennycress/configuration.rb
|
|
38
|
+
- lib/pennycress/constraints.rb
|
|
39
|
+
- lib/pennycress/errors.rb
|
|
40
|
+
- lib/pennycress/input.rb
|
|
41
|
+
- lib/pennycress/integration.rb
|
|
42
|
+
- lib/pennycress/model_reference.rb
|
|
43
|
+
- lib/pennycress/models.rb
|
|
44
|
+
- lib/pennycress/output.rb
|
|
45
|
+
- lib/pennycress/output_reference.rb
|
|
46
|
+
- lib/pennycress/railtie.rb
|
|
47
|
+
- lib/pennycress/registry.rb
|
|
48
|
+
- lib/pennycress/schema.rb
|
|
49
|
+
- lib/pennycress/value.rb
|
|
50
|
+
- lib/pennycress/value_config.rb
|
|
51
|
+
- lib/pennycress/version.rb
|
|
52
|
+
- lib/pennycress/warm_seed_job.rb
|
|
53
|
+
- lib/pennycress/warming.rb
|
|
54
|
+
- lib/pennycress/watched_model.rb
|
|
55
|
+
- lib/tasks/pennycress.rake
|
|
56
|
+
licenses:
|
|
57
|
+
- MIT
|
|
58
|
+
metadata:
|
|
59
|
+
rubygems_mfa_required: 'true'
|
|
60
|
+
rdoc_options: []
|
|
61
|
+
require_paths:
|
|
62
|
+
- lib
|
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 3.3.0
|
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
requirements: []
|
|
74
|
+
rubygems_version: 4.0.20
|
|
75
|
+
specification_version: 4
|
|
76
|
+
summary: Memoized domain values for Rails
|
|
77
|
+
test_files: []
|