dynamic_paperclip 0.0.4 → 1.0.0a.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +33 -25
- data/lib/dynamic_paperclip/attachment.rb +5 -1
- data/lib/dynamic_paperclip/attachment_registry.rb +4 -0
- data/lib/dynamic_paperclip/attachment_style_generator.rb +73 -0
- data/lib/dynamic_paperclip/errors.rb +0 -9
- data/lib/dynamic_paperclip/has_attached_file.rb +3 -19
- data/lib/dynamic_paperclip/railtie.rb +9 -0
- data/lib/dynamic_paperclip/version.rb +1 -1
- data/lib/dynamic_paperclip.rb +6 -1
- data/lib/generators/dynamic_paperclip/install_generator.rb +1 -3
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/test.log +5561 -0
- data/test/generators/install_generator_test.rb +1 -3
- data/test/tmp/config/initializers/dynamic_paperclip.rb +1 -1
- data/test/unit/attachment_style_generator_test.rb +95 -0
- data/test/unit/attachment_test.rb +2 -2
- data/test/unit/has_attached_file_test.rb +4 -22
- metadata +68 -16
- data/app/controllers/dynamic_paperclip/attachment_styles_controller.rb +0 -43
- data/lib/dynamic_paperclip/engine.rb +0 -4
- data/test/controllers/attachment_style_controller_test.rb +0 -129
data/README.md
CHANGED
@@ -2,7 +2,7 @@ Dynamic Paperclip
|
|
2
2
|
=================
|
3
3
|
|
4
4
|
Dynamic Paperclip is an extension to the wonderful [Paperclip](http://github.com/thoughtbot/paperclip) gem
|
5
|
-
and
|
5
|
+
and Rack middleware that allows for the creation of Paperclip attachment styles on-the-fly.
|
6
6
|
|
7
7
|
Instead of defining your attachment styles in your model, Dynamic Paperclip allows you to generate URL's
|
8
8
|
for arbitrary attachment styles (usually in your view), effectively pushing style definition there.
|
@@ -14,10 +14,7 @@ Getting started
|
|
14
14
|
|
15
15
|
### Requirements
|
16
16
|
|
17
|
-
Dynamic Paperclip requires Paperclip 3.5.0 or above and
|
18
|
-
it's just untested at the moment).
|
19
|
-
|
20
|
-
It also only currently supports Paperclip's File Storage.
|
17
|
+
Dynamic Paperclip requires Paperclip 3.5.0 or above and only currently supports Paperclip's File Storage.
|
21
18
|
|
22
19
|
### Application
|
23
20
|
|
@@ -29,7 +26,7 @@ gem 'dynamic_paperclip'
|
|
29
26
|
|
30
27
|
Run the ``bundle`` command to install it.
|
31
28
|
|
32
|
-
After you install the gem, you need to run the generator:
|
29
|
+
After you install the gem, you need to run the generator if you're using Rails:
|
33
30
|
|
34
31
|
```console
|
35
32
|
rails generate dynamic_paperclip:install
|
@@ -38,6 +35,12 @@ rails generate dynamic_paperclip:install
|
|
38
35
|
This will install an initializer that sets up a secret key used in validating that dynamic asset URL's
|
39
36
|
originated from your application.
|
40
37
|
|
38
|
+
If you are not using Rails, you need to set ``DynamicPaperclip.config.secret`` to some random string during
|
39
|
+
your application's boot process, check out [install_generator.rb](lib/generators/dynamic_paperclip/install_generator.rb) to see how the Rails generator does it.
|
40
|
+
|
41
|
+
You'll also need to configure your application to use the ``DynamicPaperclip::AttachmentStyleGenerator`` Rack middleware
|
42
|
+
if you are not using Rails. This is configured automatically for Rails applications.
|
43
|
+
|
41
44
|
Now, you're ready to start using Dynamic Paperclip. Change any attachment definitions that you'd like to make dynamic from:
|
42
45
|
|
43
46
|
```ruby
|
@@ -65,9 +68,9 @@ passing it the style definition that you would normally define in the ``:styles`
|
|
65
68
|
|
66
69
|
### Server Configuration
|
67
70
|
|
68
|
-
If you're using
|
71
|
+
If you're using your application to serve static assets, then no configuration is required. But if you're not,
|
69
72
|
which you shouldn't be in any production environment, then you just need to make sure that your HTTP server
|
70
|
-
is configured to serve static assets if they exist, but pass the request along to your
|
73
|
+
is configured to serve static assets if they exist, but pass the request along to your application
|
71
74
|
if they do not.
|
72
75
|
|
73
76
|
For example, on Nginx, this would be accomplished with something along the lines of:
|
@@ -96,7 +99,7 @@ and is a pretty standard Nginx setup.
|
|
96
99
|
Why?
|
97
100
|
----
|
98
101
|
|
99
|
-
Because as your
|
102
|
+
Because as your application grows, you may discover that you have a large number of attachment styles. This is
|
100
103
|
slowing down your requests, because every time a user attempts to upload a file, Paperclip must process each and every
|
101
104
|
one of those styles right then and there, in the middle of the request.
|
102
105
|
|
@@ -106,10 +109,11 @@ that needs it, and not in the model.
|
|
106
109
|
How does this wizardry work?
|
107
110
|
---------------------------
|
108
111
|
|
109
|
-
It's pretty simple, actually.
|
110
|
-
|
112
|
+
It's pretty simple, actually. Dynamic Paperclip includes a piece of Rack middleware that intercepts requests
|
113
|
+
for URLs to any dynamic attachments, generates the requested style if it doesn't exist, and sends it
|
114
|
+
back to the browser.
|
111
115
|
|
112
|
-
For
|
116
|
+
For example, in your model, you may define a dynamic attachment like this:
|
113
117
|
|
114
118
|
```ruby
|
115
119
|
class User
|
@@ -117,13 +121,7 @@ class User
|
|
117
121
|
end
|
118
122
|
```
|
119
123
|
|
120
|
-
|
121
|
-
|
122
|
-
```ruby
|
123
|
-
get '/system/users/:attachment/:id/dynamic_:definition', to: 'DynamicPaperclip::AttachmentStyles#generate_user'
|
124
|
-
```
|
125
|
-
|
126
|
-
Now, in your view, you might call something like this:
|
124
|
+
Then, in your view, you might call something like this:
|
127
125
|
|
128
126
|
```ruby
|
129
127
|
@user.avatar.dynamic_url('50x50')
|
@@ -135,14 +133,17 @@ Which will return the following url (assuming a JPG avatar and a User ID of 42):
|
|
135
133
|
/system/users/avatars/42/dynamic_50x50.jpg?s=secrethash
|
136
134
|
```
|
137
135
|
|
138
|
-
When
|
139
|
-
|
140
|
-
|
141
|
-
and
|
142
|
-
from the stye name, and then sends it back to your visitor via ``#send_file``.
|
136
|
+
When the first request comes in for that URL, Dynamic Paperclip's Rack middleware will intercept the request,
|
137
|
+
validate that "secrethash" to ensure that the dynamic URL was generated by your application and not some third-party,
|
138
|
+
then simply tell Paperclip to process that style by extracting the definition from the stye name,
|
139
|
+
and then finally send the processed file back to your visitor's browser.
|
143
140
|
|
144
141
|
On subsequent requests, the attachment will already exist, and your HTTP server will simply return it without
|
145
|
-
ever hitting your
|
142
|
+
ever hitting your application. Sweet!
|
143
|
+
|
144
|
+
If your HTTP server is not configured to serve static assets, on subsequent requests, the middleware will simply
|
145
|
+
intercept the request and return the asset to the browser. It will not reprocess it. This is still far less efficient
|
146
|
+
than having your HTTP server serve the asset itself.
|
146
147
|
|
147
148
|
Contributing
|
148
149
|
------------
|
@@ -152,3 +153,10 @@ Contributing
|
|
152
153
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
153
154
|
4. Push to the branch (`git push origin my-new-feature`)
|
154
155
|
5. Create new Pull Request
|
156
|
+
|
157
|
+
Known Issues
|
158
|
+
------------
|
159
|
+
- Dynamic attachments aren't registered in time when class caching is disabled (Rails development, etc.).
|
160
|
+
Since we register the attachment when it's defined, a request for a dynamic attachment in an environment
|
161
|
+
where the class isn't preloaded will pass through the middleware, since the attachment won't be registered
|
162
|
+
yet, and never generate.
|
@@ -13,7 +13,11 @@ module DynamicPaperclip
|
|
13
13
|
style_position = path_with_wildcard.index('dynamic_*')
|
14
14
|
|
15
15
|
Dir.glob(path_with_wildcard) do |file|
|
16
|
-
|
16
|
+
style_name = file[style_position..-1].split('/').first
|
17
|
+
|
18
|
+
# In the event that the style name is used as the filename,
|
19
|
+
# we want to remove the extension for our style name
|
20
|
+
add_dynamic_style! File.basename(style_name, File.extname(style_name))
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
require 'action_controller/metal/data_streaming'
|
3
|
+
|
4
|
+
module DynamicPaperclip
|
5
|
+
class AttachmentStyleGenerator
|
6
|
+
# Rack middleware that catches requests for dynamic attachment styles
|
7
|
+
# that have not yet been generated and generates them.
|
8
|
+
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
request = Rack::Request.new(env)
|
15
|
+
|
16
|
+
DynamicPaperclip::AttachmentRegistry.each_definition do |klass, name, options|
|
17
|
+
if match = regexp_for_attachment_url(klass, (options[:url] || Attachment.default_options[:url])).match(request.path)
|
18
|
+
id = id_from_partition(match[:id])
|
19
|
+
attachment = klass.find(id).send(name)
|
20
|
+
|
21
|
+
# The definition will be escaped twice in the URL, so we need to unescape it once.
|
22
|
+
# We should always reference dynamic style names after escaping once - that's how they reside on the FS.
|
23
|
+
style_name = StyleNaming.dynamic_style_name_from_definition(URI.unescape(match[:definition]), false)
|
24
|
+
|
25
|
+
# Validate URL hash against requested style name
|
26
|
+
if DynamicPaperclip::UrlSecurity.valid_hash?(request.params['s'], style_name)
|
27
|
+
|
28
|
+
# Only process style if it doesn't exist,
|
29
|
+
# otherwise we may just be fielding a request for
|
30
|
+
# an existing style
|
31
|
+
attachment.process_dynamic_style style_name unless attachment.exists?(style_name)
|
32
|
+
|
33
|
+
return [
|
34
|
+
200,
|
35
|
+
{
|
36
|
+
'Content-Type' => attachment.content_type,
|
37
|
+
'Content-Transfer-Encoding' => 'binary',
|
38
|
+
'Content-Disposition' => "inline; filename=#{File.basename(attachment.path(style_name))}"
|
39
|
+
},
|
40
|
+
ActionController::DataStreaming::FileBody.new(attachment.path(style_name))
|
41
|
+
]
|
42
|
+
else
|
43
|
+
# Invalid hash, just 403
|
44
|
+
|
45
|
+
return [403, {}, []]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@app.call env
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def regexp_for_attachment_url(klass, url)
|
56
|
+
Regexp.new '^' + url.
|
57
|
+
gsub('.' , '\.').
|
58
|
+
gsub(':id_partition', '(?<id>.*)').
|
59
|
+
gsub(':class' , klass.name.underscore.pluralize).
|
60
|
+
gsub(':style' , "dynamic_#{url_named_capture_group('definition')}").
|
61
|
+
gsub(/\:(\w+)/ , url_named_capture_group('\1')) + '$'
|
62
|
+
end
|
63
|
+
|
64
|
+
def url_named_capture_group(name)
|
65
|
+
"(?<#{name}>[^\/]*)"
|
66
|
+
end
|
67
|
+
|
68
|
+
def id_from_partition(partition)
|
69
|
+
partition.gsub('/', '').to_i
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -3,16 +3,7 @@ module DynamicPaperclip
|
|
3
3
|
end
|
4
4
|
|
5
5
|
module Errors
|
6
|
-
class UndefinedAttachment < DynamicPaperclip::Error
|
7
|
-
end
|
8
|
-
|
9
|
-
class MissingID < DynamicPaperclip::Error
|
10
|
-
end
|
11
|
-
|
12
6
|
class SecretNotSet < DynamicPaperclip::Error
|
13
7
|
end
|
14
|
-
|
15
|
-
class InvalidHash < DynamicPaperclip::Error
|
16
|
-
end
|
17
8
|
end
|
18
9
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module DynamicPaperclip
|
2
2
|
class HasAttachedFile < Paperclip::HasAttachedFile
|
3
|
-
def
|
4
|
-
|
3
|
+
def register_new_attachment
|
4
|
+
DynamicPaperclip::AttachmentRegistry.register(@klass, @name, @options)
|
5
5
|
|
6
|
-
|
6
|
+
super
|
7
7
|
end
|
8
8
|
|
9
9
|
private
|
@@ -31,21 +31,5 @@ module DynamicPaperclip
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
34
|
-
|
35
|
-
def add_route!
|
36
|
-
url = (@options[:url] || Attachment.default_options[:url]).
|
37
|
-
gsub(':id_partition', '*id_partition').
|
38
|
-
gsub(':class' , @klass.name.underscore.pluralize).
|
39
|
-
gsub(':style' , "dynamic_:definition")
|
40
|
-
|
41
|
-
action = "generate_#{@klass.name.underscore}"
|
42
|
-
default_attachment = @name.to_s.downcase.pluralize
|
43
|
-
|
44
|
-
Rails.application.routes do
|
45
|
-
get url,
|
46
|
-
:to => "DynamicPaperclip::AttachmentStyles##{action}",
|
47
|
-
:defaults => { :attachment => default_attachment }
|
48
|
-
end
|
49
|
-
end
|
50
34
|
end
|
51
35
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'dynamic_paperclip/attachment_style_generator'
|
2
|
+
|
3
|
+
module DynamicPaperclip
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer 'dynamic_paperclip.insert_middleware' do |app|
|
6
|
+
app.config.middleware.use "DynamicPaperclip::AttachmentStyleGenerator"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
data/lib/dynamic_paperclip.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
require 'paperclip'
|
2
2
|
require "dynamic_paperclip/errors"
|
3
3
|
require "dynamic_paperclip/config"
|
4
|
-
require "dynamic_paperclip/engine"
|
5
4
|
require "dynamic_paperclip/attachment"
|
6
5
|
require "dynamic_paperclip/has_attached_file"
|
7
6
|
require "dynamic_paperclip/paperclip_shim"
|
8
7
|
require "dynamic_paperclip/url_security"
|
9
8
|
require "dynamic_paperclip/style_naming"
|
9
|
+
require "dynamic_paperclip/attachment_registry"
|
10
|
+
|
11
|
+
require 'dynamic_paperclip/railtie' if defined?(Rails)
|
12
|
+
|
13
|
+
require 'active_support'
|
14
|
+
require 'active_support/core_ext/string/inflections'
|
10
15
|
|
11
16
|
module DynamicPaperclip
|
12
17
|
extend self
|
@@ -2,9 +2,7 @@ module DynamicPaperclip
|
|
2
2
|
class InstallGenerator < Rails::Generators::Base
|
3
3
|
def create_initializer_file
|
4
4
|
create_file 'config/initializers/dynamic_paperclip.rb' do
|
5
|
-
|
6
|
-
DynamicPaperclip.config.secret = '#{SecureRandom.urlsafe_base64(50)}'
|
7
|
-
init
|
5
|
+
"DynamicPaperclip.config.secret = '#{SecureRandom.urlsafe_base64(50)}'"
|
8
6
|
end
|
9
7
|
end
|
10
8
|
end
|
data/test/dummy/db/test.sqlite3
CHANGED
Binary file
|