advanced_ar 0.1.4 → 0.1.5
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/advanced_ar.rb +1 -1
- data/lib/advanced_ar/arbitrary_prefetch.rb +111 -109
- data/lib/advanced_ar/batched_destruction.rb +68 -66
- data/lib/advanced_ar/custom_preloaders.rb +20 -18
- data/lib/advanced_ar/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4635f5a6d31cb307162adc165155a6ca6dae23c37d9ad406b12fe5d6b3192b5e
|
4
|
+
data.tar.gz: f4693daf2c847e2ce6743d2afcf1fa11716c45682dd580346c580d30b88047d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44d3d78df56df26cb0e8273cc1bedfa66a711bca449db6078d56811382d2ef0b0ccfd78fb686a81c234ff4a183c0cbf8b7c6522de1f640cd6882794572b8c903
|
7
|
+
data.tar.gz: a57ec87822bc24078b85df731ad733a7612d0e24acd02c00a4f44891ff3ff1239b02df953ab88b82de8c2a54427fa18bdf3ac0b5b7c5613500e4553f14759aff
|
data/lib/advanced_ar.rb
CHANGED
@@ -14,147 +14,149 @@
|
|
14
14
|
# 2. Duplicates the relevant snippets from Goldiloader into this module. See Goldiloader::AutoIncludeContext
|
15
15
|
# The current Goldiloader implementation uses Option 1 internally, but also makes the relations lazy - even
|
16
16
|
# if you define a prefetch, it won't actually be loaded until you attempt to access it on one of the models.
|
17
|
-
module AdvancedAR
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
17
|
+
module AdvancedAR
|
18
|
+
module ArbitraryPrefetch
|
19
|
+
class PrefetcherContext
|
20
|
+
attr_accessor :model, :target_attribute
|
21
|
+
attr_reader :options
|
22
|
+
|
23
|
+
def initialize(model, opts)
|
24
|
+
@options = opts
|
25
|
+
@model = model
|
26
|
+
@source_key = opts[:relation]
|
27
|
+
@target_attribute = opts[:attribute]
|
28
|
+
@queryset = opts[:queryset]
|
29
|
+
@models = []
|
30
|
+
end
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
def link_models(models)
|
33
|
+
Array(models).each do |m|
|
34
|
+
@models << m
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
# assoc = PrefetchAssociation.new(m, self, reflection)
|
37
|
+
assoc = reflection.association_class.new(m, reflection)
|
38
|
+
m.send(:association_instance_set, target_attribute, assoc)
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
m.instance_eval <<-CODE, __FILE__, __LINE__ + 1
|
41
|
+
def #{target_attribute}
|
42
|
+
association(:#{target_attribute}).reader
|
43
|
+
end
|
44
|
+
CODE
|
45
|
+
end
|
44
46
|
end
|
45
|
-
end
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
48
|
+
def reflection
|
49
|
+
@reflection ||= begin
|
50
|
+
queryset = @queryset
|
51
|
+
source_refl = model.reflections[@source_key.to_s]
|
52
|
+
scope = lambda { |*_args|
|
53
|
+
qs = queryset
|
54
|
+
qs = qs.merge(source_refl.scope_for(model.unscoped)) if source_refl.scope
|
55
|
+
qs
|
56
|
+
}
|
57
|
+
ActiveRecord::Reflection.create(
|
58
|
+
options[:type],
|
59
|
+
@target_attribute,
|
60
|
+
scope,
|
61
|
+
source_refl.options.merge(
|
62
|
+
class_name: source_refl.class_name,
|
63
|
+
inverse_of: nil
|
64
|
+
),
|
65
|
+
model
|
66
|
+
)
|
67
|
+
end
|
66
68
|
end
|
67
69
|
end
|
68
|
-
end
|
69
70
|
|
70
|
-
|
71
|
-
|
71
|
+
module ActiveRecordBasePatch
|
72
|
+
extend ActiveSupport::Concern
|
72
73
|
|
73
|
-
|
74
|
-
|
75
|
-
|
74
|
+
included do
|
75
|
+
class << self
|
76
|
+
delegate :prefetch, to: :all
|
77
|
+
end
|
76
78
|
end
|
77
79
|
end
|
78
|
-
end
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
|
81
|
+
module ActiveRecordRelationPatch
|
82
|
+
def exec_queries
|
83
|
+
return super if loaded?
|
83
84
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
85
|
+
records = super
|
86
|
+
preloader = nil
|
87
|
+
(@values[:prefetches] || {}).each do |_key, opts|
|
88
|
+
pfc = PrefetcherContext.new(model, opts)
|
89
|
+
pfc.link_models(records)
|
89
90
|
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
unless defined?(Goldiloader)
|
92
|
+
preloader ||= build_preloader
|
93
|
+
preloader.preload(records, opts[:attribute])
|
94
|
+
end
|
93
95
|
end
|
96
|
+
records
|
94
97
|
end
|
95
|
-
records
|
96
|
-
end
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
|
99
|
+
def prefetch(**kwargs)
|
100
|
+
spawn.add_prefetches!(kwargs)
|
101
|
+
end
|
101
102
|
|
102
|
-
|
103
|
-
|
103
|
+
def add_prefetches!(kwargs)
|
104
|
+
return unless kwargs.present?
|
104
105
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
106
|
+
assert_mutability!
|
107
|
+
@values[:prefetches] ||= {}
|
108
|
+
kwargs.each do |attr, opts|
|
109
|
+
@values[:prefetches][attr] = normalize_options(attr, opts)
|
110
|
+
end
|
111
|
+
self
|
109
112
|
end
|
110
|
-
self
|
111
|
-
end
|
112
113
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
114
|
+
def normalize_options(attr, opts)
|
115
|
+
norm = if opts.is_a?(Array)
|
116
|
+
{ relation: opts[0], queryset: opts[1] }
|
117
|
+
elsif opts.is_a?(ActiveRecord::Relation)
|
118
|
+
rel_name = opts.model.name.underscore
|
119
|
+
rel = (model.reflections[rel_name] || model.reflections[rel_name.pluralize])&.name
|
120
|
+
{ relation: rel, queryset: opts }
|
121
|
+
else
|
122
|
+
opts
|
123
|
+
end
|
123
124
|
|
124
|
-
|
125
|
-
|
125
|
+
norm[:attribute] = attr
|
126
|
+
norm[:type] ||= (attr.to_s.pluralize == attr.to_s) ? :has_many : :has_one
|
126
127
|
|
127
|
-
|
128
|
+
norm
|
129
|
+
end
|
128
130
|
end
|
129
|
-
end
|
130
131
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
132
|
+
module ActiveRecordMergerPatch
|
133
|
+
def merge
|
134
|
+
super.tap do
|
135
|
+
merge_prefetches
|
136
|
+
end
|
135
137
|
end
|
136
|
-
end
|
137
138
|
|
138
|
-
|
139
|
+
private
|
139
140
|
|
140
|
-
|
141
|
-
|
141
|
+
def merge_prefetches
|
142
|
+
relation.add_prefetches!(other.values[:prefetches])
|
143
|
+
end
|
142
144
|
end
|
143
|
-
end
|
144
145
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
146
|
+
def self.install
|
147
|
+
::ActiveRecord::Base.include(ActiveRecordBasePatch)
|
148
|
+
::ActiveRecord::Relation.prepend(ActiveRecordRelationPatch)
|
149
|
+
::ActiveRecord::Relation::Merger.prepend(ActiveRecordMergerPatch)
|
149
150
|
|
150
|
-
|
151
|
+
return unless defined? ::Goldiloader
|
151
152
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
153
|
+
::Goldiloader::AssociationLoader.module_eval do
|
154
|
+
def self.has_association?(model, association_name) # rubocop:disable Naming/PredicateName
|
155
|
+
model.association(association_name)
|
156
|
+
true
|
157
|
+
rescue ::ActiveRecord::AssociationNotFoundError => _err
|
158
|
+
false
|
159
|
+
end
|
158
160
|
end
|
159
161
|
end
|
160
162
|
end
|
@@ -1,94 +1,96 @@
|
|
1
|
-
module AdvancedAR
|
2
|
-
|
1
|
+
module AdvancedAR
|
2
|
+
module BatchedDestruction
|
3
|
+
extend ActiveSupport::Concern
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
5
|
+
included do
|
6
|
+
define_model_callbacks :bulk_destroy
|
7
|
+
define_model_callbacks :destroy_batch
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
before_destroy_batch do
|
10
|
+
# TODO Delete Dependant Relations
|
11
|
+
model_class.reflections.each do |name, reflection|
|
12
|
+
options = reflection.options
|
13
|
+
end
|
12
14
|
end
|
13
15
|
end
|
14
|
-
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
class_methods do
|
18
|
+
def bulk_destroy(**kwargs)
|
19
|
+
return to_sql
|
20
|
+
bulk_destroy_internal(self, **kwargs)
|
21
|
+
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
23
|
+
# Hook for performing the actual deletion of items, may be used to facilitate soft-deletion.
|
24
|
+
# Must not call destroy().
|
25
|
+
# Default implementation is to delete the batch using delete_all(id: batch_ids).
|
26
|
+
def destroy_bulk_batch(batch, options)
|
27
|
+
delete_ids = batch.map(&:id)
|
28
|
+
where(id: delete_ids).delete_all()
|
29
|
+
end
|
29
30
|
|
30
|
-
|
31
|
+
private
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
33
|
+
def bulk_destroy_internal(items, **kwargs)
|
34
|
+
options = {}
|
35
|
+
options.merge!(kwargs)
|
36
|
+
ClassCallbackExector.run_callbacks(model_class, :bulk_destroy, options: options) do
|
37
|
+
if items.respond_to?(:find_in_batches)
|
38
|
+
items.find_in_batches do |batch|
|
39
|
+
_destroy_batch(batch, options)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
_destroy_batch(items, options)
|
39
43
|
end
|
40
|
-
else
|
41
|
-
_destroy_batch(items, options)
|
42
44
|
end
|
43
45
|
end
|
44
|
-
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
def _destroy_batch(batch, options)
|
48
|
+
ClassCallbackExector.run_callbacks(model_class, :destroy_batch, {
|
49
|
+
model_class: model_class,
|
50
|
+
batch: batch,
|
51
|
+
}) do
|
52
|
+
model_class.destroy_bulk_batch(batch, options)
|
53
|
+
end
|
52
54
|
end
|
53
|
-
end
|
54
55
|
|
55
|
-
|
56
|
+
private
|
56
57
|
|
57
|
-
|
58
|
-
|
58
|
+
def model_class
|
59
|
+
try(:model) || self
|
60
|
+
end
|
59
61
|
end
|
60
|
-
end
|
61
62
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
63
|
+
def destroy(*args, legacy: false, **kwargs)
|
64
|
+
if legacy
|
65
|
+
super(*args)
|
66
|
+
else
|
67
|
+
self.class.send(:bulk_destroy_internal, [self], **kwargs)
|
68
|
+
end
|
67
69
|
end
|
68
|
-
end
|
69
70
|
|
70
|
-
|
71
|
+
private
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
# These classes are some Hackery to allow us to use callbacks against the Model classes instead of Model instances
|
74
|
+
class ClassCallbackExector
|
75
|
+
include ActiveSupport::Callbacks
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
attr_reader :callback_class
|
78
|
+
delegate :__callbacks, to: :callback_class
|
79
|
+
delegate_missing_to :callback_class
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
def initialize(cls, env)
|
82
|
+
@callback_class = cls
|
83
|
+
env.keys.each do |k|
|
84
|
+
define_singleton_method(k) do
|
85
|
+
env[k]
|
86
|
+
end
|
85
87
|
end
|
88
|
+
@options = options
|
86
89
|
end
|
87
|
-
@options = options
|
88
|
-
end
|
89
90
|
|
90
|
-
|
91
|
-
|
91
|
+
def self.run_callbacks(cls, callback, env={}, &blk)
|
92
|
+
new(cls, env).run_callbacks(callback, &blk)
|
93
|
+
end
|
92
94
|
end
|
93
95
|
end
|
94
96
|
end
|
@@ -29,29 +29,31 @@
|
|
29
29
|
# end
|
30
30
|
# end
|
31
31
|
#
|
32
|
-
module AdvancedAR
|
33
|
-
module
|
34
|
-
|
32
|
+
module AdvancedAR
|
33
|
+
module CustomPreloaders
|
34
|
+
module AssociationBuilderExtension
|
35
|
+
def self.build(model, reflection); end
|
35
36
|
|
36
|
-
|
37
|
-
|
37
|
+
def self.valid_options
|
38
|
+
[:preloader]
|
39
|
+
end
|
38
40
|
end
|
39
|
-
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
42
|
+
module PreloaderExtension
|
43
|
+
def preloader_for(reflection, owners)
|
44
|
+
cust_preloader = reflection.options[:preloader]
|
45
|
+
if cust_preloader.present?
|
46
|
+
cust_preloader = cust_preloader.constantize if cust_preloader.is_a?(String)
|
47
|
+
cust_preloader
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
49
51
|
end
|
50
52
|
end
|
51
|
-
end
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
def self.install
|
55
|
+
ActiveRecord::Associations::Builder::Association.extensions << AssociationBuilderExtension
|
56
|
+
ActiveRecord::Associations::Preloader.prepend(CustomPreloaders::PreloaderExtension)
|
57
|
+
end
|
56
58
|
end
|
57
59
|
end
|
data/lib/advanced_ar/version.rb
CHANGED