aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeremy Kemper <jeremy@bitsweat.net>2008-12-29 14:38:54 -0800
committerJeremy Kemper <jeremy@bitsweat.net>2008-12-29 14:38:54 -0800
commit276ec16007b03d0a527fb0b83a7ee0b81e460fa1 (patch)
tree224491aa1948d613a551189028746d73400fccce
parent2e053aec9bafa8735d70886f36dea06ea10dc4ce (diff)
parent490c26c8433a6d278bc61118782da360e8889646 (diff)
downloadrails-276ec16007b03d0a527fb0b83a7ee0b81e460fa1.tar.gz
rails-276ec16007b03d0a527fb0b83a7ee0b81e460fa1.tar.bz2
rails-276ec16007b03d0a527fb0b83a7ee0b81e460fa1.zip
Merge branch 'master' of git@github.com:rails/rails
-rw-r--r--actionmailer/lib/action_mailer/base.rb4
-rw-r--r--actionmailer/test/abstract_unit.rb9
-rw-r--r--actionpack/CHANGELOG14
-rw-r--r--actionpack/lib/action_controller.rb9
-rw-r--r--actionpack/lib/action_controller/assertions/routing_assertions.rb2
-rw-r--r--actionpack/lib/action_controller/assertions/selector_assertions.rb1
-rw-r--r--actionpack/lib/action_controller/base.rb72
-rw-r--r--actionpack/lib/action_controller/caching.rb3
-rw-r--r--actionpack/lib/action_controller/caching/sql_cache.rb18
-rw-r--r--actionpack/lib/action_controller/cookies.rb38
-rw-r--r--actionpack/lib/action_controller/dispatcher.rb29
-rw-r--r--actionpack/lib/action_controller/helpers.rb6
-rw-r--r--actionpack/lib/action_controller/http_authentication.rb191
-rw-r--r--actionpack/lib/action_controller/integration.rb95
-rw-r--r--actionpack/lib/action_controller/layout.rb22
-rw-r--r--actionpack/lib/action_controller/middlewares.rb21
-rw-r--r--actionpack/lib/action_controller/mime_responds.rb23
-rw-r--r--actionpack/lib/action_controller/polymorphic_routes.rb18
-rw-r--r--actionpack/lib/action_controller/rack_process.rb73
-rwxr-xr-xactionpack/lib/action_controller/request.rb512
-rw-r--r--actionpack/lib/action_controller/request_parser.rb314
-rw-r--r--actionpack/lib/action_controller/rescue.rb4
-rw-r--r--actionpack/lib/action_controller/response.rb54
-rw-r--r--actionpack/lib/action_controller/routing/route_set.rb67
-rw-r--r--actionpack/lib/action_controller/streaming.rb15
-rw-r--r--actionpack/lib/action_controller/test_case.rb5
-rw-r--r--actionpack/lib/action_controller/test_process.rb80
-rw-r--r--actionpack/lib/action_controller/uploaded_file.rb37
-rw-r--r--actionpack/lib/action_controller/url_encoded_pair_parser.rb95
-rw-r--r--actionpack/lib/action_controller/verb_piggybacking.rb24
-rw-r--r--actionpack/lib/action_view/base.rb62
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb75
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb11
-rw-r--r--actionpack/lib/action_view/locale/en.yml7
-rw-r--r--actionpack/lib/action_view/partials.rb2
-rw-r--r--actionpack/lib/action_view/paths.rb41
-rw-r--r--actionpack/lib/action_view/renderable.rb22
-rw-r--r--actionpack/lib/action_view/template.rb28
-rw-r--r--actionpack/test/controller/assert_select_test.rb8
-rw-r--r--actionpack/test/controller/caching_test.rb3
-rw-r--r--actionpack/test/controller/cookie_test.rb72
-rw-r--r--actionpack/test/controller/dispatcher_test.rb4
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb73
-rw-r--r--actionpack/test/controller/integration_test.rb88
-rw-r--r--actionpack/test/controller/layout_test.rb22
-rw-r--r--actionpack/test/controller/rack_test.rb33
-rw-r--r--actionpack/test/controller/render_test.rb80
-rw-r--r--actionpack/test/controller/request_test.rb80
-rw-r--r--actionpack/test/controller/rescue_test.rb6
-rw-r--r--actionpack/test/controller/routing_test.rb133
-rw-r--r--actionpack/test/controller/send_file_test.rb25
-rw-r--r--actionpack/test/controller/session/cookie_store_test.rb9
-rw-r--r--actionpack/test/template/date_helper_i18n_test.rb11
-rw-r--r--actionpack/test/template/date_helper_test.rb321
-rw-r--r--activerecord/CHANGELOG4
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/association_preload.rb35
-rwxr-xr-xactiverecord/lib/active_record/associations.rb38
-rwxr-xr-xactiverecord/lib/active_record/base.rb90
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb14
-rw-r--r--activerecord/lib/active_record/dirty.rb2
-rw-r--r--activerecord/lib/active_record/dynamic_scope_match.rb25
-rw-r--r--activerecord/lib/active_record/query_cache.rb38
-rw-r--r--activerecord/lib/active_record/timestamp.rb4
-rw-r--r--activerecord/lib/active_record/validations.rb1
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb8
-rw-r--r--activerecord/test/cases/associations/eager_test.rb19
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb13
-rwxr-xr-xactiverecord/test/cases/base_test.rb15
-rw-r--r--activerecord/test/cases/method_scoping_test.rb18
-rw-r--r--activerecord/test/cases/named_scope_test.rb20
-rw-r--r--activerecord/test/fixtures/people.yml11
-rw-r--r--activerecord/test/models/company.rb1
-rw-r--r--activerecord/test/models/person.rb6
-rw-r--r--activerecord/test/schema/schema.rb6
-rw-r--r--activeresource/lib/active_resource/http_mock.rb8
-rw-r--r--activesupport/CHANGELOG4
-rw-r--r--activesupport/lib/active_support/buffered_logger.rb14
-rw-r--r--activesupport/lib/active_support/callbacks.rb41
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb48
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb39
-rw-r--r--activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb68
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/logger.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb48
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb32
-rw-r--r--activesupport/lib/active_support/core_ext/module/synchronization.rb10
-rw-r--r--activesupport/lib/active_support/deprecation.rb13
-rw-r--r--activesupport/lib/active_support/memoizable.rb58
-rw-r--r--activesupport/lib/active_support/multibyte/unicode_database.rb8
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb6
-rw-r--r--activesupport/lib/active_support/vendor.rb4
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/.gitignore3
-rwxr-xr-xactivesupport/lib/active_support/vendor/i18n-0.1.1/MIT-LICENSE20
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/README.textile20
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/Rakefile5
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/i18n.gemspec27
-rwxr-xr-xactivesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb (renamed from activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb)74
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb (renamed from activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb)58
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/exceptions.rb (renamed from activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/exceptions.rb)6
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/all.rb5
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_exceptions_test.rb100
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_test.rb125
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.rb1
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.yml3
-rw-r--r--activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb502
-rw-r--r--activesupport/test/callbacks_test.rb42
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb20
-rw-r--r--activesupport/test/core_ext/module_test.rb27
-rw-r--r--railties/doc/guides/source/command_line.txt145
-rw-r--r--railties/doc/guides/source/finders.txt152
-rw-r--r--railties/doc/guides/source/form_helpers.txt121
-rw-r--r--railties/lib/commands/dbconsole.rb2
-rw-r--r--railties/lib/rails_generator/commands.rb2
-rw-r--r--railties/lib/rails_generator/generators/applications/app/template_runner.rb131
-rw-r--r--railties/lib/tasks/tmp.rake4
-rw-r--r--railties/test/fcgi_dispatcher_test.rb49
-rw-r--r--railties/test/gem_dependency_test.rb24
-rw-r--r--railties/test/generators/rails_template_runner_test.rb190
125 files changed, 4177 insertions, 1651 deletions
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index f1273eb02e..c878a8d205 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -570,7 +570,9 @@ module ActionMailer #:nodoc:
end
def candidate_for_layout?(options)
- !@template.send(:_exempt_from_layout?, default_template_name)
+ !self.view_paths.find_template(default_template_name, default_template_format).exempt_from_layout?
+ rescue ActionView::MissingTemplate
+ return true
end
def template_root
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index ad1eac912b..4900f6fb35 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -10,11 +10,14 @@ require 'action_mailer/test_case'
ActiveSupport::Deprecation.debug = true
# Bogus template processors
-ActionView::Template.register_template_handler :haml, lambda { |template| "Look its HAML!" }
-ActionView::Template.register_template_handler :bak, lambda { |template| "Lame backup" }
+ActionView::Template.register_template_handler :haml, lambda { |template| "Look its HAML!".inspect }
+ActionView::Template.register_template_handler :bak, lambda { |template| "Lame backup".inspect }
$:.unshift "#{File.dirname(__FILE__)}/fixtures/helpers"
-ActionMailer::Base.template_root = "#{File.dirname(__FILE__)}/fixtures"
+
+FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
+ActionMailer::Base.template_root = FIXTURE_LOAD_PATH
+ActionMailer::Base.template_root.load
class MockSMTP
def self.deliveries
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 639cf14cd1..a8abf48441 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,19 @@
*2.3.0 [Edge]*
+* Make ActionController#render(string) work as a shortcut for render :file/:template/:action => string. [#1435] [Pratik Naik] Examples:
+
+ # Instead of render(:action => 'other_action')
+ render('other_action') # argument has no '/'
+ render(:other_action)
+
+ # Instead of render(:template => 'controller/action')
+ render('controller/action') # argument must not begin with a '/', but contain a '/'
+
+ # Instead of render(:file => '/Users/lifo/home.html.erb')
+ render('/Users/lifo/home.html.erb') # argument must begin with a '/'
+
+* Add :prompt option to date/time select helpers. #561 [Sam Oliver]
+
* Fixed that send_file shouldn't set an etag #1578 [Hongli Lai]
* Allow users to opt out of the spoofing checks in Request#remote_ip. Useful for sites whose traffic regularly triggers false positives. [Darren Boyd]
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index ae947820b4..98fb490d64 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -38,7 +38,7 @@ module ActionController
# TODO: Review explicit to see if they will automatically be handled by
# the initilizer if they are really needed.
def self.load_all!
- [Base, CGIHandler, CgiRequest, RackRequest, RackRequest, Http::Headers, UrlRewriter, UrlWriter]
+ [Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
end
autoload :AbstractRequest, 'action_controller/request'
@@ -59,7 +59,11 @@ module ActionController
autoload :MiddlewareStack, 'action_controller/middleware_stack'
autoload :MimeResponds, 'action_controller/mime_responds'
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
- autoload :RackRequest, 'action_controller/rack_process'
+ autoload :Request, 'action_controller/request'
+ autoload :RequestParser, 'action_controller/request_parser'
+ autoload :UrlEncodedPairParser, 'action_controller/url_encoded_pair_parser'
+ autoload :UploadedStringIO, 'action_controller/uploaded_file'
+ autoload :UploadedTempfile, 'action_controller/uploaded_file'
autoload :RecordIdentifier, 'action_controller/record_identifier'
autoload :Response, 'action_controller/response'
autoload :RequestForgeryProtection, 'action_controller/request_forgery_protection'
@@ -74,6 +78,7 @@ module ActionController
autoload :Translation, 'action_controller/translation'
autoload :UrlRewriter, 'action_controller/url_rewriter'
autoload :UrlWriter, 'action_controller/url_rewriter'
+ autoload :VerbPiggybacking, 'action_controller/verb_piggybacking'
autoload :Verification, 'action_controller/verification'
module Assertions
diff --git a/actionpack/lib/action_controller/assertions/routing_assertions.rb b/actionpack/lib/action_controller/assertions/routing_assertions.rb
index 8a837c592c..5101751cea 100644
--- a/actionpack/lib/action_controller/assertions/routing_assertions.rb
+++ b/actionpack/lib/action_controller/assertions/routing_assertions.rb
@@ -134,7 +134,7 @@ module ActionController
path = "/#{path}" unless path.first == '/'
# Assume given controller
- request = ActionController::TestRequest.new({}, {}, nil)
+ request = ActionController::TestRequest.new
request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
request.path = path
diff --git a/actionpack/lib/action_controller/assertions/selector_assertions.rb b/actionpack/lib/action_controller/assertions/selector_assertions.rb
index 248ca85994..7f8fe9ab19 100644
--- a/actionpack/lib/action_controller/assertions/selector_assertions.rb
+++ b/actionpack/lib/action_controller/assertions/selector_assertions.rb
@@ -402,6 +402,7 @@ module ActionController
if rjs_type
if rjs_type == :insert
position = args.shift
+ id = args.shift
insertion = "insert_#{position}".to_sym
raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion]
statement = "(#{RJS_STATEMENTS[insertion]})"
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index eae17d6dd5..da3d1f46ee 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -254,7 +254,7 @@ module ActionController #:nodoc:
cattr_reader :protected_instance_variables
# Controller specific instance variables which will not be accessible inside views.
@@protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
- @action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params
+ @action_name @before_filter_chain_aborted @action_cache_path @_session @_headers @_params
@_flash @_response)
# Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
@@ -382,6 +382,13 @@ module ActionController #:nodoc:
attr_accessor :action_name
class << self
+ def call(env)
+ # HACK: For global rescue to have access to the original request and response
+ request = env["actioncontroller.rescue.request"] ||= Request.new(env)
+ response = env["actioncontroller.rescue.response"] ||= Response.new
+ process(request, response)
+ end
+
# Factory for the standard create, process loop where the controller is discarded after processing.
def process(request, response) #:nodoc:
new.process(request, response)
@@ -502,7 +509,7 @@ module ActionController #:nodoc:
protected :filter_parameters
end
- delegate :exempt_from_layout, :to => 'ActionView::Base'
+ delegate :exempt_from_layout, :to => 'ActionView::Template'
end
public
@@ -859,16 +866,23 @@ module ActionController #:nodoc:
def render(options = nil, extra_options = {}, &block) #:doc:
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
+ validate_render_arguments(options, extra_options, block_given?)
+
if options.nil?
- return render(:file => default_template_name, :layout => true)
- elsif !extra_options.is_a?(Hash)
- raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
- else
- if options == :update
- options = extra_options.merge({ :update => true })
- elsif !options.is_a?(Hash)
- raise RenderError, "You called render with invalid options : #{options.inspect}"
+ options = { :template => default_template.filename, :layout => true }
+ elsif options == :update
+ options = extra_options.merge({ :update => true })
+ elsif options.is_a?(String) || options.is_a?(Symbol)
+ case options.to_s.index('/')
+ when 0
+ extra_options[:file] = options
+ when nil
+ extra_options[:action] = options
+ else
+ extra_options[:template] = options
end
+
+ options = extra_options
end
layout = pick_layout(options)
@@ -898,7 +912,7 @@ module ActionController #:nodoc:
render_for_text(@template.render(options.merge(:layout => layout)), options[:status])
elsif action_name = options[:action]
- render_for_file(default_template_name(action_name.to_s), options[:status], layout)
+ render_for_file(default_template(action_name.to_s), options[:status], layout)
elsif xml = options[:xml]
response.content_type ||= Mime::XML
@@ -933,7 +947,7 @@ module ActionController #:nodoc:
render_for_text(nil, options[:status])
else
- render_for_file(default_template_name, options[:status], layout)
+ render_for_file(default_template, options[:status], layout)
end
end
end
@@ -1164,7 +1178,8 @@ module ActionController #:nodoc:
private
def render_for_file(template_path, status = nil, layout = nil, locals = {}) #:nodoc:
- logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
+ path = template_path.respond_to?(:path_without_format_and_extension) ? template_path.path_without_format_and_extension : template_path
+ logger.info("Rendering #{path}" + (status ? " (#{status})" : '')) if logger
render_for_text @template.render(:file => template_path, :locals => locals, :layout => layout), status
end
@@ -1185,6 +1200,16 @@ module ActionController #:nodoc:
end
end
+ def validate_render_arguments(options, extra_options, has_block)
+ if options && (has_block && options != :update) && !options.is_a?(String) && !options.is_a?(Hash) && !options.is_a?(Symbol)
+ raise RenderError, "You called render with invalid options : #{options.inspect}"
+ end
+
+ if !extra_options.is_a?(Hash)
+ raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
+ end
+ end
+
def initialize_template_class(response)
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
response.template.helpers.send :include, self.class.master_helper_module
@@ -1193,7 +1218,7 @@ module ActionController #:nodoc:
end
def assign_shortcuts(request, response)
- @_request, @_params, @_cookies = request, request.parameters, request.cookies
+ @_request, @_params = request, request.parameters
@_response = response
@_response.session = request.session
@@ -1241,10 +1266,17 @@ module ActionController #:nodoc:
elsif respond_to? :method_missing
method_missing action_name
default_render unless performed?
- elsif template_exists?
- default_render
else
- raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller
+ begin
+ default_render
+ rescue ActionView::MissingTemplate => e
+ # Was the implicit template missing, or was it another template?
+ if e.path == default_template_name
+ raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller
+ else
+ raise e
+ end
+ end
end
end
@@ -1290,10 +1322,8 @@ module ActionController #:nodoc:
@_session.close if @_session && @_session.respond_to?(:close)
end
- def template_exists?(template_name = default_template_name)
- @template.send(:_pick_template, template_name) ? true : false
- rescue ActionView::MissingTemplate
- false
+ def default_template(action_name = self.action_name)
+ self.view_paths.find_template(default_template_name(action_name), default_template_format)
end
def default_template_name(action_name = self.action_name)
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index b4d251eb3c..1d14df0052 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -27,7 +27,6 @@ module ActionController #:nodoc:
autoload :Actions, 'action_controller/caching/actions'
autoload :Fragments, 'action_controller/caching/fragments'
autoload :Pages, 'action_controller/caching/pages'
- autoload :SqlCache, 'action_controller/caching/sql_cache'
autoload :Sweeping, 'action_controller/caching/sweeping'
def self.included(base) #:nodoc:
@@ -41,7 +40,7 @@ module ActionController #:nodoc:
end
include Pages, Actions, Fragments
- include Sweeping, SqlCache if defined?(ActiveRecord)
+ include Sweeping if defined?(ActiveRecord)
@@perform_caching = true
cattr_accessor :perform_caching
diff --git a/actionpack/lib/action_controller/caching/sql_cache.rb b/actionpack/lib/action_controller/caching/sql_cache.rb
deleted file mode 100644
index 139be6100d..0000000000
--- a/actionpack/lib/action_controller/caching/sql_cache.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-module ActionController #:nodoc:
- module Caching
- module SqlCache
- def self.included(base) #:nodoc:
- if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache)
- base.alias_method_chain :perform_action, :caching
- end
- end
-
- protected
- def perform_action_with_caching
- ActiveRecord::Base.cache do
- perform_action_without_caching
- end
- end
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/cookies.rb b/actionpack/lib/action_controller/cookies.rb
index 0e058085ec..840ceb5abd 100644
--- a/actionpack/lib/action_controller/cookies.rb
+++ b/actionpack/lib/action_controller/cookies.rb
@@ -64,45 +64,31 @@ module ActionController #:nodoc:
# Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
def [](name)
- cookie = @cookies[name.to_s]
- if cookie && cookie.respond_to?(:value)
- cookie.size > 1 ? cookie.value : cookie.value[0]
- else
- cookie
- end
+ super(name.to_s)
end
# Sets the cookie named +name+. The second argument may be the very cookie
# value, or a hash of options as documented above.
- def []=(name, options)
+ def []=(key, options)
if options.is_a?(Hash)
- options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
- options["name"] = name.to_s
+ options.symbolize_keys!
else
- options = { "name" => name.to_s, "value" => options }
+ options = { :value => options }
end
- set_cookie(options)
+ options[:path] = "/" unless options.has_key?(:path)
+ super(key.to_s, options[:value])
+ @controller.response.set_cookie(key, options)
end
# Removes the cookie on the client machine by setting the value to an empty string
# and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
- def delete(name, options = {})
- options.stringify_keys!
- set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0)))
+ def delete(key, options = {})
+ options.symbolize_keys!
+ options[:path] = "/" unless options.has_key?(:path)
+ super(key.to_s)
+ @controller.response.delete_cookie(key, options)
end
-
- private
- # Builds a CGI::Cookie object and adds the cookie to the response headers.
- #
- # The path of the cookie defaults to "/" if there's none in +options+, and
- # everything is passed to the CGI::Cookie constructor.
- def set_cookie(options) #:doc:
- options["path"] = "/" unless options["path"]
- cookie = CGI::Cookie.new(options)
- @controller.logger.info "Cookie set: #{cookie}" unless @controller.logger.nil?
- @controller.response.headers["cookie"] << cookie
- end
end
end
diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb
index e1eaaf7cbb..c4e7357b81 100644
--- a/actionpack/lib/action_controller/dispatcher.rb
+++ b/actionpack/lib/action_controller/dispatcher.rb
@@ -44,22 +44,8 @@ module ActionController
cattr_accessor :middleware
self.middleware = MiddlewareStack.new do |middleware|
- middleware.use "ActionController::Lock", :if => lambda {
- !ActionController::Base.allow_concurrency
- }
- middleware.use "ActionController::Failsafe"
-
- ["ActionController::Session::CookieStore",
- "ActionController::Session::MemCacheStore",
- "ActiveRecord::SessionStore"].each do |store|
- middleware.use(store, ActionController::Base.session_options,
- :if => lambda {
- if session_store = ActionController::Base.session_store
- session_store.name == store
- end
- }
- )
- end
+ middlewares = File.join(File.dirname(__FILE__), "middlewares.rb")
+ middleware.instance_eval(File.read(middlewares))
end
include ActiveSupport::Callbacks
@@ -74,11 +60,10 @@ module ActionController
def dispatch
begin
run_callbacks :before_dispatch
- controller = Routing::Routes.recognize(@request)
- controller.process(@request, @response).to_a
+ Routing::Routes.call(@env)
rescue Exception => exception
if controller ||= (::ApplicationController rescue Base)
- controller.process_with_exception(@request, @response, exception).to_a
+ controller.call_with_exception(@env, exception).to_a
else
raise exception
end
@@ -97,8 +82,7 @@ module ActionController
end
def _call(env)
- @request = RackRequest.new(env)
- @response = Response.new
+ @env = env
dispatch
end
@@ -124,8 +108,7 @@ module ActionController
def checkin_connections
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
- # TODO: This callback should have direct access to env
- return if @request.key?("rack.test")
+ return if @env.key?("rack.test")
ActiveRecord::Base.clear_active_connections!
end
end
diff --git a/actionpack/lib/action_controller/helpers.rb b/actionpack/lib/action_controller/helpers.rb
index 402750c57d..ba65032f6a 100644
--- a/actionpack/lib/action_controller/helpers.rb
+++ b/actionpack/lib/action_controller/helpers.rb
@@ -163,9 +163,9 @@ module ActionController #:nodoc:
def helper_method(*methods)
methods.flatten.each do |method|
master_helper_module.module_eval <<-end_eval
- def #{method}(*args, &block)
- controller.send(%(#{method}), *args, &block)
- end
+ def #{method}(*args, &block) # def current_user(*args, &block)
+ controller.send(%(#{method}), *args, &block) # controller.send(%(current_user), *args, &block)
+ end # end
end_eval
end
end
diff --git a/actionpack/lib/action_controller/http_authentication.rb b/actionpack/lib/action_controller/http_authentication.rb
index 2ed810db7d..3cb5829eca 100644
--- a/actionpack/lib/action_controller/http_authentication.rb
+++ b/actionpack/lib/action_controller/http_authentication.rb
@@ -55,7 +55,31 @@ module ActionController
# end
# end
#
- #
+ # Simple Digest example. Note the block must return the user's password so the framework
+ # can appropriately hash it to check the user's credentials. Returning nil will cause authentication to fail.
+ #
+ # class PostsController < ApplicationController
+ # Users = {"dhh" => "secret"}
+ #
+ # before_filter :authenticate, :except => [ :index ]
+ #
+ # def index
+ # render :text => "Everyone can see me!"
+ # end
+ #
+ # def edit
+ # render :text => "I'm only accessible if you know the password"
+ # end
+ #
+ # private
+ # def authenticate
+ # authenticate_or_request_with_http_digest(realm) do |user_name|
+ # Users[user_name]
+ # end
+ # end
+ # end
+ #
+ #
# In your integration tests, you can do something like this:
#
# def test_access_granted_from_xml
@@ -108,7 +132,10 @@ module ActionController
end
def decode_credentials(request)
- ActiveSupport::Base64.decode64(authorization(request).split.last || '')
+ # Properly decode credentials spanning a new-line
+ auth = authorization(request)
+ auth.slice!('Basic ')
+ ActiveSupport::Base64.decode64(auth || '')
end
def encode_credentials(user_name, password)
@@ -120,5 +147,165 @@ module ActionController
controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
end
end
+
+ module Digest
+ extend self
+
+ module ControllerMethods
+ def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
+ begin
+ authenticate_with_http_digest!(realm, &password_procedure)
+ rescue ActionController::HttpAuthentication::Error => e
+ msg = e.message
+ msg = "#{msg} expected '#{e.expected}' was '#{e.was}'" unless e.expected.nil?
+ raise msg if e.fatal?
+ request_http_digest_authentication(realm, msg)
+ end
+ end
+
+ # Authenticate using HTTP Digest, throwing ActionController::HttpAuthentication::Error on failure.
+ # This allows more detailed analysis of authentication failures
+ # to be relayed to the client.
+ def authenticate_with_http_digest!(realm = "Application", &login_procedure)
+ HttpAuthentication::Digest.authenticate(self, realm, &login_procedure)
+ end
+
+ # Authenticate with HTTP Digest, returns true or false
+ def authenticate_with_http_digest(realm = "Application", &login_procedure)
+ HttpAuthentication::Digest.authenticate(self, realm, &login_procedure) rescue false
+ end
+
+ # Render output including the HTTP Digest authentication header
+ def request_http_digest_authentication(realm = "Application", message = nil)
+ HttpAuthentication::Digest.authentication_request(self, realm, message)
+ end
+
+ # Add HTTP Digest authentication header to result headers
+ def http_digest_authentication_header(realm = "Application")
+ HttpAuthentication::Digest.authentication_header(self, realm)
+ end
+ end
+
+ # Raises error unless authentictaion succeeds, returns true otherwise
+ def authenticate(controller, realm, &password_procedure)
+ raise Error.new(false), "No authorization header found" unless authorization(controller.request)
+ validate_digest_response(controller, realm, &password_procedure)
+ true
+ end
+
+ def authorization(request)
+ request.env['HTTP_AUTHORIZATION'] ||
+ request.env['X-HTTP_AUTHORIZATION'] ||
+ request.env['X_HTTP_AUTHORIZATION'] ||
+ request.env['REDIRECT_X_HTTP_AUTHORIZATION']
+ end
+
+ # Raises error unless the request credentials response value matches the expected value.
+ def validate_digest_response(controller, realm, &password_procedure)
+ credentials = decode_credentials(controller.request)
+
+ # Check the nonce, opaque and realm.
+ # Ignore nc, as we have no way to validate the number of times this nonce has been used
+ validate_nonce(controller.request, credentials[:nonce])
+ raise Error.new(false, realm, credentials[:realm]), "Realm doesn't match" unless realm == credentials[:realm]
+ raise Error.new(true, opaque(controller.request), credentials[:opaque]),"Opaque doesn't match" unless opaque(controller.request) == credentials[:opaque]
+
+ password = password_procedure.call(credentials[:username])
+ raise Error.new(false), "No password" if password.nil?
+ expected = expected_response(controller.request.env['REQUEST_METHOD'], controller.request.url, credentials, password)
+ raise Error.new(false, expected, credentials[:response]), "Invalid response" unless expected == credentials[:response]
+ end
+
+ # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
+ def expected_response(http_method, uri, credentials, password)
+ ha1 = ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
+ ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase,uri].join(':'))
+ ::Digest::MD5.hexdigest([ha1,credentials[:nonce], credentials[:nc], credentials[:cnonce],credentials[:qop],ha2].join(':'))
+ end
+
+ def encode_credentials(http_method, credentials, password)
+ credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password)
+ "Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ')
+ end
+
+ def decode_credentials(request)
+ authorization(request).to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair|
+ key, value = pair.split('=', 2)
+ hash[key.strip.to_sym] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')
+ hash
+ end
+ end
+
+ def authentication_header(controller, realm)
+ controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce(controller.request)}", opaque="#{opaque(controller.request)}")
+ end
+
+ def authentication_request(controller, realm, message = "HTTP Digest: Access denied")
+ authentication_header(controller, realm)
+ controller.send! :render, :text => message, :status => :unauthorized
+ end
+
+ # Uses an MD5 digest based on time to generate a value to be used only once.
+ #
+ # A server-specified data string which should be uniquely generated each time a 401 response is made.
+ # It is recommended that this string be base64 or hexadecimal data.
+ # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
+ #
+ # The contents of the nonce are implementation dependent.
+ # The quality of the implementation depends on a good choice.
+ # A nonce might, for example, be constructed as the base 64 encoding of
+ #
+ # => time-stamp H(time-stamp ":" ETag ":" private-key)
+ #
+ # where time-stamp is a server-generated time or other non-repeating value,
+ # ETag is the value of the HTTP ETag header associated with the requested entity,
+ # and private-key is data known only to the server.
+ # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
+ # reject the request if it did not match the nonce from that header or
+ # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
+ # The inclusion of the ETag prevents a replay request for an updated version of the resource.
+ # (Note: including the IP address of the client in the nonce would appear to offer the server the ability
+ # to limit the reuse of the nonce to the same client that originally got it.
+ # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
+ # Also, IP address spoofing is not that hard.)
+ #
+ # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
+ # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
+ # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
+ # of this document.
+ #
+ # The nonce is opaque to the client.
+ def nonce(request, time = Time.now)
+ session_id = request.is_a?(String) ? request : request.session.session_id
+ t = time.to_i
+ hashed = [t, session_id]
+ digest = ::Digest::MD5.hexdigest(hashed.join(":"))
+ Base64.encode64("#{t}:#{digest}").gsub("\n", '')
+ end
+
+ def validate_nonce(request, value)
+ t = Base64.decode64(value).split(":").first.to_i
+ raise Error.new(true), "Stale Nonce" if (t - Time.now.to_i).abs > 10 * 60
+ n = nonce(request, t)
+ raise Error.new(true, value, n), "Bad Nonce" unless n == value
+ end
+
+ # Opaque based on digest of session_id
+ def opaque(request)
+ session_id = request.is_a?(String) ? request : request.session.session_id
+ @opaque ||= Base64.encode64(::Digest::MD5::hexdigest(session_id)).gsub("\n", '')
+ end
+ end
+
+ class Error < RuntimeError
+ attr_accessor :expected, :was
+ def initialize(fatal = false, expected = nil, was = nil)
+ @fatal = fatal
+ @expected = expected
+ @was = was
+ end
+
+ def fatal?; @fatal; end
+ end
end
end
diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb
index d952c3489b..a8e54c2fc7 100644
--- a/actionpack/lib/action_controller/integration.rb
+++ b/actionpack/lib/action_controller/integration.rb
@@ -2,6 +2,17 @@ require 'stringio'
require 'uri'
require 'active_support/test_case'
+# Monkey patch Rack::Lint to support rewind
+module Rack
+ class Lint
+ class InputWrapper
+ def rewind
+ @input.rewind
+ end
+ end
+ end
+end
+
module ActionController
module Integration #:nodoc:
# An integration Session instance represents a set of requests and responses
@@ -57,6 +68,15 @@ module ActionController
# A running counter of the number of requests processed.
attr_accessor :request_count
+ # Nonce value for Digest Authentication, implicitly set on response with WWW-Authentication
+ attr_accessor :nonce
+
+ # Opaque value for Digest Authentication, implicitly set on response with WWW-Authentication
+ attr_accessor :opaque
+
+ # Opaque value for Authentication, implicitly set on response with WWW-Authentication
+ attr_accessor :realm
+
class MultiPartNeededException < Exception
end
@@ -232,6 +252,53 @@ module ActionController
end
alias xhr :xml_http_request
+ def request_with_noauth(http_method, uri, parameters, headers)
+ process_with_auth http_method, uri, parameters, headers
+ end
+
+ # Performs a request with the given http_method and parameters, including HTTP Basic authorization headers.
+ # See get() for more details on paramters and headers.
+ #
+ # You can perform GET, POST, PUT, DELETE, and HEAD requests with #get_with_basic, #post_with_basic,
+ # #put_with_basic, #delete_with_basic, and #head_with_basic.
+ def request_with_basic(http_method, uri, parameters, headers, user_name, password)
+ process_with_auth http_method, uri, parameters, headers.merge(:authorization => ActionController::HttpAuthentication::Basic.encode_credentials(user_name, password))
+ end
+
+ # Performs a request with the given http_method and parameters, including HTTP Digest authorization headers.
+ # See get() for more details on paramters and headers.
+ #
+ # You can perform GET, POST, PUT, DELETE, and HEAD requests with #get_with_digest, #post_with_digest,
+ # #put_with_digest, #delete_with_digest, and #head_with_digest.
+ def request_with_digest(http_method, uri, parameters, headers, user_name, password)
+ # Realm, Nonce, and Opaque taken from previoius 401 response
+
+ credentials = {
+ :username => user_name,
+ :realm => @realm,
+ :nonce => @nonce,
+ :qop => "auth",
+ :nc => "00000001",
+ :cnonce => "0a4f113b",
+ :opaque => @opaque,
+ :uri => uri
+ }
+
+ raise "Digest request without previous 401 response" if @opaque.nil?
+
+ process_with_auth http_method, uri, parameters, headers.merge(:authorization => ActionController::HttpAuthentication::Digest.encode_credentials(http_method, credentials, password))
+ end
+
+ # def get_with_basic, def post_with_basic, def put_with_basic, def delete_with_basic, def head_with_basic
+ # def get_with_digest, def post_with_digest, def put_with_digest, def delete_with_digest, def head_with_digest
+ [:get, :post, :put, :delete, :head].each do |method|
+ [:noauth, :basic, :digest].each do |auth_type|
+ define_method("#{method}_with_#{auth_type}") do |uri, parameters, headers, *auth|
+ send("request_with_#{auth_type}", method, uri, parameters, headers, *auth)
+ end
+ end
+ end
+
# Returns the URL for the given options, according to the rules specified
# in the application's routes.
def url_for(options)
@@ -353,6 +420,32 @@ module ActionController
return status
end
+ # Same as process, but handles authentication returns to perform
+ # Basic or Digest authentication
+ def process_with_auth(method, path, parameters = nil, headers = nil)
+ status = process(method, path, parameters, headers)
+
+ if status == 401
+ # Extract authentication information from response
+ auth_data = @response.headers['WWW-Authenticate']
+ if /^Basic /.match(auth_data)
+ # extract realm, to be used in subsequent request
+ @realm = auth_header.split(' ')[1]
+ elsif /^Digest/.match(auth_data)
+ creds = auth_data.to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair|
+ key, value = pair.split('=', 2)
+ hash[key.strip.to_sym] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')
+ hash
+ end
+ @realm = creds[:realm]
+ @nonce = creds[:nonce]
+ @opaque = creds[:opaque]
+ end
+ end
+
+ return status
+ end
+
# Encode the cookies hash in a format suitable for passing to a
# request.
def encode_cookies
@@ -371,7 +464,7 @@ module ActionController
"SERVER_PORT" => https? ? "443" : "80",
"HTTPS" => https? ? "on" : "off"
}
- UrlRewriter.new(RackRequest.new(env), {})
+ UrlRewriter.new(Request.new(env), {})
end
def name_with_prefix(prefix, name)
diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb
index 54108df06d..159c5c7326 100644
--- a/actionpack/lib/action_controller/layout.rb
+++ b/actionpack/lib/action_controller/layout.rb
@@ -178,9 +178,15 @@ module ActionController #:nodoc:
find_layout(layout, format)
end
+ def layout_list #:nodoc:
+ Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] }
+ end
+
def find_layout(layout, *formats) #:nodoc:
return layout if layout.respond_to?(:render)
view_paths.find_template(layout.to_s =~ /layouts\// ? layout : "layouts/#{layout}", *formats)
+ rescue ActionView::MissingTemplate
+ nil
end
private
@@ -188,7 +194,7 @@ module ActionController #:nodoc:
inherited_without_layout(child)
unless child.name.blank?
layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
- child.layout(layout_match, {}, true) if child.find_layout(layout_match, :all)
+ child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty?
end
end
@@ -225,8 +231,16 @@ module ActionController #:nodoc:
private
def candidate_for_layout?(options)
- options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty? &&
- !@template.__send__(:_exempt_from_layout?, options[:template] || default_template_name(options[:action]))
+ template = options[:template] || default_template(options[:action])
+ if options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty?
+ begin
+ !self.view_paths.find_template(template, default_template_format).exempt_from_layout?
+ rescue ActionView::MissingTemplate
+ true
+ end
+ end
+ rescue ActionView::MissingTemplate
+ false
end
def pick_layout(options)
@@ -235,7 +249,7 @@ module ActionController #:nodoc:
when FalseClass
nil
when NilClass, TrueClass
- active_layout if action_has_layout? && !@template.__send__(:_exempt_from_layout?, default_template_name)
+ active_layout if action_has_layout? && candidate_for_layout?(:template => default_template_name)
else
active_layout(layout)
end
diff --git a/actionpack/lib/action_controller/middlewares.rb b/actionpack/lib/action_controller/middlewares.rb
new file mode 100644
index 0000000000..793739723f
--- /dev/null
+++ b/actionpack/lib/action_controller/middlewares.rb
@@ -0,0 +1,21 @@
+use "ActionController::Lock", :if => lambda {
+ !ActionController::Base.allow_concurrency
+}
+
+use "ActionController::Failsafe"
+
+use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) }
+
+["ActionController::Session::CookieStore",
+ "ActionController::Session::MemCacheStore",
+ "ActiveRecord::SessionStore"].each do |store|
+ use(store, ActionController::Base.session_options,
+ :if => lambda {
+ if session_store = ActionController::Base.session_store
+ session_store.name == store
+ end
+ }
+ )
+end
+
+use ActionController::VerbPiggybacking
diff --git a/actionpack/lib/action_controller/mime_responds.rb b/actionpack/lib/action_controller/mime_responds.rb
index 29294476f7..b755363873 100644
--- a/actionpack/lib/action_controller/mime_responds.rb
+++ b/actionpack/lib/action_controller/mime_responds.rb
@@ -143,12 +143,27 @@ module ActionController #:nodoc:
custom(@mime_type_priority.first, &block)
end
end
+
+ def self.generate_method_for_mime(mime)
+ sym = mime.is_a?(Symbol) ? mime : mime.to_sym
+ const = sym.to_s.upcase
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{sym}(&block) # def html(&block)
+ custom(Mime::#{const}, &block) # custom(Mime::HTML, &block)
+ end # end
+ RUBY
+ end
- def method_missing(symbol, &block)
- mime_constant = symbol.to_s.upcase
+ Mime::SET.each do |mime|
+ generate_method_for_mime(mime)
+ end
- if Mime::SET.include?(Mime.const_get(mime_constant))
- custom(Mime.const_get(mime_constant), &block)
+ def method_missing(symbol, &block)
+ mime_constant = Mime.const_get(symbol.to_s.upcase)
+
+ if Mime::SET.include?(mime_constant)
+ self.class.generate_method_for_mime(mime_constant)
+ send(symbol, &block)
else
super
end
diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
index dce50c6c3b..924d1aa6bd 100644
--- a/actionpack/lib/action_controller/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/polymorphic_routes.rb
@@ -118,13 +118,17 @@ module ActionController
%w(edit new).each do |action|
module_eval <<-EOT, __FILE__, __LINE__
- def #{action}_polymorphic_url(record_or_hash, options = {})
- polymorphic_url(record_or_hash, options.merge(:action => "#{action}"))
- end
-
- def #{action}_polymorphic_path(record_or_hash, options = {})
- polymorphic_url(record_or_hash, options.merge(:action => "#{action}", :routing_type => :path))
- end
+ def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
+ polymorphic_url( # polymorphic_url(
+ record_or_hash, # record_or_hash,
+ options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
+ end # end
+ #
+ def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
+ polymorphic_url( # polymorphic_url(
+ record_or_hash, # record_or_hash,
+ options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
+ end # end
EOT
end
diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb
deleted file mode 100644
index 8c6db91dd0..0000000000
--- a/actionpack/lib/action_controller/rack_process.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-require 'action_controller/cgi_ext'
-
-module ActionController #:nodoc:
- class RackRequest < AbstractRequest #:nodoc:
- attr_accessor :session_options
-
- class SessionFixationAttempt < StandardError #:nodoc:
- end
-
- def initialize(env)
- @env = env
- super()
- end
-
- %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
- PATH_TRANSLATED REMOTE_HOST
- REMOTE_IDENT REMOTE_USER SCRIPT_NAME
- SERVER_NAME SERVER_PROTOCOL
-
- HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
- HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
- HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
- define_method(env.sub(/^HTTP_/n, '').downcase) do
- @env[env]
- end
- end
-
- def query_string
- qs = super
- if !qs.blank?
- qs
- else
- @env['QUERY_STRING']
- end
- end
-
- def body_stream #:nodoc:
- @env['rack.input']
- end
-
- def key?(key)
- @env.key?(key)
- end
-
- def cookies
- Rack::Request.new(@env).cookies
- end
-
- def server_port
- @env['SERVER_PORT'].to_i
- end
-
- def server_software
- @env['SERVER_SOFTWARE'].split("/").first
- end
-
- def session_options
- @env['rack.session.options'] ||= {}
- end
-
- def session_options=(options)
- @env['rack.session.options'] = options
- end
-
- def session
- @env['rack.session'] ||= {}
- end
-
- def reset_session
- @env['rack.session'] = {}
- end
- end
-end
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index 087fffe87d..822955d1db 100755
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -3,32 +3,48 @@ require 'stringio'
require 'strscan'
require 'active_support/memoizable'
+require 'action_controller/cgi_ext'
module ActionController
# CgiRequest and TestRequest provide concrete implementations.
- class AbstractRequest
+ class Request
extend ActiveSupport::Memoizable
- def self.relative_url_root=(relative_url_root)
- ActiveSupport::Deprecation.warn(
- "ActionController::AbstractRequest.relative_url_root= has been renamed." +
- "You can now set it with config.action_controller.relative_url_root=", caller)
- ActionController::Base.relative_url_root=relative_url_root
+ class SessionFixationAttempt < StandardError #:nodoc:
end
- HTTP_METHODS = %w(get head put post delete options)
- HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
-
# The hash of environment variables for this request,
# such as { 'RAILS_ENV' => 'production' }.
attr_reader :env
+ def initialize(env)
+ @env = env
+ end
+
+ %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
+ PATH_TRANSLATED REMOTE_HOST
+ REMOTE_IDENT REMOTE_USER SCRIPT_NAME
+ SERVER_NAME SERVER_PROTOCOL
+
+ HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
+ HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
+ HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
+ define_method(env.sub(/^HTTP_/n, '').downcase) do
+ @env[env]
+ end
+ end
+
+ def key?(key)
+ @env.key?(key)
+ end
+
+ HTTP_METHODS = %w(get head put post delete options)
+ HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
+
# The true HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
# UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
def request_method
method = @env['REQUEST_METHOD']
- method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank?
-
HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
end
memoize :request_method
@@ -85,7 +101,7 @@ module ActionController
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
- Mime::Type.lookup(content_type_without_parameters)
+ Mime::Type.lookup(parser.content_type_without_parameters)
end
memoize :content_type
@@ -125,15 +141,15 @@ module ActionController
# supplied, both must match, or the request is not considered fresh.
def fresh?(response)
case
- when if_modified_since && if_none_match
- not_modified?(response.last_modified) && etag_matches?(response.etag)
- when if_modified_since
- not_modified?(response.last_modified)
- when if_none_match
- etag_matches?(response.etag)
- else
- false
- end
+ when if_modified_since && if_none_match
+ not_modified?(response.last_modified) && etag_matches?(response.etag)
+ when if_modified_since
+ not_modified?(response.last_modified)
+ when if_none_match
+ etag_matches?(response.etag)
+ else
+ false
+ end
end
# Returns the Mime type for the \format used in the request.
@@ -248,7 +264,6 @@ EOM
end
memoize :server_software
-
# Returns the complete URL used for this request.
def url
protocol + host_with_port + request_uri
@@ -271,7 +286,7 @@ EOM
if forwarded = env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
else
- env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
+ env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
end
end
@@ -332,11 +347,7 @@ EOM
# Returns the query string, accounting for server idiosyncrasies.
def query_string
- if uri = @env['REQUEST_URI']
- uri.split('?', 2)[1] || ''
- else
- @env['QUERY_STRING'] || ''
- end
+ @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '')
end
memoize :query_string
@@ -378,11 +389,7 @@ EOM
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
- unless env.include? 'RAW_POST_DATA'
- env['RAW_POST_DATA'] = body.read(content_length)
- body.rewind if body.respond_to?(:rewind)
- end
- env['RAW_POST_DATA']
+ parser.raw_post
end
# Returns both GET and POST \parameters in a single hash.
@@ -391,7 +398,7 @@ EOM
end
def path_parameters=(parameters) #:nodoc:
- @path_parameters = parameters
+ @env["rack.routing_args"] = parameters
@symbolized_path_parameters = @parameters = nil
end
@@ -407,18 +414,11 @@ EOM
#
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
def path_parameters
- @path_parameters ||= {}
+ @env["rack.routing_args"] ||= {}
end
- # The request body is an IO input stream. If the RAW_POST_DATA environment
- # variable is already set, wrap it in a StringIO.
def body
- if raw_post = env['RAW_POST_DATA']
- raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
- StringIO.new(raw_post)
- else
- body_stream
- end
+ parser.body
end
def remote_addr
@@ -430,441 +430,53 @@ EOM
end
alias referer referrer
-
def query_parameters
- @query_parameters ||= self.class.parse_query_parameters(query_string)
+ @query_parameters ||= parser.query_parameters
end
def request_parameters
- @request_parameters ||= parse_formatted_request_parameters
+ @request_parameters ||= parser.request_parameters
end
-
- #--
- # Must be implemented in the concrete request
- #++
-
def body_stream #:nodoc:
+ @env['rack.input']
end
- def cookies #:nodoc:
+ def cookies
+ Rack::Request.new(@env).cookies
end
- def session #:nodoc:
+ def session
+ @env['rack.session'] ||= {}
end
def session=(session) #:nodoc:
@session = session
end
- def reset_session #:nodoc:
+ def reset_session
+ @env['rack.session'] = {}
end
- protected
- # The raw content type string. Use when you need parameters such as
- # charset or boundary which aren't included in the content_type MIME type.
- # Overridden by the X-POST_DATA_FORMAT header for backward compatibility.
- def content_type_with_parameters
- content_type_from_legacy_post_data_format_header ||
- env['CONTENT_TYPE'].to_s
- end
-
- # The raw content type string with its parameters stripped off.
- def content_type_without_parameters
- self.class.extract_content_type_without_parameters(content_type_with_parameters)
- end
- memoize :content_type_without_parameters
-
- private
- def content_type_from_legacy_post_data_format_header
- if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
- case x_post_format.to_s.downcase
- when 'yaml'; 'application/x-yaml'
- when 'xml'; 'application/xml'
- end
- end
- end
-
- def parse_formatted_request_parameters
- return {} if content_length.zero?
-
- content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters)
-
- # Don't parse params for unknown requests.
- return {} if content_type.blank?
-
- mime_type = Mime::Type.lookup(content_type)
- strategy = ActionController::Base.param_parsers[mime_type]
-
- # Only multipart form parsing expects a stream.
- body = (strategy && strategy != :multipart_form) ? raw_post : self.body
-
- case strategy
- when Proc
- strategy.call(body)
- when :url_encoded_form
- self.class.clean_up_ajax_request_body! body
- self.class.parse_query_parameters(body)
- when :multipart_form
- self.class.parse_multipart_form_parameters(body, boundary, content_length, env)
- when :xml_simple, :xml_node
- body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
- when :yaml
- YAML.load(body)
- when :json
- if body.blank?
- {}
- else
- data = ActiveSupport::JSON.decode(body)
- data = {:_json => data} unless data.is_a?(Hash)
- data.with_indifferent_access
- end
- else
- {}
- end
- rescue Exception => e # YAML, XML or Ruby code block errors
- raise
- { "body" => body,
- "content_type" => content_type_with_parameters,
- "content_length" => content_length,
- "exception" => "#{e.message} (#{e.class})",
- "backtrace" => e.backtrace }
- end
-
- def named_host?(host)
- !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
- end
-
- class << self
- def parse_query_parameters(query_string)
- return {} if query_string.blank?
-
- pairs = query_string.split('&').collect do |chunk|
- next if chunk.empty?
- key, value = chunk.split('=', 2)
- next if key.empty?
- value = value.nil? ? nil : CGI.unescape(value)
- [ CGI.unescape(key), value ]
- end.compact
-
- UrlEncodedPairParser.new(pairs).result
- end
-
- def parse_request_parameters(params)
- parser = UrlEncodedPairParser.new
-
- params = params.dup
- until params.empty?
- for key, value in params
- if key.blank?
- params.delete key
- elsif !key.include?('[')
- # much faster to test for the most common case first (GET)
- # and avoid the call to build_deep_hash
- parser.result[key] = get_typed_value(value[0])
- params.delete key
- elsif value.is_a?(Array)
- parser.parse(key, get_typed_value(value.shift))
- params.delete key if value.empty?
- else
- raise TypeError, "Expected array, found #{value.inspect}"
- end
- end
- end
-
- parser.result
- end
-
- def parse_multipart_form_parameters(body, boundary, body_size, env)
- parse_request_parameters(read_multipart(body, boundary, body_size, env))
- end
-
- def extract_multipart_boundary(content_type_with_parameters)
- if content_type_with_parameters =~ MULTIPART_BOUNDARY
- ['multipart/form-data', $1.dup]
- else
- extract_content_type_without_parameters(content_type_with_parameters)
- end
- end
-
- def extract_content_type_without_parameters(content_type_with_parameters)
- $1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/
- end
-
- def clean_up_ajax_request_body!(body)
- body.chop! if body[-1] == 0
- body.gsub!(/&_=$/, '')
- end
-
-
- private
- def get_typed_value(value)
- case value
- when String
- value
- when NilClass
- ''
- when Array
- value.map { |v| get_typed_value(v) }
- else
- if value.respond_to? :original_filename
- # Uploaded file
- if value.original_filename
- value
- # Multipart param
- else
- result = value.read
- value.rewind
- result
- end
- # Unknown value, neither string nor multipart.
- else
- raise "Unknown form value: #{value.inspect}"
- end
- end
- end
-
- MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
-
- EOL = "\015\012"
-
- def read_multipart(body, boundary, body_size, env)
- params = Hash.new([])
- boundary = "--" + boundary
- quoted_boundary = Regexp.quote(boundary)
- buf = ""
- bufsize = 10 * 1024
- boundary_end=""
-
- # start multipart/form-data
- body.binmode if defined? body.binmode
- case body
- when File
- body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding)
- when StringIO
- body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding)
- end
- boundary_size = boundary.size + EOL.size
- body_size -= boundary_size
- status = body.read(boundary_size)
- if nil == status
- raise EOFError, "no content body"
- elsif boundary + EOL != status
- raise EOFError, "bad content body"
- end
-
- loop do
- head = nil
- content =
- if 10240 < body_size
- UploadedTempfile.new("CGI")
- else
- UploadedStringIO.new
- end
- content.binmode if defined? content.binmode
-
- until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
-
- if (not head) and /#{EOL}#{EOL}/n.match(buf)
- buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
- head = $1.dup
- ""
- end
- next
- end
-
- if head and ( (EOL + boundary + EOL).size < buf.size )
- content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
- buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
- end
-
- c = if bufsize < body_size
- body.read(bufsize)
- else
- body.read(body_size)
- end
- if c.nil? || c.empty?
- raise EOFError, "bad content body"
- end
- buf.concat(c)
- body_size -= c.size
- end
-
- buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
- content.print $1
- if "--" == $2
- body_size = -1
- end
- boundary_end = $2.dup
- ""
- end
-
- content.rewind
-
- head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni
- if filename = $1 || $2
- if /Mac/ni.match(env['HTTP_USER_AGENT']) and
- /Mozilla/ni.match(env['HTTP_USER_AGENT']) and
- (not /MSIE/ni.match(env['HTTP_USER_AGENT']))
- filename = CGI.unescape(filename)
- end
- content.original_path = filename.dup
- end
-
- head =~ /Content-Type: ([^\r]*)/ni
- content.content_type = $1.dup if $1
-
- head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni
- name = $1.dup if $1
-
- if params.has_key?(name)
- params[name].push(content)
- else
- params[name] = [content]
- end
- break if body_size == -1
- end
- raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
-
- begin
- body.rewind if body.respond_to?(:rewind)
- rescue Errno::ESPIPE
- # Handles exceptions raised by input streams that cannot be rewound
- # such as when using plain CGI under Apache
- end
-
- params
- end
+ def session_options
+ @env['rack.session.options'] ||= {}
end
- end
- class UrlEncodedPairParser < StringScanner #:nodoc:
- attr_reader :top, :parent, :result
-
- def initialize(pairs = [])
- super('')
- @result = {}
- pairs.each { |key, value| parse(key, value) }
+ def session_options=(options)
+ @env['rack.session.options'] = options
end
- KEY_REGEXP = %r{([^\[\]=&]+)}
- BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
-
- # Parse the query string
- def parse(key, value)
- self.string = key
- @top, @parent = result, nil
-
- # First scan the bare key
- key = scan(KEY_REGEXP) or return
- key = post_key_check(key)
-
- # Then scan as many nestings as present
- until eos?
- r = scan(BRACKETED_KEY_REGEXP) or return
- key = self[1]
- key = post_key_check(key)
- end
-
- bind(key, value)
+ def server_port
+ @env['SERVER_PORT'].to_i
end
private
- # After we see a key, we must look ahead to determine our next action. Cases:
- #
- # [] follows the key. Then the value must be an array.
- # = follows the key. (A value comes next)
- # & or the end of string follows the key. Then the key is a flag.
- # otherwise, a hash follows the key.
- def post_key_check(key)
- if scan(/\[\]/) # a[b][] indicates that b is an array
- container(key, Array)
- nil
- elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
- container(key, Hash)
- nil
- else # End of key? We do nothing.
- key
- end
- end
-
- # Add a container to the stack.
- def container(key, klass)
- type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
- value = bind(key, klass.new)
- type_conflict! klass, value unless value.is_a?(klass)
- push(value)
- end
-
- # Push a value onto the 'stack', which is actually only the top 2 items.
- def push(value)
- @parent, @top = @top, value
- end
-
- # Bind a key (which may be nil for items in an array) to the provided value.
- def bind(key, value)
- if top.is_a? Array
- if key
- if top[-1].is_a?(Hash) && ! top[-1].key?(key)
- top[-1][key] = value
- else
- top << {key => value}.with_indifferent_access
- push top.last
- value = top[key]
- end
- else
- top << value
- end
- elsif top.is_a? Hash
- key = CGI.unescape(key)
- parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
- top[key] ||= value
- return top[key]
- else
- raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
- end
-
- return value
- end
-
- def type_conflict!(klass, value)
- raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
- end
- end
-
- module UploadedFile
- def self.included(base)
- base.class_eval do
- attr_accessor :original_path, :content_type
- alias_method :local_path, :path
+ def named_host?(host)
+ !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
end
- end
- # Take the basename of the upload's original filename.
- # This handles the full Windows paths given by Internet Explorer
- # (and perhaps other broken user agents) without affecting
- # those which give the lone filename.
- # The Windows regexp is adapted from Perl's File::Basename.
- def original_filename
- unless defined? @original_filename
- @original_filename =
- unless original_path.blank?
- if original_path =~ /^(?:.*[:\\\/])?(.*)/m
- $1
- else
- File.basename original_path
- end
- end
+ def parser
+ @parser ||= ActionController::RequestParser.new(@env)
end
- @original_filename
- end
- end
-
- class UploadedStringIO < StringIO
- include UploadedFile
- end
-
- class UploadedTempfile < Tempfile
- include UploadedFile
end
end
diff --git a/actionpack/lib/action_controller/request_parser.rb b/actionpack/lib/action_controller/request_parser.rb
new file mode 100644
index 0000000000..82ee4c84c4
--- /dev/null
+++ b/actionpack/lib/action_controller/request_parser.rb
@@ -0,0 +1,314 @@
+module ActionController
+ class RequestParser
+ def initialize(env)
+ @env = env
+ end
+
+ def request_parameters
+ @request_parameters ||= parse_formatted_request_parameters
+ end
+
+ def query_parameters
+ @query_parameters ||= self.class.parse_query_parameters(query_string)
+ end
+
+ # Returns the query string, accounting for server idiosyncrasies.
+ def query_string
+ @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '')
+ end
+
+ # The request body is an IO input stream. If the RAW_POST_DATA environment
+ # variable is already set, wrap it in a StringIO.
+ def body
+ if raw_post = @env['RAW_POST_DATA']
+ raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
+ StringIO.new(raw_post)
+ else
+ @env['rack.input']
+ end
+ end
+
+ # The raw content type string with its parameters stripped off.
+ def content_type_without_parameters
+ self.class.extract_content_type_without_parameters(content_type_with_parameters)
+ end
+
+ def raw_post
+ unless @env.include? 'RAW_POST_DATA'
+ @env['RAW_POST_DATA'] = body.read(content_length)
+ body.rewind if body.respond_to?(:rewind)
+ end
+ @env['RAW_POST_DATA']
+ end
+
+ private
+
+ def parse_formatted_request_parameters
+ return {} if content_length.zero?
+
+ content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters)
+
+ # Don't parse params for unknown requests.
+ return {} if content_type.blank?
+
+ mime_type = Mime::Type.lookup(content_type)
+ strategy = ActionController::Base.param_parsers[mime_type]
+
+ # Only multipart form parsing expects a stream.
+ body = (strategy && strategy != :multipart_form) ? raw_post : self.body
+
+ case strategy
+ when Proc
+ strategy.call(body)
+ when :url_encoded_form
+ self.class.clean_up_ajax_request_body! body
+ self.class.parse_query_parameters(body)
+ when :multipart_form
+ self.class.parse_multipart_form_parameters(body, boundary, content_length, @env)
+ when :xml_simple, :xml_node
+ body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
+ when :yaml
+ YAML.load(body)
+ when :json
+ if body.blank?
+ {}
+ else
+ data = ActiveSupport::JSON.decode(body)
+ data = {:_json => data} unless data.is_a?(Hash)
+ data.with_indifferent_access
+ end
+ else
+ {}
+ end
+ rescue Exception => e # YAML, XML or Ruby code block errors
+ raise
+ { "body" => body,
+ "content_type" => content_type_with_parameters,
+ "content_length" => content_length,
+ "exception" => "#{e.message} (#{e.class})",
+ "backtrace" => e.backtrace }
+ end
+
+ def content_length
+ @content_length ||= @env['CONTENT_LENGTH'].to_i
+ end
+
+ # The raw content type string. Use when you need parameters such as
+ # charset or boundary which aren't included in the content_type MIME type.
+ # Overridden by the X-POST_DATA_FORMAT header for backward compatibility.
+ def content_type_with_parameters
+ content_type_from_legacy_post_data_format_header || @env['CONTENT_TYPE'].to_s
+ end
+
+ def content_type_from_legacy_post_data_format_header
+ if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
+ case x_post_format.to_s.downcase
+ when 'yaml'; 'application/x-yaml'
+ when 'xml'; 'application/xml'
+ end
+ end
+ end
+
+ class << self
+ def parse_query_parameters(query_string)
+ return {} if query_string.blank?
+
+ pairs = query_string.split('&').collect do |chunk|
+ next if chunk.empty?
+ key, value = chunk.split('=', 2)
+ next if key.empty?
+ value = value.nil? ? nil : CGI.unescape(value)
+ [ CGI.unescape(key), value ]
+ end.compact
+
+ UrlEncodedPairParser.new(pairs).result
+ end
+
+ def parse_request_parameters(params)
+ parser = UrlEncodedPairParser.new
+
+ params = params.dup
+ until params.empty?
+ for key, value in params
+ if key.blank?
+ params.delete key
+ elsif !key.include?('[')
+ # much faster to test for the most common case first (GET)
+ # and avoid the call to build_deep_hash
+ parser.result[key] = get_typed_value(value[0])
+ params.delete key
+ elsif value.is_a?(Array)
+ parser.parse(key, get_typed_value(value.shift))
+ params.delete key if value.empty?
+ else
+ raise TypeError, "Expected array, found #{value.inspect}"
+ end
+ end
+ end
+
+ parser.result
+ end
+
+ def parse_multipart_form_parameters(body, boundary, body_size, env)
+ parse_request_parameters(read_multipart(body, boundary, body_size, env))
+ end
+
+ def extract_multipart_boundary(content_type_with_parameters)
+ if content_type_with_parameters =~ MULTIPART_BOUNDARY
+ ['multipart/form-data', $1.dup]
+ else
+ extract_content_type_without_parameters(content_type_with_parameters)
+ end
+ end
+
+ def extract_content_type_without_parameters(content_type_with_parameters)
+ $1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/
+ end
+
+ def clean_up_ajax_request_body!(body)
+ body.chop! if body[-1] == 0
+ body.gsub!(/&_=$/, '')
+ end
+
+
+ private
+ def get_typed_value(value)
+ case value
+ when String
+ value
+ when NilClass
+ ''
+ when Array
+ value.map { |v| get_typed_value(v) }
+ else
+ if value.respond_to? :original_filename
+ # Uploaded file
+ if value.original_filename
+ value
+ # Multipart param
+ else
+ result = value.read
+ value.rewind
+ result
+ end
+ # Unknown value, neither string nor multipart.
+ else
+ raise "Unknown form value: #{value.inspect}"
+ end
+ end
+ end
+
+ MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
+
+ EOL = "\015\012"
+
+ def read_multipart(body, boundary, body_size, env)
+ params = Hash.new([])
+ boundary = "--" + boundary
+ quoted_boundary = Regexp.quote(boundary)
+ buf = ""
+ bufsize = 10 * 1024
+ boundary_end=""
+
+ # start multipart/form-data
+ body.binmode if defined? body.binmode
+ case body
+ when File
+ body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding)
+ when StringIO
+ body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding)
+ end
+ boundary_size = boundary.size + EOL.size
+ body_size -= boundary_size
+ status = body.read(boundary_size)
+ if nil == status
+ raise EOFError, "no content body"
+ elsif boundary + EOL != status
+ raise EOFError, "bad content body"
+ end
+
+ loop do
+ head = nil
+ content =
+ if 10240 < body_size
+ UploadedTempfile.new("CGI")
+ else
+ UploadedStringIO.new
+ end
+ content.binmode if defined? content.binmode
+
+ until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
+
+ if (not head) and /#{EOL}#{EOL}/n.match(buf)
+ buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
+ head = $1.dup
+ ""
+ end
+ next
+ end
+
+ if head and ( (EOL + boundary + EOL).size < buf.size )
+ content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
+ buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
+ end
+
+ c = if bufsize < body_size
+ body.read(bufsize)
+ else
+ body.read(body_size)
+ end
+ if c.nil? || c.empty?
+ raise EOFError, "bad content body"
+ end
+ buf.concat(c)
+ body_size -= c.size
+ end
+
+ buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
+ content.print $1
+ if "--" == $2
+ body_size = -1
+ end
+ boundary_end = $2.dup
+ ""
+ end
+
+ content.rewind
+
+ head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni
+ if filename = $1 || $2
+ if /Mac/ni.match(env['HTTP_USER_AGENT']) and
+ /Mozilla/ni.match(env['HTTP_USER_AGENT']) and
+ (not /MSIE/ni.match(env['HTTP_USER_AGENT']))
+ filename = CGI.unescape(filename)
+ end
+ content.original_path = filename.dup
+ end
+
+ head =~ /Content-Type: ([^\r]*)/ni
+ content.content_type = $1.dup if $1
+
+ head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni
+ name = $1.dup if $1
+
+ if params.has_key?(name)
+ params[name].push(content)
+ else
+ params[name] = [content]
+ end
+ break if body_size == -1
+ end
+ raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
+
+ begin
+ body.rewind if body.respond_to?(:rewind)
+ rescue Errno::ESPIPE
+ # Handles exceptions raised by input streams that cannot be rewound
+ # such as when using plain CGI under Apache
+ end
+
+ params
+ end
+ end # class << self
+ end
+end
diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb
index 5ef79a36ce..3a5e5071bb 100644
--- a/actionpack/lib/action_controller/rescue.rb
+++ b/actionpack/lib/action_controller/rescue.rb
@@ -59,7 +59,9 @@ module ActionController #:nodoc:
end
module ClassMethods
- def process_with_exception(request, response, exception) #:nodoc:
+ def call_with_exception(env, exception) #:nodoc:
+ request = env["actioncontroller.rescue.request"]
+ response = env["actioncontroller.rescue.response"]
new.process(request, response, :rescue_action, exception)
end
end
diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb
index 866616bac3..64319fe102 100644
--- a/actionpack/lib/action_controller/response.rb
+++ b/actionpack/lib/action_controller/response.rb
@@ -34,14 +34,14 @@ module ActionController # :nodoc:
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
attr_accessor :request
- attr_accessor :session, :cookies, :assigns, :template, :layout
+ attr_accessor :session, :assigns, :template, :layout
attr_accessor :redirected_to, :redirected_to_method_params
delegate :default_charset, :to => 'ActionController::Base'
def initialize
@status = 200
- @header = DEFAULT_HEADERS.merge("cookie" => [])
+ @header = DEFAULT_HEADERS.dup
@writer = lambda { |x| @body << x }
@block = nil
@@ -143,10 +143,9 @@ module ActionController # :nodoc:
handle_conditional_get!
set_content_length!
convert_content_type!
-
convert_language!
convert_expires!
- set_cookies!
+ convert_cookies!
end
def each(&callback)
@@ -168,6 +167,35 @@ module ActionController # :nodoc:
str
end
+ # Over Rack::Response#set_cookie to add HttpOnly option
+ def set_cookie(key, value)
+ case value
+ when Hash
+ domain = "; domain=" + value[:domain] if value[:domain]
+ path = "; path=" + value[:path] if value[:path]
+ # According to RFC 2109, we need dashes here.
+ # N.B.: cgi.rb uses spaces...
+ expires = "; expires=" + value[:expires].clone.gmtime.
+ strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
+ secure = "; secure" if value[:secure]
+ httponly = "; HttpOnly" if value[:http_only]
+ value = value[:value]
+ end
+ value = [value] unless Array === value
+ cookie = ::Rack::Utils.escape(key) + "=" +
+ value.map { |v| ::Rack::Utils.escape v }.join("&") +
+ "#{domain}#{path}#{expires}#{secure}#{httponly}"
+
+ case self["Set-Cookie"]
+ when Array
+ self["Set-Cookie"] << cookie
+ when String
+ self["Set-Cookie"] = [self["Set-Cookie"], cookie]
+ when nil
+ self["Set-Cookie"] = cookie
+ end
+ end
+
private
def handle_conditional_get!
if etag? || last_modified?
@@ -217,22 +245,8 @@ module ActionController # :nodoc:
headers["Expires"] = headers.delete("") if headers["expires"]
end
- def set_cookies!
- # Convert 'cookie' header to 'Set-Cookie' headers.
- # Because Set-Cookie header can appear more the once in the response body,
- # we store it in a line break separated string that will be translated to
- # multiple Set-Cookie header by the handler.
- if cookie = headers.delete('cookie')
- cookies = []
-
- case cookie
- when Array then cookie.each { |c| cookies << c.to_s }
- when Hash then cookie.each { |_, c| cookies << c.to_s }
- else cookies << cookie.to_s
- end
-
- headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact
- end
+ def convert_cookies!
+ headers['Set-Cookie'] = Array(headers['Set-Cookie']).compact
end
end
end
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
index 13646aef61..06aef6e169 100644
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ b/actionpack/lib/action_controller/routing/route_set.rb
@@ -145,10 +145,10 @@ module ActionController
def define_hash_access(route, name, kind, options)
selector = hash_access_name(name, kind)
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
- def #{selector}(options = nil)
- options ? #{options.inspect}.merge(options) : #{options.inspect}
- end
- protected :#{selector}
+ def #{selector}(options = nil) # def hash_for_users_url(options = nil)
+ options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false}
+ end # end
+ protected :#{selector} # protected :hash_for_users_url
end_eval
helpers << selector
end
@@ -173,32 +173,33 @@ module ActionController
# foo_url(bar, baz, bang, :sort_by => 'baz')
#
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
- def #{selector}(*args)
-
- #{generate_optimisation_block(route, kind)}
-
- opts = if args.empty? || Hash === args.first
- args.first || {}
- else
- options = args.extract_options!
- args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
- h[k] = v
- h
- end
- options.merge(args)
- end
-
- url_for(#{hash_access_method}(opts))
-
- end
- #Add an alias to support the now deprecated formatted_* URL.
- def formatted_#{selector}(*args)
- ActiveSupport::Deprecation.warn(
- "formatted_#{selector}() has been deprecated. please pass format to the standard" +
- "#{selector}() method instead.", caller)
- #{selector}(*args)
- end
- protected :#{selector}
+ def #{selector}(*args) # def users_url(*args)
+ #
+ #{generate_optimisation_block(route, kind)} # #{generate_optimisation_block(route, kind)}
+ #
+ opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
+ args.first || {} # args.first || {}
+ else # else
+ options = args.extract_options! # options = args.extract_options!
+ args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)|
+ h[k] = v # h[k] = v
+ h # h
+ end # end
+ options.merge(args) # options.merge(args)
+ end # end
+ #
+ url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
+ #
+ end # end
+ #Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL.
+ def formatted_#{selector}(*args) # def formatted_users_url(*args)
+ ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn(
+ "formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " +
+ "please pass format to the standard" + # "please pass format to the standard" +
+ "#{selector}() method instead.", caller) # "users_url() method instead.", caller)
+ #{selector}(*args) # users_url(*args)
+ end # end
+ protected :#{selector} # protected :users_url
end_eval
helpers << selector
end
@@ -426,6 +427,12 @@ module ActionController
end
end
+ def call(env)
+ request = Request.new(env)
+ app = Routing::Routes.recognize(request)
+ app.call(env).to_a
+ end
+
def recognize(request)
params = recognize_path(request.path, extract_request_environment(request))
request.path_parameters = params.with_indifferent_access
diff --git a/actionpack/lib/action_controller/streaming.rb b/actionpack/lib/action_controller/streaming.rb
index 333fb61b45..e1786913a7 100644
--- a/actionpack/lib/action_controller/streaming.rb
+++ b/actionpack/lib/action_controller/streaming.rb
@@ -24,7 +24,8 @@ module ActionController #:nodoc:
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# Defaults to <tt>File.basename(path)</tt>.
- # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
+ # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
+ # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
# * <tt>:length</tt> - used to manually override the length (in bytes) of the content that
# is going to be sent to the client. Defaults to <tt>File.size(path)</tt>.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
@@ -107,7 +108,8 @@ module ActionController #:nodoc:
#
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
- # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
+ # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
+ # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
@@ -143,9 +145,16 @@ module ActionController #:nodoc:
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
+ content_type = options[:type]
+ if content_type.is_a?(Symbol)
+ raise ArgumentError, "Unknown MIME type #{options[:type]}" unless Mime::EXTENSION_LOOKUP.has_key?(content_type.to_s)
+ content_type = Mime::Type.lookup_by_extension(content_type.to_s)
+ end
+ content_type = content_type.to_s.strip # fixes a problem with extra '\r' with some browsers
+
headers.update(
'Content-Length' => options[:length],
- 'Content-Type' => options[:type].to_s.strip, # fixes a problem with extra '\r' with some browsers
+ 'Content-Type' => content_type,
'Content-Disposition' => disposition,
'Content-Transfer-Encoding' => 'binary'
)
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 79a8e1364d..7ed1a3e160 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -93,10 +93,7 @@ module ActionController
# and cookies, though. For sessions, you just do:
#
# @request.session[:key] = "value"
- #
- # For cookies, you need to manually create the cookie, like this:
- #
- # @request.cookies["key"] = CGI::Cookie.new("key", "value")
+ # @request.cookies["key"] = "value"
#
# == Testing named routes
#
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index c4d7d52951..285a8b09e4 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -27,20 +27,19 @@ module ActionController #:nodoc:
alias_method_chain :process, :test
end
- class TestRequest < AbstractRequest #:nodoc:
+ class TestRequest < Request #:nodoc:
attr_accessor :cookies, :session_options
- attr_accessor :query_parameters, :request_parameters, :path, :session
- attr_accessor :host, :user_agent
+ attr_accessor :query_parameters, :path, :session
+ attr_accessor :host
- def initialize(query_parameters = nil, request_parameters = nil, session = nil)
- @query_parameters = query_parameters || {}
- @request_parameters = request_parameters || {}
- @session = session || TestSession.new
+ def initialize
+ super(Rack::MockRequest.env_for("/"))
- initialize_containers
- initialize_default_values
+ @query_parameters = {}
+ @session = TestSession.new
- super()
+ initialize_default_values
+ initialize_containers
end
def reset_session
@@ -55,7 +54,11 @@ module ActionController #:nodoc:
# Either the RAW_POST_DATA environment variable or the URL-encoded request
# parameters.
def raw_post
- env['RAW_POST_DATA'] ||= returning(url_encoded_request_parameters) { |b| b.force_encoding(Encoding::BINARY) if b.respond_to?(:force_encoding) }
+ @env['RAW_POST_DATA'] ||= begin
+ data = url_encoded_request_parameters
+ data.force_encoding(Encoding::BINARY) if data.respond_to?(:force_encoding)
+ data
+ end
end
def port=(number)
@@ -125,26 +128,30 @@ module ActionController #:nodoc:
path_parameters[key.to_s] = value
end
end
+ raw_post # populate env['RAW_POST_DATA']
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
end
def recycle!
- self.request_parameters = {}
self.query_parameters = {}
self.path_parameters = {}
unmemoize_all
end
+ def user_agent=(user_agent)
+ @env['HTTP_USER_AGENT'] = user_agent
+ end
+
private
def initialize_containers
- @env, @cookies = {}, {}
+ @cookies = {}
end
def initialize_default_values
@host = "test.host"
@request_uri = "/"
- @user_agent = "Rails Testing"
- self.remote_addr = "0.0.0.0"
+ @env['HTTP_USER_AGENT'] = "Rails Testing"
+ @env['REMOTE_ADDR'] = "0.0.0.0"
@env["SERVER_PORT"] = 80
@env['REQUEST_METHOD'] = "GET"
end
@@ -260,14 +267,14 @@ module ActionController #:nodoc:
!template_objects[name].nil?
end
- # Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs
+ # Returns the response cookies, converted to a Hash of (name => value) pairs
#
- # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value
+ # assert_equal 'AuthorOfNewPage', r.cookies['author']
def cookies
cookies = {}
Array(headers['Set-Cookie']).each do |cookie|
key, value = cookie.split(";").first.split("=")
- cookies[key] = [value].compact
+ cookies[key] = value
end
cookies
end
@@ -377,20 +384,33 @@ module ActionController #:nodoc:
module TestProcess
def self.included(base)
- # execute the request simulating a specific HTTP method and set/volley the response
- # TODO: this should be un-DRY'ed for the sake of API documentation.
- %w( get post put delete head ).each do |method|
- base.class_eval <<-EOV, __FILE__, __LINE__
- def #{method}(action, parameters = nil, session = nil, flash = nil)
- @request.env['REQUEST_METHOD'] = "#{method.upcase}" if defined?(@request)
- process(action, parameters, session, flash)
- end
- EOV
+ # Executes a request simulating GET HTTP method and set/volley the response
+ def get(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "GET")
+ end
+
+ # Executes a request simulating POST HTTP method and set/volley the response
+ def post(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "POST")
+ end
+
+ # Executes a request simulating PUT HTTP method and set/volley the response
+ def put(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "PUT")
+ end
+
+ # Executes a request simulating DELETE HTTP method and set/volley the response
+ def delete(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "DELETE")
+ end
+
+ # Executes a request simulating HEAD HTTP method and set/volley the response
+ def head(action, parameters = nil, session = nil, flash = nil)
+ process(action, parameters, session, flash, "HEAD")
end
end
- # execute the request and set/volley the response
- def process(action, parameters = nil, session = nil, flash = nil)
+ def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
# Sanity check for required instance variables so we can give an
# understandable error message.
%w(@controller @request @response).each do |iv_name|
@@ -403,7 +423,7 @@ module ActionController #:nodoc:
@response.recycle!
@html_document = nil
- @request.env['REQUEST_METHOD'] ||= "GET"
+ @request.env['REQUEST_METHOD'] = http_method
@request.action = action.to_s
diff --git a/actionpack/lib/action_controller/uploaded_file.rb b/actionpack/lib/action_controller/uploaded_file.rb
new file mode 100644
index 0000000000..ea4845c68f
--- /dev/null
+++ b/actionpack/lib/action_controller/uploaded_file.rb
@@ -0,0 +1,37 @@
+module ActionController
+ module UploadedFile
+ def self.included(base)
+ base.class_eval do
+ attr_accessor :original_path, :content_type
+ alias_method :local_path, :path
+ end
+ end
+
+ # Take the basename of the upload's original filename.
+ # This handles the full Windows paths given by Internet Explorer
+ # (and perhaps other broken user agents) without affecting
+ # those which give the lone filename.
+ # The Windows regexp is adapted from Perl's File::Basename.
+ def original_filename
+ unless defined? @original_filename
+ @original_filename =
+ unless original_path.blank?
+ if original_path =~ /^(?:.*[:\\\/])?(.*)/m
+ $1
+ else
+ File.basename original_path
+ end
+ end
+ end
+ @original_filename
+ end
+ end
+
+ class UploadedStringIO < StringIO
+ include UploadedFile
+ end
+
+ class UploadedTempfile < Tempfile
+ include UploadedFile
+ end
+end
diff --git a/actionpack/lib/action_controller/url_encoded_pair_parser.rb b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
new file mode 100644
index 0000000000..bea96c711d
--- /dev/null
+++ b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
@@ -0,0 +1,95 @@
+module ActionController
+ class UrlEncodedPairParser < StringScanner #:nodoc:
+ attr_reader :top, :parent, :result
+
+ def initialize(pairs = [])
+ super('')
+ @result = {}
+ pairs.each { |key, value| parse(key, value) }
+ end
+
+ KEY_REGEXP = %r{([^\[\]=&]+)}
+ BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
+
+ # Parse the query string
+ def parse(key, value)
+ self.string = key
+ @top, @parent = result, nil
+
+ # First scan the bare key
+ key = scan(KEY_REGEXP) or return
+ key = post_key_check(key)
+
+ # Then scan as many nestings as present
+ until eos?
+ r = scan(BRACKETED_KEY_REGEXP) or return
+ key = self[1]
+ key = post_key_check(key)
+ end
+
+ bind(key, value)
+ end
+
+ private
+ # After we see a key, we must look ahead to determine our next action. Cases:
+ #
+ # [] follows the key. Then the value must be an array.
+ # = follows the key. (A value comes next)
+ # & or the end of string follows the key. Then the key is a flag.
+ # otherwise, a hash follows the key.
+ def post_key_check(key)
+ if scan(/\[\]/) # a[b][] indicates that b is an array
+ container(key, Array)
+ nil
+ elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
+ container(key, Hash)
+ nil
+ else # End of key? We do nothing.
+ key
+ end
+ end
+
+ # Add a container to the stack.
+ def container(key, klass)
+ type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
+ value = bind(key, klass.new)
+ type_conflict! klass, value unless value.is_a?(klass)
+ push(value)
+ end
+
+ # Push a value onto the 'stack', which is actually only the top 2 items.
+ def push(value)
+ @parent, @top = @top, value
+ end
+
+ # Bind a key (which may be nil for items in an array) to the provided value.
+ def bind(key, value)
+ if top.is_a? Array
+ if key
+ if top[-1].is_a?(Hash) && ! top[-1].key?(key)
+ top[-1][key] = value
+ else
+ top << {key => value}.with_indifferent_access
+ push top.last
+ value = top[key]
+ end
+ else
+ top << value
+ end
+ elsif top.is_a? Hash
+ key = CGI.unescape(key)
+ parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
+ top[key] ||= value
+ return top[key]
+ else
+ raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
+ end
+
+ return value
+ end
+
+ def type_conflict!(klass, value)
+ raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/verb_piggybacking.rb b/actionpack/lib/action_controller/verb_piggybacking.rb
new file mode 100644
index 0000000000..86cde304a0
--- /dev/null
+++ b/actionpack/lib/action_controller/verb_piggybacking.rb
@@ -0,0 +1,24 @@
+module ActionController
+ # TODO: Use Rack::MethodOverride when it is released
+ class VerbPiggybacking
+ HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS)
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ if env["REQUEST_METHOD"] == "POST"
+ req = Request.new(env)
+ if method = (req.parameters[:_method] || env["HTTP_X_HTTP_METHOD_OVERRIDE"])
+ method = method.to_s.upcase
+ if HTTP_METHODS.include?(method)
+ env["REQUEST_METHOD"] = method
+ end
+ end
+ end
+
+ @app.call(env)
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 33517ffb7b..8958e61e9d 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -3,7 +3,10 @@ module ActionView #:nodoc:
end
class MissingTemplate < ActionViewError #:nodoc:
+ attr_reader :path
+
def initialize(paths, path, template_format = nil)
+ @path = path
full_template_path = path.include?('.') ? path : "#{path}.erb"
display_paths = paths.compact.join(":")
template_type = (path =~ /layouts/i) ? 'layout' : 'template'
@@ -172,17 +175,6 @@ module ActionView #:nodoc:
delegate :logger, :to => 'ActionController::Base'
end
- # Templates that are exempt from layouts
- @@exempt_from_layout = Set.new([/\.rjs$/])
-
- # Don't render layouts for templates with the given extensions.
- def self.exempt_from_layout(*extensions)
- regexps = extensions.collect do |extension|
- extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
- end
- @@exempt_from_layout.merge(regexps)
- end
-
@@debug_rjs = false
##
# :singleton-method:
@@ -190,12 +182,6 @@ module ActionView #:nodoc:
# that alert()s the caught exception (and then re-raises it).
cattr_accessor :debug_rjs
- @@warn_cache_misses = false
- ##
- # :singleton-method:
- # A warning will be displayed whenever an action results in a cache miss on your view paths.
- cattr_accessor :warn_cache_misses
-
attr_internal :request
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
@@ -257,7 +243,8 @@ module ActionView #:nodoc:
if options[:layout]
_render_with_layout(options, local_assigns, &block)
elsif options[:file]
- _pick_template(options[:file]).render_template(self, options[:locals])
+ tempalte = self.view_paths.find_template(options[:file], template_format)
+ tempalte.render_template(self, options[:locals])
elsif options[:partial]
render_partial(options)
elsif options[:inline]
@@ -315,45 +302,6 @@ module ActionView #:nodoc:
end
end
- def _pick_template(template_path)
- return template_path if template_path.respond_to?(:render)
-
- path = template_path.sub(/^\//, '')
- if m = path.match(/(.*)\.(\w+)$/)
- template_file_name, template_file_extension = m[1], m[2]
- else
- template_file_name = path
- end
-
- # OPTIMIZE: Checks to lookup template in view path
- if template = self.view_paths.find_template(template_file_name, template_format)
- template
- elsif (first_render = @_render_stack.first) && first_render.respond_to?(:format_and_extension) &&
- (template = self.view_paths["#{template_file_name}.#{first_render.format_and_extension}"])
- template
- else
- template = Template.new(template_path, view_paths)
-
- if self.class.warn_cache_misses && logger
- logger.debug "[PERFORMANCE] Rendering a template that was " +
- "not found in view path. Templates outside the view path are " +
- "not cached and result in expensive disk operations. Move this " +
- "file into #{view_paths.join(':')} or add the folder to your " +
- "view path list"
- end
-
- template
- end
- end
- memoize :_pick_template
-
- def _exempt_from_layout?(template_path) #:nodoc:
- template = _pick_template(template_path).to_s
- @@exempt_from_layout.any? { |ext| template =~ ext }
- rescue ActionView::MissingTemplate
- return false
- end
-
def _render_with_layout(options, local_assigns, &block) #:nodoc:
partial_layout = options.delete(:layout)
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index a04bb8c598..4305617ac8 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -136,6 +136,10 @@ module ActionView
# dates.
# * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil.
# * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled.
+ # * <tt>:prompt</tt> - Set to true (for a generic prompt), a prompt string or a hash of prompt strings
+ # for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>.
+ # Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds)
+ # or the given prompt string.
#
# If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
#
@@ -171,6 +175,9 @@ module ActionView
# # that will have a default day of 20.
# date_select("credit_card", "bill_due", :default => { :day => 20 })
#
+ # # Generates a date select with custom prompts
+ # date_select("post", "written_on", :prompt => { :day => 'Select day', :month => 'Select month', :year => 'Select year' })
+ #
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
@@ -210,6 +217,11 @@ module ActionView
# # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45.
# time_select 'game', 'game_time', {:minute_step => 15}
#
+ # # Creates a time select tag with a custom prompt. Use :prompt => true for generic prompts.
+ # time_select("post", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
+ # time_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours
+ # time_select("post", "written_on", :prompt => true) # generic prompts for all
+ #
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
@@ -241,6 +253,11 @@ module ActionView
# # as the written_on attribute.
# datetime_select("post", "written_on", :discard_type => true)
#
+ # # Generates a datetime select with a custom prompt. Use :prompt=>true for generic prompts.
+ # datetime_select("post", "written_on", :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
+ # datetime_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours
+ # datetime_select("post", "written_on", :prompt => true) # generic prompts for all
+ #
# The selects are prepared for multi-parameter assignment to an Active Record object.
def datetime_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
@@ -285,6 +302,11 @@ module ActionView
# # prefixed with 'payday' rather than 'date'
# select_datetime(my_date_time, :prefix => 'payday')
#
+ # # Generates a datetime select with a custom prompt. Use :prompt=>true for generic prompts.
+ # select_datetime(my_date_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
+ # select_datetime(my_date_time, :prompt => {:hour => true}) # generic prompt for hours
+ # select_datetime(my_date_time, :prompt => true) # generic prompts for all
+ #
def select_datetime(datetime = Time.current, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_datetime
end
@@ -321,6 +343,11 @@ module ActionView
# # prefixed with 'payday' rather than 'date'
# select_date(my_date, :prefix => 'payday')
#
+ # # Generates a date select with a custom prompt. Use :prompt=>true for generic prompts.
+ # select_date(my_date, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
+ # select_date(my_date, :prompt => {:hour => true}) # generic prompt for hours
+ # select_date(my_date, :prompt => true) # generic prompts for all
+ #
def select_date(date = Date.current, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_date
end
@@ -352,6 +379,11 @@ module ActionView
# # separated by ':' and includes an input for seconds
# select_time(my_time, :time_separator => ':', :include_seconds => true)
#
+ # # Generates a time select with a custom prompt. Use :prompt=>true for generic prompts.
+ # select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
+ # select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours
+ # select_time(my_time, :prompt => true) # generic prompts for all
+ #
def select_time(datetime = Time.current, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_time
end
@@ -373,6 +405,10 @@ module ActionView
# # that is named 'interval' rather than 'second'
# select_second(my_time, :field_name => 'interval')
#
+ # # Generates a select field for seconds with a custom prompt. Use :prompt=>true for a
+ # # generic prompt.
+ # select_minute(14, :prompt => 'Choose seconds')
+ #
def select_second(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_second
end
@@ -395,6 +431,10 @@ module ActionView
# # that is named 'stride' rather than 'second'
# select_minute(my_time, :field_name => 'stride')
#
+ # # Generates a select field for minutes with a custom prompt. Use :prompt=>true for a
+ # # generic prompt.
+ # select_minute(14, :prompt => 'Choose minutes')
+ #
def select_minute(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_minute
end
@@ -416,6 +456,10 @@ module ActionView
# # that is named 'stride' rather than 'second'
# select_hour(my_time, :field_name => 'stride')
#
+ # # Generates a select field for hours with a custom prompt. Use :prompt => true for a
+ # # generic prompt.
+ # select_hour(13, :prompt =>'Choose hour')
+ #
def select_hour(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_hour
end
@@ -437,6 +481,10 @@ module ActionView
# # that is named 'due' rather than 'day'
# select_day(my_time, :field_name => 'due')
#
+ # # Generates a select field for days with a custom prompt. Use :prompt => true for a
+ # # generic prompt.
+ # select_day(5, :prompt => 'Choose day')
+ #
def select_day(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_day
end
@@ -475,6 +523,10 @@ module ActionView
# # will use keys like "Januar", "Marts."
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
#
+ # # Generates a select field for months with a custom prompt. Use :prompt => true for a
+ # # generic prompt.
+ # select_month(14, :prompt => 'Choose month')
+ #
def select_month(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_month
end
@@ -502,6 +554,10 @@ module ActionView
# # has ascending year values
# select_year(2006, :start_year => 2000, :end_year => 2010)
#
+ # # Generates a select field for years with a custom prompt. Use :prompt => true for a
+ # # generic prompt.
+ # select_year(14, :prompt => 'Choose year')
+ #
def select_year(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_year
end
@@ -764,11 +820,30 @@ module ActionView
select_html = "\n"
select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
+ select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
select_html << select_options_as_html.to_s
content_tag(:select, select_html, select_options) + "\n"
end
+ # Builds a prompt option tag with supplied options or from default options
+ # prompt_option_tag(:month, :prompt => 'Select month')
+ # => "<option value="">Select month</option>"
+ def prompt_option_tag(type, options)
+ default_options = {:year => false, :month => false, :day => false, :hour => false, :minute => false, :second => false}
+
+ case options
+ when Hash
+ prompt = default_options.merge(options)[type.to_sym]
+ when String
+ prompt = options
+ else
+ prompt = I18n.translate(('datetime.prompts.' + type.to_s).to_sym, :locale => @options[:locale])
+ end
+
+ prompt ? content_tag(:option, prompt, :value => '') : ''
+ end
+
# Builds hidden input tag for date part and value
# build_hidden(:year, 2008)
# => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />"
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 621e2946b5..a85751c657 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -737,9 +737,13 @@ module ActionView
(field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
src = <<-end_src
- def #{selector}(method, options = {})
- @template.send(#{selector.inspect}, @object_name, method, objectify_options(options))
- end
+ def #{selector}(method, options = {}) # def text_field(method, options = {})
+ @template.send( # @template.send(
+ #{selector.inspect}, # "text_field",
+ @object_name, # @object_name,
+ method, # method,
+ objectify_options(options)) # objectify_options(options))
+ end # end
end_src
class_eval src, __FILE__, __LINE__
end
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index 7fab3102e7..18a209dcea 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -531,11 +531,6 @@ module ActionView
# is shorthand for
# :with => "'name=' + value"
# This essentially just changes the key of the parameter.
- # <tt>:on</tt>:: Specifies which event handler to observe. By default,
- # it's set to "changed" for text fields and areas and
- # "click" for radio buttons and checkboxes. With this,
- # you can specify it instead to be "blur" or "focus" or
- # any other event.
#
# Additionally, you may specify any of the options documented in the
# <em>Common options</em> section at the top of this document.
@@ -548,11 +543,6 @@ module ActionView
# :url => 'http://example.com/books/edit/1',
# :with => 'title'
#
- # # Sends params: {:book_title => 'Title of the book'} when the focus leaves
- # # the input field.
- # observe_field 'book_title',
- # :url => 'http://example.com/books/edit/1',
- # :on => 'blur'
#
def observe_field(field_id, options = {})
if options[:frequency] && options[:frequency] > 0
@@ -1094,7 +1084,6 @@ module ActionView
javascript << "#{options[:frequency]}, " if options[:frequency]
javascript << "function(element, value) {"
javascript << "#{callback}}"
- javascript << ", '#{options[:on]}'" if options[:on]
javascript << ")"
javascript_tag(javascript)
end
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index 9542b035aa..a880fd83ef 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -80,6 +80,13 @@
over_x_years:
one: "over 1 year"
other: "over {{count}} years"
+ prompts:
+ year: "Year"
+ month: "Month"
+ day: "Day"
+ hour: "Hour"
+ minute: "Minute"
+ second: "Seconds"
activerecord:
errors:
diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb
index bbc995a340..59e82b98a4 100644
--- a/actionpack/lib/action_view/partials.rb
+++ b/actionpack/lib/action_view/partials.rb
@@ -228,7 +228,7 @@ module ActionView
path = "_#{partial_path}"
end
- _pick_template(path)
+ self.view_paths.find_template(path, self.template_format)
end
memoize :_pick_partial_template
end
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index 623b9ff6b0..b030156889 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -2,13 +2,6 @@ module ActionView #:nodoc:
class PathSet < Array #:nodoc:
def self.type_cast(obj)
if obj.is_a?(String)
- if Base.warn_cache_misses && defined?(Rails) && Rails.initialized?
- Base.logger.debug "[PERFORMANCE] Processing view path during a " +
- "request. This an expense disk operation that should be done at " +
- "boot. You can manually process this view path with " +
- "ActionView::Base.process_view_paths(#{obj.inspect}) and set it " +
- "as your view path"
- end
Path.new(obj)
else
obj
@@ -92,7 +85,7 @@ module ActionView #:nodoc:
else
Dir.glob("#{@path}/#{path}*").each do |file|
template = create_template(file)
- if path == template.path_without_extension || path == template.path
+ if template.accessible_paths.include?(path)
return template
end
end
@@ -115,8 +108,9 @@ module ActionView #:nodoc:
templates_in_path do |template|
template.load!
- @paths[template.path] = template
- @paths[template.path_without_extension] ||= template
+ template.accessible_paths.each do |path|
+ @paths[path] = template
+ end
end
@paths.freeze
@@ -143,28 +137,19 @@ module ActionView #:nodoc:
each { |path| path.reload! }
end
- def [](template_path)
- each do |path|
- if template = path[template_path]
- return template
- end
- end
- nil
- end
+ def find_template(original_template_path, format = nil)
+ return original_template_path if original_template_path.respond_to?(:render)
+ template_path = original_template_path.sub(/^\//, '')
- def find_template(path, *formats)
- if formats && formats.first == :all
- formats = Mime::EXTENSION_LOOKUP.values.map(&:to_sym)
- end
- formats.each do |format|
- if template = self["#{path}.#{format}"]
+ each do |load_path|
+ if format && (template = load_path["#{template_path}.#{format}"])
+ return template
+ elsif template = load_path[template_path]
return template
end
end
- if template = self[path]
- return template
- end
- nil
+
+ Template.new(original_template_path, self)
end
end
end
diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb
index 7c0e62f1d7..d8e72f1179 100644
--- a/actionpack/lib/action_view/renderable.rb
+++ b/actionpack/lib/action_view/renderable.rb
@@ -4,10 +4,6 @@ module ActionView
module Renderable #:nodoc:
extend ActiveSupport::Memoizable
- def self.included(base)
- @@mutex = Mutex.new
- end
-
def filename
'compiled-template'
end
@@ -22,6 +18,11 @@ module ActionView
end
memoize :compiled_source
+ def method_name_without_locals
+ ['_run', extension, method_segment].compact.join('_')
+ end
+ memoize :method_name_without_locals
+
def render(view, local_assigns = {})
compile(local_assigns)
@@ -46,9 +47,12 @@ module ActionView
def method_name(local_assigns)
if local_assigns && local_assigns.any?
- local_assigns_keys = "locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}"
+ method_name = method_name_without_locals.dup
+ method_name << "_locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}"
+ else
+ method_name = method_name_without_locals
end
- ['_run', extension, method_segment, local_assigns_keys].compact.join('_').to_sym
+ method_name.to_sym
end
private
@@ -56,10 +60,8 @@ module ActionView
def compile(local_assigns)
render_symbol = method_name(local_assigns)
- @@mutex.synchronize do
- if recompile?(render_symbol)
- compile!(render_symbol, local_assigns)
- end
+ if recompile?(render_symbol)
+ compile!(render_symbol, local_assigns)
end
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 93748638c3..5b384d0e4d 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -4,6 +4,17 @@ module ActionView #:nodoc:
extend ActiveSupport::Memoizable
include Renderable
+ # Templates that are exempt from layouts
+ @@exempt_from_layout = Set.new([/\.rjs$/])
+
+ # Don't render layouts for templates with the given extensions.
+ def self.exempt_from_layout(*extensions)
+ regexps = extensions.collect do |extension|
+ extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
+ end
+ @@exempt_from_layout.merge(regexps)
+ end
+
attr_accessor :filename, :load_path, :base_path, :name, :format, :extension
delegate :to_s, :to => :path
@@ -17,6 +28,18 @@ module ActionView #:nodoc:
extend RenderablePartial if @name =~ /^_/
end
+ def accessible_paths
+ paths = []
+ paths << path
+ paths << path_without_extension
+ if multipart?
+ formats = format.split(".")
+ paths << "#{path_without_format_and_extension}.#{formats.first}"
+ paths << "#{path_without_format_and_extension}.#{formats.second}"
+ end
+ paths
+ end
+
def format_and_extension
(extensions = [format, extension].compact.join(".")).blank? ? nil : extensions
end
@@ -57,6 +80,10 @@ module ActionView #:nodoc:
end
memoize :relative_path
+ def exempt_from_layout?
+ @@exempt_from_layout.any? { |exempted| path =~ exempted }
+ end
+
def mtime
File.mtime(filename)
end
@@ -94,6 +121,7 @@ module ActionView #:nodoc:
def load!
@loaded = true
+ compile({})
freeze
end
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb
index ed8c4427c9..99c57c0c91 100644
--- a/actionpack/test/controller/assert_select_test.rb
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -248,6 +248,14 @@ class AssertSelectTest < ActionController::TestCase
end
end
+ def test_assert_select_rjs_for_positioned_insert_should_fail_when_mixing_arguments
+ render_rjs do |page|
+ page.insert_html :top, "test1", "<div id=\"1\">foo</div>"
+ page.insert_html :bottom, "test2", "<div id=\"2\">foo</div>"
+ end
+ assert_raises(Assertion) {assert_select_rjs :insert, :top, "test2"}
+ end
+
#
# Test css_select.
#
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index e24bb00bc7..7f8e47ba58 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -121,8 +121,7 @@ class PageCachingTest < ActionController::TestCase
[:get, :post, :put, :delete].each do |method|
unless method == :get and status == :ok
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
- @request.env['REQUEST_METHOD'] = method.to_s.upcase
- process status
+ send(method, status)
assert_response status
assert_page_not_cached status, "#{method} with #{status} status shouldn't have been cached"
end
diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb
index 4b969519c6..3ddc5768a9 100644
--- a/actionpack/test/controller/cookie_test.rb
+++ b/actionpack/test/controller/cookie_test.rb
@@ -7,15 +7,15 @@ class CookieTest < Test::Unit::TestCase
end
def authenticate_for_fourteen_days
- cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) }
+ cookies["user_name"] = { "value" => "david", "expires" => Time.utc(2005, 10, 10,5) }
end
def authenticate_for_fourteen_days_with_symbols
- cookies[:user_name] = { :value => "david", :expires => Time.local(2005, 10, 10) }
+ cookies[:user_name] = { :value => "david", :expires => Time.utc(2005, 10, 10,5) }
end
def set_multiple_cookies
- cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) }
+ cookies["user_name"] = { "value" => "david", "expires" => Time.utc(2005, 10, 10,5) }
cookies["login"] = "XJ-122"
end
@@ -52,33 +52,33 @@ class CookieTest < Test::Unit::TestCase
def test_setting_cookie
get :authenticate
assert_equal ["user_name=david; path=/"], @response.headers["Set-Cookie"]
- assert_equal({"user_name" => ["david"]}, @response.cookies)
+ assert_equal({"user_name" => "david"}, @response.cookies)
end
def test_setting_cookie_for_fourteen_days
get :authenticate_for_fourteen_days
- assert_equal ["user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 GMT"], @response.headers["Set-Cookie"]
- assert_equal({"user_name" => ["david"]}, @response.cookies)
+ assert_equal ["user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT"], @response.headers["Set-Cookie"]
+ assert_equal({"user_name" => "david"}, @response.cookies)
end
def test_setting_cookie_for_fourteen_days_with_symbols
get :authenticate_for_fourteen_days_with_symbols
- assert_equal ["user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 GMT"], @response.headers["Set-Cookie"]
- assert_equal({"user_name" => ["david"]}, @response.cookies)
+ assert_equal ["user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT"], @response.headers["Set-Cookie"]
+ assert_equal({"user_name" => "david"}, @response.cookies)
end
def test_setting_cookie_with_http_only
get :authenticate_with_http_only
assert_equal ["user_name=david; path=/; HttpOnly"], @response.headers["Set-Cookie"]
- assert_equal({"user_name" => ["david"]}, @response.cookies)
+ assert_equal({"user_name" => "david"}, @response.cookies)
end
def test_multiple_cookies
get :set_multiple_cookies
assert_equal 2, @response.cookies.size
- assert_equal "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 GMT", @response.headers["Set-Cookie"][0]
+ assert_equal "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT", @response.headers["Set-Cookie"][0]
assert_equal "login=XJ-122; path=/", @response.headers["Set-Cookie"][1]
- assert_equal({"login" => ["XJ-122"], "user_name" => ["david"]}, @response.cookies)
+ assert_equal({"login" => "XJ-122", "user_name" => "david"}, @response.cookies)
end
def test_setting_test_cookie
@@ -87,12 +87,12 @@ class CookieTest < Test::Unit::TestCase
def test_expiring_cookie
get :logout
- assert_equal ["user_name=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"], @response.headers["Set-Cookie"]
- assert_equal({"user_name" => []}, @response.cookies)
+ assert_equal ["user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"], @response.headers["Set-Cookie"]
+ assert_equal({"user_name" => nil}, @response.cookies)
end
def test_cookiejar_accessor
- @request.cookies["user_name"] = CGI::Cookie.new("name" => "user_name", "value" => "david", "expires" => Time.local(2025, 10, 10))
+ @request.cookies["user_name"] = "david"
@controller.request = @request
jar = ActionController::CookieJar.new(@controller)
assert_equal "david", jar["user_name"]
@@ -100,52 +100,14 @@ class CookieTest < Test::Unit::TestCase
end
def test_cookiejar_accessor_with_array_value
- a = %w{1 2 3}
- @request.cookies["pages"] = CGI::Cookie.new("name" => "pages", "value" => a, "expires" => Time.local(2025, 10, 10))
+ @request.cookies["pages"] = %w{1 2 3}
@controller.request = @request
jar = ActionController::CookieJar.new(@controller)
- assert_equal a, jar["pages"]
+ assert_equal %w{1 2 3}, jar["pages"]
end
def test_delete_cookie_with_path
get :delete_cookie_with_path
- assert_equal ["user_name=; path=/beaten; expires=Thu, 01 Jan 1970 00:00:00 GMT"], @response.headers["Set-Cookie"]
- end
-
- def test_cookie_to_s_simple_values
- assert_equal 'myname=myvalue; path=', CGI::Cookie.new('myname', 'myvalue').to_s
- end
-
- def test_cookie_to_s_hash
- cookie_str = CGI::Cookie.new(
- 'name' => 'myname',
- 'value' => 'myvalue',
- 'domain' => 'mydomain',
- 'path' => 'mypath',
- 'expires' => Time.utc(2007, 10, 20),
- 'secure' => true,
- 'http_only' => true).to_s
- assert_equal 'myname=myvalue; domain=mydomain; path=mypath; expires=Sat, 20 Oct 2007 00:00:00 GMT; secure; HttpOnly', cookie_str
- end
-
- def test_cookie_to_s_hash_default_not_secure_not_http_only
- cookie_str = CGI::Cookie.new(
- 'name' => 'myname',
- 'value' => 'myvalue',
- 'domain' => 'mydomain',
- 'path' => 'mypath',
- 'expires' => Time.utc(2007, 10, 20))
- assert cookie_str !~ /secure/
- assert cookie_str !~ /HttpOnly/
- end
-
- def test_cookies_should_not_be_split_on_ampersand_values
- cookies = CGI::Cookie.parse('return_to=http://rubyonrails.org/search?term=api&scope=all&global=true')
- assert_equal({"return_to" => ["http://rubyonrails.org/search?term=api&scope=all&global=true"]}, cookies)
- end
-
- def test_cookies_should_not_be_split_on_values_with_newlines
- cookies = CGI::Cookie.new("name" => "val", "value" => "this\nis\na\ntest")
- assert cookies.size == 1
+ assert_equal ["user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT"], @response.headers["Set-Cookie"]
end
end
diff --git a/actionpack/test/controller/dispatcher_test.rb b/actionpack/test/controller/dispatcher_test.rb
index fd06b4ea99..da87d26146 100644
--- a/actionpack/test/controller/dispatcher_test.rb
+++ b/actionpack/test/controller/dispatcher_test.rb
@@ -96,9 +96,7 @@ class DispatcherTest < Test::Unit::TestCase
private
def dispatch(cache_classes = true)
- controller = mock()
- controller.stubs(:process).returns([200, {}, 'response'])
- ActionController::Routing::Routes.stubs(:recognize).returns(controller)
+ ActionController::Routing::RouteSet.any_instance.stubs(:call).returns([200, {}, 'response'])
Dispatcher.define_dispatcher_callbacks(cache_classes)
@dispatcher.call({})
end
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
new file mode 100644
index 0000000000..d5c8636a9e
--- /dev/null
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -0,0 +1,73 @@
+require 'abstract_unit'
+
+class HttpDigestAuthenticationTest < Test::Unit::TestCase
+ include ActionController::HttpAuthentication::Digest
+
+ class DummyController
+ attr_accessor :headers, :renders, :request, :response
+
+ def initialize
+ @headers, @renders = {}, []
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ request.session.session_id = "test_session"
+ end
+
+ def render(options)
+ self.renderers << options
+ end
+ end
+
+ def setup
+ @controller = DummyController.new
+ @credentials = {
+ :username => "dhh",
+ :realm => "testrealm@host.com",
+ :nonce => ActionController::HttpAuthentication::Digest.nonce(@controller.request),
+ :qop => "auth",
+ :nc => "00000001",
+ :cnonce => "0a4f113b",
+ :opaque => ActionController::HttpAuthentication::Digest.opaque(@controller.request),
+ :uri => "http://test.host/"
+ }
+ @encoded_credentials = ActionController::HttpAuthentication::Digest.encode_credentials("GET", @credentials, "secret")
+ end
+
+ def test_decode_credentials
+ set_headers
+ assert_equal @credentials, decode_credentials(@controller.request)
+ end
+
+ def test_nonce_format
+ assert_nothing_thrown do
+ validate_nonce(@controller.request, nonce(@controller.request))
+ end
+ end
+
+ def test_authenticate_should_raise_for_nil_password
+ set_headers ActionController::HttpAuthentication::Digest.encode_credentials(:get, @credentials, nil)
+ assert_raise ActionController::HttpAuthentication::Error do
+ authenticate(@controller, @credentials[:realm]) { |user| user == "dhh" && "secret" }
+ end
+ end
+
+ def test_authenticate_should_raise_for_incorrect_password
+ set_headers
+ assert_raise ActionController::HttpAuthentication::Error do
+ authenticate(@controller, @credentials[:realm]) { |user| user == "dhh" && "bad password" }
+ end
+ end
+
+ def test_authenticate_should_not_raise_for_correct_password
+ set_headers
+ assert_nothing_thrown do
+ authenticate(@controller, @credentials[:realm]) { |user| user == "dhh" && "secret" }
+ end
+ end
+
+ private
+ def set_headers(value = @encoded_credentials, name = 'HTTP_AUTHORIZATION', method = "GET")
+ @controller.request.env[name] = value
+ @controller.request.env["REQUEST_METHOD"] = method
+ end
+end
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index c28050fe0d..53cebf768e 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -8,7 +8,25 @@ class SessionTest < Test::Unit::TestCase
}
def setup
+ @credentials = {
+ :username => "username",
+ :realm => "MyApp",
+ :nonce => ActionController::HttpAuthentication::Digest.nonce("session_id"),
+ :qop => "auth",
+ :nc => "00000001",
+ :cnonce => "0a4f113b",
+ :opaque => ActionController::HttpAuthentication::Digest.opaque("session_id"),
+ :uri => "/index"
+ }
+
@session = ActionController::Integration::Session.new(StubApp)
+ @session.nonce = @credentials[:nonce]
+ @session.opaque = @credentials[:opaque]
+ @session.realm = @credentials[:realm]
+ end
+
+ def encoded_credentials(method)
+ ActionController::HttpAuthentication::Digest.encode_credentials(method, @credentials, "password")
end
def test_https_bang_works_and_sets_truth_by_default
@@ -132,6 +150,76 @@ class SessionTest < Test::Unit::TestCase
@session.head(path,params,headers)
end
+ def test_get_with_basic
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
+ @session.expects(:process).with(:get,path,params,expected_headers)
+ @session.get_with_basic(path,params,headers,'username','password')
+ end
+
+ def test_post_with_basic
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
+ @session.expects(:process).with(:post,path,params,expected_headers)
+ @session.post_with_basic(path,params,headers,'username','password')
+ end
+
+ def test_put_with_basic
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
+ @session.expects(:process).with(:put,path,params,expected_headers)
+ @session.put_with_basic(path,params,headers,'username','password')
+ end
+
+ def test_delete_with_basic
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
+ @session.expects(:process).with(:delete,path,params,expected_headers)
+ @session.delete_with_basic(path,params,headers,'username','password')
+ end
+
+ def test_head_with_basic
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n")
+ @session.expects(:process).with(:head,path,params,expected_headers)
+ @session.head_with_basic(path,params,headers,'username','password')
+ end
+
+ def test_get_with_digest
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => encoded_credentials(:get))
+ @session.expects(:process).with(:get,path,params,expected_headers)
+ @session.get_with_digest(path,params,headers,'username','password')
+ end
+
+ def test_post_with_digest
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => encoded_credentials(:post))
+ @session.expects(:process).with(:post,path,params,expected_headers)
+ @session.post_with_digest(path,params,headers,'username','password')
+ end
+
+ def test_put_with_digest
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => encoded_credentials(:put))
+ @session.expects(:process).with(:put,path,params,expected_headers)
+ @session.put_with_digest(path,params,headers,'username','password')
+ end
+
+ def test_delete_with_digest
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => encoded_credentials(:delete))
+ @session.expects(:process).with(:delete,path,params,expected_headers)
+ @session.delete_with_digest(path,params,headers,'username','password')
+ end
+
+ def test_head_with_digest
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ expected_headers = headers.merge(:authorization => encoded_credentials(:head))
+ @session.expects(:process).with(:head,path,params,expected_headers)
+ @session.head_with_digest(path,params,headers,'username','password')
+ end
+
def test_xml_http_request_get
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb
index 18c01f755c..c2efe9d00b 100644
--- a/actionpack/test/controller/layout_test.rb
+++ b/actionpack/test/controller/layout_test.rb
@@ -165,15 +165,17 @@ class LayoutStatusIsRenderedTest < ActionController::TestCase
end
end
-class LayoutSymlinkedTest < LayoutTest
- layout "symlinked/symlinked_layout"
-end
-
-class LayoutSymlinkedIsRenderedTest < ActionController::TestCase
- def test_symlinked_layout_is_rendered
- @controller = LayoutSymlinkedTest.new
- get :hello
- assert_response 200
- assert_equal "layouts/symlinked/symlinked_layout", @response.layout
+unless RUBY_PLATFORM =~ /(:?mswin|mingw|bccwin)/
+ class LayoutSymlinkedTest < LayoutTest
+ layout "symlinked/symlinked_layout"
+ end
+
+ class LayoutSymlinkedIsRenderedTest < ActionController::TestCase
+ def test_symlinked_layout_is_rendered
+ @controller = LayoutSymlinkedTest.new
+ get :hello
+ assert_response 200
+ assert_equal "layouts/symlinked/symlinked_layout", @response.layout
+ end
end
end
diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb
index 81d103f0f9..31bff4ae6d 100644
--- a/actionpack/test/controller/rack_test.rb
+++ b/actionpack/test/controller/rack_test.rb
@@ -4,7 +4,7 @@ class BaseRackTest < Test::Unit::TestCase
def setup
@env = {
"HTTP_MAX_FORWARDS" => "10",
- "SERVER_NAME" => "glu.ttono.us:8007",
+ "SERVER_NAME" => "glu.ttono.us",
"FCGI_ROLE" => "RESPONDER",
"AUTH_TYPE" => "Basic",
"HTTP_X_FORWARDED_HOST" => "glu.ttono.us",
@@ -43,10 +43,10 @@ class BaseRackTest < Test::Unit::TestCase
"REDIRECT_STATUS" => "200",
"REQUEST_METHOD" => "GET"
}
- @request = ActionController::RackRequest.new(@env)
+ @request = ActionController::Request.new(@env)
# some Nokia phone browsers omit the space after the semicolon separator.
# some developers have grown accustomed to using comma in cookie values.
- @alt_cookie_fmt_request = ActionController::RackRequest.new(@env.merge({"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"}))
+ @alt_cookie_fmt_request = ActionController::Request.new(@env.merge({"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"}))
end
def default_test; end
@@ -145,7 +145,7 @@ class RackRequestTest < BaseRackTest
assert_equal "kevin", @request.remote_user
assert_equal :get, @request.request_method
assert_equal "/dispatch.fcgi", @request.script_name
- assert_equal "glu.ttono.us:8007", @request.server_name
+ assert_equal "glu.ttono.us", @request.server_name
assert_equal 8007, @request.server_port
assert_equal "HTTP/1.1", @request.server_protocol
assert_equal "lighttpd", @request.server_software
@@ -187,29 +187,6 @@ class RackRequestContentTypeTest < BaseRackTest
end
end
-class RackRequestMethodTest < BaseRackTest
- def test_get
- assert_equal :get, @request.request_method
- end
-
- def test_post
- @request.env['REQUEST_METHOD'] = 'POST'
- assert_equal :post, @request.request_method
- end
-
- def test_put
- set_content_data '_method=put'
-
- assert_equal :put, @request.request_method
- end
-
- def test_delete
- set_content_data '_method=delete'
-
- assert_equal :delete, @request.request_method
- end
-end
-
class RackRequestNeedsRewoundTest < BaseRackTest
def test_body_should_be_rewound
data = 'foo'
@@ -218,7 +195,7 @@ class RackRequestNeedsRewoundTest < BaseRackTest
@env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
# Read the request body by parsing params.
- request = ActionController::RackRequest.new(@env)
+ request = ActionController::Request.new(@env)
request.request_parameters
# Should have rewound the body.
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 8e08a5a8e9..5fd41d8eec 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -21,6 +21,8 @@ class MockLogger
end
class TestController < ActionController::Base
+ protect_from_forgery
+
class LabellingFormBuilder < ActionView::Helpers::FormBuilder
end
@@ -79,6 +81,10 @@ class TestController < ActionController::Base
render :action => "hello_world"
end
+ def render_action_hello_world_as_string
+ render "hello_world"
+ end
+
def render_action_hello_world_with_symbol
render :action => :hello_world
end
@@ -102,6 +108,12 @@ class TestController < ActionController::Base
render :file => path
end
+ def render_file_as_string_with_instance_variables
+ @secret = 'in the sauce'
+ path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb'))
+ render path
+ end
+
def render_file_not_using_full_path
@secret = 'in the sauce'
render :file => 'test/render_file_with_ivar'
@@ -122,6 +134,11 @@ class TestController < ActionController::Base
render :file => path, :locals => {:secret => 'in the sauce'}
end
+ def render_file_as_string_with_locals
+ path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.erb'))
+ render path, :locals => {:secret => 'in the sauce'}
+ end
+
def accessing_request_in_template
render :inline => "Hello: <%= request.host %>"
end
@@ -180,10 +197,6 @@ class TestController < ActionController::Base
render :text => "appended"
end
- def render_invalid_args
- render("test/hello")
- end
-
def render_vanilla_js_hello
render :js => "alert('hello')"
end
@@ -193,6 +206,11 @@ class TestController < ActionController::Base
render :template => "test/hello"
end
+ def render_xml_hello_as_string_template
+ @name = "David"
+ render "test/hello"
+ end
+
def render_xml_with_custom_content_type
render :xml => "<blah/>", :content_type => "application/atomsvc+xml"
end
@@ -282,6 +300,14 @@ class TestController < ActionController::Base
render :action => "hello_world", :layout => "standard"
end
+ def layout_test_with_different_layout_and_string_action
+ render "hello_world", :layout => "standard"
+ end
+
+ def layout_test_with_different_layout_and_symbol_action
+ render :hello_world, :layout => "standard"
+ end
+
def rendering_without_layout
render :action => "hello_world", :layout => false
end
@@ -323,6 +349,10 @@ class TestController < ActionController::Base
render :template => "test/hello_world"
end
+ def render_with_explicit_string_template
+ render "test/hello_world"
+ end
+
def render_with_explicit_template_with_locals
render :template => "test/render_file_with_locals", :locals => { :secret => 'area51' }
end
@@ -645,6 +675,7 @@ class TestController < ActionController::Base
"accessing_params_in_template",
"accessing_params_in_template_with_layout",
"render_with_explicit_template",
+ "render_with_explicit_string_template",
"render_js_with_explicit_template",
"render_js_with_explicit_action_template",
"delete_with_js", "update_page", "update_page_with_instance_variables"
@@ -724,6 +755,12 @@ class RenderTest < ActionController::TestCase
assert_template "test/hello_world"
end
+ def test_render_action_hello_world_as_string
+ get :render_action_hello_world_as_string
+ assert_equal "Hello world!", @response.body
+ assert_template "test/hello_world"
+ end
+
def test_render_action_with_symbol
get :render_action_hello_world_with_symbol
assert_template "test/hello_world"
@@ -749,6 +786,11 @@ class RenderTest < ActionController::TestCase
assert_equal "The secret is in the sauce\n", @response.body
end
+ def test_render_file_as_string_with_instance_variables
+ get :render_file_as_string_with_instance_variables
+ assert_equal "The secret is in the sauce\n", @response.body
+ end
+
def test_render_file_not_using_full_path
get :render_file_not_using_full_path
assert_equal "The secret is in the sauce\n", @response.body
@@ -764,6 +806,11 @@ class RenderTest < ActionController::TestCase
assert_equal "The secret is in the sauce\n", @response.body
end
+ def test_render_file_as_string_with_locals
+ get :render_file_as_string_with_locals
+ assert_equal "The secret is in the sauce\n", @response.body
+ end
+
def test_render_file_from_template
get :render_file_from_template
assert_equal "The secret is in the sauce\n", @response.body
@@ -829,10 +876,6 @@ class RenderTest < ActionController::TestCase
assert_equal 'appended', @response.body
end
- def test_attempt_to_render_with_invalid_arguments
- assert_raises(ActionController::RenderError) { get :render_invalid_args }
- end
-
def test_attempt_to_access_object_method
assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
end
@@ -873,6 +916,12 @@ class RenderTest < ActionController::TestCase
assert_equal "application/xml", @response.content_type
end
+ def test_render_xml_as_string_template
+ get :render_xml_hello_as_string_template
+ assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
+ assert_equal "application/xml", @response.content_type
+ end
+
def test_render_xml_with_default
get :greeting
assert_equal "<p>This is grand!</p>\n", @response.body
@@ -1012,6 +1061,16 @@ class RenderTest < ActionController::TestCase
assert_equal "<html>Hello world!</html>", @response.body
end
+ def test_layout_test_with_different_layout_and_string_action
+ get :layout_test_with_different_layout_and_string_action
+ assert_equal "<html>Hello world!</html>", @response.body
+ end
+
+ def test_layout_test_with_different_layout_and_symbol_action
+ get :layout_test_with_different_layout_and_symbol_action
+ assert_equal "<html>Hello world!</html>", @response.body
+ end
+
def test_rendering_without_layout
get :rendering_without_layout
assert_equal "Hello world!", @response.body
@@ -1058,6 +1117,11 @@ class RenderTest < ActionController::TestCase
assert_response :success
end
+ def test_render_with_explicit_string_template
+ get :render_with_explicit_string_template
+ assert_equal "<html>Hello world!</html>", @response.body
+ end
+
def test_double_render
assert_raises(ActionController::DoubleRenderError) { get :double_render }
end
diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb
index 71da50fab0..349cea268f 100644
--- a/actionpack/test/controller/request_test.rb
+++ b/actionpack/test/controller/request_test.rb
@@ -303,18 +303,16 @@ class RequestTest < ActiveSupport::TestCase
end
def test_allow_method_hacking_on_post
- self.request_method = :post
[:get, :head, :options, :put, :post, :delete].each do |method|
- @request.instance_eval { @parameters = { :_method => method.to_s } ; @request_method = nil }
+ self.request_method = method
@request.request_method(true)
assert_equal(method == :head ? :get : method, @request.method)
end
end
def test_invalid_method_hacking_on_post_raises_exception
- self.request_method = :post
- @request.instance_eval { @parameters = { :_method => :random_method } ; @request_method = nil }
assert_raises(ActionController::UnknownHttpMethod) do
+ self.request_method = :_random_method
@request.request_method(true)
end
end
@@ -426,95 +424,95 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase
def test_query_string
assert_equal(
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
- ActionController::AbstractRequest.parse_query_parameters(@query_string)
+ ActionController::RequestParser.parse_query_parameters(@query_string)
)
end
def test_deep_query_string
expected = {'x' => {'y' => {'z' => '10'}}}
- assert_equal(expected, ActionController::AbstractRequest.parse_query_parameters('x[y][z]=10'))
+ assert_equal(expected, ActionController::RequestParser.parse_query_parameters('x[y][z]=10'))
end
def test_deep_query_string_with_array
- assert_equal({'x' => {'y' => {'z' => ['10']}}}, ActionController::AbstractRequest.parse_query_parameters('x[y][z][]=10'))
- assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, ActionController::AbstractRequest.parse_query_parameters('x[y][z][]=10&x[y][z][]=5'))
+ assert_equal({'x' => {'y' => {'z' => ['10']}}}, ActionController::RequestParser.parse_query_parameters('x[y][z][]=10'))
+ assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, ActionController::RequestParser.parse_query_parameters('x[y][z][]=10&x[y][z][]=5'))
end
def test_deep_query_string_with_array_of_hash
- assert_equal({'x' => {'y' => [{'z' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10'))
- assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=10'))
+ assert_equal({'x' => {'y' => [{'z' => '10'}]}}, ActionController::RequestParser.parse_query_parameters('x[y][][z]=10'))
+ assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][w]=10'))
end
def test_deep_query_string_with_array_of_hashes_with_one_pair
- assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20'))
- assert_equal("10", ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"])
- assert_equal("10", ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z])
+ assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][z]=20'))
+ assert_equal("10", ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"])
+ assert_equal("10", ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z])
end
def test_deep_query_string_with_array_of_hashes_with_multiple_pairs
assert_equal(
{'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}},
- ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b')
+ ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b')
)
end
def test_query_string_with_nil
assert_equal(
{ "action" => "create_customer", "full_name" => ''},
- ActionController::AbstractRequest.parse_query_parameters(@query_string_with_empty)
+ ActionController::RequestParser.parse_query_parameters(@query_string_with_empty)
)
end
def test_query_string_with_array
assert_equal(
{ "action" => "create_customer", "selected" => ["1", "2", "3"]},
- ActionController::AbstractRequest.parse_query_parameters(@query_string_with_array)
+ ActionController::RequestParser.parse_query_parameters(@query_string_with_array)
)
end
def test_query_string_with_amps
assert_equal(
{ "action" => "create_customer", "name" => "Don't & Does"},
- ActionController::AbstractRequest.parse_query_parameters(@query_string_with_amps)
+ ActionController::RequestParser.parse_query_parameters(@query_string_with_amps)
)
end
def test_query_string_with_many_equal
assert_equal(
{ "action" => "create_customer", "full_name" => "abc=def=ghi"},
- ActionController::AbstractRequest.parse_query_parameters(@query_string_with_many_equal)
+ ActionController::RequestParser.parse_query_parameters(@query_string_with_many_equal)
)
end
def test_query_string_without_equal
assert_equal(
{ "action" => nil },
- ActionController::AbstractRequest.parse_query_parameters(@query_string_without_equal)
+ ActionController::RequestParser.parse_query_parameters(@query_string_without_equal)
)
end
def test_query_string_with_empty_key
assert_equal(
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson" },
- ActionController::AbstractRequest.parse_query_parameters(@query_string_with_empty_key)
+ ActionController::RequestParser.parse_query_parameters(@query_string_with_empty_key)
)
end
def test_query_string_with_many_ampersands
assert_equal(
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson"},
- ActionController::AbstractRequest.parse_query_parameters(@query_string_with_many_ampersands)
+ ActionController::RequestParser.parse_query_parameters(@query_string_with_many_ampersands)
)
end
def test_unbalanced_query_string_with_array
assert_equal(
{'location' => ["1", "2"], 'age_group' => ["2"]},
- ActionController::AbstractRequest.parse_query_parameters("location[]=1&location[]=2&age_group[]=2")
+ ActionController::RequestParser.parse_query_parameters("location[]=1&location[]=2&age_group[]=2")
)
assert_equal(
{'location' => ["1", "2"], 'age_group' => ["2"]},
- ActionController::AbstractRequest.parse_request_parameters({'location[]' => ["1", "2"],
+ ActionController::RequestParser.parse_request_parameters({'location[]' => ["1", "2"],
'age_group[]' => ["2"]})
)
end
@@ -527,7 +525,7 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase
expected = { "note" => { "viewers"=>{"viewer"=>[{ "id"=>"1", "type"=>"User"}, {"type"=>"Group", "id"=>"2"} ]} } }
- assert_equal(expected, ActionController::AbstractRequest.parse_request_parameters(query))
+ assert_equal(expected, ActionController::RequestParser.parse_request_parameters(query))
end
def test_parse_params
@@ -566,7 +564,7 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase
}
}
- assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input)
+ assert_equal expected_output, ActionController::RequestParser.parse_request_parameters(input)
end
UploadedStringIO = ActionController::UploadedStringIO
@@ -621,7 +619,7 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase
"text_part" => "abc"
}
- params = ActionController::AbstractRequest.parse_request_parameters(input)
+ params = ActionController::RequestParser.parse_request_parameters(input)
assert_equal expected_output, params
# Lone filenames are preserved.
@@ -652,7 +650,7 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase
"logo" => File.new(File.dirname(__FILE__) + "/rack_test.rb").path,
}
- assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input)
+ assert_equal expected_output, ActionController::RequestParser.parse_request_parameters(input)
end
def test_parse_params_with_array
@@ -660,55 +658,55 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase
expected_output = { "selected" => [ "1", "2", "3" ] }
- assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input)
+ assert_equal expected_output, ActionController::RequestParser.parse_request_parameters(input)
end
def test_parse_params_with_non_alphanumeric_name
input = { "a/b[c]" => %w(d) }
expected = { "a/b" => { "c" => "d" }}
- assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
+ assert_equal expected, ActionController::RequestParser.parse_request_parameters(input)
end
def test_parse_params_with_single_brackets_in_middle
input = { "a/b[c]d" => %w(e) }
expected = { "a/b" => {} }
- assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
+ assert_equal expected, ActionController::RequestParser.parse_request_parameters(input)
end
def test_parse_params_with_separated_brackets
input = { "a/b@[c]d[e]" => %w(f) }
expected = { "a/b@" => { }}
- assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
+ assert_equal expected, ActionController::RequestParser.parse_request_parameters(input)
end
def test_parse_params_with_separated_brackets_and_array
input = { "a/b@[c]d[e][]" => %w(f) }
expected = { "a/b@" => { }}
- assert_equal expected , ActionController::AbstractRequest.parse_request_parameters(input)
+ assert_equal expected , ActionController::RequestParser.parse_request_parameters(input)
end
def test_parse_params_with_unmatched_brackets_and_array
input = { "a/b@[c][d[e][]" => %w(f) }
expected = { "a/b@" => { "c" => { }}}
- assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
+ assert_equal expected, ActionController::RequestParser.parse_request_parameters(input)
end
def test_parse_params_with_nil_key
input = { nil => nil, "test2" => %w(value1) }
expected = { "test2" => "value1" }
- assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
+ assert_equal expected, ActionController::RequestParser.parse_request_parameters(input)
end
def test_parse_params_with_array_prefix_and_hashes
input = { "a[][b][c]" => %w(d) }
expected = {"a" => [{"b" => {"c" => "d"}}]}
- assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
+ assert_equal expected, ActionController::RequestParser.parse_request_parameters(input)
end
def test_parse_params_with_complex_nesting
input = { "a[][b][c][][d][]" => %w(e) }
expected = {"a" => [{"b" => {"c" => [{"d" => ["e"]}]}}]}
- assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
+ assert_equal expected, ActionController::RequestParser.parse_request_parameters(input)
end
end
@@ -770,7 +768,7 @@ class MultipartRequestParameterParsingTest < ActiveSupport::TestCase
# Ensures that parse_multipart_form_parameters works with streams that cannot be rewound
file = File.open(File.join(FIXTURE_PATH, 'large_text_file'), 'rb')
file.expects(:rewind).raises(Errno::ESPIPE)
- params = ActionController::AbstractRequest.parse_multipart_form_parameters(file, 'AaB03x', file.stat.size, {})
+ params = ActionController::RequestParser.parse_multipart_form_parameters(file, 'AaB03x', file.stat.size, {})
assert_not_equal 0, file.pos # file was not rewound after reading
end
end
@@ -809,7 +807,7 @@ class MultipartRequestParameterParsingTest < ActiveSupport::TestCase
private
def parse_multipart(name)
File.open(File.join(FIXTURE_PATH, name), 'rb') do |file|
- params = ActionController::AbstractRequest.parse_multipart_form_parameters(file, 'AaB03x', file.stat.size, {})
+ params = ActionController::RequestParser.parse_multipart_form_parameters(file, 'AaB03x', file.stat.size, {})
assert_equal 0, file.pos # file was rewound after reading
params
end
@@ -856,7 +854,7 @@ class XmlParamsParsingTest < ActiveSupport::TestCase
env = { 'rack.input' => StringIO.new(body),
'CONTENT_TYPE' => 'application/xml',
'CONTENT_LENGTH' => body.size.to_s }
- ActionController::RackRequest.new(env).request_parameters
+ ActionController::Request.new(env).request_parameters
end
end
@@ -866,7 +864,7 @@ class LegacyXmlParamsParsingTest < XmlParamsParsingTest
env = { 'rack.input' => StringIO.new(body),
'HTTP_X_POST_DATA_FORMAT' => 'xml',
'CONTENT_LENGTH' => body.size.to_s }
- ActionController::RackRequest.new(env).request_parameters
+ ActionController::Request.new(env).request_parameters
end
end
@@ -888,6 +886,6 @@ class JsonParamsParsingTest < ActiveSupport::TestCase
env = { 'rack.input' => StringIO.new(body),
'CONTENT_TYPE' => content_type,
'CONTENT_LENGTH' => body.size.to_s }
- ActionController::RackRequest.new(env).request_parameters
+ ActionController::Request.new(env).request_parameters
end
end
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index 63f9827f4a..49aca3a6ee 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -367,7 +367,11 @@ class RescueControllerTest < ActionController::TestCase
end
def test_rescue_dispatcher_exceptions
- RescueController.process_with_exception(@request, @response, ActionController::RoutingError.new("Route not found"))
+ env = @request.env
+ env["actioncontroller.rescue.request"] = @request
+ env["actioncontroller.rescue.response"] = @response
+
+ RescueController.call_with_exception(env, ActionController::RoutingError.new("Route not found"))
assert_equal "no way", @response.body
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index d5b6bd6b2a..b981119e1e 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -706,7 +706,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
port_string = port == 80 ? '' : ":#{port}"
protocol = options.delete(:protocol) || "http"
- host = options.delete(:host) || "named.route.test"
+ host = options.delete(:host) || "test.host"
anchor = "##{options.delete(:anchor)}" if options.key?(:anchor)
path = routes.generate(options)
@@ -715,27 +715,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
def request
- @request ||= MockRequest.new(:host => "named.route.test", :method => :get)
- end
- end
-
- class MockRequest
- attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method
-
- def initialize(values={})
- values.each { |key, value| send("#{key}=", value) }
- if values[:host]
- subdomain, self.domain = values[:host].split(/\./, 2)
- self.subdomains = [subdomain]
- end
- end
-
- def protocol
- "http://"
- end
-
- def host_with_port
- (subdomains * '.') + '.' + domain
+ @request ||= ActionController::TestRequest.new
end
end
@@ -900,7 +880,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_basic_named_route
rs.add_named_route :home, '', :controller => 'content', :action => 'list'
x = setup_for_named_route
- assert_equal("http://named.route.test/",
+ assert_equal("http://test.host/",
x.send(:home_url))
end
@@ -908,7 +888,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
rs.add_named_route :home, '', :controller => 'content', :action => 'list'
x = setup_for_named_route
ActionController::Base.relative_url_root = "/foo"
- assert_equal("http://named.route.test/foo/",
+ assert_equal("http://test.host/foo/",
x.send(:home_url))
assert_equal "/foo/", x.send(:home_path)
ActionController::Base.relative_url_root = nil
@@ -917,14 +897,14 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_named_route_with_option
rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page'
x = setup_for_named_route
- assert_equal("http://named.route.test/page/new%20stuff",
+ assert_equal("http://test.host/page/new%20stuff",
x.send(:page_url, :title => 'new stuff'))
end
def test_named_route_with_default
rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage'
x = setup_for_named_route
- assert_equal("http://named.route.test/page/AboutRails",
+ assert_equal("http://test.host/page/AboutRails",
x.send(:page_url, :title => "AboutRails"))
end
@@ -932,21 +912,21 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_named_route_with_name_prefix
rs.add_named_route :page, 'page', :controller => 'content', :action => 'show_page', :name_prefix => 'my_'
x = setup_for_named_route
- assert_equal("http://named.route.test/page",
+ assert_equal("http://test.host/page",
x.send(:my_page_url))
end
def test_named_route_with_path_prefix
rs.add_named_route :page, 'page', :controller => 'content', :action => 'show_page', :path_prefix => 'my'
x = setup_for_named_route
- assert_equal("http://named.route.test/my/page",
+ assert_equal("http://test.host/my/page",
x.send(:page_url))
end
def test_named_route_with_nested_controller
rs.add_named_route :users, 'admin/user', :controller => 'admin/user', :action => 'index'
x = setup_for_named_route
- assert_equal("http://named.route.test/admin/user",
+ assert_equal("http://test.host/admin/user",
x.send(:users_url))
end
@@ -985,7 +965,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
map.root :controller => "hello"
end
x = setup_for_named_route
- assert_equal("http://named.route.test/", x.send(:root_url))
+ assert_equal("http://test.host/", x.send(:root_url))
assert_equal("/", x.send(:root_path))
end
@@ -1001,7 +981,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
# x.send(:article_url, :title => 'hi')
# )
assert_equal(
- "http://named.route.test/page/2005/6/10/hi",
+ "http://test.host/page/2005/6/10/hi",
x.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6)
)
end
@@ -1202,7 +1182,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
assert_equal '/test', rs.generate(:controller => 'post', :action => 'show', :year => nil)
x = setup_for_named_route
- assert_equal("http://named.route.test/test",
+ assert_equal("http://test.host/test",
x.send(:blog_url))
end
@@ -1249,7 +1229,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
assert_equal '/', rs.generate(:controller => 'content')
x = setup_for_named_route
- assert_equal("http://named.route.test/",
+ assert_equal("http://test.host/",
x.send(:home_url))
end
@@ -1591,7 +1571,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
def request
- @request ||= MockRequest.new(:host => "named.routes.test", :method => :get)
+ @request ||= ActionController::TestRequest.new
end
def test_generate_extras
@@ -1692,13 +1672,13 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_named_route_url_method
controller = setup_named_route_test
- assert_equal "http://named.route.test/people/5", controller.send(:show_url, :id => 5)
+ assert_equal "http://test.host/people/5", controller.send(:show_url, :id => 5)
assert_equal "/people/5", controller.send(:show_path, :id => 5)
- assert_equal "http://named.route.test/people", controller.send(:index_url)
+ assert_equal "http://test.host/people", controller.send(:index_url)
assert_equal "/people", controller.send(:index_path)
- assert_equal "http://named.route.test/admin/users", controller.send(:users_url)
+ assert_equal "http://test.host/admin/users", controller.send(:users_url)
assert_equal '/admin/users', controller.send(:users_path)
assert_equal '/admin/users', set.generate(controller.send(:hash_for_users_url), {:controller => 'users', :action => 'index'})
end
@@ -1706,28 +1686,28 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_named_route_url_method_with_anchor
controller = setup_named_route_test
- assert_equal "http://named.route.test/people/5#location", controller.send(:show_url, :id => 5, :anchor => 'location')
+ assert_equal "http://test.host/people/5#location", controller.send(:show_url, :id => 5, :anchor => 'location')
assert_equal "/people/5#location", controller.send(:show_path, :id => 5, :anchor => 'location')
- assert_equal "http://named.route.test/people#location", controller.send(:index_url, :anchor => 'location')
+ assert_equal "http://test.host/people#location", controller.send(:index_url, :anchor => 'location')
assert_equal "/people#location", controller.send(:index_path, :anchor => 'location')
- assert_equal "http://named.route.test/admin/users#location", controller.send(:users_url, :anchor => 'location')
+ assert_equal "http://test.host/admin/users#location", controller.send(:users_url, :anchor => 'location')
assert_equal '/admin/users#location', controller.send(:users_path, :anchor => 'location')
- assert_equal "http://named.route.test/people/go/7/hello/joe/5#location",
+ assert_equal "http://test.host/people/go/7/hello/joe/5#location",
controller.send(:multi_url, 7, "hello", 5, :anchor => 'location')
- assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar#location",
+ assert_equal "http://test.host/people/go/7/hello/joe/5?baz=bar#location",
controller.send(:multi_url, 7, "hello", 5, :baz => "bar", :anchor => 'location')
- assert_equal "http://named.route.test/people?baz=bar#location",
+ assert_equal "http://test.host/people?baz=bar#location",
controller.send(:index_url, :baz => "bar", :anchor => 'location')
end
def test_named_route_url_method_with_port
controller = setup_named_route_test
- assert_equal "http://named.route.test:8080/people/5", controller.send(:show_url, 5, :port=>8080)
+ assert_equal "http://test.host:8080/people/5", controller.send(:show_url, 5, :port=>8080)
end
def test_named_route_url_method_with_host
@@ -1737,30 +1717,30 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_named_route_url_method_with_protocol
controller = setup_named_route_test
- assert_equal "https://named.route.test/people/5", controller.send(:show_url, 5, :protocol => "https")
+ assert_equal "https://test.host/people/5", controller.send(:show_url, 5, :protocol => "https")
end
def test_named_route_url_method_with_ordered_parameters
controller = setup_named_route_test
- assert_equal "http://named.route.test/people/go/7/hello/joe/5",
+ assert_equal "http://test.host/people/go/7/hello/joe/5",
controller.send(:multi_url, 7, "hello", 5)
end
def test_named_route_url_method_with_ordered_parameters_and_hash
controller = setup_named_route_test
- assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar",
+ assert_equal "http://test.host/people/go/7/hello/joe/5?baz=bar",
controller.send(:multi_url, 7, "hello", 5, :baz => "bar")
end
def test_named_route_url_method_with_ordered_parameters_and_empty_hash
controller = setup_named_route_test
- assert_equal "http://named.route.test/people/go/7/hello/joe/5",
+ assert_equal "http://test.host/people/go/7/hello/joe/5",
controller.send(:multi_url, 7, "hello", 5, {})
end
def test_named_route_url_method_with_no_positional_arguments
controller = setup_named_route_test
- assert_equal "http://named.route.test/people?baz=bar",
+ assert_equal "http://test.host/people?baz=bar",
controller.send(:index_url, :baz => "bar")
end
@@ -1896,49 +1876,54 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/people"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("index", request.path_parameters[:action])
+ request.recycle!
- request.method = :post
+ request.env["REQUEST_METHOD"] = "POST"
assert_nothing_raised { set.recognize(request) }
assert_equal("create", request.path_parameters[:action])
+ request.recycle!
- request.method = :put
+ request.env["REQUEST_METHOD"] = "PUT"
assert_nothing_raised { set.recognize(request) }
assert_equal("update", request.path_parameters[:action])
+ request.recycle!
- begin
- request.method = :bacon
+ assert_raises(ActionController::UnknownHttpMethod) {
+ request.env["REQUEST_METHOD"] = "BACON"
set.recognize(request)
- flunk 'Should have raised NotImplemented'
- rescue ActionController::NotImplemented => e
- assert_equal [:get, :post, :put, :delete], e.allowed_methods
- end
+ }
+ request.recycle!
request.path = "/people/5"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("show", request.path_parameters[:action])
assert_equal("5", request.path_parameters[:id])
+ request.recycle!
- request.method = :put
+ request.env["REQUEST_METHOD"] = "PUT"
assert_nothing_raised { set.recognize(request) }
assert_equal("update", request.path_parameters[:action])
assert_equal("5", request.path_parameters[:id])
+ request.recycle!
- request.method = :delete
+ request.env["REQUEST_METHOD"] = "DELETE"
assert_nothing_raised { set.recognize(request) }
assert_equal("destroy", request.path_parameters[:action])
assert_equal("5", request.path_parameters[:id])
+ request.recycle!
begin
- request.method = :post
+ request.env["REQUEST_METHOD"] = "POST"
set.recognize(request)
flunk 'Should have raised MethodNotAllowed'
rescue ActionController::MethodNotAllowed => e
assert_equal [:get, :put, :delete], e.allowed_methods
end
+ request.recycle!
ensure
Object.send(:remove_const, :PeopleController)
@@ -1954,13 +1939,13 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/people"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("people", request.path_parameters[:controller])
assert_equal("index", request.path_parameters[:action])
request.path = "/"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("people", request.path_parameters[:controller])
assert_equal("index", request.path_parameters[:action])
@@ -1978,7 +1963,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/articles/2005/11/05/a-very-interesting-article"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("permalink", request.path_parameters[:action])
assert_equal("2005", request.path_parameters[:year])
@@ -2015,17 +2000,19 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/people/5"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("show", request.path_parameters[:action])
assert_equal("5", request.path_parameters[:id])
+ request.recycle!
- request.method = :put
+ request.env["REQUEST_METHOD"] = "PUT"
assert_nothing_raised { set.recognize(request) }
assert_equal("update", request.path_parameters[:action])
+ request.recycle!
request.path = "/people/5.png"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("show", request.path_parameters[:action])
assert_equal("5", request.path_parameters[:id])
@@ -2050,7 +2037,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
set.draw { |map| map.root :controller => "people" }
request.path = ""
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("people", request.path_parameters[:controller])
assert_equal("index", request.path_parameters[:action])
@@ -2070,7 +2057,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/api/inventory"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("api/products", request.path_parameters[:controller])
assert_equal("inventory", request.path_parameters[:action])
@@ -2090,7 +2077,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/api"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("api/products", request.path_parameters[:controller])
assert_equal("index", request.path_parameters[:action])
@@ -2110,7 +2097,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/prefix/inventory"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("api/products", request.path_parameters[:controller])
assert_equal("inventory", request.path_parameters[:action])
@@ -2246,7 +2233,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
request.path = "/projects/1/milestones"
- request.method = :get
+ request.env["REQUEST_METHOD"] = "GET"
assert_nothing_raised { set.recognize(request) }
assert_equal("milestones", request.path_parameters[:controller])
assert_equal("index", request.path_parameters[:action])
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index c4349bfc7f..1b7486ad34 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -119,6 +119,31 @@ class SendFileTest < Test::Unit::TestCase
assert_equal 'private', h['Cache-Control']
end
+ def test_send_file_headers_with_mime_lookup_with_symbol
+ options = {
+ :length => 1,
+ :type => :png
+ }
+
+ @controller.headers = {}
+ @controller.send(:send_file_headers!, options)
+
+ headers = @controller.headers
+
+ assert_equal 'image/png', headers['Content-Type']
+ end
+
+
+ def test_send_file_headers_with_bad_symbol
+ options = {
+ :length => 1,
+ :type => :this_type_is_not_registered
+ }
+
+ @controller.headers = {}
+ assert_raises(ArgumentError){ @controller.send(:send_file_headers!, options) }
+ end
+
%w(file data).each do |method|
define_method "test_send_#{method}_status" do
@controller.options = { :stream => false, :status => 500 }
diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb
index 69aec59dc0..d349c18d1d 100644
--- a/actionpack/test/controller/session/cookie_store_test.rb
+++ b/actionpack/test/controller/session/cookie_store_test.rb
@@ -25,7 +25,7 @@ class CookieStoreTest < ActionController::IntegrationTest
def set_session_value
session[:foo] = "bar"
- render :text => Marshal.dump(session.to_hash)
+ render :text => Verifier.generate(session.to_hash)
end
def get_session_value
@@ -94,8 +94,7 @@ class CookieStoreTest < ActionController::IntegrationTest
with_test_route_set do
get '/set_session_value'
assert_response :success
- session_payload = Verifier.generate(Marshal.load(response.body))
- assert_equal ["_myapp_session=#{session_payload}; path=/"],
+ assert_equal ["_myapp_session=#{response.body}; path=/"],
headers['Set-Cookie']
end
end
@@ -148,8 +147,8 @@ class CookieStoreTest < ActionController::IntegrationTest
with_test_route_set do
get '/set_session_value'
assert_response :success
- session_payload = Verifier.generate(Marshal.load(response.body))
- assert_equal ["_myapp_session=#{session_payload}; path=/"],
+ session_payload = response.body
+ assert_equal ["_myapp_session=#{response.body}; path=/"],
headers['Set-Cookie']
get '/call_reset_session'
diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb
index dc9616db3b..fac30da128 100644
--- a/actionpack/test/template/date_helper_i18n_test.rb
+++ b/actionpack/test/template/date_helper_i18n_test.rb
@@ -78,6 +78,8 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
uses_mocha 'date_helper_select_tags_i18n_tests' do
def setup
+ @prompt_defaults = {:year => 'Year', :month => 'Month', :day => 'Day', :hour => 'Hour', :minute => 'Minute', :second => 'Seconds'}
+
I18n.stubs(:translate).with(:'date.month_names', :locale => 'en').returns Date::MONTHNAMES
end
@@ -98,6 +100,15 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
select_month(8, :locale => 'en', :use_short_month => true)
end
+ def test_date_or_time_select_translates_prompts
+ @prompt_defaults.each do |key, prompt|
+ I18n.expects(:translate).with(('datetime.prompts.' + key.to_s).to_sym, :locale => 'en').returns prompt
+ end
+
+ I18n.expects(:translate).with(:'date.order', :locale => 'en').returns [:year, :month, :day]
+ datetime_select('post', 'updated_at', :locale => 'en', :include_seconds => true, :prompt => true)
+ end
+
# date_or_time_select
def test_date_or_time_select_given_an_order_options_does_not_translate_order
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 49ba140c23..6ec01b7a8f 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -153,6 +153,22 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_day(16, {}, :class => 'selector')
end
+ def test_select_day_with_default_prompt
+ expected = %(<select id="date_day" name="date[day]">\n)
+ expected << %(<option value="">Day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_day(16, :prompt => true)
+ end
+
+ def test_select_day_with_custom_prompt
+ expected = %(<select id="date_day" name="date[day]">\n)
+ expected << %(<option value="">Choose day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_day(16, :prompt => 'Choose day')
+ end
+
def test_select_month
expected = %(<select id="date_month" name="date[month]">\n)
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
@@ -276,6 +292,22 @@ class DateHelperTest < ActionView::TestCase
#assert result.include?('<option value="1">January')
end
+ def test_select_month_with_default_prompt
+ expected = %(<select id="date_month" name="date[month]">\n)
+ expected << %(<option value="">Month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_month(8, :prompt => true)
+ end
+
+ def test_select_month_with_custom_prompt
+ expected = %(<select id="date_month" name="date[month]">\n)
+ expected << %(<option value="">Choose month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_month(8, :prompt => 'Choose month')
+ end
+
def test_select_year
expected = %(<select id="date_year" name="date[year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
@@ -344,6 +376,22 @@ class DateHelperTest < ActionView::TestCase
#assert result.include?('<option value="2003"')
end
+ def test_select_year_with_default_prompt
+ expected = %(<select id="date_year" name="date[year]">\n)
+ expected << %(<option value="">Year</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_year(nil, :start_year => 2003, :end_year => 2005, :prompt => true)
+ end
+
+ def test_select_year_with_custom_prompt
+ expected = %(<select id="date_year" name="date[year]">\n)
+ expected << %(<option value="">Choose year</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_year(nil, :start_year => 2003, :end_year => 2005, :prompt => 'Choose year')
+ end
+
def test_select_hour
expected = %(<select id="date_hour" name="date[hour]">\n)
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
@@ -392,6 +440,22 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector', :accesskey => 'M')
end
+ def test_select_hour_with_default_prompt
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="">Hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true)
+ end
+
+ def test_select_hour_with_custom_prompt
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="">Choose hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => 'Choose hour')
+ end
+
def test_select_minute
expected = %(<select id="date_minute" name="date[minute]">\n)
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
@@ -470,6 +534,22 @@ class DateHelperTest < ActionView::TestCase
#assert result.include?('<option value="00">00')
end
+ def test_select_minute_with_default_prompt
+ expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value="">Minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true)
+ end
+
+ def test_select_minute_with_custom_prompt
+ expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => 'Choose minute')
+ end
+
def test_select_second
expected = %(<select id="date_second" name="date[second]">\n)
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
@@ -524,6 +604,22 @@ class DateHelperTest < ActionView::TestCase
#assert result.include?('<option value="00">00')
end
+ def test_select_second_with_default_prompt
+ expected = %(<select id="date_second" name="date[second]">\n)
+ expected << %(<option value="">Seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true)
+ end
+
+ def test_select_second_with_custom_prompt
+ expected = %(<select id="date_second" name="date[second]">\n)
+ expected << %(<option value="">Choose seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => 'Choose seconds')
+ end
+
def test_select_date
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
@@ -914,6 +1010,57 @@ class DateHelperTest < ActionView::TestCase
assert_nothing_raised { select_datetime(Date.today) }
end
+ def test_select_datetime_with_default_prompt
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="">Year</option>\n<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="">Month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="">Day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_hour" name="date[first][hour]">\n)
+ expected << %(<option value="">Hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_minute" name="date[first][minute]">\n)
+ expected << %(<option value="">Minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005,
+ :prefix => "date[first]", :prompt => true)
+ end
+
+ def test_select_datetime_with_custom_prompt
+
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="">Choose year</option>\n<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="">Choose month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="">Choose day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_hour" name="date[first][hour]">\n)
+ expected << %(<option value="">Choose hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_minute" name="date[first][minute]">\n)
+ expected << %(<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]",
+ :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year', :hour => 'Choose hour', :minute => 'Choose minute'})
+ end
+
def test_select_time
expected = %(<select id="date_hour" name="date[hour]">\n)
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
@@ -995,6 +1142,40 @@ class DateHelperTest < ActionView::TestCase
assert_nothing_raised { select_time(Date.today) }
end
+ def test_select_time_with_default_prompt
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="">Hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value="">Minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_second" name="date[second]">\n)
+ expected << %(<option value="">Seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true, :prompt => true)
+ end
+
+ def test_select_time_with_custom_prompt
+
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="">Choose hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_second" name="date[second]">\n)
+ expected << %(<option value="">Choose seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true, :include_seconds => true,
+ :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
+ end
+
def test_date_select
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
@@ -1277,6 +1458,46 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on", { :date_separator => " / " })
end
+ def test_date_select_with_default_prompt
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << %{<option value="">Year</option>\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ expected << %{<option value="">Month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected << %{<option value="">Day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+
+ expected << "</select>\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", :prompt => true)
+ end
+
+ def test_date_select_with_custom_prompt
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << %{<option value="">Choose year</option>\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ expected << %{<option value="">Choose month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected << %{<option value="">Choose day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+
+ expected << "</select>\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", :prompt => {:year => 'Choose year', :month => 'Choose month', :day => 'Choose day'})
+ end
+
def test_time_select
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
@@ -1403,6 +1624,48 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, time_select("post", "written_on", { :time_separator => " - ", :include_seconds => true })
end
+ def test_time_select_with_default_prompt
+ @post = Post.new
+ @post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
+ expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
+
+ expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n)
+ expected << %(<option value="">Hour</option>\n)
+ 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n)
+ expected << %(<option value="">Minute</option>\n)
+ 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_dom_equal expected, time_select("post", "written_on", :prompt => true)
+ end
+
+ def test_time_select_with_custom_prompt
+ @post = Post.new
+ @post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
+ expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
+
+ expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n)
+ expected << %(<option value="">Choose hour</option>\n)
+ 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n)
+ expected << %(<option value="">Choose minute</option>\n)
+ 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_dom_equal expected, time_select("post", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute'})
+ end
+
def test_datetime_select
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
@@ -1526,6 +1789,64 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, datetime_select("post", "updated_at", { :date_separator => " / ", :datetime_separator => " , ", :time_separator => " - ", :include_seconds => true })
end
+ def test_datetime_select_with_default_prompt
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 16, 35)
+
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected << %{<option value="">Year</option>\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
+ expected << %{<option value="">Month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ expected << %{<option value="">Day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ expected << %{<option value="">Hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n}
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ expected << %{<option value="">Minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n}
+ expected << "</select>\n"
+
+ assert_dom_equal expected, datetime_select("post", "updated_at", :prompt => true)
+ end
+
+ def test_datetime_select_with_custom_prompt
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 16, 35)
+
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected << %{<option value="">Choose year</option>\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
+ expected << %{<option value="">Choose month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ expected << %{<option value="">Choose day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
+ expected << %{<option value="">Choose hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n}
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n}
+ expected << %{<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n}
+ expected << "</select>\n"
+
+ assert_dom_equal expected, datetime_select("post", "updated_at", :prompt => {:year => 'Choose year', :month => 'Choose month', :day => 'Choose day', :hour => 'Choose hour', :minute => 'Choose minute'})
+ end
+
def test_date_select_with_zero_value_and_no_start_year
expected = %(<select id="date_first_year" name="date[first][year]">\n)
(Date.today.year-5).upto(Date.today.year+1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index d057ddfcd0..c750f486f9 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,9 @@
*2.3.0/3.0*
+* Added dynamic scopes ala dynamic finders #1648 [Yaroslav Markin]
+
+* Fixed that ActiveRecord::Base#new_record? should return false (not nil) for existing records #1219 [Yaroslav Markin]
+
* I18n the word separator for error messages. Introduces the activerecord.errors.format.separator translation key. #1294 [Akira Matsuda]
* Add :having as a key to find and the relevant associations. [Emilio Tagua]
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index c428366a04..390c005785 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -51,6 +51,7 @@ module ActiveRecord
autoload :Callbacks, 'active_record/callbacks'
autoload :Dirty, 'active_record/dirty'
autoload :DynamicFinderMatch, 'active_record/dynamic_finder_match'
+ autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match'
autoload :Migration, 'active_record/migration'
autoload :Migrator, 'active_record/migration'
autoload :NamedScope, 'active_record/named_scope'
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 7b1b2d9ad9..9d0bf3a308 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -43,7 +43,7 @@ module ActiveRecord
# loading in a more high-level (application developer-friendly) manner.
module ClassMethods
protected
-
+
# Eager loads the named associations for the given ActiveRecord record(s).
#
# In this description, 'association name' shall refer to the name passed
@@ -94,8 +94,8 @@ module ActiveRecord
raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol)
preload_associations(records, parent, preload_options)
reflection = reflections[parent]
- parents = records.map {|record| record.send(reflection.name)}.flatten
- unless parents.empty? || parents.first.nil?
+ parents = records.map {|record| record.send(reflection.name)}.flatten.compact
+ unless parents.empty?
parents.first.class.preload_associations(parents, child)
end
end
@@ -113,7 +113,7 @@ module ActiveRecord
# unnecessarily
records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
-
+
# 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus,
# the following could call 'preload_belongs_to_association',
# 'preload_has_many_association', etc.
@@ -128,7 +128,7 @@ module ActiveRecord
association_proxy.target.push(*[associated_record].flatten)
end
end
-
+
def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
parent_records.each do |parent_record|
parent_record.send("set_#{reflection_name}_target", associated_record)
@@ -183,18 +183,19 @@ module ActiveRecord
conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}"
conditions << append_conditions(reflection, preload_options)
- associated_records = reflection.klass.find(:all, :conditions => [conditions, ids],
- :include => options[:include],
- :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
- :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
- :order => options[:order])
-
+ associated_records = reflection.klass.with_exclusive_scope do
+ reflection.klass.find(:all, :conditions => [conditions, ids],
+ :include => options[:include],
+ :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
+ :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
+ :order => options[:order])
+ end
set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
end
def preload_has_one_association(records, reflection, preload_options={})
return if records.first.send("loaded_#{reflection.name}?")
- id_to_record_map, ids = construct_id_map(records)
+ id_to_record_map, ids = construct_id_map(records)
options = reflection.options
records.each {|record| record.send("set_#{reflection.name}_target", nil)}
if options[:through]
@@ -248,7 +249,7 @@ module ActiveRecord
reflection.primary_key_name)
end
end
-
+
def preload_through_records(records, reflection, through_association)
through_reflection = reflections[through_association]
through_primary_key = through_reflection.primary_key_name
@@ -333,11 +334,13 @@ module ActiveRecord
end
conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
conditions << append_conditions(reflection, preload_options)
- associated_records = klass.find(:all, :conditions => [conditions, ids],
+ associated_records = klass.with_exclusive_scope do
+ klass.find(:all, :conditions => [conditions, ids],
:include => options[:include],
:select => options[:select],
:joins => options[:joins],
:order => options[:order])
+ end
set_association_single_records(id_map, reflection.name, associated_records, primary_key)
end
end
@@ -355,13 +358,15 @@ module ActiveRecord
conditions << append_conditions(reflection, preload_options)
- reflection.klass.find(:all,
+ reflection.klass.with_exclusive_scope do
+ reflection.klass.find(:all,
:select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
:include => preload_options[:include] || options[:include],
:conditions => [conditions, ids],
:joins => options[:joins],
:group => preload_options[:group] || options[:group],
:order => preload_options[:order] || options[:order])
+ end
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 07bc50c886..c154a5087c 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1216,11 +1216,11 @@ module ActiveRecord
# callbacks will be executed after the association is wiped out.
old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
class_eval <<-end_eval unless method_defined?(old_method)
- alias_method :#{old_method}, :destroy_without_callbacks
- def destroy_without_callbacks
- #{reflection.name}.clear
- #{old_method}
- end
+ alias_method :#{old_method}, :destroy_without_callbacks # alias_method :destroy_without_habtm_shim_for_posts, :destroy_without_callbacks
+ def destroy_without_callbacks # def destroy_without_callbacks
+ #{reflection.name}.clear # posts.clear
+ #{old_method} # destroy_without_habtm_shim_for_posts
+ end # end
end_eval
add_association_callbacks(reflection.name, options)
@@ -1453,7 +1453,7 @@ module ActiveRecord
dependent_conditions << sanitize_sql(reflection.options[:conditions]) if reflection.options[:conditions]
dependent_conditions << extra_conditions if extra_conditions
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
-
+ dependent_conditions = dependent_conditions.gsub('@', '\@')
case reflection.options[:dependent]
when :destroy
method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
@@ -1463,22 +1463,22 @@ module ActiveRecord
before_destroy method_name
when :delete_all
module_eval %Q{
- before_destroy do |record|
- delete_all_has_many_dependencies(record,
- "#{reflection.name}",
- #{reflection.class_name},
- "#{dependent_conditions}")
- end
+ before_destroy do |record| # before_destroy do |record|
+ delete_all_has_many_dependencies(record, # delete_all_has_many_dependencies(record,
+ "#{reflection.name}", # "posts",
+ #{reflection.class_name}, # Post,
+ %@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...)
+ end # end
}
when :nullify
module_eval %Q{
- before_destroy do |record|
- nullify_has_many_dependencies(record,
- "#{reflection.name}",
- #{reflection.class_name},
- "#{reflection.primary_key_name}",
- "#{dependent_conditions}")
- end
+ before_destroy do |record| # before_destroy do |record|
+ nullify_has_many_dependencies(record, # nullify_has_many_dependencies(record,
+ "#{reflection.name}", # "posts",
+ #{reflection.class_name}, # Post,
+ "#{reflection.primary_key_name}", # "user_id",
+ %@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...)
+ end # end
}
else
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})"
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index aa6013583d..cca012ed55 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1456,7 +1456,10 @@ module ActiveRecord #:nodoc:
def respond_to?(method_id, include_private = false)
if match = DynamicFinderMatch.match(method_id)
return true if all_attributes_exists?(match.attribute_names)
+ elsif match = DynamicScopeMatch.match(method_id)
+ return true if all_attributes_exists?(match.attribute_names)
end
+
super
end
@@ -1494,11 +1497,16 @@ module ActiveRecord #:nodoc:
end
if scoped?(:find, :order)
- scoped_order = reverse_sql_order(scope(:find, :order))
- scoped_methods.select { |s| s[:find].update(:order => scoped_order) }
+ scope = scope(:find)
+ original_scoped_order = scope[:order]
+ scope[:order] = reverse_sql_order(original_scoped_order)
end
- find_initial(options.merge({ :order => order }))
+ begin
+ find_initial(options.merge({ :order => order }))
+ ensure
+ scope[:order] = original_scoped_order if original_scoped_order
+ end
end
def reverse_sql_order(order_query)
@@ -1804,7 +1812,11 @@ module ActiveRecord #:nodoc:
# This also enables you to initialize a record if it is not found, such as find_or_initialize_by_amount(amount)
# or find_or_create_by_user_and_password(user, password).
#
- # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
+ # Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that
+ # are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password])
+ # respectively.
+ #
+ # Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future
# attempts to use it do not run through method_missing.
def method_missing(method_id, *arguments, &block)
if match = DynamicFinderMatch.match(method_id)
@@ -1813,10 +1825,31 @@ module ActiveRecord #:nodoc:
if match.finder?
finder = match.finder
bang = match.bang?
+ # def self.find_by_login_and_activated(*args)
+ # options = args.extract_options!
+ # attributes = construct_attributes_from_arguments(
+ # [:login,:activated],
+ # args
+ # )
+ # finder_options = { :conditions => attributes }
+ # validate_find_options(options)
+ # set_readonly_option!(options)
+ #
+ # if options[:conditions]
+ # with_scope(:find => finder_options) do
+ # find(:first, options)
+ # end
+ # else
+ # find(:first, options.merge(finder_options))
+ # end
+ # end
self.class_eval %{
def self.#{method_id}(*args)
options = args.extract_options!
- attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
+ attributes = construct_attributes_from_arguments(
+ [:#{attribute_names.join(',:')}],
+ args
+ )
finder_options = { :conditions => attributes }
validate_find_options(options)
set_readonly_option!(options)
@@ -1834,6 +1867,31 @@ module ActiveRecord #:nodoc:
send(method_id, *arguments)
elsif match.instantiator?
instantiator = match.instantiator
+ # def self.find_or_create_by_user_id(*args)
+ # guard_protected_attributes = false
+ #
+ # if args[0].is_a?(Hash)
+ # guard_protected_attributes = true
+ # attributes = args[0].with_indifferent_access
+ # find_attributes = attributes.slice(*[:user_id])
+ # else
+ # find_attributes = attributes = construct_attributes_from_arguments([:user_id], args)
+ # end
+ #
+ # options = { :conditions => find_attributes }
+ # set_readonly_option!(options)
+ #
+ # record = find(:first, options)
+ #
+ # if record.nil?
+ # record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
+ # yield(record) if block_given?
+ # record.save
+ # record
+ # else
+ # record
+ # end
+ # end
self.class_eval %{
def self.#{method_id}(*args)
guard_protected_attributes = false
@@ -1863,6 +1921,22 @@ module ActiveRecord #:nodoc:
}, __FILE__, __LINE__
send(method_id, *arguments, &block)
end
+ elsif match = DynamicScopeMatch.match(method_id)
+ attribute_names = match.attribute_names
+ super unless all_attributes_exists?(attribute_names)
+ if match.scope?
+ self.class_eval %{
+ def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
+ options = args.extract_options! # options = args.extract_options!
+ attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
+ [:#{attribute_names.join(',:')}], args # [:user_name, :password], args
+ ) # )
+ #
+ scoped(:conditions => attributes) # scoped(:conditions => attributes)
+ end # end
+ }, __FILE__, __LINE__
+ send(method_id, *arguments)
+ end
else
super
end
@@ -2401,9 +2475,9 @@ module ActiveRecord #:nodoc:
write_attribute(self.class.primary_key, value)
end
- # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet.
+ # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
def new_record?
- defined?(@new_record) && @new_record
+ @new_record || false
end
# :call-seq:
@@ -3010,7 +3084,7 @@ module ActiveRecord #:nodoc:
end
Base.class_eval do
- extend QueryCache
+ extend QueryCache::ClassMethods
include Validations
include Locking::Optimistic, Locking::Pessimistic
include AttributeMethods
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 950bd72101..00c71090f3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -14,12 +14,12 @@ module ActiveRecord
def dirties_query_cache(base, *method_names)
method_names.each do |method_name|
base.class_eval <<-end_code, __FILE__, __LINE__
- def #{method_name}_with_query_dirty(*args)
- clear_query_cache if @query_cache_enabled
- #{method_name}_without_query_dirty(*args)
- end
-
- alias_method_chain :#{method_name}, :query_dirty
+ def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args)
+ clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
+ #{method_name}_without_query_dirty(*args) # update_without_query_dirty(*args)
+ end # end
+ #
+ alias_method_chain :#{method_name}, :query_dirty # alias_method_chain :update, :query_dirty
end_code
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index fe9cbcf024..273f823e7f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -476,12 +476,12 @@ module ActiveRecord
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
class_eval <<-EOV
- def #{column_type}(*args)
- options = args.extract_options!
- column_names = args
-
- column_names.each { |name| column(name, '#{column_type}', options) }
- end
+ def #{column_type}(*args) # def string(*args)
+ options = args.extract_options! # options = args.extract_options!
+ column_names = args # column_names = args
+ #
+ column_names.each { |name| column(name, '#{column_type}', options) } # column_names.each { |name| column(name, 'string', options) }
+ end # end
EOV
end
@@ -676,24 +676,24 @@ module ActiveRecord
# t.string(:goat, :sheep)
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
class_eval <<-EOV
- def #{column_type}(*args)
- options = args.extract_options!
- column_names = args
-
- column_names.each do |name|
- column = ColumnDefinition.new(@base, name, '#{column_type}')
- if options[:limit]
- column.limit = options[:limit]
- elsif native['#{column_type}'.to_sym].is_a?(Hash)
- column.limit = native['#{column_type}'.to_sym][:limit]
- end
- column.precision = options[:precision]
- column.scale = options[:scale]
- column.default = options[:default]
- column.null = options[:null]
- @base.add_column(@table_name, name, column.sql_type, options)
- end
- end
+ def #{column_type}(*args) # def string(*args)
+ options = args.extract_options! # options = args.extract_options!
+ column_names = args # column_names = args
+ #
+ column_names.each do |name| # column_names.each do |name|
+ column = ColumnDefinition.new(@base, name, '#{column_type}') # column = ColumnDefinition.new(@base, name, 'string')
+ if options[:limit] # if options[:limit]
+ column.limit = options[:limit] # column.limit = options[:limit]
+ elsif native['#{column_type}'.to_sym].is_a?(Hash) # elsif native['string'.to_sym].is_a?(Hash)
+ column.limit = native['#{column_type}'.to_sym][:limit] # column.limit = native['string'.to_sym][:limit]
+ end # end
+ column.precision = options[:precision] # column.precision = options[:precision]
+ column.scale = options[:scale] # column.scale = options[:scale]
+ column.default = options[:default] # column.default = options[:default]
+ column.null = options[:null] # column.null = options[:null]
+ @base.add_column(@table_name, name, column.sql_type, options) # @base.add_column(@table_name, name, column.sql_type, options)
+ end # end
+ end # end
EOV
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 46d4b6c89c..60729c63db 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -13,23 +13,25 @@ module MysqlCompat #:nodoc:
# C driver >= 2.7 returns null values in each_hash
if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
target.class_eval <<-'end_eval'
- def all_hashes
- rows = []
- each_hash { |row| rows << row }
- rows
- end
+ def all_hashes # def all_hashes
+ rows = [] # rows = []
+ each_hash { |row| rows << row } # each_hash { |row| rows << row }
+ rows # rows
+ end # end
end_eval
# adapters before 2.7 don't have a version constant
# and don't return null values in each_hash
else
target.class_eval <<-'end_eval'
- def all_hashes
- rows = []
- all_fields = fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
- each_hash { |row| rows << all_fields.dup.update(row) }
- rows
- end
+ def all_hashes # def all_hashes
+ rows = [] # rows = []
+ all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f|
+ fields[f.name] = nil; fields # fields[f.name] = nil; fields
+ } # }
+ each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) }
+ rows # rows
+ end # end
end_eval
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 60ec01b95e..6685cb8663 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -950,13 +950,13 @@ module ActiveRecord
# should know about this but can't detect it there, so deal with it here.
money_precision = (postgresql_version >= 80300) ? 19 : 10
PostgreSQLColumn.module_eval(<<-end_eval)
- def extract_precision(sql_type)
- if sql_type =~ /^money$/
- #{money_precision}
- else
- super
- end
- end
+ def extract_precision(sql_type) # def extract_precision(sql_type)
+ if sql_type =~ /^money$/ # if sql_type =~ /^money$/
+ #{money_precision} # 19
+ else # else
+ super # super
+ end # end
+ end # end
end_eval
configure_connection
diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb
index a1760875ba..4c899f58e5 100644
--- a/activerecord/lib/active_record/dirty.rb
+++ b/activerecord/lib/active_record/dirty.rb
@@ -174,7 +174,7 @@ module ActiveRecord
alias_attribute_without_dirty(new_name, old_name)
DIRTY_SUFFIXES.each do |suffix|
module_eval <<-STR, __FILE__, __LINE__+1
- def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end
+ def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end
STR
end
end
diff --git a/activerecord/lib/active_record/dynamic_scope_match.rb b/activerecord/lib/active_record/dynamic_scope_match.rb
new file mode 100644
index 0000000000..f796ba669a
--- /dev/null
+++ b/activerecord/lib/active_record/dynamic_scope_match.rb
@@ -0,0 +1,25 @@
+module ActiveRecord
+ class DynamicScopeMatch
+ def self.match(method)
+ ds_match = self.new(method)
+ ds_match.scope ? ds_match : nil
+ end
+
+ def initialize(method)
+ @scope = true
+ case method.to_s
+ when /^scoped_by_([_a-zA-Z]\w*)$/
+ names = $1
+ else
+ @scope = nil
+ end
+ @attribute_names = names && names.split('_and_')
+ end
+
+ attr_reader :scope, :attribute_names
+
+ def scope?
+ !@scope.nil?
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index a8af89fcb9..eb92bc2545 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -1,20 +1,32 @@
module ActiveRecord
- module QueryCache
- # Enable the query cache within the block if Active Record is configured.
- def cache(&block)
- if ActiveRecord::Base.configurations.blank?
- yield
- else
- connection.cache(&block)
+ class QueryCache
+ module ClassMethods
+ # Enable the query cache within the block if Active Record is configured.
+ def cache(&block)
+ if ActiveRecord::Base.configurations.blank?
+ yield
+ else
+ connection.cache(&block)
+ end
end
+
+ # Disable the query cache within the block if Active Record is configured.
+ def uncached(&block)
+ if ActiveRecord::Base.configurations.blank?
+ yield
+ else
+ connection.uncached(&block)
+ end
+ end
+ end
+
+ def initialize(app)
+ @app = app
end
- # Disable the query cache within the block if Active Record is configured.
- def uncached(&block)
- if ActiveRecord::Base.configurations.blank?
- yield
- else
- connection.uncached(&block)
+ def call(env)
+ ActiveRecord::Base.cache do
+ @app.call(env)
end
end
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index a9e0efa6fe..8dbe80a01a 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -23,8 +23,8 @@ module ActiveRecord
write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
- write_attribute('updated_at', t) if respond_to?(:updated_at)
- write_attribute('updated_on', t) if respond_to?(:updated_on)
+ write_attribute('updated_at', t) if respond_to?(:updated_at) && updated_at.nil?
+ write_attribute('updated_on', t) if respond_to?(:updated_on) && updated_on.nil?
end
create_without_timestamps
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 617b3f440f..6a9690ba85 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -203,7 +203,6 @@ module ActiveRecord
if attr == "base"
full_messages << message
else
- #key = :"activerecord.att.#{@base.class.name.underscore.to_sym}.#{attr}"
attr_name = @base.class.human_attribute_name(attr)
full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message
end
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 8c9ae8a031..45e74ea024 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -104,6 +104,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
authors.first.posts.first.special_comments.first.post.very_special_comment
end
end
+
+ def test_eager_association_loading_where_first_level_returns_nil
+ authors = Author.find(:all, :include => {:post_about_thinking => :comments}, :order => 'authors.id DESC')
+ assert_equal [authors(:mary), authors(:david)], authors
+ assert_no_queries do
+ authors[1].post_about_thinking.comments.first
+ end
+ end
end
require 'models/vertex'
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 42063d18a3..afbd9fddf9 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -729,12 +729,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal authors(:david), assert_no_queries { posts[0].author}
posts = assert_queries(2) do
- Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'")
+ Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'", :order => 'posts.id')
end
assert_equal posts(:welcome, :thinking), posts
posts = assert_queries(2) do
- Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2")
+ Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2", :order => 'posts.id')
end
assert_equal posts(:welcome, :thinking), posts
@@ -771,4 +771,19 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal author_addresses(:david_address), authors[0].author_address
end
+ def test_preload_belongs_to_uses_exclusive_scope
+ people = Person.males.find(:all, :include => :primary_contact)
+ assert_not_equal people.length, 0
+ people.each do |person|
+ assert_no_queries {assert_not_nil person.primary_contact}
+ assert_equal Person.find(person.id).primary_contact, person.primary_contact
+ end
+ end
+
+ def test_preload_has_many_uses_exclusive_scope
+ people = Person.males.find :all, :include => :agents
+ people.each do |person|
+ assert_equal Person.find(person.id).agents, person.agents
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 816ceb6855..20b9acda44 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -665,6 +665,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Client.find_all_by_client_of(firm.id).size
end
+ def test_dependent_association_respects_optional_hash_conditions_on_delete
+ firm = companies(:odegy)
+ Client.create(:client_of => firm.id, :name => "BigShot Inc.")
+ Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
+ # only one of two clients is included in the association due to the :conditions key
+ assert_equal 2, Client.find_all_by_client_of(firm.id).size
+ assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size
+ firm.destroy
+ # only the correctly associated client should have been deleted
+ assert_equal 1, Client.find_all_by_client_of(firm.id).size
+ end
+
+
def test_creation_respects_hash_condition
ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.build
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 5f54931d00..0f03dae829 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -16,6 +16,7 @@ require 'models/post'
require 'models/comment'
require 'models/minimalistic'
require 'models/warehouse_thing'
+require 'models/parrot'
require 'rexml/document'
class Category < ActiveRecord::Base; end
@@ -1197,6 +1198,11 @@ class BasicsTest < ActiveRecord::TestCase
assert b_true.value?
end
+ def test_new_record_returns_boolean
+ assert_equal Topic.new.new_record?, true
+ assert_equal Topic.find(1).new_record?, false
+ end
+
def test_clone
topic = Topic.find(1)
cloned_topic = nil
@@ -2071,6 +2077,15 @@ class BasicsTest < ActiveRecord::TestCase
ActiveRecord::Base.logger = original_logger
end
+ def test_create_with_custom_timestamps
+ custom_datetime = 1.hour.ago.beginning_of_day
+
+ %w(created_at created_on updated_at updated_on).each do |attribute|
+ parrot = LiveParrot.create(:name => "colombian", attribute => custom_datetime)
+ assert_equal custom_datetime, parrot[attribute]
+ end
+ end
+
private
def with_kcode(kcode)
if RUBY_VERSION < '1.9'
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 6372b4f6aa..80a06116ad 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -27,6 +27,24 @@ class MethodScopingTest < ActiveRecord::TestCase
end
end
+ def test_scoped_find_last
+ highest_salary = Developer.find(:first, :order => "salary DESC")
+
+ Developer.with_scope(:find => { :order => "salary" }) do
+ assert_equal highest_salary, Developer.last
+ end
+ end
+
+ def test_scoped_find_last_preserves_scope
+ lowest_salary = Developer.find(:first, :order => "salary ASC")
+ highest_salary = Developer.find(:first, :order => "salary DESC")
+
+ Developer.with_scope(:find => { :order => "salary" }) do
+ assert_equal highest_salary, Developer.last
+ assert_equal lowest_salary, Developer.first
+ end
+ end
+
def test_scoped_find_combines_conditions
Developer.with_scope(:find => { :conditions => "salary = 9000" }) do
assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => "name = 'Jamis'")
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 64e899780c..b152f95a15 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -278,3 +278,23 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size
end
end
+
+class DynamicScopeMatchTest < ActiveRecord::TestCase
+ def test_scoped_by_no_match
+ assert_nil ActiveRecord::DynamicScopeMatch.match("not_scoped_at_all")
+ end
+
+ def test_scoped_by
+ match = ActiveRecord::DynamicScopeMatch.match("scoped_by_age_and_sex_and_location")
+ assert_not_nil match
+ assert match.scope?
+ assert_equal %w(age sex location), match.attribute_names
+ end
+end
+
+class DynamicScopeTest < ActiveRecord::TestCase
+ def test_dynamic_scope
+ assert_equal Post.scoped_by_author_id(1).find(1), Post.find(1)
+ assert_equal Post.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, Post.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"})
+ end
+end
diff --git a/activerecord/test/fixtures/people.yml b/activerecord/test/fixtures/people.yml
index d5a69e561d..3babb1fe59 100644
--- a/activerecord/test/fixtures/people.yml
+++ b/activerecord/test/fixtures/people.yml
@@ -1,6 +1,15 @@
michael:
id: 1
first_name: Michael
+ primary_contact_id: 2
+ gender: M
david:
id: 2
- first_name: David \ No newline at end of file
+ first_name: David
+ primary_contact_id: 3
+ gender: M
+susan:
+ id: 3
+ first_name: Susan
+ primary_contact_id: 2
+ gender: F \ No newline at end of file
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 0e3fafa37c..3b27a9e272 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -80,6 +80,7 @@ class ExclusivelyDependentFirm < Company
has_one :account, :foreign_key => "firm_id", :dependent => :delete
has_many :dependent_sanitized_conditional_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all, :conditions => "name = 'BigShot Inc.'"
has_many :dependent_conditional_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all, :conditions => ["name = ?", 'BigShot Inc.']
+ has_many :dependent_hash_conditional_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all, :conditions => {:name => 'BigShot Inc.'}
end
class Client < Company
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 430d0b38f7..ec2f684a6e 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -7,4 +7,10 @@ class Person < ActiveRecord::Base
has_many :jobs, :through => :references
has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true]
has_many :posts_with_comments_sorted_by_comment_id, :through => :readers, :source => :post, :include => :comments, :order => 'comments.id'
+
+ belongs_to :primary_contact, :class_name => 'Person'
+ has_many :agents, :class_name => 'Person', :foreign_key => 'primary_contact_id'
+
+ named_scope :males, :conditions => { :gender => 'M' }
+ named_scope :females, :conditions => { :gender => 'F' }
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index fbacc692b4..8199cb8fc7 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -298,8 +298,10 @@ ActiveRecord::Schema.define do
end
create_table :people, :force => true do |t|
- t.string :first_name, :null => false
- t.integer :lock_version, :null => false, :default => 0
+ t.string :first_name, :null => false
+ t.references :primary_contact
+ t.string :gender, :limit => 1
+ t.integer :lock_version, :null => false, :default => 0
end
create_table :pets, :primary_key => :pet_id ,:force => true do |t|
diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb
index 9ed532b48c..0b4549f759 100644
--- a/activeresource/lib/active_resource/http_mock.rb
+++ b/activeresource/lib/active_resource/http_mock.rb
@@ -54,6 +54,9 @@ module ActiveResource
end
for method in [ :post, :put, :get, :delete, :head ]
+ # def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
+ # @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
+ # end
module_eval <<-EOE, __FILE__, __LINE__
def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
@responses[Request.new(:#{method}, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
@@ -118,6 +121,11 @@ module ActiveResource
end
for method in [ :post, :put ]
+ # def post(path, body, headers)
+ # request = ActiveResource::Request.new(:post, path, body, headers)
+ # self.class.requests << request
+ # self.class.responses[request] || raise(InvalidRequestError.new("No response recorded for #{request}"))
+ # end
module_eval <<-EOE, __FILE__, __LINE__
def #{method}(path, body, headers)
request = ActiveResource::Request.new(:#{method}, path, body, headers)
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index bc29c41d7c..7b9ccc888d 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,9 @@
*2.3.0 [Edge]*
+* Updated i18n gem to version 0.1.1 #1635 [Yaroslav Markin]
+
+* Add :allow_nil option to delegate. #1127 [Sergio Gil]
+
* Add Benchmark.ms convenience method to benchmark realtime in milliseconds. [Jeremy Kemper]
* Updated included memcache-client to the 1.5.0.5 version which includes fixes from fiveruns and 37signals to deal with failover and timeouts #1535 [Joshua Sierles]
diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb
index 445d8edf47..33bcf327f8 100644
--- a/activesupport/lib/active_support/buffered_logger.rb
+++ b/activesupport/lib/active_support/buffered_logger.rb
@@ -68,13 +68,13 @@ module ActiveSupport
for severity in Severity.constants
class_eval <<-EOT, __FILE__, __LINE__
- def #{severity.downcase}(message = nil, progname = nil, &block)
- add(#{severity}, message, progname, &block)
- end
-
- def #{severity.downcase}?
- #{severity} >= @level
- end
+ def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
+ add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block)
+ end # end
+ #
+ def #{severity.downcase}? # def debug?
+ #{severity} >= @level # DEBUG >= @level
+ end # end
EOT
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 5cdcaf5ad1..86e66e0588 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -192,13 +192,8 @@ module ActiveSupport
end
def should_run_callback?(*args)
- if options[:if]
- evaluate_method(options[:if], *args)
- elsif options[:unless]
- !evaluate_method(options[:unless], *args)
- else
- true
- end
+ [options[:if]].flatten.compact.all? { |a| evaluate_method(a, *args) } &&
+ ![options[:unless]].flatten.compact.any? { |a| evaluate_method(a, *args) }
end
end
@@ -210,20 +205,24 @@ module ActiveSupport
def define_callbacks(*callbacks)
callbacks.each do |callback|
class_eval <<-"end_eval"
- def self.#{callback}(*methods, &block)
- callbacks = CallbackChain.build(:#{callback}, *methods, &block)
- (@#{callback}_callbacks ||= CallbackChain.new).concat callbacks
- end
-
- def self.#{callback}_callback_chain
- @#{callback}_callbacks ||= CallbackChain.new
-
- if superclass.respond_to?(:#{callback}_callback_chain)
- CallbackChain.new(superclass.#{callback}_callback_chain + @#{callback}_callbacks)
- else
- @#{callback}_callbacks
- end
- end
+ def self.#{callback}(*methods, &block) # def self.before_save(*methods, &block)
+ callbacks = CallbackChain.build(:#{callback}, *methods, &block) # callbacks = CallbackChain.build(:before_save, *methods, &block)
+ @#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new
+ @#{callback}_callbacks.concat callbacks # @before_save_callbacks.concat callbacks
+ end # end
+ #
+ def self.#{callback}_callback_chain # def self.before_save_callback_chain
+ @#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new
+ #
+ if superclass.respond_to?(:#{callback}_callback_chain) # if superclass.respond_to?(:before_save_callback_chain)
+ CallbackChain.new( # CallbackChain.new(
+ superclass.#{callback}_callback_chain + # superclass.before_save_callback_chain +
+ @#{callback}_callbacks # @before_save_callbacks
+ ) # )
+ else # else
+ @#{callback}_callbacks # @before_save_callbacks
+ end # end
+ end # end
end_eval
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
index 186ca69c05..c795871474 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -11,17 +11,17 @@ class Class
syms.flatten.each do |sym|
next if sym.is_a?(Hash)
class_eval(<<-EOS, __FILE__, __LINE__)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
- def self.#{sym}
- @@#{sym}
- end
-
- def #{sym}
- @@#{sym}
- end
+ unless defined? @@#{sym} # unless defined? @@hair_colors
+ @@#{sym} = nil # @@hair_colors = nil
+ end # end
+ #
+ def self.#{sym} # def self.hair_colors
+ @@#{sym} # @@hair_colors
+ end # end
+ #
+ def #{sym} # def hair_colors
+ @@#{sym} # @@hair_colors
+ end # end
EOS
end
end
@@ -30,19 +30,19 @@ class Class
options = syms.extract_options!
syms.flatten.each do |sym|
class_eval(<<-EOS, __FILE__, __LINE__)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
- def self.#{sym}=(obj)
- @@#{sym} = obj
- end
-
- #{"
- def #{sym}=(obj)
- @@#{sym} = obj
- end
- " unless options[:instance_writer] == false }
+ unless defined? @@#{sym} # unless defined? @@hair_colors
+ @@#{sym} = nil # @@hair_colors = nil
+ end # end
+ #
+ def self.#{sym}=(obj) # def self.hair_colors=(obj)
+ @@#{sym} = obj # @@hair_colors = obj
+ end # end
+ #
+ #{" #
+ def #{sym}=(obj) # def hair_colors=(obj)
+ @@#{sym} = obj # @@hair_colors = obj
+ end # end
+ " unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
EOS
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
index 368317df9b..000ccf4d55 100644
--- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
@@ -9,22 +9,23 @@ class Class
class_name_to_stop_searching_on = self.superclass.name.blank? ? "Object" : self.superclass.name
names.each do |name|
class_eval <<-EOS
- def self.#{name}
- if defined?(@#{name})
- @#{name}
- elsif superclass < #{class_name_to_stop_searching_on} && superclass.respond_to?(:#{name})
- superclass.#{name}
- end
- end
- def #{name}
- self.class.#{name}
- end
- def self.#{name}?
- !!#{name}
- end
- def #{name}?
- !!#{name}
- end
+ def self.#{name} # def self.only_reader
+ if defined?(@#{name}) # if defined?(@only_reader)
+ @#{name} # @only_reader
+ elsif superclass < #{class_name_to_stop_searching_on} && # elsif superclass < Object &&
+ superclass.respond_to?(:#{name}) # superclass.respond_to?(:only_reader)
+ superclass.#{name} # superclass.only_reader
+ end # end
+ end # end
+ def #{name} # def only_reader
+ self.class.#{name} # self.class.only_reader
+ end # end
+ def self.#{name}? # def self.only_reader?
+ !!#{name} # !!only_reader
+ end # end
+ def #{name}? # def only_reader?
+ !!#{name} # !!only_reader
+ end # end
EOS
end
end
@@ -32,9 +33,9 @@ class Class
def superclass_delegating_writer(*names)
names.each do |name|
class_eval <<-EOS
- def self.#{name}=(value)
- @#{name} = value
- end
+ def self.#{name}=(value) # def self.only_writer=(value)
+ @#{name} = value # @only_writer = value
+ end # end
EOS
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
index e6143a274b..1794afe77c 100644
--- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
@@ -11,13 +11,13 @@ class Class # :nodoc:
syms.each do |sym|
next if sym.is_a?(Hash)
class_eval <<-EOS
- def self.#{sym}
- read_inheritable_attribute(:#{sym})
- end
-
- def #{sym}
- self.class.#{sym}
- end
+ def self.#{sym} # def self.before_add_for_comments
+ read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:before_add_for_comments)
+ end # end
+ #
+ def #{sym} # def before_add_for_comments
+ self.class.#{sym} # self.class.before_add_for_comments
+ end # end
EOS
end
end
@@ -26,15 +26,15 @@ class Class # :nodoc:
options = syms.extract_options!
syms.each do |sym|
class_eval <<-EOS
- def self.#{sym}=(obj)
- write_inheritable_attribute(:#{sym}, obj)
- end
-
- #{"
- def #{sym}=(obj)
- self.class.#{sym} = obj
- end
- " unless options[:instance_writer] == false }
+ def self.#{sym}=(obj) # def self.color=(obj)
+ write_inheritable_attribute(:#{sym}, obj) # write_inheritable_attribute(:color, obj)
+ end # end
+ #
+ #{" #
+ def #{sym}=(obj) # def color=(obj)
+ self.class.#{sym} = obj # self.class.color = obj
+ end # end
+ " unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
EOS
end
end
@@ -43,15 +43,15 @@ class Class # :nodoc:
options = syms.extract_options!
syms.each do |sym|
class_eval <<-EOS
- def self.#{sym}=(obj)
- write_inheritable_array(:#{sym}, obj)
- end
-
- #{"
- def #{sym}=(obj)
- self.class.#{sym} = obj
- end
- " unless options[:instance_writer] == false }
+ def self.#{sym}=(obj) # def self.levels=(obj)
+ write_inheritable_array(:#{sym}, obj) # write_inheritable_array(:levels, obj)
+ end # end
+ #
+ #{" #
+ def #{sym}=(obj) # def levels=(obj)
+ self.class.#{sym} = obj # self.class.levels = obj
+ end # end
+ " unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
EOS
end
end
@@ -60,15 +60,15 @@ class Class # :nodoc:
options = syms.extract_options!
syms.each do |sym|
class_eval <<-EOS
- def self.#{sym}=(obj)
- write_inheritable_hash(:#{sym}, obj)
- end
-
- #{"
- def #{sym}=(obj)
- self.class.#{sym} = obj
- end
- " unless options[:instance_writer] == false }
+ def self.#{sym}=(obj) # def self.nicknames=(obj)
+ write_inheritable_hash(:#{sym}, obj) # write_inheritable_hash(:nicknames, obj)
+ end # end
+ #
+ #{" #
+ def #{sym}=(obj) # def nicknames=(obj)
+ self.class.#{sym} = obj # self.class.nicknames = obj
+ end # end
+ " unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
EOS
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index 88df49a69f..d845a6d8ca 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -24,10 +24,17 @@ module ActiveSupport #:nodoc:
end
# Replaces the hash with only the given keys.
+ # Returns a hash contained the removed key/value pairs
+ # {:a => 1, :b => 2, :c => 3, :d => 4}.slice!(:a, :b) # => {:c => 3, :d =>4}
def slice!(*keys)
- replace(slice(*keys))
+ keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
+ omit = slice(*self.keys - keys)
+ hash = slice(*keys)
+ replace(hash)
+ omit
end
end
end
end
end
+
diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb
index 24fe7294c9..858da7aa07 100644
--- a/activesupport/lib/active_support/core_ext/logger.rb
+++ b/activesupport/lib/active_support/core_ext/logger.rb
@@ -3,12 +3,12 @@
class Logger
def self.define_around_helper(level)
module_eval <<-end_eval
- def around_#{level}(before_message, after_message, &block)
- self.#{level}(before_message)
- return_value = block.call(self)
- self.#{level}(after_message)
- return return_value
- end
+ def around_#{level}(before_message, after_message, &block) # def around_debug(before_message, after_message, &block)
+ self.#{level}(before_message) # self.debug(before_message)
+ return_value = block.call(self) # return_value = block.call(self)
+ self.#{level}(after_message) # self.debug(after_message)
+ return return_value # return return_value
+ end # end
end_eval
end
[:debug, :info, :error, :fatal].each {|level| define_around_helper(level) }
diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb
index e640f64520..10fa520ba1 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -64,9 +64,9 @@ module ActiveSupport
# e.title # => "Megastars"
def alias_attribute(new_name, old_name)
module_eval <<-STR, __FILE__, __LINE__+1
- def #{new_name}; self.#{old_name}; end
- def #{new_name}?; self.#{old_name}?; end
- def #{new_name}=(v); self.#{old_name} = v; end
+ def #{new_name}; self.#{old_name}; end # def subject; self.title; end
+ def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end
+ def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end
STR
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb b/activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb
index 683789d853..4d0198f028 100644
--- a/activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb
+++ b/activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb
@@ -22,10 +22,10 @@ class Module
raise 'Default value or block required' unless !default.nil? || block
define_method(sym, block_given? ? block : Proc.new { default })
module_eval(<<-EVAL, __FILE__, __LINE__)
- def #{sym}=(value)
- class << self; attr_reader :#{sym} end
- @#{sym} = value
- end
+ def #{sym}=(value) # def age=(value)
+ class << self; attr_reader :#{sym} end # class << self; attr_reader :age end
+ @#{sym} = value # @age = value
+ end # end
EVAL
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index 51e1c9af90..9402cb8534 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -15,17 +15,17 @@ class Module
syms.each do |sym|
next if sym.is_a?(Hash)
class_eval(<<-EOS, __FILE__, __LINE__)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
- def self.#{sym}
- @@#{sym}
- end
-
- def #{sym}
- @@#{sym}
- end
+ unless defined? @@#{sym} # unless defined? @@pagination_options
+ @@#{sym} = nil # @@pagination_options = nil
+ end # end
+ #
+ def self.#{sym} # def self.pagination_options
+ @@#{sym} # @@pagination_options
+ end # end
+ #
+ def #{sym} # def pagination_options
+ @@#{sym} # @@pagination_options
+ end # end
EOS
end
end
@@ -34,19 +34,19 @@ class Module
options = syms.extract_options!
syms.each do |sym|
class_eval(<<-EOS, __FILE__, __LINE__)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
- def self.#{sym}=(obj)
- @@#{sym} = obj
- end
-
- #{"
- def #{sym}=(obj)
- @@#{sym} = obj
- end
- " unless options[:instance_writer] == false }
+ unless defined? @@#{sym} # unless defined? @@pagination_options
+ @@#{sym} = nil # @@pagination_options = nil
+ end # end
+ #
+ def self.#{sym}=(obj) # def self.pagination_options=(obj)
+ @@#{sym} = obj # @@pagination_options = obj
+ end # end
+ #
+ #{" #
+ def #{sym}=(obj) # def pagination_options=(obj)
+ @@#{sym} = obj # @@pagination_options = obj
+ end # end
+ " unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
EOS
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 2905eebc85..fb4b5f0f3c 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -72,6 +72,30 @@ class Module
# invoice.customer_name # => "John Doe"
# invoice.customer_address # => "Vimmersvej 13"
#
+ # If the object to which you delegate can be nil, you may want to use the
+ # :allow_nil option. In that case, it returns nil instead of raising a
+ # NoMethodError exception:
+ #
+ # class Foo
+ # attr_accessor :bar
+ # def initialize(bar = nil)
+ # @bar = bar
+ # end
+ # delegate :zoo, :to => :bar
+ # end
+ #
+ # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
+ #
+ # class Foo
+ # attr_accessor :bar
+ # def initialize(bar = nil)
+ # @bar = bar
+ # end
+ # delegate :zoo, :to => :bar, :allow_nil => true
+ # end
+ #
+ # Foo.new.zoo # returns nil
+ #
def delegate(*methods)
options = methods.pop
unless options.is_a?(Hash) && to = options[:to]
@@ -84,11 +108,13 @@ class Module
prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_"
+ allow_nil = options[:allow_nil] && "#{to} && "
+
methods.each do |method|
module_eval(<<-EOS, "(__DELEGATION__)", 1)
- def #{prefix}#{method}(*args, &block)
- #{to}.__send__(#{method.inspect}, *args, &block)
- end
+ def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block)
+ #{allow_nil}#{to}.__send__(#{method.inspect}, *args, &block) # client && client.__send__(:name, *args, &block)
+ end # end
EOS
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/synchronization.rb b/activesupport/lib/active_support/core_ext/module/synchronization.rb
index 251606024e..069db3fed0 100644
--- a/activesupport/lib/active_support/core_ext/module/synchronization.rb
+++ b/activesupport/lib/active_support/core_ext/module/synchronization.rb
@@ -26,11 +26,11 @@ class Module
end
module_eval(<<-EOS, __FILE__, __LINE__)
- def #{aliased_method}_with_synchronization#{punctuation}(*args, &block)
- #{with}.synchronize do
- #{aliased_method}_without_synchronization#{punctuation}(*args, &block)
- end
- end
+ def #{aliased_method}_with_synchronization#{punctuation}(*args, &block) # def expire_with_synchronization(*args, &block)
+ #{with}.synchronize do # @@lock.synchronize do
+ #{aliased_method}_without_synchronization#{punctuation}(*args, &block) # expire_without_synchronization(*args, &block)
+ end # end
+ end # end
EOS
alias_method_chain method, :synchronization
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 25b26e9c96..d20151661b 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -90,10 +90,15 @@ module ActiveSupport
method_names.each do |method_name|
alias_method_chain(method_name, :deprecation) do |target, punctuation|
class_eval(<<-EOS, __FILE__, __LINE__)
- def #{target}_with_deprecation#{punctuation}(*args, &block)
- ::ActiveSupport::Deprecation.warn(self.class.deprecated_method_warning(:#{method_name}, #{options[method_name].inspect}), caller)
- #{target}_without_deprecation#{punctuation}(*args, &block)
- end
+ def #{target}_with_deprecation#{punctuation}(*args, &block) # def generate_secret_with_deprecation(*args, &block)
+ ::ActiveSupport::Deprecation.warn( # ::ActiveSupport::Deprecation.warn(
+ self.class.deprecated_method_warning( # self.class.deprecated_method_warning(
+ :#{method_name}, # :generate_secret,
+ #{options[method_name].inspect}), # "You should use ActiveSupport::SecureRandom.hex(64)"),
+ caller # caller
+ ) # )
+ #{target}_without_deprecation#{punctuation}(*args, &block) # generate_secret_without_deprecation(*args, &block)
+ end # end
EOS
end
end
diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb
index 9f2fd3a401..952b4d8063 100644
--- a/activesupport/lib/active_support/memoizable.rb
+++ b/activesupport/lib/active_support/memoizable.rb
@@ -59,34 +59,36 @@ module ActiveSupport
memoized_ivar = ActiveSupport::Memoizable.memoized_ivar_for(symbol)
class_eval <<-EOS, __FILE__, __LINE__
- include InstanceMethods
-
- raise "Already memoized #{symbol}" if method_defined?(:#{original_method})
- alias #{original_method} #{symbol}
-
- if instance_method(:#{symbol}).arity == 0
- def #{symbol}(reload = false)
- if reload || !defined?(#{memoized_ivar}) || #{memoized_ivar}.empty?
- #{memoized_ivar} = [#{original_method}.freeze]
- end
- #{memoized_ivar}[0]
- end
- else
- def #{symbol}(*args)
- #{memoized_ivar} ||= {} unless frozen?
- reload = args.pop if args.last == true || args.last == :reload
-
- if defined?(#{memoized_ivar}) && #{memoized_ivar}
- if !reload && #{memoized_ivar}.has_key?(args)
- #{memoized_ivar}[args]
- elsif #{memoized_ivar}
- #{memoized_ivar}[args] = #{original_method}(*args).freeze
- end
- else
- #{original_method}(*args)
- end
- end
- end
+ include InstanceMethods # include InstanceMethods
+ #
+ if method_defined?(:#{original_method}) # if method_defined?(:_unmemoized_mime_type)
+ raise "Already memoized #{symbol}" # raise "Already memoized mime_type"
+ end # end
+ alias #{original_method} #{symbol} # alias _unmemoized_mime_type mime_type
+ #
+ if instance_method(:#{symbol}).arity == 0 # if instance_method(:mime_type).arity == 0
+ def #{symbol}(reload = false) # def mime_type(reload = false)
+ if reload || !defined?(#{memoized_ivar}) || #{memoized_ivar}.empty? # if reload || !defined?(@_memoized_mime_type) || @_memoized_mime_type.empty?
+ #{memoized_ivar} = [#{original_method}.freeze] # @_memoized_mime_type = [_unmemoized_mime_type.freeze]
+ end # end
+ #{memoized_ivar}[0] # @_memoized_mime_type[0]
+ end # end
+ else # else
+ def #{symbol}(*args) # def mime_type(*args)
+ #{memoized_ivar} ||= {} unless frozen? # @_memoized_mime_type ||= {} unless frozen?
+ reload = args.pop if args.last == true || args.last == :reload # reload = args.pop if args.last == true || args.last == :reload
+ #
+ if defined?(#{memoized_ivar}) && #{memoized_ivar} # if defined?(@_memoized_mime_type) && @_memoized_mime_type
+ if !reload && #{memoized_ivar}.has_key?(args) # if !reload && @_memoized_mime_type.has_key?(args)
+ #{memoized_ivar}[args] # @_memoized_mime_type[args]
+ elsif #{memoized_ivar} # elsif @_memoized_mime_type
+ #{memoized_ivar}[args] = #{original_method}(*args).freeze # @_memoized_mime_type[args] = _unmemoized_mime_type(*args).freeze
+ end # end
+ else # else
+ #{original_method}(*args) # _unmemoized_mime_type(*args)
+ end # end
+ end # end
+ end # end
EOS
end
end
diff --git a/activesupport/lib/active_support/multibyte/unicode_database.rb b/activesupport/lib/active_support/multibyte/unicode_database.rb
index 3b8cf8f9eb..a08f38cdbb 100644
--- a/activesupport/lib/active_support/multibyte/unicode_database.rb
+++ b/activesupport/lib/active_support/multibyte/unicode_database.rb
@@ -24,10 +24,10 @@ module ActiveSupport #:nodoc:
# Lazy load the Unicode database so it's only loaded when it's actually used
ATTRIBUTES.each do |attr_name|
class_eval(<<-EOS, __FILE__, __LINE__)
- def #{attr_name}
- load
- @#{attr_name}
- end
+ def #{attr_name} # def codepoints
+ load # load
+ @#{attr_name} # @codepoints
+ end # end
EOS
end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 9a2d283b30..72ff684fcc 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -234,9 +234,9 @@ module ActiveSupport
%w(year mon month day mday wday yday hour min sec to_date).each do |method_name|
class_eval <<-EOV
- def #{method_name}
- time.#{method_name}
- end
+ def #{method_name} # def year
+ time.#{method_name} # time.year
+ end # end
EOV
end
diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb
index 4525bba559..3d7d52ca71 100644
--- a/activesupport/lib/active_support/vendor.rb
+++ b/activesupport/lib/active_support/vendor.rb
@@ -22,8 +22,8 @@ end
# TODO I18n gem has not been released yet
# begin
-# gem 'i18n', '~> 0.0.1'
+# gem 'i18n', '~> 0.1.1'
# rescue Gem::LoadError
- $:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.0.1"
+ $:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.1.1/lib"
require 'i18n'
# end
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/.gitignore b/activesupport/lib/active_support/vendor/i18n-0.1.1/.gitignore
new file mode 100644
index 0000000000..0f41a39f89
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/.gitignore
@@ -0,0 +1,3 @@
+.DS_Store
+test/rails/fixtures
+doc
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/MIT-LICENSE b/activesupport/lib/active_support/vendor/i18n-0.1.1/MIT-LICENSE
new file mode 100755
index 0000000000..ed8e9ee66d
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 The Ruby I18n team
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/README.textile b/activesupport/lib/active_support/vendor/i18n-0.1.1/README.textile
new file mode 100644
index 0000000000..a07fc8426d
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/README.textile
@@ -0,0 +1,20 @@
+h1. Ruby I18n gem
+
+I18n and localization solution for Ruby.
+
+For information please refer to http://rails-i18n.org
+
+h2. Authors
+
+* "Matt Aimonetti":http://railsontherun.com
+* "Sven Fuchs":http://www.artweb-design.de
+* "Joshua Harvey":http://www.workingwithrails.com/person/759-joshua-harvey
+* "Saimon Moore":http://saimonmoore.net
+* "Stephan Soller":http://www.arkanis-development.de
+
+h2. License
+
+MIT License. See the included MIT-LICENCE file.
+
+
+
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/Rakefile b/activesupport/lib/active_support/vendor/i18n-0.1.1/Rakefile
new file mode 100644
index 0000000000..2164e13e69
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/Rakefile
@@ -0,0 +1,5 @@
+task :default => [:test]
+
+task :test do
+ ruby "test/all.rb"
+end
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/i18n.gemspec b/activesupport/lib/active_support/vendor/i18n-0.1.1/i18n.gemspec
new file mode 100644
index 0000000000..14294606bd
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/i18n.gemspec
@@ -0,0 +1,27 @@
+Gem::Specification.new do |s|
+ s.name = "i18n"
+ s.version = "0.1.1"
+ s.date = "2008-10-26"
+ s.summary = "Internationalization support for Ruby"
+ s.email = "rails-i18n@googlegroups.com"
+ s.homepage = "http://rails-i18n.org"
+ s.description = "Add Internationalization support to your Ruby application."
+ s.has_rdoc = false
+ s.authors = ['Sven Fuchs', 'Joshua Harvey', 'Matt Aimonetti', 'Stephan Soller', 'Saimon Moore']
+ s.files = [
+ 'i18n.gemspec',
+ 'lib/i18n/backend/simple.rb',
+ 'lib/i18n/exceptions.rb',
+ 'lib/i18n.rb',
+ 'MIT-LICENSE',
+ 'README.textile'
+ ]
+ s.test_files = [
+ 'test/all.rb',
+ 'test/i18n_exceptions_test.rb',
+ 'test/i18n_test.rb',
+ 'test/locale/en.rb',
+ 'test/locale/en.yml',
+ 'test/simple_backend_test.rb'
+ ]
+end
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb
index 2ffe3618b5..b5ad094d0e 100755
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb
@@ -2,39 +2,39 @@
# Sven Fuchs (http://www.artweb-design.de),
# Joshua Harvey (http://www.workingwithrails.com/person/759-joshua-harvey),
# Saimon Moore (http://saimonmoore.net),
-# Stephan Soller (http://www.arkanis-development.de/)
+# Stephan Soller (http://www.arkanis-development.de/)
# Copyright:: Copyright (c) 2008 The Ruby i18n Team
# License:: MIT
require 'i18n/backend/simple'
require 'i18n/exceptions'
-module I18n
+module I18n
@@backend = nil
@@load_path = nil
@@default_locale = :'en'
@@exception_handler = :default_exception_handler
-
+
class << self
# Returns the current backend. Defaults to +Backend::Simple+.
def backend
@@backend ||= Backend::Simple.new
end
-
+
# Sets the current backend. Used to set a custom backend.
- def backend=(backend)
+ def backend=(backend)
@@backend = backend
end
-
- # Returns the current default locale. Defaults to 'en'
+
+ # Returns the current default locale. Defaults to :'en'
def default_locale
- @@default_locale
+ @@default_locale
end
-
+
# Sets the current default locale. Used to set a custom default locale.
- def default_locale=(locale)
- @@default_locale = locale
+ def default_locale=(locale)
+ @@default_locale = locale
end
-
+
# Returns the current locale. Defaults to I18n.default_locale.
def locale
Thread.current[:locale] ||= default_locale
@@ -44,12 +44,12 @@ module I18n
def locale=(locale)
Thread.current[:locale] = locale
end
-
+
# Sets the exception handler.
def exception_handler=(exception_handler)
@@exception_handler = exception_handler
end
-
+
# Allow clients to register paths providing translation data sources. The
# backend defines acceptable sources.
#
@@ -74,25 +74,25 @@ module I18n
def reload!
backend.reload!
end
-
- # Translates, pluralizes and interpolates a given key using a given locale,
+
+ # Translates, pluralizes and interpolates a given key using a given locale,
# scope, and default, as well as interpolation values.
#
# *LOOKUP*
#
- # Translation data is organized as a nested hash using the upper-level keys
- # as namespaces. <em>E.g.</em>, ActionView ships with the translation:
+ # Translation data is organized as a nested hash using the upper-level keys
+ # as namespaces. <em>E.g.</em>, ActionView ships with the translation:
# <tt>:date => {:formats => {:short => "%b %d"}}</tt>.
- #
- # Translations can be looked up at any level of this hash using the key argument
- # and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt>
+ #
+ # Translations can be looked up at any level of this hash using the key argument
+ # and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt>
# returns the whole translations hash <tt>{:formats => {:short => "%b %d"}}</tt>.
- #
- # Key can be either a single key or a dot-separated key (both Strings and Symbols
+ #
+ # Key can be either a single key or a dot-separated key (both Strings and Symbols
# work). <em>E.g.</em>, the short format can be looked up using both:
# I18n.t 'date.formats.short'
# I18n.t :'date.formats.short'
- #
+ #
# Scope can be either a single key, a dot-separated key or an array of keys
# or dot-separated keys. Keys and scopes can be combined freely. So these
# examples will all look up the same short date format:
@@ -105,9 +105,9 @@ module I18n
#
# Translations can contain interpolation variables which will be replaced by
# values passed to #translate as part of the options hash, with the keys matching
- # the interpolation variable names.
+ # the interpolation variable names.
#
- # <em>E.g.</em>, with a translation <tt>:foo => "foo {{bar}}"</tt> the option
+ # <em>E.g.</em>, with a translation <tt>:foo => "foo {{bar}}"</tt> the option
# value for the key +bar+ will be interpolated into the translation:
# I18n.t :foo, :bar => 'baz' # => 'foo baz'
#
@@ -116,7 +116,7 @@ module I18n
# Translation data can contain pluralized translations. Pluralized translations
# are arrays of singluar/plural versions of translations like <tt>['Foo', 'Foos']</tt>.
#
- # Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English
+ # Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English
# pluralization rules. Other algorithms can be supported by custom backends.
#
# This returns the singular version of a pluralized translation:
@@ -125,9 +125,9 @@ module I18n
# These both return the plural version of a pluralized translation:
# I18n.t :foo, :count => 0 # => 'Foos'
# I18n.t :foo, :count => 2 # => 'Foos'
- #
- # The <tt>:count</tt> option can be used both for pluralization and interpolation.
- # <em>E.g.</em>, with the translation
+ #
+ # The <tt>:count</tt> option can be used both for pluralization and interpolation.
+ # <em>E.g.</em>, with the translation
# <tt>:foo => ['{{count}} foo', '{{count}} foos']</tt>, count will
# be interpolated to the pluralized translation:
# I18n.t :foo, :count => 1 # => '1 foo'
@@ -137,11 +137,11 @@ module I18n
# This returns the translation for <tt>:foo</tt> or <tt>default</tt> if no translation was found:
# I18n.t :foo, :default => 'default'
#
- # This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no
+ # This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no
# translation for <tt>:foo</tt> was found:
# I18n.t :foo, :default => :bar
#
- # Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt>
+ # Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt>
# or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found.
# I18n.t :foo, :default => [:bar, 'default']
#
@@ -161,9 +161,9 @@ module I18n
rescue I18n::ArgumentError => e
raise e if options[:raise]
send(@@exception_handler, e, locale, key, options)
- end
+ end
alias :t :translate
-
+
# Localizes certain objects, such as dates and numbers to local formatting.
def localize(object, options = {})
locale = options[:locale] || I18n.locale
@@ -171,7 +171,7 @@ module I18n
backend.localize(locale, object, format)
end
alias :l :localize
-
+
protected
# Handles exceptions raised in the backend. All exceptions except for
# MissingTranslationData exceptions are re-raised. When a MissingTranslationData
@@ -181,7 +181,7 @@ module I18n
return exception.message if MissingTranslationData === exception
raise exception
end
-
+
# Merges the given locale, key and scope into a single array of keys.
# Splits keys that contain dots into multiple keys. Makes sure all
# keys are Symbols.
@@ -191,4 +191,4 @@ module I18n
keys.flatten.map { |k| k.to_sym }
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb
index bdda55d3fe..d298b3a85a 100644
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb
@@ -6,21 +6,21 @@ module I18n
INTERPOLATION_RESERVED_KEYS = %w(scope default)
MATCH = /(\\\\)?\{\{([^\}]+)\}\}/
- # Accepts a list of paths to translation files. Loads translations from
+ # Accepts a list of paths to translation files. Loads translations from
# plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
# for details.
def load_translations(*filenames)
filenames.each { |filename| load_file(filename) }
end
-
- # Stores translations for the given locale in memory.
+
+ # Stores translations for the given locale in memory.
# This uses a deep merge for the translations hash, so existing
# translations will be overwritten by new ones only at the deepest
# level of the hash.
def store_translations(locale, data)
merge_translations(locale, data)
end
-
+
def translate(locale, key, options = {})
raise InvalidLocale.new(locale) if locale.nil?
return key.map { |k| translate(locale, k, options) } if key.is_a? Array
@@ -41,13 +41,13 @@ module I18n
entry = interpolate(locale, entry, values)
entry
end
-
- # Acts the same as +strftime+, but returns a localized version of the
- # formatted date string. Takes a key from the date/time formats
- # translations as a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
+
+ # Acts the same as +strftime+, but returns a localized version of the
+ # formatted date string. Takes a key from the date/time formats
+ # translations as a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
def localize(locale, object, format = :default)
raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
-
+
type = object.respond_to?(:sec) ? 'time' : 'date'
# TODO only translate these if format is a String?
formats = translate(locale, :"#{type}.formats")
@@ -57,14 +57,14 @@ module I18n
# TODO only translate these if the format string is actually present
# TODO check which format strings are present, then bulk translate then, then replace them
- format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday])
+ format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday])
format.gsub!(/%A/, translate(locale, :"date.day_names")[object.wday])
format.gsub!(/%b/, translate(locale, :"date.abbr_month_names")[object.mon])
format.gsub!(/%B/, translate(locale, :"date.month_names")[object.mon])
format.gsub!(/%p/, translate(locale, :"time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour
object.strftime(format)
end
-
+
def initialized?
@initialized ||= false
end
@@ -79,12 +79,12 @@ module I18n
load_translations(*I18n.load_path)
@initialized = true
end
-
+
def translations
@translations ||= {}
end
-
- # Looks up a translation from the translations hash. Returns nil if
+
+ # Looks up a translation from the translations hash. Returns nil if
# eiher key is nil, or locale, scope or key do not exist as a key in the
# nested translations hash. Splits keys or scopes containing dots
# into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
@@ -101,19 +101,19 @@ module I18n
end
end
end
-
- # Evaluates a default translation.
+
+ # Evaluates a default translation.
# If the given default is a String it is used literally. If it is a Symbol
# it will be translated with the given options. If it is an Array the first
# translation yielded will be returned.
- #
- # <em>I.e.</em>, <tt>default(locale, [:foo, 'default'])</tt> will return +default+ if
+ #
+ # <em>I.e.</em>, <tt>default(locale, [:foo, 'default'])</tt> will return +default+ if
# <tt>translate(locale, :foo)</tt> does not yield a result.
def default(locale, default, options = {})
case default
when String then default
when Symbol then translate locale, default, options
- when Array then default.each do |obj|
+ when Array then default.each do |obj|
result = default(locale, obj, options.dup) and return result
end and nil
end
@@ -135,10 +135,10 @@ module I18n
end
# Interpolates values into a given string.
- #
- # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
+ #
+ # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
# # => "file test.txt opened by {{user}}"
- #
+ #
# Note that you have to double escape the <tt>\\</tt> when you want to escape
# the <tt>{{...}}</tt> key in a string (once for the string and once for the
# interpolation).
@@ -167,8 +167,8 @@ module I18n
result.force_encoding(original_encoding) if original_encoding
result
end
-
- # Loads a single translations file by delegating to #load_rb or
+
+ # Loads a single translations file by delegating to #load_rb or
# #load_yml depending on the file extension and directly merges the
# data to the existing translations. Raises I18n::UnknownFileType
# for all other file extensions.
@@ -178,19 +178,19 @@ module I18n
data = send :"load_#{type}", filename # TODO raise a meaningful exception if this does not yield a Hash
data.each { |locale, d| merge_translations(locale, d) }
end
-
+
# Loads a plain Ruby translations file. eval'ing the file must yield
# a Hash containing translation data with locales as toplevel keys.
def load_rb(filename)
eval(IO.read(filename), binding, filename)
end
-
- # Loads a YAML translations file. The data must have locales as
+
+ # Loads a YAML translations file. The data must have locales as
# toplevel keys.
def load_yml(filename)
YAML::load(IO.read(filename))
end
-
+
# Deep merges the given translations hash with the existing translations
# for the given locale
def merge_translations(locale, data)
@@ -202,7 +202,7 @@ module I18n
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
translations[locale].merge!(data, &merger)
end
-
+
# Return a new hash with all keys and nested keys converted to symbols.
def deep_symbolize_keys(hash)
hash.inject({}) { |result, (key, value)|
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/exceptions.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/exceptions.rb
index 0f3eff1071..b5cea7acb4 100644
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/exceptions.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/exceptions.rb
@@ -1,6 +1,6 @@
module I18n
class ArgumentError < ::ArgumentError; end
-
+
class InvalidLocale < ArgumentError
attr_reader :locale
def initialize(locale)
@@ -42,7 +42,7 @@ module I18n
super "reserved key #{key.inspect} used in #{string.inspect}"
end
end
-
+
class UnknownFileType < ArgumentError
attr_reader :type, :filename
def initialize(type, filename)
@@ -50,4 +50,4 @@ module I18n
super "can not load translations from #{filename}, the file type #{type} is not known"
end
end
-end \ No newline at end of file
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/all.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/all.rb
new file mode 100644
index 0000000000..353712da49
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/all.rb
@@ -0,0 +1,5 @@
+dir = File.dirname(__FILE__)
+require dir + '/i18n_test.rb'
+require dir + '/simple_backend_test.rb'
+require dir + '/i18n_exceptions_test.rb'
+# *require* dir + '/custom_backend_test.rb' \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_exceptions_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_exceptions_test.rb
new file mode 100644
index 0000000000..dfcba6901f
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_exceptions_test.rb
@@ -0,0 +1,100 @@
+$:.unshift "lib"
+
+require 'rubygems'
+require 'test/unit'
+require 'mocha'
+require 'i18n'
+require 'active_support'
+
+class I18nExceptionsTest < Test::Unit::TestCase
+ def test_invalid_locale_stores_locale
+ force_invalid_locale
+ rescue I18n::ArgumentError => e
+ assert_nil e.locale
+ end
+
+ def test_invalid_locale_message
+ force_invalid_locale
+ rescue I18n::ArgumentError => e
+ assert_equal 'nil is not a valid locale', e.message
+ end
+
+ def test_missing_translation_data_stores_locale_key_and_options
+ force_missing_translation_data
+ rescue I18n::ArgumentError => e
+ options = {:scope => :bar}
+ assert_equal 'de', e.locale
+ assert_equal :foo, e.key
+ assert_equal options, e.options
+ end
+
+ def test_missing_translation_data_message
+ force_missing_translation_data
+ rescue I18n::ArgumentError => e
+ assert_equal 'translation missing: de, bar, foo', e.message
+ end
+
+ def test_invalid_pluralization_data_stores_entry_and_count
+ force_invalid_pluralization_data
+ rescue I18n::ArgumentError => e
+ assert_equal [:bar], e.entry
+ assert_equal 1, e.count
+ end
+
+ def test_invalid_pluralization_data_message
+ force_invalid_pluralization_data
+ rescue I18n::ArgumentError => e
+ assert_equal 'translation data [:bar] can not be used with :count => 1', e.message
+ end
+
+ def test_missing_interpolation_argument_stores_key_and_string
+ force_missing_interpolation_argument
+ rescue I18n::ArgumentError => e
+ assert_equal 'bar', e.key
+ assert_equal "{{bar}}", e.string
+ end
+
+ def test_missing_interpolation_argument_message
+ force_missing_interpolation_argument
+ rescue I18n::ArgumentError => e
+ assert_equal 'interpolation argument bar missing in "{{bar}}"', e.message
+ end
+
+ def test_reserved_interpolation_key_stores_key_and_string
+ force_reserved_interpolation_key
+ rescue I18n::ArgumentError => e
+ assert_equal 'scope', e.key
+ assert_equal "{{scope}}", e.string
+ end
+
+ def test_reserved_interpolation_key_message
+ force_reserved_interpolation_key
+ rescue I18n::ArgumentError => e
+ assert_equal 'reserved key "scope" used in "{{scope}}"', e.message
+ end
+
+ private
+ def force_invalid_locale
+ I18n.backend.translate nil, :foo
+ end
+
+ def force_missing_translation_data
+ I18n.backend.store_translations 'de', :bar => nil
+ I18n.backend.translate 'de', :foo, :scope => :bar
+ end
+
+ def force_invalid_pluralization_data
+ I18n.backend.store_translations 'de', :foo => [:bar]
+ I18n.backend.translate 'de', :foo, :count => 1
+ end
+
+ def force_missing_interpolation_argument
+ I18n.backend.store_translations 'de', :foo => "{{bar}}"
+ I18n.backend.translate 'de', :foo, :baz => 'baz'
+ end
+
+ def force_reserved_interpolation_key
+ I18n.backend.store_translations 'de', :foo => "{{scope}}"
+ I18n.backend.translate 'de', :foo, :baz => 'baz'
+ end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_test.rb
new file mode 100644
index 0000000000..bbb35ec809
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/i18n_test.rb
@@ -0,0 +1,125 @@
+$:.unshift "lib"
+
+require 'rubygems'
+require 'test/unit'
+require 'mocha'
+require 'i18n'
+require 'active_support'
+
+class I18nTest < Test::Unit::TestCase
+ def setup
+ I18n.backend.store_translations :'en', {
+ :currency => {
+ :format => {
+ :separator => '.',
+ :delimiter => ',',
+ }
+ }
+ }
+ end
+
+ def test_uses_simple_backend_set_by_default
+ assert I18n.backend.is_a?(I18n::Backend::Simple)
+ end
+
+ def test_can_set_backend
+ assert_nothing_raised{ I18n.backend = self }
+ assert_equal self, I18n.backend
+ I18n.backend = I18n::Backend::Simple.new
+ end
+
+ def test_uses_en_us_as_default_locale_by_default
+ assert_equal 'en', I18n.default_locale
+ end
+
+ def test_can_set_default_locale
+ assert_nothing_raised{ I18n.default_locale = 'de' }
+ assert_equal 'de', I18n.default_locale
+ I18n.default_locale = 'en'
+ end
+
+ def test_uses_default_locale_as_locale_by_default
+ assert_equal I18n.default_locale, I18n.locale
+ end
+
+ def test_can_set_locale_to_thread_current
+ assert_nothing_raised{ I18n.locale = 'de' }
+ assert_equal 'de', I18n.locale
+ assert_equal 'de', Thread.current[:locale]
+ I18n.locale = 'en'
+ end
+
+ def test_can_set_exception_handler
+ assert_nothing_raised{ I18n.exception_handler = :custom_exception_handler }
+ I18n.exception_handler = :default_exception_handler # revert it
+ end
+
+ def test_uses_custom_exception_handler
+ I18n.exception_handler = :custom_exception_handler
+ I18n.expects(:custom_exception_handler)
+ I18n.translate :bogus
+ I18n.exception_handler = :default_exception_handler # revert it
+ end
+
+ def test_delegates_translate_to_backend
+ I18n.backend.expects(:translate).with 'de', :foo, {}
+ I18n.translate :foo, :locale => 'de'
+ end
+
+ def test_delegates_localize_to_backend
+ I18n.backend.expects(:localize).with 'de', :whatever, :default
+ I18n.localize :whatever, :locale => 'de'
+ end
+
+ def test_translate_given_no_locale_uses_i18n_locale
+ I18n.backend.expects(:translate).with 'en', :foo, {}
+ I18n.translate :foo
+ end
+
+ def test_translate_on_nested_symbol_keys_works
+ assert_equal ".", I18n.t(:'currency.format.separator')
+ end
+
+ def test_translate_with_nested_string_keys_works
+ assert_equal ".", I18n.t('currency.format.separator')
+ end
+
+ def test_translate_with_array_as_scope_works
+ assert_equal ".", I18n.t(:separator, :scope => ['currency.format'])
+ end
+
+ def test_translate_with_array_containing_dot_separated_strings_as_scope_works
+ assert_equal ".", I18n.t(:separator, :scope => ['currency.format'])
+ end
+
+ def test_translate_with_key_array_and_dot_separated_scope_works
+ assert_equal [".", ","], I18n.t(%w(separator delimiter), :scope => 'currency.format')
+ end
+
+ def test_translate_with_dot_separated_key_array_and_scope_works
+ assert_equal [".", ","], I18n.t(%w(format.separator format.delimiter), :scope => 'currency')
+ end
+
+ def test_translate_with_options_using_scope_works
+ I18n.backend.expects(:translate).with('de', :precision, :scope => :"currency.format")
+ I18n.with_options :locale => 'de', :scope => :'currency.format' do |locale|
+ locale.t :precision
+ end
+ end
+
+ # def test_translate_given_no_args_raises_missing_translation_data
+ # assert_equal "translation missing: en, no key", I18n.t
+ # end
+
+ def test_translate_given_a_bogus_key_raises_missing_translation_data
+ assert_equal "translation missing: en, bogus", I18n.t(:bogus)
+ end
+
+ def test_localize_nil_raises_argument_error
+ assert_raises(I18n::ArgumentError) { I18n.l nil }
+ end
+
+ def test_localize_object_raises_argument_error
+ assert_raises(I18n::ArgumentError) { I18n.l Object.new }
+ end
+end
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.rb
new file mode 100644
index 0000000000..6044ce10d9
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.rb
@@ -0,0 +1 @@
+{:'en-Ruby' => {:foo => {:bar => "baz"}}} \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.yml b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.yml
new file mode 100644
index 0000000000..0b298c9c0e
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/locale/en.yml
@@ -0,0 +1,3 @@
+en-Yaml:
+ foo:
+ bar: baz \ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb
new file mode 100644
index 0000000000..e181975f38
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb
@@ -0,0 +1,502 @@
+# encoding: utf-8
+$:.unshift "lib"
+
+require 'rubygems'
+require 'test/unit'
+require 'mocha'
+require 'i18n'
+require 'time'
+require 'yaml'
+
+module I18nSimpleBackendTestSetup
+ def setup_backend
+ # backend_reset_translations!
+ @backend = I18n::Backend::Simple.new
+ @backend.store_translations 'en', :foo => {:bar => 'bar', :baz => 'baz'}
+ @locale_dir = File.dirname(__FILE__) + '/locale'
+ end
+ alias :setup :setup_backend
+
+ # def backend_reset_translations!
+ # I18n::Backend::Simple::ClassMethods.send :class_variable_set, :@@translations, {}
+ # end
+
+ def backend_get_translations
+ # I18n::Backend::Simple::ClassMethods.send :class_variable_get, :@@translations
+ @backend.instance_variable_get :@translations
+ end
+
+ def add_datetime_translations
+ @backend.store_translations :'de', {
+ :date => {
+ :formats => {
+ :default => "%d.%m.%Y",
+ :short => "%d. %b",
+ :long => "%d. %B %Y",
+ },
+ :day_names => %w(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag),
+ :abbr_day_names => %w(So Mo Di Mi Do Fr Sa),
+ :month_names => %w(Januar Februar März April Mai Juni Juli August September Oktober November Dezember).unshift(nil),
+ :abbr_month_names => %w(Jan Feb Mar Apr Mai Jun Jul Aug Sep Okt Nov Dez).unshift(nil),
+ :order => [:day, :month, :year]
+ },
+ :time => {
+ :formats => {
+ :default => "%a, %d. %b %Y %H:%M:%S %z",
+ :short => "%d. %b %H:%M",
+ :long => "%d. %B %Y %H:%M",
+ },
+ :am => 'am',
+ :pm => 'pm'
+ },
+ :datetime => {
+ :distance_in_words => {
+ :half_a_minute => 'half a minute',
+ :less_than_x_seconds => {
+ :one => 'less than 1 second',
+ :other => 'less than {{count}} seconds'
+ },
+ :x_seconds => {
+ :one => '1 second',
+ :other => '{{count}} seconds'
+ },
+ :less_than_x_minutes => {
+ :one => 'less than a minute',
+ :other => 'less than {{count}} minutes'
+ },
+ :x_minutes => {
+ :one => '1 minute',
+ :other => '{{count}} minutes'
+ },
+ :about_x_hours => {
+ :one => 'about 1 hour',
+ :other => 'about {{count}} hours'
+ },
+ :x_days => {
+ :one => '1 day',
+ :other => '{{count}} days'
+ },
+ :about_x_months => {
+ :one => 'about 1 month',
+ :other => 'about {{count}} months'
+ },
+ :x_months => {
+ :one => '1 month',
+ :other => '{{count}} months'
+ },
+ :about_x_years => {
+ :one => 'about 1 year',
+ :other => 'about {{count}} year'
+ },
+ :over_x_years => {
+ :one => 'over 1 year',
+ :other => 'over {{count}} years'
+ }
+ }
+ }
+ }
+ end
+end
+
+class I18nSimpleBackendTranslationsTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def test_store_translations_adds_translations # no, really :-)
+ @backend.store_translations :'en', :foo => 'bar'
+ assert_equal Hash[:'en', {:foo => 'bar'}], backend_get_translations
+ end
+
+ def test_store_translations_deep_merges_translations
+ @backend.store_translations :'en', :foo => {:bar => 'bar'}
+ @backend.store_translations :'en', :foo => {:baz => 'baz'}
+ assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], backend_get_translations
+ end
+
+ def test_store_translations_forces_locale_to_sym
+ @backend.store_translations 'en', :foo => 'bar'
+ assert_equal Hash[:'en', {:foo => 'bar'}], backend_get_translations
+ end
+
+ def test_store_translations_converts_keys_to_symbols
+ # backend_reset_translations!
+ @backend.store_translations 'en', 'foo' => {'bar' => 'bar', 'baz' => 'baz'}
+ assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], backend_get_translations
+ end
+end
+
+class I18nSimpleBackendTranslateTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def test_translate_calls_lookup_with_locale_given
+ @backend.expects(:lookup).with('de', :bar, [:foo]).returns 'bar'
+ @backend.translate 'de', :bar, :scope => [:foo]
+ end
+
+ def test_given_no_keys_it_returns_the_default
+ assert_equal 'default', @backend.translate('en', nil, :default => 'default')
+ end
+
+ def test_translate_given_a_symbol_as_a_default_translates_the_symbol
+ assert_equal 'bar', @backend.translate('en', nil, :scope => [:foo], :default => :bar)
+ end
+
+ def test_translate_given_an_array_as_default_uses_the_first_match
+ assert_equal 'bar', @backend.translate('en', :does_not_exist, :scope => [:foo], :default => [:does_not_exist_2, :bar])
+ end
+
+ def test_translate_given_an_array_of_inexistent_keys_it_raises_missing_translation_data
+ assert_raises I18n::MissingTranslationData do
+ @backend.translate('en', :does_not_exist, :scope => [:foo], :default => [:does_not_exist_2, :does_not_exist_3])
+ end
+ end
+
+ def test_translate_an_array_of_keys_translates_all_of_them
+ assert_equal %w(bar baz), @backend.translate('en', [:bar, :baz], :scope => [:foo])
+ end
+
+ def test_translate_calls_pluralize
+ @backend.expects(:pluralize).with 'en', 'bar', 1
+ @backend.translate 'en', :bar, :scope => [:foo], :count => 1
+ end
+
+ def test_translate_calls_interpolate
+ @backend.expects(:interpolate).with 'en', 'bar', {}
+ @backend.translate 'en', :bar, :scope => [:foo]
+ end
+
+ def test_translate_calls_interpolate_including_count_as_a_value
+ @backend.expects(:interpolate).with 'en', 'bar', {:count => 1}
+ @backend.translate 'en', :bar, :scope => [:foo], :count => 1
+ end
+
+ def test_translate_given_nil_as_a_locale_raises_an_argument_error
+ assert_raises(I18n::InvalidLocale){ @backend.translate nil, :bar }
+ end
+
+ def test_translate_with_a_bogus_key_and_no_default_raises_missing_translation_data
+ assert_raises(I18n::MissingTranslationData){ @backend.translate 'de', :bogus }
+ end
+end
+
+class I18nSimpleBackendLookupTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ # useful because this way we can use the backend with no key for interpolation/pluralization
+ def test_lookup_given_nil_as_a_key_returns_nil
+ assert_nil @backend.send(:lookup, 'en', nil)
+ end
+
+ def test_lookup_given_nested_keys_looks_up_a_nested_hash_value
+ assert_equal 'bar', @backend.send(:lookup, 'en', :bar, [:foo])
+ end
+end
+
+class I18nSimpleBackendPluralizeTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def test_pluralize_given_nil_returns_the_given_entry
+ entry = {:one => 'bar', :other => 'bars'}
+ assert_equal entry, @backend.send(:pluralize, nil, entry, nil)
+ end
+
+ def test_pluralize_given_0_returns_zero_string_if_zero_key_given
+ assert_equal 'zero', @backend.send(:pluralize, nil, {:zero => 'zero', :one => 'bar', :other => 'bars'}, 0)
+ end
+
+ def test_pluralize_given_0_returns_plural_string_if_no_zero_key_given
+ assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 0)
+ end
+
+ def test_pluralize_given_1_returns_singular_string
+ assert_equal 'bar', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 1)
+ end
+
+ def test_pluralize_given_2_returns_plural_string
+ assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 2)
+ end
+
+ def test_pluralize_given_3_returns_plural_string
+ assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 3)
+ end
+
+ def test_interpolate_given_incomplete_pluralization_data_raises_invalid_pluralization_data
+ assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, {:one => 'bar'}, 2) }
+ end
+
+ # def test_interpolate_given_a_string_raises_invalid_pluralization_data
+ # assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, 'bar', 2) }
+ # end
+ #
+ # def test_interpolate_given_an_array_raises_invalid_pluralization_data
+ # assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, ['bar'], 2) }
+ # end
+end
+
+class I18nSimpleBackendInterpolateTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def test_interpolate_given_a_value_hash_interpolates_the_values_to_the_string
+ assert_equal 'Hi David!', @backend.send(:interpolate, nil, 'Hi {{name}}!', :name => 'David')
+ end
+
+ def test_interpolate_given_a_value_hash_interpolates_into_unicode_string
+ assert_equal 'Häi David!', @backend.send(:interpolate, nil, 'Häi {{name}}!', :name => 'David')
+ end
+
+ def test_interpolate_given_nil_as_a_string_returns_nil
+ assert_nil @backend.send(:interpolate, nil, nil, :name => 'David')
+ end
+
+ def test_interpolate_given_an_non_string_as_a_string_returns_nil
+ assert_equal [], @backend.send(:interpolate, nil, [], :name => 'David')
+ end
+
+ def test_interpolate_given_a_values_hash_with_nil_values_interpolates_the_string
+ assert_equal 'Hi !', @backend.send(:interpolate, nil, 'Hi {{name}}!', {:name => nil})
+ end
+
+ def test_interpolate_given_an_empty_values_hash_raises_missing_interpolation_argument
+ assert_raises(I18n::MissingInterpolationArgument) { @backend.send(:interpolate, nil, 'Hi {{name}}!', {}) }
+ end
+
+ def test_interpolate_given_a_string_containing_a_reserved_key_raises_reserved_interpolation_key
+ assert_raises(I18n::ReservedInterpolationKey) { @backend.send(:interpolate, nil, '{{default}}', {:default => nil}) }
+ end
+end
+
+class I18nSimpleBackendLocalizeDateTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def setup
+ @backend = I18n::Backend::Simple.new
+ add_datetime_translations
+ @date = Date.new 2008, 1, 1
+ end
+
+ def test_translate_given_the_short_format_it_uses_it
+ assert_equal '01. Jan', @backend.localize('de', @date, :short)
+ end
+
+ def test_translate_given_the_long_format_it_uses_it
+ assert_equal '01. Januar 2008', @backend.localize('de', @date, :long)
+ end
+
+ def test_translate_given_the_default_format_it_uses_it
+ assert_equal '01.01.2008', @backend.localize('de', @date, :default)
+ end
+
+ def test_translate_given_a_day_name_format_it_returns_a_day_name
+ assert_equal 'Dienstag', @backend.localize('de', @date, '%A')
+ end
+
+ def test_translate_given_an_abbr_day_name_format_it_returns_an_abbrevated_day_name
+ assert_equal 'Di', @backend.localize('de', @date, '%a')
+ end
+
+ def test_translate_given_a_month_name_format_it_returns_a_month_name
+ assert_equal 'Januar', @backend.localize('de', @date, '%B')
+ end
+
+ def test_translate_given_an_abbr_month_name_format_it_returns_an_abbrevated_month_name
+ assert_equal 'Jan', @backend.localize('de', @date, '%b')
+ end
+
+ def test_translate_given_no_format_it_does_not_fail
+ assert_nothing_raised{ @backend.localize 'de', @date }
+ end
+
+ def test_translate_given_an_unknown_format_it_does_not_fail
+ assert_nothing_raised{ @backend.localize 'de', @date, '%x' }
+ end
+
+ def test_localize_nil_raises_argument_error
+ assert_raises(I18n::ArgumentError) { @backend.localize 'de', nil }
+ end
+
+ def test_localize_object_raises_argument_error
+ assert_raises(I18n::ArgumentError) { @backend.localize 'de', Object.new }
+ end
+end
+
+class I18nSimpleBackendLocalizeDateTimeTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def setup
+ @backend = I18n::Backend::Simple.new
+ add_datetime_translations
+ @morning = DateTime.new 2008, 1, 1, 6
+ @evening = DateTime.new 2008, 1, 1, 18
+ end
+
+ def test_translate_given_the_short_format_it_uses_it
+ assert_equal '01. Jan 06:00', @backend.localize('de', @morning, :short)
+ end
+
+ def test_translate_given_the_long_format_it_uses_it
+ assert_equal '01. Januar 2008 06:00', @backend.localize('de', @morning, :long)
+ end
+
+ def test_translate_given_the_default_format_it_uses_it
+ assert_equal 'Di, 01. Jan 2008 06:00:00 +0000', @backend.localize('de', @morning, :default)
+ end
+
+ def test_translate_given_a_day_name_format_it_returns_the_correct_day_name
+ assert_equal 'Dienstag', @backend.localize('de', @morning, '%A')
+ end
+
+ def test_translate_given_an_abbr_day_name_format_it_returns_the_correct_abbrevated_day_name
+ assert_equal 'Di', @backend.localize('de', @morning, '%a')
+ end
+
+ def test_translate_given_a_month_name_format_it_returns_the_correct_month_name
+ assert_equal 'Januar', @backend.localize('de', @morning, '%B')
+ end
+
+ def test_translate_given_an_abbr_month_name_format_it_returns_the_correct_abbrevated_month_name
+ assert_equal 'Jan', @backend.localize('de', @morning, '%b')
+ end
+
+ def test_translate_given_a_meridian_indicator_format_it_returns_the_correct_meridian_indicator
+ assert_equal 'am', @backend.localize('de', @morning, '%p')
+ assert_equal 'pm', @backend.localize('de', @evening, '%p')
+ end
+
+ def test_translate_given_no_format_it_does_not_fail
+ assert_nothing_raised{ @backend.localize 'de', @morning }
+ end
+
+ def test_translate_given_an_unknown_format_it_does_not_fail
+ assert_nothing_raised{ @backend.localize 'de', @morning, '%x' }
+ end
+end
+
+class I18nSimpleBackendLocalizeTimeTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def setup
+ @old_timezone, ENV['TZ'] = ENV['TZ'], 'UTC'
+ @backend = I18n::Backend::Simple.new
+ add_datetime_translations
+ @morning = Time.parse '2008-01-01 6:00 UTC'
+ @evening = Time.parse '2008-01-01 18:00 UTC'
+ end
+
+ def teardown
+ @old_timezone ? ENV['TZ'] = @old_timezone : ENV.delete('TZ')
+ end
+
+ def test_translate_given_the_short_format_it_uses_it
+ assert_equal '01. Jan 06:00', @backend.localize('de', @morning, :short)
+ end
+
+ def test_translate_given_the_long_format_it_uses_it
+ assert_equal '01. Januar 2008 06:00', @backend.localize('de', @morning, :long)
+ end
+
+ # TODO Seems to break on Windows because ENV['TZ'] is ignored. What's a better way to do this?
+ # def test_translate_given_the_default_format_it_uses_it
+ # assert_equal 'Di, 01. Jan 2008 06:00:00 +0000', @backend.localize('de', @morning, :default)
+ # end
+
+ def test_translate_given_a_day_name_format_it_returns_the_correct_day_name
+ assert_equal 'Dienstag', @backend.localize('de', @morning, '%A')
+ end
+
+ def test_translate_given_an_abbr_day_name_format_it_returns_the_correct_abbrevated_day_name
+ assert_equal 'Di', @backend.localize('de', @morning, '%a')
+ end
+
+ def test_translate_given_a_month_name_format_it_returns_the_correct_month_name
+ assert_equal 'Januar', @backend.localize('de', @morning, '%B')
+ end
+
+ def test_translate_given_an_abbr_month_name_format_it_returns_the_correct_abbrevated_month_name
+ assert_equal 'Jan', @backend.localize('de', @morning, '%b')
+ end
+
+ def test_translate_given_a_meridian_indicator_format_it_returns_the_correct_meridian_indicator
+ assert_equal 'am', @backend.localize('de', @morning, '%p')
+ assert_equal 'pm', @backend.localize('de', @evening, '%p')
+ end
+
+ def test_translate_given_no_format_it_does_not_fail
+ assert_nothing_raised{ @backend.localize 'de', @morning }
+ end
+
+ def test_translate_given_an_unknown_format_it_does_not_fail
+ assert_nothing_raised{ @backend.localize 'de', @morning, '%x' }
+ end
+end
+
+class I18nSimpleBackendHelperMethodsTest < Test::Unit::TestCase
+ def setup
+ @backend = I18n::Backend::Simple.new
+ end
+
+ def test_deep_symbolize_keys_works
+ result = @backend.send :deep_symbolize_keys, 'foo' => {'bar' => {'baz' => 'bar'}}
+ expected = {:foo => {:bar => {:baz => 'bar'}}}
+ assert_equal expected, result
+ end
+end
+
+class I18nSimpleBackendLoadTranslationsTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def test_load_translations_with_unknown_file_type_raises_exception
+ assert_raises(I18n::UnknownFileType) { @backend.load_translations "#{@locale_dir}/en.xml" }
+ end
+
+ def test_load_translations_with_ruby_file_type_does_not_raise_exception
+ assert_nothing_raised { @backend.load_translations "#{@locale_dir}/en.rb" }
+ end
+
+ def test_load_rb_loads_data_from_ruby_file
+ data = @backend.send :load_rb, "#{@locale_dir}/en.rb"
+ assert_equal({:'en-Ruby' => {:foo => {:bar => "baz"}}}, data)
+ end
+
+ def test_load_rb_loads_data_from_yaml_file
+ data = @backend.send :load_yml, "#{@locale_dir}/en.yml"
+ assert_equal({'en-Yaml' => {'foo' => {'bar' => 'baz'}}}, data)
+ end
+
+ def test_load_translations_loads_from_different_file_formats
+ @backend = I18n::Backend::Simple.new
+ @backend.load_translations "#{@locale_dir}/en.rb", "#{@locale_dir}/en.yml"
+ expected = {
+ :'en-Ruby' => {:foo => {:bar => "baz"}},
+ :'en-Yaml' => {:foo => {:bar => "baz"}}
+ }
+ assert_equal expected, backend_get_translations
+ end
+end
+
+class I18nSimpleBackendReloadTranslationsTest < Test::Unit::TestCase
+ include I18nSimpleBackendTestSetup
+
+ def setup
+ @backend = I18n::Backend::Simple.new
+ I18n.load_path = [File.dirname(__FILE__) + '/locale/en.yml']
+ assert_nil backend_get_translations
+ @backend.send :init_translations
+ end
+
+ def teardown
+ I18n.load_path = []
+ end
+
+ def test_setup
+ assert_not_nil backend_get_translations
+ end
+
+ def test_reload_translations_unloads_translations
+ @backend.reload!
+ assert_nil backend_get_translations
+ end
+
+ def test_reload_translations_uninitializes_translations
+ @backend.reload!
+ assert_equal @backend.initialized?, false
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 25b8eecef5..2bc2e1eaf0 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -53,10 +53,41 @@ class Person < Record
end
class ConditionalPerson < Record
+ # proc
before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true }
before_save Proc.new { |r| r.history << "b00m" }, :if => Proc.new { |r| false }
before_save Proc.new { |r| r.history << [:before_save, :proc] }, :unless => Proc.new { |r| false }
before_save Proc.new { |r| r.history << "b00m" }, :unless => Proc.new { |r| true }
+ # symbol
+ before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :if => :yes
+ before_save Proc.new { |r| r.history << "b00m" }, :if => :no
+ before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :unless => :no
+ before_save Proc.new { |r| r.history << "b00m" }, :unless => :yes
+ # string
+ before_save Proc.new { |r| r.history << [:before_save, :string] }, :if => 'yes'
+ before_save Proc.new { |r| r.history << "b00m" }, :if => 'no'
+ before_save Proc.new { |r| r.history << [:before_save, :string] }, :unless => 'no'
+ before_save Proc.new { |r| r.history << "b00m" }, :unless => 'yes'
+ # Array with conditions
+ before_save Proc.new { |r| r.history << [:before_save, :symbol_array] }, :if => [:yes, :other_yes]
+ before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, :no]
+ before_save Proc.new { |r| r.history << [:before_save, :symbol_array] }, :unless => [:no, :other_no]
+ before_save Proc.new { |r| r.history << "b00m" }, :unless => [:yes, :no]
+ # Combined if and unless
+ before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, :if => :yes, :unless => :no
+ before_save Proc.new { |r| r.history << "b00m" }, :if => :yes, :unless => :yes
+ # Array with different types of conditions
+ before_save Proc.new { |r| r.history << [:before_save, :symbol_proc_string_array] }, :if => [:yes, Proc.new { |r| true }, 'yes']
+ before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, Proc.new { |r| true }, 'no']
+ # Array with different types of conditions comibned if and unless
+ before_save Proc.new { |r| r.history << [:before_save, :combined_symbol_proc_string_array] },
+ :if => [:yes, Proc.new { |r| true }, 'yes'], :unless => [:no, 'no']
+ before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, Proc.new { |r| true }, 'no'], :unless => [:no, 'no']
+
+ def yes; true; end
+ def other_yes; true; end
+ def no; false; end
+ def other_no; false; end
def save
run_callbacks(:before_save)
@@ -90,7 +121,16 @@ class ConditionalCallbackTest < Test::Unit::TestCase
person.save
assert_equal [
[:before_save, :proc],
- [:before_save, :proc]
+ [:before_save, :proc],
+ [:before_save, :symbol],
+ [:before_save, :symbol],
+ [:before_save, :string],
+ [:before_save, :string],
+ [:before_save, :symbol_array],
+ [:before_save, :symbol_array],
+ [:before_save, :combined_symbol],
+ [:before_save, :symbol_proc_string_array],
+ [:before_save, :combined_symbol_proc_string_array]
], person.history
end
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 63ccb5a7da..b63ab30965 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -287,10 +287,14 @@ class HashExtTest < Test::Unit::TestCase
# Should return a new hash with only the given keys.
assert_equal expected, original.slice(:a, :b)
assert_not_equal expected, original
+ end
+
+ def test_slice_inplace
+ original = { :a => 'x', :b => 'y', :c => 10 }
+ expected = { :c => 10 }
# Should replace the hash with only the given keys.
assert_equal expected, original.slice!(:a, :b)
- assert_equal expected, original
end
def test_slice_with_an_array_key
@@ -300,10 +304,14 @@ class HashExtTest < Test::Unit::TestCase
# Should return a new hash with only the given keys when given an array key.
assert_equal expected, original.slice([:a, :b], :c)
assert_not_equal expected, original
+ end
+
+ def test_slice_inplace_with_an_array_key
+ original = { :a => 'x', :b => 'y', :c => 10, [:a, :b] => "an array key" }
+ expected = { :a => 'x', :b => 'y' }
# Should replace the hash with only the given keys when given an array key.
assert_equal expected, original.slice!([:a, :b], :c)
- assert_equal expected, original
end
def test_slice_with_splatted_keys
@@ -322,11 +330,17 @@ class HashExtTest < Test::Unit::TestCase
# Should return a new hash with only the given keys.
assert_equal expected, original.slice(*keys), keys.inspect
assert_not_equal expected, original
+ end
+ end
+ def test_indifferent_slice_inplace
+ original = { :a => 'x', :b => 'y', :c => 10 }.with_indifferent_access
+ expected = { :c => 10 }.with_indifferent_access
+
+ [['a', 'b'], [:a, :b]].each do |keys|
# Should replace the hash with only the given keys.
copy = original.dup
assert_equal expected, copy.slice!(*keys)
- assert_equal expected, copy
end
end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 886f692499..a5d98507ba 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -41,6 +41,10 @@ Invoice = Struct.new(:client) do
delegate :street, :city, :name, :to => :client, :prefix => :customer
end
+Project = Struct.new(:description, :person) do
+ delegate :name, :to => :person, :allow_nil => true
+end
+
class Name
delegate :upcase, :to => :@full_name
@@ -117,6 +121,29 @@ class ModuleTest < Test::Unit::TestCase
end
end
+ def test_delegation_with_allow_nil
+ rails = Project.new("Rails", Someone.new("David"))
+ assert_equal rails.name, "David"
+ end
+
+ def test_delegation_with_allow_nil_and_nil_value
+ rails = Project.new("Rails")
+ assert_nil rails.name
+ end
+
+ def test_delegation_with_allow_nil_and_nil_value_and_prefix
+ Project.class_eval do
+ delegate :name, :to => :person, :allow_nil => true, :prefix => true
+ end
+ rails = Project.new("Rails")
+ assert_nil rails.person_name
+ end
+
+ def test_delegation_without_allow_nil_and_nil_value
+ david = Someone.new("David")
+ assert_raises(NoMethodError) { david.street }
+ end
+
def test_parent
assert_equal Yz::Zy, Yz::Zy::Cd.parent
assert_equal Yz, Yz::Zy.parent
diff --git a/railties/doc/guides/source/command_line.txt b/railties/doc/guides/source/command_line.txt
index 1ad2e75c51..8a887bd001 100644
--- a/railties/doc/guides/source/command_line.txt
+++ b/railties/doc/guides/source/command_line.txt
@@ -52,7 +52,7 @@ NOTE: This output will seem very familiar when we get to the `generate` command.
=== server ===
-Let's try it! The `server` command launches a small web server written in Ruby named WEBrick which was also installed when you installed Rails. You'll use this any time you want to view your work through a web browser.
+Let's try it! The `server` command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to view your work through a web browser.
NOTE: WEBrick isn't your only option for serving Rails. We'll get to that in a later section. [XXX: which section]
@@ -99,7 +99,7 @@ Using generators will save you a large amount of time by writing *boilerplate co
Let's make our own controller with the controller generator. But what command should we use? Let's ask the generator:
-NOTE: All Rails console utilities have help text. For commands that require a lot of input to run correctly, you can try the command without any parameters (like `rails` or `./script/generate`). For others, you can try adding `--help` or `-h` to the end, as in `./script/server --help`.
+NOTE: All Rails console utilities have help text. As with most *NIX utilities, you can try adding `--help` or `-h` to the end, for example `./script/server --help`.
[source,shell]
------------------------------------------------------
@@ -200,24 +200,47 @@ Examples:
creates a Post model with a string title, text body, and published flag.
------------------------------------------------------
-Let's set up a simple model called "HighScore" that will keep track of our highest score on video games we play. Then we'll wire up our controller and view to modify and list our scores.
+But instead of generating a model directly (which we'll be doing later), let's set up a scaffold. A *scaffold* in Rails is a full set of model, database migration for that model, controller to manipulate it, views to view and manipulate the data, and a test suite for each of the above.
+
+Let's set up a simple resource called "HighScore" that will keep track of our highest score on video games we play.
[source,shell]
------------------------------------------------------
-$ ./script/generate model HighScore id:integer game:string score:integer
- exists app/models/
- exists test/unit/
- exists test/fixtures/
- create app/models/high_score.rb
- create test/unit/high_score_test.rb
- create test/fixtures/high_scores.yml
- create db/migrate
- create db/migrate/20081126032945_create_high_scores.rb
+$ ./script/generate scaffold HighScore game:string score:integer
+ exists app/models/
+ exists app/controllers/
+ exists app/helpers/
+ create app/views/high_scores
+ create app/views/layouts/
+ exists test/functional/
+ create test/unit/
+ create public/stylesheets/
+ create app/views/high_scores/index.html.erb
+ create app/views/high_scores/show.html.erb
+ create app/views/high_scores/new.html.erb
+ create app/views/high_scores/edit.html.erb
+ create app/views/layouts/high_scores.html.erb
+ create public/stylesheets/scaffold.css
+ create app/controllers/high_scores_controller.rb
+ create test/functional/high_scores_controller_test.rb
+ create app/helpers/high_scores_helper.rb
+ route map.resources :high_scores
+dependency model
+ exists app/models/
+ exists test/unit/
+ create test/fixtures/
+ create app/models/high_score.rb
+ create test/unit/high_score_test.rb
+ create test/fixtures/high_scores.yml
+ exists db/migrate
+ create db/migrate/20081217071914_create_high_scores.rb
------------------------------------------------------
-Taking it from the top, we have the *models* directory, where all of your data models live. *test/unit*, where all the unit tests live (gasp! -- unit tests!), fixtures for those tests, a test, the *migrate* directory, where the database-modifying migrations live, and a migration to create the `high_scores` table with the right fields.
+Taking it from the top - the generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the *resource*, and new tests for everything.
+
+The migration requires that we *migrate*, that is, run some Ruby code (living in that `20081217071914_create_high_scores.rb`) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while.
-The migration requires that we *migrate*, that is, run some Ruby code (living in that `20081126032945_create_high_scores.rb`) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while.
+NOTE: Hey. Install the sqlite3-ruby gem while you're at it. `gem install sqlite3-ruby`
[source,shell]
------------------------------------------------------
@@ -231,23 +254,87 @@ $ rake db:migrate
NOTE: Let's talk about unit tests. Unit tests are code that tests and makes assertions about code. In unit testing, we take a little part of code, say a method of a model, and test its inputs and outputs. Unit tests are your friend. The sooner you make peace with the fact that your quality of life will drastically increase when you unit test your code, the better. Seriously. We'll make one in a moment.
-Yo! Let's shove a small table into our greeting controller and view, listing our sweet scores.
+Let's see the interface Rails created for us. ./script/server; http://localhost:3000/high_scores
-[source,ruby]
+We can create new high scores (55,160 on Space Invaders!)
+
+=== console ===
+The `console` command lets you interact with your Rails application from the command line. On the underside, `script/console` uses IRB, so if you've ever used it, you'll be right at home. This is useful for testing out quick ideas with code and changing data server-side without touching the website.
+
+=== dbconsole ===
+`dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3.
+
+=== plugin ===
+The `plugin` command simplifies plugin management; think a miniature version of the Gem utility. Let's walk through installing a plugin. You can call the sub-command *discover*, which sifts through repositories looking for plugins, or call *source* to add a specific repository of plugins, or you can specify the plugin location directly.
+
+Let's say you're creating a website for a client who wants a small accounting system. Every event having to do with money must be logged, and must never be deleted. Wouldn't it be great if we could override the behavior of a model to never actually take its record out of the database, but *instead*, just set a field?
+
+There is such a thing! The plugin we're installing is called "acts_as_paranoid", and it lets models implement a "deleted_at" column that gets set when you call destroy. Later, when calling find, the plugin will tack on a database check to filter out "deleted" things.
+
+[source,shell]
+------------------------------------------------------
+$ ./script/plugin install http://svn.techno-weenie.net/projects/plugins/acts_as_paranoid
++ ./CHANGELOG
++ ./MIT-LICENSE
+...
+...
------------------------------------------------------
-class GreetingController < ApplicationController
- def hello
- if request.post?
- score = HighScore.new(params[:high_score])
- if score.save
- flash[:notice] = "New score posted!"
- end
- end
-
- @scores = HighScore.find(:all)
- end
-end
+=== runner ===
+`runner` runs Ruby code in the context of Rails non-interactively. For instance:
+
+[source,shell]
------------------------------------------------------
+$ ./script/runner "Model.long_running_method"
+------------------------------------------------------
+
+=== destroy ===
+Think of `destroy` as the opposite of `generate`. It'll figure out what generate did, and undo it. Believe you-me, the creation of this tutorial used this command many times!
-XXX: Go with scaffolding instead, modifying greeting controller for high scores seems dumb.
+[source,shell]
+------------------------------------------------------
+$ ./script/generate model Oops
+ exists app/models/
+ exists test/unit/
+ exists test/fixtures/
+ create app/models/oops.rb
+ create test/unit/oops_test.rb
+ create test/fixtures/oops.yml
+ exists db/migrate
+ create db/migrate/20081221040817_create_oops.rb
+$ ./script/destroy model Oops
+ notempty db/migrate
+ notempty db
+ rm db/migrate/20081221040817_create_oops.rb
+ rm test/fixtures/oops.yml
+ rm test/unit/oops_test.rb
+ rm app/models/oops.rb
+ notempty test/fixtures
+ notempty test
+ notempty test/unit
+ notempty test
+ notempty app/models
+ notempty app
+------------------------------------------------------
+
+=== about ===
+Check it: Version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version! `about` is useful when you need to ask help, check if a security patch might affect you, or when you need some stats for an existing Rails installation.
+
+[source,shell]
+------------------------------------------------------
+$ ./script/about
+About your application's environment
+Ruby version 1.8.6 (i486-linux)
+RubyGems version 1.3.1
+Rails version 2.2.0
+Active Record version 2.2.0
+Action Pack version 2.2.0
+Active Resource version 2.2.0
+Action Mailer version 2.2.0
+Active Support version 2.2.0
+Edge Rails revision unknown
+Application root /home/commandsapp
+Environment development
+Database adapter sqlite3
+Database schema version 20081217073400
+------------------------------------------------------ \ No newline at end of file
diff --git a/railties/doc/guides/source/finders.txt b/railties/doc/guides/source/finders.txt
index d2bd55ada7..88e7c15cb6 100644
--- a/railties/doc/guides/source/finders.txt
+++ b/railties/doc/guides/source/finders.txt
@@ -1,7 +1,7 @@
Rails Finders
=============
-This guide covers the +find+ method defined in +ActiveRecord::Base+, as well as other ways of finding particular instances of your models. By using this guide, you will be able to:
+This guide covers the +find+ method defined in ActiveRecord::Base, as well as other ways of finding particular instances of your models. By using this guide, you will be able to:
* Find records using a variety of methods and conditions
* Specify the order, retrieved attributes, grouping, and other properties of the found records
@@ -50,7 +50,7 @@ Active Record will perform queries on the database for you and is compatible wit
== IDs, First, Last and All
-+ActiveRecord::Base+ has methods defined on it to make interacting with your database and the tables within it much, much easier. For finding records, the key method is +find+. This method allows you to pass arguments into it to perform certain queries on your database without the need of SQL. If you wanted to find the record with the id of 1, you could type +Client.find(1)+ which would execute this query on your database:
+ActiveRecord::Base has methods defined on it to make interacting with your database and the tables within it much, much easier. For finding records, the key method is +find+. This method allows you to pass arguments into it to perform certain queries on your database without the need of SQL. If you wanted to find the record with the id of 1, you could type +Client.find(1)+ which would execute this query on your database:
[source, sql]
-------------------------------------------------------
@@ -74,9 +74,9 @@ SELECT * FROM clients WHERE (clients.id IN (1,2))
created_at: "2008-09-28 13:12:40", updated_at: "2008-09-28 13:12:40">]
-------------------------------------------------------
-Note that if you pass in a list of numbers that the result will be returned as an array, not as a single +Client+ object.
+Note that if you pass in a list of numbers that the result will be returned as an array, not as a single Client object.
-NOTE: If +find(id)+ or +find([id1, id2])+ fails to find any records, it will raise a +RecordNotFound+ exception.
+NOTE: If +find(id)+ or +find([id1, id2])+ fails to find any records, it will raise a RecordNotFound exception.
If you wanted to find the first Client object you would simply type +Client.first+ and that would find the first client in your clients table:
@@ -143,7 +143,7 @@ WARNING: Building your own conditions as pure strings can leave you vulnerable t
=== Array Conditions ===
-Now what if that number could vary, say as a parameter from somewhere, or perhaps from the user's level status somewhere? The find then becomes something like +Client.first(:conditions => ["orders_count = ?", params[:orders]])+. Active Record will go through the first element in the conditions value and any additional elements will replace the question marks (?) in the first element. If you want to specify two conditions, you can do it like +Client.first(:conditions => ["orders_count = ? AND locked = ?", params[:orders], false])+. In this example, the first question mark will be replaced with the value in +params[:orders]+ and the second will be replaced with +false+ and this will find the first record in the table that has '2' as its value for the +orders_count+ field and +false+ for its locked field.
+Now what if that number could vary, say as a argument from somewhere, or perhaps from the user's level status somewhere? The find then becomes something like +Client.first(:conditions => ["orders_count = ?", params[:orders]])+. Active Record will go through the first element in the conditions value and any additional elements will replace the question marks (?) in the first element. If you want to specify two conditions, you can do it like +Client.first(:conditions => ["orders_count = ? AND locked = ?", params[:orders], false])+. In this example, the first question mark will be replaced with the value in +params[:orders]+ and the second will be replaced with the SQL representation of +false+, which depends on the adapter.
The reason for doing code like:
@@ -159,7 +159,7 @@ instead of:
Client.first(:conditions => "orders_count = #{params[:orders]}")
-------------------------------------------------------
-is because of parameter safety. Putting the variable directly into the conditions string will pass the variable to the database *as-is*. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your parameters directly inside the conditions string.
+is because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database *as-is*. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string.
TIP: For more information on the dangers of SQL injection, see the link:../security.html#_sql_injection[Ruby on Rails Security Guide].
@@ -240,7 +240,7 @@ This makes for clearer readability if you have a large number of variable condit
=== Hash Conditions
-Rails also allows you to pass in a hash conditions too which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them:
+Rails also allows you to pass in a hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them:
[source, ruby]
-------------------------------------------------------
@@ -258,27 +258,63 @@ The good thing about this is that we can pass in a range for our fields without
[source, ruby]
-------------------------------------------------------
-Client.all(:conditions => { :created_at => ((Time.now.midnight - 1.day)..Time.now.midnight})
+Client.all(:conditions => { :created_at => (Time.now.midnight - 1.day)..Time.now.midnight})
-------------------------------------------------------
-This will find all clients created yesterday. This shows the shorter syntax for the examples in <<_array_conditions, Array Conditions>>
+This will find all clients created yesterday by using a BETWEEN sql statement:
+
+[source, sql]
+-------------------------------------------------------
+SELECT * FROM `clients` WHERE (`clients`.`created_at` BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00')
+-------------------------------------------------------
+
+This demonstrates a shorter syntax for the examples in <<_array_conditions, Array Conditions>>
You can also join in tables and specify their columns in the hash:
[source, ruby]
-------------------------------------------------------
-Client.all(:include => "orders", :conditions => { 'orders.created_at; => ((Time.now.midnight - 1.day)..Time.now.midnight})
+Client.all(:include => "orders", :conditions => { 'orders.created_at' => (Time.now.midnight - 1.day)..Time.now.midnight })
-------------------------------------------------------
-This will find all clients who have orders that were created yesterday.
+An alternative and cleaner syntax to this is:
+
+[source, ruby]
+-------------------------------------------------------
+Client.all(:include => "orders", :conditions => { :orders => { :created_at => (Time.now.midnight - 1.day)..Time.now.midnight } })
+-------------------------------------------------------
+
+This will find all clients who have orders that were created yesterday, again using a BETWEEN expression.
+
+If you want to find records using the IN expression you can pass an array to the conditions hash:
+
+[source, ruby]
+-------------------------------------------------------
+Client.all(:include => "orders", :conditions => { :orders_count => [1,3,5] }
+-------------------------------------------------------
+
+This code will generate SQL like this:
+
+[source, sql]
+-------------------------------------------------------
+SELECT * FROM `clients` WHERE (`clients`.`orders_count` IN (1,2,3))
+-------------------------------------------------------
== Ordering
-If you're getting a set of records and want to force an order, you can use +Client.all(:order => "created_at")+ which by default will sort the records by ascending order. If you'd like to order it in descending order, just tell it to do that using +Client.all(:order => "created_at desc")+
+If you're getting a set of records and want to order them in ascending order by the +created_at+ field in your table, you can use +Client.all(:order => "created_at")+. If you'd like to order it in descending order, just tell it to do that using +Client.all(:order => "created_at desc")+. The value for this option is passed in as sanitized SQL and allows you to sort via multiple fields: +Client.all(:order => "created_at desc, orders_count asc")+.
== Selecting Certain Fields
-To select certain fields, you can use the select option like this: +Client.first(:select => "viewable_by, locked")+. This select option does not use an array of fields, but rather requires you to type SQL-like code. The above code will execute +SELECT viewable_by, locked FROM clients LIMIT 0,1+ on your database.
+To select certain fields, you can use the select option like this: +Client.first(:select => "viewable_by, locked")+. This select option does not use an array of fields, but rather requires you to type SQL-like code. The above code will execute +SELECT viewable_by, locked FROM clients LIMIT 1+ on your database.
+
+Be careful because this also means you're initializing a model object with only the fields that you've selected. If you attempt to access a field that is not in the initialized record you'll receive:
+
+-------------------------------------------------------
+ActiveRecord::MissingAttributeError: missing attribute: <attribute>
+-------------------------------------------------------
+
+Where <attribute> is the atrribute you asked for. The +id+ method will not raise the +ActiveRecord::MissingAttributeError+, so just be careful when working with associations because they need the +id+ method to function properly.
You can also call SQL functions within the select option. For example, if you would like to only grab a single record per unique value in a certain field by using the +DISTINCT+ function you can do it like this: +Client.all(:select => "DISTINCT(name)")+.
@@ -328,16 +364,29 @@ The SQL that would be executed would be something like this:
SELECT * FROM orders GROUP BY date(created_at)
-------------------------------------------------------
+== Having
+
+The +:having+ option allows you to specify SQL and acts as a kind of a filter on the group option. +:having+ can only be specified when +:group+ is specified.
+
+An example of using it would be:
+
+[source, ruby]
+-------------------------------------------------------
+Order.all(:group => "date(created_at)", :having => ["created_at > ?", 1.month.ago])
+-------------------------------------------------------
+
+This will return single order objects for each day, but only for the last month.
+
== Read Only
-Readonly is a find option that you can set in order to make that instance of the record read-only. Any attempt to alter or destroy the record will not succeed, raising an +Active Record::ReadOnlyRecord+ exception. To set this option, specify it like this:
++readonly+ is a +find+ option that you can set in order to make that instance of the record read-only. Any attempt to alter or destroy the record will not succeed, raising an ActiveRecord::ReadOnlyRecord exception. To set this option, specify it like this:
[source, ruby]
-------------------------------------------------------
Client.first(:readonly => true)
-------------------------------------------------------
-If you assign this record to a variable +client+, calling the following code will raise an +ActiveRecord::ReadOnlyRecord+ exception:
+If you assign this record to a variable client, calling the following code will raise an ActiveRecord::ReadOnlyRecord exception:
[source, ruby]
-------------------------------------------------------
@@ -358,13 +407,23 @@ Topic.transaction do
end
-------------------------------------------------------
+You can also pass SQL to this option to allow different types of locks. For example, MySQL has an expression called LOCK IN SHARE MODE where you can lock a record but still allow other queries to read it. To specify this expression just pass it in as the lock option:
+
+[source, ruby]
+-------------------------------------------------------
+Topic.transaction do
+ t = Topic.find(params[:id], :lock => "LOCK IN SHARE MODE")
+ t.increment!(:views)
+end
+-------------------------------------------------------
+
== Making It All Work Together
-You can chain these options together in no particular order as Active Record will write the correct SQL for you. If you specify two instances of the same options inside the find statement Active Record will use the latter.
+You can chain these options together in no particular order as Active Record will write the correct SQL for you. If you specify two instances of the same options inside the +find+ method Active Record will use the last one you specified. This is because the options passed to find are a hash and defining the same key twice in a hash will result in the last definition being used.
== Eager Loading
-Eager loading is loading associated records along with any number of records in as few queries as possible. For example, if you wanted to load all the addresses associated with all the clients in a single query you could use +Client.all(:include => :address)+. If you wanted to include both the address and mailing address for the client you would use +Client.find(:all, :include => [:address, :mailing_address]). Include will first find the client records and then load the associated address records. Running script/server in one window, and executing the code through script/console in another window, the output should look similar to this:
+Eager loading is loading associated records along with any number of records in as few queries as possible. For example, if you wanted to load all the addresses associated with all the clients in a single query you could use +Client.all(:include => :address)+. If you wanted to include both the address and mailing address for the client you would use +Client.find(:all, :include => [:address, :mailing_address])+. Include will first find the client records and then load the associated address records. Running script/server in one window, and executing the code through script/console in another window, the output should look similar to this:
[source, sql]
-------------------------------------------------------
@@ -375,9 +434,10 @@ MailingAddress Load (0.001985) SELECT mailing_addresses.* FROM
mailing_addresses WHERE (mailing_addresses.client_id IN (13,14))
-------------------------------------------------------
-The numbers +13+ and +14+ in the above SQL are the ids of the clients gathered from the +Client.all+ query. Rails will then run a query to gather all the addresses and mailing addresses that have a client_id of 13 or 14. Although this is done in 3 queries, this is more efficient than not eager loading because without eager loading it would run a query for every time you called +address+ or +mailing_address+ on one of the objects in the clients array, which may lead to performance issues if you're loading a large number of records at once.
+The numbers +13+ and +14+ in the above SQL are the ids of the clients gathered from the +Client.all+ query. Rails will then run a query to gather all the addresses and mailing addresses that have a client_id of 13 or 14. Although this is done in 3 queries, this is more efficient than not eager loading because without eager loading it would run a query for every time you called +address+ or +mailing_address+ on one of the objects in the clients array, which may lead to performance issues if you're loading a large number of records at once and is often called the "N+1 query problem". The problem is that the more queries your server has to execute, the slower it will run.
-If you wanted to get all the addresses for a client in the same query you would do +Client.all(:joins => :address)+ and you wanted to find the address and mailing address for that client you would do +Client.all(:joins => [:address, :mailing_address])+. This is more efficient because it does all the SQL in one query, as shown by this example:
+If you wanted to get all the addresses for a client in the same query you would do +Client.all(:joins => :address)+.
+If you wanted to find the address and mailing address for that client you would do +Client.all(:joins => [:address, :mailing_address])+. This is more efficient because it does all the SQL in one query, as shown by this example:
[source, sql]
-------------------------------------------------------
@@ -400,44 +460,44 @@ When using eager loading you can specify conditions for the columns of the table
[source, ruby]
-------------------------------------------------------
Client.first(:include => "orders", :conditions =>
- ["orders.created_at >= ? AND orders.created_at <= ?", Time.now - 2.weeks, Time.now])
+ ["orders.created_at >= ? AND orders.created_at <= ?", 2.weeks.ago, Time.now])
-------------------------------------------------------
== Dynamic finders
-For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +name+ on your Client model for example, you get +find_by_name+ and +find_all_by_name+ for free from Active Record. If you have also have a +locked+ field on the client model, you also get +find_by_locked+ and +find_all_by_locked+.
+For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +name+ on your Client model for example, you get +find_by_name+ and +find_all_by_name+ for free from Active Record. If you have also have a +locked+ field on the Client model, you also get +find_by_locked+ and +find_all_by_locked+.
-You can do +find_last_by_*+ methods too which will find the last record matching your parameter.
+You can do +find_last_by_*+ methods too which will find the last record matching your argument.
-You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an +ActiveRecord::RecordNotFound+ error if they do not return any records, like +Client.find_by_name!('Ryan')+
+You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an ActiveRecord::RecordNotFound error if they do not return any records, like +Client.find_by_name!("Ryan")+
-If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields for example +Client.find_by_name_and_locked('Ryan', true)+.
+If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields for example +Client.find_by_name_and_locked("Ryan", true)+.
-There's another set of dynamic finders that let you find or create/initialize objects if they aren't find. These work in a similar fashion to the other finders and can be used like +find_or_create_by_name(params[:name])+. Using this will firstly perform a find and then create if the find returns nil. The SQL looks like this for +Client.find_or_create_by_name('Ryan')+:
+There's another set of dynamic finders that let you find or create/initialize objects if they aren't found. These work in a similar fashion to the other finders and can be used like +find_or_create_by_name(params[:name])+. Using this will firstly perform a find and then create if the find returns nil. The SQL looks like this for +Client.find_or_create_by_name("Ryan")+:
[source,sql]
-------------------------------------------------------
SELECT * FROM clients WHERE (clients.name = 'Ryan') LIMIT 1
BEGIN
INSERT INTO clients (name, updated_at, created_at, orders_count, locked)
- VALUES('Ryan', '2008-09-28 15:39:12', '2008-09-28 15:39:12', '0', '0')
+ VALUES('Ryan', '2008-09-28 15:39:12', '2008-09-28 15:39:12', 0, '0')
COMMIT
-------------------------------------------------------
-+find_or_create+'s sibling, +find_or_initialize+, will find an object and if it does not exist will act similar to calling +new+ with the parameters you passed in. For example:
++find_or_create+'s sibling, +find_or_initialize+, will find an object and if it does not exist will act similar to calling +new+ with the arguments you passed in. For example:
[source, ruby]
-------------------------------------------------------
client = Client.find_or_initialize_by_name('Ryan')
-------------------------------------------------------
-will either assign an existing client object with the name 'Ryan' to the client local variable, or initialize new object similar to calling +Client.new(:name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it.
+will either assign an existing client object with the name 'Ryan' to the client local variable, or initialize a new object similar to calling +Client.new(:name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it.
== Finding By SQL
-If you'd like to use your own SQL to find records a table you can use +find_by_sql+. The +find_by_sql+ method will return an array of objects even if it only returns a single record in it's call to the database. For example you could run this query:
+If you'd like to use your own SQL to find records in a table you can use +find_by_sql+. The +find_by_sql+ method will return an array of objects even the underlying query returns just a single record. For example you could run this query:
[source, ruby]
-------------------------------------------------------
@@ -457,7 +517,7 @@ Client.connection.select_all("SELECT * FROM `clients` WHERE `id` = '1'")
== Working with Associations
-When you define a has_many association on a model you get the find method and dynamic finders also on that association. This is helpful for finding associated records within the scope of an existing record, for example finding all the orders for a client that have been sent and not received by doing something like +Client.find(params[:id]).orders.find_by_sent_and_received(true, false)+. Having this find method available on associations is extremely helpful when using nested controllers.
+When you define a has_many association on a model you get the +find+ method and dynamic finders also on that association. This is helpful for finding associated records within the scope of an existing record, for example finding all the orders for a client that have been sent and not received by doing something like +Client.find(params[:id]).orders.find_by_sent_and_received(true, false)+. Having this find method available on associations is extremely helpful when using nested resources.
== Named Scopes
@@ -465,7 +525,7 @@ Named scopes are another way to add custom finding behavior to the models in the
=== Simple Named Scopes
-Suppose want to find all clients who are male. You could use this code:
+Suppose we want to find all clients who are male. You could use this code:
[source, ruby]
-------------------------------------------------------
@@ -485,7 +545,7 @@ class Client < ActiveRecord::Base
end
-------------------------------------------------------
-You can call this new named_scope with +Client.active.all+ and this will do the same query as if we just used +Client.all(:conditions => ["active = ?", true])+. Please be aware that the conditions syntax in named_scope and find is different and the two are not interchangeable. If you want to find the first client within this named scope you could do +Client.active.first+.
+You can call this new named_scope with +Client.active.all+ and this will do the same query as if we just used +Client.all(:conditions => ["active = ?", true])+. If you want to find the first client within this named scope you could do +Client.active.first+.
=== Combining Named Scopes
@@ -514,7 +574,7 @@ class Client < ActiveRecord::Base
end
-------------------------------------------------------
-This looks like a standard named scope that defines a method called recent which gathers all records created any time between now and 2 weeks ago. That's correct for the first time the model is loaded but for any time after that, +2.weeks.ago+ is set to that same value, so you will consistently get records from a certain date until your model is reloaded by something like your application restarting. The way to fix this is to put the code in a lambda block:
+This looks like a standard named scope that defines a method called +recent+ which gathers all records created any time between now and 2 weeks ago. That's correct for the first time the model is loaded but for any time after that, +2.weeks.ago+ is set to that same value, so you will consistently get records from a certain date until your model is reloaded by something like your application restarting. The way to fix this is to put the code in a lambda block:
[source, ruby]
-------------------------------------------------------
@@ -523,11 +583,11 @@ class Client < ActiveRecord::Base
end
-------------------------------------------------------
-And now every time the recent named scope is called, the code in the lambda block will be parsed, so you'll get actually 2 weeks ago from the code execution, not 2 weeks ago from the time the model was loaded.
+And now every time the +recent+ named scope is called, the code in the lambda block will be executed, so you'll get actually 2 weeks ago from the code execution, not 2 weeks ago from the time the model was loaded.
=== Named Scopes with Multiple Models
-In a named scope you can use +:include+ and +:joins+ options just like in find.
+In a named scope you can use +:include+ and +:joins+ options just like in +find+.
[source, ruby]
-------------------------------------------------------
@@ -541,7 +601,7 @@ This method, called as +Client.active_within_2_weeks.all+, will return all clien
=== Arguments to Named Scopes
-If you want to pass a named scope a compulsory argument, just specify it as a block parameter like this:
+If you want to pass to a named scope a required arugment, just specify it as a block argument like this:
[source, ruby]
-------------------------------------------------------
@@ -550,7 +610,7 @@ class Client < ActiveRecord::Base
end
-------------------------------------------------------
-This will work if you call +Client.recent(2.weeks.ago).all+ but not if you call +Client.recent+. If you want to add an optional argument for this, you have to use the splat operator as the block's parameter.
+This will work if you call +Client.recent(2.weeks.ago).all+ but not if you call +Client.recent+. If you want to add an optional argument for this, you have to use prefix the arugment with an *.
[source, ruby]
-------------------------------------------------------
@@ -587,14 +647,14 @@ Just like named scopes, anonymous scopes can be stacked, either with other anony
== Existence of Objects
-If you simply want to check for the existence of the object there's a method called +exists?+. This method will query the database using the same query as find, but instead of returning an object or collection of objects it will return either true or false.
+If you simply want to check for the existence of the object there's a method called +exists?+. This method will query the database using the same query as +find+, but instead of returning an object or collection of objects it will return either +true+ or false+.
[source, ruby]
-------------------------------------------------------
Client.exists?(1)
-------------------------------------------------------
-The above code will check for the existence of a clients table record with the id of 1 and return true if it exists.
+The +exists?+ method also takes multiple ids, but the catch is that it will return true if any one of those records exists.
[source, ruby]
-------------------------------------------------------
@@ -603,8 +663,6 @@ Client.exists?(1,2,3)
Client.exists?([1,2,3])
-------------------------------------------------------
-The +exists?+ method also takes multiple ids, as shown by the above code, but the catch is that it will return true if any one of those records exists.
-
Further more, +exists+ takes a +conditions+ option much like find:
[source, ruby]
@@ -627,10 +685,10 @@ Which will execute:
[source, sql]
-------------------------------------------------------
-SELECT count(*) AS count_all FROM clients WHERE (first_name = 1)
+SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan')
-------------------------------------------------------
-You can also use +include+ or +joins+ for this to do something a little more complex:
+You can also use +:include+ or +:joins+ for this to do something a little more complex:
[source, ruby]
-------------------------------------------------------
@@ -643,7 +701,7 @@ Which will execute:
-------------------------------------------------------
SELECT count(DISTINCT clients.id) AS count_all FROM clients
LEFT OUTER JOIN orders ON orders.client_id = client.id WHERE
- (clients.first_name = 'name' AND orders.status = 'received')
+ (clients.first_name = 'Ryan' AND orders.status = 'received')
-------------------------------------------------------
This code specifies +clients.first_name+ just in case one of the join tables has a field also called +first_name+ and it uses +orders.status+ because that's the name of our join table.
@@ -711,6 +769,12 @@ Thanks to Mike Gunderloy for his tips on creating this guide.
http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/16[Lighthouse ticket]
+* December 23 2008: Xavier Noria suggestions added! From http://rails.lighthouseapp.com/projects/16213/tickets/16-activerecord-finders#ticket-16-27[this ticket] and http://rails.lighthouseapp.com/projects/16213/tickets/16-activerecord-finders#ticket-16-28[this ticket] and http://rails.lighthouseapp.com/projects/16213/tickets/16-activerecord-finders#ticket-16-29[this ticket]
+* December 22 2008: Added section on having.
+* December 22 2008: Added description of how to make hash conditions use an IN expression http://rails.loglibrary.com/chats/15279234[mentioned here]
+* December 22 2008: Mentioned using SQL as values for the lock option as mentioned in http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/16-activerecord-finders#ticket-16-24[this ticket]
+* December 21 2008: Fixed http://rails.lighthouseapp.com/projects/16213/tickets/16-activerecord-finders#ticket-16-22[this ticket] minus two points; the lock SQL syntax and the having option.
+* December 21 2008: Added more to the has conditions section.
* December 17 2008: Fixed up syntax errors.
* December 16 2008: Covered hash conditions that were introduced in Rails 2.2.2.
* December 1 2008: Added using an SQL function example to Selecting Certain Fields section as per http://rails.lighthouseapp.com/projects/16213/tickets/36-adding-an-example-for-using-distinct-to-ar-finders[this ticket]
diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt
index 88ca74a557..d09ad15a90 100644
--- a/railties/doc/guides/source/form_helpers.txt
+++ b/railties/doc/guides/source/form_helpers.txt
@@ -247,7 +247,7 @@ A nice thing about `f.text_field` and other helper methods is that they will pre
Relying on record identification
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-In the previous chapter we handled the Article model. This model is directly available to users of our application and, following the best practices for developing with Rails, we should declare it *a resource*.
+In the previous chapter we handled the Article model. This model is directly available to users of our application, so -- following the best practices for developing with Rails -- we should declare it *a resource*.
When dealing with RESTful resources, our calls to `form_for` can get significantly easier if we rely on *record identification*. In short, we can just pass the model instance and have Rails figure out model name and the rest:
@@ -291,15 +291,13 @@ Here we have a list of cities where their names are presented to the user, but i
The select tag and options
~~~~~~~~~~~~~~~~~~~~~~~~~~
-The most generic helper is `select_tag`, which -- as the name implies -- simply generates the `SELECT` tag that encapsulates the options:
+The most generic helper is `select_tag`, which -- as the name implies -- simply generates the `SELECT` tag that encapsulates an options string:
----------------------------------------------------------------------------
<%= select_tag(:city_id, '<option value="1">Lisabon</option>...') %>
----------------------------------------------------------------------------
-This is a start, but it doesn't dynamically create our option tags. We had to pass them in as a string.
-
-We can generate option tags with the `options_for_select` helper:
+This is a start, but it doesn't dynamically create our option tags. We can generate option tags with the `options_for_select` helper:
----------------------------------------------------------------------------
<%= options_for_select([['Lisabon', 1], ['Madrid', 2], ...]) %>
@@ -311,9 +309,9 @@ output:
...
----------------------------------------------------------------------------
-For input data we used a nested array where each element has two elements: visible value (name) and internal value (ID).
+For input data we used a nested array where each item has two elements: option text (city name) and option value (city id).
-Now you can combine `select_tag` and `options_for_select` to achieve the desired, complete markup:
+Knowing this, you can combine `select_tag` and `options_for_select` to achieve the desired, complete markup:
----------------------------------------------------------------------------
<%= select_tag(:city_id, options_for_select(...)) %>
@@ -333,13 +331,114 @@ output:
So whenever Rails sees that the internal value of an option being generated matches this value, it will add the `selected` attribute to that option.
-Select boxes for dealing with models
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Select helpers for dealing with models
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Until now we've covered how to make generic select boxes, but in most cases our form controls will be tied to a specific database model. So, to continue from our previous examples, let's assume that we have a "Person" model with a `city_id` attribute.
+Consistent with other form helpers, when dealing with models we drop the `"_tag"` suffix from `select_tag` that we used in previous examples:
+
----------------------------------------------------------------------------
-...
+# controller:
+@person = Person.new(:city_id => 2)
+
+# view:
+<%= select(:person, :city_id, [['Lisabon', 1], ['Madrid', 2], ...]) %>
+----------------------------------------------------------------------------
+
+Notice that the third parameter, the options array, is the same kind of argument we pass to `options_for_select`. One thing that we have as an advantage here is that we don't have to worry about pre-selecting the correct city if the user already has one -- Rails will do this for us by reading from `@person.city_id` attribute.
+
+As before, if we were to use `select` helper on a form builder scoped to `@person` object, the syntax would be:
+
+----------------------------------------------------------------------------
+# select on a form builder
+<%= f.select(:city_id, ...) %>
+----------------------------------------------------------------------------
+
+Option tags from a collection of arbitrary objects
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Until now we were generating option tags from nested arrays with the help of `options_for_select` method. Data in our array were raw values:
+
+----------------------------------------------------------------------------
+<%= options_for_select([['Lisabon', 1], ['Madrid', 2], ...]) %>
+----------------------------------------------------------------------------
+
+But what if we had a *City* model (perhaps an ActiveRecord one) and we wanted to generate option tags from a collection of those objects? One solution would be to make a nested array by iterating over them:
+
+----------------------------------------------------------------------------
+<% cities_array = City.find(:all).map { |city| [city.name, city.id] } %>
+<%= options_for_select(cities_array) %>
+----------------------------------------------------------------------------
+
+This is a perfectly valid solution, but Rails provides us with a less verbose alternative: `options_from_collection_for_select`. This helper expects a collection of arbitrary objects and two additional arguments: the names of the methods to read the option *value* and *text* from, respectively:
+
+----------------------------------------------------------------------------
+<%= options_from_collection_for_select(City.all, :id, :name) %>
+----------------------------------------------------------------------------
+
+As the name implies, this only generates option tags. A method to go along with it is `collection_select`:
+
+----------------------------------------------------------------------------
+<%= collection_select(:person, :city_id, City.all, :id, :name) %>
----------------------------------------------------------------------------
-... \ No newline at end of file
+To recap, `options_from_collection_for_select` are to `collection_select` what `options_for_select` are to `select`.
+
+Time zone and country select
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To leverage time zone support in Rails, we have to ask our users what time zone they are in. Doing so would require generating select options from a list of pre-defined TimeZone objects using `collection_select`, but we can simply use the `time_zone_select` helper that already wraps this:
+
+----------------------------------------------------------------------------
+<%= time_zone_select(:person, :city_id) %>
+----------------------------------------------------------------------------
+
+There is also `time_zone_options_for_select` helper for a more manual (therefore more customizable) way of doing this. Read the API documentation to learn about the possible arguments for these two methods.
+
+When it comes to country select, Rails _used_ to have the built-in helper `country_select` but was extracted to a plugin.
+TODO: plugin URL
+
+
+Miscellaneous
+-------------
+
+File upload form
+~~~~~~~~~~~~~~~~
+
+:multipart - If set to true, the enctype is set to "multipart/form-data".
+
+Scoping out form controls with `fields_for`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Creates a scope around a specific model object like `form_for`, but doesn’t create the form tags themselves. This makes `fields_for` suitable for specifying additional model objects in the same form:
+
+Making custom form builders
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers, then use your custom builder. For example, let’s say you made a helper to automatically add labels to form inputs.
+
+----------------------------------------------------------------------------
+<% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
+ <%= f.text_field :first_name %>
+ <%= f.text_field :last_name %>
+ <%= text_area :person, :biography %>
+ <%= check_box_tag "person[admin]", @person.company.admin? %>
+<% end %>
+----------------------------------------------------------------------------
+
+
+* `form_for` within a namespace
+
+----------------------------------------------------------------------------
+select_tag(name, option_tags = nil, html_options = { :multiple, :disabled })
+
+select(object, method, choices, options = {}, html_options = {})
+options_for_select(container, selected = nil)
+
+collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
+options_from_collection_for_select(collection, value_method, text_method, selected = nil)
+
+time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
+time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
+----------------------------------------------------------------------------
diff --git a/railties/lib/commands/dbconsole.rb b/railties/lib/commands/dbconsole.rb
index 6ff895aa30..06848d3c91 100644
--- a/railties/lib/commands/dbconsole.rb
+++ b/railties/lib/commands/dbconsole.rb
@@ -41,7 +41,7 @@ when "mysql"
if config['password'] && include_password
args << "--password=#{config['password']}"
- elsif config['password'] && !config['password'].empty?
+ elsif config['password'] && !config['password'].to_s.empty?
args << "-p"
end
diff --git a/railties/lib/rails_generator/commands.rb b/railties/lib/rails_generator/commands.rb
index cacb3807d6..299044c3d7 100644
--- a/railties/lib/rails_generator/commands.rb
+++ b/railties/lib/rails_generator/commands.rb
@@ -294,7 +294,7 @@ HELP
file(relative_source, relative_destination, template_options) do |file|
# Evaluate any assignments in a temporary, throwaway binding.
vars = template_options[:assigns] || {}
- b = binding
+ b = template_options[:binding] || binding
vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
# Render the source file with the temporary binding.
diff --git a/railties/lib/rails_generator/generators/applications/app/template_runner.rb b/railties/lib/rails_generator/generators/applications/app/template_runner.rb
index c6113648e6..bb7bd0e6f4 100644
--- a/railties/lib/rails_generator/generators/applications/app/template_runner.rb
+++ b/railties/lib/rails_generator/generators/applications/app/template_runner.rb
@@ -8,23 +8,24 @@ require 'fileutils'
module Rails
class TemplateRunner
attr_reader :root
+ attr_writer :logger
def initialize(template, root = '') # :nodoc:
- @root = File.join(Dir.pwd, root)
+ @root = File.expand_path(File.directory?(root) ? root : File.join(Dir.pwd, root))
- puts "applying template: #{template}"
+ log 'applying', "template: #{template}"
load_template(template)
- puts "#{template} applied."
+ log 'applied', "#{template}"
end
def load_template(template)
begin
code = open(template).read
in_root { self.instance_eval(code) }
- rescue LoadError
- raise "The template [#{template}] could not be loaded."
+ rescue LoadError, Errno::ENOENT => e
+ raise "The template [#{template}] could not be loaded. Error: #{e}"
end
end
@@ -41,8 +42,8 @@ module Rails
#
# file("config/apach.conf", "your apache config")
#
- def file(filename, data = nil, &block)
- puts "creating file #{filename}"
+ def file(filename, data = nil, log_action = true, &block)
+ log 'file', filename if log_action
dir, file = [File.dirname(filename), File.basename(filename)]
inside(dir) do
@@ -66,7 +67,7 @@ module Rails
# plugin 'restful-authentication', :svn => 'svn://svnhub.com/technoweenie/restful-authentication/trunk'
#
def plugin(name, options)
- puts "installing plugin #{name}"
+ log 'plugin', name
if options[:git] && options[:submodule]
in_root do
@@ -74,28 +75,36 @@ module Rails
end
elsif options[:git] || options[:svn]
in_root do
- `script/plugin install #{options[:svn] || options[:git]}`
+ run("script/plugin install #{options[:svn] || options[:git]}", false)
end
else
- puts "! no git or svn provided for #{name}. skipping..."
+ log "! no git or svn provided for #{name}. skipping..."
end
end
# Adds an entry into config/environment.rb for the supplied gem :
def gem(name, options = {})
- puts "adding gem #{name}"
+ log 'gem', name
- sentinel = 'Rails::Initializer.run do |config|'
gems_code = "config.gem '#{name}'"
if options.any?
- opts = options.inject([]) {|result, h| result << [":#{h[0]} => '#{h[1]}'"] }.join(", ")
+ opts = options.inject([]) {|result, h| result << [":#{h[0]} => '#{h[1]}'"] }.sort.join(", ")
gems_code << ", #{opts}"
end
+ environment gems_code
+ end
+
+ # Adds a line inside the Initializer block for config/environment.rb. Used by #gem
+ def environment(data = nil, &block)
+ sentinel = 'Rails::Initializer.run do |config|'
+
+ data = block.call if !data && block_given?
+
in_root do
gsub_file 'config/environment.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
- "#{match}\n #{gems_code}"
+ "#{match}\n " << data
end
end
end
@@ -111,11 +120,11 @@ module Rails
def git(command = {})
in_root do
if command.is_a?(Symbol)
- puts "running git #{command}"
+ log 'running', "git #{command}"
Git.run(command.to_s)
else
command.each do |command, options|
- puts "running git #{command} #{options}"
+ log 'running', "git #{command} #{options}"
Git.run("#{command} #{options}")
end
end
@@ -135,16 +144,8 @@ module Rails
# vendor("foreign.rb", "# Foreign code is fun")
#
def vendor(filename, data = nil, &block)
- puts "vendoring file #{filename}"
- inside("vendor") do |folder|
- File.open("#{folder}/#{filename}", "w") do |f|
- if block_given?
- f.write(block.call)
- else
- f.write(data)
- end
- end
- end
+ log 'vendoring', filename
+ file("vendor/#{filename}", data, false, &block)
end
# Create a new file in the lib/ directory. Code can be specified
@@ -158,17 +159,9 @@ module Rails
#
# lib("foreign.rb", "# Foreign code is fun")
#
- def lib(filename, data = nil)
- puts "add lib file #{filename}"
- inside("lib") do |folder|
- File.open("#{folder}/#{filename}", "w") do |f|
- if block_given?
- f.write(block.call)
- else
- f.write(data)
- end
- end
- end
+ def lib(filename, data = nil, &block)
+ log 'lib', filename
+ file("lib/#{filename}", data, false, &block)
end
# Create a new Rakefile with the provided code (either in a block or a string).
@@ -190,16 +183,8 @@ module Rails
# rakefile("seed.rake", "puts 'im plantin ur seedz'")
#
def rakefile(filename, data = nil, &block)
- puts "adding rakefile #{filename}"
- inside("lib/tasks") do |folder|
- File.open("#{folder}/#{filename}", "w") do |f|
- if block_given?
- f.write(block.call)
- else
- f.write(data)
- end
- end
- end
+ log 'rakefile', filename
+ file("lib/tasks/#{filename}", data, false, &block)
end
# Create a new initializer with the provided code (either in a block or a string).
@@ -219,16 +204,8 @@ module Rails
# initializer("api.rb", "API_KEY = '123456'")
#
def initializer(filename, data = nil, &block)
- puts "adding initializer #{filename}"
- inside("config/initializers") do |folder|
- File.open("#{folder}/#{filename}", "w") do |f|
- if block_given?
- f.write(block.call)
- else
- f.write(data)
- end
- end
- end
+ log 'initializer', filename
+ file("config/initializers/#{filename}", data, false, &block)
end
# Generate something using a generator from Rails or a plugin.
@@ -240,10 +217,10 @@ module Rails
# generate(:authenticated, "user session")
#
def generate(what, *args)
- puts "generating #{what}"
+ log 'generating', what
argument = args.map(&:to_s).flatten.join(" ")
- in_root { `#{root}/script/generate #{what} #{argument}` }
+ in_root { run("script/generate #{what} #{argument}", false) }
end
# Executes a command
@@ -254,8 +231,8 @@ module Rails
# run('ln -s ~/edge rails)
# end
#
- def run(command)
- puts "executing #{command} from #{Dir.pwd}"
+ def run(command, log_action = true)
+ log 'executing', "#{command} from #{Dir.pwd}" if log_action
`#{command}`
end
@@ -268,10 +245,10 @@ module Rails
# rake("gems:install", :sudo => true)
#
def rake(command, options = {})
- puts "running rake task #{command}"
+ log 'rake', command
env = options[:env] || 'development'
sudo = options[:sudo] ? 'sudo ' : ''
- in_root { `#{sudo}rake #{command} RAILS_ENV=#{env}` }
+ in_root { run("#{sudo}rake #{command} RAILS_ENV=#{env}", false) }
end
# Just run the capify command in root
@@ -281,7 +258,8 @@ module Rails
# capify!
#
def capify!
- in_root { `capify .` }
+ log 'capifying'
+ in_root { run('capify .', false) }
end
# Add Rails to /vendor/rails
@@ -291,8 +269,8 @@ module Rails
# freeze!
#
def freeze!(args = {})
- puts "vendoring rails edge"
- in_root { `rake rails:freeze:edge` }
+ log 'vendor', 'rails edge'
+ in_root { run('rake rails:freeze:edge', false) }
end
# Make an entry in Rails routing file conifg/routes.rb
@@ -302,6 +280,7 @@ module Rails
# route "map.root :controller => :welcome"
#
def route(routing_code)
+ log 'route', routing_code
sentinel = 'ActionController::Routing::Routes.draw do |map|'
in_root do
@@ -321,7 +300,7 @@ module Rails
# freeze! if ask("Should I freeze the latest Rails?") == "yes"
#
def ask(string)
- puts string
+ log '', string
gets.strip
end
@@ -368,5 +347,23 @@ module Rails
def destination_path(relative_destination)
File.join(root, relative_destination)
end
+
+ def log(action, message = '')
+ logger.log(action, message)
+ end
+
+ def logger
+ @logger ||= Rails::Generator::Base.logger
+ end
+
+ def logger
+ @logger ||= if defined?(Rails::Generator::Base)
+ Rails::Generator::Base.logger
+ else
+ require 'rails_generator/simple_logger'
+ Rails::Generator::SimpleLogger.new(STDOUT)
+ end
+ end
+
end
end \ No newline at end of file
diff --git a/railties/lib/tasks/tmp.rake b/railties/lib/tasks/tmp.rake
index b191039d63..fea15058bb 100644
--- a/railties/lib/tasks/tmp.rake
+++ b/railties/lib/tasks/tmp.rake
@@ -2,7 +2,7 @@ namespace :tmp do
desc "Clear session, cache, and socket files from tmp/"
task :clear => [ "tmp:sessions:clear", "tmp:cache:clear", "tmp:sockets:clear"]
- desc "Creates tmp directories for sessions, cache, and sockets"
+ desc "Creates tmp directories for sessions, cache, sockets, and pids"
task :create do
FileUtils.mkdir_p(%w( tmp/sessions tmp/cache tmp/sockets tmp/pids ))
end
@@ -34,4 +34,4 @@ namespace :tmp do
FileUtils.rm(Dir['tmp/pids/[^.]*'])
end
end
-end \ No newline at end of file
+end
diff --git a/railties/test/fcgi_dispatcher_test.rb b/railties/test/fcgi_dispatcher_test.rb
index cc054c24aa..c469c5dd01 100644
--- a/railties/test/fcgi_dispatcher_test.rb
+++ b/railties/test/fcgi_dispatcher_test.rb
@@ -1,10 +1,9 @@
require 'abstract_unit'
begin
+require 'action_controller'
require 'fcgi_handler'
-module ActionController; module Routing; module Routes; end end end
-
class RailsFCGIHandlerTest < Test::Unit::TestCase
def setup
@log = StringIO.new
@@ -131,19 +130,11 @@ class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
end
end
- class ::Dispatcher
- class << self
- attr_accessor :signal
- alias_method :old_dispatch, :dispatch
- def dispatch(cgi)
- signal ? Process.kill(signal, $$) : old_dispatch
- end
- end
- end
-
def setup
@log = StringIO.new
@handler = RailsFCGIHandler.new(@log)
+ @dispatcher = mock
+ Dispatcher.stubs(:new).returns(@dispatcher)
end
def test_interrupted_via_HUP_when_not_in_request
@@ -159,19 +150,6 @@ class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
assert_equal :reload, @handler.when_ready
end
- def test_interrupted_via_HUP_when_in_request
- cgi = mock
- FCGI.expects(:each_cgi).once.yields(cgi)
- Dispatcher.expects(:signal).times(2).returns('HUP')
-
- @handler.expects(:reload!).once
- @handler.expects(:close_connection).never
- @handler.expects(:exit).never
-
- @handler.process!
- assert_equal :reload, @handler.when_ready
- end
-
def test_interrupted_via_USR1_when_not_in_request
cgi = mock
FCGI.expects(:each_cgi).once.yields(cgi)
@@ -186,19 +164,6 @@ class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
assert_nil @handler.when_ready
end
- def test_interrupted_via_USR1_when_in_request
- cgi = mock
- FCGI.expects(:each_cgi).once.yields(cgi)
- Dispatcher.expects(:signal).times(2).returns('USR1')
-
- @handler.expects(:reload!).never
- @handler.expects(:close_connection).with(cgi).once
- @handler.expects(:exit).never
-
- @handler.process!
- assert_equal :exit, @handler.when_ready
- end
-
def test_restart_via_USR2_when_in_request
cgi = mock
FCGI.expects(:each_cgi).once.yields(cgi)
@@ -217,7 +182,7 @@ class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
def test_interrupted_via_TERM
cgi = mock
FCGI.expects(:each_cgi).once.yields(cgi)
- Dispatcher.expects(:signal).times(2).returns('TERM')
+ ::Rack::Handler::FastCGI.expects(:serve).once.returns('TERM')
@handler.expects(:reload!).never
@handler.expects(:close_connection).never
@@ -238,7 +203,7 @@ class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
cgi = mock
error = RuntimeError.new('foo')
FCGI.expects(:each_cgi).once.yields(cgi)
- Dispatcher.expects(:dispatch).once.with(cgi).raises(error)
+ ::Rack::Handler::FastCGI.expects(:serve).once.raises(error)
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^unhandled/))
@handler.process!
end
@@ -254,7 +219,7 @@ class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
cgi = mock
error = SignalException.new('USR2')
FCGI.expects(:each_cgi).once.yields(cgi)
- Dispatcher.expects(:dispatch).once.with(cgi).raises(error)
+ ::Rack::Handler::FastCGI.expects(:serve).once.raises(error)
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^stopping/))
@handler.process!
end
@@ -284,7 +249,7 @@ class RailsFCGIHandlerPeriodicGCTest < Test::Unit::TestCase
cgi = mock
FCGI.expects(:each_cgi).times(10).yields(cgi)
- Dispatcher.expects(:dispatch).times(10).with(cgi)
+ Dispatcher.expects(:new).times(10)
@handler.expects(:run_gc!).never
9.times { @handler.process! }
diff --git a/railties/test/gem_dependency_test.rb b/railties/test/gem_dependency_test.rb
index 30fd899fea..6c1f0961a1 100644
--- a/railties/test/gem_dependency_test.rb
+++ b/railties/test/gem_dependency_test.rb
@@ -9,33 +9,33 @@ Rails::VendorGemSourceIndex.silence_spec_warnings = true
uses_mocha "Plugin Tests" do
class GemDependencyTest < Test::Unit::TestCase
def setup
- @gem = Rails::GemDependency.new "hpricot"
- @gem_with_source = Rails::GemDependency.new "hpricot", :source => "http://code.whytheluckystiff.net"
- @gem_with_version = Rails::GemDependency.new "hpricot", :version => "= 0.6"
- @gem_with_lib = Rails::GemDependency.new "aws-s3", :lib => "aws/s3"
- @gem_without_load = Rails::GemDependency.new "hpricot", :lib => false
+ @gem = Rails::GemDependency.new "xhpricotx"
+ @gem_with_source = Rails::GemDependency.new "xhpricotx", :source => "http://code.whytheluckystiff.net"
+ @gem_with_version = Rails::GemDependency.new "xhpricotx", :version => "= 0.6"
+ @gem_with_lib = Rails::GemDependency.new "xaws-s3x", :lib => "aws/s3"
+ @gem_without_load = Rails::GemDependency.new "xhpricotx", :lib => false
end
def test_configuration_adds_gem_dependency
config = Rails::Configuration.new
- config.gem "aws-s3", :lib => "aws/s3", :version => "0.4.0"
- assert_equal [["install", "aws-s3", "--version", '"= 0.4.0"']], config.gems.collect(&:install_command)
+ config.gem "xaws-s3x", :lib => "aws/s3", :version => "0.4.0"
+ assert_equal [["install", "xaws-s3x", "--version", '"= 0.4.0"']], config.gems.collect(&:install_command)
end
def test_gem_creates_install_command
- assert_equal %w(install hpricot), @gem.install_command
+ assert_equal %w(install xhpricotx), @gem.install_command
end
def test_gem_with_source_creates_install_command
- assert_equal %w(install hpricot --source http://code.whytheluckystiff.net), @gem_with_source.install_command
+ assert_equal %w(install xhpricotx --source http://code.whytheluckystiff.net), @gem_with_source.install_command
end
def test_gem_with_version_creates_install_command
- assert_equal ["install", "hpricot", "--version", '"= 0.6"'], @gem_with_version.install_command
+ assert_equal ["install", "xhpricotx", "--version", '"= 0.6"'], @gem_with_version.install_command
end
def test_gem_creates_unpack_command
- assert_equal %w(unpack hpricot), @gem.unpack_command
+ assert_equal %w(unpack xhpricotx), @gem.unpack_command
end
def test_gem_with_version_unpack_install_command
@@ -43,7 +43,7 @@ uses_mocha "Plugin Tests" do
mock_spec = mock()
mock_spec.stubs(:version).returns('0.6')
@gem_with_version.stubs(:specification).returns(mock_spec)
- assert_equal ["unpack", "hpricot", "--version", '= 0.6'], @gem_with_version.unpack_command
+ assert_equal ["unpack", "xhpricotx", "--version", '= 0.6'], @gem_with_version.unpack_command
end
def test_gem_adds_load_paths
diff --git a/railties/test/generators/rails_template_runner_test.rb b/railties/test/generators/rails_template_runner_test.rb
new file mode 100644
index 0000000000..fcc020603d
--- /dev/null
+++ b/railties/test/generators/rails_template_runner_test.rb
@@ -0,0 +1,190 @@
+require 'abstract_unit'
+require 'generators/generator_test_helper'
+
+class RailsTemplateRunnerTest < GeneratorTestCase
+ def setup
+ Rails::Generator::Base.use_application_sources!
+ run_generator('app', [RAILS_ROOT])
+ # generate empty template
+ @template_path = File.join(RAILS_ROOT, 'template.rb')
+ File.open(File.join(@template_path), 'w') {|f| f << '' }
+
+ @git_plugin_uri = 'git://github.com/technoweenie/restful-authentication.git'
+ @svn_plugin_uri = 'svn://svnhub.com/technoweenie/restful-authentication/trunk'
+ end
+
+ def teardown
+ super
+ rm_rf "#{RAILS_ROOT}/README"
+ rm_rf "#{RAILS_ROOT}/Rakefile"
+ rm_rf "#{RAILS_ROOT}/doc"
+ rm_rf "#{RAILS_ROOT}/lib"
+ rm_rf "#{RAILS_ROOT}/log"
+ rm_rf "#{RAILS_ROOT}/script"
+ rm_rf "#{RAILS_ROOT}/vendor"
+ rm_rf "#{RAILS_ROOT}/tmp"
+ rm_rf "#{RAILS_ROOT}/Capfile"
+ rm_rf @template_path
+ end
+
+ def test_initialize_should_load_template
+ Rails::TemplateRunner.any_instance.expects(:load_template).with(@template_path)
+ silence_generator do
+ Rails::TemplateRunner.new(@template_path, RAILS_ROOT)
+ end
+ end
+
+ def test_initialize_should_raise_error_on_missing_template_file
+ assert_raise(RuntimeError) do
+ silence_generator do
+ Rails::TemplateRunner.new('non/existent/path/to/template.rb', RAILS_ROOT)
+ end
+ end
+ end
+
+ def test_file_should_write_data_to_file_path
+ run_template_method(:file, 'lib/test_file.rb', 'heres test data')
+ assert_generated_file_with_data 'lib/test_file.rb', 'heres test data'
+ end
+
+ def test_file_should_write_block_contents_to_file_path
+ run_template_method(:file, 'lib/test_file.rb') { 'heres block data' }
+ assert_generated_file_with_data 'lib/test_file.rb', 'heres block data'
+ end
+
+ def test_plugin_with_git_option_should_run_plugin_install
+ expects_run_with_command("script/plugin install #{@git_plugin_uri}")
+ run_template_method(:plugin, 'restful-authentication', :git => @git_plugin_uri)
+ end
+
+ def test_plugin_with_svn_option_should_run_plugin_install
+ expects_run_with_command("script/plugin install #{@svn_plugin_uri}")
+ run_template_method(:plugin, 'restful-authentication', :svn => @svn_plugin_uri)
+ end
+
+ def test_plugin_with_git_option_and_submodule_should_use_git_scm
+ Rails::Git.expects(:run).with("submodule add #{@git_plugin_uri} vendor/plugins/rest_auth")
+ run_template_method(:plugin, 'rest_auth', :git => @git_plugin_uri, :submodule => true)
+ end
+
+ def test_plugin_with_no_options_should_skip_method
+ Rails::TemplateRunner.any_instance.expects(:run).never
+ run_template_method(:plugin, 'rest_auth', {})
+ end
+
+ def test_gem_should_put_gem_dependency_in_enviroment
+ run_template_method(:gem, 'will-paginate')
+ assert_rails_initializer_includes("config.gem 'will-paginate'")
+ end
+
+ def test_gem_with_options_should_include_options_in_gem_dependency_in_environment
+ run_template_method(:gem, 'mislav-will-paginate', :lib => 'will-paginate', :source => 'http://gems.github.com')
+ assert_rails_initializer_includes("config.gem 'mislav-will-paginate', :lib => 'will-paginate', :source => 'http://gems.github.com'")
+ end
+
+ def test_environment_should_include_data_in_environment_initializer_block
+ load_paths = 'config.load_paths += %w["#{RAILS_ROOT}/app/extras"]'
+ run_template_method(:environment, load_paths)
+ assert_rails_initializer_includes(load_paths)
+ end
+
+ def test_environment_with_block_should_include_block_contents_in_environment_initializer_block
+ run_template_method(:environment) do
+ '# This wont be added'
+ '# This will be added'
+ end
+ assert_rails_initializer_includes('# This will be added')
+ end
+
+ def test_git_with_symbol_should_run_command_using_git_scm
+ Rails::Git.expects(:run).once.with('init')
+ run_template_method(:git, :init)
+ end
+
+ def test_git_with_hash_should_run_each_command_using_git_scm
+ Rails::Git.expects(:run).times(2)
+ run_template_method(:git, {:init => '', :add => '.'})
+ end
+
+ def test_vendor_should_write_data_to_file_in_vendor
+ run_template_method(:vendor, 'vendor_file.rb', '# vendor data')
+ assert_generated_file_with_data('vendor/vendor_file.rb', '# vendor data')
+ end
+
+ def test_lib_should_write_data_to_file_in_lib
+ run_template_method(:lib, 'my_library.rb', 'class MyLibrary')
+ assert_generated_file_with_data('lib/my_library.rb', 'class MyLibrary')
+ end
+
+ def test_rakefile_should_write_date_to_file_in_lib_tasks
+ run_template_method(:rakefile, 'myapp.rake', 'task :run => [:environment]')
+ assert_generated_file_with_data('lib/tasks/myapp.rake', 'task :run => [:environment]')
+ end
+
+ def test_initializer_should_write_date_to_file_in_config_initializers
+ run_template_method(:initializer, 'constants.rb', 'MY_CONSTANT = 42')
+ assert_generated_file_with_data('config/initializers/constants.rb', 'MY_CONSTANT = 42')
+ end
+
+ def test_generate_should_run_script_generate_with_argument_and_options
+ expects_run_with_command('script/generate model MyModel')
+ run_template_method(:generate, 'model', 'MyModel')
+ end
+
+ def test_rake_should_run_rake_command_with_development_env
+ expects_run_with_command('rake log:clear RAILS_ENV=development')
+ run_template_method(:rake, 'log:clear')
+ end
+
+ def test_rake_with_env_option_should_run_rake_command_in_env
+ expects_run_with_command('rake log:clear RAILS_ENV=production')
+ run_template_method(:rake, 'log:clear', :env => 'production')
+ end
+
+ def test_rake_with_sudo_option_should_run_rake_command_with_sudo
+ expects_run_with_command('sudo rake log:clear RAILS_ENV=development')
+ run_template_method(:rake, 'log:clear', :sudo => true)
+ end
+
+ def test_capify_should_run_the_capify_command
+ expects_run_with_command('capify .')
+ run_template_method(:capify!)
+ end
+
+ def test_freeze_should_freeze_rails_edge
+ expects_run_with_command('rake rails:freeze:edge')
+ run_template_method(:freeze!)
+ end
+
+ def test_route_should_add_data_to_the_routes_block_in_config_routes
+ route_command = "map.route '/login', :controller => 'sessions', :action => 'new'"
+ run_template_method(:route, route_command)
+ assert_generated_file_with_data 'config/routes.rb', route_command
+ end
+
+ protected
+ def run_template_method(method_name, *args, &block)
+ silence_generator do
+ @template_runner = Rails::TemplateRunner.new(@template_path, RAILS_ROOT)
+ @template_runner.send(method_name, *args, &block)
+ end
+ end
+
+ def expects_run_with_command(command)
+ Rails::TemplateRunner.any_instance.stubs(:run).once.with(command, false)
+ end
+
+ def assert_rails_initializer_includes(data, message = nil)
+ message ||= "Rails::Initializer should include #{data}"
+ assert_generated_file 'config/environment.rb' do |body|
+ assert_match(/#{Regexp.escape("Rails::Initializer.run do |config|")}.+#{Regexp.escape(data)}.+end/m, body, message)
+ end
+ end
+
+ def assert_generated_file_with_data(file, data, message = nil)
+ message ||= "#{file} should include '#{data}'"
+ assert_generated_file(file) do |file|
+ assert_match(/#{Regexp.escape(data)}/,file, message)
+ end
+ end
+end \ No newline at end of file