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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 36e7122d614e09b22cecbf83c3698e9d6e9343cb
4
- data.tar.gz: cede5637f600b6c1121c4c7239af3a7367ed5c35
3
+ metadata.gz: 4c124aecf9f335b22baf9214604df964e1a965dd
4
+ data.tar.gz: 6328ab40a1eead17adc5b399678383cc88761a48
5
5
  SHA512:
6
- metadata.gz: 5a60d0ed2a91ae998f46fa602d389fb881c384fd43735a5727c26a07539f3db76fc93cca01c3872c335b7a917054e5ef868e1f44a1596acafa0ef709918cd4b9
7
- data.tar.gz: fd032e73607fb56b7e11c90b968c84f502858a465c6f6e1ea018b4901757fef14c557e4144d64a896bfa1b517c7722c86e1d9ecc7329c5f4c8e497ce2d0dcd18
6
+ metadata.gz: 0fc0da95f318aeab85656c5d76cc06e008e0fb2cddfac3da524501f1633f5cd04ef9fedc14ce11badc2e1a91ea3259482d9f1b95d5609523eca446163e0880ac
7
+ data.tar.gz: e6b9f994f29266b394c5d8c5999b40425a2808002419dea331bf628985d746a4f2bf7e331c495204441de4bbcf84c0fae753be2e021f7b74b8a77dabbc9221cc
data/README.md CHANGED
@@ -1,71 +1,314 @@
1
1
  # Paperdragon
2
2
 
3
- TODO: Write a gem description
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
- gem 'paperdragon'
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
- Paperdragon is completely decoupled from ActiveRecord. Attachment-related calls are delegated to paperdragon objects, the model is solely used for persisting file UIDs.
124
+ Only a few things have changed compared to the initial processing.
13
125
 
14
- Where Paperclip or Carrierwave offer you a handy DSL to configure the processing, Paperdragon comes with an API. You _program_ what you wanna do. This is only a tiny little bit more code and gives you complete control over the entire task.
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
- The goal is to make you _understand_ what is going on.
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
- File
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
- All process methods return Metadata hash
29
- yield Job, save it from the block if you need it
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
- Design
34
- Operations in File look like scripts per design. I could have abstracted various steps into higher level methods, however, as file processing _is_ a lot scripting, I decided to sacrifice redundancy for better understandable code.
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
- Paperclip Compatibility
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
- Paperclip uses several columns to compute the UID. Once this is done, it doesn't store that UID in the database but updates the respective fields, which makes it a bit awkward to maintain.
164
+ ## Deleting
45
165
 
46
- Paperdragon simply dumps the image uid along with meta data into image_meta_data.
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
- You have to take care of updating image_fingerprint etc yourself when changing stuff and still using paperclip to compute urls.
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
- Original paperclip UID:
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
- 1) Uid
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
- Feel like a hacker reverse-engineering
183
+ ```ruby
184
+ class User < ActiveRecord::Base
185
+ include Paperdragon::Model
62
186
 
63
- ## Rails
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.
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Paperdragon
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
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
- def image_meta_data
30
- {:thumb => {:uid => "Avatar-thumb"}}
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
- it { Image.new.image[:thumb].url.must_equal "/paperdragon/Avatar-thumb" }
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
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-26 00:00:00.000000000 Z
11
+ date: 2014-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dragonfly