activesupport-minimizer 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +1 -1
- data/lib/as_minimizer/census.rb +6 -4
- data/lib/as_minimizer/check.rb +147 -0
- data/lib/as_minimizer/unroller.rb +50 -32
- data/lib/as_minimizer/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: b015df981e76956005ec4dbec6f10808c1fe61a2be2d17395be4bc622ae0541d
|
|
4
|
+
data.tar.gz: c569ebbecfa90d6b135966578016eb725e9b705e8bb3896443a87fa096c9c340
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a65151b39365a9eaeefb4bf743edfd103e20f0c9e3cdb9efb38384ed61a4b6f771263df98a8c15e766d00072686fd25fea6abf7187f62b70bce6ec42db751112
|
|
7
|
+
data.tar.gz: c9f8740139908120958cec43192c1b241cdb7df945aa2bf01851dc0c05060bd67e1ff8f8b32170c56fcabbe8f5e3b872f01bcbe1c07fe402f00df6acb9de35a3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1 (2026-10-18)
|
|
4
|
+
|
|
5
|
+
- Unrolled callbacks: one method per class and chain instead of one per controller action (`only:`/`except:` become a Set lookup), and classes whose chains are the same share one method body. Fewer methods for the JIT to compile.
|
|
6
|
+
- `Check.long_chains`: chains with more than a limit (default 5) of the app's own callbacks. Callbacks inherited from excluded classes (default `ApplicationController`) don't count; a base class over the limit is reported once, with the classes that inherit it.
|
|
7
|
+
- `Check.callback_loops`: model callbacks that call `save`, `update`, `touch`, ... on their own record, which runs the callbacks again (found with Prism), with the guard if there is one.
|
|
8
|
+
- Census counts per class, not per action.
|
|
9
|
+
|
|
3
10
|
## 0.1.0 (2026-10-18)
|
|
4
11
|
|
|
5
12
|
First release.
|
data/README.md
CHANGED
|
@@ -77,4 +77,4 @@ validations, 2 before_save, 2 after_save, after_initialize):
|
|
|
77
77
|
A controller action behind 9 callbacks: 12.6 µs of callback overhead → 4.0 µs
|
|
78
78
|
(6.4 → 2.3 µs with YJIT).
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
[Japan Voyage](https://japanvoyage.jp)'s test suite (4317 runs) gives the same results with it on.
|
data/lib/as_minimizer/census.rb
CHANGED
|
@@ -28,12 +28,14 @@ module AsMinimizer
|
|
|
28
28
|
klass.__callbacks.each do |kind, chain|
|
|
29
29
|
next if chain.empty?
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
# Controllers no route reaches (abstract ones included) never run their chain.
|
|
32
|
+
next if kind == :process_action && klass.respond_to?(:action_methods) && controller_actions(klass, routed).empty?
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
Unroller::Generator.new(klass, kind, chain).source
|
|
34
36
|
unrolled += 1
|
|
35
37
|
rescue Unroller::Unsupported => e
|
|
36
|
-
fallbacks[e.message] << "#{name}##{kind}
|
|
38
|
+
fallbacks[e.message] << "#{name}##{kind}"
|
|
37
39
|
end
|
|
38
40
|
end
|
|
39
41
|
end
|
data/lib/as_minimizer/check.rb
CHANGED
|
@@ -14,6 +14,33 @@ module AsMinimizer
|
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# A chain with more of the app's own callbacks than the limit.
|
|
18
|
+
LongChain = Data.define(:klass, :chain, :callbacks, :limit, :action, :also) do
|
|
19
|
+
def to_s
|
|
20
|
+
where = action ? " (#{action})" : ""
|
|
21
|
+
others = also.empty? ? "" : " (inherited by #{also.size}: #{also.first(3).map { Check.name_of(it) }.join(", ")}#{", ..." if also.size > 3})"
|
|
22
|
+
"#{Check.name_of(klass)}#{others}: #{chain} chain has #{callbacks.size} callbacks#{where}, more than #{limit}\n" +
|
|
23
|
+
callbacks.map { " #{it}" }.join("\n")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# A model callback that saves (updates, touches, ...) the record it runs
|
|
28
|
+
# for: that runs the chain again, so it repeats until something stops it.
|
|
29
|
+
Loop = Data.define(:klass, :chain, :callback, :call, :location, :guard) do
|
|
30
|
+
def to_s
|
|
31
|
+
"#{Check.name_of(klass)}: #{callback} calls #{call} on the record itself (#{location}), " \
|
|
32
|
+
"which runs the #{chain} callbacks again#{"; guarded by #{guard}, check it can't stay true" if guard}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Chains whose callbacks are validations, not callbacks.
|
|
37
|
+
SKIP_CHAINS = %i[validate].freeze
|
|
38
|
+
# Model chains a save, update or touch of the same record runs again.
|
|
39
|
+
REENTERED_CHAINS = %i[validation save create update commit touch].freeze
|
|
40
|
+
# Calls on the record itself that run its callbacks (update_column(s) and
|
|
41
|
+
# update_all don't).
|
|
42
|
+
SELF_SAVES = %i[save save! update update! update_attribute update_attributes update_attributes! touch increment! decrement! toggle!].freeze
|
|
43
|
+
|
|
17
44
|
# method_missing implementations whose answers are known: attribute methods.
|
|
18
45
|
ATTRIBUTE_METHOD_MISSING = %w[ActiveModel::AttributeMethods ActiveRecord::AttributeMethods ArMinimizer::Slim::Record].freeze
|
|
19
46
|
PLAIN_METHOD_MISSING = %w[BasicObject Kernel].freeze
|
|
@@ -55,6 +82,67 @@ module AsMinimizer
|
|
|
55
82
|
end
|
|
56
83
|
end
|
|
57
84
|
|
|
85
|
+
# Chains with more than +limit+ of the app's own callbacks (defined under
|
|
86
|
+
# +root+, not in gems). Controllers: their action with the most.
|
|
87
|
+
# Callbacks a subclass inherits from an +exclude+d class (the
|
|
88
|
+
# ApplicationController default: time zone, locale, current user, ...,
|
|
89
|
+
# the per-request basics) don't count. A class over the limit is reported
|
|
90
|
+
# once, with how many classes inherit its chain; those aren't repeated.
|
|
91
|
+
def long_chains(classes = callback_classes, limit: 5, root:, routed: nil, exclude: ["ApplicationController"])
|
|
92
|
+
@routed = routed
|
|
93
|
+
excluded = exclude.filter_map { Object.const_get(it) if Object.const_defined?(it) }
|
|
94
|
+
reported = {} # [class, chain] => LongChain
|
|
95
|
+
classes.sort_by { it.ancestors.size }.each do |klass|
|
|
96
|
+
next if excluded.include?(klass)
|
|
97
|
+
|
|
98
|
+
klass.__callbacks.each do |chain_name, chain|
|
|
99
|
+
next if SKIP_CHAINS.include?(chain_name)
|
|
100
|
+
next if chain.none?
|
|
101
|
+
|
|
102
|
+
# Already over the limit in a superclass: covered by that report.
|
|
103
|
+
parent = klass.ancestors.drop(1).find { reported.key?([it, chain_name]) }
|
|
104
|
+
if parent
|
|
105
|
+
reported[[parent, chain_name]].also << klass
|
|
106
|
+
next
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
inherited = excluded.select { klass < it && it.respond_to?(:__callbacks) }
|
|
110
|
+
.flat_map { it.__callbacks[chain_name].to_a }.to_set
|
|
111
|
+
actions = controller_actions(klass, chain_name)
|
|
112
|
+
candidates = actions.nil? || actions.empty? ? [nil] : actions # a base class: all of its callbacks
|
|
113
|
+
action, own = candidates.map do |act|
|
|
114
|
+
[act, chain.select { |cb| !inherited.include?(cb) && (act.nil? || applies?(cb, act)) && own_location(klass, cb, root) }]
|
|
115
|
+
end.max_by { it[1].size }
|
|
116
|
+
next unless own.size > limit
|
|
117
|
+
|
|
118
|
+
reported[[klass, chain_name]] = LongChain.new(klass, chain_name, own.map { describe(klass, it, root) }, limit, action, [])
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
reported.values
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Model callbacks (under +root+) that call save, update, touch, ... on
|
|
125
|
+
# the record they run for. allow: ["Trip#recalculate", ...] to accept one.
|
|
126
|
+
def callback_loops(classes = callback_classes, root:, allow: [])
|
|
127
|
+
classes.flat_map do |klass|
|
|
128
|
+
next [] unless defined?(ActiveRecord::Base) && klass < ActiveRecord::Base
|
|
129
|
+
|
|
130
|
+
klass.__callbacks.flat_map do |chain_name, chain|
|
|
131
|
+
next [] unless REENTERED_CHAINS.include?(chain_name)
|
|
132
|
+
|
|
133
|
+
chain.flat_map do |callback|
|
|
134
|
+
next [] unless (location = own_location(klass, callback, root))
|
|
135
|
+
next [] if allow.include?("#{name_of(klass)}##{callback.filter}")
|
|
136
|
+
|
|
137
|
+
self_saves(*location).map do |call, line|
|
|
138
|
+
Loop.new(klass, chain_name, "#{callback.kind}_#{chain_name} #{callback.filter.is_a?(Symbol) ? callback.filter.inspect : "block"}",
|
|
139
|
+
call, "#{relative(location[0], root)}:#{line}", guard(callback))
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end.uniq { [it.klass, it.callback, it.call, it.location] }
|
|
144
|
+
end
|
|
145
|
+
|
|
58
146
|
def callback_classes
|
|
59
147
|
ObjectSpace.each_object(Class).filter_map do |klass|
|
|
60
148
|
name = Module.instance_method(:name).bind_call(klass) # some classes redefine .name (and raise)
|
|
@@ -113,6 +201,65 @@ module AsMinimizer
|
|
|
113
201
|
|
|
114
202
|
def listed?(filter, action) = filter.instance_variable_get(:@actions).include?(action)
|
|
115
203
|
|
|
204
|
+
# [file, line] of the callback's code when it's the app's own (under root, not a gem), else nil.
|
|
205
|
+
def own_location(klass, callback, root)
|
|
206
|
+
filter = callback.filter
|
|
207
|
+
location =
|
|
208
|
+
case filter
|
|
209
|
+
when Symbol then (klass.instance_method(filter).source_location rescue nil)
|
|
210
|
+
when Proc then filter.source_location
|
|
211
|
+
end
|
|
212
|
+
return unless location && location[0].start_with?(root.to_s) && !location[0].match?(%r{/(vendor|gems)/})
|
|
213
|
+
|
|
214
|
+
location
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def describe(klass, callback, root)
|
|
218
|
+
file, line = own_location(klass, callback, root)
|
|
219
|
+
name = callback.filter.is_a?(Symbol) ? callback.filter.inspect : "block"
|
|
220
|
+
"#{callback.kind}_#{callback.name} #{name} (#{relative(file, root)}:#{line})"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def relative(file, root) = file.delete_prefix("#{root}/")
|
|
224
|
+
|
|
225
|
+
# The :if/:unless that might stop a loop, as written. Rails' own
|
|
226
|
+
# condition on model after_* callbacks (skip after a halt) isn't one.
|
|
227
|
+
def guard(callback)
|
|
228
|
+
conditions = (callback.instance_variable_get(:@if).map { ["if", it] } + callback.instance_variable_get(:@unless).map { ["unless", it] })
|
|
229
|
+
.reject { |_, c| c.is_a?(ActiveSupport::Callbacks::Conditionals::Value) }
|
|
230
|
+
conditions.filter_map { |kind, c| "#{kind}: #{c.inspect}" if c.is_a?(Symbol) }.first ||
|
|
231
|
+
(conditions.any? ? "a #{conditions.first[0]}: condition" : nil)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# [[call, line]] for calls in SELF_SAVES on self in the method or block
|
|
235
|
+
# starting at file:line.
|
|
236
|
+
def self_saves(file, line)
|
|
237
|
+
require "prism"
|
|
238
|
+
@parsed ||= {}
|
|
239
|
+
root_node = (@parsed[file] ||= Prism.parse_file(file).value)
|
|
240
|
+
body = find_starting(root_node, line) or return []
|
|
241
|
+
found = []
|
|
242
|
+
walk = lambda do |node|
|
|
243
|
+
if node.is_a?(Prism::CallNode) && SELF_SAVES.include?(node.name) && (node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode))
|
|
244
|
+
found << [node.name.to_s, node.location.start_line]
|
|
245
|
+
end
|
|
246
|
+
node.compact_child_nodes.each(&walk)
|
|
247
|
+
end
|
|
248
|
+
walk.(body)
|
|
249
|
+
found
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def find_starting(node, line)
|
|
253
|
+
return node if (node.is_a?(Prism::DefNode) || node.is_a?(Prism::LambdaNode) || node.is_a?(Prism::BlockNode)) && node.location.start_line == line
|
|
254
|
+
|
|
255
|
+
node.compact_child_nodes.each do |child|
|
|
256
|
+
next unless child.location.start_line <= line && child.location.end_line >= line
|
|
257
|
+
|
|
258
|
+
found = find_starting(child, line) and return found
|
|
259
|
+
end
|
|
260
|
+
nil
|
|
261
|
+
end
|
|
262
|
+
|
|
116
263
|
# name_changed?, saved_change_to_title?, ... for a real attribute (Slim may not have generated it).
|
|
117
264
|
def attribute_method?(klass, name)
|
|
118
265
|
return false unless klass.respond_to?(:attribute_method_patterns) && klass.respond_to?(:attribute_names)
|
|
@@ -36,13 +36,21 @@ module AsMinimizer
|
|
|
36
36
|
# throw :abort for the rest, or any custom terminator), around callbacks
|
|
37
37
|
# nesting the rest of the chain, after callbacks last-defined first and
|
|
38
38
|
# skipped after a halt when the chain says so. For controllers, `only:` and
|
|
39
|
-
# `except:` are
|
|
39
|
+
# `except:` are a lookup of action_name in the filter's action set.
|
|
40
|
+
#
|
|
41
|
+
# One method per class and chain (not per controller action), and one body
|
|
42
|
+
# per distinct chain: classes whose chains come out the same (subclasses of
|
|
43
|
+
# a controller that add no callbacks of their own, ...) get the same method
|
|
44
|
+
# body, so a JIT compiles it once.
|
|
40
45
|
#
|
|
41
46
|
# The generated code is named in backtraces ("unrolled: Post#save"), and with
|
|
42
47
|
# source_dir set, written to a file there, so backtraces point at code you can open.
|
|
43
48
|
module Unroller
|
|
44
49
|
# Filters and conditions that aren't symbols are referenced from here.
|
|
45
50
|
OBJECTS = []
|
|
51
|
+
# [kind, body] => a module holding the generated method; classes with the
|
|
52
|
+
# same chain define theirs from its UnboundMethod, sharing the body.
|
|
53
|
+
SHARED = {}
|
|
46
54
|
MUTEX = Mutex.new
|
|
47
55
|
Unsupported = Class.new(StandardError)
|
|
48
56
|
|
|
@@ -62,35 +70,43 @@ module AsMinimizer
|
|
|
62
70
|
def method_for(target, kind)
|
|
63
71
|
klass = target.class
|
|
64
72
|
chain = target.__callbacks[kind]
|
|
65
|
-
action = target.action_name if kind == :process_action && target.respond_to?(:action_name)
|
|
66
73
|
cache = klass.instance_variable_get(:@__as_unrolled) ||
|
|
67
74
|
MUTEX.synchronize { klass.instance_variable_get(:@__as_unrolled) || klass.instance_variable_set(:@__as_unrolled, {}) }
|
|
68
|
-
hit =
|
|
75
|
+
hit = cache[kind]
|
|
69
76
|
return hit[1] if hit && hit[0].equal?(chain)
|
|
70
77
|
|
|
71
78
|
MUTEX.synchronize do
|
|
72
|
-
|
|
73
|
-
hit = per_kind[action]
|
|
79
|
+
hit = cache[kind]
|
|
74
80
|
return hit[1] if hit && hit[0].equal?(chain)
|
|
75
81
|
|
|
76
|
-
name = generate(klass, kind,
|
|
77
|
-
|
|
82
|
+
name = generate(klass, kind, chain)
|
|
83
|
+
cache[kind] = [chain, name]
|
|
78
84
|
name
|
|
79
85
|
end
|
|
80
86
|
end
|
|
81
87
|
|
|
82
|
-
def source_for(klass, kind
|
|
88
|
+
def source_for(klass, kind) = Generator.new(klass, kind, klass.__callbacks[kind]).source
|
|
89
|
+
|
|
90
|
+
# Generated methods, and the distinct bodies behind them.
|
|
91
|
+
def stats
|
|
92
|
+
classes = ObjectSpace.each_object(Class).select { it.instance_variable_defined?(:@__as_unrolled) }
|
|
93
|
+
{ methods: classes.sum { it.instance_variable_get(:@__as_unrolled).count { |_, (_, name)| name } }, bodies: SHARED.size }
|
|
94
|
+
end
|
|
83
95
|
|
|
84
96
|
private
|
|
85
97
|
|
|
86
|
-
def generate(klass, kind,
|
|
87
|
-
generator = Generator.new(klass, kind,
|
|
98
|
+
def generate(klass, kind, chain)
|
|
99
|
+
generator = Generator.new(klass, kind, chain)
|
|
88
100
|
body = generator.source
|
|
89
|
-
warn_missing(klass, kind,
|
|
90
|
-
name = :"__as_#{kind}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
warn_missing(klass, kind, generator.targets)
|
|
102
|
+
name = :"__as_#{kind}"
|
|
103
|
+
shared = SHARED[[kind, body]] ||= begin
|
|
104
|
+
label = "#{klass.name || "anonymous"}##{kind}"
|
|
105
|
+
code = "# #{label} callbacks (and every class whose chain is the same), unrolled by activesupport-minimizer\n" \
|
|
106
|
+
"def #{name}\n#{body}\nend\n"
|
|
107
|
+
Module.new.tap { it.module_eval(code, file_for(klass, kind, label, code), 1) }
|
|
108
|
+
end
|
|
109
|
+
klass.send(:define_method, name, shared.instance_method(name))
|
|
94
110
|
klass.send(:private, name)
|
|
95
111
|
name
|
|
96
112
|
rescue Unsupported
|
|
@@ -99,7 +115,7 @@ module AsMinimizer
|
|
|
99
115
|
|
|
100
116
|
# Rails finds out when the chain runs (NoMethodError, a 500 for a
|
|
101
117
|
# controller); say so when the method is generated, once per target.
|
|
102
|
-
def warn_missing(klass, kind,
|
|
118
|
+
def warn_missing(klass, kind, targets)
|
|
103
119
|
targets.each do |name, role, callback_kind|
|
|
104
120
|
next unless Check.status(klass, name) == :missing
|
|
105
121
|
|
|
@@ -108,15 +124,14 @@ module AsMinimizer
|
|
|
108
124
|
|
|
109
125
|
@warned[[klass, kind, name]] = true
|
|
110
126
|
AsMinimizer.warn("activesupport-minimizer: #{klass.name || klass}'s #{callback_kind}_#{kind} #{role} #{name.inspect} " \
|
|
111
|
-
"names no method
|
|
127
|
+
"names no method; running the chain will raise NoMethodError")
|
|
112
128
|
end
|
|
113
129
|
end
|
|
114
130
|
|
|
115
|
-
def file_for(klass, kind,
|
|
131
|
+
def file_for(klass, kind, label, code)
|
|
116
132
|
return "unrolled: #{label}" unless source_dir
|
|
117
133
|
|
|
118
|
-
path = File.join(source_dir.to_s, (klass.name || "anonymous_#{klass.object_id}").gsub("::", "/"),
|
|
119
|
-
"#{kind}#{"_#{action}" if action}.rb")
|
|
134
|
+
path = File.join(source_dir.to_s, (klass.name || "anonymous_#{klass.object_id}").gsub("::", "/"), "#{kind}.rb")
|
|
120
135
|
FileUtils.mkdir_p(File.dirname(path))
|
|
121
136
|
File.write(path, code)
|
|
122
137
|
path
|
|
@@ -139,10 +154,9 @@ module AsMinimizer
|
|
|
139
154
|
# Symbols the generated code calls (filters and conditions that apply).
|
|
140
155
|
attr_reader :targets
|
|
141
156
|
|
|
142
|
-
def initialize(klass, kind,
|
|
157
|
+
def initialize(klass, kind, chain)
|
|
143
158
|
@klass = klass
|
|
144
159
|
@kind = kind
|
|
145
|
-
@action = action
|
|
146
160
|
@chain = chain
|
|
147
161
|
@config = chain.config
|
|
148
162
|
@lambdas = 0
|
|
@@ -167,8 +181,9 @@ module AsMinimizer
|
|
|
167
181
|
object(terminator)
|
|
168
182
|
end
|
|
169
183
|
@skip_after = @config[:skip_after_callbacks_if_terminated]
|
|
170
|
-
# With raise_on_missing_callback_actions, only:/except:
|
|
171
|
-
|
|
184
|
+
# With raise_on_missing_callback_actions, only:/except: go through the
|
|
185
|
+
# filter object, which raises for actions that don't exist.
|
|
186
|
+
@inline_actions = !(@klass.respond_to?(:raise_on_missing_callback_actions) && @klass.raise_on_missing_callback_actions)
|
|
172
187
|
end
|
|
173
188
|
|
|
174
189
|
# What CallbackChain#compile builds: each around opens a nested level;
|
|
@@ -232,19 +247,21 @@ module AsMinimizer
|
|
|
232
247
|
end
|
|
233
248
|
|
|
234
249
|
# Ruby for the callback's :if/:unless, "true" when there are none, nil
|
|
235
|
-
# when it never
|
|
250
|
+
# when it can never run (an `only:` with no actions).
|
|
236
251
|
def conditions(callback)
|
|
237
252
|
parts = []
|
|
238
253
|
callback.instance_variable_get(:@if).each do |c|
|
|
239
|
-
if
|
|
240
|
-
return nil
|
|
254
|
+
if inline_action_filter?(c)
|
|
255
|
+
return nil if actions_of(c).empty?
|
|
256
|
+
|
|
257
|
+
parts << "#{object(actions_of(c))}.include?(action_name)"
|
|
241
258
|
else
|
|
242
259
|
parts << condition(callback, c)
|
|
243
260
|
end
|
|
244
261
|
end
|
|
245
262
|
callback.instance_variable_get(:@unless).each do |c|
|
|
246
|
-
if
|
|
247
|
-
|
|
263
|
+
if inline_action_filter?(c)
|
|
264
|
+
parts << "!#{object(actions_of(c))}.include?(action_name)" unless actions_of(c).empty?
|
|
248
265
|
else
|
|
249
266
|
parts << "!(#{condition(callback, c)})"
|
|
250
267
|
end
|
|
@@ -252,11 +269,12 @@ module AsMinimizer
|
|
|
252
269
|
parts.empty? ? "true" : parts.join(" && ")
|
|
253
270
|
end
|
|
254
271
|
|
|
255
|
-
def
|
|
256
|
-
@
|
|
272
|
+
def inline_action_filter?(c)
|
|
273
|
+
@inline_actions && defined?(AbstractController::Callbacks::ActionFilter) && c.is_a?(AbstractController::Callbacks::ActionFilter)
|
|
257
274
|
end
|
|
258
275
|
|
|
259
|
-
|
|
276
|
+
# only:/except: actions, as the frozen Set of Strings Rails keeps.
|
|
277
|
+
def actions_of(filter) = filter.instance_variable_get(:@actions)
|
|
260
278
|
|
|
261
279
|
# As CallTemplate.build + make_lambda.
|
|
262
280
|
def condition(callback, c)
|
data/lib/as_minimizer/version.rb
CHANGED