hidden_hooks 1.0.0
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 +7 -0
- data/.rubocop.yml +451 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +170 -0
- data/Rakefile +9 -0
- data/hidden_hooks.gemspec +32 -0
- data/lib/hidden_hooks/active_record.rb +27 -0
- data/lib/hidden_hooks/version.rb +3 -0
- data/lib/hidden_hooks.rb +48 -0
- metadata +72 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b03055b97dfd21671433cde45b6d61ab1822c2939c21df095d17c532811c3195
|
|
4
|
+
data.tar.gz: b29b849c86f76924f3d9007d5c80519b99531ac9f0057fc91df9b277aebf41a9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2c975f3f1baf90742efb7a31daf6ef725fa344c5f9b86c8c5f7ca47ef92151ecce0279ee26fdc921a4215e39c40e5472294b233550e2a40f1af5157b11727fda
|
|
7
|
+
data.tar.gz: 0a695c0b6baa6336f873f1d608e630e364a39b10b82c9188befd8880fede1ba6a150bc2fa6da8c42c85604285945a20e98eaa4a89b3fa66570a07b4d30526bb3
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
Exclude:
|
|
3
|
+
- 'bin/**'
|
|
4
|
+
|
|
5
|
+
Gemspec/DeprecatedAttributeAssignment:
|
|
6
|
+
Enabled: true
|
|
7
|
+
|
|
8
|
+
Gemspec/DevelopmentDependencies:
|
|
9
|
+
Enabled: true
|
|
10
|
+
|
|
11
|
+
Gemspec/RequireMFA:
|
|
12
|
+
Enabled: false
|
|
13
|
+
|
|
14
|
+
Layout/AccessModifierIndentation:
|
|
15
|
+
EnforcedStyle: outdent
|
|
16
|
+
|
|
17
|
+
Layout/BeginEndAlignment:
|
|
18
|
+
EnforcedStyleAlignWith: begin
|
|
19
|
+
|
|
20
|
+
Layout/BlockAlignment:
|
|
21
|
+
EnforcedStyleAlignWith: start_of_block
|
|
22
|
+
|
|
23
|
+
Layout/CommentIndentation:
|
|
24
|
+
AllowForAlignment: true
|
|
25
|
+
|
|
26
|
+
Layout/EmptyLineAfterMultilineCondition:
|
|
27
|
+
Enabled: true
|
|
28
|
+
|
|
29
|
+
Layout/EndOfLine:
|
|
30
|
+
EnforcedStyle: lf
|
|
31
|
+
|
|
32
|
+
Layout/ExtraSpacing:
|
|
33
|
+
AllowForAlignment: true
|
|
34
|
+
AllowBeforeTrailingComments: true
|
|
35
|
+
|
|
36
|
+
Layout/HashAlignment:
|
|
37
|
+
EnforcedHashRocketStyle: table
|
|
38
|
+
EnforcedColonStyle: table
|
|
39
|
+
EnforcedLastArgumentHashStyle: ignore_implicit
|
|
40
|
+
|
|
41
|
+
Layout/LineContinuationLeadingSpace:
|
|
42
|
+
Enabled: true
|
|
43
|
+
|
|
44
|
+
Layout/LineContinuationSpacing:
|
|
45
|
+
Enabled: true
|
|
46
|
+
|
|
47
|
+
Layout/LineEndStringConcatenationIndentation:
|
|
48
|
+
Enabled: true
|
|
49
|
+
|
|
50
|
+
Layout/MultilineArrayLineBreaks:
|
|
51
|
+
Enabled: true
|
|
52
|
+
|
|
53
|
+
Layout/MultilineAssignmentLayout:
|
|
54
|
+
Enabled: true
|
|
55
|
+
EnforcedStyle: same_line
|
|
56
|
+
|
|
57
|
+
Layout/MultilineHashKeyLineBreaks:
|
|
58
|
+
Enabled: true
|
|
59
|
+
|
|
60
|
+
Layout/MultilineMethodArgumentLineBreaks:
|
|
61
|
+
Enabled: true
|
|
62
|
+
|
|
63
|
+
Layout/MultilineMethodCallIndentation:
|
|
64
|
+
EnforcedStyle: indented_relative_to_receiver
|
|
65
|
+
|
|
66
|
+
Layout/SingleLineBlockChain:
|
|
67
|
+
Enabled: true
|
|
68
|
+
|
|
69
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
|
70
|
+
EnforcedStyle: no_space
|
|
71
|
+
|
|
72
|
+
Layout/SpaceAroundOperators:
|
|
73
|
+
EnforcedStyleForExponentOperator: space
|
|
74
|
+
|
|
75
|
+
Layout/SpaceBeforeBrackets:
|
|
76
|
+
Enabled: true
|
|
77
|
+
|
|
78
|
+
Layout/SpaceInsideHashLiteralBraces:
|
|
79
|
+
EnforcedStyle: no_space
|
|
80
|
+
|
|
81
|
+
Layout/TrailingWhitespace:
|
|
82
|
+
AllowInHeredoc: true
|
|
83
|
+
|
|
84
|
+
Lint/AmbiguousAssignment:
|
|
85
|
+
Enabled: true
|
|
86
|
+
|
|
87
|
+
Lint/AmbiguousOperatorPrecedence:
|
|
88
|
+
Enabled: true
|
|
89
|
+
|
|
90
|
+
Lint/AmbiguousRange:
|
|
91
|
+
Enabled: true
|
|
92
|
+
RequireParenthesesForMethodChains: true
|
|
93
|
+
|
|
94
|
+
Lint/AssignmentInCondition:
|
|
95
|
+
AllowSafeAssignment: false
|
|
96
|
+
|
|
97
|
+
Lint/ConstantOverwrittenInRescue:
|
|
98
|
+
Enabled: true
|
|
99
|
+
|
|
100
|
+
Lint/DeprecatedConstants:
|
|
101
|
+
Enabled: true
|
|
102
|
+
|
|
103
|
+
Lint/DuplicateBranch:
|
|
104
|
+
Enabled: true
|
|
105
|
+
IgnoreLiteralBranches: true
|
|
106
|
+
IgnoreConstantBranches: true
|
|
107
|
+
|
|
108
|
+
Lint/DuplicateMagicComment:
|
|
109
|
+
Enabled: true
|
|
110
|
+
|
|
111
|
+
Lint/DuplicateRegexpCharacterClassElement:
|
|
112
|
+
Enabled: true
|
|
113
|
+
|
|
114
|
+
Lint/EmptyBlock:
|
|
115
|
+
Enabled: true
|
|
116
|
+
|
|
117
|
+
Lint/EmptyClass:
|
|
118
|
+
Enabled: true
|
|
119
|
+
AllowComments: true
|
|
120
|
+
|
|
121
|
+
Lint/EmptyInPattern:
|
|
122
|
+
Enabled: true
|
|
123
|
+
|
|
124
|
+
Lint/HeredocMethodCallPosition:
|
|
125
|
+
Enabled: true
|
|
126
|
+
|
|
127
|
+
Lint/IncompatibleIoSelectWithFiberScheduler:
|
|
128
|
+
Enabled: false
|
|
129
|
+
|
|
130
|
+
Lint/LambdaWithoutLiteralBlock:
|
|
131
|
+
Enabled: true
|
|
132
|
+
|
|
133
|
+
Lint/NoReturnInBeginEndBlocks:
|
|
134
|
+
Enabled: true
|
|
135
|
+
|
|
136
|
+
Lint/NonAtomicFileOperation:
|
|
137
|
+
Enabled: true
|
|
138
|
+
|
|
139
|
+
Lint/NumberedParameterAssignment:
|
|
140
|
+
Enabled: true
|
|
141
|
+
|
|
142
|
+
Lint/OrAssignmentToConstant:
|
|
143
|
+
Enabled: true
|
|
144
|
+
|
|
145
|
+
Lint/RedundantDirGlobSort:
|
|
146
|
+
Enabled: true
|
|
147
|
+
|
|
148
|
+
Lint/RedundantSplatExpansion:
|
|
149
|
+
AllowPercentLiteralArrayArgument: false
|
|
150
|
+
|
|
151
|
+
Lint/RefinementImportMethods:
|
|
152
|
+
Enabled: true
|
|
153
|
+
|
|
154
|
+
Lint/RequireRangeParentheses:
|
|
155
|
+
Enabled: true
|
|
156
|
+
|
|
157
|
+
Lint/RequireRelativeSelfPath:
|
|
158
|
+
Enabled: true
|
|
159
|
+
|
|
160
|
+
Lint/SymbolConversion:
|
|
161
|
+
Enabled: true
|
|
162
|
+
|
|
163
|
+
Lint/ToEnumArguments:
|
|
164
|
+
Enabled: true
|
|
165
|
+
|
|
166
|
+
Lint/TripleQuotes:
|
|
167
|
+
Enabled: true
|
|
168
|
+
|
|
169
|
+
Lint/UnexpectedBlockArity:
|
|
170
|
+
Enabled: true
|
|
171
|
+
|
|
172
|
+
Lint/UnmodifiedReduceAccumulator:
|
|
173
|
+
Enabled: true
|
|
174
|
+
|
|
175
|
+
Lint/UnusedBlockArgument:
|
|
176
|
+
AutoCorrect: false
|
|
177
|
+
|
|
178
|
+
Lint/UnusedMethodArgument:
|
|
179
|
+
AutoCorrect: false
|
|
180
|
+
|
|
181
|
+
Lint/UselessRescue:
|
|
182
|
+
Enabled: true
|
|
183
|
+
|
|
184
|
+
Lint/UselessRuby2Keywords:
|
|
185
|
+
Enabled: true
|
|
186
|
+
|
|
187
|
+
Metrics:
|
|
188
|
+
Enabled: false
|
|
189
|
+
|
|
190
|
+
Naming/BlockForwarding:
|
|
191
|
+
Enabled: true
|
|
192
|
+
|
|
193
|
+
Naming/InclusiveLanguage:
|
|
194
|
+
Enabled: false
|
|
195
|
+
|
|
196
|
+
Security/CompoundHash:
|
|
197
|
+
Enabled: true
|
|
198
|
+
|
|
199
|
+
Security/Eval:
|
|
200
|
+
Enabled: false
|
|
201
|
+
|
|
202
|
+
Security/IoMethods:
|
|
203
|
+
Enabled: true
|
|
204
|
+
|
|
205
|
+
Style/AccessorGrouping:
|
|
206
|
+
EnforcedStyle: separated
|
|
207
|
+
|
|
208
|
+
Style/ArgumentsForwarding:
|
|
209
|
+
Enabled: false
|
|
210
|
+
|
|
211
|
+
Style/ArrayIntersect:
|
|
212
|
+
Enabled: true
|
|
213
|
+
|
|
214
|
+
Style/AutoResourceCleanup:
|
|
215
|
+
Enabled: true
|
|
216
|
+
|
|
217
|
+
Style/CollectionCompact:
|
|
218
|
+
Enabled: true
|
|
219
|
+
|
|
220
|
+
Style/CollectionMethods:
|
|
221
|
+
Enabled: true
|
|
222
|
+
|
|
223
|
+
Style/ComparableClamp:
|
|
224
|
+
Enabled: true
|
|
225
|
+
|
|
226
|
+
Style/ConcatArrayLiterals:
|
|
227
|
+
Enabled: true
|
|
228
|
+
|
|
229
|
+
Style/DirEmpty:
|
|
230
|
+
Enabled: true
|
|
231
|
+
|
|
232
|
+
Style/DocumentDynamicEvalDefinition:
|
|
233
|
+
Enabled: false
|
|
234
|
+
|
|
235
|
+
Style/Documentation:
|
|
236
|
+
Enabled: false
|
|
237
|
+
|
|
238
|
+
Style/DocumentationMethod:
|
|
239
|
+
Enabled: false
|
|
240
|
+
|
|
241
|
+
Style/DoubleNegation:
|
|
242
|
+
EnforcedStyle: forbidden
|
|
243
|
+
|
|
244
|
+
Style/EmptyHeredoc:
|
|
245
|
+
Enabled: true
|
|
246
|
+
|
|
247
|
+
Style/EmptyMethod:
|
|
248
|
+
EnforcedStyle: expanded
|
|
249
|
+
|
|
250
|
+
Style/EndlessMethod:
|
|
251
|
+
Enabled: true
|
|
252
|
+
EnforcedStyle: disallow
|
|
253
|
+
|
|
254
|
+
Style/EnvHome:
|
|
255
|
+
Enabled: true
|
|
256
|
+
|
|
257
|
+
Style/FetchEnvVar:
|
|
258
|
+
Enabled: false
|
|
259
|
+
|
|
260
|
+
Style/FileEmpty:
|
|
261
|
+
Enabled: true
|
|
262
|
+
|
|
263
|
+
Style/FileRead:
|
|
264
|
+
Enabled: true
|
|
265
|
+
|
|
266
|
+
Style/FileWrite:
|
|
267
|
+
Enabled: true
|
|
268
|
+
|
|
269
|
+
Style/FormatString:
|
|
270
|
+
EnforcedStyle: percent
|
|
271
|
+
|
|
272
|
+
Style/FrozenStringLiteralComment:
|
|
273
|
+
Enabled: false
|
|
274
|
+
|
|
275
|
+
Style/HashConversion:
|
|
276
|
+
Enabled: true
|
|
277
|
+
|
|
278
|
+
Style/HashExcept:
|
|
279
|
+
Enabled: true
|
|
280
|
+
|
|
281
|
+
Style/HashSyntax:
|
|
282
|
+
EnforcedShorthandSyntax: never
|
|
283
|
+
|
|
284
|
+
Style/IfWithBooleanLiteralBranches:
|
|
285
|
+
Enabled: true
|
|
286
|
+
|
|
287
|
+
Style/ImplicitRuntimeError:
|
|
288
|
+
Enabled: true
|
|
289
|
+
|
|
290
|
+
Style/InPatternThen:
|
|
291
|
+
Enabled: true
|
|
292
|
+
|
|
293
|
+
Style/IpAddresses:
|
|
294
|
+
Enabled: true
|
|
295
|
+
|
|
296
|
+
Style/MagicCommentFormat:
|
|
297
|
+
Enabled: true
|
|
298
|
+
|
|
299
|
+
Style/MapCompactWithConditionalBlock:
|
|
300
|
+
Enabled: true
|
|
301
|
+
|
|
302
|
+
Style/MapToHash:
|
|
303
|
+
Enabled: true
|
|
304
|
+
|
|
305
|
+
Style/MapToSet:
|
|
306
|
+
Enabled: true
|
|
307
|
+
|
|
308
|
+
Style/MethodCallWithArgsParentheses:
|
|
309
|
+
Enabled: true
|
|
310
|
+
EnforcedStyle: omit_parentheses
|
|
311
|
+
AllowParenthesesInMultilineCall: true
|
|
312
|
+
AllowParenthesesInChaining: true
|
|
313
|
+
AllowParenthesesInCamelCaseMethod: true
|
|
314
|
+
|
|
315
|
+
Style/MethodDefParentheses:
|
|
316
|
+
EnforcedStyle: require_no_parentheses_except_multiline
|
|
317
|
+
|
|
318
|
+
Style/MinMaxComparison:
|
|
319
|
+
Enabled: true
|
|
320
|
+
|
|
321
|
+
Style/MultilineBlockChain:
|
|
322
|
+
Enabled: false
|
|
323
|
+
|
|
324
|
+
Style/MultilineInPatternThen:
|
|
325
|
+
Enabled: true
|
|
326
|
+
|
|
327
|
+
Style/NegatedIfElseCondition:
|
|
328
|
+
Enabled: true
|
|
329
|
+
|
|
330
|
+
Style/NestedFileDirname:
|
|
331
|
+
Enabled: true
|
|
332
|
+
|
|
333
|
+
Style/NestedParenthesizedCalls:
|
|
334
|
+
Enabled: false
|
|
335
|
+
|
|
336
|
+
Style/NilLambda:
|
|
337
|
+
Enabled: true
|
|
338
|
+
|
|
339
|
+
Style/NonNilCheck:
|
|
340
|
+
Enabled: false
|
|
341
|
+
|
|
342
|
+
Style/NumberedParameters:
|
|
343
|
+
Enabled: true
|
|
344
|
+
EnforcedStyle: disallow
|
|
345
|
+
|
|
346
|
+
Style/NumberedParametersLimit:
|
|
347
|
+
Enabled: true
|
|
348
|
+
|
|
349
|
+
Style/ObjectThen:
|
|
350
|
+
Enabled: true
|
|
351
|
+
|
|
352
|
+
Style/OpenStructUse:
|
|
353
|
+
Enabled: false
|
|
354
|
+
|
|
355
|
+
Style/OperatorMethodCall:
|
|
356
|
+
Enabled: true
|
|
357
|
+
|
|
358
|
+
Style/OptionHash:
|
|
359
|
+
Enabled: true
|
|
360
|
+
|
|
361
|
+
Style/QuotedSymbols:
|
|
362
|
+
Enabled: true
|
|
363
|
+
|
|
364
|
+
Style/RedundantArgument:
|
|
365
|
+
Enabled: false
|
|
366
|
+
|
|
367
|
+
Style/RedundantConstantBase:
|
|
368
|
+
Enabled: false
|
|
369
|
+
|
|
370
|
+
Style/RedundantDoubleSplatHashBraces:
|
|
371
|
+
Enabled: true
|
|
372
|
+
|
|
373
|
+
Style/RedundantEach:
|
|
374
|
+
Enabled: true
|
|
375
|
+
|
|
376
|
+
Style/RedundantException:
|
|
377
|
+
Enabled: false
|
|
378
|
+
|
|
379
|
+
Style/RedundantHeredocDelimiterQuotes:
|
|
380
|
+
Enabled: true
|
|
381
|
+
|
|
382
|
+
Style/RedundantInitialize:
|
|
383
|
+
Enabled: true
|
|
384
|
+
|
|
385
|
+
Style/RedundantParentheses:
|
|
386
|
+
Enabled: false
|
|
387
|
+
|
|
388
|
+
Style/RedundantSelfAssignmentBranch:
|
|
389
|
+
Enabled: true
|
|
390
|
+
|
|
391
|
+
Style/RedundantStringEscape:
|
|
392
|
+
Enabled: true
|
|
393
|
+
|
|
394
|
+
Style/RegexpLiteral:
|
|
395
|
+
EnforcedStyle: percent_r
|
|
396
|
+
|
|
397
|
+
Style/ReturnNil:
|
|
398
|
+
Enabled: true
|
|
399
|
+
|
|
400
|
+
Style/SelectByRegexp:
|
|
401
|
+
Enabled: true
|
|
402
|
+
|
|
403
|
+
Style/SingleLineMethods:
|
|
404
|
+
AllowIfMethodIsEmpty: false
|
|
405
|
+
|
|
406
|
+
Style/StaticClass:
|
|
407
|
+
Enabled: true
|
|
408
|
+
|
|
409
|
+
Style/StringChars:
|
|
410
|
+
Enabled: true
|
|
411
|
+
|
|
412
|
+
Style/StringHashKeys:
|
|
413
|
+
Enabled: false
|
|
414
|
+
|
|
415
|
+
Style/SwapValues:
|
|
416
|
+
Enabled: true
|
|
417
|
+
|
|
418
|
+
Style/SymbolArray:
|
|
419
|
+
EnforcedStyle: brackets
|
|
420
|
+
|
|
421
|
+
Style/TernaryParentheses:
|
|
422
|
+
EnforcedStyle: require_parentheses_when_complex
|
|
423
|
+
AllowSafeAssignment: false
|
|
424
|
+
|
|
425
|
+
Style/TopLevelMethodDefinition:
|
|
426
|
+
Enabled: true
|
|
427
|
+
|
|
428
|
+
Style/TrailingCommaInArguments:
|
|
429
|
+
Enabled: true
|
|
430
|
+
EnforcedStyleForMultiline: no_comma
|
|
431
|
+
|
|
432
|
+
Style/TrailingCommaInArrayLiteral:
|
|
433
|
+
Enabled: true
|
|
434
|
+
EnforcedStyleForMultiline: no_comma
|
|
435
|
+
|
|
436
|
+
Style/TrailingCommaInBlockArgs:
|
|
437
|
+
Enabled: true
|
|
438
|
+
|
|
439
|
+
Style/TrailingCommaInHashLiteral:
|
|
440
|
+
Enabled: true
|
|
441
|
+
EnforcedStyleForMultiline: no_comma
|
|
442
|
+
|
|
443
|
+
Style/UnlessLogicalOperators:
|
|
444
|
+
EnforcedStyle: forbid_logical_operators
|
|
445
|
+
|
|
446
|
+
Style/WordArray:
|
|
447
|
+
EnforcedStyle: brackets
|
|
448
|
+
|
|
449
|
+
Style/YodaCondition:
|
|
450
|
+
Enabled: true
|
|
451
|
+
EnforcedStyle: forbid_for_all_comparison_operators
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
<!--[//]: # (
|
|
4
|
+
## <Release number> <Date YYYY-MM-DD>
|
|
5
|
+
### Breaking changes
|
|
6
|
+
### Deprecations
|
|
7
|
+
### New features
|
|
8
|
+
### Bug fixes
|
|
9
|
+
)-->
|
|
10
|
+
|
|
11
|
+
## 1.0.0 2025-01-24
|
|
12
|
+
|
|
13
|
+
First release. Refer to [README.md](README.md) for the full documentation.
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Moku S.r.l., Riccardo Agatea
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Hidden Hooks
|
|
2
|
+
|
|
3
|
+
A way to defer hooks to reduce dependencies.
|
|
4
|
+
|
|
5
|
+
Sometimes we need callbacks that break dependencies. This gem allows to invert those dependencies.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'hidden_hooks', '~> 1.0'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
$ bundle
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or you can install the gem on its own:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
gem install hidden_hooks
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Raison d'être
|
|
30
|
+
|
|
31
|
+
Let's say we have a `User` model. Let's then say we integrate our app with a third-party issue tracker, and we need to mirror a user's issues in our app, so we create a `IssueTracker::Issue` model. In Rails we could do something like this:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
# app/models/user.rb
|
|
35
|
+
class User < ApplicationRecord
|
|
36
|
+
has_many :issues,
|
|
37
|
+
class_name: 'IssueTracker::Issue',
|
|
38
|
+
dependent: :destroy
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# app/models/issue_tracker/issue.rb
|
|
42
|
+
module IssueTracker
|
|
43
|
+
class Issue < ApplicationRecord
|
|
44
|
+
belongs_to :user
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This is fine for a small application, but becomes worrisome when the application grows and we start to need to track dependencies. Very clearly, the `User` model, which is a core part of the business model, should not depend on a third-party integration, but if we remove the association outright we lose the `dependent: :destroy` and the callback that it comes with.
|
|
50
|
+
|
|
51
|
+
We could do something like this:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
# app/models/user.rb
|
|
55
|
+
class User < ApplicationRecord
|
|
56
|
+
# Nothing here
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# app/models/issue_tracker/issue.rb
|
|
60
|
+
module IssueTracker
|
|
61
|
+
class Issue < ApplicationRecord
|
|
62
|
+
belongs_to :user
|
|
63
|
+
User.has_many :issues,
|
|
64
|
+
class_name: 'IssueTracker::Issue',
|
|
65
|
+
dependent: :destroy
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This is just hiding the association, but it's still there; we can still do something like `user.issues`. A slightly better solution is to forego the association, and only keep the callback:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
# app/models/user.rb
|
|
74
|
+
class User < ApplicationRecord
|
|
75
|
+
# Still nothing here
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# app/models/issue_tracker/issue.rb
|
|
79
|
+
module IssueTracker
|
|
80
|
+
class Issue < ApplicationRecord
|
|
81
|
+
belongs_to :user
|
|
82
|
+
User.before_destroy do
|
|
83
|
+
Issue.where(user: self).find_each(&:destroy!)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This solves the dependency issue, but introduces a new one: looking at the `User` model, there's no trace of the callback, so for example we might assume that `user.destroy!` will never fail, just to be surprised by a completely unexpected `ActiveRecord::RecordNotDestroyed`.
|
|
90
|
+
|
|
91
|
+
What we need is a way for `User` to declare that it expects others to define callbacks, while remaining ignorant about what those callbacks do. This would be an application of the Dependency Inversion Principle: `User` defines an interface, and others use that interface without having to really touch `User`.
|
|
92
|
+
|
|
93
|
+
### `HiddenHooks`
|
|
94
|
+
|
|
95
|
+
Hidden Hooks provides a unified interface for this specific dependency inversion: in the example above, the models would be defined like this:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
# app/models/user.rb
|
|
99
|
+
class User < ApplicationRecord
|
|
100
|
+
before_destroy do
|
|
101
|
+
HiddenHooks[User].before_destroy self
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# app/models/issue_tracker/issue.rb
|
|
106
|
+
module IssueTracker
|
|
107
|
+
class Issue < ApplicationRecord
|
|
108
|
+
belongs_to :user
|
|
109
|
+
HiddenHooks.hook_up do
|
|
110
|
+
before_destroy User do |user|
|
|
111
|
+
Issue.where(user: user).find_each(&:destroy!)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Interface Declaration
|
|
119
|
+
|
|
120
|
+
A class `C` declares the interface through `HiddenHooks[C]`. Calling a method on the returned proxy will call every hook that someone else defined, forwarding any argument.
|
|
121
|
+
|
|
122
|
+
#### Hook Definition
|
|
123
|
+
|
|
124
|
+
Whenever you want to define a hook, you simply call `HiddenHooks.hook_up`. Inside the block, you can call any method and pass it a class and a block: the block will become a hook for that class.
|
|
125
|
+
|
|
126
|
+
#### Rails Callbacks
|
|
127
|
+
|
|
128
|
+
Thanks to the [callback objects](https://guides.rubyonrails.org/active_record_callbacks.html#callback-objects) system, in Rails you can simply pass the proxy to the callback methods:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
class User < ApplicationRecord
|
|
132
|
+
before_destroy HiddenHooks[User]
|
|
133
|
+
before_create HiddenHooks[User]
|
|
134
|
+
...
|
|
135
|
+
end
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
To implicitly set all hooks like this for a given model, you can include `HiddenHooks::ActiveRecord`. If you want this to work for all models, include it in your `ApplicationRecord`.
|
|
139
|
+
|
|
140
|
+
### Eager Loading
|
|
141
|
+
|
|
142
|
+
The hooks you set up in a file can only work if that file is loaded, of course. In a Rails application, by default the development environment is not eager loaded, so you will probably see only certain hooks. To make Hidden Hooks work properly, enable eager loading in the `config/environments/development.rb` configuration file.
|
|
143
|
+
|
|
144
|
+
### Multithreading
|
|
145
|
+
|
|
146
|
+
Hidden Hooks doesn't do anything to protect against concurrency fumbles. Its `hook_up` method is meant to be used "during class definition", not inside "runtime logic", for as much as these terms can mean in Ruby. For example, using `hook_up` inside an instance method is a recipe for fast disaster.
|
|
147
|
+
|
|
148
|
+
## Version numbers
|
|
149
|
+
|
|
150
|
+
Hidden Hooks loosely follows [Semantic Versioning](https://semver.org/), with a hard guarantee that breaking changes to the public API will always coincide with an increase to the `MAJOR` number.
|
|
151
|
+
|
|
152
|
+
Version numbers are in three parts: `MAJOR.MINOR.PATCH`.
|
|
153
|
+
|
|
154
|
+
- Breaking changes to the public API increment the `MAJOR`. There may also be changes that would otherwise increase the `MINOR` or the `PATCH`.
|
|
155
|
+
- Additions, deprecations, and "big" non breaking changes to the public API increment the `MINOR`. There may also be changes that would otherwise increase the `PATCH`.
|
|
156
|
+
- Bug fixes and "small" non breaking changes to the public API increment the `PATCH`.
|
|
157
|
+
|
|
158
|
+
Notice that any feature deprecated by a minor release can be expected to be removed by the next major release.
|
|
159
|
+
|
|
160
|
+
## Changelog
|
|
161
|
+
|
|
162
|
+
Full list of changes in [CHANGELOG.md](CHANGELOG.md)
|
|
163
|
+
|
|
164
|
+
## Contributing
|
|
165
|
+
|
|
166
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/moku-io/hidden_hooks.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require_relative 'lib/hidden_hooks/version'
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = 'hidden_hooks'
|
|
5
|
+
spec.version = HiddenHooks::VERSION
|
|
6
|
+
spec.authors = ['Moku S.r.l.', 'Riccardo Agatea']
|
|
7
|
+
spec.email = ['info@moku.io']
|
|
8
|
+
spec.license = 'MIT'
|
|
9
|
+
|
|
10
|
+
spec.summary = 'A way to defer hooks to reduce dependencies.'
|
|
11
|
+
spec.description = 'Sometimes we need callbacks that break dependencies. This gem allows to invert those ' \
|
|
12
|
+
'dependencies.'
|
|
13
|
+
spec.homepage = 'https://github.com/moku-io/hidden_hooks'
|
|
14
|
+
spec.required_ruby_version = '>= 3.0.0'
|
|
15
|
+
|
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
17
|
+
spec.metadata['source_code_uri'] = 'https://github.com/moku-io/hidden_hooks'
|
|
18
|
+
spec.metadata['changelog_uri'] = 'https://github.com/moku-io/hidden_hooks/blob/master/CHANGELOG.md'
|
|
19
|
+
|
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
22
|
+
spec.files = Dir.chdir __dir__ do
|
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
24
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
spec.bindir = 'exe'
|
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
29
|
+
spec.require_paths = ['lib']
|
|
30
|
+
|
|
31
|
+
spec.add_dependency 'activesupport'
|
|
32
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
|
|
3
|
+
module HiddenHooks
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
[
|
|
9
|
+
:after_create,
|
|
10
|
+
:after_destroy,
|
|
11
|
+
:after_find,
|
|
12
|
+
:after_initialize,
|
|
13
|
+
:after_save,
|
|
14
|
+
:after_touch,
|
|
15
|
+
:after_update,
|
|
16
|
+
:after_validation,
|
|
17
|
+
:before_create,
|
|
18
|
+
:before_destroy,
|
|
19
|
+
:before_save,
|
|
20
|
+
:before_update,
|
|
21
|
+
:before_validation
|
|
22
|
+
].each do |callback|
|
|
23
|
+
send callback, HiddenHooks[self], prepend: true
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/hidden_hooks.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require_relative 'hidden_hooks/version'
|
|
2
|
+
|
|
3
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
|
4
|
+
|
|
5
|
+
module HiddenHooks
|
|
6
|
+
mattr_reader :hooks,
|
|
7
|
+
default: Hash.new { |h1, k1| h1[k1] = Hash.new { |h2, k2| h2[k2] = [] } },
|
|
8
|
+
instance_accessor: false,
|
|
9
|
+
instance_reader: false
|
|
10
|
+
|
|
11
|
+
class SetUpProxy
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def method_missing hook, klass, &block
|
|
15
|
+
::HiddenHooks.hooks[klass][hook] << block
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def respond_to_missing? _, _=false
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class LookUpProxy
|
|
24
|
+
def initialize klass
|
|
25
|
+
@hooks = ::HiddenHooks.hooks[klass]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def method_missing(hook, *args, **kwargs, &block)
|
|
31
|
+
@hooks[hook].each { _1.call(*args, **kwargs, &block) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def respond_to_missing? _, _=false
|
|
35
|
+
true
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.hook_up(&block)
|
|
40
|
+
SetUpProxy.new.instance_exec(&block)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.[] klass
|
|
44
|
+
LookUpProxy.new klass
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
require_relative 'hidden_hooks/active_record'
|
metadata
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hidden_hooks
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Moku S.r.l.
|
|
8
|
+
- Riccardo Agatea
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: exe
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2025-01-24 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: activesupport
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - ">="
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: '0'
|
|
21
|
+
type: :runtime
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - ">="
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: '0'
|
|
28
|
+
description: Sometimes we need callbacks that break dependencies. This gem allows
|
|
29
|
+
to invert those dependencies.
|
|
30
|
+
email:
|
|
31
|
+
- info@moku.io
|
|
32
|
+
executables: []
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- ".rubocop.yml"
|
|
37
|
+
- CHANGELOG.md
|
|
38
|
+
- Gemfile
|
|
39
|
+
- LICENSE
|
|
40
|
+
- README.md
|
|
41
|
+
- Rakefile
|
|
42
|
+
- hidden_hooks.gemspec
|
|
43
|
+
- lib/hidden_hooks.rb
|
|
44
|
+
- lib/hidden_hooks/active_record.rb
|
|
45
|
+
- lib/hidden_hooks/version.rb
|
|
46
|
+
homepage: https://github.com/moku-io/hidden_hooks
|
|
47
|
+
licenses:
|
|
48
|
+
- MIT
|
|
49
|
+
metadata:
|
|
50
|
+
homepage_uri: https://github.com/moku-io/hidden_hooks
|
|
51
|
+
source_code_uri: https://github.com/moku-io/hidden_hooks
|
|
52
|
+
changelog_uri: https://github.com/moku-io/hidden_hooks/blob/master/CHANGELOG.md
|
|
53
|
+
post_install_message:
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 3.0.0
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 3.5.22
|
|
69
|
+
signing_key:
|
|
70
|
+
specification_version: 4
|
|
71
|
+
summary: A way to defer hooks to reduce dependencies.
|
|
72
|
+
test_files: []
|