t_bird 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +8 -0
- data/README.md +156 -7
- data/lib/t_bird/namer.rb +3 -2
- data/lib/t_bird/processor.rb +22 -15
- data/lib/t_bird/transmitter.rb +7 -3
- data/lib/t_bird/uploader.rb +40 -10
- data/lib/t_bird/version.rb +1 -1
- data/spec/spec_helper.rb +0 -2
- data/spec/t_bird/processor_spec.rb +14 -15
- data/spec/t_bird/transmitter_spec.rb +9 -2
- data/spec/t_bird/uploader_spec.rb +41 -8
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 165550328a315a0ceb17e4176ec881efbc24c091
|
4
|
+
data.tar.gz: 0f66fcca8da662fb05d55a3575ac8608e49f82b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38fa5f3ff9c0d32d372d837d8fe9619d9e59216caa75b8085e2d8f2e541963067ace76a422af46cdf18427fdf12b09ab6df727f0b94c7aab04131eb59b912ecb
|
7
|
+
data.tar.gz: 17fa255fbc9856f9d653e040ba860de76a3064e19d57b0cc0a3123407c14ce2014b3dd8cf6369a681252538df98d074664aeab527743d28a03f50a177bce88b5
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
-
#
|
1
|
+
# t_bird
|
2
2
|
|
3
|
-
|
3
|
+
Uploading is... fun, fun, fun, until daddy takes the `t_bird` away.
|
4
|
+
|
5
|
+
|
6
|
+
## Project Status
|
7
|
+
|
8
|
+
- Build: [![Build Status](https://secure.travis-ci.org/xentek/t_bird.png)](http://travis-ci.org/xentek/t_bird)
|
9
|
+
- Dependencies: [![Dependency Status](https://gemnasium.com/xentek/t_bird.png)](https://gemnasium.com/xentek/t_bird)
|
10
|
+
- Code Quality: [![Code Climate](https://d3s6mut3hikguw.cloudfront.net/github/xentek/t_bird.png)](https://codeclimate.com/github/xentek/t_bird)
|
4
11
|
|
5
12
|
## Installation
|
6
13
|
|
@@ -15,8 +22,8 @@ And then execute:
|
|
15
22
|
## Why?
|
16
23
|
|
17
24
|
I became frustrated by the very coupled design of the leading ruby
|
18
|
-
upload libraries and wanted something that was a
|
19
|
-
allow for flexibility), wasn't glued to a `model
|
25
|
+
upload libraries and wanted something that was a more modular (to
|
26
|
+
allow for flexibility), wasn't glued to a `model`, and fulfilled my
|
20
27
|
most common use case out of the box:
|
21
28
|
|
22
29
|
- Upload an image, posted from a multi-part form,
|
@@ -26,7 +33,146 @@ most common use case out of the box:
|
|
26
33
|
|
27
34
|
## Usage
|
28
35
|
|
29
|
-
|
36
|
+
First, configure `t_bird` with a few settings:
|
37
|
+
|
38
|
+
````ruby
|
39
|
+
TBird::Configuration.configure |config|
|
40
|
+
config.aws_key 'amazon access key id'
|
41
|
+
config.aws_secret 'amazon secret access key'
|
42
|
+
config.aws_bucket 'name of s3 bucket that already exists'
|
43
|
+
config.thumbnail_size 100
|
44
|
+
end
|
45
|
+
````
|
46
|
+
Place this code so that it runs when your app boots.
|
47
|
+
|
48
|
+
- If you're using Sinatra or Rack, `config.ru` is probably a good spot.
|
49
|
+
- If you're using Rails, put the configuration in an initializer, e.g. `config/initializers/t_bird.rb`.
|
50
|
+
|
51
|
+
Then, assuming you have a multipart form like this:
|
52
|
+
|
53
|
+
````ruby
|
54
|
+
form enctype="multipart/form-data" method="POST"
|
55
|
+
input type="file" name="brand[image]"
|
56
|
+
input type="submit"
|
57
|
+
````
|
58
|
+
_Example is using [slim](http://slim-lang.com) for clarity and terseness, but that doesn't mean you have to._
|
59
|
+
|
60
|
+
In the action the form posts to, grab the uploaded file and upload it with `t_bird`:
|
61
|
+
|
62
|
+
````ruby
|
63
|
+
uploader = TBird::Uploader.new(params[:brand][:image])
|
64
|
+
|
65
|
+
uploader.upload! # return value is same as uploader.uploads
|
66
|
+
|
67
|
+
uploader.uploads # returns a hash of urls pointing to your image versions
|
68
|
+
# store this in the way that makes the most sense for your app
|
69
|
+
````
|
70
|
+
|
71
|
+
- By default, there are two `versions` defined: `:thumbnail` and `:original`.
|
72
|
+
- `:thumbnail` will create crop a square image from the upload.
|
73
|
+
- You can control the size of the square by setting `thumbnail_size` in your configuration (see above).
|
74
|
+
- `:original` will be the raw, unadulterated file that the user submitted.
|
75
|
+
|
76
|
+
## Options
|
77
|
+
|
78
|
+
There are three options you can pass into your `TBird::Uploader` instance:
|
79
|
+
|
80
|
+
- `:identifer`
|
81
|
+
- app specific identifer for his upload, e.g. your model's databae ID, timestamp, whatever
|
82
|
+
- used as the folder this file's uploads are stored in on s3
|
83
|
+
- you can pass a path fragment here to create a folder heirarchy, e.g. 'uploads/images'
|
84
|
+
- don't want any folders?, just pass an empty string, e.g. `''`
|
85
|
+
- defaults to a `SHA1` digest of the `original_filename`
|
86
|
+
- value should be URL safe, no encoding is done for you by `t_bird`
|
87
|
+
- `:token`
|
88
|
+
- needs to be a unique value per upload, to help avoid name collisions and writing over existing files
|
89
|
+
- used as part of the filename, version and extension are
|
90
|
+
automatically appended on to the end.
|
91
|
+
- defaults to a [UUID](http://en.wikipedia.org/wiki/Universally_unique_identifier), think hard before straying from this strategy
|
92
|
+
- value should be URL safe, no encoding is done for you by `t_bird`
|
93
|
+
- `:metadata`
|
94
|
+
- value must be a hash, as it will be merged in when your uploader is instantiated
|
95
|
+
- the file's `content_type` is automatically added to this hash
|
96
|
+
- values in this hash will be stored, with your file, on S3 as metadata.
|
97
|
+
- this could be used for versioning your files, e.g. `version: 2` , marking them with
|
98
|
+
the name/title of the model it's associated with, etc.
|
99
|
+
- `t_bird` doesn't provide any read access to this metadata, once it's stored on S3, so it's up to you to use S3's API to do anything with it.
|
100
|
+
|
101
|
+
## Custom Uploaders
|
102
|
+
|
103
|
+
In order to define custom versions, and other advanced customization, create a subclass of `TBird::Uploader`:
|
104
|
+
|
105
|
+
````ruby
|
106
|
+
class FileUploader < TBird::Uploader
|
107
|
+
version :original do
|
108
|
+
->(file) { file.original }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
````
|
112
|
+
|
113
|
+
- In this simple example we have created a subclass and defined a single
|
114
|
+
version, `:original`, that skips processing the file, making `FileUploader` suitable
|
115
|
+
for handling non-image uploads.
|
116
|
+
- No other versions were defined, so only `:original` will be created.
|
117
|
+
- In other words, if you create a subclass without any `versions`
|
118
|
+
defined, your uploader won't upload anything.
|
119
|
+
|
120
|
+
Here's a more complex example that defines several custom `versions`:
|
121
|
+
|
122
|
+
````ruby
|
123
|
+
class ComplexUploader < TBird::Uploader
|
124
|
+
|
125
|
+
# resizes image to a *width* of 200px, maintains aspect ratio
|
126
|
+
version :small do
|
127
|
+
->(img) { img.resize '200' }
|
128
|
+
end
|
129
|
+
|
130
|
+
# resizes image to a *height* of 300px, maintains aspect ratio
|
131
|
+
version :large do
|
132
|
+
->(img) { img.resize 'x300' }
|
133
|
+
end
|
134
|
+
|
135
|
+
# resizes image to a *height* of 300px, maintains aspect ratio
|
136
|
+
version :resized do
|
137
|
+
->(img) { img.resize '500x300' }
|
138
|
+
end
|
139
|
+
|
140
|
+
# custom thumbnail size, allowing your to ignore TBird::Configuration.thumbnail_size
|
141
|
+
# on an uploader by uploader basis
|
142
|
+
version :thumbnail do
|
143
|
+
->(img) { img.thumbnail 200 }
|
144
|
+
end
|
145
|
+
|
146
|
+
# want MOAR power?
|
147
|
+
# use process, which gets you inside a MiniMagick::Image#combine_options block
|
148
|
+
version :complex do
|
149
|
+
lambda do |img|
|
150
|
+
img.process do |magick|
|
151
|
+
magick.sample "50%"
|
152
|
+
magick.rotate "-90>"
|
153
|
+
magick.quality '88'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
````
|
159
|
+
For more information on `MiniMagick::Image#combine_options`, refer to the [mini_magick docs](https://github.com/minimagick/minimagick/blob/master/README.md).
|
160
|
+
|
161
|
+
:skull: `TBird::Processor#process` can be a _very sharp stick_, so carefully test your processing code with a variety of files in a non-production environment, and as usual be wary of using values from outside of your class as input for your processing routines.
|
162
|
+
|
163
|
+
## More customization options
|
164
|
+
|
165
|
+
- Redefine the `namer` method in your subclass to switch out the object that's responsible for generating the name used on S3.
|
166
|
+
- Your object must respond to `new_name`, and take the `version`, which will be a symbol, as input.
|
167
|
+
- Redefine the `processor` method in your subclass to switch out the processing library.
|
168
|
+
- Blocks defined by the `version` macro will be passed an instance of your `processor`,
|
169
|
+
so be sure that your `processor` will respond to any methods the code inside your blocks call on it.
|
170
|
+
- Redefine the `upload!` to remix the whole show!
|
171
|
+
- really the sky's the limit here, but at some point you might be
|
172
|
+
better of just writing your own uploader class.
|
173
|
+
- Currently this is the only way to swith out S3 for another storage provider.
|
174
|
+
- Better support changing the storage mechanism will likely come out in a future version.
|
175
|
+
- Talk to me before working on a patch for this, so we can agree on implementation.
|
30
176
|
|
31
177
|
## Contributing
|
32
178
|
|
@@ -35,7 +181,10 @@ _Coming Soon_
|
|
35
181
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
36
182
|
4. Push to the branch (`git push origin my-new-feature`)
|
37
183
|
5. Create new Pull Request
|
184
|
+
6. ???
|
185
|
+
7. Profit!
|
38
186
|
|
39
|
-
|
187
|
+
#### Colophon
|
40
188
|
|
41
|
-
|
189
|
+
- This project was not named after the Thunderbird model of car. Ford Motor Company does not have any connection to, or responsibility for this project, and does not endorse it in any way (or even know it exists).
|
190
|
+
- Sample image, used in tests, was provided by [cherrylet](http://www.flickr.com/photos/cherrylet/10258332985/sizes/o/in/photostream/), under the Creative Commons 2.0. No endorsement of this library by the photographer is intended or implied.
|
data/lib/t_bird/namer.rb
CHANGED
@@ -2,14 +2,15 @@
|
|
2
2
|
|
3
3
|
require 'digest/sha1'
|
4
4
|
require 'pathname'
|
5
|
+
require 'securerandom'
|
5
6
|
|
6
7
|
module TBird
|
7
8
|
class Namer
|
8
9
|
attr_reader :ext, :identifier, :token
|
9
|
-
def initialize(original_filename, identifier = nil, token =
|
10
|
+
def initialize(original_filename, identifier = nil, token = nil)
|
10
11
|
@ext = Pathname.new(original_filename).extname
|
11
12
|
@identifier = identifier || Digest::SHA1.hexdigest(original_filename)
|
12
|
-
@token = token
|
13
|
+
@token = token || SecureRandom.uuid
|
13
14
|
end
|
14
15
|
|
15
16
|
def new_name(version = 'original')
|
data/lib/t_bird/processor.rb
CHANGED
@@ -4,34 +4,41 @@ require 'mini_magick'
|
|
4
4
|
|
5
5
|
module TBird
|
6
6
|
class Processor
|
7
|
-
attr_reader :image
|
7
|
+
attr_reader :image
|
8
8
|
def initialize(file_blob)
|
9
9
|
@image = MiniMagick::Image.read(file_blob)
|
10
|
-
@thumbnail_size = Configuration.thumbnail_size
|
11
10
|
end
|
12
11
|
|
13
12
|
def process(&block)
|
14
|
-
image.combine_options do |
|
15
|
-
block.call(
|
13
|
+
image.combine_options do |img|
|
14
|
+
block.call(img) if block_given?
|
16
15
|
end
|
17
16
|
image
|
18
17
|
end
|
19
18
|
|
19
|
+
def stream
|
20
|
+
image.write StringIO.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def original
|
24
|
+
image # noop
|
25
|
+
end
|
26
|
+
|
20
27
|
def resize(size)
|
21
|
-
process do |
|
22
|
-
|
28
|
+
process do |img|
|
29
|
+
img.resize size
|
23
30
|
end
|
24
31
|
end
|
25
32
|
|
26
|
-
def thumbnail
|
27
|
-
process do |
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
def thumbnail(thumbnail_size = Configuration.thumbnail_size)
|
34
|
+
process do |img|
|
35
|
+
img.auto_orient
|
36
|
+
img.thumbnail "x#{thumbnail_size*2}"
|
37
|
+
img.resize "#{thumbnail_size*2}x<"
|
38
|
+
img.resize "50%"
|
39
|
+
img.gravity "center"
|
40
|
+
img.crop "#{thumbnail_size}x#{thumbnail_size}+0+0"
|
41
|
+
img.quality 92
|
35
42
|
end
|
36
43
|
end
|
37
44
|
end
|
data/lib/t_bird/transmitter.rb
CHANGED
@@ -10,15 +10,19 @@ module TBird
|
|
10
10
|
@name = name
|
11
11
|
@file = file
|
12
12
|
@metadata = default_metadata.merge(metadata)
|
13
|
+
connect!
|
13
14
|
end
|
14
15
|
|
15
16
|
def transmit!
|
16
|
-
|
17
|
-
|
17
|
+
if @transmission.nil?
|
18
|
+
@transmission = S3Object.store(name, file, Configuration.aws_bucket, metadata)
|
19
|
+
end
|
20
|
+
@success ||= Service.response.success?
|
21
|
+
@success
|
18
22
|
end
|
19
23
|
|
20
24
|
def url
|
21
|
-
@
|
25
|
+
@url ||= S3Object.url_for(name, Configuration.aws_bucket, authenticated: false, use_ssl: true)
|
22
26
|
end
|
23
27
|
|
24
28
|
private
|
data/lib/t_bird/uploader.rb
CHANGED
@@ -1,29 +1,59 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module TBird
|
4
|
-
class
|
5
|
-
attr_reader :options
|
4
|
+
class Uploader
|
5
|
+
attr_reader :file, :options, :uploads, :content_type, :original_filename
|
6
6
|
def initialize(file, options = {})
|
7
|
-
@options = default_options.merge(options)
|
8
7
|
@file = file
|
8
|
+
@options = default_options.merge(options)
|
9
|
+
@uploads = {}
|
10
|
+
@content_type = @file.content_type
|
11
|
+
@original_filename = @file.original_filename
|
12
|
+
@options[:metadata].merge!(content_type: content_type)
|
13
|
+
end
|
14
|
+
|
15
|
+
def namer
|
16
|
+
@namer ||= Namer.new(original_filename, options[:identifier], options[:token])
|
9
17
|
end
|
10
18
|
|
11
|
-
def
|
12
|
-
@
|
19
|
+
def processor
|
20
|
+
@processor ||= Processor.new(@file)
|
13
21
|
end
|
14
22
|
|
15
|
-
def
|
16
|
-
|
23
|
+
def upload!
|
24
|
+
versions.each do |version,block|
|
25
|
+
block.call(processor)
|
26
|
+
transmission = Transmitter.new(namer.new_name(version), processor.stream, options[:metadata])
|
27
|
+
transmission.transmit!
|
28
|
+
@uploads[version] = transmission.url
|
29
|
+
end
|
30
|
+
uploads
|
17
31
|
end
|
18
32
|
|
19
|
-
def
|
20
|
-
|
33
|
+
def versions
|
34
|
+
self.class.versions
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.versions
|
38
|
+
@versions ||= {
|
39
|
+
thumbnail: ->(img) { img.thumbnail },
|
40
|
+
original: ->(img) { img.original }
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.version(name, &block)
|
45
|
+
@versions ||= {}
|
46
|
+
@versions[name.to_sym] = block
|
21
47
|
end
|
22
48
|
|
23
49
|
private
|
24
50
|
|
25
51
|
def default_options
|
26
|
-
{
|
52
|
+
{
|
53
|
+
identifier: nil,
|
54
|
+
token: nil,
|
55
|
+
metadata: {}
|
56
|
+
}
|
27
57
|
end
|
28
58
|
end
|
29
59
|
end
|
data/lib/t_bird/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -6,38 +6,37 @@ describe TBird::Processor do
|
|
6
6
|
include TBirdSpecData
|
7
7
|
|
8
8
|
before do
|
9
|
-
@custom_process =
|
10
|
-
magick.resize '50%'
|
11
|
-
magick.quality 88
|
12
|
-
end
|
13
|
-
|
9
|
+
@custom_process = ->(img) { img.quality 88 }
|
14
10
|
@processor = TBird::Processor.new(upload_file)
|
15
|
-
@versions = {
|
16
|
-
thumb: '90x90',
|
17
|
-
medium: '300',
|
18
|
-
large: 'x300',
|
19
|
-
custom: @custom_process
|
20
|
-
}
|
21
11
|
end
|
22
12
|
|
23
13
|
it "can process an image" do
|
24
14
|
image = @processor.process(&@custom_process)
|
25
|
-
image.write StringIO.new
|
26
15
|
image.valid?.must_equal true
|
27
16
|
image.destroy!
|
28
17
|
end
|
29
18
|
|
30
|
-
it "can
|
19
|
+
it "can thumbnail an image" do
|
31
20
|
image = @processor.thumbnail
|
32
|
-
image.write StringIO.new
|
33
21
|
image.valid?.must_equal true
|
34
22
|
image.destroy!
|
35
23
|
end
|
36
24
|
|
37
25
|
it "can resize an image" do
|
38
26
|
image = @processor.resize('300')
|
39
|
-
image.write StringIO.new
|
40
27
|
image.valid?.must_equal true
|
41
28
|
image.destroy!
|
42
29
|
end
|
30
|
+
|
31
|
+
it "can return the original image" do
|
32
|
+
image = @processor.original
|
33
|
+
image.valid?.must_equal true
|
34
|
+
image.destroy!
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can write image to a stream" do
|
38
|
+
image = @processor.resize('x200')
|
39
|
+
@processor.stream.must_be_instance_of StringIO
|
40
|
+
image.destroy!
|
41
|
+
end
|
43
42
|
end
|
@@ -17,13 +17,20 @@ describe TBird::Transmitter do
|
|
17
17
|
|
18
18
|
AWS::S3::S3Object.stubs(:connected?).returns(true)
|
19
19
|
AWS::S3::Base.stubs(:establish_connection!).with(keys)
|
20
|
+
AWS::S3::Service.stubs(response: stub(:success? => true))
|
20
21
|
|
21
22
|
@stored_filename = '1/sample_original.jpg'
|
22
|
-
@
|
23
|
+
@upload = upload_file
|
24
|
+
@transmitter = TBird::Transmitter.new(@stored_filename, @upload, { content_type: 'image/jpeg' })
|
23
25
|
end
|
24
26
|
|
25
27
|
it "transmit file to store" do
|
26
|
-
AWS::S3::S3Object.expects(:store).with(@stored_filename,
|
28
|
+
AWS::S3::S3Object.expects(:store).with(@stored_filename, @upload, 'bucket', {:access => :public_read, :content_type => 'image/jpeg'})
|
27
29
|
@transmitter.transmit!
|
28
30
|
end
|
31
|
+
|
32
|
+
it "can return the S3 url for the file uploaded" do
|
33
|
+
AWS::S3::S3Object.expects(:url_for).with(@stored_filename, 'bucket', authenticated: false, use_ssl: true)
|
34
|
+
@transmitter.url
|
35
|
+
end
|
29
36
|
end
|
@@ -2,22 +2,55 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe TBird::
|
5
|
+
describe TBird::Uploader do
|
6
6
|
include TBirdSpecData
|
7
7
|
|
8
8
|
before do
|
9
|
-
@
|
9
|
+
@uploader = TBird::Uploader.new(upload_file)
|
10
|
+
@urls = ['https://s3.example.com/5a3f124f68b57512d3865da4c527f6b7bbd4173a/b54c0891-92a1-4641-8334-d396d5cc21c4_thumbnail.jpg',
|
11
|
+
'https://s3.example.com/5a3f124f68b57512d3865da4c527f6b7bbd4173a/b54c0891-92a1-4641-8334-d396d5cc21c4_original.jpg']
|
10
12
|
end
|
11
13
|
|
12
|
-
it "can return
|
13
|
-
@
|
14
|
+
it "can return the file" do
|
15
|
+
@uploader.file.wont_be_nil
|
14
16
|
end
|
15
17
|
|
16
|
-
it "can return
|
17
|
-
@
|
18
|
+
it "can return the options" do
|
19
|
+
@uploader.options.must_equal({ identifier: nil, token: nil, metadata: { content_type: "image/jpeg" } })
|
18
20
|
end
|
19
21
|
|
20
|
-
it "can return the
|
21
|
-
@
|
22
|
+
it "can return the content type" do
|
23
|
+
@uploader.content_type.must_equal 'image/jpeg'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "can return the original filename" do
|
27
|
+
@uploader.original_filename.must_equal 'sample.jpg'
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can return an instance of Processor" do
|
31
|
+
@uploader.processor.must_be_instance_of TBird::Processor
|
32
|
+
end
|
33
|
+
|
34
|
+
it "can return an instance of Namer" do
|
35
|
+
@uploader.namer.must_be_instance_of TBird::Namer
|
36
|
+
end
|
37
|
+
|
38
|
+
it "can return the registered versions" do
|
39
|
+
@uploader.versions.must_equal TBird::Uploader.versions
|
40
|
+
end
|
41
|
+
|
42
|
+
it "can upload all versions" do
|
43
|
+
TBird::Transmitter.any_instance.stubs(:transmit!)
|
44
|
+
TBird::Transmitter.any_instance.stubs(:url).returns(*@urls)
|
45
|
+
|
46
|
+
@uploader.upload!.must_equal({ thumbnail: @urls.first, original: @urls.last })
|
47
|
+
end
|
48
|
+
|
49
|
+
it "can return the upload urls" do
|
50
|
+
TBird::Transmitter.any_instance.stubs(:transmit!)
|
51
|
+
TBird::Transmitter.any_instance.stubs(:url).returns(*@urls)
|
52
|
+
|
53
|
+
@uploader.upload!
|
54
|
+
@uploader.uploads.must_equal({ thumbnail: @urls.first, original: @urls.last })
|
22
55
|
end
|
23
56
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: t_bird
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Marden
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-10-
|
11
|
+
date: 2013-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mini_magick
|
@@ -88,6 +88,7 @@ extensions: []
|
|
88
88
|
extra_rdoc_files: []
|
89
89
|
files:
|
90
90
|
- .gitignore
|
91
|
+
- .travis.yml
|
91
92
|
- Gemfile
|
92
93
|
- LICENSE.txt
|
93
94
|
- README.md
|