actionpack 1.9.0 → 1.9.1

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.

data/CHANGELOG CHANGED
@@ -1,3 +1,41 @@
1
+ *1.9.1* (11 July, 2005)
2
+
3
+ * Fixed that auto_complete_for didn't force the input string to lower case even as the db comparison was
4
+
5
+ * Fixed that Action View should always use the included Builder, never attempt to require the gem, to ensure compatibility
6
+
7
+ * Added that nil options are not included in tags, so tag("p", :ignore => nil) now returns <p /> not <p ignore="" /> but that tag("p", :ignore => "") still includes it #1465 [michael@schuerig.de]
8
+
9
+ * Fixed that UrlHelper#link_to_unless/link_to_if used html_escape on the name if no link was to be applied. This is unnecessary and breaks its use with images #1649 [joergd@pobox.com]
10
+
11
+ * Improved error message for DoubleRenderError
12
+
13
+ * Fixed routing to allow for testing of *path components #1650 [Nicholas Seckar]
14
+
15
+ * Added :handle as an option to sortable_element to restrict the drag handle to a given class #1642 [thejohnny]
16
+
17
+ * Added a bunch of script.aculo.us features #1644, #1677, #1695 [Thomas Fuchs]
18
+ * Effect.ScrollTo, to smoothly scroll the page to an element
19
+ * Better Firefox flickering handling on SlideUp/SlideDown
20
+ * Removed a possible memory leak in IE with draggables
21
+ * Added support for cancelling dragging my hitting ESC
22
+ * Added capability to remove draggables/droppables and redeclare sortables in dragdrop.js (this makes it possible to call sortable_element on the same element more than once, e.g. in AJAX returns that modify the sortable element. all current sortable 'stuff' on the element will be discarded and the sortable will be rebuilt)
23
+ * Always reset background color on Effect.Highlight; this make change backwards-compatibility, to be sure include style="background-color:(target-color)" on your elements or else elements will fall back to their CSS rules (which is a good thing in most circumstances)
24
+ * Removed circular references from element to prevent memory leaks (still not completely gone in IE)
25
+ * Changes to class extension in effects.js
26
+ * Make Effect.Highlight restore any previously set background color when finishing (makes effect work with CSS classes that set a background color)
27
+ * Fixed myriads of memory leaks in IE and Gecko-based browsers [David Zülke]
28
+ * Added incremental and local autocompleting and loads of documentation to controls.js [Ivan Krstic]
29
+ * Extended the auto_complete_field helper to accept tokens option
30
+ * Changed object extension mechanism to favor Object.extend to make script.aculo.us easily adaptable to support 3rd party libs like IE7.js [David Zülke]
31
+
32
+ * Fixed that named routes didn't use the default values for action and possible other parameters #1534 [Nicholas Seckar]
33
+
34
+ * Fixed JavascriptHelper#visual_effect to use camelize such that :blind_up will work #1639 [pelletierm@eastmedia.net]
35
+
36
+ * Fixed that a SessionRestoreError was thrown if a model object was placed in the session that wasn't available to all controllers. This means that it's no longer necessary to use the 'model :post' work-around in ApplicationController to have a Post model in your session.
37
+
38
+
1
39
  *1.9.0* (6 July, 2005)
2
40
 
3
41
  * Added logging of the request URI in the benchmark statement (makes it easy to grep for slow actions)
data/README CHANGED
@@ -174,7 +174,7 @@ A short rundown of the major features:
174
174
  link_to_remote "Delete this post", :update => "posts",
175
175
  :url => { :action => "destroy", :id => post.id }
176
176
 
177
- {Learn more}[link:classes/ActionView/Helpers/JavascriptHelper.html]
177
+ {Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html]
178
178
 
179
179
 
180
180
  * Pagination for navigating lists of results.
@@ -24,7 +24,7 @@ module ActionController
24
24
  # auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC'
25
25
  #
26
26
  # For help on defining text input fields with autocompletion,
27
- # see ActionView::Helpers::JavascriptHelper.
27
+ # see ActionView::Helpers::JavaScriptHelper.
28
28
  #
29
29
  # For more examples, see script.aculo.us:
30
30
  # * http://script.aculo.us/demos/ajax/autocompleter
@@ -33,7 +33,7 @@ module ActionController
33
33
  def auto_complete_for(object, method, options = {})
34
34
  define_method("auto_complete_for_#{object}_#{method}") do
35
35
  find_options = {
36
- :conditions => [ "LOWER(#{method}) LIKE ?", '%' + params[object][method] + '%' ],
36
+ :conditions => [ "LOWER(#{method}) LIKE ?", '%' + params[object][method].downcase + '%' ],
37
37
  :order => "#{method} ASC",
38
38
  :limit => 10 }.merge!(options)
39
39
 
@@ -26,6 +26,11 @@ module ActionController #:nodoc:
26
26
  class MissingFile < ActionControllerError #:nodoc:
27
27
  end
28
28
  class DoubleRenderError < ActionControllerError #:nodoc:
29
+ DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and only once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\". Finally, note that to cause a before filter to halt execution of the rest of the filter chain, the filter must return false, explicitly, so \"render(...) and return false\"."
30
+
31
+ def initialize(message=nil)
32
+ super(message || DEFAULT_MESSAGE)
33
+ end
29
34
  end
30
35
 
31
36
  # Action Controllers are made up of one or more actions that performs its purpose and then either renders a template or
@@ -179,16 +184,19 @@ module ActionController #:nodoc:
179
184
  #
180
185
  # == Calling multiple redirects or renders
181
186
  #
182
- # The rule for handling calls of multiple redirects and renders is that the first call wins. So in the following example:
187
+ # An action should conclude by a single render or redirect. Attempting to try to do either again will result in a DoubleRenderError:
183
188
  #
184
189
  # def do_something
185
190
  # redirect_to :action => "elsewhere"
186
- # render :action => "overthere"
191
+ # render :action => "overthere" # raises DoubleRenderError
187
192
  # end
188
193
  #
189
- # Only the redirect happens. The rendering call is simply ignored.
194
+ # If you need to redirect on the condition of something, then be sure to add "and return" to halt execution.
190
195
  #
191
- # == Environments
196
+ # def do_something
197
+ # redirect_to(:action => "elsewhere") and return if monkeys.nil?
198
+ # render :action => "overthere" # won't be called unless monkeys is nil
199
+ # end # == Environments
192
200
  #
193
201
  # Action Controller works out of the box with CGI, FastCGI, and mod_ruby. CGI and mod_ruby controllers are triggered just the same using:
194
202
  #
@@ -455,9 +463,6 @@ module ActionController #:nodoc:
455
463
  # # Renders the template for the action "goal" within the current controller
456
464
  # render :action => "goal"
457
465
  #
458
- # # Renders the template for the action "explosion" from the ErrorsController
459
- # render :action => "errors/explosion", :status => 500
460
- #
461
466
  # # Renders the template for the action "short_goal" within the current controller,
462
467
  # # but without the current active layout
463
468
  # render :action => "short_goal", :layout => false
@@ -500,14 +505,17 @@ module ActionController #:nodoc:
500
505
  #
501
506
  # === Rendering a file
502
507
  #
503
- # File rendering works just like action rendering except that it takes a complete path to the template intended
504
- # for rendering and that the current layout is not applied automatically.
508
+ # File rendering works just like action rendering except that it takes a path relative to the template root or an absolute
509
+ # path if use_full_path is passed as true. The current layout is not applied automatically.
510
+ #
511
+ # # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.rhtml)
512
+ # render :file => "weblog/show"
505
513
  #
506
514
  # # Renders the template located in /path/to/some/template.r(html|xml)
507
- # render :file => "/path/to/some/template"
515
+ # render :file => "/path/to/some/template", :use_full_path => false
508
516
  #
509
517
  # # Renders the same template within the current layout, but with a 404 status code
510
- # render :file => "/path/to/some/template", :layout => true, :status => 404
518
+ # render :file => "/path/to/some/template", :use_full_path => false, :layout => true, :status => 404
511
519
  #
512
520
  # _Deprecation_ _notice_: This used to have the signature <tt>render_file(path, status = 200)</tt>
513
521
  #
@@ -560,7 +568,7 @@ module ActionController #:nodoc:
560
568
  # render :nothing => true, :status => 401
561
569
  def render(options = {}, deprecated_status = nil) #:doc:
562
570
  # puts "Rendering: #{options.inspect}"
563
- raise DoubleRenderError, "Can only render or redirect once per action" if performed?
571
+ raise DoubleRenderError if performed?
564
572
 
565
573
  # Backwards compatibility
566
574
  return render({ :template => options || default_template_name, :status => deprecated_status }) if !options.is_a?(Hash)
@@ -684,7 +692,7 @@ module ActionController #:nodoc:
684
692
  def redirect_to(options = {}, *parameters_for_method_reference) #:doc:
685
693
  case options
686
694
  when %r{^\w+://.*}
687
- raise DoubleRenderError, "Can only render or redirect once per action" if performed?
695
+ raise DoubleRenderError if performed?
688
696
  logger.info("Redirected to #{options}") unless logger.nil?
689
697
  response.redirect(options)
690
698
  response.redirected_to = options
@@ -95,29 +95,22 @@ module ActionController #:nodoc:
95
95
  @session["__valid_session"]
96
96
  return @session
97
97
  rescue ArgumentError => e
98
- # TODO: Uncomment this on 0.13.1
99
- # if e.message =~ %r{undefined class/module (\w+)}
100
- # begin
101
- # Module.const_missing($1)
102
- # rescue LoadError, NameError => e
103
- # raise(
104
- # ActionController::SessionRestoreError,
105
- # "Session contained objects where the class definition wasn't available. " +
106
- # "Remember to require classes for all objects kept in the session. " +
107
- # "(Original exception: #{e.message} [#{e.class}])"
108
- # )
109
- # end
110
- #
111
- # retry
112
- # else
113
- # raise
114
- # end
115
- raise(
116
- ActionController::SessionRestoreError,
117
- "Session contained objects where the class definition wasn't available. " +
118
- "Remember to require classes for all objects kept in the session. " +
119
- "(Original exception: #{e.message} [#{e.class}])"
120
- )
98
+ if e.message =~ %r{undefined class/module (\w+)}
99
+ begin
100
+ Module.const_missing($1)
101
+ rescue LoadError, NameError => e
102
+ raise(
103
+ ActionController::SessionRestoreError,
104
+ "Session contained objects where the class definition wasn't available. " +
105
+ "Remember to require classes for all objects kept in the session. " +
106
+ "(Original exception: #{e.message} [#{e.class}])"
107
+ )
108
+ end
109
+
110
+ retry
111
+ else
112
+ raise
113
+ end
121
114
  end
122
115
  end
123
116
 
@@ -92,14 +92,14 @@ module ActionController
92
92
 
93
93
  def initialize(key, options = {})
94
94
  @key = key.to_sym
95
- @default, @condition = options[:default], options[:condition]
96
- @optional = options.key?(:default)
95
+ default, @condition = options[:default], options[:condition]
96
+ self.default = default if options.key?(:default)
97
97
  end
98
98
 
99
99
  def default_check(g)
100
100
  presence = "#{g.hash_value(key, !! default)}"
101
101
  if default
102
- "!(#{presence} && #{g.hash_value(key, false)} != #{default.inspect})"
102
+ "!(#{presence} && #{g.hash_value(key, false)} != #{default.to_s.inspect})"
103
103
  else
104
104
  "! #{presence}"
105
105
  end
@@ -210,7 +210,7 @@ module ActionController
210
210
  mod_name = controller_name = segment = nil
211
211
 
212
212
  while index < length
213
- return nil unless /^[a-z][a-z\d_]*$/ =~ (segment = segments[index])
213
+ return nil unless /^[A-Za-z][A-Za-z\d_]*$/ =~ (segment = segments[index])
214
214
  index += 1
215
215
 
216
216
  mod_name = segment.camelize
@@ -226,8 +226,12 @@ module ActionController
226
226
 
227
227
  class PathComponent < DynamicComponent #:nodoc:
228
228
  def optional?() true end
229
- def default() '' end
229
+ def default() [] end
230
230
  def condition() nil end
231
+
232
+ def default=(value)
233
+ raise RoutingError, "All path components have an implicit default of []" unless value == []
234
+ end
231
235
 
232
236
  def write_generation(g)
233
237
  raise RoutingError, 'Path components must occur last' unless g.after.empty?
@@ -245,24 +249,28 @@ module ActionController
245
249
  start = "(#{start})" unless /^\w+$/ =~ start
246
250
 
247
251
  value_expr = "#{g.path_name}[#{start}..-1] || []"
248
- g.result key, "ActionController::Routing::PathComponent::Result.new(#{value_expr})"
252
+ g.result key, "ActionController::Routing::PathComponent::Result.new_escaped(#{value_expr})"
249
253
  g.finish(false)
250
254
  end
251
255
 
252
256
  class Result < ::Array
253
257
  def to_s() join '/' end
258
+ def self.new_escaped(strings)
259
+ new strings.collect {|str| CGI.unescape str}
260
+ end
254
261
  end
255
262
  end
256
263
 
257
264
  class Route #:nodoc:
258
265
  attr_accessor :components, :known
259
- attr_reader :path, :options, :keys
266
+ attr_reader :path, :options, :keys, :defaults
260
267
 
261
268
  def initialize(path, options = {})
262
269
  @path, @options = path, options
263
270
 
264
271
  initialize_components path
265
272
  defaults, conditions = initialize_hashes options.dup
273
+ @defaults = defaults.dup
266
274
  configure_components(defaults, conditions)
267
275
  initialize_keys
268
276
  end
@@ -423,7 +431,7 @@ module ActionController
423
431
  path.shift
424
432
 
425
433
  hash = recognize_path(path)
426
- recognition_failed(request) unless hash && hash['controller']
434
+ return recognition_failed(request) unless hash && hash['controller']
427
435
 
428
436
  controller = hash['controller']
429
437
  hash['controller'] = controller.controller_path
@@ -576,7 +584,7 @@ module ActionController
576
584
  end
577
585
 
578
586
  def name_route(route, name)
579
- hash = route.known.symbolize_keys
587
+ hash = route.defaults.merge(route.known).symbolize_keys
580
588
  hash[:controller] = "/#{hash[:controller]}"
581
589
 
582
590
  define_method(hash_access_name(name)) { hash }
@@ -27,7 +27,7 @@ class CGI
27
27
  # initialize(hash_of_session_id_and_data)
28
28
  # attr_reader :session_id
29
29
  # attr_accessor :data
30
- # save!
30
+ # save
31
31
  # destroy
32
32
  #
33
33
  # The fast SqlBypass class is a generic SQL session store. You may
@@ -198,7 +198,7 @@ class CGI
198
198
  @data
199
199
  end
200
200
 
201
- def save!
201
+ def save
202
202
  marshaled_data = self.class.marshal(data)
203
203
  if @new_record
204
204
  @new_record = false
@@ -240,7 +240,7 @@ class CGI
240
240
  # Find or instantiate a session given a CGI::Session.
241
241
  def initialize(session, option = nil)
242
242
  session_id = session.session_id
243
- unless @session = @@session_class.find_by_session_id(session_id)
243
+ unless @session = ActiveRecord::Base.silence { @@session_class.find_by_session_id(session_id) }
244
244
  unless session.new_session
245
245
  raise CGI::Session::NoSession, 'uninitialized session'
246
246
  end
@@ -258,7 +258,7 @@ class CGI
258
258
  # Save session store.
259
259
  def update
260
260
  if @session
261
- ActiveRecord::Base.silence { @session.save! }
261
+ ActiveRecord::Base.silence { @session.save }
262
262
  end
263
263
  end
264
264
 
@@ -72,10 +72,16 @@ module ActionController #:nodoc:
72
72
  extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
73
73
  non_path_parameters = get? ? query_parameters : request_parameters
74
74
  parameters.each do |key, value|
75
+ if value.is_a? Fixnum
76
+ value = value.to_s
77
+ elsif value.is_a? Array
78
+ value = ActionController::Routing::PathComponent::Result.new(value)
79
+ end
80
+
75
81
  if extra_keys.include?(key.to_sym)
76
82
  non_path_parameters[key] = value
77
83
  else
78
- path_parameters[key] = value.to_s
84
+ path_parameters[key.to_s] = value
79
85
  end
80
86
  end
81
87
  end
@@ -352,6 +358,36 @@ module Test
352
358
  return @controller.send(selector, *args) if ActionController::Routing::NamedRoutes::Helpers.include?(selector)
353
359
  return super
354
360
  end
361
+
362
+ # A helper to make it easier to test different route configurations.
363
+ # This method temporarily replaces ActionController::Routing::Routes
364
+ # with a new RouteSet instance.
365
+ #
366
+ # The new instance is yielded to the passed block. Typically the block
367
+ # will create some routes using map.draw { map.connect ... }:
368
+ #
369
+ # with_routing do |set|
370
+ # set.draw { set.connect ':controller/:id/:action' }
371
+ # assert_equal(
372
+ # ['/content/10/show', {}],
373
+ # set.generate(:controller => 'content', :id => 10, :action => 'show')
374
+ # )
375
+ # end
376
+ #
377
+ def with_routing
378
+ real_routes = ActionController::Routing::Routes
379
+ ActionController::Routing.send :remove_const, :Routes
380
+
381
+ temporary_routes = ActionController::Routing::RouteSet.new
382
+ ActionController::Routing.send :const_set, :Routes, temporary_routes
383
+
384
+ yield temporary_routes
385
+ ensure
386
+ if ActionController::Routing.const_defined? :Routes
387
+ ActionController::Routing.send(:remove_const, :Routes)
388
+ end
389
+ ActionController::Routing.const_set(:Routes, real_routes) if real_routes
390
+ end
355
391
  end
356
392
  end
357
393
  end
@@ -21,14 +21,8 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
- begin
25
- require 'rubygems'
26
- require 'builder'
27
- rescue LoadError
28
- # RubyGems is not available, use included Builder
29
- $:.unshift(File.dirname(__FILE__) + "/action_view/vendor")
30
- require 'action_view/vendor/builder'
31
- end
24
+ $:.unshift(File.dirname(__FILE__) + "/action_view/vendor")
25
+ require 'action_view/vendor/builder'
32
26
 
33
27
  require 'action_view/base'
34
28
  require 'action_view/partials'
@@ -72,7 +72,7 @@ module ActionView
72
72
  # :url => { :action => "undo", :n => word_counter },
73
73
  # :complete => "undoRequestCompleted(request)"
74
74
  #
75
- # The callbacks that may be specified are:
75
+ # The callbacks that may be specified are (in order):
76
76
  #
77
77
  # <tt>:loading</tt>:: Called when the remote document is being
78
78
  # loaded with data by the browser.
@@ -81,13 +81,15 @@ module ActionView
81
81
  # <tt>:interactive</tt>:: Called when the user can interact with the
82
82
  # remote document, even though it has not
83
83
  # finished loading.
84
- # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete,
85
- # and the HTTP status code is 200 OK.
86
- # <tt>:failure</tt>:: Called when the XMLHttpRequest is complete,
87
- # and the HTTP status code is anything other than
88
- # 200 OK.
89
- #
90
- # You can further refine <tt>:failure</tt> by adding additional
84
+ # <tt>:success</tt>:: Called when the XMLHttpRequest is completed,
85
+ # and the HTTP status code is in the 2XX range.
86
+ # <tt>:failure</tt>:: Called when the XMLHttpRequest is completed,
87
+ # and the HTTP status code is not in the 2XX
88
+ # range.
89
+ # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
90
+ # (fires after success/failure if they are present).,
91
+ #
92
+ # You can further refine <tt>:success</tt> and <tt>:failure</tt> by adding additional
91
93
  # callbacks for specific status codes:
92
94
  #
93
95
  # Example:
@@ -96,6 +98,7 @@ module ActionView
96
98
  # 404 => "alert('Not found...? Wrong URL...?')",
97
99
  # :failure => "alert('HTTP Error ' + request.status + '!')"
98
100
  #
101
+ # A status code callback overrides the success/failure handlers if present.
99
102
  #
100
103
  # If you for some reason or another need synchronous processing (that'll
101
104
  # block the browser while the request is happening), you can specify
@@ -358,8 +361,13 @@ module ActionView
358
361
  function << "'#{field_id}', "
359
362
  function << "'" + (options[:update] || "#{field_id}_auto_complete") + "', "
360
363
  function << "'#{url_for(options[:url])}'"
361
-
364
+
362
365
  js_options = {}
366
+ if options[:tokens] and options[:tokens].kind_of?(Array)
367
+ js_options[:tokens] = "['#{options[:tokens].join('\',\'')}']"
368
+ elsif options[:tokens]
369
+ js_options[:tokens] = "'#{options[:tokens]}'" if options[:tokens]
370
+ end
363
371
  js_options[:callback] = "function(element, value) { return #{options[:with]} }" if options[:with]
364
372
  js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator]
365
373
  function << (', ' + options_for_javascript(js_options) + ')')
@@ -419,7 +427,7 @@ module ActionView
419
427
  # http://script.aculo.us for more documentation.
420
428
  def visual_effect(name, element_id = false, js_options = {})
421
429
  element = element_id ? "'#{element_id}'" : "element"
422
- "new Effect.#{name.to_s.capitalize}(#{element},#{options_for_javascript(js_options)});"
430
+ "new Effect.#{name.to_s.camelize}(#{element},#{options_for_javascript(js_options)});"
423
431
  end
424
432
 
425
433
  # Makes the element with the DOM ID specified by +element_id+ sortable
@@ -441,7 +449,7 @@ module ActionView
441
449
  options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
442
450
  options.delete_if { |key, value| AJAX_OPTIONS.include?(key) }
443
451
 
444
- [:tag, :overlap, :constraint].each do |option|
452
+ [:tag, :overlap, :constraint, :handle].each do |option|
445
453
  options[option] = "'#{options[option]}'" if options[option]
446
454
  end
447
455
 
@@ -504,7 +512,7 @@ module ActionView
504
512
 
505
513
  private
506
514
  def options_for_javascript(options)
507
- '{' + options.map {|k, v| "#{k}:#{v}"}.join(', ') + '}'
515
+ '{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}'
508
516
  end
509
517
 
510
518
  def options_for_ajax(options)