paperdragon 0.0.4 → 0.0.5
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 +276 -33
- data/lib/paperdragon/model.rb +7 -2
- data/lib/paperdragon/version.rb +1 -1
- data/test/model_test.rb +22 -5
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c124aecf9f335b22baf9214604df964e1a965dd
|
4
|
+
data.tar.gz: 6328ab40a1eead17adc5b399678383cc88761a48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fc0da95f318aeab85656c5d76cc06e008e0fb2cddfac3da524501f1633f5cd04ef9fedc14ce11badc2e1a91ea3259482d9f1b95d5609523eca446163e0880ac
|
7
|
+
data.tar.gz: e6b9f994f29266b394c5d8c5999b40425a2808002419dea331bf628985d746a4f2bf7e331c495204441de4bbcf84c0fae753be2e021f7b74b8a77dabbc9221cc
|
data/README.md
CHANGED
@@ -1,71 +1,314 @@
|
|
1
1
|
# Paperdragon
|
2
2
|
|
3
|
-
|
3
|
+
_Explicit image processing._
|
4
|
+
|
5
|
+
## Summary
|
6
|
+
|
7
|
+
Paperdragon gives you image processing as known from [Paperclip](https://github.com/thoughtbot/paperclip, [CarrierWave](https://github.com/carrierwaveuploader/carrierwave) or [Dragonfly](https://github.com/markevans/dragonfly). It allows uploading, cropping, resizing, watermarking, maintaining different versions of an image, and so on.
|
8
|
+
|
9
|
+
It provides a very explicit DSL: **No magic is happening behind the scenes, paperdragon makes _you_ implement the processing steps.**
|
10
|
+
|
11
|
+
With only a little bit of more code you are fully in control of what gets uploaded where, which image version gets resized when and what gets sent to a background job.
|
12
|
+
|
13
|
+
Paperdragon uses the excellent [Dragonfly](https://github.com/markevans/dragonfly) gem for processing, resizing, storing, etc.
|
14
|
+
|
15
|
+
Paperdragon is database-agnostic, doesn't know anything about ActiveRecord and _does not_ hook into AR's callbacks.
|
4
16
|
|
5
17
|
## Installation
|
6
18
|
|
7
19
|
Add this line to your application's Gemfile:
|
8
20
|
|
9
|
-
|
21
|
+
```ruby
|
22
|
+
gem 'paperdragon'
|
23
|
+
```
|
24
|
+
|
25
|
+
|
26
|
+
## Example
|
27
|
+
|
28
|
+
This README only documents the public DSL. You're free to use the public API [documented here](# TODO) if you don't like the DSL.
|
29
|
+
|
30
|
+
### Model
|
31
|
+
|
32
|
+
Paperdragon has only one requirement for the model: It needs to have a column `image_meta_data`. This is a serialised hash where paperdragon saves UIDs for the different image versions. We'll learn about this in a minute.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class User < ActiveRecord::Base # this could be just anything.
|
36
|
+
include Paperdragon::Model
|
37
|
+
|
38
|
+
processable :image
|
39
|
+
|
40
|
+
serialize :image_meta_data
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
Calling `::processable` advises paperdragon to create a `User#image` reader to the attachment. Nothing else is added to the class.
|
45
|
+
|
46
|
+
|
47
|
+
## Uploading
|
48
|
+
|
49
|
+
Processing and storing an uploaded image is an explicit step - you have to code it! This code usually goes to a separate class or an [Operation in Trailblazer](https://github.com/apotonick/trailblazer#domain-layer-operation), don't leave it in the controller if you don't have to.
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
def create
|
53
|
+
file = params.delete(:image)
|
54
|
+
|
55
|
+
user = User.create(params) # this is your code.
|
56
|
+
|
57
|
+
# upload code:
|
58
|
+
user.image(file) do |v|
|
59
|
+
v.process!(:original) # save the unprocessed.
|
60
|
+
v.process!(:thumb) { |job| job.thumb!("75x75#") } # resizing.
|
61
|
+
v.process!(:cropped) { |job| job.thumb!("140x140+20+20") } # cropping.
|
62
|
+
v.process!(:public) { |job| job.watermark! } # watermark.
|
63
|
+
end
|
64
|
+
|
65
|
+
user.save
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
This is a completely transparent process.
|
70
|
+
|
71
|
+
1. Calling `#image` usually returns the image attachment. However, passing a `file` to it allows to create different versions of the uploaded image in the block.
|
72
|
+
2. `#process!` requires you to pass in a name for that particular image version. It is a convention to call the unprocessed image `:original`.
|
73
|
+
3. The `job` object is responsible for creating the final version. This is simply a `Dragonfly::Job` object and gives you [everything that can be done with dragonfly](http://markevans.github.io/dragonfly/imagemagick/).
|
74
|
+
4. After the block is run, paperdragon pushes a hash with all the images meta data to the model via `model.image_meta_data=`.
|
75
|
+
|
76
|
+
For a better understanding and to see how simple it is, go and check out the `image_meta_data` field.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
user.image_meta_data #=> {original: {uid: "original-logo.jpg", width: 240, height: 800},
|
80
|
+
# thumb: {uid: "thumb-logo.jpg", width: 48, height: 48},
|
81
|
+
# ..and so on..
|
82
|
+
# }
|
83
|
+
```
|
84
|
+
|
85
|
+
|
86
|
+
## Rendering Images
|
87
|
+
|
88
|
+
After processing, you may want to render those image versions in your app.
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
user.image[:thumb].url
|
92
|
+
```
|
93
|
+
|
94
|
+
This is all you need to retrieve the URL/path for a stored image. Use this for your image tags
|
95
|
+
|
96
|
+
```haml
|
97
|
+
= img_tag user.image[:thumb].url
|
98
|
+
```
|
99
|
+
|
100
|
+
Internally, Paperdragon will call `model#image_meta_data` and use this hash to find the address of the image.
|
101
|
+
|
102
|
+
While gems like paperclip often use several fields of the model to compute UIDs (addresses) at run-time, paperdragon does that once and then dumps it to the database. This completely removes the dependency to the model.
|
103
|
+
|
104
|
+
|
105
|
+
## Reprocessing And Cropping
|
106
|
+
|
107
|
+
Once an image has been processed to several versions, you might need to reprocess some of them. As an example, users could re-crop their thumbs.
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
def crop
|
111
|
+
user = User.find(params[:id]) # this is your code.
|
112
|
+
|
113
|
+
# reprocessing code:
|
114
|
+
cropping = "#{params[:w]}x#{params[:h]}#"
|
115
|
+
|
116
|
+
user.image do |v|
|
117
|
+
v.reprocess!(:thumb, Time.now) { |job| job.thumb!(cropping) } # re-crop.
|
118
|
+
end
|
10
119
|
|
120
|
+
user.save
|
121
|
+
end
|
122
|
+
```
|
11
123
|
|
12
|
-
|
124
|
+
Only a few things have changed compared to the initial processing.
|
13
125
|
|
14
|
-
|
126
|
+
1. We do not pass a file to `#image` anymore. This makes sense as reprocessing will re-use the existing original file.
|
127
|
+
2. Note that it's not `#process!` but `#reprocess!` indicating a surprising reprocessing.
|
128
|
+
3. As a second argument to `#reprocess!` a fingerprint string is required. To understand what this does, let's inspect `image_meta_data` once again. (The fingerprint feature is optional but extremely helpful.)
|
15
129
|
|
16
130
|
|
131
|
+
```ruby
|
132
|
+
user.image_meta_data # ..original..
|
133
|
+
# thumb: {uid: "thumb-logo-1234567890.jpg", width: 48, height: 48},
|
134
|
+
# ..and so on..
|
135
|
+
# }
|
136
|
+
```
|
17
137
|
|
18
|
-
|
138
|
+
See how the file name has changed? Paperdragon will automatically append the fingerprint you pass into `#reprocess!` to the existing version's file name.
|
19
139
|
|
20
|
-
* you control processing and storage, e.g. first thumbnails and cropping, then process the rest. easy to sidekiq.
|
21
|
-
error handling
|
22
|
-
UID generation handled by you. also, updating (e.g. new fingerprint)
|
23
|
-
* only process subset, e.g. in test.
|
24
140
|
|
141
|
+
## Renaming
|
25
142
|
|
26
|
-
|
143
|
+
Sometimes you just want to rename files without processing them. For instance, when a new fingerprint for an image is introduced, you want to apply that to all versions.
|
27
144
|
|
28
|
-
|
29
|
-
|
30
|
-
override #default_metadata_for when you wanna change it
|
31
|
-
last arg in process method gets merged into metadata hash
|
145
|
+
```ruby
|
146
|
+
fingerprint = Time.now
|
32
147
|
|
33
|
-
|
34
|
-
|
148
|
+
user.image do |v|
|
149
|
+
v.reprocess!(:thumb, fingerprint) { |job| job.thumb!(cropping) } # re-crop.
|
150
|
+
v.rename!(:original, fingerprint) # just rename it.
|
151
|
+
end
|
152
|
+
```
|
35
153
|
|
154
|
+
This will re-crop the thumb and _rename_ the original.
|
36
155
|
|
37
|
-
|
156
|
+
```ruby
|
157
|
+
user.image_meta_data #=> {original: {uid: "original-logo-1234567890.jpg", ..},
|
158
|
+
# thumb: {uid: "thumb-logo-1234567890.jpg", ..},
|
159
|
+
# ..and so on..
|
160
|
+
# }
|
161
|
+
```
|
38
162
|
|
39
|
-
1. Stores file to same location as paperclip would do.
|
40
|
-
2. `Photo#url` will return the same URL as paperclip.
|
41
|
-
3. P::Model image.url(:thumb) still works, your rendering code will still work.
|
42
|
-
4. Cleaner API for generating URLs. For example, we needed to copy images from production to staging. With paperclip, it was impossible to create paths for both environments.
|
43
163
|
|
44
|
-
|
164
|
+
## Deleting
|
45
165
|
|
46
|
-
|
166
|
+
While making images is a wonderful thing, sometimes you need to destroy to create. This is why paperdragon gives you a deleting mechanism, too.
|
47
167
|
|
48
|
-
|
168
|
+
```ruby
|
169
|
+
user.image do |v|
|
170
|
+
v.delete!(:thumb)
|
171
|
+
end
|
172
|
+
```
|
49
173
|
|
174
|
+
This will also remove the associated metadata from the model.
|
50
175
|
|
51
176
|
|
177
|
+
## Fingerprints
|
52
178
|
|
53
|
-
|
54
|
-
it { pic.image(:original).should == "/system/test/pics/images/002/216/376/bc7b26d983db8ced792e38f0c34aba417f75c2e7_key/original-c5b7e624adc5b67e13435baf26e65bc8-1399980114/DSC_4876.jpg" }
|
179
|
+
Paperdragon comes with a very simple built-in file naming.
|
55
180
|
|
56
|
-
|
57
|
-
Failure/Error: should == "system/test/pics/images/002/216/376/bc7b26d983db8ced792e38f0c34aba417f75c2e7_key/original-c5b7e624adc5b67e13435baf26e65bc8-1399980114/DSC_4876.jpg" }
|
58
|
-
expected: "system/test/pics/images/002/216/376/bc7b26d983db8ced792e38f0c34aba417f75c2e7_key/original-c5b7e624adc5b67e13435baf26e65bc8-1399980114/DSC_4876.jpg"
|
59
|
-
got: "system/test/pics/images/002/216/376/bc7b26d983db8ced792e38f0c34aba417f75c2e7_key/original/DSC_4876.jpg" (using ==)
|
181
|
+
Computing a file UID (or, name, or path) happens in the `Attachment` class. You need to provide your own implementation if you want to change things.
|
60
182
|
|
61
|
-
|
183
|
+
```ruby
|
184
|
+
class User < ActiveRecord::Base
|
185
|
+
include Paperdragon::Model
|
62
186
|
|
63
|
-
|
187
|
+
class Attachment < Paperdragon::Attachment
|
188
|
+
def build_uid(style, file)
|
189
|
+
"/path/to/#{style}/#{obfuscator}/#{file.original_filename}"
|
190
|
+
end
|
64
191
|
|
192
|
+
def obfuscator
|
193
|
+
Obfuscator.call # this is your code.
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
processable :image, Attachment # use the class you just wrote.
|
198
|
+
```
|
199
|
+
|
200
|
+
The `Attachment#build_uid` method is invoked when processing images.
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
user.image(file) do |v|
|
204
|
+
v.process!(:thumb) { |job| job.thumb!("75x75#") }
|
205
|
+
end
|
206
|
+
```
|
207
|
+
|
208
|
+
To create the image UID, _your_ attachment is now being used.
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
user.image_meta_data # ..original..
|
212
|
+
# thumb: {uid: "/path/to/thumb/ac97dnxid8/logo.jpg", ..},
|
213
|
+
# ..and so on..
|
214
|
+
# }
|
215
|
+
```
|
216
|
+
|
217
|
+
What a beautiful, cryptic and mysterious filename you just created!
|
218
|
+
|
219
|
+
The same pattern applies for _re-building_ UIDs when reprocessing images.
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
class Attachment < Paperdragon::Attachment
|
223
|
+
# def build_uid and the other code from above..
|
224
|
+
|
225
|
+
def rebuild_uid(file, fingerprint)
|
226
|
+
file.uid.sub("logo.png", "logo-#{fingerprint".png)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
```
|
230
|
+
|
231
|
+
This code is used to re-compute UIDs in `#reprocess!`.
|
232
|
+
|
233
|
+
That example is stupid, I know, but it shows how you have access to the `Paperdragon::File` instance that represents the existing version of the reprocessed image.
|
234
|
+
|
235
|
+
|
236
|
+
## Local Rails Configuration
|
237
|
+
|
238
|
+
Configuration of paperdragon completely relies on [configuring dragonfly](http://markevans.github.io/dragonfly/configuration/). As an example, for a Rails app with a local file storage, I use the following configuration in `config/initializers/paperdragon.rb`.
|
239
|
+
|
240
|
+
```ruby
|
65
241
|
Dragonfly.app.configure do
|
66
242
|
plugin :imagemagick
|
67
243
|
|
68
244
|
datastore :file,
|
69
245
|
:server_root => 'public',
|
70
246
|
:root_path => 'public/images'
|
71
|
-
end
|
247
|
+
end
|
248
|
+
```
|
249
|
+
|
250
|
+
This would result in image UIDs being prefixed accordingly.
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
user.image[:thumb].url #=> "/images/logo-1234567890.png"
|
254
|
+
```
|
255
|
+
|
256
|
+
|
257
|
+
## S3
|
258
|
+
|
259
|
+
As [dragonfly allows S3](https://github.com/markevans/dragonfly-s3_data_store), using the amazon cloud service is straight-forward.
|
260
|
+
|
261
|
+
All you need to do is configuring your bucket. The API for paperdragon remains unchanged.
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
require 'dragonfly/s3_data_store'
|
265
|
+
|
266
|
+
Dragonfly.app.configure do
|
267
|
+
datastore :s3,
|
268
|
+
bucket_name: 'my-bucket',
|
269
|
+
access_key_id: 'blahblahblah',
|
270
|
+
secret_access_key: 'blublublublu'
|
271
|
+
end
|
272
|
+
```
|
273
|
+
|
274
|
+
Images will be stored "in the cloud" when using `#process!`, renaming, deleting and re-processing do the same!
|
275
|
+
|
276
|
+
|
277
|
+
## Background Processing
|
278
|
+
|
279
|
+
The explicit design of paperdragon makes it incredibly simple to move all or certain processing steps to background jobs.
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
class Image::Processor
|
283
|
+
include Sidekiq::Worker
|
284
|
+
|
285
|
+
def perform(params)
|
286
|
+
user = User.find(params[:id])
|
287
|
+
|
288
|
+
user.image(params[:file]) do |v|
|
289
|
+
v.process!(:original)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
```
|
294
|
+
|
295
|
+
Documentation how to use Sidekiq and paperdragon in Traiblazer will be added shortly.
|
296
|
+
|
297
|
+
## Validations
|
298
|
+
|
299
|
+
## Paperclip compatibility
|
300
|
+
|
301
|
+
I wrote paperdragon as an explicit alternative to paperclip. In the process of doing so, I step-wise replaced upload code, but left the rendering code unchanged. Paperclip has a slightly different API for rendering.
|
302
|
+
|
303
|
+
```ruby
|
304
|
+
user.image.url(:thumb)
|
305
|
+
```
|
306
|
+
|
307
|
+
Allowing your paperdragon-backed model to expose this API is piece-of-cake.
|
308
|
+
|
309
|
+
```ruby
|
310
|
+
class User < ActiveRecord::Base
|
311
|
+
include Paperdragon::Paperclip::Model
|
312
|
+
```
|
313
|
+
|
314
|
+
This will allow both APIs for a smooth transition.
|
data/lib/paperdragon/model.rb
CHANGED
@@ -13,8 +13,13 @@ module Paperdragon
|
|
13
13
|
# Creates Avatar#image that returns a Paperdragon::File instance.
|
14
14
|
def attachment_accessor_for(name, attachment_class)
|
15
15
|
mod = Module.new do # TODO: abstract that into Uber, we use it everywhere.
|
16
|
-
define_method name do
|
17
|
-
attachment_class.new(self.image_meta_data, {:model => self})
|
16
|
+
define_method name do |file=nil, &block|
|
17
|
+
attachment = attachment_class.new(self.image_meta_data, {:model => self})
|
18
|
+
|
19
|
+
return attachment unless file or block
|
20
|
+
|
21
|
+
# run the task block and save the returned new metadata in the model.
|
22
|
+
self.image_meta_data = attachment.task(*[file], &block)
|
18
23
|
end
|
19
24
|
end
|
20
25
|
end
|
data/lib/paperdragon/version.rb
CHANGED
data/test/model_test.rb
CHANGED
@@ -22,14 +22,31 @@ class PaperdragonModelTest < MiniTest::Spec
|
|
22
22
|
|
23
23
|
|
24
24
|
# minimum setup
|
25
|
-
class Image
|
25
|
+
class Image < OpenStruct
|
26
26
|
include Paperdragon::Model
|
27
27
|
processable :image
|
28
|
+
end
|
28
29
|
|
29
|
-
|
30
|
-
|
30
|
+
it { Image.new(:image_meta_data => {:thumb => {:uid => "Avatar-thumb"}}).image[:thumb].url.must_equal "/paperdragon/Avatar-thumb" }
|
31
|
+
|
32
|
+
|
33
|
+
# process with #image{}
|
34
|
+
let (:logo) { Pathname("test/fixtures/apotomo.png") }
|
35
|
+
|
36
|
+
it do
|
37
|
+
model = Image.new
|
38
|
+
model.image(logo) do |v|
|
39
|
+
v.process!(:original)
|
40
|
+
v.process!(:thumb) { |j| j.thumb!("16x16") }
|
31
41
|
end
|
32
|
-
end
|
33
42
|
|
34
|
-
|
43
|
+
model.image_meta_data.must_equal({:original=>{:width=>216, :height=>63, :uid=>"original-apotomo.png"}, :thumb=>{:width=>16, :height=>5, :uid=>"thumb-apotomo.png"}})
|
44
|
+
|
45
|
+
|
46
|
+
model.image do |v|
|
47
|
+
v.reprocess!(:thumb, "1") { |j| j.thumb!("8x8") }
|
48
|
+
end
|
49
|
+
|
50
|
+
model.image_meta_data.must_equal({:original=>{:width=>216, :height=>63, :uid=>"original-apotomo.png"}, :thumb=>{:width=>8, :height=>2, :uid=>"thumb-apotomo-1.png"}})
|
51
|
+
end
|
35
52
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paperdragon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-08-
|
11
|
+
date: 2014-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dragonfly
|