active_shrine 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|