active_shrine 0.2.0 → 0.3.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 +4 -4
- data/README.md +94 -18
- 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 +0 -1
- data/lib/active_shrine/model.rb +91 -22
- 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: 698b07ac6ab5b98eecfeae48419c2c5203cf6ae8f2c3e1c924aa0c2de6ee3513
|
4
|
+
data.tar.gz: 56439876a6a623a4e3d27f0b9080f00197b444430043d74ddecf6ccb3c0f9542
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 629bf6b87acb29d9f4468d2869b8d20dd736e28817ca129061661b78f2245d646faae0dd81fb6d52404a9901d9beae9a25ce5a76c88ac013d38f4ada48f6b0e0
|
7
|
+
data.tar.gz: c171501c31fe29b67b8240aa89dad180bbdc10fe6e11d7d142ab4c9f9f7c1a05945d6fe633404bacb6e23148299a5812793b4bd27b1594a0d4f580acf99518a3
|
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/[USERNAME]/active_shrine.
|
49
125
|
|
50
126
|
## License
|
51
127
|
|
@@ -53,4 +129,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
53
129
|
|
54
130
|
## Code of Conduct
|
55
131
|
|
56
|
-
Everyone interacting in the ActiveShrine project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/active_shrine/blob/main/CODE_OF_CONDUCT.md).
|
132
|
+
Everyone interacting in the ActiveShrine project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/active_shrine/blob/main/CODE_OF_CONDUCT.md).
|
@@ -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
|
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,13 +122,18 @@ 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
|
|
@@ -97,7 +145,7 @@ module ActiveShrine
|
|
97
145
|
:has_one_attached,
|
98
146
|
name,
|
99
147
|
nil,
|
100
|
-
{dependent
|
148
|
+
{dependent: dependent, class_name: attachment_class_name, source: :active_shrine},
|
101
149
|
self
|
102
150
|
)
|
103
151
|
yield reflection if block_given?
|
@@ -110,6 +158,20 @@ module ActiveShrine
|
|
110
158
|
# has_many_attached :photos
|
111
159
|
# end
|
112
160
|
#
|
161
|
+
# You can specify a custom Shrine implementation to use for the attachments:
|
162
|
+
#
|
163
|
+
# class ImageUploader < Shrine
|
164
|
+
# plugin :validation_helpers
|
165
|
+
#
|
166
|
+
# Attacher.validate do
|
167
|
+
# validate_max_size 10 * 1024 * 1024
|
168
|
+
# end
|
169
|
+
# end
|
170
|
+
#
|
171
|
+
# class Gallery < ApplicationRecord
|
172
|
+
# has_many_attached :photos, uploader: ::ImageUploader
|
173
|
+
# end
|
174
|
+
#
|
113
175
|
# There are no columns defined on the model side, ActiveShrine takes
|
114
176
|
# care of the mapping between your records and the attachments.
|
115
177
|
#
|
@@ -138,28 +200,35 @@ module ActiveShrine
|
|
138
200
|
# When renaming classes that use <tt>has_many</tt>, make sure to also update the class names in the
|
139
201
|
# <tt>active_shrine_attachments.record_type</tt> polymorphic type column of
|
140
202
|
# the corresponding rows.
|
141
|
-
def has_many_attached(name,
|
203
|
+
def has_many_attached(name, uploader: ::Shrine, dependent: :destroy, strict_loading: false)
|
204
|
+
_, attachment_class_name = resolve_attachment_class(uploader)
|
205
|
+
|
142
206
|
generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
207
|
+
# frozen_string_literal: true
|
208
|
+
def #{name}
|
209
|
+
@active_shrine_attached ||= {}
|
210
|
+
@active_shrine_attached[:#{name}] ||= Attached::Many.new("#{name}", self)
|
211
|
+
end
|
148
212
|
|
149
|
-
|
150
|
-
|
151
|
-
|
213
|
+
def #{name}=(attachables)
|
214
|
+
attachables = Array(attachables).compact_blank
|
215
|
+
pending_uploads = shrine_attachment_changes["#{name}"].try(:pending_uploads)
|
152
216
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
217
|
+
shrine_attachment_changes["#{name}"] = if attachables.none?
|
218
|
+
Attached::Changes::DeleteMany.new("#{name}", self)
|
219
|
+
else
|
220
|
+
Attached::Changes::CreateMany.new("#{name}", self, #{attachment_class}, attachables, pending_uploads: pending_uploads)
|
221
|
+
end
|
157
222
|
end
|
158
|
-
end
|
159
223
|
CODE
|
160
224
|
|
161
|
-
has_many(:"#{name}_attachments",
|
162
|
-
|
225
|
+
has_many(:"#{name}_attachments",
|
226
|
+
-> { where(name:) },
|
227
|
+
class_name: attachment_class_name,
|
228
|
+
as: :record,
|
229
|
+
inverse_of: :record,
|
230
|
+
dependent: dependent,
|
231
|
+
strict_loading: strict_loading)
|
163
232
|
|
164
233
|
scope :"with_attached_#{name}", -> { includes(:"#{name}_attachments") }
|
165
234
|
|
@@ -171,7 +240,7 @@ module ActiveShrine
|
|
171
240
|
:has_many_attached,
|
172
241
|
name,
|
173
242
|
nil,
|
174
|
-
{dependent
|
243
|
+
{dependent: dependent, class_name: attachment_class_name, source: :active_shrine},
|
175
244
|
self
|
176
245
|
)
|
177
246
|
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.0
|
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-02 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
|