activerecord-minimizer 0.1.1 → 0.1.2

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: 4d267d90b4e0fb5fa138099d607c877642be888d811836e9a01ba5d902805f62
4
- data.tar.gz: 0b5835ae5a2ed8290e2a1cfbce79be67e5a5a088339aba4531c38679f8bbe560
3
+ metadata.gz: 765a2a713e6c6e2582ea3710affccdbfcae87fc798c40a8687f32856b24d2d6b
4
+ data.tar.gz: 32d4267611a306b162b1a3c47d082c29b6a080d30ae066d3dfd1a6db1d4a69a7
5
5
  SHA512:
6
- metadata.gz: 225301a99201d5b86bc759bd0aed0fb2b52ee410036ca844be740a013036639c94e9e58ffb71aca19aae5c17a364ba35e634500053a68b9f6b718838534bd2b9
7
- data.tar.gz: ca8f655b05effb30eeaa8558463467ea42e96ddd0694842d90b18c170419ed7137791c83b98c4050665e34aeefcbb8272bda170fd9fde4ce7b96cc3fc2c08a1d
6
+ metadata.gz: 67faf17cd90cfd3583be2eefbc532423e75628270a198f1f55834c2881b62c3428db9c83b272e6ccfb4ffd5d1ecfe8f0233b8b2ce14cbc9f4a2671458d5cb923
7
+ data.tar.gz: 8b9fafd7a79f04d9dda376491def78b8441003e9b948955c8a7e07d7a3bf06669842bdb7b3f2922509ec54932f580fe79110ac3779ca4be01cb3fdb090fffb83
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2 (2026-10-18)
4
+
5
+ - Strict mode no longer breaks what gems and Rails call by name, so a keep
6
+ list is only needed for an app's own names:
7
+ - `<attr>_in_database` and `clear_<attr>_change` are generated on first
8
+ call: `increment!`/`decrement!`, and so counter caches, raised before.
9
+ - Devise models keep every attribute method of the columns Devise uses
10
+ (email, encrypted_password, unconfirmed_email and the configured keys).
11
+ Recoverable asks `respond_to?("will_save_change_to_<key>?")` before
12
+ clearing the reset password token, so without these an old reset link
13
+ kept working after a password change.
14
+ - Active Storage blobs and Action Mailbox inbound emails keep the methods
15
+ those frameworks call.
16
+
3
17
  ## 0.1.1 (2026-10-18)
4
18
 
5
19
  - Small records work on Rails main, whose attribute sets take a `default_keys` argument and get a fresh `{}` from Rails; the association cache Rails main leaves `nil` until used is left alone.
data/README.md CHANGED
@@ -26,18 +26,25 @@ name_changed? was removed by activerecord-minimizer's strict mode; use attribute
26
26
  ```
27
27
 
28
28
  [rubocop-minify](../rubocop-minify) rewrites those calls in your Ruby and ERB.
29
- Gems that call them by name (Devise's `will_save_change_to_email?`, Active
30
- Storage's `service_name_changed?`, counter caches) get them back from a keep
31
- list, `config/ar_minimizer_keep.txt`, one `name` or `Model#name` per line.
32
- To find what to put there, run the test suite with `strict = false` and
29
+ What other gems and your own code call by name goes in a keep list,
30
+ `config/ar_minimizer_keep.txt`, one `name` or `Model#name` per line. To find
31
+ what to put there, run the test suite with `strict = false` and
33
32
  `AR_MINIMIZER_PROFILE=tmp/ar_minimizer`, then `bin/rails ar_minimizer:profile`.
34
33
 
35
- Rails itself still gets what it needs:
34
+ Rails and common gems still get what they call by name, without a keep list:
36
35
 
37
36
  * `<attr>_before_type_cast` and `<attr>_came_from_user?` answer `respond_to?`
38
37
  and are generated on first call. Form fields and numericality validation ask
39
38
  before calling them, and quietly do something else when told no (a form
40
39
  would show the cast value, `"abc"` would validate as 0).
40
+ * `<attr>_in_database` and `clear_<attr>_change` are generated on first call:
41
+ `increment!`/`decrement!`, and so counter caches, use them.
42
+ * Devise models keep every attribute method of the columns Devise uses (email,
43
+ encrypted_password, unconfirmed_email and the configured keys). Recoverable
44
+ asks `respond_to?` before clearing the reset password token on a password
45
+ or email change.
46
+ * Active Storage blobs and Action Mailbox inbound emails keep what those
47
+ frameworks call (`service_name_changed?`, `status_previously_changed?`, ...).
41
48
  * `<enum>_for_database` and `updated_at_before_type_cast`/`_came_from_user?`
42
49
  are generated up front: Rails calls them on hot paths.
43
50
  * Aliases (`alias_attribute`, Rails' own `id_value`), partial selects
@@ -47,6 +54,9 @@ Rails itself still gets what it needs:
47
54
  `config.ar_minimizer.strict = false` keeps the removed methods working: the
48
55
  first call to one generates it on that model.
49
56
 
57
+ Turn `slim` on in development and tests as well as production: the
58
+ errors then show up while you work and in CI. Slim follows code reloads.
59
+
50
60
  ## Compiling call sites
51
61
 
52
62
  Calls whose receiver is known to be a record of a model with that column are
@@ -17,11 +17,35 @@ module ArMinimizer
17
17
 
18
18
  MATCH = ActiveModel::AttributeMethods::ClassMethods::AttributeMethodPattern::AttributeMethod
19
19
 
20
- # Rails asks respond_to? before calling these, and quietly does something
21
- # else when the answer is no: form fields show the cast value instead of
22
- # what the user typed, numericality validates the cast value ("abc" -> 0).
23
- # Strict mode keeps them answering, generated on first call.
24
- PROBED = { "_before_type_cast" => "attribute_before_type_cast", "_came_from_user?" => "attribute_came_from_user?" }.freeze
20
+ # [prefix, suffix, proxy] of the patterns Rails itself calls by name, for
21
+ # any attribute. Strict mode generates them on first call instead of
22
+ # removing them:
23
+ # - _before_type_cast, _came_from_user?: Rails asks respond_to? first and
24
+ # quietly does something else when told no (form fields would show the
25
+ # cast value, numericality would validate it: "abc" -> 0).
26
+ # - _in_database, clear_..._change: increment!/decrement!, and so every
27
+ # counter cache, call them on the attribute they change.
28
+ ON_DEMAND = [
29
+ ["", "_before_type_cast", "attribute_before_type_cast"],
30
+ ["", "_came_from_user?", "attribute_came_from_user?"],
31
+ ["", "_in_database", "attribute_in_database"],
32
+ ["clear_", "_change", "clear_attribute_change"],
33
+ ].freeze
34
+ ON_DEMAND_PROXIES = ON_DEMAND.map(&:last).freeze
35
+
36
+ # Methods Rails' own models call by name.
37
+ FRAMEWORK_KEEP = {
38
+ "ActiveStorage::Blob" => %w[content_type_previously_changed? metadata_previously_changed? metadata_changed? service_name_changed?],
39
+ "ActionMailbox::InboundEmail" => %w[status_previously_changed?],
40
+ }.freeze
41
+
42
+ # Columns Devise works with on a model, besides its configured keys: it
43
+ # calls their dirty-tracking methods by name, some behind respond_to?
44
+ # (Recoverable clears the reset password token only when it's told the
45
+ # password or an authentication key will change).
46
+ DEVISE_COLUMNS = %w[email encrypted_password unconfirmed_email].freeze
47
+ DEVISE_KEYS = %i[authentication_keys reset_password_keys confirmation_keys unlock_keys
48
+ case_insensitive_keys strip_whitespace_keys].freeze
25
49
 
26
50
  class << self
27
51
  attr_reader :keep, :keep_methods
@@ -86,6 +110,7 @@ module ArMinimizer
86
110
  return false if Thread.current[:ar_minimizer_generating] == name
87
111
  return false if keep.include?(pattern.proxy_target)
88
112
  return false if keep_methods.include?(name) || keep_methods.include?("#{model.name}##{name}")
113
+ return false if FRAMEWORK_KEEP[model.name]&.include?(name)
89
114
 
90
115
  !framework_calls?(model, pattern.proxy_target, attr_name.to_s)
91
116
  end
@@ -145,8 +170,21 @@ module ArMinimizer
145
170
  # every enum predicate (`published?`) and enum scope calls status_for_database
146
171
  when "attribute_for_database" then model.defined_enums.key?(attr)
147
172
  # cache_version, on every cache key with recyclable cache keys
148
- when "attribute_before_type_cast", "attribute_came_from_user?" then attr == "updated_at"
149
- else false
173
+ when "attribute_before_type_cast", "attribute_came_from_user?" then attr == "updated_at" || devise_column?(model, attr)
174
+ else devise_column?(model, attr)
175
+ end
176
+ end
177
+
178
+ # All of a Devise model's attribute methods for the columns Devise uses.
179
+ def devise_column?(model, attr)
180
+ return false unless model.respond_to?(:devise_modules)
181
+ return true if DEVISE_COLUMNS.include?(attr)
182
+
183
+ DEVISE_KEYS.any? do |config|
184
+ next false unless model.respond_to?(config)
185
+
186
+ keys = model.public_send(config)
187
+ (keys.is_a?(Hash) ? keys.keys : Array(keys)).any? { it.to_s == attr }
150
188
  end
151
189
  end
152
190
  end
@@ -190,7 +228,7 @@ module ArMinimizer
190
228
  # without this `user.method(:name_changed?)` raises NameError.
191
229
  def respond_to_missing?(name, include_private = false)
192
230
  # Strict: matched_attribute_method only matches readers, writers and
193
- # the PROBED patterns.
231
+ # the ON_DEMAND patterns.
194
232
  super || (Slim.enabled? && !matched_attribute_method(name.to_s).nil?)
195
233
  end
196
234
 
@@ -209,7 +247,7 @@ module ArMinimizer
209
247
  return super
210
248
  end
211
249
 
212
- Slim.generate(self.class, name.name, match) if Slim.lazy? || Slim::PROBED.value?(match.proxy_target)
250
+ Slim.generate(self.class, name.name, match) if Slim.lazy? || Slim::ON_DEMAND_PROXIES.include?(match.proxy_target)
213
251
  attribute_missing(match, ...)
214
252
  end
215
253
 
@@ -219,10 +257,10 @@ module ArMinimizer
219
257
  def matched_attribute_method(method_name)
220
258
  return super unless Slim.strict?
221
259
 
222
- Slim::PROBED.each do |suffix, proxy|
223
- next unless method_name.end_with?(suffix)
260
+ Slim::ON_DEMAND.each do |prefix, suffix, proxy|
261
+ next unless method_name.length > prefix.length + suffix.length && method_name.start_with?(prefix) && method_name.end_with?(suffix)
224
262
 
225
- attr = method_name.delete_suffix(suffix)
263
+ attr = method_name[prefix.length...-suffix.length]
226
264
  klass = self.class
227
265
  return Slim::MATCH.new(proxy, attr) if klass._has_attribute?(attr) || klass.attribute_aliases.key?(attr) || @attributes&.key?(attr)
228
266
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ArMinimizer
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-minimizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sampo Kuokkanen