Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' into streaming

  • Loading branch information...
commit d307853c2fc56110d47b562deb04f3eff6c4766f 2 parents f22b4b5 + 5bf3294
@jeremy jeremy authored
Showing with 1,424 additions and 869 deletions.
  1. +1 −0  .gitignore
  2. +5 −3 actionmailer/lib/action_mailer/base.rb
  3. +1 −0  actionmailer/test/base_test.rb
  4. +7 −5 actionmailer/test/old_base/url_test.rb
  5. +7 −0 actionpack/CHANGELOG
  6. +1 −1  actionpack/actionpack.gemspec
  7. +2 −2 actionpack/lib/abstract_controller/rendering.rb
  8. +5 −5 actionpack/lib/action_controller/metal/url_for.rb
  9. +5 −0 actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
  10. +1 −1  actionpack/lib/action_dispatch/routing.rb
  11. +1 −1  actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
  12. +149 −199 actionpack/lib/action_dispatch/routing/mapper.rb
  13. +42 −36 actionpack/lib/action_dispatch/routing/route_set.rb
  14. +1 −1  actionpack/lib/action_dispatch/routing/url_for.rb
  15. +1 −1  actionpack/lib/action_view/base.rb
  16. +3 −1 actionpack/lib/action_view/helpers/form_tag_helper.rb
  17. +1 −1  actionpack/lib/action_view/helpers/prototype_helper.rb
  18. +1 −1  actionpack/lib/action_view/helpers/tag_helper.rb
  19. +22 −30 actionpack/lib/action_view/helpers/url_helper.rb
  20. +1 −1  actionpack/lib/action_view/template.rb
  21. +5 −5 actionpack/lib/action_view/test_case.rb
  22. +14 −11 actionpack/test/abstract_unit.rb
  23. +20 −0 actionpack/test/controller/mime_responds_test.rb
  24. +1 −1  actionpack/test/controller/url_for_test.rb
  25. +133 −7 actionpack/test/dispatch/routing_test.rb
  26. +22 −0 actionpack/test/dispatch/session/cookie_store_test.rb
  27. +8 −8 actionpack/test/dispatch/url_generation_test.rb
  28. +1 −1  actionpack/test/template/form_helper_test.rb
  29. +1 −1  actionpack/test/template/form_tag_helper_test.rb
  30. +2 −2 actionpack/test/template/tag_helper_test.rb
  31. +34 −22 actionpack/test/template/url_helper_test.rb
  32. +1 −0  activemodel/lib/active_model/errors.rb
  33. +2 −0  activerecord/CHANGELOG
  34. +17 −1 activerecord/lib/active_record/associations/association_collection.rb
  35. +5 −1 activerecord/lib/active_record/associations/has_many_association.rb
  36. +47 −20 activerecord/lib/active_record/base.rb
  37. +1 −1  activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
  38. +1 −0  activerecord/lib/active_record/migration.rb
  39. +23 −12 activerecord/lib/active_record/named_scope.rb
  40. +1 −1  activerecord/lib/active_record/nested_attributes.rb
  41. +1 −1  activerecord/lib/active_record/persistence.rb
  42. +8 −8 activerecord/lib/active_record/railties/databases.rake
  43. +24 −21 activerecord/lib/active_record/relation.rb
  44. +1 −3 activerecord/lib/active_record/relation/predicate_builder.rb
  45. +4 −10 activerecord/lib/active_record/relation/query_methods.rb
  46. +1 −1  activerecord/lib/active_record/relation/spawn_methods.rb
  47. +62 −0 activerecord/test/cases/associations/has_many_associations_test.rb
  48. +0 −19 activerecord/test/cases/base_test.rb
  49. +1 −1  activerecord/test/cases/inheritance_test.rb
  50. +11 −202 activerecord/test/cases/method_scoping_test.rb
  51. +17 −0 activerecord/test/cases/migration_test.rb
  52. +8 −2 activerecord/test/cases/named_scope_test.rb
  53. +6 −0 activerecord/test/cases/nested_attributes_test.rb
  54. +396 −0 activerecord/test/cases/relation_scoping_test.rb
  55. +1 −1  activerecord/test/cases/relations_test.rb
  56. +7 −1 activerecord/test/models/developer.rb
  57. +3 −0  activerecord/test/models/without_table.rb
  58. +6 −0 activesupport/CHANGELOG
  59. +0 −1  activesupport/lib/active_support/all.rb
  60. +7 −0 activesupport/lib/active_support/core_ext/array/random_access.rb
  61. +2 −1  activesupport/lib/active_support/core_ext/date/calculations.rb
  62. +3 −13 activesupport/lib/active_support/core_ext/date/conversions.rb
  63. +14 −0 activesupport/lib/active_support/core_ext/date/zones.rb
  64. +1 −0  activesupport/lib/active_support/core_ext/date_time/calculations.rb
  65. +1 −0  activesupport/lib/active_support/core_ext/date_time/conversions.rb
  66. +2 −0  activesupport/lib/active_support/core_ext/date_time/zones.rb
  67. +1 −0  activesupport/lib/active_support/core_ext/object.rb
  68. +19 −0 activesupport/lib/active_support/core_ext/object/to_json.rb
  69. +5 −0 activesupport/lib/active_support/core_ext/time/calculations.rb
  70. +0 −5 activesupport/lib/active_support/core_ext/time/zones.rb
  71. +13 −9 activesupport/lib/active_support/deprecation/behaviors.rb
  72. +3 −2 activesupport/lib/active_support/deprecation/reporting.rb
  73. +5 −6 activesupport/lib/active_support/file_update_checker.rb
  74. +1 −0  activesupport/lib/active_support/i18n_railtie.rb
  75. +10 −32 activesupport/lib/active_support/json/encoding.rb
  76. +2 −4 activesupport/lib/active_support/json/variable.rb
  77. +5 −4 activesupport/lib/active_support/multibyte/chars.rb
  78. +1 −1  activesupport/lib/active_support/multibyte/unicode.rb
  79. +31 −1 activesupport/lib/active_support/railtie.rb
  80. +18 −36 activesupport/lib/active_support/testing/performance.rb
  81. +1 −0  activesupport/lib/active_support/time.rb
  82. +6 −6 activesupport/lib/active_support/values/time_zone.rb
  83. BIN  activesupport/lib/active_support/values/unicode_tables.dat
  84. +20 −0 activesupport/test/deprecation_test.rb
  85. +4 −2 activesupport/test/json/encoding_test.rb
  86. +9 −6 activesupport/test/multibyte_chars_test.rb
  87. +1 −0  activesupport/test/ordered_hash_test.rb
  88. +1 −1  rails.gemspec
  89. +29 −49 railties/guides/source/initialization.textile
  90. +9 −14 railties/lib/rails/application.rb
  91. +5 −5 railties/lib/rails/commands.rb
  92. +1 −1  railties/lib/rails/commands/application.rb
  93. +1 −1  railties/lib/rails/commands/runner.rb
  94. +1 −1  railties/lib/rails/generators/rails/app/templates/Rakefile
  95. +3 −0  railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
  96. +3 −0  railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
  97. +3 −0  railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
  98. +1 −1  railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
  99. +2 −2 railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
  100. +1 −1  railties/lib/rails/info_routes.rb
  101. +1 −1  railties/lib/rails/tasks/middleware.rake
  102. +1 −1  railties/lib/rails/tasks/routes.rake
  103. +1 −1  railties/railties.gemspec
  104. +1 −1  railties/test/application/initializers/frameworks_test.rb
  105. +1 −1  railties/test/application/initializers/i18n_test.rb
  106. +14 −1 railties/test/application/loading_test.rb
  107. +2 −2 railties/test/application/rake_test.rb
  108. +1 −1  railties/test/application/runner_test.rb
  109. +1 −0  railties/test/application/url_generation_test.rb
  110. +2 −1  railties/test/isolation/abstract_unit.rb
  111. +1 −1  railties/test/rails_info_controller_test.rb
View
1  .gitignore
@@ -1,6 +1,7 @@
*.gem
pkg
.bundle
+Gemfile.lock
debug.log
doc/rdoc
activemodel/doc
View
8 actionmailer/lib/action_mailer/base.rb
@@ -540,7 +540,9 @@ def attachments
# :reply_to => 'bounces@test.lindsaar.net'
# end
#
- # If you need other headers not listed above, use the <tt>headers['name'] = value</tt> method.
+ # If you need other headers not listed above, you can either pass them in
+ # as part of the headers hash or use the <tt>headers['name'] = value</tt>
+ # method.
#
# When a <tt>:return_path</tt> is specified as header, that value will be used as the 'envelope from'
# address for the Mail message. Setting this is useful when you want delivery notifications
@@ -746,13 +748,13 @@ def deprecated_url_options
raise "You can no longer call ActionMailer::Base.default_url_options " \
"directly. You need to set config.action_mailer.default_url_options. " \
"If you are using ActionMailer standalone, you need to include the " \
- "url_helpers of a router directly."
+ "routing url_helpers directly."
end
end
# This module will complain if the user tries to set default_url_options
# directly instead of through the config object. In Action Mailer's Railtie,
- # we include the url_helpers of the router, which will override this module
+ # we include the router's url_helpers, which will override this module.
extend DeprecatedUrlOptions
ActiveSupport.run_load_hooks(:action_mailer, self)
View
1  actionmailer/test/base_test.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
require 'abstract_unit'
+require 'active_support/time'
class BaseTest < ActiveSupport::TestCase
# TODO Add some tests for implicity layout render and url helpers
View
12 actionmailer/test/old_base/url_test.rb
@@ -28,7 +28,7 @@ def signed_up_with_url(recipient)
end
end
-class ActionMailerUrlTest < Test::Unit::TestCase
+class ActionMailerUrlTest < ActionMailer::TestCase
def encode( text, charset="UTF-8" )
quoted_printable( text, charset )
@@ -57,10 +57,12 @@ def teardown
def test_signed_up_with_url
UrlTestMailer.delivery_method = :test
-
- AppRoutes.draw do |map|
- map.connect ':controller/:action/:id'
- map.welcome 'welcome', :controller=>"foo", :action=>"bar"
+
+ assert_deprecated do
+ AppRoutes.draw do |map|
+ map.connect ':controller/:action/:id'
+ map.welcome 'welcome', :controller=>"foo", :action=>"bar"
+ end
end
expected = new_mail
View
7 actionpack/CHANGELOG
@@ -1,5 +1,12 @@
*Rails 3.0.0 [Release Candidate] (unreleased)*
+* link_to, button_to, and tag/tag_options now rely on html_escape instead of escape_once. [fxn]
+
+* url_for returns always unescaped strings, and the :escape option is gone. [fxn]
+
+* Added accept-charset parameter and _snowman hidden field to force the contents
+ of Rails POSTed forms to be in UTF-8 [Yehuda Katz]
+
* Upgrade to Rack 1.2.1 [Jeremy Kemper]
* Allow :path to be given to match/get/post/put/delete instead of :path_names in the new router [Carlos Antônio da Silva]
View
2  actionpack/actionpack.gemspec
@@ -27,5 +27,5 @@ Gem::Specification.new do |s|
s.add_dependency('rack-test', '~> 0.5.4')
#s.add_dependency('rack-mount', '~> 0.6.6')
s.add_dependency('tzinfo', '~> 0.3.16')
- s.add_dependency('erubis', '~> 2.6.5')
+ s.add_dependency('erubis', '~> 2.6.6')
end
View
4 actionpack/lib/abstract_controller/rendering.rb
@@ -50,8 +50,8 @@ def view_context_class
if controller.respond_to?(:_helpers)
include controller._helpers
- if controller.respond_to?(:_router)
- include controller._router.url_helpers
+ if controller.respond_to?(:_routes)
+ include controller._routes.url_helpers
end
# TODO: Fix RJS to not require this
View
10 actionpack/lib/action_controller/metal/url_for.rb
@@ -12,17 +12,17 @@ def url_options
).merge(:script_name => request.script_name)
end
- def _router
- raise "In order to use #url_for, you must include the helpers of a particular " \
- "router. For instance, `include Rails.application.routes.url_helpers"
+ def _routes
+ raise "In order to use #url_for, you must include routing helpers explicitly. " \
+ "For instance, `include Rails.application.routes.url_helpers"
end
module ClassMethods
def action_methods
@action_methods ||= begin
- super - _router.named_routes.helper_names
+ super - _routes.named_routes.helper_names
end
end
end
end
-end
+end
View
5 actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -64,6 +64,11 @@ def []=(key, value)
super(key.to_s, value)
end
+ def clear
+ load_for_write!
+ super
+ end
+
def to_hash
load_for_read!
h = {}.replace(self)
View
2  actionpack/lib/action_dispatch/routing.rb
@@ -167,7 +167,7 @@ module ActionDispatch
#
# You can reload routes if you feel you must:
#
- # Rails::Application.reload_routes!
+ # Rails.application.reload_routes!
#
# This will clear all named routes and reload routes.rb if the file has been modified from
# last load. To absolutely force reloading, use <tt>reload!</tt>.
View
2  actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
@@ -31,7 +31,7 @@ def in_memory_controller_namespaces
class DeprecatedMapper #:nodoc:
def initialize(set) #:nodoc:
ActiveSupport::Deprecation.warn "You are using the old router DSL which will be removed in Rails 3.1. " <<
- "Please check how to update your router file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
+ "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
@set = set
end
View
348 actionpack/lib/action_dispatch/routing/mapper.rb
@@ -91,7 +91,7 @@ def normalize_path(path)
def app
Constraints.new(
- to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults),
+ to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults, :module => @scope[:module]),
blocks,
@set.request_class
)
@@ -131,6 +131,7 @@ def default_controller_and_action
end
defaults[:controller] ||= default_controller
+ defaults[:action] ||= default_action
defaults.delete(:controller) if defaults[:controller].blank?
defaults.delete(:action) if defaults[:action].blank?
@@ -187,6 +188,12 @@ def default_controller
@scope[:controller].to_s
end
end
+
+ def default_action
+ if @options[:action]
+ @options[:action].to_s
+ end
+ end
end
# Invokes Rack::Mount::Utils.normalize path and ensure that
@@ -433,12 +440,14 @@ def merge_shallow_scope(parent, child)
end
module Resources
- MERGE_FROM_SCOPE_OPTIONS = [:shallow, :constraints]
+ # CANONICAL_ACTIONS holds all actions that does not need a prefix or
+ # a path appended since they fit properly in their scope level.
+ VALID_ON_OPTIONS = [:new, :collection, :member]
+ CANONICAL_ACTIONS = [:index, :create, :new, :show, :update, :destroy]
+ RESOURCE_OPTIONS = [:as, :controller, :path]
class Resource #:nodoc:
- def self.default_actions
- [:index, :create, :new, :show, :update, :destroy, :edit]
- end
+ DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit]
attr_reader :controller, :path, :options
@@ -451,17 +460,7 @@ def initialize(entities, options = {})
end
def default_actions
- self.class.default_actions
- end
-
- def actions
- if only = options[:only]
- Array(only).map(&:to_sym)
- elsif except = options[:except]
- default_actions - Array(except).map(&:to_sym)
- else
- default_actions
- end
+ self.class::DEFAULT_ACTIONS
end
def name
@@ -480,79 +479,40 @@ def member_name
singular
end
- alias_method :nested_name, :member_name
-
# Checks for uncountable plurals, and appends "_index" if they're.
def collection_name
singular == plural ? "#{plural}_index" : plural
end
- def shallow?
- options[:shallow] ? true : false
- end
-
- def constraints
- options[:constraints] || {}
- end
-
- def id_constraint?
- options[:id] && options[:id].is_a?(Regexp) || constraints[:id] && constraints[:id].is_a?(Regexp)
- end
-
- def id_constraint
- options[:id] || constraints[:id]
- end
-
- def collection_options
- (options || {}).dup.tap do |opts|
- opts.delete(:id)
- opts[:constraints] = options[:constraints].dup if options[:constraints]
- opts[:constraints].delete(:id) if options[:constraints].is_a?(Hash)
- end
- end
-
- def nested_path
- "#{path}/:#{singular}_id"
- end
-
- def nested_options
- {}.tap do |opts|
- opts[:as] = member_name
- opts["#{singular}_id".to_sym] = id_constraint if id_constraint?
- opts[:options] = { :shallow => shallow? } unless options[:shallow].nil?
- end
- end
-
def resource_scope
- [{ :controller => controller }]
+ { :controller => controller }
end
def collection_scope
- [path, collection_options]
+ path
end
def member_scope
- ["#{path}/:id", options]
+ "#{path}/:id"
end
- def new_scope
- [path]
+ def new_scope(new_path)
+ "#{path}/#{new_path}"
end
def nested_scope
- [nested_path, nested_options]
+ "#{path}/:#{singular}_id"
end
+
end
class SingletonResource < Resource #:nodoc:
- def self.default_actions
- [:show, :create, :update, :destroy, :new, :edit]
- end
+ DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit]
def initialize(entities, options)
@name = entities.to_s
@path = options.delete(:path) || @name
- @controller = (options.delete(:controller) || @name.to_s.pluralize).to_s
+ @controller = (options.delete(:controller) || plural).to_s
@as = options.delete(:as)
@options = options
end
@@ -562,24 +522,10 @@ def member_name
end
alias :collection_name :member_name
- def nested_path
- path
- end
-
- def nested_options
- {}.tap do |opts|
- opts[:as] = member_name
- opts[:options] = { :shallow => shallow? } unless @options[:shallow].nil?
- end
- end
-
- def shallow?
- false
- end
-
def member_scope
- [path, options]
+ path
end
+ alias :nested_scope :member_scope
end
def initialize(*args) #:nodoc:
@@ -602,15 +548,18 @@ def resource(*resources, &block)
yield if block_given?
collection_scope do
- post :create if parent_resource.actions.include?(:create)
- get :new if parent_resource.actions.include?(:new)
- end
+ post :create
+ end if resource_actions.include?(:create)
+
+ new_scope do
+ get :new
+ end if resource_actions.include?(:new)
member_scope do
- get :show if parent_resource.actions.include?(:show)
- put :update if parent_resource.actions.include?(:update)
- delete :destroy if parent_resource.actions.include?(:destroy)
- get :edit if parent_resource.actions.include?(:edit)
+ get :show if resource_actions.include?(:show)
+ put :update if resource_actions.include?(:update)
+ delete :destroy if resource_actions.include?(:destroy)
+ get :edit if resource_actions.include?(:edit)
end
end
@@ -628,16 +577,19 @@ def resources(*resources, &block)
yield if block_given?
collection_scope do
- get :index if parent_resource.actions.include?(:index)
- post :create if parent_resource.actions.include?(:create)
- get :new if parent_resource.actions.include?(:new)
+ get :index if resource_actions.include?(:index)
+ post :create if resource_actions.include?(:create)
end
+ new_scope do
+ get :new
+ end if resource_actions.include?(:new)
+
member_scope do
- get :show if parent_resource.actions.include?(:show)
- put :update if parent_resource.actions.include?(:update)
- delete :destroy if parent_resource.actions.include?(:destroy)
- get :edit if parent_resource.actions.include?(:edit)
+ get :show if resource_actions.include?(:show)
+ put :update if resource_actions.include?(:update)
+ delete :destroy if resource_actions.include?(:destroy)
+ get :edit if resource_actions.include?(:edit)
end
end
@@ -669,12 +621,8 @@ def new
raise ArgumentError, "can't use new outside resource(s) scope"
end
- with_scope_level(:new) do
- scope(*parent_resource.new_scope) do
- scope(action_path(:new)) do
- yield
- end
- end
+ new_scope do
+ yield
end
end
@@ -684,18 +632,18 @@ def nested
end
with_scope_level(:nested) do
- if parent_resource.shallow?
+ if shallow?
with_exclusive_scope do
if @scope[:shallow_path].blank?
- scope(*parent_resource.nested_scope) { yield }
+ scope(parent_resource.nested_scope, nested_options) { yield }
else
scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do
- scope(*parent_resource.nested_scope) { yield }
+ scope(parent_resource.nested_scope, nested_options) { yield }
end
end
end
else
- scope(*parent_resource.nested_scope) { yield }
+ scope(parent_resource.nested_scope, nested_options) { yield }
end
end
end
@@ -714,9 +662,12 @@ def shallow
end
end
- def match(*args)
- options = args.extract_options!
+ def shallow?
+ parent_resource.instance_of?(Resource) && @scope[:shallow]
+ end
+ def match(*args)
+ options = args.extract_options!.dup
options[:anchor] = true unless options.key?(:anchor)
if args.length > 1
@@ -724,17 +675,12 @@ def match(*args)
return self
end
- if [:collection, :member, :new].include?(options[:on])
+ on = options.delete(:on)
+ if VALID_ON_OPTIONS.include?(on)
args.push(options)
-
- case options.delete(:on)
- when :collection
- return collection { match(*args) }
- when :member
- return member { match(*args) }
- when :new
- return new { match(*args) }
- end
+ return send(on){ match(*args) }
+ elsif on
+ raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
end
if @scope[:scope_level] == :resource
@@ -743,10 +689,12 @@ def match(*args)
end
path = options.delete(:path)
+ action = args.first
- if args.first.is_a?(Symbol)
- path = path_for_action(args.first, path)
- options = options_for_action(args.first, options)
+ if action.is_a?(Symbol)
+ path = path_for_action(action, path)
+ options[:to] ||= action
+ options[:as] = name_for_action(action, options[:as])
with_exclusive_scope do
return super(path, options)
@@ -784,28 +732,35 @@ def root(options={})
end
protected
+
def parent_resource #:nodoc:
@scope[:scope_level_resource]
end
- private
+ def resource_actions
+ if only = @scope[:options][:only]
+ Array(only).map(&:to_sym)
+ elsif except = @scope[:options][:except]
+ parent_resource.default_actions - Array(except).map(&:to_sym)
+ else
+ parent_resource.default_actions
+ end
+ end
+
def apply_common_behavior_for(method, resources, options, &block)
if resources.length > 1
resources.each { |r| send(method, r, options, &block) }
return true
end
- if path_names = options.delete(:path_names)
- scope(:path_names => path_names) do
+ scope_options = options.slice!(*RESOURCE_OPTIONS)
+ unless scope_options.empty?
+ scope(scope_options) do
send(method, resources.pop, options, &block)
end
return true
end
- scope_options = @scope.slice(*MERGE_FROM_SCOPE_OPTIONS).delete_if{ |k,v| v.blank? }
- options.reverse_merge!(scope_options) unless scope_options.empty?
- options.reverse_merge!(@scope[:options]) unless @scope[:options].blank?
-
if resource_scope?
nested do
send(method, resources.pop, options, &block)
@@ -848,7 +803,15 @@ def with_scope_level(kind, resource = parent_resource)
def resource_scope(resource)
with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
- scope(*parent_resource.resource_scope) do
+ scope(parent_resource.resource_scope) do
+ yield
+ end
+ end
+ end
+
+ def new_scope
+ with_scope_level(:new) do
+ scope(parent_resource.new_scope(action_path(:new))) do
yield
end
end
@@ -856,7 +819,7 @@ def resource_scope(resource)
def collection_scope
with_scope_level(:collection) do
- scope(*parent_resource.collection_scope) do
+ scope(parent_resource.collection_scope) do
yield
end
end
@@ -864,54 +827,51 @@ def collection_scope
def member_scope
with_scope_level(:member) do
- scope(*parent_resource.member_scope) do
+ scope(parent_resource.member_scope) do
yield
end
end
end
+ def nested_options
+ {}.tap do |options|
+ options[:as] = parent_resource.member_name
+ options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint?
+ end
+ end
+
+ def id_constraint?
+ @scope[:id].is_a?(Regexp) || (@scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp))
+ end
+
+ def id_constraint
+ @scope[:id] || @scope[:constraints][:id]
+ end
+
+ def canonical_action?(action, flag)
+ flag && CANONICAL_ACTIONS.include?(action)
+ end
+
+ def shallow_scoping?
+ shallow? && @scope[:scope_level] == :member
+ end
+
def path_for_action(action, path)
- case action
- when :index, :create
- "#{@scope[:path]}(.:format)"
- when :show, :update, :destroy
- if parent_resource.shallow?
- "#{@scope[:shallow_path]}/#{parent_resource.path}/:id(.:format)"
- else
- "#{@scope[:path]}(.:format)"
- end
- when :new
- "#{@scope[:path]}/#{action_path(:new)}(.:format)"
- when :edit
- if parent_resource.shallow?
- "#{@scope[:shallow_path]}/#{parent_resource.path}/:id/#{action_path(:edit)}(.:format)"
- else
- "#{@scope[:path]}/#{action_path(:edit)}(.:format)"
- end
+ prefix = shallow_scoping? ?
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
+
+ if canonical_action?(action, path.blank?)
+ "#{prefix}(.:format)"
else
- case @scope[:scope_level]
- when :collection, :new
- "#{@scope[:path]}/#{action_path(action, path)}(.:format)"
- else
- if parent_resource.shallow?
- "#{@scope[:shallow_path]}/#{parent_resource.path}/:id/#{action_path(action, path)}(.:format)"
- else
- "#{@scope[:path]}/#{action_path(action, path)}(.:format)"
- end
- end
+ "#{prefix}/#{action_path(action, path)}(.:format)"
end
end
def path_for_custom_action
- case @scope[:scope_level]
- when :collection, :new
- @scope[:path]
+ if shallow_scoping?
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id"
else
- if parent_resource.shallow?
- "#{@scope[:shallow_path]}/#{parent_resource.path}/:id"
- else
- @scope[:path]
- end
+ @scope[:path]
end
end
@@ -919,50 +879,40 @@ def action_path(name, path = nil)
path || @scope[:path_names][name.to_sym] || name.to_s
end
- def options_for_action(action, options)
- options.reverse_merge(
- :to => action,
- :as => name_for_action(action)
- )
+ def prefix_name_for_action(action, as)
+ if as.present?
+ "#{as}_"
+ elsif as
+ ""
+ elsif !canonical_action?(action, @scope[:scope_level])
+ "#{action}_"
+ end
end
- def name_for_action(action)
- name_prefix = @scope[:as].blank? ? "" : "#{@scope[:as]}_"
- shallow_prefix = @scope[:shallow_prefix].blank? ? "" : "#{@scope[:shallow_prefix]}_"
+ def name_for_action(action, as=nil)
+ prefix = prefix_name_for_action(action, as)
+ name_prefix = @scope[:as]
- case action
- when :index, :create
- "#{name_prefix}#{parent_resource.collection_name}"
- when :show, :update, :destroy
- if parent_resource.shallow?
- "#{shallow_prefix}#{parent_resource.member_name}"
- else
- "#{name_prefix}#{parent_resource.member_name}"
- end
- when :edit
- if parent_resource.shallow?
- "edit_#{shallow_prefix}#{parent_resource.member_name}"
- else
- "edit_#{name_prefix}#{parent_resource.member_name}"
- end
+ if parent_resource
+ collection_name = parent_resource.collection_name
+ member_name = parent_resource.member_name
+ name_prefix = "#{name_prefix}_" if name_prefix.present?
+ end
+
+ case @scope[:scope_level]
+ when :collection
+ "#{prefix}#{name_prefix}#{collection_name}"
when :new
- "new_#{name_prefix}#{parent_resource.member_name}"
+ "#{prefix}new_#{name_prefix}#{member_name}"
else
- case @scope[:scope_level]
- when :collection
- "#{action}_#{name_prefix}#{parent_resource.collection_name}"
- when :new
- "#{action}_new_#{name_prefix}#{parent_resource.member_name}"
+ if shallow_scoping?
+ shallow_prefix = "#{@scope[:shallow_prefix]}_" if @scope[:shallow_prefix].present?
+ "#{prefix}#{shallow_prefix}#{member_name}"
else
- if parent_resource.shallow?
- "#{action}_#{shallow_prefix}#{parent_resource.member_name}"
- else
- "#{action}_#{name_prefix}#{parent_resource.member_name}"
- end
+ "#{prefix}#{name_prefix}#{member_name}"
end
end
end
-
end
include Base
View
78 actionpack/lib/action_dispatch/routing/route_set.rb
@@ -14,6 +14,7 @@ class Dispatcher #:nodoc:
def initialize(options={})
@defaults = options[:defaults]
@glob_param = options.delete(:glob)
+ @module = options.delete(:module)
@controllers = {}
end
@@ -26,7 +27,7 @@ def call(env)
return [404, {'X-Cascade' => 'pass'}, []]
end
- controller.action(params[:action]).call(env)
+ dispatch(controller, params[:action], env)
end
def prepare_params!(params)
@@ -34,29 +35,44 @@ def prepare_params!(params)
split_glob_param!(params) if @glob_param
end
- def controller(params, raise_error=true)
+ # If this is a default_controller (i.e. a controller specified by the user)
+ # we should raise an error in case it's not found, because it usually means
+ # an user error. However, if the controller was retrieved through a dynamic
+ # segment, as in :controller(/:action), we should simply return nil and
+ # delegate the control back to Rack cascade. Besides, if this is not a default
+ # controller, it means we should respect the @scope[:module] parameter.
+ def controller(params, default_controller=true)
if params && params.key?(:controller)
- controller_param = params[:controller]
- unless controller = @controllers[controller_param]
- controller_name = "#{controller_param.camelize}Controller"
- controller = @controllers[controller_param] =
- ActiveSupport::Dependencies.ref(controller_name)
- end
-
- controller.get
+ controller_param = @module && !default_controller ?
+ "#{@module}/#{params[:controller]}" : params[:controller]
+ controller_reference(controller_param)
end
rescue NameError => e
- raise ActionController::RoutingError, e.message, e.backtrace if raise_error
+ raise ActionController::RoutingError, e.message, e.backtrace if default_controller
end
- private
- def merge_default_action!(params)
- params[:action] ||= 'index'
- end
+ private
- def split_glob_param!(params)
- params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) }
+ def controller_reference(controller_param)
+ unless controller = @controllers[controller_param]
+ controller_name = "#{controller_param.camelize}Controller"
+ controller = @controllers[controller_param] =
+ ActiveSupport::Dependencies.ref(controller_name)
end
+ controller.get
+ end
+
+ def dispatch(controller, action, env)
+ controller.action(action).call(env)
+ end
+
+ def merge_default_action!(params)
+ params[:action] ||= 'index'
+ end
+
+ def split_glob_param!(params)
+ params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) }
+ end
end
# A NamedRouteCollection instance is a collection of named routes, and also
@@ -268,10 +284,10 @@ class << self
# Yes plz - JP
included do
routes.install_helpers(self)
- singleton_class.send(:define_method, :_router) { routes }
+ singleton_class.send(:define_method, :_routes) { routes }
end
- define_method(:_router) { routes }
+ define_method(:_routes) { routes }
end
helpers
@@ -302,7 +318,6 @@ def initialize(options, recall, set, extras = false)
@extras = extras
normalize_options!
- normalize_recall!
normalize_controller_action_id!
use_relative_controller!
controller.sub!(%r{^/}, '') if controller
@@ -319,7 +334,11 @@ def current_controller
def use_recall_for(key)
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
- @options[key] = @recall.delete(key)
+ if named_route_exists?
+ @options[key] = @recall.delete(key) if segment_keys.include?(key)
+ else
+ @options[key] = @recall.delete(key)
+ end
end
end
@@ -343,15 +362,6 @@ def normalize_options!
end
end
- def normalize_recall!
- # If the target route is not a standard route then remove controller and action
- # from the options otherwise they will appear in the url parameters
- if block_or_proc_route_target?
- recall.delete(:controller) unless segment_keys.include?(:controller)
- recall.delete(:action) unless segment_keys.include?(:action)
- end
- end
-
# This pulls :controller, :action, and :id out of the recall.
# The recall key is only used if there is no key in the options
# or if the key in the options is identical. If any of
@@ -424,12 +434,8 @@ def named_route_exists?
named_route && set.named_routes[named_route]
end
- def block_or_proc_route_target?
- named_route_exists? && !set.named_routes[named_route].app.is_a?(Dispatcher)
- end
-
def segment_keys
- named_route_exists? ? set.named_routes[named_route].segment_keys : []
+ set.named_routes[named_route].segment_keys
end
end
@@ -474,7 +480,7 @@ def url_for(options)
path_options = yield(path_options) if block_given?
path = generate(path_options, path_segments || {})
- # ROUTES TODO: This can be called directly, so script_name should probably be set in the router
+ # ROUTES TODO: This can be called directly, so script_name should probably be set in the routes
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "##{Rack::Mount::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor]
View
2  actionpack/lib/action_dispatch/routing/url_for.rb
@@ -128,7 +128,7 @@ def url_for(options = nil)
when String
options
when nil, Hash
- _router.url_for(url_options.merge((options || {}).symbolize_keys))
+ _routes.url_for(url_options.merge((options || {}).symbolize_keys))
else
polymorphic_url(options)
end
View
2  actionpack/lib/action_view/base.rb
@@ -173,7 +173,7 @@ module Subclasses
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
class_attribute :helpers
- class_attribute :_router
+ class_attribute :_routes
class << self
delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
View
4 actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -529,6 +529,8 @@ def range_field_tag(name, value = nil, options = {})
def html_options_for_form(url_for_options, options, *parameters_for_url)
returning options.stringify_keys do |html_options|
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
+ # The following URL is unescaped, this is just a hash of options, and it is the
+ # responsability of the caller to escape all the values.
html_options["action"] = url_for(url_for_options, *parameters_for_url)
html_options["accept-charset"] = "UTF-8"
html_options["data-remote"] = true if html_options.delete("remote")
@@ -537,7 +539,7 @@ def html_options_for_form(url_for_options, options, *parameters_for_url)
def extra_tags_for_form(html_options)
snowman_tag = tag(:input, :type => "hidden",
- :name => "_snowman_", :value => "&#9731;")
+ :name => "_snowman", :value => "&#9731;".html_safe)
method = html_options.delete("method").to_s
View
2  actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -131,7 +131,7 @@ def remote_function(options)
url_options = options[:url]
url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
- function << "'#{escape_javascript(url_for(url_options))}'"
+ function << "'#{html_escape(escape_javascript(url_for(url_options)))}'"
function << ", #{javascript_options})"
function = "#{options[:before]}; #{function}" if options[:before]
View
2  actionpack/lib/action_view/helpers/tag_helper.rb
@@ -122,7 +122,7 @@ def tag_options(options, escape = true)
attrs << %(#{key}="#{key}") if value
elsif !value.nil?
final_value = value.is_a?(Array) ? value.join(" ") : value
- final_value = escape_once(final_value) if escape
+ final_value = html_escape(final_value) if escape
attrs << %(#{key}="#{final_value}")
end
end
View
52 actionpack/lib/action_view/helpers/url_helper.rb
@@ -12,7 +12,7 @@ module Helpers #:nodoc:
# and controllers.
module UrlHelper
# This helper may be included in any class that includes the
- # URL helpers of a router (router.url_helpers). Some methods
+ # URL helpers of a routes (routes.url_helpers). Some methods
# provided here will only work in the4 context of a request
# (link_to_unless_current, for instance), which must be provided
# as a method called #request on the context.
@@ -38,9 +38,6 @@ def url_options
# <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action"
# instead of the fully qualified URL like "http://example.com/controller/action".
#
- # When called from a view, +url_for+ returns an HTML escaped url. If you
- # need an unescaped url, pass <tt>:escape => false</tt> in the +options+.
- #
# ==== Options
# * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path.
# * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified).
@@ -50,7 +47,6 @@ def url_options
# * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
# * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
# * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
- # * <tt>:escape</tt> - Determines whether the returned URL will be HTML escaped or not (<tt>true</tt> by default).
#
# ==== Relying on named routes
#
@@ -72,10 +68,7 @@ def url_options
# <%= url_for(:action => 'play', :anchor => 'player') %>
# # => /messages/play/#player
#
- # <%= url_for(:action => 'checkout', :anchor => 'tax&ship') %>
- # # => /testing/jump/#tax&amp;ship
- #
- # <%= url_for(:action => 'checkout', :anchor => 'tax&ship', :escape => false) %>
+ # <%= url_for(:action => 'jump', :anchor => 'tax&ship') %>
# # => /testing/jump/#tax&ship
#
# <%= url_for(Workshop.new) %>
@@ -100,21 +93,17 @@ def url_for(options = {})
options ||= {}
url = case options
when String
- escape = true
options
when Hash
options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
- escape = options.key?(:escape) ? options.delete(:escape) : true
super
when :back
- escape = false
controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
else
- escape = false
polymorphic_path(options)
end
- escape ? escape_once(url).html_safe : url
+ url
end
# Creates a link tag of the given +name+ using a URL created by the set
@@ -254,8 +243,8 @@ def link_to(*args, &block)
tag_options = nil
end
- href_attr = "href=\"#{url}\"" unless href
- "<a #{href_attr}#{tag_options}>#{ERB::Util.h(name || url)}</a>".html_safe
+ href_attr = "href=\"#{html_escape(url)}\"" unless href
+ "<a #{href_attr}#{tag_options}>#{html_escape(name || url)}</a>".html_safe
end
end
@@ -339,7 +328,7 @@ def button_to(name, options = {}, html_options = {})
html_options.merge!("type" => "submit", "value" => name)
- ("<form method=\"#{form_method}\" action=\"#{escape_once url}\" #{"data-remote=\"true\"" if remote} class=\"button_to\"><div>" +
+ ("<form method=\"#{form_method}\" action=\"#{html_escape(url)}\" #{"data-remote=\"true\"" if remote} class=\"button_to\"><div>" +
method_tag + tag("input", html_options) + request_token_tag + "</div></form>").html_safe
end
@@ -485,24 +474,27 @@ def link_to_if(condition, name, options = {}, html_options = {}, &block)
# :subject => "This is an example email"
# # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
def mail_to(email_address, name = nil, html_options = {})
+ email_address = html_escape(email_address)
+
html_options = html_options.stringify_keys
encode = html_options.delete("encode").to_s
cc, bcc, subject, body = html_options.delete("cc"), html_options.delete("bcc"), html_options.delete("subject"), html_options.delete("body")
- string = ''
- extras = ''
- extras << "cc=#{Rack::Utils.escape(cc).gsub("+", "%20")}&" unless cc.nil?
- extras << "bcc=#{Rack::Utils.escape(bcc).gsub("+", "%20")}&" unless bcc.nil?
- extras << "body=#{Rack::Utils.escape(body).gsub("+", "%20")}&" unless body.nil?
- extras << "subject=#{Rack::Utils.escape(subject).gsub("+", "%20")}&" unless subject.nil?
- extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty?
-
- email_address_obfuscated = html_escape(email_address)
+ extras = []
+ extras << "cc=#{Rack::Utils.escape(cc).gsub("+", "%20")}" unless cc.nil?
+ extras << "bcc=#{Rack::Utils.escape(bcc).gsub("+", "%20")}" unless bcc.nil?
+ extras << "body=#{Rack::Utils.escape(body).gsub("+", "%20")}" unless body.nil?
+ extras << "subject=#{Rack::Utils.escape(subject).gsub("+", "%20")}" unless subject.nil?
+ extras = extras.empty? ? '' : '?' + html_escape(extras.join('&'))
+
+ email_address_obfuscated = email_address.dup
email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at")
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
+ string = ''
+
if encode == "javascript"
- "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
+ "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))}');".each_byte do |c|
string << sprintf("%%%x", c)
end
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe
@@ -519,9 +511,9 @@ def mail_to(email_address, name = nil, html_options = {})
char = c.chr
string << (char =~ /\w/ ? sprintf("%%%x", c) : char)
end
- content_tag "a", name || email_address_encoded.html_safe, html_options.merge({ "href" => "#{string}#{extras}" })
+ content_tag "a", name || email_address_encoded.html_safe, html_options.merge("href" => "#{string}#{extras}".html_safe)
else
- content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" })
+ content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)
end
end
@@ -574,7 +566,7 @@ def current_page?(options)
"in a #request method"
end
- url_string = CGI.unescapeHTML(url_for(options))
+ url_string = url_for(options)
# We ignore any extra parameters in the request_uri if the
# submitted url doesn't have any either. This lets the function
View
2  actionpack/lib/action_view/template.rb
@@ -275,7 +275,7 @@ def #{method_name}(local_assigns)
end
def build_method_name(locals)
- @method_names[locals.keys.hash] ||= "#{identifier_method_name}__#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
+ @method_names[locals.keys.hash] ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
end
def identifier_method_name
View
10 actionpack/lib/action_view/test_case.rb
@@ -151,7 +151,7 @@ def view
@view ||= begin
view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller)
view.singleton_class.send :include, _helpers
- view.singleton_class.send :include, @controller._router.url_helpers
+ view.singleton_class.send :include, @controller._routes.url_helpers
view.singleton_class.send :delegate, :alert, :notice, :to => "request.flash"
view.extend(Locals)
view.locals = self.locals
@@ -192,13 +192,13 @@ def _assigns
end
end
- def _router
- @controller._router if @controller.respond_to?(:_router)
+ def _routes
+ @controller._routes if @controller.respond_to?(:_routes)
end
def method_missing(selector, *args)
- if @controller.respond_to?(:_router) &&
- @controller._router.named_routes.helpers.include?(selector)
+ if @controller.respond_to?(:_routes) &&
+ @controller._routes.named_routes.helpers.include?(selector)
@controller.__send__(selector, *args)
else
super
View
25 actionpack/test/abstract_unit.rb
@@ -41,7 +41,7 @@
module Rails
end
-# Monkey patch the old router initialization to be silenced.
+# Monkey patch the old routes initialization to be silenced.
class ActionDispatch::Routing::DeprecatedMapper
def initialize_with_silencer(*args)
ActiveSupport::Deprecation.silence { initialize_without_silencer(*args) }
@@ -182,13 +182,16 @@ def self.build_app(routes = nil)
self.app = build_app
- class StubDispatcher
- def self.new(*args)
- lambda { |env|
- params = env['action_dispatch.request.path_parameters']
- controller, action = params[:controller], params[:action]
- [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]]
- }
+ # Stub Rails dispatcher so it does not get controller references and
+ # simply return the controller#action as Rack::Body.
+ class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher
+ protected
+ def controller_reference(controller_param)
+ controller_param
+ end
+
+ def dispatch(controller, action, env)
+ [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]]
end
end
@@ -275,9 +278,9 @@ def assert_header(name, value)
class ActionController::Base
def self.test_routes(&block)
- router = ActionDispatch::Routing::RouteSet.new
- router.draw(&block)
- include router.url_helpers
+ routes = ActionDispatch::Routing::RouteSet.new
+ routes.draw(&block)
+ include routes.url_helpers
end
end
View
20 actionpack/test/controller/mime_responds_test.rb
@@ -36,6 +36,15 @@ def html_or_xml
type.all { render :text => "Nothing" }
end
end
+
+ def json_xml_or_html
+ respond_to do |type|
+ type.json { render :text => 'JSON' }
+ type.xml { render :xml => 'XML' }
+ type.html { render :text => 'HTML' }
+ end
+ end
+
def forced_xml
request.format = :xml
@@ -364,6 +373,17 @@ def test_handle_any_any_xml
get :handle_any_any
assert_equal 'Whatever you ask for, I got it', @response.body
end
+
+ def test_browser_check_with_any_any
+ @request.accept = "application/json, application/xml"
+ get :json_xml_or_html
+ assert_equal 'JSON', @response.body
+
+ @request.accept = "application/json, application/xml, */*"
+ get :json_xml_or_html
+ assert_equal 'HTML', @response.body
+ end
+
def test_rjs_type_skips_layout
@request.accept = "text/javascript"
View
2  actionpack/test/controller/url_for_test.rb
@@ -120,7 +120,7 @@ def test_trailing_slash_with_params
def test_relative_url_root_is_respected
# ROUTES TODO: Tests should not have to pass :relative_url_root directly. This
- # should probably come from the router.
+ # should probably come from routes.
add_host!
assert_equal('https://www.basecamphq.com/subdir/c/a/i',
View
140 actionpack/test/dispatch/routing_test.rb
@@ -35,6 +35,13 @@ def self.matches?(request)
end
end
+ scope "bookmark", :controller => "bookmarks", :as => :bookmark do
+ get :new, :path => "build"
+ post :create, :path => "create", :as => ""
+ put :update
+ get "remove", :action => :destroy, :as => :remove
+ end
+
match 'account/logout' => redirect("/logout"), :as => :logout_redirect
match 'account/login', :to => redirect("/login")
@@ -291,8 +298,8 @@ def self.matches?(request)
resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ }
+ resource :token, :module => :api
scope :module => :api do
- resource :token
resources :errors, :shallow => true do
resources :notices
end
@@ -303,11 +310,6 @@ def self.matches?(request)
match '/' => 'mes#index'
end
- namespace :private do
- root :to => redirect('/private/index')
- match "index", :to => 'private#index'
- end
-
get "(/:username)/followers" => "followers#index"
get "/groups(/user/:username)" => "groups#index"
get "(/user/:username)/photos" => "photos#index"
@@ -341,6 +343,28 @@ def self.matches?(request)
end
end
+ namespace :private do
+ root :to => redirect('/private/index')
+ match "index", :to => 'private#index'
+ match ":controller(/:action(/:id))"
+ end
+
+ scope :only => :index do
+ resources :clubs do
+ resources :players, :only => [:show]
+ resource :chairman, :only => [:show]
+ end
+ end
+
+ scope :except => [:new, :create, :edit, :update] do
+ resources :sectors do
+ resources :companies, :except => :destroy do
+ resources :divisions, :only => :index
+ end
+ resource :leader, :except => :destroy
+ end
+ end
+
match '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
end
end
@@ -463,6 +487,19 @@ def test_namespace_redirect
end
end
+ def test_namespace_with_controller_segment
+ with_test_routes do
+ get '/private/foo'
+ assert_equal 'private/foo#index', @response.body
+
+ get '/private/foo/bar'
+ assert_equal 'private/foo#bar', @response.body
+
+ get '/private/foo/bar/1'
+ assert_equal 'private/foo#bar', @response.body
+ end
+ end
+
def test_session_singleton_resource
with_test_routes do
get '/session'
@@ -545,6 +582,26 @@ def test_openid
end
end
+ def test_bookmarks
+ with_test_routes do
+ get '/bookmark/build'
+ assert_equal 'bookmarks#new', @response.body
+ assert_equal '/bookmark/build', new_bookmark_path
+
+ post '/bookmark/create'
+ assert_equal 'bookmarks#create', @response.body
+ assert_equal '/bookmark/create', bookmark_path
+
+ put '/bookmark'
+ assert_equal 'bookmarks#update', @response.body
+ assert_equal '/bookmark', update_bookmark_path
+
+ get '/bookmark/remove'
+ assert_equal 'bookmarks#destroy', @response.body
+ assert_equal '/bookmark/remove', bookmark_remove_path
+ end
+ end
+
def test_admin
with_test_routes do
get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
@@ -1213,7 +1270,7 @@ def test_resource_constraints
assert_equal 'pass', @response.headers['X-Cascade']
get '/products/0001/images'
assert_equal 'images#index', @response.body
- get '/products/0001/images/1'
+ get '/products/0001/images/0001'
assert_equal 'images#show', @response.body
get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'}
@@ -1601,6 +1658,75 @@ def test_constraints_are_merged_from_scope
end
end
+ def test_only_option_should_be_overwritten
+ with_test_routes do
+ get '/clubs'
+ assert_equal 'clubs#index', @response.body
+ assert_equal '/clubs', clubs_path
+
+ get '/clubs/1'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { club_path(:id => '1') }
+
+ get '/clubs/1/players'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { club_players_path(:club_id => '1') }
+
+ get '/clubs/1/players/2'
+ assert_equal 'players#show', @response.body
+ assert_equal '/clubs/1/players/2', club_player_path(:club_id => '1', :id => '2')
+
+ get '/clubs/1/chairman/new'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { new_club_chairman_path(:club_id => '1') }
+
+ get '/clubs/1/chairman'
+ assert_equal 'chairmen#show', @response.body
+ assert_equal '/clubs/1/chairman', club_chairman_path(:club_id => '1')
+ end
+ end
+
+ def test_except_option_should_be_overwritten
+ with_test_routes do
+ get '/sectors'
+ assert_equal 'sectors#index', @response.body
+ assert_equal '/sectors', sectors_path
+
+ get '/sectors/1/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_sector_path(:id => '1') }
+
+ delete '/sectors/1'
+ assert_equal 'sectors#destroy', @response.body
+
+ get '/sectors/1/companies/new'
+ assert_equal 'companies#new', @response.body
+ assert_equal '/sectors/1/companies/new', new_sector_company_path(:sector_id => '1')
+
+ delete '/sectors/1/companies/1'
+ assert_equal 'Not Found', @response.body
+
+ get '/sectors/1/leader/new'
+ assert_equal 'leaders#new', @response.body
+ assert_equal '/sectors/1/leader/new', new_sector_leader_path(:sector_id => '1')
+
+ delete '/sectors/1/leader'
+ assert_equal 'Not Found', @response.body
+ end
+ end
+
+ def test_only_option_should_overwrite_except_option
+ with_test_routes do
+ get '/sectors/1/companies/2/divisions'
+ assert_equal 'divisions#index', @response.body
+ assert_equal '/sectors/1/companies/2/divisions', sector_company_divisions_path(:sector_id => '1', :company_id => '2')
+
+ get '/sectors/1/companies/2/divisions/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
+ end
+ end
+
private
def with_test_routes
yield
View
22 actionpack/test/dispatch/session/cookie_store_test.rb
@@ -30,6 +30,11 @@ def get_session_id
render :text => "id: #{request.session_options[:id]}"
end
+ def call_session_clear
+ session.clear
+ head :ok
+ end
+
def call_reset_session
reset_session
head :ok
@@ -175,6 +180,23 @@ def test_getting_from_nonexistent_session
end
end
+ def test_setting_session_value_after_session_clear
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ session_payload = response.body
+ assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
+ headers['Set-Cookie']
+
+ get '/call_session_clear'
+ assert_response :success
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ end
+ end
+
def test_persistent_session_id
with_test_route_set do
cookies[SessionKey] = SignedBar
View
16 actionpack/test/dispatch/url_generation_test.rb
@@ -2,24 +2,24 @@
module TestUrlGeneration
class WithMountPoint < ActionDispatch::IntegrationTest
- Router = ActionDispatch::Routing::RouteSet.new
- Router.draw { match "/foo", :to => "my_route_generating#index", :as => :foo }
+ Routes = ActionDispatch::Routing::RouteSet.new
+ Routes.draw { match "/foo", :to => "my_route_generating#index", :as => :foo }
class ::MyRouteGeneratingController < ActionController::Base
- include Router.url_helpers
+ include Routes.url_helpers
def index
render :text => foo_path
end
end
- include Router.url_helpers
+ include Routes.url_helpers
- def _router
- Router
+ def _routes
+ Routes
end
def app
- Router
+ Routes
end
test "generating URLS normally" do
@@ -30,7 +30,7 @@ def app
assert_equal "/bar/foo", foo_path(:script_name => "/bar")
end
- test "the request's SCRIPT_NAME takes precedence over the router's" do
+ test "the request's SCRIPT_NAME takes precedence over the routes'" do
get "/foo", {}, 'SCRIPT_NAME' => "/new"
assert_equal "/new/foo", response.body
end
View
2  actionpack/test/template/form_helper_test.rb
@@ -1484,7 +1484,7 @@ def test_form_for_with_labelled_builder
def snowman(method = nil)
txt = %{<div style="margin:0;padding:0;display:inline">}
- txt << %{<input name="_snowman_" type="hidden" value="&#9731;" />}
+ txt << %{<input name="_snowman" type="hidden" value="&#9731;" />}
txt << %{<input name="_method" type="hidden" value="#{method}" />} if method
txt << %{</div>}
end
View
2  actionpack/test/template/form_tag_helper_test.rb
@@ -12,7 +12,7 @@ def snowman(options = {})
method = options[:method]
txt = %{<div style="margin:0;padding:0;display:inline">}
- txt << %{<input name="_snowman_" type="hidden" value="&#9731;" />}
+ txt << %{<input name="_snowman" type="hidden" value="&#9731;" />}
txt << %{<input name="_method" type="hidden" value="#{method}" />} if method
txt << %{</div>}
end
View
4 actionpack/test/template/tag_helper_test.rb
@@ -95,9 +95,9 @@ def test_escape_once
assert_equal '1 &lt; 2 &amp; 3', escape_once('1 < 2 &amp; 3')
end
- def test_double_escaping_attributes
+ def test_tag_honors_html_safe_for_param_values
['1&amp;2', '1 &lt; 2', '&#8220;test&#8220;'].each do |escaped|
- assert_equal %(<a href="#{escaped}" />), tag('a', :href => escaped)
+ assert_equal %(<a href="#{escaped}" />), tag('a', :href => escaped.html_safe)
end
end
View
56 actionpack/test/template/url_helper_test.rb
@@ -44,20 +44,8 @@ def hash_for(opts = {})
end
alias url_hash hash_for
- def test_url_for_escapes_urls
- assert_equal "/?a=b&amp;c=d", url_for(abcd)
- assert_equal "/?a=b&amp;c=d", url_for(abcd(:escape => true))
- assert_equal "/?a=b&c=d", url_for(abcd(:escape => false))
- end
-
- def test_url_for_escaping_is_safety_aware
- assert url_for(abcd(:escape => true)).html_safe?, "escaped urls should be html_safe?"
- assert !url_for(abcd(:escape => false)).html_safe?, "non-escaped urls should not be html_safe?"
- end
-
- def test_url_for_escapes_url_once
- assert_equal "/?a=b&amp;c=d", url_for("/?a=b&amp;c=d")
- assert_equal "/?a=b&amp;c=d", url_for(abcd)
+ def test_url_for_does_not_escape_urls
+ assert_equal "/?a=b&c=d", url_for(abcd)
end
def test_url_for_with_back
@@ -81,8 +69,8 @@ def test_button_to_with_query
assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&amp;q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
end
- def test_button_to_with_escaped_query
- assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&amp;q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&amp;q2=v2")
+ def test_button_to_with_html_safe_URL
+ assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&amp;q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&amp;q2=v2".html_safe)
end
def test_button_to_with_query_and_no_name
@@ -151,13 +139,12 @@ def test_link_tag_with_host_option
def test_link_tag_with_query
expected = %{<a href="http://www.example.com?q1=v1&amp;q2=v2">Hello</a>}
- assert_dom_equal expected, link_to("Hello", "http://www.example.com?q1=v1&amp;q2=v2")
+ assert_dom_equal expected, link_to("Hello", "http://www.example.com?q1=v1&q2=v2")
end
def test_link_tag_with_query_and_no_name
- link = link_to(nil, "http://www.example.com?q1=v1&amp;q2=v2")
expected = %{<a href="http://www.example.com?q1=v1&amp;q2=v2">http://www.example.com?q1=v1&amp;q2=v2</a>}
- assert_dom_equal expected, link
+ assert_dom_equal expected, link_to(nil, "http://www.example.com?q1=v1&q2=v2")
end
def test_link_tag_with_back
@@ -312,7 +299,7 @@ def test_current_page_with_params_that_match
@request = request_for_url("/?order=desc&page=1")
assert current_page?(hash_for(:order => "desc", :page => "1"))
- assert current_page?("http://www.example.com/?order=desc&amp;page=1")
+ assert current_page?("http://www.example.com/?order=desc&page=1")
end
def test_link_unless_current
@@ -423,6 +410,14 @@ def sort_query_string_params(uri)
class UrlHelperControllerTest < ActionController::TestCase
class UrlHelperController < ActionController::Base
test_routes do |map|
+ match 'url_helper_controller_test/url_helper/show/:id',
+ :to => 'url_helper_controller_test/url_helper#show',
+ :as => :show
+
+ match 'url_helper_controller_test/url_helper/profile/:name',
+ :to => 'url_helper_controller_test/url_helper#show',
+ :as => :profile
+
match 'url_helper_controller_test/url_helper/show_named_route',
:to => 'url_helper_controller_test/url_helper#show_named_route',
:as => :show_named_route
@@ -435,6 +430,14 @@ class UrlHelperController < ActionController::Base
:as => :normalize_recall_params
end
+ def show
+ if params[:name]
+ render :inline => 'ok'
+ else
+ redirect_to profile_path(params[:id])
+ end
+ end
+
def show_url_for
render :inline => "<%= url_for :controller => 'url_helper_controller_test/url_helper', :action => 'show_url_for' %>"
end
@@ -501,15 +504,24 @@ def default_url_options(options = nil)
assert_equal 'http://testtwo.host/url_helper_controller_test/url_helper/show_named_route', @response.body
end
- def test_recall_params_should_be_normalized_when_using_block_route
+ def test_recall_params_should_be_normalized
get :normalize_recall_params
assert_equal '/url_helper_controller_test/url_helper/normalize_recall_params', @response.body
end
- def test_recall_params_should_not_be_changed_when_using_normal_route
+ def test_recall_params_should_not_be_changed
get :recall_params_not_changed
assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body
end
+
+ def test_recall_params_should_normalize_id
+ get :show, :id => '123'
+ assert_equal 302, @response.status
+ assert_equal 'http://test.host/url_helper_controller_test/url_helper/profile/123', @response.location
+
+ get :show, :name => '123'
+ assert_equal 'ok', @response.body
+ end
end
class TasksController < ActionController::Base
View
1  activemodel/lib/active_model/errors.rb
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
require 'active_support/core_ext/array/wrap'
+require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/reverse_merge'
View
2  activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.0.0 [RC1] (unreleased)*
+* Add scoping and unscoped as the syntax to replace the old with_scope and with_exclusive_scope [José Valim]
+
* New rake task, db:migrate:status, displays status of migrations #4947 [Kevin Skoglund]
* select and order for ActiveRecord now always concatenate nested calls. Use reorder if you want the original order to be overwritten [Santiago Pastorino]
View
18 activerecord/lib/active_record/associations/association_collection.rb
@@ -393,7 +393,12 @@ def load_target
@target = find_target.map do |f|
i = @target.index(f)
t = @target.delete_at(i) if i
- (t && t.changed?) ? t : f
+ if t && t.changed?
+ t
+ else
+ f.mark_for_destruction if t && t.marked_for_destruction?
+ f
+ end
end + @target
else
@target = find_target
@@ -409,6 +414,17 @@ def load_target
end
def method_missing(method, *args)
+ case method.to_s
+ when 'find_or_create'
+ return find(:first, :conditions => args.first) || create(args.first)
+ when /^find_or_create_by_(.*)$/
+ rest = $1
+ return send("find_by_#{rest}", *args) ||
+ method_missing("create_by_#{rest}", *args)
+ when /^create_by_(.*)$/
+ return create Hash[$1.split('_and_').zip(args)]
+ end
+
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
if block_given?
super { |*block_args| yield(*block_args) }
View
6 activerecord/lib/active_record/associations/has_many_association.rb
@@ -110,7 +110,11 @@ def construct_scope
create_scoping = {}
set_belongs_to_association_for(create_scoping)
{
- :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
+ :find => { :conditions => @finder_sql,
+ :readonly => false,
+ :order => @reflection.options[:order],
+ :limit => @reflection.options[:limit],
+ :include => @reflection.options[:include]},
:create => create_scoping
}
end
View
67 activerecord/lib/active_record/base.rb
@@ -398,7 +398,7 @@ def colorize_logging(*args)
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
- delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
+ delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
@@ -720,14 +720,6 @@ def set_sequence_name(value = nil, &block)
end
alias :sequence_name= :set_sequence_name
- # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
- def class_name(table_name = table_name) # :nodoc:
- # remove any prefix and/or suffix from the table name
- class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
- class_name = class_name.singularize if pluralize_table_names
- class_name
- end
-
# Indicates whether the table associated with this class exists
def table_exists?
connection.table_exists?(table_name)
@@ -801,7 +793,7 @@ def column_methods_hash #:nodoc:
def reset_column_information
undefine_attribute_methods
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
- @arel_engine = @unscoped = @arel_table = nil
+ @arel_engine = @relation = @arel_table = nil
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
@@ -904,11 +896,6 @@ def sti_name
store_full_sti_class ? name : name.demodulize
end
- def unscoped
- @unscoped ||= Relation.new(self, arel_table)
- finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
- end
-
def arel_table
@arel_table ||= Arel::Table.new(table_name, :engine => arel_engine)
end
@@ -923,7 +910,38 @@ def arel_engine
end
end
+ # Returns a scope for this class without taking into account the default_scope.
+ #
+ # class Post < ActiveRecord::Base
+ # default_scope :published => true
+ # end
+ #
+ # Post.all # Fires "SELECT * FROM posts WHERE published = true"
+ # Post.unscoped.all # Fires "SELECT * FROM posts"
+ #
+ # This method also accepts a block meaning that all queries inside the block will
+ # not use the default_scope:
+ #
+ # Post.unscoped {
+ # limit(10) # Fires "SELECT * FROM posts LIMIT 10"
+ # }