miscellany 0.1.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 296f82b755fbbff37b7b939e31d30d53c1e1ed3b528df93992460a5c5093e1bb
4
- data.tar.gz: f42d95c04a728a4b16e9b0286e900d243b18b1385649150fed2b1a0501e7098a
3
+ metadata.gz: 70a91bb987b7c99319cbe607cdfba6c32e9637a5a3fb8bec3ec38725339ea3c6
4
+ data.tar.gz: f9607f408e91b1abadb51e73c4238b1a1d9f378fe294461efe11f3172783d9e2
5
5
  SHA512:
6
- metadata.gz: 513b776e716e713413ee7f1c45cb2300c6142ffad6fcd3b550bfa06f5f496b630dea82fb99274853e2f825d47387648d015364d619de3b7572e0565b83160219
7
- data.tar.gz: 85540e4cca127328d52c1b68a51be2b2366e5bcf5dfef95464703ece61539ba68ed3e0689fd55e22d8c258bbb625efa486b2d5f0b32075bffcf43da866d4e110
6
+ metadata.gz: f8c1ba0f079635d38ed0d3a29d3cb865899fb66112593625a2ae159061c5fd48f63cfa2300a825f671bdbd04e4e2317d6ccedd80702391bdadd7329c0a2deae9
7
+ data.tar.gz: a73fd773078016b440b08243c4c6dc5477ff26cdbacb054f57d8b99b77c15305af7a1741d0c94b02399c3b59af7553254a23be5441d1d30948f4f51cb53b6ccf
@@ -4,18 +4,11 @@
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
9
+ ACTIVE_RECORD_VERSION = ::Gem::Version.new(::ActiveRecord::VERSION::STRING).release
10
+ PRE_RAILS_6_2 = ACTIVE_RECORD_VERSION < ::Gem::Version.new('6.2.0')
11
+
19
12
  class PrefetcherContext
20
13
  attr_accessor :model, :target_attribute
21
14
  attr_reader :options
@@ -49,7 +42,7 @@ module Miscellany
49
42
  @reflection ||= begin
50
43
  queryset = @queryset
51
44
  source_refl = model.reflections[@source_key.to_s]
52
- scope = lambda { |*_args|
45
+ scope = lambda {|*_args|
53
46
  qs = queryset
54
47
  qs = qs.merge(source_refl.scope_for(model.unscoped)) if source_refl.scope
55
48
  qs
@@ -83,14 +76,16 @@ module Miscellany
83
76
  return super if loaded?
84
77
 
85
78
  records = super
86
- preloader = nil
87
79
  (@values[:prefetches] || {}).each do |_key, opts|
88
80
  pfc = PrefetcherContext.new(model, opts)
89
81
  pfc.link_models(records)
90
82
 
91
83
  unless defined?(Goldiloader)
92
- preloader ||= build_preloader
93
- preloader.preload(records, opts[:attribute])
84
+ if PRE_RAILS_6_2
85
+ ::ActiveRecord::Associations::Preloader.new.preload(records, [opts[:attribute]])
86
+ else
87
+ ::ActiveRecord::Associations::Preloader.new(records: records, associations: [opts[:attribute]]).call
88
+ end
94
89
  end
95
90
  end
96
91
  records
@@ -106,12 +101,12 @@ module Miscellany
106
101
  assert_mutability!
107
102
  @values[:prefetches] ||= {}
108
103
  kwargs.each do |attr, opts|
109
- @values[:prefetches][attr] = normalize_options(attr, opts)
104
+ @values[:prefetches][attr] = normalize_prefetch_options(attr, opts)
110
105
  end
111
106
  self
112
107
  end
113
108
 
114
- def normalize_options(attr, opts)
109
+ def normalize_prefetch_options(attr, opts)
115
110
  norm = if opts.is_a?(Array)
116
111
  { relation: opts[0], queryset: opts[1] }
117
112
  elsif opts.is_a?(ActiveRecord::Relation)
@@ -143,15 +138,43 @@ module Miscellany
143
138
  end
144
139
  end
145
140
 
141
+ module ActiveRecordPreloaderPatch
142
+ if ACTIVE_RECORD_VERSION >= ::Gem::Version.new('6.0.0')
143
+ def grouped_records(association, records, polymorphic_parent)
144
+ h = {}
145
+ records.each do |record|
146
+ next unless record
147
+ reflection = record.class._reflect_on_association(association)
148
+ reflection ||= record.association(association)&.reflection rescue nil
149
+ next if polymorphic_parent && !reflection || !record.association(association).klass
150
+ (h[reflection] ||= []) << record
151
+ end
152
+ h
153
+ end
154
+ end
155
+ end
156
+
157
+ module ActiveRecordReflectionPatch
158
+ def check_preloadable!
159
+ return if scope && scope.arity < 0
160
+ super
161
+ end
162
+ end
163
+
146
164
  def self.install
147
165
  ::ActiveRecord::Base.include(ActiveRecordBasePatch)
166
+
148
167
  ::ActiveRecord::Relation.prepend(ActiveRecordRelationPatch)
149
168
  ::ActiveRecord::Relation::Merger.prepend(ActiveRecordMergerPatch)
150
169
 
170
+ ::ActiveRecord::Associations::Preloader.prepend(ActiveRecordPreloaderPatch)
171
+
172
+ ::ActiveRecord::Reflection::AssociationReflection.prepend(ActiveRecordReflectionPatch)
173
+
151
174
  return unless defined? ::Goldiloader
152
175
 
153
176
  ::Goldiloader::AssociationLoader.module_eval do
154
- def self.has_association?(model, association_name) # rubocop:disable Naming/PredicateName
177
+ def self.has_association?(model, association_name)
155
178
  model.association(association_name)
156
179
  true
157
180
  rescue ::ActiveRecord::AssociationNotFoundError => _err
@@ -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
@@ -1,3 +1,5 @@
1
+ require_relative './http_error_handling'
2
+
1
3
  module Miscellany
2
4
  module SlicedResponse
3
5
  extend ActiveSupport::Concern
@@ -84,7 +86,7 @@ module Miscellany
84
86
  def normalize_sort(sort, key: nil)
85
87
  sort = sort.to_s if sort.is_a?(Symbol)
86
88
  if sort.is_a?(String)
87
- m = sort.match(/(\w+)(?: (ASC|DESC)(!?))?/)
89
+ m = sort.match(/^([\w\.]+)(?: (ASC|DESC)(!?))?$/)
88
90
  sort = { column: m[1], order: m[2], force_order: m[3].present? }.compact
89
91
  elsif sort.is_a?(Proc)
90
92
  sort = { column: sort }
@@ -213,12 +215,20 @@ module Miscellany
213
215
 
214
216
  def rendered_items
215
217
  ritems = sliced_items
216
- ritems = ritems.map(&options[:item_transformer]) if options[:item_transformer]
218
+ ritems = ritems.to_a.map(&options[:item_transformer]) if options[:item_transformer]
217
219
  ritems
218
220
  end
219
221
 
220
222
  def total_item_count
221
- @total_item_count ||= options[:total_count] || (items.respond_to?(:count) && items.count) || nil
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
222
232
  end
223
233
 
224
234
  def sliced_items
@@ -1,6 +1,7 @@
1
1
  module Miscellany
2
2
  class ParamValidator
3
- attr_accessor :context, :options, :errors
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, options = nil)
25
+ def initialize(block, context, parameters = nil)
25
26
  @block = block
26
27
  @context = context
27
28
  @params = parameters || context.params
28
- @subkeys = []
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, @subkeys[-1]])
52
- instance_exec(*args, &blk)
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
- iterate_array = false # TODO
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
- # Nested check
116
+ # Array Items check
123
117
  run_check[:items] do |blk|
124
- sub_parameter(pk) do
125
- if params.is_a?(Array)
126
- params.each_with_index do |v, i|
127
- sub_parameter(i) { apply_checks(&blk) }
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[field] = merge_error_hashes(final_errors[field], errs)
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[field] = merge_error_hashes(final_errors[field], "must NOT be #{check}") unless errs.present?
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 = merge_error_hashes(final_errors, "#{string_prefixes[check_prefix]} #{field_key} #{check}")
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 = merge_error_hashes(@errors, final_errors)
169
- final_errors
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
- initial_errors = @errors
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 = yield(*args)
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
- state[check] = merge_error_hashes(state[check], @errors)
217
- @errors = initial_errors
230
+ store.push(result)
231
+
232
+ encountered_errors = true
233
+ end
218
234
  end
219
235
 
220
- !state[check].present?
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
- if (param.is_a?(Array) && type != Array) || ((param.is_a?(Hash) || param.is_a?(ActionController::Parameters)) && type != Hash)
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 (param.is_a?(ActionController::Parameters) && type == Hash rescue false)
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.starts_with?("#{spfx}_")
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
@@ -1,3 +1,3 @@
1
1
  module Miscellany
2
- VERSION = "0.1.0".freeze
2
+ VERSION = "0.1.4".freeze
3
3
  end
data/lib/miscellany.rb CHANGED
@@ -1,6 +1,18 @@
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
5
7
 
8
+ if defined?(Rails)
9
+ class Engine < ::Rails::Engine
10
+ end
11
+ end
12
+
13
+ ActiveSupport.on_load(:active_record) do
14
+ Miscellany::CustomPreloaders.install
15
+ Miscellany::ArbitraryPrefetch.install
16
+ Miscellany::ComputedColumns.install
17
+ end
6
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.add_development_dependency "bundler", "~> 1.15"
26
- spec.add_development_dependency "rake", "~> 10.0"
27
- spec.add_development_dependency "rspec", "~> 3.0"
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.add_dependency "rails", ">= 5"
42
- spec.add_dependency "activerecord-import"
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,3 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: ":memory:"