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 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
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in therefore.gemspec
4
+ gemspec
5
+
6
+ gem 'rake', '~> 13.0'
7
+
8
+ gem 'rubocop', '~> 1.21'
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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ require 'rubocop/rake_task'
6
+
7
+ RuboCop::RakeTask.new
8
+
9
+ task default: [:spec, :rubocop]
@@ -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
@@ -0,0 +1,3 @@
1
+ module HiddenHooks
2
+ VERSION = '1.0.0'
3
+ end
@@ -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: []