paperclip-multiple 0.0.1
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 +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +9 -0
- data/Appraisals +7 -0
- data/CONTRIBUTING.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +119 -0
- data/Rakefile +14 -0
- data/gemfiles/paperclip_3.gemfile +7 -0
- data/gemfiles/paperclip_3.gemfile.lock +78 -0
- data/gemfiles/paperclip_4.gemfile +7 -0
- data/gemfiles/paperclip_4.gemfile.lock +78 -0
- data/lib/paperclip-multiple.rb +1 -0
- data/lib/paperclip/multiple.rb +2 -0
- data/lib/paperclip/multiple/version.rb +5 -0
- data/lib/paperclip/storage/multiple.rb +125 -0
- data/paperclip-multiple.gemspec +27 -0
- data/test/image.jpg +0 -0
- data/test/paperclip_multiple_test.rb +102 -0
- data/test/test_helper.rb +65 -0
- metadata +167 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 59329faad2cec8aaee7625407dbbf8db8b2c9192
|
4
|
+
data.tar.gz: 465bdbc82a38e1adb0ea416e552e9b773c311dff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 04c45a8cb502f29e5d72813ccc4a7aba9a5bfdbd2e09390fbcabeb1d3714ec5331fbfcb4974664f814732c7cab90019f72c47140ea2f5d98c90c18678f7a22bb
|
7
|
+
data.tar.gz: 2c2af57a1cab791917413bcc60384fd28513ef3317e6fb12867902fa4a352cfd620b158d633f93249d2e6afdf1147ab64e688ff7bca1ffe4d95d7d4277512920
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Appraisals
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
## Contributing to paperclip-multiple
|
2
|
+
|
3
|
+
If you read the README, you know we aren't using this library anymore. We'll probably take a look,
|
4
|
+
but beware that feature requests or bug reports without any code attached might not get a big response.
|
5
|
+
|
6
|
+
That said, if you still want to contribute:
|
7
|
+
|
8
|
+
* Thoroughly what the problem is, or the feature does.
|
9
|
+
* Include as much information as possible, like gem versions and your `has_attached_file` definitions.
|
10
|
+
* Follow conventions in the code and add tests.
|
11
|
+
|
12
|
+
If you want to take over the project and continue maintaining it, feel free to contact
|
13
|
+
[@mrsimo](https://github.com/mrsimo) or just open an issue.
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Albert Llop
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# paperclip-multiple
|
2
|
+
|
3
|
+
[](https://travis-ci.org/harvesthq/paperclip-multiple)
|
4
|
+
|
5
|
+
paperclip-multiple is a storage implementation for [Paperclip](https://github.com/thoughtbot/paperclip).
|
6
|
+
It aims to help migrating files from `filesystem` storage to [`fog`](http://fog.io) storage.
|
7
|
+
|
8
|
+
It provides the `multiple` storage which instantiates two `Attachments`, one using `filesystem` storage
|
9
|
+
and another using `fog` storage. From the moment the `multiple` storage is enabled, new files
|
10
|
+
will be stored on both locations while still displaying the files from the `filesystem`.
|
11
|
+
|
12
|
+
While paperclip-multiple helps you store files in two places, you will also want to sync
|
13
|
+
existing files. We used [`s3cmd`](http://s3tools.org/s3cmd) and
|
14
|
+
[`s3cmd sync`](http://s3tools.org/s3cmd-sync) to do this.
|
15
|
+
|
16
|
+
paperclip-multiple was prepared to migrate to S3. It was not prepared to:
|
17
|
+
|
18
|
+
* Migrate from S3 to filesystem.
|
19
|
+
* Migrate to use the `S3` backend instead of the `fog`.
|
20
|
+
|
21
|
+
Paperclip was not prepared to have multiple backends, so this little thing messes with
|
22
|
+
some of the internals of Paperclip.
|
23
|
+
|
24
|
+
## :warning: :warning: Warning :warning: :warning:
|
25
|
+
|
26
|
+
The nature of this software means that once we finished the migration, we aren't using it anymore.
|
27
|
+
|
28
|
+
It also messes with the internals of Paperclip, meaning it will possibly break in future
|
29
|
+
releases. We are not going to actively maintain this library, but we wanted to offer this code to
|
30
|
+
everyone that might find a use for it. We struggled to find any useful code in this regard, and
|
31
|
+
even an old version of what you see here, paperclip-multiple, would have been appreciated.
|
32
|
+
|
33
|
+
Feel free to ask questions or fork this code. For the reasons stated before, we might not be able
|
34
|
+
to provide with very good support. However, if there's something very obvious or broken and want
|
35
|
+
to contribute with a nice Pull Request, we'll do our best.
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
Add paperclip-multiple to your Gemfile.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
gem 'paperclip-multiple', github: 'harvesthq/paperclip-multiple'
|
43
|
+
```
|
44
|
+
|
45
|
+
Make sure you have valid settings for both the filesystem and the fog storages. Let's suppose
|
46
|
+
you have information available in a constant like this:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
PAPERCLIP_SETTINGS = {
|
50
|
+
fog_credentials: {
|
51
|
+
aws_access_key_id: "whatever",
|
52
|
+
aws_secret_access_key: "whatever",
|
53
|
+
provider: "AWS"
|
54
|
+
},
|
55
|
+
fog_public: true,
|
56
|
+
fog_directory: "bucket-name"
|
57
|
+
}
|
58
|
+
```
|
59
|
+
|
60
|
+
Use it in your `has_attached_file` definition:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
class User
|
64
|
+
has_attached_file :file, PAPERCLIP_SETTINGS.merge(
|
65
|
+
storage: :multiple,
|
66
|
+
path: ":compatible_rails_root/users/files/:user_id/:style.:extension",
|
67
|
+
url: "/uploads/users/files/:user_id/:style.:extension",
|
68
|
+
multiple_if: lambda { |user| user.company.s3_enabled? },
|
69
|
+
display_from_s3: lambda { |user| user.company.display_from_s3? }
|
70
|
+
)
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
Three things might surprise you, let me explain them:
|
75
|
+
|
76
|
+
### What's that thing in your path?
|
77
|
+
|
78
|
+
You must not forget that Paperclip will be used for both fog and filesystem. If your `path`
|
79
|
+
option contained some absolute path, you'll have to tweak it to make it work with both storages.
|
80
|
+
|
81
|
+
This is what our `:compatible_rails_root` interpolation looks like:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
Paperclip.interpolates(:compatible_rails_root) do |attachment, _|
|
85
|
+
if attachment.options[:storage] == :fog
|
86
|
+
'uploads'
|
87
|
+
else
|
88
|
+
"#{rails_root(attachment, _)}/public/uploads"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
Be sure to test this in some staging environment. It's not trivial to get the slashes right!
|
94
|
+
|
95
|
+
### multiple_if
|
96
|
+
|
97
|
+
At Harvest we like to rollout things in slow, metered releases. The `multiple_if` option allows
|
98
|
+
you to define when an instance will indeed start using the multiple attachment. If this block
|
99
|
+
returns `false`, then it will work as if the `filesystem` backend was used.
|
100
|
+
|
101
|
+
### display_from_s3
|
102
|
+
|
103
|
+
This is the counterpart to the previous option. Multiple storage mainly delegates around
|
104
|
+
to the real `Attachment`. When you call `url` on your attachment, the multiple storage simply
|
105
|
+
calls the `Attachment` with the `filesystem` storage configured. If this block returns `true`,
|
106
|
+
it will call `url` on the `Attachment` with `fog` storage. A totally random example:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
user.company.display_from_s3? # => false
|
110
|
+
user.file.url # => '/uploads/users/files/1234/original.jpg'
|
111
|
+
|
112
|
+
user.company.display_from_s3? # => true
|
113
|
+
user.file.url # => 'https://whatever.s3.amazonaws.com/uploads/users/files/1234/original.jpg'
|
114
|
+
```
|
115
|
+
|
116
|
+
## Credits
|
117
|
+
|
118
|
+
[@mrsimo](https://github.com/mrsimo) built this to solve one of the multiple problems we face every day at
|
119
|
+
[Harvest](http://www.getharvest.com). We're [quite definitely hiring](http://www.getharvest.com/careers)!
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'appraisal'
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/testtask'
|
6
|
+
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
Rake::TestTask.new do |t|
|
10
|
+
t.libs << 'test'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
Rake::Task['test'].comment = 'Run all tests'
|
@@ -0,0 +1,78 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
paperclip-multiple (0.0.1)
|
5
|
+
paperclip
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.0.3)
|
11
|
+
activesupport (= 4.0.3)
|
12
|
+
builder (~> 3.1.0)
|
13
|
+
activerecord (4.0.3)
|
14
|
+
activemodel (= 4.0.3)
|
15
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
16
|
+
activesupport (= 4.0.3)
|
17
|
+
arel (~> 4.0.0)
|
18
|
+
activerecord-deprecated_finders (1.0.3)
|
19
|
+
activesupport (4.0.3)
|
20
|
+
i18n (~> 0.6, >= 0.6.4)
|
21
|
+
minitest (~> 4.2)
|
22
|
+
multi_json (~> 1.3)
|
23
|
+
thread_safe (~> 0.1)
|
24
|
+
tzinfo (~> 0.3.37)
|
25
|
+
appraisal (0.5.2)
|
26
|
+
bundler
|
27
|
+
rake
|
28
|
+
arel (4.0.2)
|
29
|
+
atomic (1.1.14)
|
30
|
+
builder (3.1.4)
|
31
|
+
climate_control (0.0.3)
|
32
|
+
activesupport (>= 3.0)
|
33
|
+
cocaine (0.5.3)
|
34
|
+
climate_control (>= 0.0.3, < 1.0)
|
35
|
+
excon (0.31.0)
|
36
|
+
fog (1.20.0)
|
37
|
+
builder
|
38
|
+
excon (~> 0.31.0)
|
39
|
+
formatador (~> 0.2.0)
|
40
|
+
mime-types
|
41
|
+
multi_json (~> 1.0)
|
42
|
+
net-scp (~> 1.1)
|
43
|
+
net-ssh (>= 2.1.3)
|
44
|
+
nokogiri (>= 1.5.11)
|
45
|
+
formatador (0.2.4)
|
46
|
+
i18n (0.6.9)
|
47
|
+
mime-types (2.1)
|
48
|
+
mini_portile (0.5.2)
|
49
|
+
minitest (4.7.5)
|
50
|
+
multi_json (1.8.4)
|
51
|
+
net-scp (1.1.2)
|
52
|
+
net-ssh (>= 2.6.5)
|
53
|
+
net-ssh (2.8.0)
|
54
|
+
nokogiri (1.6.1)
|
55
|
+
mini_portile (~> 0.5.0)
|
56
|
+
paperclip (3.5.4)
|
57
|
+
activemodel (>= 3.0.0)
|
58
|
+
activesupport (>= 3.0.0)
|
59
|
+
cocaine (~> 0.5.3)
|
60
|
+
mime-types
|
61
|
+
rake (10.1.1)
|
62
|
+
sqlite3 (1.3.8)
|
63
|
+
thread_safe (0.1.3)
|
64
|
+
atomic
|
65
|
+
tzinfo (0.3.38)
|
66
|
+
|
67
|
+
PLATFORMS
|
68
|
+
ruby
|
69
|
+
|
70
|
+
DEPENDENCIES
|
71
|
+
activerecord
|
72
|
+
appraisal
|
73
|
+
bundler
|
74
|
+
fog
|
75
|
+
paperclip (~> 3.5.0)
|
76
|
+
paperclip-multiple!
|
77
|
+
rake
|
78
|
+
sqlite3
|
@@ -0,0 +1,78 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
paperclip-multiple (0.0.1)
|
5
|
+
paperclip
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.0.3)
|
11
|
+
activesupport (= 4.0.3)
|
12
|
+
builder (~> 3.1.0)
|
13
|
+
activerecord (4.0.3)
|
14
|
+
activemodel (= 4.0.3)
|
15
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
16
|
+
activesupport (= 4.0.3)
|
17
|
+
arel (~> 4.0.0)
|
18
|
+
activerecord-deprecated_finders (1.0.3)
|
19
|
+
activesupport (4.0.3)
|
20
|
+
i18n (~> 0.6, >= 0.6.4)
|
21
|
+
minitest (~> 4.2)
|
22
|
+
multi_json (~> 1.3)
|
23
|
+
thread_safe (~> 0.1)
|
24
|
+
tzinfo (~> 0.3.37)
|
25
|
+
appraisal (0.5.2)
|
26
|
+
bundler
|
27
|
+
rake
|
28
|
+
arel (4.0.2)
|
29
|
+
atomic (1.1.15)
|
30
|
+
builder (3.1.4)
|
31
|
+
climate_control (0.0.3)
|
32
|
+
activesupport (>= 3.0)
|
33
|
+
cocaine (0.5.3)
|
34
|
+
climate_control (>= 0.0.3, < 1.0)
|
35
|
+
excon (0.31.0)
|
36
|
+
fog (1.20.0)
|
37
|
+
builder
|
38
|
+
excon (~> 0.31.0)
|
39
|
+
formatador (~> 0.2.0)
|
40
|
+
mime-types
|
41
|
+
multi_json (~> 1.0)
|
42
|
+
net-scp (~> 1.1)
|
43
|
+
net-ssh (>= 2.1.3)
|
44
|
+
nokogiri (>= 1.5.11)
|
45
|
+
formatador (0.2.4)
|
46
|
+
i18n (0.6.9)
|
47
|
+
mime-types (2.1)
|
48
|
+
mini_portile (0.5.2)
|
49
|
+
minitest (4.7.5)
|
50
|
+
multi_json (1.8.4)
|
51
|
+
net-scp (1.1.2)
|
52
|
+
net-ssh (>= 2.6.5)
|
53
|
+
net-ssh (2.8.0)
|
54
|
+
nokogiri (1.6.1)
|
55
|
+
mini_portile (~> 0.5.0)
|
56
|
+
paperclip (4.1.1)
|
57
|
+
activemodel (>= 3.0.0)
|
58
|
+
activesupport (>= 3.0.0)
|
59
|
+
cocaine (~> 0.5.3)
|
60
|
+
mime-types
|
61
|
+
rake (10.1.1)
|
62
|
+
sqlite3 (1.3.9)
|
63
|
+
thread_safe (0.2.0)
|
64
|
+
atomic (>= 1.1.7, < 2)
|
65
|
+
tzinfo (0.3.38)
|
66
|
+
|
67
|
+
PLATFORMS
|
68
|
+
ruby
|
69
|
+
|
70
|
+
DEPENDENCIES
|
71
|
+
activerecord
|
72
|
+
appraisal
|
73
|
+
bundler
|
74
|
+
fog
|
75
|
+
paperclip (~> 4.1.1)
|
76
|
+
paperclip-multiple!
|
77
|
+
rake
|
78
|
+
sqlite3
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'paperclip/multiple'
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Paperclip
|
2
|
+
module Storage
|
3
|
+
##
|
4
|
+
# This is a Paperclip storage to work simultaneously with a filesystem and fog backends.
|
5
|
+
#
|
6
|
+
# It's optimized to migrate from filesystem to s3 (with fog), so it's required that assets
|
7
|
+
# always have their filesystem version to begin with.
|
8
|
+
module Multiple
|
9
|
+
def self.extended attachment
|
10
|
+
if attachment.options[:multiple_if].call(attachment.instance)
|
11
|
+
attachment.instance_eval do
|
12
|
+
@filesystem = Attachment.new(attachment.name, attachment.instance, attachment.options.merge(storage: :filesystem))
|
13
|
+
@fog = Attachment.new(attachment.name, attachment.instance, attachment.options.merge(storage: :fog))
|
14
|
+
|
15
|
+
# `after_flush_writes` closes files and unlinks them, but we have to read them twice to save them
|
16
|
+
# on both storages, so we blank this one, and let the other attachment close the files.
|
17
|
+
def @fog.after_flush_writes; end
|
18
|
+
end
|
19
|
+
else
|
20
|
+
attachment.extend Filesystem
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def filesystem
|
25
|
+
@filesystem
|
26
|
+
end
|
27
|
+
|
28
|
+
def fog
|
29
|
+
@fog
|
30
|
+
end
|
31
|
+
|
32
|
+
def display_from_s3?
|
33
|
+
@options[:display_from_s3].call(instance) && use_multiple?
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# This defaults to the filesystem version, since it's a lot faster than querying s3.
|
38
|
+
def exists?(style_name = default_style)
|
39
|
+
filesystem.exists?
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Delegates to both filesystem and fog storages.
|
44
|
+
def flush_writes
|
45
|
+
sync(:@queued_for_write)
|
46
|
+
|
47
|
+
@fog.flush_writes
|
48
|
+
@filesystem.flush_writes
|
49
|
+
|
50
|
+
@queued_for_write = {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def queue_all_for_delete
|
54
|
+
if use_multiple? && file?
|
55
|
+
@filesystem.send :queue_some_for_delete, *all_styles
|
56
|
+
@fog.send :queue_some_for_delete, *all_styles
|
57
|
+
end
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Delegates to both filesystem and fog storages.
|
63
|
+
def flush_deletes
|
64
|
+
@filesystem.flush_deletes
|
65
|
+
begin
|
66
|
+
@fog.flush_deletes
|
67
|
+
rescue Excon::Errors::Error => e
|
68
|
+
log("There was an unexpected error while deleting file from fog: #{e}")
|
69
|
+
end
|
70
|
+
@queued_for_delete = []
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# This defaults to the filesystem version, since it's a lot faster than querying s3.
|
75
|
+
def copy_to_local_file(style, local_dest_path)
|
76
|
+
@filesystem.copy_to_local_file(style, local_dest_path)
|
77
|
+
end
|
78
|
+
|
79
|
+
def url(style_name = default_style, options = {})
|
80
|
+
if display_from_s3?
|
81
|
+
@fog.url(style_name, options)
|
82
|
+
elsif use_multiple?
|
83
|
+
@filesystem.url(style_name, options)
|
84
|
+
else
|
85
|
+
super
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def path(style_name = default_style)
|
90
|
+
if display_from_s3?
|
91
|
+
@fog.path(style_name)
|
92
|
+
elsif use_multiple?
|
93
|
+
@filesystem.path(style_name)
|
94
|
+
else
|
95
|
+
super
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# These two are needed for general fog working-around
|
101
|
+
def public_url(style = default_style)
|
102
|
+
@fog.public_url(style)
|
103
|
+
end
|
104
|
+
|
105
|
+
def expiring_url(time = (Time.now + 3600), style = default_style)
|
106
|
+
@fog.expiring_url(time, style)
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def all_styles
|
112
|
+
[:original, *styles.keys]
|
113
|
+
end
|
114
|
+
|
115
|
+
def sync(ivar)
|
116
|
+
@fog.instance_variable_set(ivar, instance_variable_get(ivar))
|
117
|
+
@filesystem.instance_variable_set(ivar, instance_variable_get(ivar))
|
118
|
+
end
|
119
|
+
|
120
|
+
def use_multiple?
|
121
|
+
@options[:multiple_if].call(instance)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'paperclip/multiple/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "paperclip-multiple"
|
8
|
+
spec.version = Paperclip::Multiple::VERSION
|
9
|
+
spec.authors = ["Albert Llop"]
|
10
|
+
spec.email = ["mrsimo@gmail.com"]
|
11
|
+
spec.summary = "Storage backend for Paperclip to help migrate from the filesystem to S3 with fog."
|
12
|
+
spec.homepage = "https://github.com/harvesthq/paperclip-multiple"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "paperclip"
|
21
|
+
spec.add_development_dependency "sqlite3"
|
22
|
+
spec.add_development_dependency "activerecord"
|
23
|
+
spec.add_development_dependency "fog"
|
24
|
+
spec.add_development_dependency "bundler"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "appraisal"
|
27
|
+
end
|
data/test/image.jpg
ADDED
Binary file
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require "paperclip/multiple"
|
3
|
+
|
4
|
+
class PaperclipMultipleTest < ActiveSupport::TestCase
|
5
|
+
setup do
|
6
|
+
User.s3_enabled = false
|
7
|
+
User.display_from_s3 = false
|
8
|
+
end
|
9
|
+
|
10
|
+
teardown do
|
11
|
+
User.delete_all
|
12
|
+
fog_directory.files.each(&:destroy) if fog_directory.files.size > 0
|
13
|
+
end
|
14
|
+
|
15
|
+
test "behaves as a normal filesystem attachment" do
|
16
|
+
@user = build_user
|
17
|
+
|
18
|
+
assert File.exists?(@user.avatar.path)
|
19
|
+
assert File.exists?(@user.avatar.path(:thumbnail))
|
20
|
+
|
21
|
+
assert_equal 0, fog_directory.files.size
|
22
|
+
assert_nil @user.avatar.filesystem
|
23
|
+
assert_nil @user.avatar.fog
|
24
|
+
end
|
25
|
+
|
26
|
+
test "stores a file both locally and remotely" do
|
27
|
+
User.s3_enabled = true
|
28
|
+
@user = build_user
|
29
|
+
|
30
|
+
assert File.exists?(@user.avatar.filesystem.path)
|
31
|
+
assert File.exists?(@user.avatar.filesystem.path(:thumbnail))
|
32
|
+
|
33
|
+
assert_equal 2, fog_directory.files.size
|
34
|
+
assert fog_directory.files.head(@user.avatar.fog.path).present?
|
35
|
+
assert fog_directory.files.head(@user.avatar.fog.path(:thumbnail)).present?
|
36
|
+
end
|
37
|
+
|
38
|
+
test "deleting deletes both" do
|
39
|
+
User.s3_enabled = true
|
40
|
+
@user = build_user
|
41
|
+
|
42
|
+
local_paths = [
|
43
|
+
@user.avatar.filesystem.path,
|
44
|
+
@user.avatar.filesystem.path(:thumbnail)
|
45
|
+
]
|
46
|
+
|
47
|
+
s3_paths = [
|
48
|
+
@user.avatar.fog.path,
|
49
|
+
@user.avatar.fog.path(:thumbnail)
|
50
|
+
]
|
51
|
+
|
52
|
+
assert_difference "fog_directory.files.all.size", -2 do
|
53
|
+
@user.destroy
|
54
|
+
end
|
55
|
+
|
56
|
+
assert !File.exists?(local_paths.first)
|
57
|
+
assert !File.exists?(local_paths.last)
|
58
|
+
|
59
|
+
assert fog_directory.files.head(s3_paths.first).blank?
|
60
|
+
assert fog_directory.files.head(s3_paths.last).blank?
|
61
|
+
end
|
62
|
+
|
63
|
+
test "returns a local url" do
|
64
|
+
User.s3_enabled = true
|
65
|
+
@user = build_user
|
66
|
+
|
67
|
+
assert_equal "/uploads/users/avatars/000/000/001/original/image.jpg", @user.avatar.url
|
68
|
+
assert_equal "/uploads/users/avatars/000/000/001/thumbnail/image.jpg", @user.avatar.url(:thumbnail)
|
69
|
+
end
|
70
|
+
|
71
|
+
test "returns an amazon url" do
|
72
|
+
User.s3_enabled = true
|
73
|
+
User.display_from_s3 = true
|
74
|
+
@user = build_user
|
75
|
+
|
76
|
+
assert_equal "https://#{FOG_DIRECTORY}.s3.amazonaws.com/uploads/users/avatars/000/000/001/original/image.jpg", @user.avatar.url
|
77
|
+
assert_equal "https://#{FOG_DIRECTORY}.s3.amazonaws.com/uploads/users/avatars/000/000/001/thumbnail/image.jpg", @user.avatar.url(:thumbnail)
|
78
|
+
end
|
79
|
+
|
80
|
+
test "returns a local url if display enabled but storage disabled" do
|
81
|
+
User.s3_enabled = false
|
82
|
+
User.display_from_s3 = true
|
83
|
+
@user = build_user
|
84
|
+
|
85
|
+
assert_equal "/uploads/users/avatars/000/000/001/original/image.jpg", @user.avatar.url
|
86
|
+
assert_equal "/uploads/users/avatars/000/000/001/thumbnail/image.jpg", @user.avatar.url(:thumbnail)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def build_user
|
92
|
+
User.create id: 1, avatar: File.open('test/image.jpg')
|
93
|
+
end
|
94
|
+
|
95
|
+
def fog_directory
|
96
|
+
@directory ||= fog_connection.directories.new(key: FOG_DIRECTORY)
|
97
|
+
end
|
98
|
+
|
99
|
+
def fog_connection
|
100
|
+
@connection ||= Fog::Storage.new FOG_CREDENTIALS
|
101
|
+
end
|
102
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'paperclip'
|
5
|
+
require 'paperclip-multiple'
|
6
|
+
require 'fog'
|
7
|
+
require 'active_support'
|
8
|
+
require 'active_record'
|
9
|
+
|
10
|
+
ActiveRecord::Base.establish_connection(
|
11
|
+
adapter: "sqlite3", database: ":memory:"
|
12
|
+
)
|
13
|
+
|
14
|
+
ActiveRecord::Schema.suppress_messages do
|
15
|
+
ActiveRecord::Schema.define version: 0 do
|
16
|
+
create_table :users, force: true do |t|
|
17
|
+
t.string :avatar_file_name
|
18
|
+
t.string :avatar_content_type
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Paperclip.options[:log] = false
|
24
|
+
|
25
|
+
Paperclip.interpolates(:compatible_path) do |attachment, _|
|
26
|
+
if attachment.options[:storage] == :fog
|
27
|
+
'uploads'
|
28
|
+
else
|
29
|
+
Dir.tmpdir
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Fog.mock!
|
34
|
+
|
35
|
+
FOG_CREDENTIALS = {
|
36
|
+
aws_access_key_id: "whatever",
|
37
|
+
aws_secret_access_key: "whatever",
|
38
|
+
provider: "AWS"
|
39
|
+
}
|
40
|
+
|
41
|
+
FOG_DIRECTORY = 'bucket-name'
|
42
|
+
|
43
|
+
class User < ActiveRecord::Base
|
44
|
+
include Paperclip::Glue
|
45
|
+
|
46
|
+
cattr_accessor :s3_enabled, :display_from_s3
|
47
|
+
|
48
|
+
has_attached_file :avatar, {
|
49
|
+
storage: :multiple,
|
50
|
+
|
51
|
+
fog_credentials: FOG_CREDENTIALS,
|
52
|
+
fog_public: true,
|
53
|
+
fog_directory: FOG_DIRECTORY,
|
54
|
+
|
55
|
+
styles: { thumbnail: '100x100>' },
|
56
|
+
|
57
|
+
path: ":compatible_path/:class/:attachment/:id_partition/:style/:filename",
|
58
|
+
url: "/uploads/:class/:attachment/:id_partition/:style/:filename",
|
59
|
+
|
60
|
+
multiple_if: lambda { |user| User.s3_enabled },
|
61
|
+
display_from_s3: lambda { |user| User.display_from_s3 }
|
62
|
+
}
|
63
|
+
|
64
|
+
validates_attachment_content_type :avatar, :content_type => /\Aimage/
|
65
|
+
end
|
metadata
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: paperclip-multiple
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Albert Llop
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: paperclip
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sqlite3
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activerecord
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: fog
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: appraisal
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- mrsimo@gmail.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- ".travis.yml"
|
120
|
+
- Appraisals
|
121
|
+
- CONTRIBUTING.md
|
122
|
+
- Gemfile
|
123
|
+
- LICENSE.txt
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- gemfiles/paperclip_3.gemfile
|
127
|
+
- gemfiles/paperclip_3.gemfile.lock
|
128
|
+
- gemfiles/paperclip_4.gemfile
|
129
|
+
- gemfiles/paperclip_4.gemfile.lock
|
130
|
+
- lib/paperclip-multiple.rb
|
131
|
+
- lib/paperclip/multiple.rb
|
132
|
+
- lib/paperclip/multiple/version.rb
|
133
|
+
- lib/paperclip/storage/multiple.rb
|
134
|
+
- paperclip-multiple.gemspec
|
135
|
+
- test/image.jpg
|
136
|
+
- test/paperclip_multiple_test.rb
|
137
|
+
- test/test_helper.rb
|
138
|
+
homepage: https://github.com/harvesthq/paperclip-multiple
|
139
|
+
licenses:
|
140
|
+
- MIT
|
141
|
+
metadata: {}
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
requirements: []
|
157
|
+
rubyforge_project:
|
158
|
+
rubygems_version: 2.2.2
|
159
|
+
signing_key:
|
160
|
+
specification_version: 4
|
161
|
+
summary: Storage backend for Paperclip to help migrate from the filesystem to S3 with
|
162
|
+
fog.
|
163
|
+
test_files:
|
164
|
+
- test/image.jpg
|
165
|
+
- test/paperclip_multiple_test.rb
|
166
|
+
- test/test_helper.rb
|
167
|
+
has_rdoc:
|