carrierwave_direct 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +21 -0
  4. data/README.md +356 -0
  5. data/Rakefile +12 -0
  6. data/carrierwave_direct.gemspec +31 -0
  7. data/lib/carrierwave_direct.rb +44 -0
  8. data/lib/carrierwave_direct/action_view_extensions/form_helper.rb +36 -0
  9. data/lib/carrierwave_direct/form_builder.rb +17 -0
  10. data/lib/carrierwave_direct/locale/en.rb +20 -0
  11. data/lib/carrierwave_direct/locale/en.yml +7 -0
  12. data/lib/carrierwave_direct/mount.rb +38 -0
  13. data/lib/carrierwave_direct/orm/activerecord.rb +55 -0
  14. data/lib/carrierwave_direct/test/capybara_helpers.rb +58 -0
  15. data/lib/carrierwave_direct/test/helpers.rb +32 -0
  16. data/lib/carrierwave_direct/uploader.rb +142 -0
  17. data/lib/carrierwave_direct/uploader/configuration.rb +38 -0
  18. data/lib/carrierwave_direct/validations/active_model.rb +126 -0
  19. data/lib/carrierwave_direct/version.rb +6 -0
  20. data/spec/action_view_extensions/form_helper_spec.rb +28 -0
  21. data/spec/form_builder_spec.rb +59 -0
  22. data/spec/mount_spec.rb +57 -0
  23. data/spec/orm/activerecord_spec.rb +551 -0
  24. data/spec/spec_helper.rb +5 -0
  25. data/spec/support/carrier_wave_config.rb +9 -0
  26. data/spec/support/direct_uploader.rb +4 -0
  27. data/spec/support/form_builder_helpers.rb +36 -0
  28. data/spec/support/global_helpers.rb +6 -0
  29. data/spec/support/model_helpers.rb +80 -0
  30. data/spec/support/mounted_class.rb +6 -0
  31. data/spec/support/uploader_helpers.rb +8 -0
  32. data/spec/support/view_helpers.rb +45 -0
  33. data/spec/test/capybara_helpers_spec.rb +160 -0
  34. data/spec/test/helpers_spec.rb +105 -0
  35. data/spec/uploader_spec.rb +461 -0
  36. metadata +168 -0
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ .rvmrc
5
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in carrierwave_direct.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) David Wilkie
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
@@ -0,0 +1,356 @@
1
+ # CarrierWaveDirect
2
+
3
+ [CarrierWave](https://github.com/jnicklas/carrierwave) is a great way to upload files from Ruby applications, but since processing and saving is done in-process, it doesn't scale well. A better way is to upload your files directly then handle the processing and saving in a background process.
4
+
5
+ [CarrierWaveDirect](https://github.com/dwilkie/carrierwave_direct) works on top of [CarrierWave](https://github.com/jnicklas/carrierwave) and provides a simple way to achieve this.
6
+
7
+ ## Compatibility
8
+
9
+ Right now, CarrierWaveDirect works with [Amazon S3](http://aws.amazon.com/s3/). Adding support for [Google Storage for Developers](http://code.google.com/apis/storage/) should be fairly straight forward since the direct upload form is essentially the same. Please see the contributing section if you would like support for Google Storage for Developers or any other service that provides direct upload capabilities.
10
+
11
+ ## Information
12
+
13
+ More information, and how-tos [available on the wiki](https://github.com/dwilkie/carrierwave_direct/wiki)
14
+
15
+ ## Installation
16
+
17
+ Install the latest release:
18
+
19
+ gem install carrierwave_direct
20
+
21
+ In Rails, add it to your Gemfile:
22
+
23
+ gem 'carrierwave_direct'
24
+
25
+ Note that CarrierWaveDirect is not compatible with Rails 2.
26
+
27
+ ## Getting Started
28
+
29
+ Please read the [CarrierWave readme](https://github.com/jnicklas/carrierwave) first
30
+
31
+ CarrierWaveDirect works with [fog](https://github.com/geemus/fog) so make sure you have [CarrierWave](https://github.com/jnicklas/carrierwave) set up and initialized with your fog credentials, for example:
32
+
33
+ CarrierWave.configure do |config|
34
+ config.fog_credentials = {
35
+ :provider => 'AWS', # required
36
+ :aws_access_key_id => 'xxx', # required
37
+ :aws_secret_access_key => 'yyy', # required
38
+ :region => 'eu-west-1' # optional, defaults to 'us-east-1'
39
+ }
40
+ config.fog_directory = 'name_of_directory' # required
41
+ config.fog_host = 'https://assets.example.com' # optional, defaults to nil
42
+ config.fog_public = false # optional, defaults to true
43
+ config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {}
44
+ end
45
+
46
+ If you haven't already done so generate an uploader
47
+
48
+ rails generate uploader Avatar
49
+
50
+ this should give you a file in:
51
+
52
+ app/uploaders/avatar_uploader.rb
53
+
54
+ Check out this file for some hints on how you can customize your uploader. It should look something like this:
55
+
56
+ class AvatarUploader < CarrierWave::Uploader::Base
57
+ storage :file
58
+ end
59
+
60
+ Remove the line `storage :file` and replace it with `include CarrierWaveDirect::Uploader` so it should look something like:
61
+
62
+ class AvatarUploader < CarrierWave::Uploader::Base
63
+ include CarrierWaveDirect::Uploader
64
+ end
65
+
66
+ This adds the extra functionality for direct uploading.
67
+
68
+ If you're *not* using Rails you can generate a direct upload form to S3 similar to this:
69
+
70
+ <form action="https://s3-bucket.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
71
+ <input type="hidden" name="key" value="uploads/${filename}">
72
+ <input type="hidden" name="AWSAccessKeyId" value="YOUR_AWS_ACCESS_KEY">
73
+ <input type="hidden" name="acl" value="private">
74
+ <input type="hidden" name="success_action_redirect" value="http://localhost/">
75
+ <input type="hidden" name="policy" value="YOUR_POLICY_DOCUMENT_BASE64_ENCODED">
76
+ <input type="hidden" name="signature" value="YOUR_CALCULATED_SIGNATURE">
77
+ <input name="file" type="file">
78
+ <input type="submit" value="Upload to S3">
79
+ </form>
80
+
81
+ by making use of the following methods in your uploader:
82
+
83
+ uploader = AvatarUploader.new
84
+
85
+ uploader.direct_fog_url # return the url to post to based off your bucket name
86
+ uploader.key # returns a key based off your store_dir
87
+ uploader.aws_access_key_id # returns your aws_access_key_id from your fog configuration
88
+ uploader.acl # returns your acl from your fog configuration
89
+ uploader.success_action_redirect= # sets the success action redirect url
90
+ uploader.success_action_redirect # gets the success action redirect url
91
+ uploader.policy # a base 64 encoded policy document
92
+ uploader.signature # your signature
93
+
94
+ If you *are* using Rails and you've mounted your uploader like this:
95
+
96
+ class User < ActiveRecord::Base
97
+ mount_uploader :avatar, AvatarUploader
98
+ end
99
+
100
+ things just got a whole lot easier. You can generate a direct upload form like this:
101
+
102
+ class AvatarController < ApplicationController
103
+ def new
104
+ @uploader = User.new.avatar
105
+ @uploader.success_action_redirect = new_user_url
106
+ end
107
+ end
108
+
109
+ <%= direct_upload_form_for @uploader do |f| %>
110
+ <%= f.file_field :avatar %>
111
+ <%= f.submit %>
112
+ <% end %>
113
+
114
+ Note if `User` is not an ActiveRecord object e.g.
115
+
116
+ class User
117
+ mount_uploader :avatar, AvatarUploader
118
+ end
119
+
120
+ you can still use the form helper by including the ActiveModel modules your uploader:
121
+
122
+ class AvatarUploader < CarrierWave::Uploader::Base
123
+ include CarrierWaveDirect::Uploader
124
+
125
+ include ActiveModel::Conversion
126
+ extend ActiveModel::Naming
127
+ end
128
+
129
+ Once you've uploaded your file directly to the cloud you'll probably need a way to reference it with an ORM and process it.
130
+
131
+ ## Processing and referencing files in a background process
132
+
133
+ Processing and saving file uploads are typically long running tasks and should be done in a background process. CarrierWaveDirect gives you a few methods to help you do this with your favorite background processor such as [DelayedJob](https://github.com/collectiveidea/delayed_job) or [Resque](https://github.com/defunkt/resque).
134
+
135
+ If your upload was successful then you will be redirected to the `success_action_redirect` url you specified in your uploader. S3 replies with a redirect like this: `http://example.com?bucket=your_fog_directory&key=uploads%2Fguid%2Ffile.ext&etag=%22d41d8cd98f00b204e9800998ecf8427%22`
136
+
137
+ The `key` is the most important piece of information as we can use it for validating the file extension, downloading the file from S3, processing it and re-uploading it.
138
+
139
+ If you're using ActiveRecord, CarrierWaveDirect will by default validate the file extension based off your `extension_white_list` in your uploader. See the [CarrierWave readme](https://github.com/jnicklas/carrierwave) for more info. You can then use the helper `filename_valid?` to check if the filename is valid. e.g.
140
+
141
+ class UsersController < ApplicationController
142
+ def new
143
+ @user = User.new(params)
144
+ unless @user.filename_valid?
145
+ flash[:error] = @user.errors.full_messages.to_sentence
146
+ redirect_to new_avatar_path
147
+ end
148
+ end
149
+ end
150
+
151
+ CarrierWaveDirect automatically gives you an accessible `key` attribute in your mounted model when using ActiveRecord. You can use this to put a hidden field for the `key` into your model's form.
152
+
153
+ <%= form_for @user do |f| %>
154
+ <%= f.hidden_field :key %>
155
+ <%= f.label :email %>
156
+ <%= f.text_field :email %>
157
+ <%= f.submit %>
158
+ <% end %>
159
+
160
+ then in your controller you can do something like this:
161
+
162
+ def create
163
+ @user = User.new(params[:user])
164
+ if @user.save_and_process_avatar
165
+ flash[:notice] = "User being created"
166
+ redirect_to :action => :index
167
+ else
168
+ render :new
169
+ end
170
+ end
171
+
172
+ ### Background processing
173
+
174
+ Now that the basic building blocks are in place you can process and save your avatar in the background. This example uses [Resque](https://github.com/defunkt/resque) but the same logic could be applied to [DelayedJob](https://github.com/collectiveidea/delayed_job) or any other background processor.
175
+
176
+ class User < ActiveRecord::Base
177
+ def save_and_process_avatar(options = {})
178
+ if options[:now]
179
+ self.remote_avatar_url = avatar.direct_fog_url(:with_path => true)
180
+ save
181
+ else
182
+ Resque.enqueue(AvatarProcessor, attributes)
183
+ end
184
+ end
185
+ end
186
+
187
+ class AvatarProcessor
188
+ @queue = :avatar_processor_queue
189
+
190
+ def self.perform(attributes)
191
+ user = User.new(attributes)
192
+ user.save_and_process_avatar(:now => true)
193
+ end
194
+ end
195
+
196
+ The method `self.remote_avatar_url=` from [CarrierWave](https://github.com/jnicklas/carrierwave) downloads the avatar from S3 and processes it. `save` then re-uploads the processed avatar to to S3
197
+
198
+ ## Uploading from a remote location
199
+
200
+ Your users may find it convenient to upload a file from a location on the Internet via a URL. CarrierWaveDirect gives you another accessor to achieve this.
201
+
202
+ <%= form_for @user do |f| %>
203
+ <%= f.hidden_field :key %>
204
+ <% unless @hangover.has_avatar_upload? %>
205
+ <%= f.label :remote_avatar_net_url %>
206
+ <%= f.text_field :remote_avatar_net_url %>
207
+ <%= f.submit %>
208
+ <% end %>
209
+
210
+
211
+ class User < ActiveRecord::Base
212
+ def save_and_process_avatar(options = {})
213
+ if options[:now]
214
+ self.remote_avatar_url = has_remote_avatar_net_url? ? remote_avatar_net_url : avatar.direct_fog_url(:with_path => true)
215
+ save
216
+ else
217
+ Resque.enqueue(AvatarProcessor, attributes)
218
+ end
219
+ end
220
+ end
221
+
222
+ The methods `has_avatar_upload?`, `remote_avatar_net_url` and `has_remote_avatar_net_url?` are automatically added to your mounted model
223
+
224
+ ## Validations
225
+
226
+ Along with validating the extension of the filename, CarrierWaveDirect also gives you some other validations:
227
+
228
+ validates :avatar :is_uploaded => true
229
+
230
+ Validates that your mounted model has an avatar uploaded from file or specified by remote url. It does not check that an your mounted model actually has a valid avatar after the download has taken place. Turned *off* by default
231
+
232
+ validates :avatar, :is_attached => true
233
+
234
+ Validates that your mounted model has an avatar attached. This checks whether there is an actual avatar attached to the mounted model after downloading. Turned *off* by default
235
+
236
+ validates :avatar, :filename_uniqueness => true
237
+
238
+ Validates that the filename in the database is unique. Turned *on* by default
239
+
240
+ validates :avatar :filename_format => true
241
+
242
+ Validates that the uploaded filename is valid. As well as validating the extension against the `extension_white_list` it also validates that the `upload_dir` is correct. Turned *on* by default
243
+
244
+ validates :avatar :remote_net_url_format => true
245
+
246
+ Validates that the remote net url is valid. As well as validating the extension against the `extension_white_list` it also validates that url is valid and has only the schemes specified in the `url_scheme_whitelist`. Turned *on* by default
247
+
248
+ ## Configuration
249
+
250
+ As well as the built in validations CarrierWaveDirect provides, some validations, such as max file size and upload expiration can be performed on the S3 side.
251
+
252
+ CarrierWave.configure do |config|
253
+ config.validate_is_attached = true # defaults to false
254
+ config.validate_is_uploaded = true # defaults to false
255
+ config.validate_unique_filename = false # defaults to true
256
+ config.validate_filename_format = false # defaults to true
257
+ config.validate_remote_net_url_format = false # defaults to true
258
+
259
+ config.max_file_size = 10.megabytes # defaults to 5.megabytes
260
+ config.upload_expiration = 1.hour # defaults to 10.hours
261
+ end
262
+
263
+ ## Testing with CarrierWaveDirect
264
+
265
+ CarrierWaveDirect provides a couple of helpers to help with integration and unit testing. You don't want to contact the Internet during your tests as this is slow, expensive and unreliable. You should first put fog into mock mode by doing something like this.
266
+
267
+ Fog.mock!
268
+
269
+ def fog_directory
270
+ ENV['AWS_FOG_DIRECTORY']
271
+ end
272
+
273
+ connection = ::Fog::Storage.new(
274
+ :aws_access_key_id => ENV['AWS_ACCESS_KEY_ID'],
275
+ :aws_secret_access_key => ENV['AWS_SECRET_ACCESS_KEY'],
276
+ :provider => 'AWS'
277
+ )
278
+
279
+ connection.directories.create(:key => fog_directory)
280
+
281
+ ### Using Capybara
282
+
283
+ If your using Capybara with Cucumber or RSpec, CarrierWaveDirect gives you a few useful helpers. To get the Capybara helpers, include the module into your test file or helper
284
+
285
+ describe AvatarUploadSpec
286
+ include CarrierWaveDirect::Test::CapybaraHelpers
287
+ end
288
+
289
+ To attach a file to the direct upload form you can use
290
+
291
+ attach_file_for_direct_upload('path/to/file.ext')
292
+
293
+ To simulate a successful upload and redirect to S3 you can use
294
+
295
+ upload_directly(AvatarUploader, "Upload to S3")
296
+
297
+ This will click the Upload to S3 button on the form and redirect you to the `success_action_redirect` url (in the form) with a sample response from S3
298
+
299
+ To simulate an unsuccessful upload you can pass `:success => false` and you'll remain on the upload page e.g.
300
+
301
+ upload_directly(AvatarUploader, "Upload to S3", :success => false)
302
+
303
+ You can also use `find_key` and `find_upload_path` to get the key and upload path from the form
304
+
305
+ ### Unit tests
306
+
307
+ If your mounted model validates a file is uploaded you might want to make use of the `sample_key` method
308
+
309
+ include CarrierWaveDirect::Test::Helpers
310
+
311
+ Factory.define :user |f|
312
+ f.email "some1@example.com"
313
+ f.key { sample_key(AvatarUploader.new) }
314
+ end
315
+
316
+ This will return a valid key based off your `upload_dir` and your `extension_white_list`
317
+
318
+ ### Faking a background download
319
+
320
+ If you wanted to fake a download in the background you could do something like this
321
+
322
+ uploader = AvatarUploader.new
323
+
324
+ upload_path = find_upload_path
325
+ redirect_key = sample_key(:base => find_key, :filename => File.basename(upload_path))
326
+
327
+ uploader.key = redirect_key
328
+ download_url = uploader.direct_fog_url(:with_path => true)
329
+
330
+ # Register the download url and return the uploaded file in the body
331
+ FakeWeb.register_uri(:get, download_url, :body => File.open(upload_path))
332
+
333
+ ## i18n
334
+
335
+ The Active Record validations use the Rails i18n framework. Add these keys to your translations file:
336
+
337
+ en:
338
+ errors:
339
+ messages:
340
+ carrierwave_direct_filename_taken: filename was already taken
341
+ carrierwave_direct_upload_missing: upload is missing
342
+ carrierwave_direct_attachment_missing: attachment is missing
343
+ carrierwave_direct_filename_invalid: is invalid. Allowed file types are ${extension_white_list}
344
+ carrierwave_direct_remote_net_url_invalid: is invalid. Allowed file types are ${extension_white_list}. Allowed url schemes are ${url_scheme_white_list}
345
+
346
+ ## Contributing to CarrierWaveDirect
347
+
348
+ Pull requests are very welcome. Before submitting a pull request, please make sure that your changes are well tested.
349
+
350
+ gem install bundler
351
+ bundle install
352
+
353
+ You should now be able to run the tests
354
+
355
+ bundle exec rake
356
+
@@ -0,0 +1,12 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake'
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc "Run all examples"
7
+ RSpec::Core::RakeTask.new(:spec) do |t|
8
+ t.rspec_opts = %w[--color]
9
+ end
10
+
11
+ task :default => [:spec]
12
+
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "carrierwave_direct/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "carrierwave_direct"
7
+ s.version = CarrierwaveDirect::VERSION
8
+ s.authors = ["David Wilkie"]
9
+ s.email = ["dwilkie@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Upload direct to S3 using CarrierWave}
12
+ s.description = %q{Process your uploads in the background by uploading directly to S3}
13
+
14
+ s.rubyforge_project = "carrierwave_direct"
15
+
16
+ s.add_dependency "carrierwave"
17
+ s.add_dependency "uuid"
18
+ s.add_dependency "fog"
19
+
20
+ s.add_development_dependency "rspec"
21
+ s.add_development_dependency "timecop"
22
+ s.add_development_dependency "rails"
23
+ s.add_development_dependency "sqlite3"
24
+ s.add_development_dependency "capybara"
25
+
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
29
+ s.require_paths = ["lib"]
30
+ end
31
+
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+ require "carrierwave_direct/version"
3
+
4
+ require "carrierwave"
5
+ require "fog"
6
+ require "uuid"
7
+
8
+ module CarrierWaveDirect
9
+
10
+ autoload :Uploader, "carrierwave_direct/uploader"
11
+ autoload :Mount, "carrierwave_direct/mount"
12
+
13
+ module Uploader
14
+ autoload :Configuration, 'carrierwave_direct/uploader/configuration'
15
+
16
+ CarrierWave::Uploader::Base.send(:include, Configuration)
17
+ end
18
+
19
+ module Test
20
+ autoload :Helpers, 'carrierwave_direct/test/helpers'
21
+ autoload :CapybaraHelpers, 'carrierwave_direct/test/capybara_helpers'
22
+ end
23
+ end
24
+
25
+ if defined?(Rails)
26
+ module CarrierWaveDirect
27
+ class Railtie < Rails::Railtie
28
+
29
+ initializer "carrierwave_direct.active_record" do
30
+ ActiveSupport.on_load :active_record do
31
+ require 'carrierwave_direct/orm/activerecord'
32
+ end
33
+ end
34
+
35
+ initializer "carrierwave_direct.action_view" do
36
+ ActiveSupport.on_load :action_view do
37
+ require 'carrierwave_direct/form_builder'
38
+ require 'carrierwave_direct/action_view_extensions/form_helper'
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+