miscellany 0.1.3 → 0.1.4
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 +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
|