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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c8a0f75cce6caf34f444f4cc357181d6e84b77ab248de2e0e847542e5eeb20c
4
- data.tar.gz: b3c5d383c8f81f8e858e9bdefcd04211129a6fc73305fd1f891b6ccedebf3526
3
+ metadata.gz: 698b07ac6ab5b98eecfeae48419c2c5203cf6ae8f2c3e1c924aa0c2de6ee3513
4
+ data.tar.gz: 56439876a6a623a4e3d27f0b9080f00197b444430043d74ddecf6ccb3c0f9542
5
5
  SHA512:
6
- metadata.gz: c710f77147da3af3c770e4122c2bfd079adb0cf6a07db94869c1cc776be556efef142684a5d03904f92f1ab9dda33803e0abed4bcf45998fbd205506f87fe6a4
7
- data.tar.gz: ebfdb98f4aba7b62c0942767a0d7e8af0e13d9443e7fb46b76275320219d6c7460faf4555e27b463e3b77d8cacf94ff88c63e37b0f44b73fe41cfde5c140d553
6
+ metadata.gz: 629bf6b87acb29d9f4468d2869b8d20dd736e28817ca129061661b78f2245d646faae0dd81fb6d52404a9901d9beae9a25ce5a76c88ac013d38f4ada48f6b0e0
7
+ data.tar.gz: c171501c31fe29b67b8240aa89dad180bbdc10fe6e11d7d142ab4c9f9f7c1a05945d6fe633404bacb6e23148299a5812793b4bd27b1594a0d4f580acf99518a3
data/README.md CHANGED
@@ -1,51 +1,127 @@
1
1
  # ActiveShrine
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
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
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/active_shrine`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ ## Features
6
6
 
7
- ## Quick Start
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
- Get ActiveShrine up and running in your Rails application with these simple steps:
12
+ ## Installation
10
13
 
11
- 1. **Add Plutonium to your Gemfile:**
14
+ Add ActiveShrine to your application's Gemfile:
12
15
 
13
16
  ```ruby
14
17
  gem "active_shrine"
15
18
  ```
16
19
 
17
- 2. **Bundle Install:**
20
+ Then execute:
18
21
 
19
- ```shell
20
- bundle
22
+ ```bash
23
+ $ bundle install
21
24
  ```
22
25
 
23
- 3. **Install ActiveShrine:**
26
+ Generate and run the migration:
24
27
 
25
- ```shell
26
- rails g active_shrine:install
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 Blog < ApplicationRecord
38
+ class User < ApplicationRecord
33
39
  include ActiveShrine::Model
34
40
 
35
41
  has_one_attached :avatar
36
- has_many_attached :documents
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, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
110
+ After checking out the repo:
43
111
 
44
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_shrine. 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/[USERNAME]/active_shrine/blob/main/CODE_OF_CONDUCT.md).
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
- ActiveShrine::Attachment.new(record:, name:)
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"
@@ -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, class_name: "::ActiveShrine::Attachment", dependent: :destroy, strict_loading: false)
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", -> { where(name:) }, class_name:, as: :record, inverse_of: :record,
88
- dependent:, strict_loading:)
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:, source: :active_shrine},
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, class_name: "::ActiveShrine::Attachment", dependent: :destroy, strict_loading: false)
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
- # frozen_string_literal: true
144
- def #{name}
145
- @active_shrine_attached ||= {}
146
- @active_shrine_attached[:#{name}] ||= Attached::Many.new("#{name}", self)
147
- end
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
- def #{name}=(attachables)
150
- attachables = Array(attachables).compact_blank
151
- pending_uploads = shrine_attachment_changes["#{name}"].try(:pending_uploads)
213
+ def #{name}=(attachables)
214
+ attachables = Array(attachables).compact_blank
215
+ pending_uploads = shrine_attachment_changes["#{name}"].try(:pending_uploads)
152
216
 
153
- shrine_attachment_changes["#{name}"] = if attachables.none?
154
- Attached::Changes::DeleteMany.new("#{name}", self)
155
- else
156
- Attached::Changes::CreateMany.new("#{name}", self, attachables, pending_uploads: pending_uploads)
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", -> { where(name:) }, class_name:, as: :record, inverse_of: :record,
162
- dependent:, strict_loading:)
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:, source: :active_shrine},
243
+ {dependent: dependent, class_name: attachment_class_name, source: :active_shrine},
175
244
  self
176
245
  )
177
246
  yield reflection if block_given?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveShrine
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
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.2.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-01-11 00:00:00.000000000 Z
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.5.16
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