miscellany 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/miscellany/active_record/arbitrary_prefetch.rb +3 -13
- data/lib/miscellany/active_record/computed_columns.rb +107 -0
- data/lib/miscellany/controller/sliced_response.rb +11 -3
- data/lib/miscellany/param_validator.rb +133 -86
- data/lib/miscellany/version.rb +1 -1
- data/lib/miscellany.rb +8 -0
- data/miscellany.gemspec +8 -17
- data/spec/db/database.yml +3 -0
- data/spec/miscellany/arbitrary_prefetch_spec.rb +69 -0
- data/spec/miscellany/computed_columns_spec.rb +62 -0
- data/spec/miscellany/param_validator_spec.rb +295 -0
- data/spec/spec_helper.rb +42 -0
- metadata +37 -177
- data/app/views/miscellany/spa_page.html.erb +0 -2
- data/config/initializers/01_custom_preloaders.rb +0 -2
- data/config/initializers/arbitrary_prefetch.rb +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70a91bb987b7c99319cbe607cdfba6c32e9637a5a3fb8bec3ec38725339ea3c6
|
4
|
+
data.tar.gz: f9607f408e91b1abadb51e73c4238b1a1d9f378fe294461efe11f3172783d9e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8c1ba0f079635d38ed0d3a29d3cb865899fb66112593625a2ae159061c5fd48f63cfa2300a825f671bdbd04e4e2317d6ccedd80702391bdadd7329c0a2deae9
|
7
|
+
data.tar.gz: a73fd773078016b440b08243c4c6dc5477ff26cdbacb054f57d8b99b77c15305af7a1741d0c94b02399c3b59af7553254a23be5441d1d30948f4f51cb53b6ccf
|
@@ -4,16 +4,6 @@
|
|
4
4
|
# The values of the Hash may be an array of [Symbol, Relation] or another (filtered) Relation.
|
5
5
|
# Objects are queried from an existing Association on the model. This Association is detemrined
|
6
6
|
# by either the Symbol when an array is passed, or by finding an Assoication for the passed Relation's model
|
7
|
-
#
|
8
|
-
# NOTICE: This implementation is NOT COMPLETE by itself - it depends on Goldiloader
|
9
|
-
# to detect the use of the virtual associations and prevent N+1s. We were already using
|
10
|
-
# Goldiloader, so this made sense. If this module is ever needed stand-alone,
|
11
|
-
# the following options have been identified:
|
12
|
-
# 1. Extend ActiveRecordRelationPatch#exec_queries to execute an ActiveRecord::Associations::Preloader
|
13
|
-
# that will load the related objects
|
14
|
-
# 2. Duplicates the relevant snippets from Goldiloader into this module. See Goldiloader::AutoIncludeContext
|
15
|
-
# The current Goldiloader implementation uses Option 1 internally, but also makes the relations lazy - even
|
16
|
-
# if you define a prefetch, it won't actually be loaded until you attempt to access it on one of the models.
|
17
7
|
module Miscellany
|
18
8
|
module ArbitraryPrefetch
|
19
9
|
ACTIVE_RECORD_VERSION = ::Gem::Version.new(::ActiveRecord::VERSION::STRING).release
|
@@ -111,12 +101,12 @@ module Miscellany
|
|
111
101
|
assert_mutability!
|
112
102
|
@values[:prefetches] ||= {}
|
113
103
|
kwargs.each do |attr, opts|
|
114
|
-
@values[:prefetches][attr] =
|
104
|
+
@values[:prefetches][attr] = normalize_prefetch_options(attr, opts)
|
115
105
|
end
|
116
106
|
self
|
117
107
|
end
|
118
108
|
|
119
|
-
def
|
109
|
+
def normalize_prefetch_options(attr, opts)
|
120
110
|
norm = if opts.is_a?(Array)
|
121
111
|
{ relation: opts[0], queryset: opts[1] }
|
122
112
|
elsif opts.is_a?(ActiveRecord::Relation)
|
@@ -155,7 +145,7 @@ module Miscellany
|
|
155
145
|
records.each do |record|
|
156
146
|
next unless record
|
157
147
|
reflection = record.class._reflect_on_association(association)
|
158
|
-
reflection ||= record.association(association)&.reflection
|
148
|
+
reflection ||= record.association(association)&.reflection rescue nil
|
159
149
|
next if polymorphic_parent && !reflection || !record.association(association).klass
|
160
150
|
(h[reflection] ||= []) << record
|
161
151
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
|
2
|
+
module Miscellany
|
3
|
+
module ComputedColumns
|
4
|
+
module ActiveRecordBasePatch
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class << self
|
9
|
+
delegate :with_computed, to: :all
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class_methods do
|
14
|
+
def define_computed(key, dirblk = nil, &blk)
|
15
|
+
blk ||= dirblk
|
16
|
+
@defined_computeds ||= {}
|
17
|
+
@defined_computeds[key] = blk
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_defined_computed(key)
|
21
|
+
@defined_computeds ||= {}
|
22
|
+
@defined_computeds[key] || superclass.try(:get_defined_computed, key)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module ActiveRecordRelationPatch
|
28
|
+
def with_computed(*args, **kwargs)
|
29
|
+
entries = { **kwargs }
|
30
|
+
args.each do |k|
|
31
|
+
entries[k] = []
|
32
|
+
end
|
33
|
+
|
34
|
+
entries.reduce(self) do |query, (k, v)|
|
35
|
+
comp = model.get_defined_computed(k)
|
36
|
+
raise "Undefined ComputedColum :#{k}" if comp.nil?
|
37
|
+
|
38
|
+
builder = ComputedBuilder.new(k, v, &comp)
|
39
|
+
builder.apply(query)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class ComputedBuilder
|
45
|
+
def initialize(key, args, &blk)
|
46
|
+
@key = key
|
47
|
+
@args = args.is_a?(Array) ? args : [args]
|
48
|
+
@block = blk
|
49
|
+
@compiled = {}
|
50
|
+
end
|
51
|
+
|
52
|
+
%i[select join_condition query].each do |m|
|
53
|
+
define_method(m) do |arg=:not_given, &blk|
|
54
|
+
raise "Must provide either a value or a block" if arg != :not_given && blk
|
55
|
+
raise "Must provide either a value or a block" if arg == :not_given && !blk
|
56
|
+
|
57
|
+
if arg == :not_given
|
58
|
+
arg = blk.call
|
59
|
+
end
|
60
|
+
|
61
|
+
@compiled[m] = arg
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def apply(q)
|
66
|
+
instance_exec(*@args, &@block)
|
67
|
+
|
68
|
+
c = @compiled
|
69
|
+
raise "defined_computed: query must be provided" unless c[:query]
|
70
|
+
|
71
|
+
join_name = @key.to_s
|
72
|
+
base_table_name = current_table_from_scope(q)
|
73
|
+
c[:join_condition] ||= "COMPUTED.id = #{base_table_name}.id"
|
74
|
+
c[:select] ||= "#{join_name}.value AS #{@key}"
|
75
|
+
|
76
|
+
q = q.select("#{base_table_name}.*") if !q.values[:select].present?
|
77
|
+
|
78
|
+
select_statement = c[:select].gsub('COMPUTED', join_name)
|
79
|
+
join_condition = c[:join_condition].gsub('COMPUTED', join_name)
|
80
|
+
join_query = c[:query]
|
81
|
+
join_query = join_query.to_sql if join_query.respond_to?(:to_sql)
|
82
|
+
|
83
|
+
q.select(select_statement).joins("LEFT OUTER JOIN (#{join_query}) #{join_name} ON #{join_condition}")
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
def current_table_from_scope(q)
|
89
|
+
current_table = q.current_scope.arel.source.left
|
90
|
+
|
91
|
+
case current_table
|
92
|
+
when Arel::Table
|
93
|
+
current_table.name
|
94
|
+
when Arel::Nodes::TableAlias
|
95
|
+
current_table.right
|
96
|
+
else
|
97
|
+
fail
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.install
|
103
|
+
::ActiveRecord::Base.include(ActiveRecordBasePatch)
|
104
|
+
::ActiveRecord::Relation.prepend(ActiveRecordRelationPatch)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -86,7 +86,7 @@ module Miscellany
|
|
86
86
|
def normalize_sort(sort, key: nil)
|
87
87
|
sort = sort.to_s if sort.is_a?(Symbol)
|
88
88
|
if sort.is_a?(String)
|
89
|
-
m = sort.match(
|
89
|
+
m = sort.match(/^([\w\.]+)(?: (ASC|DESC)(!?))?$/)
|
90
90
|
sort = { column: m[1], order: m[2], force_order: m[3].present? }.compact
|
91
91
|
elsif sort.is_a?(Proc)
|
92
92
|
sort = { column: sort }
|
@@ -215,12 +215,20 @@ module Miscellany
|
|
215
215
|
|
216
216
|
def rendered_items
|
217
217
|
ritems = sliced_items
|
218
|
-
ritems = ritems.map(&options[:item_transformer]) if options[:item_transformer]
|
218
|
+
ritems = ritems.to_a.map(&options[:item_transformer]) if options[:item_transformer]
|
219
219
|
ritems
|
220
220
|
end
|
221
221
|
|
222
222
|
def total_item_count
|
223
|
-
@total_item_count ||= options[:total_count] ||
|
223
|
+
@total_item_count ||= options[:total_count] || begin
|
224
|
+
if items.is_a?(ActiveRecord::Relation)
|
225
|
+
items.except(:select).count
|
226
|
+
elsif items.respond_to?(:count)
|
227
|
+
items.count
|
228
|
+
else
|
229
|
+
nil
|
230
|
+
end
|
231
|
+
end
|
224
232
|
end
|
225
233
|
|
226
234
|
def sliced_items
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Miscellany
|
2
2
|
class ParamValidator
|
3
|
-
attr_accessor :context, :
|
3
|
+
attr_accessor :context, :errors
|
4
|
+
attr_reader :params
|
4
5
|
|
5
6
|
delegate_missing_to :context
|
6
7
|
|
@@ -9,7 +10,7 @@ module Miscellany
|
|
9
10
|
CHECKS = %i[type specified present default transform in block items pattern].freeze
|
10
11
|
NON_PREFIXED = %i[default transform type message timezone].freeze
|
11
12
|
PREFIXES = %i[all onem onep one none].freeze
|
12
|
-
PREFIX_ALIASES = { any: :onep, not: :none }.freeze
|
13
|
+
PREFIX_ALIASES = { any: :onep, not: :none, one_or_less: :onem, one_or_more: :onem }.freeze
|
13
14
|
ALL_PREFIXES = (PREFIXES + PREFIX_ALIASES.keys).freeze
|
14
15
|
VALID_FLAGS = %i[present specified].freeze
|
15
16
|
|
@@ -21,14 +22,11 @@ module Miscellany
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
|
-
def initialize(block, context, parameters = nil
|
25
|
+
def initialize(block, context, parameters = nil)
|
25
26
|
@block = block
|
26
27
|
@context = context
|
27
28
|
@params = parameters || context.params
|
28
|
-
@
|
29
|
-
@options = options || {}
|
30
|
-
@errors = {}
|
31
|
-
@explicit_parameters = []
|
29
|
+
@errors = ErrorStore.new
|
32
30
|
end
|
33
31
|
|
34
32
|
def self.check(params, context: nil, &blk)
|
@@ -48,8 +46,13 @@ module Miscellany
|
|
48
46
|
|
49
47
|
def apply_checks(&blk)
|
50
48
|
blk ||= @block
|
51
|
-
args = trim_arguments(blk, [params,
|
52
|
-
|
49
|
+
args = trim_arguments(blk, [@params, :TODO])
|
50
|
+
|
51
|
+
dresult = instance_exec(*args, &blk)
|
52
|
+
dresult = "failed validation #{check}" if dresult == false
|
53
|
+
if dresult.present? && dresult != true
|
54
|
+
@errors.push(dresult)
|
55
|
+
end
|
53
56
|
end
|
54
57
|
|
55
58
|
def parameter(param_keys, *args, **kwargs, &blk)
|
@@ -107,45 +110,57 @@ module Miscellany
|
|
107
110
|
|
108
111
|
# Nested check
|
109
112
|
run_check[:block] do |blk|
|
110
|
-
|
111
|
-
sub_parameter(pk) do
|
112
|
-
if params.is_a?(Array) && iterate_array
|
113
|
-
params.each_with_index do |v, i|
|
114
|
-
sub_parameter(i) { apply_checks(&blk) }
|
115
|
-
end
|
116
|
-
else
|
117
|
-
apply_checks(&blk)
|
118
|
-
end
|
119
|
-
end
|
113
|
+
ParamValidator.check(params[pk], context: context, &blk)
|
120
114
|
end
|
121
115
|
|
122
|
-
#
|
116
|
+
# Array Items check
|
123
117
|
run_check[:items] do |blk|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
118
|
+
if params[pk].is_a?(Array)
|
119
|
+
error_items = 0
|
120
|
+
astore = ErrorStore.new
|
121
|
+
|
122
|
+
params[pk].each_with_index do |v, i|
|
123
|
+
errs = nil
|
124
|
+
if blk.is_a?(Hash)
|
125
|
+
pv = ParamValidator.new(nil, self.context, params[pk])
|
126
|
+
pv.parameter(i, **blk)
|
127
|
+
ers = pv.errors
|
128
|
+
else
|
129
|
+
errs = ParamValidator.check(params[pk][i], context: context, &blk)
|
130
|
+
end
|
131
|
+
|
132
|
+
if errs.present?
|
133
|
+
error_items += 1
|
134
|
+
if error_items > 5
|
135
|
+
check_results[:items]
|
136
|
+
astore.push("Too Many Errors")
|
137
|
+
break
|
138
|
+
else
|
139
|
+
astore.push_to(i, errs)
|
140
|
+
end
|
128
141
|
end
|
129
|
-
else
|
130
|
-
raise "items: validator can only be used with Arrays"
|
131
142
|
end
|
143
|
+
|
144
|
+
astore
|
145
|
+
else
|
146
|
+
raise "items: validator can only be used with Arrays"
|
132
147
|
end
|
133
148
|
end
|
134
149
|
end
|
135
150
|
|
136
|
-
final_errors =
|
151
|
+
final_errors = ErrorStore.new
|
137
152
|
checks.each do |check, check_prefix|
|
138
153
|
if check_prefix == :all || check_prefix == nil
|
139
154
|
all_results.each do |field, err_map|
|
140
155
|
errs = err_map[check]
|
141
156
|
next unless errs.present?
|
142
157
|
|
143
|
-
final_errors
|
158
|
+
final_errors.push_to(field, errs)
|
144
159
|
end
|
145
160
|
elsif check_prefix == :none
|
146
161
|
all_results.each do |field, err_map|
|
147
162
|
errs = err_map[check]
|
148
|
-
final_errors
|
163
|
+
final_errors.push_to(field, "must NOT be #{check}") unless errs.present?
|
149
164
|
end
|
150
165
|
else
|
151
166
|
counts = check_pass_count(check, all_results)
|
@@ -160,13 +175,14 @@ module Miscellany
|
|
160
175
|
(counts[:passed] > 1 && check_prefix == :onem) ||
|
161
176
|
(counts[:passed] < 1 && check_prefix == :onep)
|
162
177
|
|
163
|
-
final_errors
|
178
|
+
final_errors.push("#{string_prefixes[check_prefix]} #{field_key} must be #{check}")
|
164
179
|
end
|
165
180
|
end
|
166
181
|
end
|
167
182
|
|
168
|
-
@errors
|
169
|
-
|
183
|
+
@errors.merge!(final_errors)
|
184
|
+
|
185
|
+
nil
|
170
186
|
end
|
171
187
|
|
172
188
|
alias p parameter
|
@@ -197,27 +213,27 @@ module Miscellany
|
|
197
213
|
check_prefixes = NON_PREFIXED.include?(check) ? [nil] : Array(checks_to_run&.[](check))
|
198
214
|
return true unless check_prefixes.present?
|
199
215
|
|
216
|
+
encountered_errors = false
|
217
|
+
|
200
218
|
check_prefixes.each do |check_prefix|
|
201
|
-
|
202
|
-
@errors = []
|
219
|
+
prefixed_check = [check_prefix, check].compact.join('_')
|
203
220
|
prefix_options = (check_prefix.nil? ? options : options&.[](check_prefix)) || {}
|
204
|
-
args = trim_arguments(blk, [prefix_options[check]])
|
205
221
|
|
206
|
-
result =
|
222
|
+
result = blk.call(*trim_arguments(blk, [prefix_options[check]]))
|
207
223
|
result = "failed validation #{check}" if result == false
|
208
224
|
|
225
|
+
store = state[check] ||= ErrorStore.new
|
226
|
+
|
209
227
|
if result.present? && result != true
|
210
228
|
result = options[:message] if options&.[](:message).present?
|
211
|
-
Array(result).each do |e|
|
212
|
-
@errors << e
|
213
|
-
end
|
214
|
-
end
|
215
229
|
|
216
|
-
|
217
|
-
|
230
|
+
store.push(result)
|
231
|
+
|
232
|
+
encountered_errors = true
|
233
|
+
end
|
218
234
|
end
|
219
235
|
|
220
|
-
!
|
236
|
+
!encountered_errors
|
221
237
|
end
|
222
238
|
|
223
239
|
def coerce_type(params, key, opts)
|
@@ -243,10 +259,11 @@ module Miscellany
|
|
243
259
|
|
244
260
|
return type.call(param, options) if type.is_a?(Proc)
|
245
261
|
|
246
|
-
|
262
|
+
is_rails_parameters = defined?(ActionController::Parameters) && param.is_a?(ActionController::Parameters)
|
263
|
+
if (param.is_a?(Array) && type != Array) || ((param.is_a?(Hash) || is_rails_parameters) && type != Hash)
|
247
264
|
raise ArgumentError
|
248
265
|
end
|
249
|
-
return param if (
|
266
|
+
return param if (is_rails_parameters && type == Hash rescue false)
|
250
267
|
|
251
268
|
# Primitives
|
252
269
|
return Integer(param) if type == Integer
|
@@ -374,53 +391,13 @@ module Miscellany
|
|
374
391
|
skey = key.to_s
|
375
392
|
ALL_PREFIXES.each do |pfx|
|
376
393
|
spfx = pfx.to_s
|
377
|
-
next unless skey.
|
394
|
+
next unless skey.start_with?("#{spfx}_")
|
378
395
|
|
379
396
|
return [skey[(spfx.length + 1)..-1].to_sym, PREFIX_ALIASES[pfx] || pfx]
|
380
397
|
end
|
381
398
|
[key, nil]
|
382
399
|
end
|
383
400
|
|
384
|
-
def sub_parameter(k)
|
385
|
-
@subkeys.push(k)
|
386
|
-
yield
|
387
|
-
ensure
|
388
|
-
@subkeys.pop
|
389
|
-
end
|
390
|
-
|
391
|
-
def params
|
392
|
-
p = @params
|
393
|
-
@subkeys.each { |k| p = p[k] }
|
394
|
-
p
|
395
|
-
end
|
396
|
-
|
397
|
-
def merge_error_hashes(target, from)
|
398
|
-
target ||= []
|
399
|
-
if target.is_a?(Hash)
|
400
|
-
ta = []
|
401
|
-
th = target
|
402
|
-
else
|
403
|
-
ta = target
|
404
|
-
th = target[-1].is_a?(Hash) ? ta.pop : {}
|
405
|
-
end
|
406
|
-
|
407
|
-
if from.is_a?(Hash)
|
408
|
-
from.each_pair do |k, v|
|
409
|
-
th[k] = merge_error_hashes(th[k], v)
|
410
|
-
end
|
411
|
-
elsif from.is_a?(Array)
|
412
|
-
merge_error_hashes(th, from.pop) if from[-1].is_a?(Hash)
|
413
|
-
from.each { |f| ta << f }
|
414
|
-
else
|
415
|
-
ta << from
|
416
|
-
end
|
417
|
-
|
418
|
-
return th if !ta.present? && th.present?
|
419
|
-
|
420
|
-
ta << th if th.present?
|
421
|
-
ta
|
422
|
-
end
|
423
|
-
|
424
401
|
def merge_hashes(h1, h2)
|
425
402
|
h2.each do |k, v|
|
426
403
|
set_hash_key(h1, k, v)
|
@@ -438,5 +415,75 @@ module Miscellany
|
|
438
415
|
return args if blk.arity.negative?
|
439
416
|
args[0..(blk.arity.abs - 1)]
|
440
417
|
end
|
418
|
+
|
419
|
+
class ErrorStore
|
420
|
+
attr_reader :errors, :fields
|
421
|
+
|
422
|
+
def initialize
|
423
|
+
@errors = []
|
424
|
+
@fields = {}
|
425
|
+
end
|
426
|
+
|
427
|
+
def push(error)
|
428
|
+
if error.is_a?(ErrorStore)
|
429
|
+
merge!(error)
|
430
|
+
elsif error.is_a?(Array)
|
431
|
+
error.each{|e| push(e) }
|
432
|
+
else
|
433
|
+
@errors << error
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
def push_to(field, error)
|
438
|
+
store_for(field).push(error)
|
439
|
+
end
|
440
|
+
|
441
|
+
def present?
|
442
|
+
@errors.present? || @fields.values.any?(&:present?)
|
443
|
+
end
|
444
|
+
|
445
|
+
def serialize
|
446
|
+
if @fields.values.any?(&:present?)
|
447
|
+
h = { }
|
448
|
+
h[:_SELF_] = @errors if @errors.present?
|
449
|
+
@fields.each do |k, v|
|
450
|
+
s = v.serialize
|
451
|
+
next unless s.present?
|
452
|
+
h[k] = s
|
453
|
+
end
|
454
|
+
h
|
455
|
+
elsif @errors.present?
|
456
|
+
@errors
|
457
|
+
else
|
458
|
+
nil
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
def merge!(other_store)
|
463
|
+
return self if other_store == self
|
464
|
+
|
465
|
+
@errors |= other_store.errors
|
466
|
+
other_store.fields.each do |k, v|
|
467
|
+
store_for(k).merge!(v)
|
468
|
+
end
|
469
|
+
|
470
|
+
self
|
471
|
+
end
|
472
|
+
|
473
|
+
def store_for(field)
|
474
|
+
if field.is_a?(Symbol) || field.is_a?(Numeric)
|
475
|
+
field = [field]
|
476
|
+
elsif field.is_a?(String)
|
477
|
+
field = field.split('.')
|
478
|
+
elsif field.is_a?(Array)
|
479
|
+
field = [*field]
|
480
|
+
end
|
481
|
+
|
482
|
+
sfield = field.shift
|
483
|
+
store = @fields[sfield.to_s] ||= ErrorStore.new
|
484
|
+
return store if field.count == 0
|
485
|
+
return store.store_for(field)
|
486
|
+
end
|
487
|
+
end
|
441
488
|
end
|
442
489
|
end
|
data/lib/miscellany/version.rb
CHANGED
data/lib/miscellany.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
|
2
|
+
require "active_support/lazy_load_hooks"
|
3
|
+
|
2
4
|
Dir[File.dirname(__FILE__) + "/miscellany/**/*.rb"].each { |file| require file }
|
3
5
|
|
4
6
|
module Miscellany
|
@@ -7,4 +9,10 @@ module Miscellany
|
|
7
9
|
class Engine < ::Rails::Engine
|
8
10
|
end
|
9
11
|
end
|
12
|
+
|
13
|
+
ActiveSupport.on_load(:active_record) do
|
14
|
+
Miscellany::CustomPreloaders.install
|
15
|
+
Miscellany::ArbitraryPrefetch.install
|
16
|
+
Miscellany::ComputedColumns.install
|
17
|
+
end
|
10
18
|
end
|
data/miscellany.gemspec
CHANGED
@@ -22,22 +22,13 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.test_files = Dir["spec/**/*"]
|
23
23
|
spec.require_paths = ['lib']
|
24
24
|
|
25
|
-
spec.
|
26
|
-
spec.
|
27
|
-
spec.
|
28
|
-
spec.add_development_dependency "rspec-rails"
|
29
|
-
spec.add_development_dependency "pg"
|
30
|
-
spec.add_development_dependency "factory"
|
31
|
-
spec.add_development_dependency "factory_bot"
|
32
|
-
spec.add_development_dependency "timecop"
|
33
|
-
spec.add_development_dependency "webmock"
|
34
|
-
spec.add_development_dependency "sinatra", ">= 0"
|
35
|
-
spec.add_development_dependency "shoulda-matchers"
|
36
|
-
spec.add_development_dependency "yard"
|
37
|
-
spec.add_development_dependency "pry"
|
38
|
-
spec.add_development_dependency "pry-nav"
|
39
|
-
spec.add_development_dependency "rubocop"
|
25
|
+
spec.add_dependency 'rails', '>= 5', '< 6.3'
|
26
|
+
# spec.add_dependency 'activerecord', '>= 5', '< 6.3'
|
27
|
+
# spec.add_dependency 'activesupport', '>= 5', '< 6.3'
|
40
28
|
|
41
|
-
spec.
|
42
|
-
spec.
|
29
|
+
spec.add_development_dependency 'rake'
|
30
|
+
spec.add_development_dependency 'database_cleaner', '>= 1.2'
|
31
|
+
spec.add_development_dependency 'rspec', '~> 3'
|
32
|
+
spec.add_development_dependency 'sqlite3', '~> 1.3'
|
33
|
+
spec.add_development_dependency 'with_model'
|
43
34
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Miscellany::ArbitraryPrefetch do
|
5
|
+
with_model :Post do
|
6
|
+
table do |t|
|
7
|
+
t.string :title
|
8
|
+
t.timestamps null: false
|
9
|
+
end
|
10
|
+
|
11
|
+
model do
|
12
|
+
has_many :comments
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
with_model :Comment do
|
17
|
+
table do |t|
|
18
|
+
t.belongs_to :post
|
19
|
+
t.string :title
|
20
|
+
t.boolean :favorite
|
21
|
+
t.timestamps null: false
|
22
|
+
end
|
23
|
+
|
24
|
+
model do
|
25
|
+
belongs_to :post
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
let!(:posts) { 10.times.map{|i| Post.create!(title: "Post #{i}") } }
|
30
|
+
|
31
|
+
before :each do
|
32
|
+
posts.each do |p|
|
33
|
+
5.times{|i| p.comments.create!(title: "#{p.title} Comment #{i}") }
|
34
|
+
p.comments.last.update!(favorite: true)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'generally works' do
|
39
|
+
posts = Post.prefetch(favorite_comment: Comment.where(favorite: true))
|
40
|
+
expect(posts.count).to eq 10
|
41
|
+
expect(posts[0].favorite_comment).to be
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'prefetch is singluar' do
|
45
|
+
it 'returns a single object' do
|
46
|
+
posts = Post.prefetch(favorite_comment: Comment.where(favorite: true))
|
47
|
+
expect(posts[0].favorite_comment).to be_a Comment
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'with multiple items returns a single object' do
|
51
|
+
posts = Post.prefetch(favorite_comment: Comment.where(favorite: nil))
|
52
|
+
expect(posts[0].favorite_comment).to be_a Comment
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'prefetch is plural' do
|
57
|
+
it 'returns an Array' do
|
58
|
+
posts = Post.prefetch(non_favorite_comments: Comment.where(favorite: nil))
|
59
|
+
expect(posts[0].non_favorite_comments).to respond_to :[]
|
60
|
+
expect(posts[0].non_favorite_comments.length).to eq 4
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'with 1 item returns an Array' do
|
64
|
+
posts = Post.prefetch(non_favorite_comments: Comment.where(favorite: true))
|
65
|
+
expect(posts[0].non_favorite_comments).to respond_to :[]
|
66
|
+
expect(posts[0].non_favorite_comments.length).to eq 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Miscellany::ComputedColumns do
|
5
|
+
with_model :Post do
|
6
|
+
table do |t|
|
7
|
+
t.string :title
|
8
|
+
t.timestamps null: false
|
9
|
+
end
|
10
|
+
|
11
|
+
model do
|
12
|
+
has_many :comments
|
13
|
+
|
14
|
+
define_computed :favorite_comments_count, ->() {
|
15
|
+
select "COMPUTED.count AS favorite_comments_count"
|
16
|
+
|
17
|
+
query do
|
18
|
+
Comment.select(<<~SQL)
|
19
|
+
post_id AS id,
|
20
|
+
count(*) AS count
|
21
|
+
SQL
|
22
|
+
.group(:post_id)
|
23
|
+
.where(favorite: true)
|
24
|
+
end
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
with_model :Comment do
|
30
|
+
table do |t|
|
31
|
+
t.belongs_to :post
|
32
|
+
t.string :title
|
33
|
+
t.boolean :favorite
|
34
|
+
t.timestamps null: false
|
35
|
+
end
|
36
|
+
|
37
|
+
model do
|
38
|
+
belongs_to :post
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
let!(:posts) { 3.times.map{|i| Post.create!(title: "Post #{i}") } }
|
43
|
+
|
44
|
+
before :each do
|
45
|
+
posts.each do |p|
|
46
|
+
5.times{|i| p.comments.create!(title: "#{p.title} Comment #{i}") }
|
47
|
+
p.comments.last.update!(favorite: true)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'generally works' do
|
52
|
+
ActiveRecord::Base.verbose_query_logs = true
|
53
|
+
posts = Post.with_computed(:favorite_comments_count)
|
54
|
+
expect(posts.except(:select).count).to eq 3
|
55
|
+
expect(posts[0].favorite_comments_count).to eq 1
|
56
|
+
|
57
|
+
Comment.update_all(favorite: true)
|
58
|
+
posts = Post.with_computed(:favorite_comments_count)
|
59
|
+
expect(posts.except(:select).count).to eq 3
|
60
|
+
expect(posts[0].favorite_comments_count).to eq 5
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,295 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Miscellany::ParamValidator do
|
5
|
+
let!(:value) do
|
6
|
+
{
|
7
|
+
int_array: [1,2,3],
|
8
|
+
string_array: %w[A B C],
|
9
|
+
some_number: 2,
|
10
|
+
some_string: "Robert",
|
11
|
+
specified: nil,
|
12
|
+
array: [
|
13
|
+
{a: 2},
|
14
|
+
{a: 3},
|
15
|
+
],
|
16
|
+
hash: {
|
17
|
+
nested_hash: {
|
18
|
+
value: 1,
|
19
|
+
}
|
20
|
+
},
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
# TODO transform:
|
25
|
+
|
26
|
+
def expect_valid(&blk)
|
27
|
+
result = Miscellany::ParamValidator.check(value, &blk)
|
28
|
+
expect(result.serialize).to be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def expect_invalid(&blk)
|
32
|
+
result = Miscellany::ParamValidator.check(value, &blk)
|
33
|
+
expect(result.serialize).to be_present
|
34
|
+
end
|
35
|
+
|
36
|
+
def expect_coercion(raw, type, expectation)
|
37
|
+
result = Miscellany::ParamValidator.assert({ value: raw }, handle: ->(v){ raise 'Invalid' }) do
|
38
|
+
p :value, type: type
|
39
|
+
end
|
40
|
+
expect(result[:value]).to eq expectation
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'default:' do
|
44
|
+
it 'sets a default value' do
|
45
|
+
result = Miscellany::ParamValidator.assert({ }, handle: ->(v){ raise 'Invalid' }) do
|
46
|
+
p :value, default: 'HIA'
|
47
|
+
end
|
48
|
+
expect(result[:value]).to eq 'HIA'
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'works deep' do
|
52
|
+
result = Miscellany::ParamValidator.assert({ value: {} }, handle: ->(v){ raise 'Invalid' }) do
|
53
|
+
p :value do |x|
|
54
|
+
p :value, default: 'Hia'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
expect(result).to eq ({ value: { value: 'Hia' } })
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'specified' do
|
62
|
+
it 'passes a given value' do
|
63
|
+
expect_valid do
|
64
|
+
p :some_string, :specified
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'passes a given nil' do
|
69
|
+
expect_valid do
|
70
|
+
p :specified, :specified
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'fails an unspecified key' do
|
75
|
+
expect_invalid do
|
76
|
+
p :not_specified, :specified
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'present' do
|
82
|
+
it 'passes a given value' do
|
83
|
+
expect_valid do
|
84
|
+
p :some_string, :present
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'fails a given nil' do
|
89
|
+
expect_invalid do
|
90
|
+
p :specified, :present
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'fails an unspecified key' do
|
95
|
+
expect_invalid do
|
96
|
+
p :not_specified, :present
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'type:' do
|
102
|
+
it 'passes based on type' do
|
103
|
+
expect_valid do
|
104
|
+
p :some_number, type: Numeric
|
105
|
+
p :some_string, type: String
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'fails based on type' do
|
110
|
+
expect_invalid do
|
111
|
+
p :some_number, type: String
|
112
|
+
p :some_string, type: Numeric
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe ':bool' do
|
117
|
+
it 'transforms booleans' do
|
118
|
+
expect_coercion('t', :bool, true)
|
119
|
+
expect_coercion('T', :bool, true)
|
120
|
+
expect_coercion('true', :bool, true)
|
121
|
+
expect_coercion('True', :bool, true)
|
122
|
+
expect_coercion('TRUE', :bool, true)
|
123
|
+
expect_coercion('YES', :bool, true)
|
124
|
+
expect_coercion('yes', :bool, true)
|
125
|
+
expect_coercion('Y', :bool, true)
|
126
|
+
expect_coercion('y', :bool, true)
|
127
|
+
expect_coercion('1', :bool, true)
|
128
|
+
expect_coercion(1, :bool, true)
|
129
|
+
|
130
|
+
expect_coercion('f', :bool, false)
|
131
|
+
expect_coercion('F', :bool, false)
|
132
|
+
expect_coercion('false', :bool, false)
|
133
|
+
expect_coercion('False', :bool, false)
|
134
|
+
expect_coercion('FALSE', :bool, false)
|
135
|
+
expect_coercion('NO', :bool, false)
|
136
|
+
expect_coercion('no', :bool, false)
|
137
|
+
expect_coercion('N', :bool, false)
|
138
|
+
expect_coercion('n', :bool, false)
|
139
|
+
expect_coercion('0', :bool, false)
|
140
|
+
expect_coercion(0, :bool, false)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe 'in:' do
|
146
|
+
it 'works' do
|
147
|
+
expect_valid do
|
148
|
+
p :some_number, in: [1, 2]
|
149
|
+
end
|
150
|
+
|
151
|
+
expect_invalid do
|
152
|
+
p :some_number, in: [1, 3]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'works with modifiers' do
|
157
|
+
expect_valid do
|
158
|
+
p [:some_number, :some_string], one_in: [2]
|
159
|
+
p [:some_number, :some_string], one_in: ['Robert']
|
160
|
+
p [:some_number, :some_string], none_in: ['Steve', 3]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe 'pattern:' do
|
166
|
+
it 'works' do
|
167
|
+
expect_valid do
|
168
|
+
p :some_string, pattern: /^Rob/
|
169
|
+
p :some_string, pattern: /^Robert$/
|
170
|
+
end
|
171
|
+
expect_invalid do
|
172
|
+
p :some_string, pattern: /^Steve$/
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe 'items:' do
|
178
|
+
it 'works when given a Lambda' do
|
179
|
+
expect_valid do
|
180
|
+
p :array, items: ->(*args) {
|
181
|
+
p :a, in: [2, 3]
|
182
|
+
nil
|
183
|
+
}
|
184
|
+
end
|
185
|
+
expect_invalid do
|
186
|
+
p :array, items: ->(*args) {
|
187
|
+
p :a, in: [5]
|
188
|
+
}
|
189
|
+
end
|
190
|
+
expect_invalid do
|
191
|
+
p :array, items: ->(*args) {
|
192
|
+
'bob'
|
193
|
+
}
|
194
|
+
end
|
195
|
+
end
|
196
|
+
it 'works when given a Hash' do
|
197
|
+
# TODO
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'supports a custom validator block' do
|
202
|
+
expect_valid do
|
203
|
+
p :some_string do |v|
|
204
|
+
nil
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
expect_invalid do
|
209
|
+
p :some_string do |v|
|
210
|
+
'Bad Length'
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
describe 'modifiers' do
|
216
|
+
let!(:value) do
|
217
|
+
{
|
218
|
+
a: 1,
|
219
|
+
b: 1,
|
220
|
+
c: 1,
|
221
|
+
x: nil,
|
222
|
+
y: nil,
|
223
|
+
z: nil,
|
224
|
+
}
|
225
|
+
end
|
226
|
+
|
227
|
+
def assert_modifier(modifier, keys, exp)
|
228
|
+
blk = ->(*args) {
|
229
|
+
p keys, :"#{modifier}_present"
|
230
|
+
}
|
231
|
+
exp ? expect_valid(&blk) : expect_invalid(&blk)
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'the all modifier works as expected' do
|
235
|
+
assert_modifier(:all, %i[a b c], true)
|
236
|
+
assert_modifier(:all, %i[a b z], false)
|
237
|
+
assert_modifier(:all, %i[a y z], false)
|
238
|
+
assert_modifier(:all, %i[x y z], false)
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'the onem modifier works as expected' do
|
242
|
+
assert_modifier(:onem, %i[a b c], false)
|
243
|
+
assert_modifier(:onem, %i[a b z], false)
|
244
|
+
assert_modifier(:onem, %i[a y z], true)
|
245
|
+
assert_modifier(:onem, %i[x y z], true)
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'the onep modifier works as expected' do
|
249
|
+
assert_modifier(:onep, %i[a b c], true)
|
250
|
+
assert_modifier(:onep, %i[a b z], true)
|
251
|
+
assert_modifier(:onep, %i[a y z], true)
|
252
|
+
assert_modifier(:onep, %i[x y z], false)
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'the one modifier works as expected' do
|
256
|
+
assert_modifier(:one, %i[a b c], false)
|
257
|
+
assert_modifier(:one, %i[a b z], false)
|
258
|
+
assert_modifier(:one, %i[a y z], true)
|
259
|
+
assert_modifier(:one, %i[x y z], false)
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'the none modifier works as expected' do
|
263
|
+
assert_modifier(:none, %i[a b c], false)
|
264
|
+
assert_modifier(:none, %i[a b z], false)
|
265
|
+
assert_modifier(:none, %i[a y z], false)
|
266
|
+
assert_modifier(:none, %i[x y z], true)
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'aliases work' do
|
270
|
+
assert_modifier(:any, %i[a b c], true)
|
271
|
+
assert_modifier(:any, %i[a b z], true)
|
272
|
+
assert_modifier(:any, %i[a y z], true)
|
273
|
+
assert_modifier(:any, %i[x y z], false)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
describe 'nesting' do
|
278
|
+
it 'works' do
|
279
|
+
expect_valid do
|
280
|
+
p :hash, :present do
|
281
|
+
p :nested_hash do
|
282
|
+
p :value, in: [1]
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
expect_invalid do
|
287
|
+
p :hash, :present do
|
288
|
+
p :nested_hash do
|
289
|
+
p :value, not_in: [1]
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'yaml'
|
5
|
+
require 'database_cleaner'
|
6
|
+
require 'with_model'
|
7
|
+
|
8
|
+
require 'miscellany'
|
9
|
+
|
10
|
+
FileUtils.makedirs('log')
|
11
|
+
|
12
|
+
ActiveRecord::Base.logger = Logger.new('log/test.log')
|
13
|
+
ActiveRecord::Base.logger.level = Logger::DEBUG
|
14
|
+
ActiveRecord::Migration.verbose = false
|
15
|
+
|
16
|
+
db_adapter = ENV.fetch('ADAPTER', 'sqlite3')
|
17
|
+
db_config = YAML.safe_load(File.read('spec/db/database.yml'))
|
18
|
+
ActiveRecord::Base.establish_connection(db_config[db_adapter])
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
config.extend WithModel
|
22
|
+
|
23
|
+
# config.order = 'random'
|
24
|
+
|
25
|
+
config.before(:suite) do
|
26
|
+
DatabaseCleaner.clean_with(:truncation)
|
27
|
+
end
|
28
|
+
|
29
|
+
config.before do
|
30
|
+
DatabaseCleaner.strategy = :transaction
|
31
|
+
end
|
32
|
+
|
33
|
+
config.before do
|
34
|
+
DatabaseCleaner.start
|
35
|
+
end
|
36
|
+
|
37
|
+
config.after do
|
38
|
+
DatabaseCleaner.clean
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
puts "Testing with ActiveRecord #{ActiveRecord::VERSION::STRING}"
|
metadata
CHANGED
@@ -1,157 +1,37 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: miscellany
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ethan Knapp
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.15'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.15'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '10.0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '10.0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rspec
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '3.0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '3.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rspec-rails
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: pg
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: factory
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: factory_bot
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - ">="
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - ">="
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: timecop
|
14
|
+
name: rails
|
113
15
|
requirement: !ruby/object:Gem::Requirement
|
114
16
|
requirements:
|
115
17
|
- - ">="
|
116
18
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
118
|
-
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - ">="
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: webmock
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
19
|
+
version: '5'
|
20
|
+
- - "<"
|
130
21
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
132
|
-
type: :
|
22
|
+
version: '6.3'
|
23
|
+
type: :runtime
|
133
24
|
prerelease: false
|
134
25
|
version_requirements: !ruby/object:Gem::Requirement
|
135
26
|
requirements:
|
136
27
|
- - ">="
|
137
28
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
139
|
-
-
|
140
|
-
name: sinatra
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - ">="
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
type: :development
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - ">="
|
29
|
+
version: '5'
|
30
|
+
- - "<"
|
151
31
|
- !ruby/object:Gem::Version
|
152
|
-
version: '
|
32
|
+
version: '6.3'
|
153
33
|
- !ruby/object:Gem::Dependency
|
154
|
-
name:
|
34
|
+
name: rake
|
155
35
|
requirement: !ruby/object:Gem::Requirement
|
156
36
|
requirements:
|
157
37
|
- - ">="
|
@@ -165,49 +45,49 @@ dependencies:
|
|
165
45
|
- !ruby/object:Gem::Version
|
166
46
|
version: '0'
|
167
47
|
- !ruby/object:Gem::Dependency
|
168
|
-
name:
|
48
|
+
name: database_cleaner
|
169
49
|
requirement: !ruby/object:Gem::Requirement
|
170
50
|
requirements:
|
171
51
|
- - ">="
|
172
52
|
- !ruby/object:Gem::Version
|
173
|
-
version: '
|
53
|
+
version: '1.2'
|
174
54
|
type: :development
|
175
55
|
prerelease: false
|
176
56
|
version_requirements: !ruby/object:Gem::Requirement
|
177
57
|
requirements:
|
178
58
|
- - ">="
|
179
59
|
- !ruby/object:Gem::Version
|
180
|
-
version: '
|
60
|
+
version: '1.2'
|
181
61
|
- !ruby/object:Gem::Dependency
|
182
|
-
name:
|
62
|
+
name: rspec
|
183
63
|
requirement: !ruby/object:Gem::Requirement
|
184
64
|
requirements:
|
185
|
-
- - "
|
65
|
+
- - "~>"
|
186
66
|
- !ruby/object:Gem::Version
|
187
|
-
version: '
|
67
|
+
version: '3'
|
188
68
|
type: :development
|
189
69
|
prerelease: false
|
190
70
|
version_requirements: !ruby/object:Gem::Requirement
|
191
71
|
requirements:
|
192
|
-
- - "
|
72
|
+
- - "~>"
|
193
73
|
- !ruby/object:Gem::Version
|
194
|
-
version: '
|
74
|
+
version: '3'
|
195
75
|
- !ruby/object:Gem::Dependency
|
196
|
-
name:
|
76
|
+
name: sqlite3
|
197
77
|
requirement: !ruby/object:Gem::Requirement
|
198
78
|
requirements:
|
199
|
-
- - "
|
79
|
+
- - "~>"
|
200
80
|
- !ruby/object:Gem::Version
|
201
|
-
version: '
|
81
|
+
version: '1.3'
|
202
82
|
type: :development
|
203
83
|
prerelease: false
|
204
84
|
version_requirements: !ruby/object:Gem::Requirement
|
205
85
|
requirements:
|
206
|
-
- - "
|
86
|
+
- - "~>"
|
207
87
|
- !ruby/object:Gem::Version
|
208
|
-
version: '
|
88
|
+
version: '1.3'
|
209
89
|
- !ruby/object:Gem::Dependency
|
210
|
-
name:
|
90
|
+
name: with_model
|
211
91
|
requirement: !ruby/object:Gem::Requirement
|
212
92
|
requirements:
|
213
93
|
- - ">="
|
@@ -220,34 +100,6 @@ dependencies:
|
|
220
100
|
- - ">="
|
221
101
|
- !ruby/object:Gem::Version
|
222
102
|
version: '0'
|
223
|
-
- !ruby/object:Gem::Dependency
|
224
|
-
name: rails
|
225
|
-
requirement: !ruby/object:Gem::Requirement
|
226
|
-
requirements:
|
227
|
-
- - ">="
|
228
|
-
- !ruby/object:Gem::Version
|
229
|
-
version: '5'
|
230
|
-
type: :runtime
|
231
|
-
prerelease: false
|
232
|
-
version_requirements: !ruby/object:Gem::Requirement
|
233
|
-
requirements:
|
234
|
-
- - ">="
|
235
|
-
- !ruby/object:Gem::Version
|
236
|
-
version: '5'
|
237
|
-
- !ruby/object:Gem::Dependency
|
238
|
-
name: activerecord-import
|
239
|
-
requirement: !ruby/object:Gem::Requirement
|
240
|
-
requirements:
|
241
|
-
- - ">="
|
242
|
-
- !ruby/object:Gem::Version
|
243
|
-
version: '0'
|
244
|
-
type: :runtime
|
245
|
-
prerelease: false
|
246
|
-
version_requirements: !ruby/object:Gem::Requirement
|
247
|
-
requirements:
|
248
|
-
- - ">="
|
249
|
-
- !ruby/object:Gem::Version
|
250
|
-
version: '0'
|
251
103
|
description:
|
252
104
|
email:
|
253
105
|
- eknapp@instructure.com
|
@@ -256,14 +108,12 @@ extensions: []
|
|
256
108
|
extra_rdoc_files: []
|
257
109
|
files:
|
258
110
|
- README.md
|
259
|
-
- app/views/miscellany/spa_page.html.erb
|
260
|
-
- config/initializers/01_custom_preloaders.rb
|
261
|
-
- config/initializers/arbitrary_prefetch.rb
|
262
111
|
- config/initializers/cancancan.rb
|
263
112
|
- lib/miscellany.rb
|
264
113
|
- lib/miscellany/active_record/arbitrary_prefetch.rb
|
265
114
|
- lib/miscellany/active_record/batch_matcher.rb
|
266
115
|
- lib/miscellany/active_record/batched_destruction.rb
|
116
|
+
- lib/miscellany/active_record/computed_columns.rb
|
267
117
|
- lib/miscellany/active_record/custom_preloaders.rb
|
268
118
|
- lib/miscellany/batch_processor.rb
|
269
119
|
- lib/miscellany/batching_csv_processor.rb
|
@@ -273,6 +123,11 @@ files:
|
|
273
123
|
- lib/miscellany/param_validator.rb
|
274
124
|
- lib/miscellany/version.rb
|
275
125
|
- miscellany.gemspec
|
126
|
+
- spec/db/database.yml
|
127
|
+
- spec/miscellany/arbitrary_prefetch_spec.rb
|
128
|
+
- spec/miscellany/computed_columns_spec.rb
|
129
|
+
- spec/miscellany/param_validator_spec.rb
|
130
|
+
- spec/spec_helper.rb
|
276
131
|
homepage: https://instructure.com
|
277
132
|
licenses: []
|
278
133
|
metadata: {}
|
@@ -295,4 +150,9 @@ rubygems_version: 3.0.3
|
|
295
150
|
signing_key:
|
296
151
|
specification_version: 4
|
297
152
|
summary: Gem for a bunch of random, re-usable Rails Concerns & Helpers
|
298
|
-
test_files:
|
153
|
+
test_files:
|
154
|
+
- spec/db/database.yml
|
155
|
+
- spec/miscellany/arbitrary_prefetch_spec.rb
|
156
|
+
- spec/miscellany/param_validator_spec.rb
|
157
|
+
- spec/miscellany/computed_columns_spec.rb
|
158
|
+
- spec/spec_helper.rb
|
@@ -1 +0,0 @@
|
|
1
|
-
Miscellany::ArbitraryPrefetch.install
|