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 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 a Ruby on Rails engine that allows for the creation of Paperclip attachment styles on-the-fly.
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 Rails 3.2.0 or above (although it may work on earlier versions,
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 Rails to serve static assets, then no configuration is required. But if you're not,
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 Rails application
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 Rails application grows, you may discover that you have a large number of attachment styles. This is
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. When you define a dynamic attachment on your model, Dynamic Paperclip defines
110
- a route in your application that routes the URL you've specified (or Paperclip's default) to the ``DynamicPaperclip::AttachmentStylesController``.
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 security purposes, the class is interpolated and thus hardcoded into the route. So, if your Paperclip attachment definition looks like:
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
- Dynamic Paperclip will generate the following route:
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 your visitor's browser requests that URL, if that particular style has already been processed,
139
- it'll be served up by your HTTP server, but if it hasn't, Rails will route it to our controller,
140
- which validates that "secrethash" to ensure that the dynamic URL was generated by your application,
141
- and not some third-party, then simply tells Paperclip to process that style by extracting the definition
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 Rails application. Sweet!
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
- add_dynamic_style! file[style_position..-1].split('/').first
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,4 @@
1
+ module DynamicPaperclip
2
+ class AttachmentRegistry < Paperclip::AttachmentRegistry
3
+ end
4
+ 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 initialize(klass, name, options)
4
- super
3
+ def register_new_attachment
4
+ DynamicPaperclip::AttachmentRegistry.register(@klass, @name, @options)
5
5
 
6
- add_route!
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
@@ -1,3 +1,3 @@
1
1
  module DynamicPaperclip
2
- VERSION = "0.0.4"
2
+ VERSION = "1.0.0a.1"
3
3
  end
@@ -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
- <<-init
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
Binary file