actionpack 3.0.0.rc2 → 3.0.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.

@@ -167,6 +167,7 @@ module ActionController
167
167
  @formats = nil
168
168
  @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
169
169
  @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
170
+ @symbolized_path_params = nil
170
171
  @method = @request_method = nil
171
172
  @fullpath = @ip = @remote_ip = nil
172
173
  @env['action_dispatch.request.query_parameters'] = {}
@@ -32,7 +32,7 @@ module ActionDispatch
32
32
  end
33
33
  end
34
34
 
35
- # Returns the Mime type for the \format used in the request.
35
+ # Returns the MIME type for the \format used in the request.
36
36
  #
37
37
  # GET /posts/5.xml | request.format => Mime::XML
38
38
  # GET /posts/5.xhtml | request.format => Mime::HTML
@@ -15,14 +15,14 @@ module ActionDispatch
15
15
  alias :params :parameters
16
16
 
17
17
  def path_parameters=(parameters) #:nodoc:
18
- @_symbolized_path_params = nil
18
+ @symbolized_path_params = nil
19
19
  @env.delete("action_dispatch.request.parameters")
20
20
  @env["action_dispatch.request.path_parameters"] = parameters
21
21
  end
22
22
 
23
23
  # The same as <tt>path_parameters</tt> with explicitly symbolized keys.
24
24
  def symbolized_path_parameters
25
- @_symbolized_path_params ||= path_parameters.symbolize_keys
25
+ @symbolized_path_params ||= path_parameters.symbolize_keys
26
26
  end
27
27
 
28
28
  # Returns a hash with the \parameters used to form the \path of the request.
@@ -1,7 +1,7 @@
1
1
  module ActionDispatch
2
2
  module Http
3
3
  module URL
4
- # Returns the complete URL used for this request.
4
+ # Returns the complete \URL used for this request.
5
5
  def url
6
6
  protocol + host_with_port + fullpath
7
7
  end
@@ -96,7 +96,7 @@ module ActionDispatch
96
96
  end
97
97
 
98
98
  # Returns the request URI, accounting for server idiosyncrasies.
99
- # WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
99
+ # WEBrick includes the full \URL. IIS leaves REQUEST_URI blank.
100
100
  def request_uri
101
101
  ActiveSupport::Deprecation.warn "Using #request_uri is deprecated. Use fullpath instead.", caller
102
102
  fullpath
@@ -7,7 +7,7 @@ module ActionDispatch
7
7
  end
8
8
  end
9
9
 
10
- # Cookies are read and written through ActionController#cookies.
10
+ # \Cookies are read and written through ActionController#cookies.
11
11
  #
12
12
  # The cookies being read are the ones received along with the request, the cookies
13
13
  # being written will be sent out with the response. Reading a cookie does not get
@@ -21,6 +21,15 @@ module ActionDispatch
21
21
  # # Sets a cookie that expires in 1 hour.
22
22
  # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
23
23
  #
24
+ # # Sets a signed cookie, which prevents a user from tampering with its value.
25
+ # # You must specify a value in ActionController::Base.cookie_verifier_secret.
26
+ # cookies.signed[:remember_me] = [current_user.id, current_user.salt]
27
+ #
28
+ # # Sets a "permanent" cookie (which expires in 20 years from now).
29
+ # cookies.permanent[:login] = "XJ-122"
30
+ # # You can also chain these methods:
31
+ # cookies.permanent.signed[:login] = "XJ-122"
32
+ #
24
33
  # Examples for reading:
25
34
  #
26
35
  # cookies[:user_name] # => "david"
@@ -55,7 +64,7 @@ module ActionDispatch
55
64
  # :domain => :all # Allow the cookie for the top most level
56
65
  # domain and subdomains.
57
66
  #
58
- # * <tt>:expires</tt> - The time at which this cookie expires, as a Time object.
67
+ # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
59
68
  # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
60
69
  # Default is +false+.
61
70
  # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
@@ -10,13 +10,13 @@ module ActionDispatch
10
10
 
11
11
  # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
12
12
  # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
13
- # action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
13
+ # action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
14
14
  # then expose the flash to its template. Actually, that exposure is automatically done. Example:
15
15
  #
16
16
  # class PostsController < ActionController::Base
17
17
  # def create
18
18
  # # save post
19
- # flash[:notice] = "Successfully created post"
19
+ # flash[:notice] = "Post successfully created"
20
20
  # redirect_to posts_path(@post)
21
21
  # end
22
22
  #
@@ -30,6 +30,11 @@ module ActionDispatch
30
30
  # <div class="notice"><%= flash[:notice] %></div>
31
31
  # <% end %>
32
32
  #
33
+ # Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available:
34
+ #
35
+ # flash.alert = "You must be logged in"
36
+ # flash.notice = "Post successfully created"
37
+ #
33
38
  # This example just places a string in the flash, but you can put any object in there. And of course, you can put as
34
39
  # many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
35
40
  #
@@ -43,9 +43,10 @@ module ActionDispatch
43
43
  class Mapping #:nodoc:
44
44
  IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
45
45
 
46
- def initialize(set, scope, args)
47
- @set, @scope = set, scope
48
- @path, @options = extract_path_and_options(args)
46
+ def initialize(set, scope, path, options)
47
+ @set, @scope = set, scope
48
+ @options = (@scope[:options] || {}).merge(options)
49
+ @path = normalize_path(path)
49
50
  normalize_options!
50
51
  end
51
52
 
@@ -54,28 +55,6 @@ module ActionDispatch
54
55
  end
55
56
 
56
57
  private
57
- def extract_path_and_options(args)
58
- options = args.extract_options!
59
-
60
- if using_to_shorthand?(args, options)
61
- path, to = options.find { |name, value| name.is_a?(String) }
62
- options.merge!(:to => to).delete(path) if path
63
- else
64
- path = args.first
65
- end
66
-
67
- if path.match(':controller')
68
- raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
69
-
70
- # Add a default constraint for :controller path segments that matches namespaced
71
- # controllers with default routes like :controller/:action/:id(.:format), e.g:
72
- # GET /admin/products/show/1
73
- # => { :controller => 'admin/products', :action => 'show', :id => '1' }
74
- options.reverse_merge!(:controller => /.+?/)
75
- end
76
-
77
- [ normalize_path(path), options ]
78
- end
79
58
 
80
59
  def normalize_options!
81
60
  path_without_format = @path.sub(/\(\.:format\)$/, '')
@@ -83,25 +62,39 @@ module ActionDispatch
83
62
  if using_match_shorthand?(path_without_format, @options)
84
63
  to_shorthand = @options[:to].blank?
85
64
  @options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1')
86
- @options[:as] ||= path_without_format[1..-1].gsub("/", "_")
65
+ @options[:as] ||= Mapper.normalize_name(path_without_format)
87
66
  end
88
67
 
89
68
  @options.merge!(default_controller_and_action(to_shorthand))
90
69
  end
91
70
 
92
- # match "account" => "account#index"
93
- def using_to_shorthand?(args, options)
94
- args.empty? && options.present?
95
- end
96
-
97
71
  # match "account/overview"
98
72
  def using_match_shorthand?(path, options)
99
73
  path && options.except(:via, :anchor, :to, :as).empty? && path =~ %r{^/[\w\/]+$}
100
74
  end
101
75
 
102
76
  def normalize_path(path)
103
- raise ArgumentError, "path is required" if @scope[:path].blank? && path.blank?
104
- Mapper.normalize_path("#{@scope[:path]}/#{path}")
77
+ raise ArgumentError, "path is required" if path.blank?
78
+ path = Mapper.normalize_path(path)
79
+
80
+ if path.match(':controller')
81
+ raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
82
+
83
+ # Add a default constraint for :controller path segments that matches namespaced
84
+ # controllers with default routes like :controller/:action/:id(.:format), e.g:
85
+ # GET /admin/products/show/1
86
+ # => { :controller => 'admin/products', :action => 'show', :id => '1' }
87
+ @options.reverse_merge!(:controller => /.+?/)
88
+ end
89
+
90
+ if @options[:format] == false
91
+ @options.delete(:format)
92
+ path
93
+ elsif path.include?(":format")
94
+ path
95
+ else
96
+ "#{path}(.:format)"
97
+ end
105
98
  end
106
99
 
107
100
  def app
@@ -224,6 +217,10 @@ module ActionDispatch
224
217
  path
225
218
  end
226
219
 
220
+ def self.normalize_name(name)
221
+ normalize_path(name)[1..-1].gsub("/", "_")
222
+ end
223
+
227
224
  module Base
228
225
  def initialize(set) #:nodoc:
229
226
  @set = set
@@ -233,8 +230,8 @@ module ActionDispatch
233
230
  match '/', options.reverse_merge(:as => :root)
234
231
  end
235
232
 
236
- def match(*args)
237
- mapping = Mapping.new(@set, @scope, args).to_route
233
+ def match(path, options=nil)
234
+ mapping = Mapping.new(@set, @scope, path, options || {}).to_route
238
235
  @set.add_route(*mapping)
239
236
  self
240
237
  end
@@ -250,7 +247,7 @@ module ActionDispatch
250
247
 
251
248
  raise "A rack application must be specified" unless path
252
249
 
253
- match(path, options.merge(:to => app, :anchor => false))
250
+ match(path, options.merge(:to => app, :anchor => false, :format => false))
254
251
  self
255
252
  end
256
253
 
@@ -332,13 +329,7 @@ module ActionDispatch
332
329
  ActiveSupport::Deprecation.warn ":name_prefix was deprecated in the new router syntax. Use :as instead.", caller
333
330
  end
334
331
 
335
- case args.first
336
- when String
337
- options[:path] = args.first
338
- when Symbol
339
- options[:controller] = args.first
340
- end
341
-
332
+ options[:path] = args.first if args.first.is_a?(String)
342
333
  recover = {}
343
334
 
344
335
  options[:constraints] ||= {}
@@ -370,8 +361,9 @@ module ActionDispatch
370
361
  @scope[:blocks] = recover[:block]
371
362
  end
372
363
 
373
- def controller(controller)
374
- scope(controller.to_sym) { yield }
364
+ def controller(controller, options={})
365
+ options[:controller] = controller
366
+ scope(options) { yield }
375
367
  end
376
368
 
377
369
  def namespace(path, options = {})
@@ -389,21 +381,6 @@ module ActionDispatch
389
381
  scope(:defaults => defaults) { yield }
390
382
  end
391
383
 
392
- def match(*args)
393
- options = args.extract_options!
394
-
395
- options = (@scope[:options] || {}).merge(options)
396
-
397
- if @scope[:as] && !options[:as].blank?
398
- options[:as] = "#{@scope[:as]}_#{options[:as]}"
399
- elsif @scope[:as] && options[:as] == ""
400
- options[:as] = @scope[:as].to_s
401
- end
402
-
403
- args.push(options)
404
- super(*args)
405
- end
406
-
407
384
  private
408
385
  def scope_options
409
386
  @scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
@@ -467,9 +444,9 @@ module ActionDispatch
467
444
  module Resources
468
445
  # CANONICAL_ACTIONS holds all actions that does not need a prefix or
469
446
  # a path appended since they fit properly in their scope level.
470
- VALID_ON_OPTIONS = [:new, :collection, :member]
471
- CANONICAL_ACTIONS = [:index, :create, :new, :show, :update, :destroy]
472
- RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
447
+ VALID_ON_OPTIONS = [:new, :collection, :member]
448
+ RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
449
+ CANONICAL_ACTIONS = %w(index create new show update destroy)
473
450
 
474
451
  class Resource #:nodoc:
475
452
  DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit]
@@ -718,47 +695,31 @@ module ActionDispatch
718
695
  raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
719
696
  end
720
697
 
721
- if @scope[:scope_level] == :resource
698
+ if @scope[:scope_level] == :resources
699
+ args.push(options)
700
+ return nested { match(*args) }
701
+ elsif @scope[:scope_level] == :resource
722
702
  args.push(options)
723
703
  return member { match(*args) }
724
704
  end
725
705
 
726
- path = options.delete(:path)
727
706
  action = args.first
707
+ path = path_for_action(action, options.delete(:path))
728
708
 
729
- if action.is_a?(Symbol)
730
- path = path_for_action(action, path)
731
- options[:to] ||= action
732
- options[:as] = name_for_action(action, options[:as])
733
-
734
- with_exclusive_scope do
735
- return super(path, options)
736
- end
737
- elsif resource_method_scope?
738
- path = path_for_custom_action
739
- options[:action] ||= action
740
- options[:as] = name_for_action(options[:as]) if options[:as]
741
- args.push(options)
742
-
743
- with_exclusive_scope do
744
- scope(path) do
745
- return super
746
- end
747
- end
748
- end
749
-
750
- if resource_scope?
751
- raise ArgumentError, "can't define route directly in resource(s) scope"
709
+ if action.to_s =~ /^[\w\/]+$/
710
+ options[:action] ||= action unless action.to_s.include?("/")
711
+ options[:as] = name_for_action(action, options[:as])
712
+ else
713
+ options[:as] = name_for_action(options[:as])
752
714
  end
753
715
 
754
- args.push(options)
755
- super
716
+ super(path, options)
756
717
  end
757
718
 
758
719
  def root(options={})
759
720
  if @scope[:scope_level] == :resources
760
- with_scope_level(:nested) do
761
- scope(parent_resource.path, :as => parent_resource.collection_name) do
721
+ with_scope_level(:root) do
722
+ scope(parent_resource.path) do
762
723
  super(options)
763
724
  end
764
725
  end
@@ -895,7 +856,7 @@ module ActionDispatch
895
856
  end
896
857
 
897
858
  def canonical_action?(action, flag)
898
- flag && CANONICAL_ACTIONS.include?(action)
859
+ flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
899
860
  end
900
861
 
901
862
  def shallow_scoping?
@@ -906,18 +867,10 @@ module ActionDispatch
906
867
  prefix = shallow_scoping? ?
907
868
  "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
908
869
 
909
- if canonical_action?(action, path.blank?)
910
- "#{prefix}(.:format)"
870
+ path = if canonical_action?(action, path.blank?)
871
+ prefix.to_s
911
872
  else
912
- "#{prefix}/#{action_path(action, path)}(.:format)"
913
- end
914
- end
915
-
916
- def path_for_custom_action
917
- if shallow_scoping?
918
- "#{@scope[:shallow_path]}/#{parent_resource.path}/:id"
919
- else
920
- @scope[:path]
873
+ "#{prefix}/#{action_path(action, path)}"
921
874
  end
922
875
  end
923
876
 
@@ -927,44 +880,61 @@ module ActionDispatch
927
880
 
928
881
  def prefix_name_for_action(action, as)
929
882
  if as.present?
930
- "#{as}_"
883
+ as.to_s
931
884
  elsif as
932
- ""
885
+ nil
933
886
  elsif !canonical_action?(action, @scope[:scope_level])
934
- "#{action}_"
887
+ action.to_s
935
888
  end
936
889
  end
937
890
 
938
891
  def name_for_action(action, as=nil)
939
892
  prefix = prefix_name_for_action(action, as)
893
+ prefix = Mapper.normalize_name(prefix) if prefix
940
894
  name_prefix = @scope[:as]
941
895
 
942
896
  if parent_resource
943
897
  collection_name = parent_resource.collection_name
944
898
  member_name = parent_resource.member_name
945
- name_prefix = "#{name_prefix}_" if name_prefix.present?
946
899
  end
947
900
 
948
- case @scope[:scope_level]
901
+ name = case @scope[:scope_level]
902
+ when :nested
903
+ [member_name, prefix]
949
904
  when :collection
950
- "#{prefix}#{name_prefix}#{collection_name}"
905
+ [prefix, name_prefix, collection_name]
951
906
  when :new
952
- "#{prefix}new_#{name_prefix}#{member_name}"
907
+ [prefix, :new, name_prefix, member_name]
908
+ when :member
909
+ [prefix, shallow_scoping? ? @scope[:shallow_prefix] : name_prefix, member_name]
910
+ when :root
911
+ [name_prefix, collection_name, prefix]
953
912
  else
954
- if shallow_scoping?
955
- shallow_prefix = "#{@scope[:shallow_prefix]}_" if @scope[:shallow_prefix].present?
956
- "#{prefix}#{shallow_prefix}#{member_name}"
957
- else
958
- "#{prefix}#{name_prefix}#{member_name}"
959
- end
913
+ [name_prefix, member_name, prefix]
960
914
  end
915
+
916
+ name.select(&:present?).join("_").presence
917
+ end
918
+ end
919
+
920
+ module Shorthand
921
+ def match(*args)
922
+ if args.size == 1 && args.last.is_a?(Hash)
923
+ options = args.pop
924
+ path, to = options.find { |name, value| name.is_a?(String) }
925
+ options.merge!(:to => to).delete(path)
926
+ super(path, options)
927
+ else
928
+ super
961
929
  end
930
+ end
962
931
  end
963
932
 
964
933
  include Base
965
934
  include HttpHelpers
966
935
  include Scoping
967
936
  include Resources
937
+ include Shorthand
968
938
  end
969
939
  end
970
940
  end