hidden_hooks 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []