actionpack 1.11.2 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (149) hide show
  1. data/CHANGELOG +392 -5
  2. data/lib/action_controller.rb +8 -4
  3. data/lib/action_controller/assertions.rb +9 -10
  4. data/lib/action_controller/base.rb +177 -88
  5. data/lib/action_controller/benchmarking.rb +5 -5
  6. data/lib/action_controller/caching.rb +44 -36
  7. data/lib/action_controller/cgi_ext/cgi_methods.rb +71 -6
  8. data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +1 -1
  9. data/lib/action_controller/cgi_process.rb +36 -24
  10. data/lib/action_controller/components.rb +152 -52
  11. data/lib/action_controller/dependencies.rb +1 -1
  12. data/lib/action_controller/deprecated_redirects.rb +2 -2
  13. data/lib/action_controller/deprecated_request_methods.rb +34 -0
  14. data/lib/action_controller/filters.rb +59 -19
  15. data/lib/action_controller/flash.rb +53 -47
  16. data/lib/action_controller/helpers.rb +2 -2
  17. data/lib/action_controller/integration.rb +524 -0
  18. data/lib/action_controller/layout.rb +58 -23
  19. data/lib/action_controller/mime_responds.rb +163 -0
  20. data/lib/action_controller/mime_type.rb +142 -0
  21. data/lib/action_controller/pagination.rb +13 -7
  22. data/lib/action_controller/request.rb +59 -56
  23. data/lib/action_controller/rescue.rb +1 -1
  24. data/lib/action_controller/routing.rb +29 -10
  25. data/lib/action_controller/scaffolding.rb +8 -0
  26. data/lib/action_controller/session/active_record_store.rb +21 -10
  27. data/lib/action_controller/session/mem_cache_store.rb +18 -12
  28. data/lib/action_controller/session_management.rb +30 -11
  29. data/lib/action_controller/templates/rescues/_trace.rhtml +1 -1
  30. data/lib/action_controller/templates/scaffolds/layout.rhtml +4 -4
  31. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  32. data/lib/action_controller/test_process.rb +189 -118
  33. data/lib/action_controller/vendor/html-scanner/html/node.rb +20 -1
  34. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +3 -0
  35. data/lib/action_controller/vendor/html-scanner/html/version.rb +1 -1
  36. data/lib/action_controller/vendor/xml_node.rb +97 -0
  37. data/lib/action_controller/verification.rb +2 -0
  38. data/lib/action_pack/version.rb +3 -3
  39. data/lib/action_view.rb +0 -2
  40. data/lib/action_view/base.rb +109 -36
  41. data/lib/action_view/compiled_templates.rb +1 -1
  42. data/lib/action_view/helpers/active_record_helper.rb +4 -2
  43. data/lib/action_view/helpers/asset_tag_helper.rb +6 -7
  44. data/lib/action_view/helpers/capture_helper.rb +49 -12
  45. data/lib/action_view/helpers/date_helper.rb +14 -4
  46. data/lib/action_view/helpers/form_helper.rb +136 -20
  47. data/lib/action_view/helpers/form_options_helper.rb +29 -7
  48. data/lib/action_view/helpers/form_tag_helper.rb +22 -20
  49. data/lib/action_view/helpers/java_script_macros_helper.rb +29 -9
  50. data/lib/action_view/helpers/javascript_helper.rb +50 -446
  51. data/lib/action_view/helpers/javascripts/controls.js +95 -30
  52. data/lib/action_view/helpers/javascripts/dragdrop.js +161 -21
  53. data/lib/action_view/helpers/javascripts/effects.js +310 -211
  54. data/lib/action_view/helpers/javascripts/prototype.js +228 -28
  55. data/lib/action_view/helpers/number_helper.rb +9 -9
  56. data/lib/action_view/helpers/pagination_helper.rb +1 -1
  57. data/lib/action_view/helpers/prototype_helper.rb +900 -0
  58. data/lib/action_view/helpers/scriptaculous_helper.rb +135 -0
  59. data/lib/action_view/helpers/text_helper.rb +7 -6
  60. data/lib/action_view/helpers/url_helper.rb +23 -14
  61. data/lib/action_view/partials.rb +12 -4
  62. data/rakefile +13 -5
  63. data/test/abstract_unit.rb +4 -3
  64. data/test/active_record_unit.rb +88 -0
  65. data/test/{controller → activerecord}/active_record_assertions_test.rb +7 -50
  66. data/test/{controller → activerecord}/active_record_store_test.rb +27 -4
  67. data/test/activerecord/pagination_test.rb +161 -0
  68. data/test/controller/action_pack_assertions_test.rb +18 -15
  69. data/test/controller/base_test.rb +31 -42
  70. data/test/controller/benchmark_test.rb +8 -11
  71. data/test/controller/capture_test.rb +33 -1
  72. data/test/controller/cgi_test.rb +33 -0
  73. data/test/controller/custom_handler_test.rb +8 -0
  74. data/test/controller/fake_controllers.rb +9 -17
  75. data/test/controller/filters_test.rb +32 -3
  76. data/test/controller/flash_test.rb +26 -41
  77. data/test/controller/fragment_store_setting_test.rb +1 -1
  78. data/test/controller/layout_test.rb +73 -0
  79. data/test/controller/mime_responds_test.rb +257 -0
  80. data/test/controller/mime_type_test.rb +24 -0
  81. data/test/controller/new_render_test.rb +157 -1
  82. data/test/controller/redirect_test.rb +23 -0
  83. data/test/controller/render_test.rb +54 -56
  84. data/test/controller/request_test.rb +25 -0
  85. data/test/controller/routing_test.rb +74 -66
  86. data/test/controller/test_test.rb +66 -1
  87. data/test/controller/verification_test.rb +3 -1
  88. data/test/controller/webservice_test.rb +255 -0
  89. data/test/fixtures/companies.yml +24 -0
  90. data/test/fixtures/company.rb +9 -0
  91. data/test/fixtures/db_definitions/sqlite.sql +42 -0
  92. data/test/fixtures/developer.rb +7 -0
  93. data/test/fixtures/developers.yml +21 -0
  94. data/test/fixtures/developers_projects.yml +13 -0
  95. data/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml +1 -0
  96. data/test/fixtures/layout_tests/layouts/item.rhtml +1 -0
  97. data/test/fixtures/layout_tests/layouts/layout_test.rhtml +1 -0
  98. data/test/fixtures/layout_tests/layouts/third_party_template_library.mab +1 -0
  99. data/test/fixtures/layout_tests/views/hello.rhtml +1 -0
  100. data/test/fixtures/multipart/mona_lisa.jpg +0 -0
  101. data/test/fixtures/project.rb +3 -0
  102. data/test/fixtures/projects.yml +7 -0
  103. data/test/fixtures/replies.yml +13 -0
  104. data/test/fixtures/reply.rb +5 -0
  105. data/test/fixtures/respond_to/all_types_with_layout.rhtml +1 -0
  106. data/test/fixtures/respond_to/all_types_with_layout.rjs +1 -0
  107. data/test/fixtures/respond_to/layouts/standard.rhtml +1 -0
  108. data/test/fixtures/respond_to/using_defaults.rhtml +1 -0
  109. data/test/fixtures/respond_to/using_defaults.rjs +1 -0
  110. data/test/fixtures/respond_to/using_defaults.rxml +1 -0
  111. data/test/fixtures/respond_to/using_defaults_with_type_list.rhtml +1 -0
  112. data/test/fixtures/respond_to/using_defaults_with_type_list.rjs +1 -0
  113. data/test/fixtures/respond_to/using_defaults_with_type_list.rxml +1 -0
  114. data/test/fixtures/test/block_content_for.rhtml +2 -0
  115. data/test/fixtures/test/delete_with_js.rjs +2 -0
  116. data/test/fixtures/test/dot.directory/render_file_with_ivar.rhtml +1 -0
  117. data/test/fixtures/test/enum_rjs_test.rjs +6 -0
  118. data/test/fixtures/test/erb_content_for.rhtml +2 -0
  119. data/test/fixtures/test/hello_world.rxml +3 -0
  120. data/test/fixtures/test/hello_world_with_layout_false.rhtml +1 -0
  121. data/test/fixtures/test/non_erb_block_content_for.rxml +4 -0
  122. data/test/fixtures/topic.rb +3 -0
  123. data/test/fixtures/topics.yml +22 -0
  124. data/test/template/active_record_helper_test.rb +4 -0
  125. data/test/template/asset_tag_helper_test.rb +7 -2
  126. data/test/template/date_helper_test.rb +39 -2
  127. data/test/template/form_helper_test.rb +238 -5
  128. data/test/template/form_options_helper_test.rb +78 -0
  129. data/test/template/form_tag_helper_test.rb +11 -0
  130. data/test/template/java_script_macros_helper_test.rb +51 -6
  131. data/test/template/javascript_helper_test.rb +7 -153
  132. data/test/template/number_helper_test.rb +14 -13
  133. data/test/template/prototype_helper_test.rb +423 -0
  134. data/test/template/scriptaculous_helper_test.rb +90 -0
  135. data/test/template/text_helper_test.rb +12 -9
  136. data/test/template/url_helper_test.rb +31 -15
  137. metadata +291 -246
  138. data/lib/action_controller/cgi_ext/multipart_progress.rb +0 -169
  139. data/lib/action_controller/upload_progress.rb +0 -473
  140. data/lib/action_controller/vendor/html-scanner/html/node.rb.rej +0 -17
  141. data/lib/action_view/helpers/upload_progress_helper.rb +0 -433
  142. data/lib/action_view/vendor/builder.rb +0 -13
  143. data/lib/action_view/vendor/builder/blankslate.rb +0 -53
  144. data/lib/action_view/vendor/builder/xmlbase.rb +0 -143
  145. data/lib/action_view/vendor/builder/xmlevents.rb +0 -63
  146. data/lib/action_view/vendor/builder/xmlmarkup.rb +0 -308
  147. data/test/controller/multipart_progress_testx.rb +0 -365
  148. data/test/controller/upload_progress_testx.rb +0 -89
  149. data/test/template/upload_progress_helper_testx.rb +0 -136
@@ -1,169 +0,0 @@
1
- # == Overview
2
- #
3
- # This module will extend the CGI module with methods to track the upload
4
- # progress for multipart forms for use with progress meters. The progress is
5
- # saved in the session to be used from any request from any server with the
6
- # same session. In other words, this module will work across application
7
- # instances.
8
- #
9
- # === Usage
10
- #
11
- # Just do your file-uploads as you normally would, but include an upload_id in
12
- # the query string of your form action. Your form post action should look
13
- # like:
14
- #
15
- # <form method="post" enctype="multipart/form-data" action="postaction?upload_id=SOMEIDYOUSET">
16
- # <input type="file" name="client_file"/>
17
- # </form>
18
- #
19
- # Query the upload state in a progress by reading the progress from the session
20
- #
21
- # class UploadController < ApplicationController
22
- # def upload_status
23
- # render :text => "Percent complete: " + @session[:uploads]['SOMEIDYOUSET'].completed_percent"
24
- # end
25
- # end
26
- #
27
- # === Session options
28
- #
29
- # Upload progress uses the session options defined in
30
- # ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS. If you are passing
31
- # custom session options to your dispatcher then please follow the
32
- # "recommended way to change session options":http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
33
- #
34
- # === Update frequency
35
- #
36
- # During an upload, the progress will be written to the session every 2
37
- # seconds. This prevents excessive writes yet maintains a decent picture of
38
- # the upload progress for larger files.
39
- #
40
- # User interfaces that update more often that every 2 seconds will display the same results.
41
- # Consider this update frequency when designing your progress polling.
42
- #
43
-
44
- require 'cgi'
45
-
46
- # For integration with ActionPack
47
- require 'action_controller/base'
48
- require 'action_controller/cgi_process'
49
- require 'action_controller/upload_progress'
50
-
51
- class CGI #:nodoc:
52
- class ProgressIO < SimpleDelegator #:nodoc:
53
- MIN_SAVE_INTERVAL = 1.0 # Number of seconds between session saves
54
-
55
- attr_reader :progress, :session
56
-
57
- def initialize(orig_io, progress, session)
58
- @session = session
59
- @progress = progress
60
-
61
- @start_time = Time.now
62
- @last_save_time = @start_time
63
- save_progress
64
-
65
- super(orig_io)
66
- end
67
-
68
- def read(*args)
69
- data = __getobj__.read(*args)
70
-
71
- if data and data.size > 0
72
- now = Time.now
73
- elapsed = now - @start_time
74
- progress.update!(data.size, elapsed)
75
-
76
- if now - @last_save_time > MIN_SAVE_INTERVAL
77
- save_progress
78
- @last_save_time = now
79
- end
80
- else
81
- ActionController::Base.logger.debug("CGI::ProgressIO#read returns nothing when it should return nil if IO is finished: [#{args.inspect}], a cancelled upload or old FCGI bindings. Resetting the upload progress")
82
-
83
- progress.reset!
84
- save_progress
85
- end
86
-
87
- data
88
- end
89
-
90
- def save_progress
91
- @session.update
92
- end
93
-
94
- def finish
95
- @session.update
96
- ActionController::Base.logger.debug("Finished processing multipart upload in #{@progress.elapsed_seconds.to_s}s")
97
- end
98
- end
99
-
100
- module QueryExtension #:nodoc:
101
- # Need to do lazy aliasing on the instance that we are extending because of the way QueryExtension
102
- # gets included for each instance of the CGI object rather than on a module level. This method is a
103
- # bit obtrusive because we are overriding CGI::QueryExtension::extended which could be used in the
104
- # future. Need to research a better method
105
- def self.extended(obj)
106
- obj.instance_eval do
107
- # unless defined? will prevent clobbering the progress IO on multiple extensions
108
- alias :stdinput_without_progress :stdinput unless defined? stdinput_without_progress
109
- alias :stdinput :stdinput_with_progress
110
- end
111
- end
112
-
113
- def stdinput_with_progress
114
- @stdin_with_progress or stdinput_without_progress
115
- end
116
-
117
- private
118
- # Bootstrapped on ActionController::UploadProgress::upload_status_for
119
- def read_multipart_with_progress(boundary, content_length)
120
- begin
121
- begin
122
- # Session disabled if the default session options have been set to 'false'
123
- options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
124
- raise RuntimeError.new("Multipart upload progress disabled, no session options") unless options
125
-
126
- options = options.stringify_keys
127
-
128
- # Pull in the application controller to satisfy any dependencies on class definitions
129
- # of instances stored in the session.
130
- Controllers.const_load!(:ApplicationController, "application") unless Controllers.const_defined?(:ApplicationController)
131
-
132
- # Assumes that @cookies has already been setup
133
- # Raises nomethod if upload_id is not defined
134
- @params = CGI::parse(read_params_from_query)
135
- upload_id = @params[(options['upload_key'] || 'upload_id')].first
136
- raise RuntimeError.new("Multipart upload progress disabled, no upload id in query string") unless upload_id
137
-
138
- upload_progress = ActionController::UploadProgress::Progress.new(content_length)
139
-
140
- session = Session.new(self, options)
141
- session[:uploads] = {} unless session[:uploads]
142
- session[:uploads].delete(upload_id) # in case the same upload id is used twice
143
- session[:uploads][upload_id] = upload_progress
144
-
145
- @stdin_with_progress = CGI::ProgressIO.new(stdinput_without_progress, upload_progress, session)
146
- ActionController::Base.logger.debug("Multipart upload with progress (id: #{upload_id}, size: #{content_length})")
147
- rescue
148
- ActionController::Base.logger.debug("Exception during setup of read_multipart_with_progress: #{$!}")
149
- end
150
- ensure
151
- begin
152
- params = read_multipart_without_progress(boundary, content_length)
153
- @stdin_with_progress.finish if @stdin_with_progress.respond_to? :finish
154
- ensure
155
- @stdin_with_progress = nil
156
- session.close if session
157
- end
158
- end
159
- params
160
- end
161
-
162
- # Prevent redefinition of aliases on multiple includes
163
- unless private_instance_methods.include?("read_multipart_without_progress")
164
- alias_method :read_multipart_without_progress, :read_multipart
165
- alias_method :read_multipart, :read_multipart_with_progress
166
- end
167
-
168
- end
169
- end
@@ -1,473 +0,0 @@
1
- # Unfortunately we need to require multipart_progress here and not in
2
- # uplaod_status_for because if the upload happens to hit a fresh FCGI instance
3
- # the upload_status_for method will be called after the CGI object is created
4
- # Requiring here means that multipart progress will be enabled for all multipart
5
- # postings.
6
- require 'action_controller/cgi_ext/multipart_progress'
7
-
8
- module ActionController #:nodoc:
9
- # == THIS IS AN EXPERIMENTAL FEATURE
10
- #
11
- # Which means that it doesn't yet work on all systems. We're still working on full
12
- # compatibility. It's thus not advised to use this unless you've verified it to work
13
- # fully on all the systems that is a part of your environment. Consider this an extended
14
- # preview.
15
- #
16
- # To enable this module, add <tt>ActionController::Base.enable_upload_progress</tt> to your
17
- # config/environment.rb file.
18
- #
19
- # == Action Pack Upload Progress for multipart uploads
20
- #
21
- # The UploadProgress module aids in the process of viewing an Ajax driven
22
- # upload status when working with multipart forms. It offers a macro that
23
- # will prepare an action for handling the cleanup of the Ajax updating including
24
- # passing the redirect URL and custom parameters to the Javascript finish handler.
25
- #
26
- # UploadProgress is available for all multipart uploads when the +upload_status_for+
27
- # macro is called in one of your controllers.
28
- #
29
- # The progress is stored as an UploadProgress::Progress object in the session and
30
- # is accessible in the controller and view with the +upload_progress+ method.
31
- #
32
- # For help rendering the UploadProgress enabled form and supported elements, see
33
- # ActionView::Helpers::UploadProgressHelper.
34
- #
35
- # === Automatic updating on upload actions
36
- #
37
- # class DocumentController < ApplicationController
38
- # upload_status_for :create
39
- #
40
- # def create
41
- # # ... Your document creation action
42
- # end
43
- # end
44
- #
45
- # The +upload_status_for+ macro will override the rendering of the action passed
46
- # if +upload_id+ is found in the query string. This allows for default
47
- # behavior if Javascript is disabled. If you are tracking the upload progress
48
- # then +create+ will now return the cleanup scripts that will terminate the polling
49
- # of the upload status.
50
- #
51
- # === Customized status rendering
52
- #
53
- # class DocumentController < ApplicationController
54
- # upload_status_for :create, :status => :custom_status
55
- #
56
- # def create
57
- # # ... Your document creation action
58
- # end
59
- #
60
- # def custom_status
61
- # # ... Override this action to return content to be replaced in
62
- # # the status container
63
- # render :inline => "<%= upload_progress.completed_percent rescue 0 %> % complete", :layout => false
64
- # end
65
- #
66
- # The default status action is +upload_status+. The results of this action
67
- # are added used to replace the contents of the HTML elements defined in
68
- # +upload_status_tag+. Within +upload_status+, you can load the Progress
69
- # object from the session with the +upload_progress+ method and display your own
70
- # results.
71
- #
72
- # Completion of the upload status updating occurs automatically with an +after_filter+ call to
73
- # +finish_upload_status+. Because the upload must be posted into a hidden IFRAME to enable
74
- # Ajax updates during the upload, +finish_upload_status+ overwrites the results of any previous
75
- # +render+ or +redirect_to+ so it can render the necessary Javascript that will properly terminate
76
- # the status updating loop, trigger the completion callback or redirect to the appropriate URL.
77
- #
78
- # ==== Basic Example (View):
79
- #
80
- # <%= form_tag_with_upload_progress({:action => 'create'}, {:finish => 'alert("Document Uploaded")'}) %>
81
- # <%= upload_status_tag %>
82
- # <%= file_field 'document', 'file' %>
83
- # <%= end_form_tag %>
84
- #
85
- # ==== Basic Example (Controller):
86
- #
87
- # class DocumentController < ApplicationController
88
- # upload_status_for :create
89
- #
90
- # def create
91
- # @document = Document.create(params[:document])
92
- # end
93
- # end
94
- #
95
- # ==== Extended Example (View):
96
- #
97
- # <%= form_tag_with_upload_progress({:action => 'create'}, {}, {:action => :custom_status}) %>
98
- # <%= upload_status_tag %>
99
- # <%= file_field 'document', 'file' %>
100
- # <%= submit_tag "Upload" %>
101
- # <%= end_form_tag %>
102
- #
103
- # <%= form_tag_with_upload_progress({:action => 'add_preview'}, {:finish => 'alert(arguments[0])'}, {:action => :custom_status}) %>
104
- # <%= upload_status_tag %>
105
- # <%= submit_tag "Upload" %>
106
- # <%= file_field 'preview', 'file' %>
107
- # <%= end_form_tag %>
108
- #
109
- # ==== Extended Example (Controller):
110
- #
111
- # class DocumentController < ApplicationController
112
- # upload_status_for :add_preview, :create, {:status => :custom_status}
113
- #
114
- # def add_preview
115
- # @document = Document.find(params[:id])
116
- # @document.preview = Preview.create(params[:preview])
117
- # if @document.save
118
- # finish_upload_status "'Preview added'"
119
- # else
120
- # finish_upload_status "'Preview not added'"
121
- # end
122
- # end
123
- #
124
- # def create
125
- # @document = Document.new(params[:document])
126
- #
127
- # upload_progress.message = "Processing document..."
128
- # session.update
129
- #
130
- # @document.save
131
- # redirect_to :action => 'show', :id => @document.id
132
- # end
133
- #
134
- # def custom_status
135
- # render :inline => '<%= upload_progress_status %> <div>Updated at <%= Time.now %></div>', :layout => false
136
- # end
137
- #
138
- # ==== Environment checklist
139
- #
140
- # This is an experimental feature that requires a specific webserver environment. Use the following checklist
141
- # to confirm that you have an environment that supports upload progress.
142
- #
143
- # ===== Ruby:
144
- #
145
- # * Running the command `ruby -v` should print "ruby 1.8.2 (2004-12-25)" or older
146
- #
147
- # ===== Web server:
148
- #
149
- # * Apache 1.3, Apache 2.0 or Lighttpd *1.4* (need to build lighttpd from CVS)
150
- #
151
- # ===== FastCGI bindings:
152
- #
153
- # * > 0.8.6 and must be the compiled C version of the bindings
154
- # * The command `ruby -e "p require('fcgi.so')"` should print "true"
155
- #
156
- # ===== Apache/Lighttpd FastCGI directives:
157
- #
158
- # * You must allow more than one FCGI server process to allow concurrent requests.
159
- # * If there is only a single FCGI process you will not get the upload status updates.
160
- # * You can check this by taking a look for running FCGI servers in your process list during a progress upload.
161
- # * Apache directive: FastCGIConfig -minProcesses 2
162
- # * Lighttpd directives taken from config/lighttpd.conf (min-procs):
163
- #
164
- # fastcgi.server = (
165
- # ".fcgi" => (
166
- # "APP_NAME" => (
167
- # "socket" => "/tmp/APP_NAME1.socket",
168
- # "bin-path" => "RAILS_ROOT/public/dispatch.fcgi",
169
- # "min-procs" => 2
170
- # )
171
- # )
172
- # )
173
- #
174
- # ===== config/environment.rb:
175
- #
176
- # * Add the following line to your config/environment.rb and restart your web server.
177
- # * <tt>ActionController::Base.enable_upload_progress</tt>
178
- #
179
- # ===== Development log:
180
- #
181
- # * When the upload progress is enabled by you will find something the following lines:
182
- # * "Multipart upload with progress (id: 1, size: 85464)"
183
- # * "Finished processing multipart upload in 0.363729s"
184
- # * If you are properly running multiple FCGI processes, then you will see multiple entries for rendering the "upload_status" action before the "Finish processing..." log entry. This is a *good thing* :)
185
- #
186
- module UploadProgress
187
- def self.append_features(base) #:nodoc:
188
- super
189
- base.extend(ClassMethods)
190
- base.helper_method :upload_progress, :next_upload_id, :last_upload_id, :current_upload_id
191
- end
192
-
193
- module ClassMethods #:nodoc:
194
- # Creates an +after_filter+ which will call +finish_upload_status+
195
- # creating the document that will be loaded into the hidden IFRAME, terminating
196
- # the status polling forms created with +form_with_upload_progress+.
197
- #
198
- # Also defines an action +upload_status+ or a action name passed as
199
- # the <tt>:status</tt> option. This status action must match the one expected
200
- # in the +form_tag_with_upload_progress+ helper.
201
- #
202
- def upload_status_for(*actions)
203
- after_filter :finish_upload_status, :only => actions
204
-
205
- define_method(actions.last.is_a?(Hash) && actions.last[:status] || :upload_status) do
206
- render(:inline => '<%= upload_progress_status %>', :layout => false)
207
- end
208
- end
209
- end
210
-
211
- # Overwrites the body rendered if the upload comes from a form that tracks
212
- # the progress of the upload. After clearing the body and any redirects, this
213
- # method then renders the helper +finish_upload_status+
214
- #
215
- # This method only needs to be called if you wish to pass a
216
- # javascript parameter to your finish event handler that you optionally
217
- # define in +form_with_upload_progress+
218
- #
219
- # === Parameter:
220
- #
221
- # client_js_argument:: a string containing a Javascript expression that will
222
- # be evaluated and passed to your +finish+ handler of
223
- # +form_tag_with_upload_progress+.
224
- #
225
- # You can pass a String, Number or Boolean.
226
- #
227
- # === Strings
228
- #
229
- # Strings contain Javascript code that will be evaluated on the client. If you
230
- # wish to pass a string to the client finish callback, you will need to include
231
- # quotes in the +client_js_argument+ you pass to this method.
232
- #
233
- # ==== Example
234
- #
235
- # finish_upload_status("\"Finished\"")
236
- # finish_upload_status("'Finished #{@document.title}'")
237
- # finish_upload_status("{success: true, message: 'Done!'}")
238
- # finish_upload_status("function() { alert('Uploaded!'); }")
239
- #
240
- # === Numbers / Booleans
241
- #
242
- # Numbers and Booleans can either be passed as Number objects or string versions
243
- # of number objects as they are evaluated by Javascript the same way as in Ruby.
244
- #
245
- # ==== Example
246
- #
247
- # finish_upload_status(0)
248
- # finish_upload_status(@document.file.size)
249
- # finish_upload_status("10")
250
- #
251
- # === Nil
252
- #
253
- # To pass +nil+ to the finish callback, use a string "undefined"
254
- #
255
- # ==== Example
256
- #
257
- # finish_upload_status(@message || "undefined")
258
- #
259
- # == Redirection
260
- #
261
- # If you action performs a redirection then +finish_upload_status+ will recognize
262
- # the redirection and properly create the Javascript to perform the redirection in
263
- # the proper location.
264
- #
265
- # It is possible to redirect and pass a parameter to the finish callback.
266
- #
267
- # ==== Example
268
- #
269
- # redirect_to :action => 'show', :id => @document.id
270
- # finish_upload_status("'Redirecting you to your new file'")
271
- #
272
- #
273
- def finish_upload_status(client_js_argument='')
274
- if not @rendered_finish_upload_status and params[:upload_id]
275
- @rendered_finish_upload_status = true
276
-
277
- erase_render_results
278
- location = erase_redirect_results || ''
279
-
280
- ## TODO determine if #inspect is the appropriate way to marshall values
281
- ## in inline templates
282
-
283
- template = "<%= finish_upload_status({"
284
- template << ":client_js_argument => #{client_js_argument.inspect}, "
285
- template << ":redirect_to => #{location.to_s.inspect}, "
286
- template << "}) %>"
287
-
288
- render({ :inline => template, :layout => false })
289
- end
290
- end
291
-
292
- # Returns and saves the next unique +upload_id+ in the instance variable
293
- # <tt>@upload_id</tt>
294
- def next_upload_id
295
- @upload_id = last_upload_id.succ
296
- end
297
-
298
- # Either returns the last saved +upload_id+ or looks in the session
299
- # for the last used +upload_id+ and saves it as the intance variable
300
- # <tt>@upload_id</tt>
301
- def last_upload_id
302
- @upload_id ||= ((session[:uploads] || {}).keys.map{|k| k.to_i}.sort.last || 0).to_s
303
- end
304
-
305
- # Returns the +upload_id+ from the query parameters or if it cannot be found
306
- # in the query parameters, then return the +last_upload_id+
307
- def current_upload_id
308
- params[:upload_id] or last_upload_id
309
- end
310
-
311
- # Get the UploadProgress::Progress object for the supplied +upload_id+ from the
312
- # session. If no +upload_id+ is given, then use the +current_upload_id+
313
- #
314
- # If an UploadProgress::Progress object cannot be found, a new instance will be
315
- # returned with <code>total_bytes == 0</code>, <code>started? == false</code>,
316
- # and <code>finished? == true</code>.
317
- def upload_progress(upload_id = nil)
318
- upload_id ||= current_upload_id
319
- session[:uploads] && session[:uploads][upload_id] || UploadProgress::Progress.new(0)
320
- end
321
-
322
- # == THIS IS AN EXPERIMENTAL FEATURE
323
- #
324
- # Which means that it doesn't yet work on all systems. We're still working on full
325
- # compatibility. It's thus not advised to use this unless you've verified it to work
326
- # fully on all the systems that is a part of your environment. Consider this an extended
327
- # preview.
328
- #
329
- # Upload Progress abstracts the progress of an upload. It's used by the
330
- # multipart progress IO that keeps track of the upload progress and creating
331
- # the application depends on. It contians methods to update the progress
332
- # during an upload and read the statistics such as +received_bytes+,
333
- # +total_bytes+, +completed_percent+, +bitrate+, and
334
- # +remaining_seconds+
335
- #
336
- # You can get the current +Progress+ object by calling +upload_progress+ instance
337
- # method in your controller or view.
338
- #
339
- class Progress
340
- unless const_defined? :MIN_SAMPLE_TIME
341
- # Number of seconds between bitrate samples. Updates that occur more
342
- # frequently than +MIN_SAMPLE_TIME+ will not be queued until this
343
- # time passes. This behavior gives a good balance of accuracy and load
344
- # for both fast and slow transfers.
345
- MIN_SAMPLE_TIME = 0.150
346
-
347
- # Number of seconds between updates before giving up to try and calculate
348
- # bitrate anymore
349
- MIN_STALL_TIME = 10.0
350
-
351
- # Number of samples used to calculate bitrate
352
- MAX_SAMPLES = 20
353
- end
354
-
355
- # Number bytes received from the multipart post
356
- attr_reader :received_bytes
357
-
358
- # Total number of bytes expected from the mutlipart post
359
- attr_reader :total_bytes
360
-
361
- # The last time the upload history was updated
362
- attr_reader :last_update_time
363
-
364
- # A message you can set from your controller or view to be rendered in the
365
- # +upload_status_text+ helper method. If you set a messagein a controller
366
- # then call <code>session.update</code> to make that message available to
367
- # your +upload_status+ action.
368
- attr_accessor :message
369
-
370
- # Create a new Progress object passing the expected number of bytes to receive
371
- def initialize(total)
372
- @total_bytes = total
373
- reset!
374
- end
375
-
376
- # Resets the received_bytes, last_update_time, message and bitrate, but
377
- # but maintains the total expected bytes
378
- def reset!
379
- @received_bytes, @last_update_time, @stalled, @message = 0, 0, false, ''
380
- reset_history
381
- end
382
-
383
- # Number of bytes left for this upload
384
- def remaining_bytes
385
- @total_bytes - @received_bytes
386
- end
387
-
388
- # Completed percent in integer form from 0..100
389
- def completed_percent
390
- (@received_bytes * 100 / @total_bytes).to_i rescue 0
391
- end
392
-
393
- # Updates this UploadProgress object with the number of bytes received
394
- # since last update time and the absolute number of seconds since the
395
- # beginning of the upload.
396
- #
397
- # This method is used by the +MultipartProgress+ module and should
398
- # not be called directly.
399
- def update!(bytes, elapsed_seconds)#:nodoc:
400
- if @received_bytes + bytes > @total_bytes
401
- #warn "Progress#update received bytes exceeds expected bytes"
402
- bytes = @total_bytes - @received_bytes
403
- end
404
-
405
- @received_bytes += bytes
406
-
407
- # Age is the duration of time since the last update to the history
408
- age = elapsed_seconds - @last_update_time
409
-
410
- # Record the bytes received in the first element of the history
411
- # in case the sample rate is exceeded and we shouldn't record at this
412
- # time
413
- @history.first[0] += bytes
414
- @history.first[1] += age
415
-
416
- history_age = @history.first[1]
417
-
418
- @history.pop while @history.size > MAX_SAMPLES
419
- @history.unshift([0,0]) if history_age > MIN_SAMPLE_TIME
420
-
421
- if history_age > MIN_STALL_TIME
422
- @stalled = true
423
- reset_history
424
- else
425
- @stalled = false
426
- end
427
-
428
- @last_update_time = elapsed_seconds
429
-
430
- self
431
- end
432
-
433
- # Calculates the bitrate in bytes/second. If the transfer is stalled or
434
- # just started, the bitrate will be 0
435
- def bitrate
436
- history_bytes, history_time = @history.transpose.map { |vals| vals.inject { |sum, v| sum + v } }
437
- history_bytes / history_time rescue 0
438
- end
439
-
440
- # Number of seconds elapsed since the start of the upload
441
- def elapsed_seconds
442
- @last_update_time
443
- end
444
-
445
- # Calculate the seconds remaining based on the current bitrate. Returns
446
- # O seconds if stalled or if no bytes have been received
447
- def remaining_seconds
448
- remaining_bytes / bitrate rescue 0
449
- end
450
-
451
- # Returns true if there are bytes pending otherwise returns false
452
- def finished?
453
- remaining_bytes <= 0
454
- end
455
-
456
- # Returns true if some bytes have been received
457
- def started?
458
- @received_bytes > 0
459
- end
460
-
461
- # Returns true if there has been a delay in receiving bytes. The delay
462
- # is set by the constant MIN_STALL_TIME
463
- def stalled?
464
- @stalled
465
- end
466
-
467
- private
468
- def reset_history
469
- @history = [[0,0]]
470
- end
471
- end
472
- end
473
- end