paperdragon 0.0.4 → 0.0.5

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
  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