after_attached 0.1.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/.rspec +3 -0
- data/.rubocop.yml +35 -0
- data/CHANGELOG.md +18 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/README.md +180 -0
- data/Rakefile +12 -0
- data/lib/after_attached/attachment_callbacks.rb +88 -0
- data/lib/after_attached/railtie.rb +72 -0
- data/lib/after_attached/version.rb +5 -0
- data/lib/after_attached.rb +12 -0
- data/log/development.log +0 -0
- data/log/test.log +2565 -0
- data/sig/after_attached.rbs +4 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d86dd83ab47f5cdc21e98eccb6e0e31d601cae0c808be8860496cad87729fa6d
|
4
|
+
data.tar.gz: b33242818b08a4dc54fe5b7271421f24f7a5f5e3e2a1127de3036eaadbf532cc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ec32489dd3032e32e6f0d2d7c676ea758dbcd0a54eea6dc203930f3a74349961e3f9ae0c5f17ee01f5a30ab0beea3ee2f9dd487cd1147851635876261edeb3f8
|
7
|
+
data.tar.gz: 97edac8538345e742d81397d58ca2304618487ed9926baa598b5c0a64e12cf28c2877eca5f26fdcbd26d8a636b5c1cd7d90e3b3d19821d4496c16994701d0dd7
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.0
|
3
|
+
NewCops: enable
|
4
|
+
SuggestExtensions: false
|
5
|
+
|
6
|
+
Style/StringLiterals:
|
7
|
+
EnforcedStyle: double_quotes
|
8
|
+
|
9
|
+
Style/StringLiteralsInInterpolation:
|
10
|
+
EnforcedStyle: double_quotes
|
11
|
+
|
12
|
+
Layout/LineLength:
|
13
|
+
Max: 120
|
14
|
+
|
15
|
+
Metrics/BlockLength:
|
16
|
+
Exclude:
|
17
|
+
- 'spec/**/*'
|
18
|
+
- '*.gemspec'
|
19
|
+
- 'lib/after_attached/railtie.rb'
|
20
|
+
|
21
|
+
Style/Documentation:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
# Allow array style in schema definitions
|
25
|
+
Layout/SpaceInsideArrayLiteralBrackets:
|
26
|
+
Exclude:
|
27
|
+
- 'spec/spec_helper.rb'
|
28
|
+
|
29
|
+
Style/SymbolArray:
|
30
|
+
Exclude:
|
31
|
+
- 'spec/spec_helper.rb'
|
32
|
+
|
33
|
+
Lint/ConstantDefinitionInBlock:
|
34
|
+
Exclude:
|
35
|
+
- 'spec/**/*'
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
## [Unreleased]
|
2
|
+
|
3
|
+
## [0.1.0] - 2025-07-31
|
4
|
+
|
5
|
+
### Added
|
6
|
+
- Initial release of after_attached gem
|
7
|
+
- Complete set of attachment lifecycle callbacks:
|
8
|
+
- `before_attached` - runs before an attachment is saved
|
9
|
+
- `after_attached` - runs after an attachment is committed to the database
|
10
|
+
- `before_detached` - runs before an attachment is destroyed
|
11
|
+
- `after_detached` - runs after an attachment is destroyed and committed
|
12
|
+
- Support for method symbols and blocks as callbacks
|
13
|
+
- Support for multiple callbacks per attachment
|
14
|
+
- Support for both `has_one_attached` and `has_many_attached` associations
|
15
|
+
- Automatic inclusion in all Active Record models via Railtie
|
16
|
+
- Guards to prevent duplicate callback execution
|
17
|
+
- Full test coverage with RSpec
|
18
|
+
- CI/CD setup with GitHub Actions testing against Rails 6.1, 7.0, 7.1, and 8.0
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
9
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
10
|
+
identity and orientation.
|
11
|
+
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
13
|
+
diverse, inclusive, and healthy community.
|
14
|
+
|
15
|
+
## Our Standards
|
16
|
+
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
18
|
+
community include:
|
19
|
+
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
23
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
24
|
+
and learning from the experience
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the overall
|
26
|
+
community
|
27
|
+
|
28
|
+
Examples of unacceptable behavior include:
|
29
|
+
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or advances of
|
31
|
+
any kind
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
33
|
+
* Public or private harassment
|
34
|
+
* Publishing others' private information, such as a physical or email address,
|
35
|
+
without their explicit permission
|
36
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
37
|
+
professional setting
|
38
|
+
|
39
|
+
## Enforcement Responsibilities
|
40
|
+
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
44
|
+
or harmful.
|
45
|
+
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
49
|
+
decisions when appropriate.
|
50
|
+
|
51
|
+
## Scope
|
52
|
+
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
54
|
+
an individual is officially representing the community in public spaces.
|
55
|
+
Examples of representing our community include using an official email address,
|
56
|
+
posting via an official social media account, or acting as an appointed
|
57
|
+
representative at an online or offline event.
|
58
|
+
|
59
|
+
## Enforcement
|
60
|
+
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
62
|
+
reported to the community leaders responsible for enforcement at
|
63
|
+
[INSERT CONTACT METHOD].
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
65
|
+
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
67
|
+
reporter of any incident.
|
68
|
+
|
69
|
+
## Enforcement Guidelines
|
70
|
+
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
73
|
+
|
74
|
+
### 1. Correction
|
75
|
+
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
77
|
+
unprofessional or unwelcome in the community.
|
78
|
+
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
82
|
+
|
83
|
+
### 2. Warning
|
84
|
+
|
85
|
+
**Community Impact**: A violation through a single incident or series of
|
86
|
+
actions.
|
87
|
+
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
92
|
+
like social media. Violating these terms may lead to a temporary or permanent
|
93
|
+
ban.
|
94
|
+
|
95
|
+
### 3. Temporary Ban
|
96
|
+
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
98
|
+
sustained inappropriate behavior.
|
99
|
+
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
101
|
+
communication with the community for a specified period of time. No public or
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
104
|
+
Violating these terms may lead to a permanent ban.
|
105
|
+
|
106
|
+
### 4. Permanent Ban
|
107
|
+
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
111
|
+
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within the
|
113
|
+
community.
|
114
|
+
|
115
|
+
## Attribution
|
116
|
+
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
118
|
+
version 2.1, available at
|
119
|
+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
120
|
+
|
121
|
+
Community Impact Guidelines were inspired by
|
122
|
+
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
123
|
+
|
124
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
125
|
+
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
126
|
+
[https://www.contributor-covenant.org/translations][translations].
|
127
|
+
|
128
|
+
[homepage]: https://www.contributor-covenant.org
|
129
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
130
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
131
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
132
|
+
[translations]: https://www.contributor-covenant.org/translations
|
data/README.md
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# AfterAttached
|
2
|
+
|
3
|
+
Provides comprehensive attachment lifecycle callbacks for Rails models with Active Storage attachments. Run code before and after attachments are created or destroyed.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'after_attached'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle install
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
The gem automatically includes the `AttachmentCallbacks` concern in all Active Record models, providing four callbacks for attachment lifecycle events:
|
20
|
+
|
21
|
+
- `before_attached` - runs before an attachment is saved
|
22
|
+
- `after_attached` - runs after an attachment is committed to the database
|
23
|
+
- `before_detached` - runs before an attachment is destroyed
|
24
|
+
- `after_detached` - runs after an attachment is destroyed and committed
|
25
|
+
|
26
|
+
### Basic Usage
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
class User < ApplicationRecord
|
30
|
+
has_one_attached :avatar
|
31
|
+
|
32
|
+
# Using a method
|
33
|
+
after_attached :avatar, :process_avatar
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def process_avatar(attachment)
|
38
|
+
# This will run once when avatar is attached
|
39
|
+
AvatarProcessingJob.perform_later(self, attachment)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
You can also use a block:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class Document < ApplicationRecord
|
48
|
+
has_one_attached :file
|
49
|
+
|
50
|
+
after_attached :file do |attachment|
|
51
|
+
# Access attachment properties
|
52
|
+
Rails.logger.info "Attached file: #{attachment.filename}"
|
53
|
+
Rails.logger.info "Content type: #{attachment.content_type}"
|
54
|
+
Rails.logger.info "File size: #{attachment.byte_size}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
### Multiple Callbacks
|
60
|
+
|
61
|
+
You can define multiple callbacks for the same attachment:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
class Photo < ApplicationRecord
|
65
|
+
has_one_attached :image
|
66
|
+
|
67
|
+
after_attached :image, :generate_thumbnail
|
68
|
+
after_attached :image, :extract_metadata
|
69
|
+
after_attached :image do |attachment|
|
70
|
+
notify_admin("New photo uploaded: #{attachment.filename}")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
### Works with has_many_attached
|
76
|
+
|
77
|
+
The callbacks fire for each attachment when using `has_many_attached`:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class Gallery < ApplicationRecord
|
81
|
+
has_many_attached :photos
|
82
|
+
|
83
|
+
after_attached :photos do |attachment|
|
84
|
+
# This runs for each photo attached
|
85
|
+
ProcessPhotoJob.perform_later(attachment)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Attaching multiple files
|
90
|
+
gallery.photos.attach([photo1, photo2]) # Callback fires twice, once for each photo
|
91
|
+
|
92
|
+
# Adding more photos later
|
93
|
+
gallery.photos.attach(photo3) # Callback fires once for the new photo
|
94
|
+
```
|
95
|
+
|
96
|
+
### All Available Callbacks
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
class Document < ApplicationRecord
|
100
|
+
has_one_attached :file
|
101
|
+
|
102
|
+
# Called before the attachment record is created
|
103
|
+
before_attached :file do |attachment|
|
104
|
+
Rails.logger.info "About to attach: #{attachment.filename}"
|
105
|
+
end
|
106
|
+
|
107
|
+
# Called after the attachment record is committed
|
108
|
+
after_attached :file do |attachment|
|
109
|
+
ProcessingJob.perform_later(attachment)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Called before the attachment record is destroyed
|
113
|
+
before_detached :file do |attachment|
|
114
|
+
Rails.logger.info "About to detach: #{attachment.filename}"
|
115
|
+
end
|
116
|
+
|
117
|
+
# Called after the attachment record is destroyed and committed
|
118
|
+
after_detached :file do |attachment|
|
119
|
+
CleanupJob.perform_later(record_id: id, filename: attachment.filename.to_s)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
### Replacing Attachments
|
125
|
+
|
126
|
+
When replacing a `has_one_attached` attachment, both detachment callbacks (for the old attachment) and attachment callbacks (for the new one) fire:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
user.avatar.attach(first_file) # before_attached, after_attached fire
|
130
|
+
user.avatar.attach(second_file) # before_detached, after_detached fire for first_file
|
131
|
+
# then before_attached, after_attached fire for second_file
|
132
|
+
```
|
133
|
+
|
134
|
+
### Removing Attachments
|
135
|
+
|
136
|
+
To trigger detachment callbacks, you need to destroy the attachment record:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
# This triggers before_detached and after_detached callbacks
|
140
|
+
document.file.attachment.destroy!
|
141
|
+
|
142
|
+
# Note: purge and purge_later bypass the destroy callbacks
|
143
|
+
# If you need the callbacks to fire, use destroy! instead
|
144
|
+
```
|
145
|
+
|
146
|
+
## How It Works
|
147
|
+
|
148
|
+
Under the hood, the gem:
|
149
|
+
1. Includes an `AttachmentCallbacks` concern in all Active Record models via a Rails initializer
|
150
|
+
2. Patches ActiveStorage::Attachment with ActiveRecord callbacks:
|
151
|
+
- `before_create` → triggers `before_attached`
|
152
|
+
- `after_create_commit` → triggers `after_attached`
|
153
|
+
- `before_destroy` → triggers `before_detached`
|
154
|
+
- `after_destroy_commit` → triggers `after_detached`
|
155
|
+
3. Maintains a registry of callbacks per model and attachment name using a `class_attribute`
|
156
|
+
|
157
|
+
No blob ID tracking or complex bookkeeping - just simple, reliable callbacks that fire at the appropriate points in the attachment lifecycle. Since the callbacks are triggered at the `ActiveStorage::Attachment` level, they work seamlessly with both `has_one_attached` and `has_many_attached` associations.
|
158
|
+
|
159
|
+
## Compatibility
|
160
|
+
|
161
|
+
- Rails 6.0+
|
162
|
+
- Ruby 3.0+
|
163
|
+
|
164
|
+
## Development
|
165
|
+
|
166
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
167
|
+
|
168
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
169
|
+
|
170
|
+
## Contributing
|
171
|
+
|
172
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/obiefernandez/after_attached. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/obiefernandez/after_attached/blob/main/CODE_OF_CONDUCT.md).
|
173
|
+
|
174
|
+
## License
|
175
|
+
|
176
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
177
|
+
|
178
|
+
## Code of Conduct
|
179
|
+
|
180
|
+
Everyone interacting in the AfterAttached project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/obiefernandez/after_attached/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AfterAttached
|
4
|
+
module AttachmentCallbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :_attachment_callbacks, default: {
|
9
|
+
before_attached: {},
|
10
|
+
after_attached: {},
|
11
|
+
before_detached: {},
|
12
|
+
after_detached: {}
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
class_methods do
|
17
|
+
def before_attached(attachment_name, method_name = nil, &block)
|
18
|
+
register_attachment_callback(:before_attached, attachment_name, method_name, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def after_attached(attachment_name, method_name = nil, &block)
|
22
|
+
register_attachment_callback(:after_attached, attachment_name, method_name, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def before_detached(attachment_name, method_name = nil, &block)
|
26
|
+
register_attachment_callback(:before_detached, attachment_name, method_name, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def after_detached(attachment_name, method_name = nil, &block)
|
30
|
+
register_attachment_callback(:after_detached, attachment_name, method_name, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def register_attachment_callback(callback_type, attachment_name, method_name = nil, &block)
|
36
|
+
raise ArgumentError, "Must provide either a method name or a block" unless method_name || block
|
37
|
+
|
38
|
+
callback = method_name || block
|
39
|
+
attachment_name = attachment_name.to_s
|
40
|
+
|
41
|
+
# Deep dup the callbacks hash to avoid mutation issues
|
42
|
+
new_callbacks = _attachment_callbacks.deep_dup
|
43
|
+
new_callbacks[callback_type][attachment_name] ||= []
|
44
|
+
new_callbacks[callback_type][attachment_name] << callback
|
45
|
+
|
46
|
+
self._attachment_callbacks = new_callbacks
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def _run_attachment_callbacks(callback_type, attachment)
|
51
|
+
callbacks = self.class._attachment_callbacks[callback_type][attachment.name]
|
52
|
+
return unless callbacks
|
53
|
+
|
54
|
+
callbacks.each do |cb|
|
55
|
+
invoke_callback(cb, attachment)
|
56
|
+
end
|
57
|
+
rescue StandardError => e
|
58
|
+
Rails.logger.error("#{callback_type} callback failed on #{self.class}##{attachment.name}: #{e.message}")
|
59
|
+
raise
|
60
|
+
end
|
61
|
+
|
62
|
+
# Convenience methods for backward compatibility and clarity
|
63
|
+
def _run_before_attached_callbacks(attachment)
|
64
|
+
_run_attachment_callbacks(:before_attached, attachment)
|
65
|
+
end
|
66
|
+
|
67
|
+
def _run_after_attached_callbacks(attachment)
|
68
|
+
_run_attachment_callbacks(:after_attached, attachment)
|
69
|
+
end
|
70
|
+
|
71
|
+
def _run_before_detached_callbacks(attachment)
|
72
|
+
_run_attachment_callbacks(:before_detached, attachment)
|
73
|
+
end
|
74
|
+
|
75
|
+
def _run_after_detached_callbacks(attachment)
|
76
|
+
_run_attachment_callbacks(:after_detached, attachment)
|
77
|
+
end
|
78
|
+
|
79
|
+
def invoke_callback(callback, attachment)
|
80
|
+
case callback
|
81
|
+
when Symbol, String
|
82
|
+
send(callback, attachment)
|
83
|
+
else
|
84
|
+
instance_exec(attachment, &callback)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AfterAttached
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer "after_attached.setup_callbacks" do
|
6
|
+
ActiveSupport.on_load(:active_record) do
|
7
|
+
include AfterAttached::AttachmentCallbacks
|
8
|
+
end
|
9
|
+
|
10
|
+
ActiveSupport.on_load(:active_storage_attachment) do
|
11
|
+
# Before attached - runs before the attachment is saved
|
12
|
+
before_create do
|
13
|
+
record = self.record
|
14
|
+
# Guard against multiple calls
|
15
|
+
@_before_attached_run ||= false
|
16
|
+
unless @_before_attached_run
|
17
|
+
@_before_attached_run = true
|
18
|
+
if record && record.class.respond_to?(:_attachment_callbacks) &&
|
19
|
+
record.respond_to?(:_run_before_attached_callbacks)
|
20
|
+
record._run_before_attached_callbacks(self)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# After attached - runs after the attachment is committed
|
26
|
+
after_create_commit do
|
27
|
+
record = self.record
|
28
|
+
|
29
|
+
# Guard against multiple calls
|
30
|
+
@_after_attached_run ||= false
|
31
|
+
unless @_after_attached_run
|
32
|
+
@_after_attached_run = true
|
33
|
+
if record && record.class.respond_to?(:_attachment_callbacks) &&
|
34
|
+
record.respond_to?(:_run_after_attached_callbacks)
|
35
|
+
record._run_after_attached_callbacks(self)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Before detached - runs before the attachment is destroyed
|
41
|
+
before_destroy do
|
42
|
+
record = self.record
|
43
|
+
# Guard against multiple calls
|
44
|
+
@_before_detached_run ||= false
|
45
|
+
unless @_before_detached_run
|
46
|
+
@_before_detached_run = true
|
47
|
+
if record && record.class.respond_to?(:_attachment_callbacks) &&
|
48
|
+
record.respond_to?(:_run_before_detached_callbacks)
|
49
|
+
record._run_before_detached_callbacks(self)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# After detached - runs after the attachment is destroyed and committed
|
55
|
+
after_destroy_commit do
|
56
|
+
# Need to get the record before destruction
|
57
|
+
record = self.record
|
58
|
+
|
59
|
+
# Guard against multiple calls
|
60
|
+
@_after_detached_run ||= false
|
61
|
+
unless @_after_detached_run
|
62
|
+
@_after_detached_run = true
|
63
|
+
if record && record.class.respond_to?(:_attachment_callbacks) &&
|
64
|
+
record.respond_to?(:_run_after_detached_callbacks)
|
65
|
+
record._run_after_detached_callbacks(self)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/concern"
|
5
|
+
|
6
|
+
require_relative "after_attached/version"
|
7
|
+
require_relative "after_attached/attachment_callbacks"
|
8
|
+
require_relative "after_attached/railtie" if defined?(Rails::Railtie)
|
9
|
+
|
10
|
+
module AfterAttached
|
11
|
+
class Error < StandardError; end
|
12
|
+
end
|
data/log/development.log
ADDED
File without changes
|