undertow 0.2.0 → 0.2.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/lib/undertow/dsl.rb +10 -2
- data/lib/undertow/trackable.rb +68 -53
- data/lib/undertow/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf230477188804dcf6c63e0c97c615defb3577c78f85e930449d7dba6d9d965e
|
|
4
|
+
data.tar.gz: 6653637c116fff79ba2a2806326882f7e7e939acdf1ca0f01a653e22530dd3c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 74aca2058a93a4474bd275de1ab3d077b7e83e5a6449cb0921938585821c29f39ab531240eabbfb89d28ba7ac4a73cd4f3c58183e001992847d1a3ebae6e89b2
|
|
7
|
+
data.tar.gz: 6c18ad42284eb95008c44bfd6240e79ae14c7d99aa50befa8c1ccb417d07286a55049c1467f6e4f66a66d8f93df8e7c1b613cc045217df6ba81c9334af3cf368
|
data/lib/undertow/dsl.rb
CHANGED
|
@@ -21,19 +21,27 @@ module Undertow
|
|
|
21
21
|
_undertow_ensure_trackable!
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# Suppress self-tracking when these root-model columns are the only changes.
|
|
25
|
+
# Accepts strings or symbols; blanks are ignored.
|
|
24
26
|
def undertow_skip(columns)
|
|
25
|
-
_undertow_config.skip_columns = columns
|
|
27
|
+
_undertow_config.skip_columns = Array(columns).map(&:to_s).reject(&:blank?).uniq
|
|
26
28
|
_undertow_ensure_trackable!
|
|
27
29
|
end
|
|
28
30
|
|
|
31
|
+
# Track invalidations from an upstream association.
|
|
32
|
+
# `watched_columns` accepts strings or symbols; blanks are ignored.
|
|
33
|
+
# Empty/nil means no watched-column filter.
|
|
29
34
|
def undertow_depends_on(association, foreign_key: nil, resolver: nil, watched_columns: nil)
|
|
30
35
|
raise ArgumentError, 'provide exactly one of foreign_key: or resolver:' unless foreign_key.nil? ^ resolver.nil?
|
|
31
36
|
|
|
37
|
+
normalized_watched = Array(watched_columns).map(&:to_s).
|
|
38
|
+
reject(&:blank?).uniq.presence
|
|
39
|
+
|
|
32
40
|
_undertow_config.dependencies << {
|
|
33
41
|
association: association,
|
|
34
42
|
foreign_key: foreign_key,
|
|
35
43
|
resolver: resolver,
|
|
36
|
-
watched_columns:
|
|
44
|
+
watched_columns: normalized_watched
|
|
37
45
|
}.freeze
|
|
38
46
|
_undertow_ensure_trackable!
|
|
39
47
|
end
|
data/lib/undertow/trackable.rb
CHANGED
|
@@ -4,8 +4,9 @@ module Undertow
|
|
|
4
4
|
# ActiveRecord concern mixed in automatically when a model uses the Undertow DSL
|
|
5
5
|
# (undertow_on_drain, undertow_skip, undertow_depends_on). Never included manually.
|
|
6
6
|
#
|
|
7
|
-
# Provides class-level callback registration and
|
|
8
|
-
# Callbacks are wired at boot by the Railtie
|
|
7
|
+
# Provides class-level callback registration and dependency push handlers, plus
|
|
8
|
+
# instance-level self-tracking handlers. Callbacks are wired at boot by the Railtie
|
|
9
|
+
# after all models are loaded.
|
|
9
10
|
module Trackable
|
|
10
11
|
extend ActiveSupport::Concern
|
|
11
12
|
|
|
@@ -30,86 +31,100 @@ module Undertow
|
|
|
30
31
|
(config.dependencies || []).each { |dep| _register_dep_callbacks!(dep) }
|
|
31
32
|
end
|
|
32
33
|
|
|
34
|
+
def _push_undertow_pending(ids)
|
|
35
|
+
Buffer.push_pending(name, ids)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def _push_undertow_deleted(ids)
|
|
39
|
+
Buffer.push_deleted(name, ids)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def _push_dep_pending(record, dep)
|
|
43
|
+
ids = _dep_ids_for(record, dep)
|
|
44
|
+
_push_undertow_pending(ids) if ids.any?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def _push_dep_updated(record, dep)
|
|
48
|
+
watched = dep[:watched_columns]
|
|
49
|
+
# Two non-obvious cases where after_commit on :update fires:
|
|
50
|
+
# - no-op save: saved_changes is {}, suppressed when watched_columns is configured
|
|
51
|
+
# - touch (e.g. belongs_to touch: true): saved_changes contains only updated_at,
|
|
52
|
+
# suppressed only when watched_columns is configured and updated_at is not in it
|
|
53
|
+
return if watched && (record.saved_changes.keys & watched).none?
|
|
54
|
+
|
|
55
|
+
_push_dep_pending(record, dep)
|
|
56
|
+
end
|
|
57
|
+
|
|
33
58
|
private
|
|
34
59
|
|
|
35
60
|
def _register_self_callbacks!
|
|
36
|
-
after_commit :
|
|
61
|
+
after_commit :_push_self_created, on: :create
|
|
62
|
+
after_commit :_push_self_updated, on: :update
|
|
37
63
|
after_destroy :_push_self_deleted
|
|
38
|
-
after_restore :
|
|
64
|
+
after_restore :_push_self_restored if respond_to?(:after_restore)
|
|
39
65
|
end
|
|
40
66
|
|
|
41
67
|
def _register_dep_callbacks!(dep)
|
|
42
68
|
dep_class = _resolve_dep_class(dep)
|
|
43
|
-
return unless dep_class
|
|
44
|
-
|
|
45
69
|
root_class = self
|
|
46
|
-
watched = dep[:watched_columns].presence # [] treated same as nil, watch all
|
|
47
|
-
|
|
48
|
-
resolver = dep[:resolver] || begin
|
|
49
|
-
fk = dep[:foreign_key]
|
|
50
|
-
->(record) { root_class.where(fk => record.id) }
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
push_pending = ->(record) {
|
|
54
|
-
ids = resolver.call(record).pluck(:id)
|
|
55
|
-
next unless ids.any?
|
|
56
70
|
|
|
57
|
-
|
|
58
|
-
}
|
|
71
|
+
dep_class.after_commit(on: :create) { root_class._push_dep_pending(self, dep) }
|
|
72
|
+
dep_class.after_commit(on: :update) { root_class._push_dep_updated(self, dep) }
|
|
59
73
|
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
dep_class.
|
|
64
|
-
next if watched && (saved_changes.keys & watched).none?
|
|
74
|
+
# Soft-delete gems fire run_callbacks(:destroy), triggering after_destroy, but
|
|
75
|
+
# mark the record deleted via update_columns (bypassing after_commit),
|
|
76
|
+
# so the create/update callbacks above never double-fire on a soft delete.
|
|
77
|
+
dep_class.after_destroy { root_class._push_dep_pending(self, dep) }
|
|
65
78
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# Dep destroyed, reindex surviving root records. SoftDeletable calls
|
|
70
|
-
# run_callbacks(:destroy), which fires after_destroy, but update_columns does NOT
|
|
71
|
-
# trigger after_commit, so scoping after_commit to [:create, :update] above
|
|
72
|
-
# ensures destroy commits don't double-fire.
|
|
73
|
-
dep_class.after_destroy { push_pending.call(self) }
|
|
74
|
-
|
|
75
|
-
# Dep restored, after_restore is the only hook that fires because restore!
|
|
76
|
-
# uses update_columns, bypassing after_commit.
|
|
77
|
-
if dep_class.respond_to?(:after_restore)
|
|
78
|
-
dep_class.after_restore { push_pending.call(self) }
|
|
79
|
-
end
|
|
79
|
+
# Soft-delete restores use update_columns to unmark the record, bypassing
|
|
80
|
+
# after_commit. after_restore is the only hook that fires for restores.
|
|
81
|
+
dep_class.after_restore { root_class._push_dep_pending(self, dep) } if dep_class.respond_to?(:after_restore)
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
def _resolve_dep_class(dep)
|
|
83
|
-
|
|
85
|
+
reflect_on_association(dep[:association])&.klass ||
|
|
84
86
|
dep[:association].to_s.classify.constantize
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def _dep_ids_for(record, dep)
|
|
90
|
+
if dep[:resolver]
|
|
91
|
+
dep[:resolver].call(record).pluck(:id)
|
|
85
92
|
else
|
|
86
|
-
|
|
87
|
-
dep[:association].to_s.classify.constantize
|
|
93
|
+
where(dep[:foreign_key] => record.id).pluck(:id)
|
|
88
94
|
end
|
|
89
95
|
end
|
|
96
|
+
end
|
|
90
97
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _push_undertow_pending(ids)
|
|
94
|
-
Buffer.push_pending(name, ids)
|
|
95
|
-
end
|
|
98
|
+
private
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
end
|
|
100
|
+
def _push_self_created
|
|
101
|
+
_enqueue_self_pending
|
|
100
102
|
end
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
def _push_self_updated
|
|
105
|
+
return unless _self_update_requires_invalidation?
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return if ignored.any? && saved_changes.any? && (saved_changes.keys - ignored).empty?
|
|
107
|
+
_enqueue_self_pending
|
|
108
|
+
end
|
|
107
109
|
|
|
108
|
-
|
|
110
|
+
def _push_self_restored
|
|
111
|
+
_enqueue_self_pending
|
|
109
112
|
end
|
|
110
113
|
|
|
111
114
|
def _push_self_deleted
|
|
112
115
|
self.class._push_undertow_deleted([id])
|
|
113
116
|
end
|
|
117
|
+
|
|
118
|
+
def _enqueue_self_pending
|
|
119
|
+
self.class._push_undertow_pending([id])
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def _self_update_requires_invalidation?
|
|
123
|
+
changed = saved_changes.keys
|
|
124
|
+
return false if changed.empty?
|
|
125
|
+
|
|
126
|
+
ignored = self.class._undertow_ignored_columns
|
|
127
|
+
(changed - ignored).any?
|
|
128
|
+
end
|
|
114
129
|
end
|
|
115
130
|
end
|
data/lib/undertow/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: undertow
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan Allen
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|