active_shrine 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +93 -17
- data/lib/active_shrine/attached/changes/create_many.rb +4 -3
- data/lib/active_shrine/attached/changes/create_one.rb +4 -3
- data/lib/active_shrine/attachment.rb +2 -3
- data/lib/active_shrine/model.rb +103 -24
- data/lib/active_shrine/version.rb +1 -1
- metadata +3 -5
- data/.DS_Store +0 -0
- data/lib/.DS_Store +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 756c7f37f8450e532d53547e256301bdb3368e6ce8b27248566a051d7a0ab880
|
4
|
+
data.tar.gz: 8d089b85b9a0d4ff6b80b5088f45a35daa2fc3218897a6b02938e507210f10e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b9bfe585f6ded80b0dfa17626eec1a74c6d2fc82e67b519dd0363df5baa6199681e814ca80a9240c18c9b2229aa775efecacf35bb86ba773887f049f8dd68ec
|
7
|
+
data.tar.gz: 351afd5f4ab728e5313e3d57a4691b319ad5ecae8c21cc36b98cc2ccd0806ac13a00fc90545bc98617452bd96a69e183e991ae40b72db2ef8cb27ea62e080cae
|
data/README.md
CHANGED
@@ -1,51 +1,127 @@
|
|
1
1
|
# ActiveShrine
|
2
2
|
|
3
|
-
|
3
|
+
ActiveShrine integrates Shrine file attachments with Active Record models using a familiar API inspired by Active Storage. It provides a simple, flexible way to manage file uploads in your Rails applications while leveraging Shrine's powerful features.
|
4
4
|
|
5
|
-
|
5
|
+
## Features
|
6
6
|
|
7
|
-
|
7
|
+
- **Active Storage-like API**: Familiar `has_one_attached` and `has_many_attached` interface
|
8
|
+
- **Customizable Uploaders**: Use custom Shrine uploaders with validation, processing, and more
|
9
|
+
- **Polymorphic Associations**: Attachments are stored using polymorphic associations
|
10
|
+
- **Eager Loading Support**: Prevent N+1 queries with `with_attached_*` scopes
|
8
11
|
|
9
|
-
|
12
|
+
## Installation
|
10
13
|
|
11
|
-
|
14
|
+
Add ActiveShrine to your application's Gemfile:
|
12
15
|
|
13
16
|
```ruby
|
14
17
|
gem "active_shrine"
|
15
18
|
```
|
16
19
|
|
17
|
-
|
20
|
+
Then execute:
|
18
21
|
|
19
|
-
```
|
20
|
-
bundle
|
22
|
+
```bash
|
23
|
+
$ bundle install
|
21
24
|
```
|
22
25
|
|
23
|
-
|
26
|
+
Generate and run the migration:
|
24
27
|
|
25
|
-
```
|
26
|
-
rails
|
28
|
+
```bash
|
29
|
+
$ rails generate active_shrine:install
|
30
|
+
$ rails db:migrate
|
27
31
|
```
|
28
32
|
|
29
|
-
## Usage
|
33
|
+
## Basic Usage
|
34
|
+
|
35
|
+
Include `ActiveShrine::Model` in your models and declare attachments:
|
30
36
|
|
31
37
|
```ruby
|
32
|
-
class
|
38
|
+
class User < ApplicationRecord
|
33
39
|
include ActiveShrine::Model
|
34
40
|
|
35
41
|
has_one_attached :avatar
|
36
|
-
has_many_attached :
|
42
|
+
has_many_attached :photos
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
Work with attachments using a familiar API:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
# Attach a file
|
50
|
+
user.avatar = File.open("avatar.jpg")
|
51
|
+
user.save
|
52
|
+
|
53
|
+
# Access the attachment
|
54
|
+
user.avatar.url
|
55
|
+
user.avatar.content_type
|
56
|
+
user.avatar.filename
|
57
|
+
|
58
|
+
# Remove the attachment
|
59
|
+
user.avatar = nil
|
60
|
+
user.save
|
61
|
+
```
|
62
|
+
|
63
|
+
### Eager Loading
|
64
|
+
|
65
|
+
Prevent N+1 queries by eager loading attachments:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
# Single attachment
|
69
|
+
User.with_attached_avatar
|
70
|
+
|
71
|
+
# Multiple attachments
|
72
|
+
User.with_attached_photos
|
73
|
+
```
|
74
|
+
|
75
|
+
## Custom Uploaders
|
76
|
+
|
77
|
+
Define custom uploaders with Shrine features and validations:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class ImageUploader < Shrine
|
81
|
+
plugin :validation_helpers
|
82
|
+
plugin :derivatives
|
83
|
+
|
84
|
+
Attacher.validate do
|
85
|
+
validate_max_size 10 * 1024 * 1024
|
86
|
+
validate_mime_type %w[image/jpeg image/png image/webp]
|
87
|
+
end
|
88
|
+
|
89
|
+
Attacher.derivatives do |original|
|
90
|
+
{
|
91
|
+
small: shrine_derivative(:resize_to_limit, 300, 300),
|
92
|
+
medium: shrine_derivative(:resize_to_limit, 500, 500)
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
Use custom uploaders in your models:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
class User < ApplicationRecord
|
102
|
+
include ActiveShrine::Model
|
103
|
+
|
104
|
+
has_one_attached :avatar, uploader: ::ImageUploader
|
37
105
|
end
|
38
106
|
```
|
39
107
|
|
40
108
|
## Development
|
41
109
|
|
42
|
-
After checking out the repo
|
110
|
+
After checking out the repo:
|
43
111
|
|
44
|
-
|
112
|
+
1. Run `bin/setup` to install dependencies
|
113
|
+
2. Run `bundle exec rake test` to run the tests
|
114
|
+
3. Run `bin/console` for an interactive prompt
|
45
115
|
|
46
116
|
## Contributing
|
47
117
|
|
48
|
-
|
118
|
+
1. Fork it
|
119
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
120
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
121
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
122
|
+
5. Create new Pull Request
|
123
|
+
|
124
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/radioactive-labs/active_shrine.
|
49
125
|
|
50
126
|
## License
|
51
127
|
|
@@ -4,11 +4,12 @@ module ActiveShrine
|
|
4
4
|
module Attached
|
5
5
|
module Changes
|
6
6
|
class CreateMany # :nodoc:
|
7
|
-
attr_reader :name, :record, :attachables, :pending_uploads
|
7
|
+
attr_reader :name, :record, :attachment_class, :attachables, :pending_uploads
|
8
8
|
|
9
|
-
def initialize(name, record, attachables, pending_uploads: [])
|
9
|
+
def initialize(name, record, attachment_class, attachables, pending_uploads: [])
|
10
10
|
@name = name
|
11
11
|
@record = record
|
12
|
+
@attachment_class = attachment_class
|
12
13
|
@attachables = Array(attachables)
|
13
14
|
@pending_uploads = Array(pending_uploads) + subchanges
|
14
15
|
attachments
|
@@ -29,7 +30,7 @@ module ActiveShrine
|
|
29
30
|
end
|
30
31
|
|
31
32
|
def build_subchange_from(attachable)
|
32
|
-
Attached::Changes::CreateOneOfMany.new(name, record, attachable)
|
33
|
+
Attached::Changes::CreateOneOfMany.new(name, record, attachment_class, attachable)
|
33
34
|
end
|
34
35
|
|
35
36
|
def assign_associated_attachments
|
@@ -7,11 +7,12 @@ module ActiveShrine
|
|
7
7
|
module Attached
|
8
8
|
module Changes
|
9
9
|
class CreateOne # :nodoc:
|
10
|
-
attr_reader :name, :record, :attachable
|
10
|
+
attr_reader :name, :record, :attachment_class, :attachable
|
11
11
|
|
12
|
-
def initialize(name, record, attachable)
|
12
|
+
def initialize(name, record, attachment_class, attachable)
|
13
13
|
@name = name
|
14
14
|
@record = record
|
15
|
+
@attachment_class = attachment_class
|
15
16
|
@attachable = attachable
|
16
17
|
|
17
18
|
attach
|
@@ -43,7 +44,7 @@ module ActiveShrine
|
|
43
44
|
end
|
44
45
|
|
45
46
|
def build_attachment
|
46
|
-
|
47
|
+
attachment_class.new(record:, name:)
|
47
48
|
end
|
48
49
|
end
|
49
50
|
end
|
@@ -25,7 +25,6 @@ require "shrine"
|
|
25
25
|
#
|
26
26
|
module ActiveShrine
|
27
27
|
class Attachment < ActiveRecord::Base
|
28
|
-
include Shrine::Attachment(:file)
|
29
28
|
include ActiveModel::Serializers::JSON
|
30
29
|
|
31
30
|
self.table_name = "active_shrine_attachments"
|
@@ -37,8 +36,8 @@ module ActiveShrine
|
|
37
36
|
|
38
37
|
before_save :maybe_store_record
|
39
38
|
|
40
|
-
def url
|
41
|
-
file_url
|
39
|
+
def url(derivative=nil)
|
40
|
+
file_url(derivative) || file_url
|
42
41
|
end
|
43
42
|
|
44
43
|
def content_type
|
data/lib/active_shrine/model.rb
CHANGED
@@ -32,12 +32,53 @@ module ActiveShrine
|
|
32
32
|
# Gallery.with_attached_photos
|
33
33
|
|
34
34
|
class_methods do
|
35
|
+
private
|
36
|
+
|
37
|
+
# Resolves or creates a custom attachment class for a given uploader.
|
38
|
+
#
|
39
|
+
# @param uploader [Class] the uploader class (e.g. ::ImageUploader)
|
40
|
+
# @return [Class, String] the resolved attachment class and its name
|
41
|
+
def resolve_attachment_class(uploader)
|
42
|
+
attachment_class_name = "::ActiveShrine::#{uploader}Attachment"
|
43
|
+
|
44
|
+
# Try to find or create the custom attachment class
|
45
|
+
attachment_class = begin
|
46
|
+
attachment_class_name.constantize
|
47
|
+
rescue NameError
|
48
|
+
# Dynamically create a new class that inherits from ActiveShrine::Attachment
|
49
|
+
Class.new(::ActiveShrine::Attachment) do
|
50
|
+
include uploader::Attachment(:file)
|
51
|
+
end.tap do |klass|
|
52
|
+
# Define the class in the ActiveShrine namespace
|
53
|
+
ActiveShrine.const_set(:"#{uploader}Attachment", klass)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
[attachment_class, attachment_class_name]
|
58
|
+
end
|
59
|
+
|
60
|
+
public
|
61
|
+
|
35
62
|
# Specifies the relation between a single attachment and the model.
|
36
63
|
#
|
37
64
|
# class User < ApplicationRecord
|
38
65
|
# has_one_attached :avatar
|
39
66
|
# end
|
40
67
|
#
|
68
|
+
# You can specify a custom uploader implementation to use for the attachment:
|
69
|
+
#
|
70
|
+
# class ImageUploader < Shrine
|
71
|
+
# plugin :validation_helpers
|
72
|
+
#
|
73
|
+
# Attacher.validate do
|
74
|
+
# validate_max_size 10 * 1024 * 1024
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# class User < ApplicationRecord
|
79
|
+
# has_one_attached :avatar, uploader: ::ImageUploader
|
80
|
+
# end
|
81
|
+
#
|
41
82
|
# There is no column defined on the model side, ActiveShrine takes
|
42
83
|
# care of the mapping between your records and the attachment.
|
43
84
|
#
|
@@ -66,7 +107,9 @@ module ActiveShrine
|
|
66
107
|
# When renaming classes that use <tt>has_many</tt>, make sure to also update the class names in the
|
67
108
|
# <tt>active_shrine_attachments.record_type</tt> polymorphic type column of
|
68
109
|
# the corresponding rows.
|
69
|
-
def has_one_attached(name,
|
110
|
+
def has_one_attached(name, uploader: ::Shrine, dependent: :destroy, strict_loading: false)
|
111
|
+
attachment_class, attachment_class_name = resolve_attachment_class(uploader)
|
112
|
+
|
70
113
|
generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
71
114
|
# frozen_string_literal: true
|
72
115
|
def #{name}
|
@@ -79,17 +122,27 @@ module ActiveShrine
|
|
79
122
|
if attachable.presence.nil?
|
80
123
|
Attached::Changes::DeleteOne.new("#{name}", self)
|
81
124
|
else
|
82
|
-
Attached::Changes::CreateOne.new("#{name}", self, attachable)
|
125
|
+
Attached::Changes::CreateOne.new("#{name}", self, #{attachment_class}, attachable)
|
83
126
|
end
|
84
127
|
end
|
85
128
|
CODE
|
86
129
|
|
87
|
-
has_one(:"#{name}_attachment",
|
88
|
-
|
130
|
+
has_one(:"#{name}_attachment",
|
131
|
+
-> { where(name:) },
|
132
|
+
class_name: attachment_class_name,
|
133
|
+
as: :record,
|
134
|
+
inverse_of: :record,
|
135
|
+
dependent: dependent,
|
136
|
+
strict_loading: strict_loading)
|
89
137
|
|
90
138
|
scope :"with_attached_#{name}", -> { includes(:"#{name}_attachment") }
|
91
139
|
|
92
|
-
after_save
|
140
|
+
after_save do
|
141
|
+
shrine_attachment_changes[name.to_s]&.save
|
142
|
+
rescue => e
|
143
|
+
errors.add(name, :invalid, message: "failed to save. Please make sure it is a valid file.")
|
144
|
+
raise ActiveRecord::RecordInvalid.new(self)
|
145
|
+
end
|
93
146
|
|
94
147
|
after_commit(on: %i[create update]) { shrine_attachment_changes.delete(name.to_s) }
|
95
148
|
|
@@ -97,7 +150,7 @@ module ActiveShrine
|
|
97
150
|
:has_one_attached,
|
98
151
|
name,
|
99
152
|
nil,
|
100
|
-
{dependent
|
153
|
+
{dependent: dependent, class_name: attachment_class_name, source: :active_shrine},
|
101
154
|
self
|
102
155
|
)
|
103
156
|
yield reflection if block_given?
|
@@ -110,6 +163,20 @@ module ActiveShrine
|
|
110
163
|
# has_many_attached :photos
|
111
164
|
# end
|
112
165
|
#
|
166
|
+
# You can specify a custom Shrine implementation to use for the attachments:
|
167
|
+
#
|
168
|
+
# class ImageUploader < Shrine
|
169
|
+
# plugin :validation_helpers
|
170
|
+
#
|
171
|
+
# Attacher.validate do
|
172
|
+
# validate_max_size 10 * 1024 * 1024
|
173
|
+
# end
|
174
|
+
# end
|
175
|
+
#
|
176
|
+
# class Gallery < ApplicationRecord
|
177
|
+
# has_many_attached :photos, uploader: ::ImageUploader
|
178
|
+
# end
|
179
|
+
#
|
113
180
|
# There are no columns defined on the model side, ActiveShrine takes
|
114
181
|
# care of the mapping between your records and the attachments.
|
115
182
|
#
|
@@ -138,32 +205,44 @@ module ActiveShrine
|
|
138
205
|
# When renaming classes that use <tt>has_many</tt>, make sure to also update the class names in the
|
139
206
|
# <tt>active_shrine_attachments.record_type</tt> polymorphic type column of
|
140
207
|
# the corresponding rows.
|
141
|
-
def has_many_attached(name,
|
208
|
+
def has_many_attached(name, uploader: ::Shrine, dependent: :destroy, strict_loading: false)
|
209
|
+
_, attachment_class_name = resolve_attachment_class(uploader)
|
210
|
+
|
142
211
|
generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
212
|
+
# frozen_string_literal: true
|
213
|
+
def #{name}
|
214
|
+
@active_shrine_attached ||= {}
|
215
|
+
@active_shrine_attached[:#{name}] ||= Attached::Many.new("#{name}", self)
|
216
|
+
end
|
148
217
|
|
149
|
-
|
150
|
-
|
151
|
-
|
218
|
+
def #{name}=(attachables)
|
219
|
+
attachables = Array(attachables).compact_blank
|
220
|
+
pending_uploads = shrine_attachment_changes["#{name}"].try(:pending_uploads)
|
152
221
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
222
|
+
shrine_attachment_changes["#{name}"] = if attachables.none?
|
223
|
+
Attached::Changes::DeleteMany.new("#{name}", self)
|
224
|
+
else
|
225
|
+
Attached::Changes::CreateMany.new("#{name}", self, #{attachment_class}, attachables, pending_uploads: pending_uploads)
|
226
|
+
end
|
157
227
|
end
|
158
|
-
end
|
159
228
|
CODE
|
160
229
|
|
161
|
-
has_many(:"#{name}_attachments",
|
162
|
-
|
230
|
+
has_many(:"#{name}_attachments",
|
231
|
+
-> { where(name:) },
|
232
|
+
class_name: attachment_class_name,
|
233
|
+
as: :record,
|
234
|
+
inverse_of: :record,
|
235
|
+
dependent: dependent,
|
236
|
+
strict_loading: strict_loading)
|
163
237
|
|
164
238
|
scope :"with_attached_#{name}", -> { includes(:"#{name}_attachments") }
|
165
239
|
|
166
|
-
after_save
|
240
|
+
after_save do
|
241
|
+
shrine_attachment_changes[name.to_s]&.save
|
242
|
+
rescue => e
|
243
|
+
errors.add(name, :invalid, message: "failed to save. Please make sure it is a valid file.")
|
244
|
+
raise ActiveRecord::RecordInvalid.new(self)
|
245
|
+
end
|
167
246
|
|
168
247
|
after_commit(on: %i[create update]) { shrine_attachment_changes.delete(name.to_s) }
|
169
248
|
|
@@ -171,7 +250,7 @@ module ActiveShrine
|
|
171
250
|
:has_many_attached,
|
172
251
|
name,
|
173
252
|
nil,
|
174
|
-
{dependent
|
253
|
+
{dependent: dependent, class_name: attachment_class_name, source: :active_shrine},
|
175
254
|
self
|
176
255
|
)
|
177
256
|
yield reflection if block_given?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_shrine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Radioactive Labs
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-02-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -114,7 +114,6 @@ executables: []
|
|
114
114
|
extensions: []
|
115
115
|
extra_rdoc_files: []
|
116
116
|
files:
|
117
|
-
- ".DS_Store"
|
118
117
|
- ".rspec"
|
119
118
|
- ".ruby-version"
|
120
119
|
- CHANGELOG.md
|
@@ -123,7 +122,6 @@ files:
|
|
123
122
|
- README.md
|
124
123
|
- Rakefile
|
125
124
|
- config.ru
|
126
|
-
- lib/.DS_Store
|
127
125
|
- lib/active_shrine.rb
|
128
126
|
- lib/active_shrine/attached.rb
|
129
127
|
- lib/active_shrine/attached/base.rb
|
@@ -177,7 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
177
175
|
- !ruby/object:Gem::Version
|
178
176
|
version: '0'
|
179
177
|
requirements: []
|
180
|
-
rubygems_version: 3.
|
178
|
+
rubygems_version: 3.4.10
|
181
179
|
signing_key:
|
182
180
|
specification_version: 4
|
183
181
|
summary: A compatible ActiveStorage api for attaching Shrine uploads to ActiveRecord
|
data/.DS_Store
DELETED
Binary file
|
data/lib/.DS_Store
DELETED
Binary file
|