diff options
364 files changed, 7483 insertions, 6549 deletions
diff --git a/.gitignore b/.gitignore index 8c5dfb64e1..bba7d5dce8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ debug.log +doc/rdoc activeresource/doc activerecord/doc actionpack/doc @@ -1,4 +1,6 @@ require 'rake' +require 'rake/rdoctask' +require 'rake/contrib/sshpublisher' env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD'] @@ -11,7 +13,7 @@ end desc 'Run all tests by default' task :default => :test -%w(test rdoc package pgem release).each do |task_name| +%w(test rdoc pgem package release).each do |task_name| desc "Run #{task_name} task for all projects" task task_name do PROJECTS.each do |project| @@ -19,3 +21,61 @@ task :default => :test end end end + + +desc "Generate documentation for the Rails framework" +Rake::RDocTask.new do |rdoc| + rdoc.rdoc_dir = 'doc/rdoc' + rdoc.title = "Ruby on Rails Documentation" + + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.options << '-A cattr_accessor=object' + rdoc.options << '--charset' << 'utf-8' + + rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : './doc/template/horo' + + rdoc.rdoc_files.include('railties/CHANGELOG') + rdoc.rdoc_files.include('railties/MIT-LICENSE') + rdoc.rdoc_files.include('railties/README') + rdoc.rdoc_files.include('railties/lib/{*.rb,commands/*.rb,rails/*.rb,rails_generator/*.rb}') + + rdoc.rdoc_files.include('activerecord/README') + rdoc.rdoc_files.include('activerecord/CHANGELOG') + rdoc.rdoc_files.include('activerecord/lib/active_record/**/*.rb') + rdoc.rdoc_files.exclude('activerecord/lib/active_record/vendor/*') + + rdoc.rdoc_files.include('activeresource/README') + rdoc.rdoc_files.include('activeresource/CHANGELOG') + rdoc.rdoc_files.include('activeresource/lib/active_resource.rb') + rdoc.rdoc_files.include('activeresource/lib/active_resource/*') + + rdoc.rdoc_files.include('actionpack/README') + rdoc.rdoc_files.include('actionpack/CHANGELOG') + rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.rb') + rdoc.rdoc_files.include('actionpack/lib/action_view/**/*.rb') + rdoc.rdoc_files.exclude('actionpack/lib/action_controller/vendor/*') + + rdoc.rdoc_files.include('actionmailer/README') + rdoc.rdoc_files.include('actionmailer/CHANGELOG') + rdoc.rdoc_files.include('actionmailer/lib/action_mailer/base.rb') + rdoc.rdoc_files.exclude('actionmailer/lib/action_mailer/vendor/*') + + rdoc.rdoc_files.include('activesupport/README') + rdoc.rdoc_files.include('activesupport/CHANGELOG') + rdoc.rdoc_files.include('activesupport/lib/active_support/**/*.rb') + rdoc.rdoc_files.exclude('activesupport/lib/active_support/vendor/*') +end + +# Enhance rdoc task to copy referenced images also +task :rdoc do + FileUtils.mkdir_p "doc/rdoc/files/examples/" + FileUtils.copy "activerecord/examples/associations.png", "doc/rdoc/files/examples/associations.png" +end + +desc "Publish API docs for Rails as a whole and for each component" +task :pdoc => :rdoc do + Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/api", "doc/rdoc").upload + PROJECTS.each do |project| + system %(cd #{project} && #{env} #{$0} pdoc) + end +end diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index c09526cbef..612bd27774 100755 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -35,7 +35,7 @@ Rake::RDocTask.new { |rdoc| rdoc.title = "Action Mailer -- Easy email delivery and testing" rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' rdoc.options << '--charset' << 'utf-8' - rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' rdoc.rdoc_files.include('README', 'CHANGELOG') rdoc.rdoc_files.include('lib/action_mailer.rb') rdoc.rdoc_files.include('lib/action_mailer/*.rb') @@ -76,12 +76,13 @@ end desc "Publish the API documentation" task :pgem => [:package] do - Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload + Rake::SshFilePublisher.new("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload + `ssh wrath.rubyonrails.org './gemupdate.sh'` end desc "Publish the API documentation" task :pdoc => [:rdoc] do - Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/am", "doc").upload + Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/am", "doc").upload end desc "Publish the release files to RubyForge." diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index e065132107..1518e23dfe 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -426,8 +426,7 @@ module ActionMailer #:nodoc: end def template_root=(root) - write_inheritable_attribute(:template_root, root) - ActionView::TemplateFinder.process_view_paths(root) + write_inheritable_attribute(:template_root, ActionView::ViewLoadPaths.new(Array(root))) end end @@ -530,7 +529,7 @@ module ActionMailer #:nodoc: end def render_message(method_name, body) - render :file => method_name, :body => body + render :file => method_name, :body => body, :use_full_path => true end def render(opts) @@ -538,6 +537,7 @@ module ActionMailer #:nodoc: if opts[:file] && opts[:file] !~ /\// opts[:file] = "#{mailer_name}/#{opts[:file]}" end + opts[:use_full_path] = true initialize_template_class(body).render(opts) end @@ -546,7 +546,7 @@ module ActionMailer #:nodoc: end def initialize_template_class(assigns) - ActionView::Base.new([template_root], assigns, self) + ActionView::Base.new(template_root, assigns, self) end def sort_parts(parts, order = []) diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index e5ecb0e254..7f4a8817ca 100755 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -942,13 +942,13 @@ end # uses_mocha class InheritableTemplateRootTest < Test::Unit::TestCase def test_attr expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots" - assert_equal expected, FunkyPathMailer.template_root + assert_equal [expected], FunkyPathMailer.template_root.map(&:to_s) sub = Class.new(FunkyPathMailer) sub.template_root = 'test/path' - assert_equal 'test/path', sub.template_root - assert_equal expected, FunkyPathMailer.template_root + assert_equal ['test/path'], sub.template_root.map(&:to_s) + assert_equal [expected], FunkyPathMailer.template_root.map(&:to_s) end end diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 9622029362..a28bf66b88 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,3 +1,29 @@ +*Edge* + +* Fix polymorphic_url with singleton resources. #461 [Tammer Saleh] + +* Replaced TemplateFinder abstraction with ViewLoadPaths [Josh Peek] + +* Added block-call style to link_to [Sam Stephenson/DHH]. Example: + + <% link_to(@profile) do %> + <strong><%= @profile.name %></strong> -- <span>Check it out!!</span> + <% end %> + +* Performance: integration test benchmarking and profiling. [Jeremy Kemper] + +* Make caching more aware of mime types. Ensure request format is not considered while expiring cache. [Jonathan del Strother] + +* Drop ActionController::Base.allow_concurrency flag [Josh Peek] + +* More efficient concat and capture helpers. Remove ActionView::Base.erb_variable. [Jeremy Kemper] + +* Added page.reload functionality. Resolves #277. [Sean Huber] + +* Fixed Request#remote_ip to only raise hell if the HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR doesn't match (not just if they're both present) [Mark Imbriaco, Bradford Folkens] + +* Allow caches_action to accept a layout option [José Valim] + * Added Rack processor [Ezra Zygmuntowicz, Josh Peek] diff --git a/actionpack/Rakefile b/actionpack/Rakefile index b37f756c1e..1880f21f67 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -49,12 +49,14 @@ Rake::RDocTask.new { |rdoc| rdoc.title = "Action Pack -- On rails from request to response" rdoc.options << '--line-numbers' << '--inline-source' rdoc.options << '--charset' << 'utf-8' - rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' if ENV['DOC_FILES'] rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/)) else rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG') - rdoc.rdoc_files.include('lib/**/*.rb') + rdoc.rdoc_files.include(Dir['lib/**/*.rb'] - + Dir['lib/*/vendor/**/*.rb']) + rdoc.rdoc_files.exclude('lib/actionpack.rb') end } @@ -132,13 +134,13 @@ task :update_js => [ :update_scriptaculous ] desc "Publish the API documentation" task :pgem => [:package] do - Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload - `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'` + Rake::SshFilePublisher.new("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload + `ssh wrath.rubyonrails.org './gemupdate.sh'` end desc "Publish the API documentation" task :pdoc => [:rdoc] do - Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ap", "doc").upload + Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload end desc "Publish the release files to RubyForge." diff --git a/actionpack/lib/action_controller/assertions/response_assertions.rb b/actionpack/lib/action_controller/assertions/response_assertions.rb index c5fc6c7966..3deda0b45a 100644 --- a/actionpack/lib/action_controller/assertions/response_assertions.rb +++ b/actionpack/lib/action_controller/assertions/response_assertions.rb @@ -97,7 +97,7 @@ module ActionController value['controller'] = value['controller'].to_s if key == :actual && value['controller'].first != '/' && !value['controller'].include?('/') new_controller_path = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.controller_path) - value['controller'] = new_controller_path if value['controller'] != new_controller_path && ActionController::Routing.possible_controllers.include?(new_controller_path) + value['controller'] = new_controller_path if value['controller'] != new_controller_path && ActionController::Routing.possible_controllers.include?(new_controller_path) && @response.redirected_to.is_a?(Hash) end value['controller'] = value['controller'][1..-1] if value['controller'].first == '/' # strip leading hash end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 6222c3205d..817b270feb 100755 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -283,13 +283,6 @@ module ActionController #:nodoc: @@debug_routes = true cattr_accessor :debug_routes - # Indicates to Mongrel or Webrick whether to allow concurrent action - # processing. Your controller actions and any other code they call must - # also behave well when called from concurrent threads. Turned off by - # default. - @@allow_concurrency = false - cattr_accessor :allow_concurrency - # Modern REST web services often need to submit complex data to the web application. # The <tt>@@param_parsers</tt> hash lets you register handlers which will process the HTTP body and add parameters to the # <tt>params</tt> hash. These handlers are invoked for POST and PUT requests. @@ -428,8 +421,7 @@ module ActionController #:nodoc: end def view_paths=(value) - @view_paths = value - ActionView::TemplateFinder.process_view_paths(value) + @view_paths = ActionView::ViewLoadPaths.new(Array(value)) if value end # Adds a view_path to the front of the view_paths array. @@ -441,8 +433,7 @@ module ActionController #:nodoc: # def prepend_view_path(path) @view_paths = superclass.view_paths.dup if @view_paths.nil? - view_paths.unshift(*path) - ActionView::TemplateFinder.process_view_paths(path) + @view_paths.unshift(*path) end # Adds a view_path to the end of the view_paths array. @@ -454,8 +445,7 @@ module ActionController #:nodoc: # def append_view_path(path) @view_paths = superclass.view_paths.dup if @view_paths.nil? - view_paths.push(*path) - ActionView::TemplateFinder.process_view_paths(path) + @view_paths.push(*path) end # Replace sensitive parameter data from the request log. @@ -613,8 +603,8 @@ module ActionController #:nodoc: # # This takes the current URL as is and only exchanges the action. In contrast, <tt>url_for :action => 'print'</tt> # would have slashed-off the path components after the changed action. - def url_for(options = nil) #:doc: - case options || {} + def url_for(options = {}) #:doc: + case options when String options when Hash @@ -647,11 +637,11 @@ module ActionController #:nodoc: # View load paths for controller. def view_paths - @template.finder.view_paths + @template.view_paths end def view_paths=(value) - @template.finder.view_paths = value # Mutex needed + @template.view_paths = ViewLoadPaths.new(value) end # Adds a view_path to the front of the view_paths array. @@ -661,7 +651,7 @@ module ActionController #:nodoc: # self.prepend_view_path(["views/default", "views/custom"]) # def prepend_view_path(path) - @template.finder.prepend_view_path(path) # Mutex needed + @template.view_paths.unshift(*path) end # Adds a view_path to the end of the view_paths array. @@ -671,7 +661,7 @@ module ActionController #:nodoc: # self.append_view_path(["views/default", "views/custom"]) # def append_view_path(path) - @template.finder.append_view_path(path) # Mutex needed + @template.view_paths.push(*path) end protected @@ -1232,7 +1222,7 @@ module ActionController #:nodoc: end def template_exists?(template_name = default_template_name) - @template.finder.file_exists?(template_name) + @template.file_exists?(template_name) end def template_public?(template_name = default_template_name) @@ -1240,9 +1230,8 @@ module ActionController #:nodoc: end def template_exempt_from_layout?(template_name = default_template_name) - extension = @template && @template.finder.pick_template_extension(template_name) - name_with_extension = !template_name.include?('.') && extension ? "#{template_name}.#{extension}" : template_name - @@exempt_from_layout.any? { |ext| name_with_extension =~ ext } + template_name = @template.send(:template_file_from_name, template_name) if @template + @@exempt_from_layout.any? { |ext| template_name.to_s =~ ext } end def default_template_name(action_name = self.action_name) diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index 1ef9e60a21..65a36f7f98 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -40,6 +40,8 @@ module ActionController #:nodoc: # controller.send(:list_url, c.params[:id]) } # end # + # If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information. + # module Actions def self.included(base) #:nodoc: base.extend(ClassMethods) @@ -54,7 +56,8 @@ module ActionController #:nodoc: def caches_action(*actions) return unless cache_configured? options = actions.extract_options! - around_filter(ActionCacheFilter.new(:cache_path => options.delete(:cache_path)), {:only => actions}.merge(options)) + cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path)) + around_filter(cache_filter, {:only => actions}.merge(options)) end end @@ -64,10 +67,10 @@ module ActionController #:nodoc: if options[:action].is_a?(Array) options[:action].dup.each do |action| - expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }))) + expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }), false)) end else - expire_fragment(ActionCachePath.path_for(self, options)) + expire_fragment(ActionCachePath.path_for(self, options, false)) end end @@ -81,7 +84,9 @@ module ActionController #:nodoc: if cache = controller.read_fragment(cache_path.path) controller.rendered_action_cache = true set_content_type!(controller, cache_path.extension) - controller.send!(:render_for_text, cache) + options = { :text => cache } + options.merge!(:layout => true) if cache_layout? + controller.send!(:render, options) false else controller.action_cache_path = cache_path @@ -90,7 +95,8 @@ module ActionController #:nodoc: def after(controller) return if controller.rendered_action_cache || !caching_allowed(controller) - controller.write_fragment(controller.action_cache_path.path, controller.response.body) + action_content = cache_layout? ? content_for_layout(controller) : controller.response.body + controller.write_fragment(controller.action_cache_path.path, action_content) end private @@ -105,22 +111,38 @@ module ActionController #:nodoc: def caching_allowed(controller) controller.request.get? && controller.response.headers['Status'].to_i == 200 end + + def cache_layout? + @options[:layout] == false + end + + def content_for_layout(controller) + controller.response.layout && controller.response.template.instance_variable_get('@content_for_layout') + end end class ActionCachePath attr_reader :path, :extension class << self - def path_for(controller, options) - new(controller, options).path + def path_for(controller, options, infer_extension=true) + new(controller, options, infer_extension).path end end - - def initialize(controller, options = {}) - @extension = extract_extension(controller.request.path) + + # When true, infer_extension will look up the cache path extension from the request's path & format. + # This is desirable when reading and writing the cache, but not when expiring the cache - expire_action should expire the same files regardless of the request format. + def initialize(controller, options = {}, infer_extension=true) + if infer_extension and options.is_a? Hash + request_extension = extract_extension(controller.request) + options = options.reverse_merge(:format => request_extension) + end path = controller.url_for(options).split('://').last normalize!(path) - add_extension!(path, @extension) + if infer_extension + @extension = request_extension + add_extension!(path, @extension) + end @path = URI.unescape(path) end @@ -130,13 +152,22 @@ module ActionController #:nodoc: end def add_extension!(path, extension) - path << ".#{extension}" if extension + path << ".#{extension}" if extension and !path.ends_with?(extension) end - - def extract_extension(file_path) + + def extract_extension(request) # Don't want just what comes after the last '.' to accommodate multi part extensions # such as tar.gz. - file_path[/^[^.]+\.(.+)$/, 1] + extension = request.path[/^[^.]+\.(.+)$/, 1] + + # If there's no extension in the path, check request.format + if extension.nil? + extension = request.format.to_sym.to_s + if extension=='all' + extension = nil + end + end + extension end end end diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 578e031a17..45946421fc 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -61,16 +61,18 @@ module ActionController #:nodoc: end def fragment_for(block, name = {}, options = nil) #:nodoc: - unless perform_caching then block.call; return end - - buffer = yield - - if cache = read_fragment(name, options) - buffer.concat(cache) + if perform_caching + buffer = yield + + if cache = read_fragment(name, options) + buffer.concat(cache) + else + pos = buffer.length + block.call + write_fragment(name, buffer[pos..-1], options) + end else - pos = buffer.length block.call - write_fragment(name, buffer[pos..-1], options) end end diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index b40f1ba9be..7df987d525 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -125,7 +125,7 @@ module ActionController def call(env) @request = RackRequest.new(env) - @response = RackResponse.new + @response = RackResponse.new(@request) dispatch end @@ -134,14 +134,14 @@ module ActionController run_callbacks :prepare_dispatch Routing::Routes.reload - ActionView::TemplateFinder.reload! unless ActionView::Base.cache_template_loading + ActionController::Base.view_paths.reload! end # Cleanup the application by clearing out loaded classes so they can # be reloaded on the next request without restarting the server. def cleanup_application ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord) - Dependencies.clear + ActiveSupport::Dependencies.clear ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord) end diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index 12e1b4d493..2a732448f2 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -1,9 +1,10 @@ -require 'stringio' -require 'uri' - +require 'active_support/test_case' require 'action_controller/dispatcher' require 'action_controller/test_process' +require 'stringio' +require 'uri' + module ActionController module Integration #:nodoc: # An integration Session instance represents a set of requests and responses @@ -580,7 +581,7 @@ EOF # end # end # end - class IntegrationTest < Test::Unit::TestCase + class IntegrationTest < ActiveSupport::TestCase include Integration::Runner # Work around a bug in test/unit caused by the default test being named diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb index b5b59f2d7c..0721f71498 100644 --- a/actionpack/lib/action_controller/layout.rb +++ b/actionpack/lib/action_controller/layout.rb @@ -304,7 +304,7 @@ module ActionController #:nodoc: end def layout_directory?(layout_name) - @template.finder.find_template_extension_from_handler(File.join('layouts', layout_name)) + @template.view_paths.find_template_file_for_path("#{File.join('layouts', layout_name)}.#{@template.template_format}.erb") ? true : false end end end diff --git a/actionpack/lib/action_controller/performance_test.rb b/actionpack/lib/action_controller/performance_test.rb new file mode 100644 index 0000000000..85543fffae --- /dev/null +++ b/actionpack/lib/action_controller/performance_test.rb @@ -0,0 +1,16 @@ +require 'action_controller/integration' +require 'active_support/testing/performance' +require 'active_support/testing/default' + +module ActionController + # An integration test that runs a code profiler on your test methods. + # Profiling output for combinations of each test method, measurement, and + # output format are written to your tmp/performance directory. + # + # By default, process_time is measured and both flat and graph_html output + # formats are written, so you'll have two output files per test method. + class PerformanceTest < ActionController::IntegrationTest + include ActiveSupport::Testing::Performance + include ActiveSupport::Testing::Default + end +end diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index 509fa6a08e..7c30bf0778 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -48,6 +48,9 @@ module ActionController # # # calls post_url(post) # polymorphic_url(post) # => "http://example.com/posts/1" + # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1" + # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1" + # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1" # # ==== Options # @@ -83,8 +86,6 @@ module ActionController else [ record_or_hash_or_array ] end - args << format if format - inflection = case when options[:action].to_s == "new" @@ -96,6 +97,9 @@ module ActionController else :singular end + + args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)} + args << format if format named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options) send!(named_route, *args) @@ -136,11 +140,19 @@ module ActionController else record = records.pop route = records.inject("") do |string, parent| - string << "#{RecordIdentifier.send!("singular_class_name", parent)}_" + if parent.is_a?(Symbol) || parent.is_a?(String) + string << "#{parent}_" + else + string << "#{RecordIdentifier.send!("singular_class_name", parent)}_" + end end end - route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_" + if record.is_a?(Symbol) || record.is_a?(String) + route << "#{record}_" + else + route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_" + end action_prefix(options) + namespace + route + routing_type(options).to_s end @@ -163,16 +175,17 @@ module ActionController end end + # Remove the first symbols from the array and return the url prefix + # implied by those symbols. def extract_namespace(record_or_hash_or_array) - returning "" do |namespace| - if record_or_hash_or_array.is_a?(Array) - record_or_hash_or_array.delete_if do |record_or_namespace| - if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol) - namespace << "#{record_or_namespace}_" - end - end - end + return "" unless record_or_hash_or_array.is_a?(Array) + + namespace_keys = [] + while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol) + namespace_keys << record_or_hash_or_array.shift end + + namespace_keys.map {|k| "#{k}_"}.join end end end diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb index f42212e740..58a0a9280d 100644 --- a/actionpack/lib/action_controller/rack_process.rb +++ b/actionpack/lib/action_controller/rack_process.rb @@ -4,6 +4,7 @@ require 'action_controller/session/cookie_store' module ActionController #:nodoc: class RackRequest < AbstractRequest #:nodoc: attr_accessor :env, :session_options + attr_reader :cgi class SessionFixationAttempt < StandardError #:nodoc: end @@ -48,21 +49,12 @@ module ActionController #:nodoc: def cookies return {} unless @env["HTTP_COOKIE"] - if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"] - @env["rack.request.cookie_hash"] - else + unless @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"] @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"] - # According to RFC 2109: - # If multiple cookies satisfy the criteria above, they are ordered in - # the Cookie header such that those with more specific Path attributes - # precede those with less specific. Ordering with respect to other - # attributes (e.g., Domain) is unspecified. - @env["rack.request.cookie_hash"] = - parse_query(@env["rack.request.cookie_string"], ';,').inject({}) { |h, (k,v)| - h[k] = Array === v ? v.first : v - h - } + @env["rack.request.cookie_hash"] = CGI::Cookie::parse(@env["rack.request.cookie_string"]) end + + @env["rack.request.cookie_hash"] end def host_with_port_without_standard_port_handling @@ -169,37 +161,13 @@ end_msg def session_options_with_string_keys @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys end - - # From Rack::Utils - def parse_query(qs, d = '&;') - params = {} - (qs || '').split(/[#{d}] */n).inject(params) { |h,p| - k, v = unescape(p).split('=',2) - if cur = params[k] - if cur.class == Array - params[k] << v - else - params[k] = [cur, v] - end - else - params[k] = v - end - } - - return params - end - - def unescape(s) - s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){ - [$1.delete('%')].pack('H*') - } - end end class RackResponse < AbstractResponse #:nodoc: attr_accessor :status - def initialize + def initialize(request) + @request = request @writer = lambda { |x| @body << x } @block = nil super() @@ -221,6 +189,8 @@ end_msg if @body.respond_to?(:call) @writer = lambda { |x| callback.call(x) } @body.call(self, self) + elsif @body.is_a?(String) + @body.each_line(&callback) else @body.each(&callback) end @@ -270,9 +240,9 @@ end_msg else cookies << cookie.to_s end - @output_cookies.each { |c| cookies << c.to_s } if @output_cookies + @request.cgi.output_cookies.each { |c| cookies << c.to_s } if @request.cgi.output_cookies - headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].compact.join("\n") + headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact end options.each { |k,v| headers[k] = v } @@ -283,6 +253,8 @@ end_msg end class CGIWrapper < ::CGI + attr_reader :output_cookies + def initialize(request, *args) @request = request @args = *args diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 643ff7e5f4..742d290ad6 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -31,18 +31,21 @@ module ActionController module RecordIdentifier extend self + JOIN = '_'.freeze + NEW = 'new'.freeze + # Returns plural/singular for a record or class. Example: # # partial_path(post) # => "posts/post" # partial_path(Person) # => "people/person" # partial_path(Person, "admin/games") # => "admin/people/person" def partial_path(record_or_class, controller_path = nil) - klass = class_from_record_or_class(record_or_class) + name = model_name_from_record_or_class(record_or_class) if controller_path && controller_path.include?("/") - "#{File.dirname(controller_path)}/#{klass.name.tableize}/#{klass.name.demodulize.underscore}" + "#{File.dirname(controller_path)}/#{name.partial_path}" else - "#{klass.name.tableize}/#{klass.name.demodulize.underscore}" + name.partial_path end end @@ -56,21 +59,25 @@ module ActionController # dom_class(post, :edit) # => "edit_post" # dom_class(Person, :edit) # => "edit_person" def dom_class(record_or_class, prefix = nil) - [ prefix, singular_class_name(record_or_class) ].compact * '_' + singular = singular_class_name(record_or_class) + prefix ? "#{prefix}#{JOIN}#{singular}" : singular end # The DOM id convention is to use the singular form of an object or class with the id following an underscore. # If no id is found, prefix with "new_" instead. Examples: # - # dom_id(Post.new(:id => 45)) # => "post_45" + # dom_id(Post.find(45)) # => "post_45" # dom_id(Post.new) # => "new_post" # # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id: # - # dom_id(Post.new(:id => 45), :edit) # => "edit_post_45" + # dom_id(Post.find(45), :edit) # => "edit_post_45" def dom_id(record, prefix = nil) - prefix ||= 'new' unless record.id - [ prefix, singular_class_name(record), record.id ].compact * '_' + if record_id = record.id + "#{dom_class(record, prefix)}#{JOIN}#{record_id}" + else + dom_class(record, prefix || NEW) + end end # Returns the plural class name of a record or class. Examples: @@ -78,7 +85,7 @@ module ActionController # plural_class_name(post) # => "posts" # plural_class_name(Highrise::Person) # => "highrise_people" def plural_class_name(record_or_class) - singular_class_name(record_or_class).pluralize + model_name_from_record_or_class(record_or_class).plural end # Returns the singular class name of a record or class. Examples: @@ -86,12 +93,12 @@ module ActionController # singular_class_name(post) # => "post" # singular_class_name(Highrise::Person) # => "highrise_person" def singular_class_name(record_or_class) - class_from_record_or_class(record_or_class).name.underscore.tr('/', '_') + model_name_from_record_or_class(record_or_class).singular end private - def class_from_record_or_class(record_or_class) - record_or_class.is_a?(Class) ? record_or_class : record_or_class.class + def model_name_from_record_or_class(record_or_class) + (record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index 914163a709..38f8e10fe7 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -134,14 +134,15 @@ module ActionController # REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma- # delimited list in the case of multiple chained proxies; the last # address which is not trusted is the originating IP. - def remote_ip if TRUSTED_PROXIES !~ @env['REMOTE_ADDR'] return @env['REMOTE_ADDR'] end + remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',') + if @env.include? 'HTTP_CLIENT_IP' - if @env.include? 'HTTP_X_FORWARDED_FOR' + if remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP']) # We don't know which came from the proxy, and which from the user raise ActionControllerError.new(<<EOM) IP spoofing attack?! @@ -149,11 +150,11 @@ HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect} EOM end + return @env['HTTP_CLIENT_IP'] end - if @env.include? 'HTTP_X_FORWARDED_FOR' then - remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',') + if remote_ips while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip remote_ips.pop end diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index 79ec2b13b7..b11aa5625b 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -72,7 +72,7 @@ module ActionController end def conditions - @conditions = @options[:conditions] || {} + @conditions ||= @options[:conditions] || {} end def path @@ -80,9 +80,9 @@ module ActionController end def new_path - new_action = self.options[:path_names][:new] if self.options[:path_names] + new_action = self.options[:path_names][:new] if self.options[:path_names] new_action ||= Base.resources_path_names[:new] - @new_path ||= "#{path}/#{new_action}" + @new_path ||= "#{path}/#{new_action}" end def member_path diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb index 6aa266513d..8846dcc504 100644 --- a/actionpack/lib/action_controller/routing.rb +++ b/actionpack/lib/action_controller/routing.rb @@ -369,7 +369,7 @@ module ActionController Routes = RouteSet.new - ::Inflector.module_eval do + ActiveSupport::Inflector.module_eval do # Ensures that routes are reloaded when Rails inflections are updated. def inflections_with_route_reloading(&block) returning(inflections_without_route_reloading(&block)) { diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb index 864e068004..f0ad066bad 100644 --- a/actionpack/lib/action_controller/routing/segments.rb +++ b/actionpack/lib/action_controller/routing/segments.rb @@ -249,7 +249,7 @@ module ActionController end def extract_value - "#{local_name} = hash[:#{key}] && hash[:#{key}].collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}" + "#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}" end def default diff --git a/actionpack/lib/action_controller/templates/rescues/layout.erb b/actionpack/lib/action_controller/templates/rescues/layout.erb index d38f3e67f9..4a04742e40 100644 --- a/actionpack/lib/action_controller/templates/rescues/layout.erb +++ b/actionpack/lib/action_controller/templates/rescues/layout.erb @@ -1,4 +1,4 @@ -<html> +<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Action Controller: Exception caught</title> <style> diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb index 607fd186b9..b8d73c350d 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb @@ -17,7 +17,7 @@ module HTML #:nodoc: @root = Node.new(nil) node_stack = [ @root ] while token = tokenizer.next - node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token) + node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token, strict) node_stack.last.children << node unless node.tag? && node.closing == :close if node.tag? diff --git a/actionpack/lib/action_controller/verification.rb b/actionpack/lib/action_controller/verification.rb index 9f606e7b7c..35b12a7f13 100644 --- a/actionpack/lib/action_controller/verification.rb +++ b/actionpack/lib/action_controller/verification.rb @@ -116,7 +116,7 @@ module ActionController #:nodoc: end def apply_redirect_to(redirect_to_option) # :nodoc: - redirect_to_option.is_a?(Symbol) ? self.send!(redirect_to_option) : redirect_to_option + (redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.send!(redirect_to_option) : redirect_to_option end def apply_remaining_actions(options) # :nodoc: diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 5f4126e4e9..973020a768 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -21,13 +21,9 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -require 'action_view/template_handler' -require 'action_view/template_handlers/compilable' -require 'action_view/template_handlers/builder' -require 'action_view/template_handlers/erb' -require 'action_view/template_handlers/rjs' - -require 'action_view/template_finder' +require 'action_view/template_handlers' +require 'action_view/template_file' +require 'action_view/view_load_paths' require 'action_view/template' require 'action_view/partial_template' require 'action_view/inline_template' diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index c236666dcd..91a86470d3 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -1,17 +1,17 @@ module ActionView #:nodoc: class ActionViewError < StandardError #:nodoc: end - + class MissingTemplate < ActionViewError #:nodoc: end - # Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb - # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used. + # Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb + # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used. # If the template file has a <tt>.rjs</tt> extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator. - # + # # = ERb - # - # You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the + # + # You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the # following loop for names: # # <b>Names of all the people</b> @@ -51,7 +51,7 @@ module ActionView #:nodoc: # <title><%= @page_title %></title> # # == Passing local variables to sub templates - # + # # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values: # # <%= render "shared/header", { :headline => "Welcome", :person => person } %> @@ -77,8 +77,8 @@ module ActionView #:nodoc: # # == Builder # - # Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An XmlMarkup object - # named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension. + # Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An XmlMarkup object + # named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension. # # Here are some basic examples: # @@ -87,7 +87,7 @@ module ActionView #:nodoc: # xml.a("A Link", "href"=>"http://onestepback.org") # => <a href="http://onestepback.org">A Link</a> # xml.target("name"=>"compile", "option"=>"fast") # => <target option="fast" name="compile"\> # # NOTE: order of attributes is not specified. - # + # # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following: # # xml.div { @@ -111,7 +111,7 @@ module ActionView #:nodoc: # xml.description "Basecamp: Recent items" # xml.language "en-us" # xml.ttl "40" - # + # # for item in @recent_items # xml.item do # xml.title(item_title(item)) @@ -119,7 +119,7 @@ module ActionView #:nodoc: # xml.pubDate(item_pubDate(item)) # xml.guid(@person.firm.account.url + @recent_items.url(item)) # xml.link(@person.firm.account.url + @recent_items.url(item)) - # + # # xml.tag!("dc:creator", item.author_name) if item_has_creator?(item) # end # end @@ -130,12 +130,12 @@ module ActionView #:nodoc: # # == JavaScriptGenerator # - # JavaScriptGenerator templates end in <tt>.rjs</tt>. Unlike conventional templates which are used to - # render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to - # modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax + # JavaScriptGenerator templates end in <tt>.rjs</tt>. Unlike conventional templates which are used to + # render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to + # modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax # and make updates to the page where the request originated from. - # - # An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block. + # + # An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block. # # When an <tt>.rjs</tt> action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example: # @@ -145,48 +145,45 @@ module ActionView #:nodoc: # # page.replace_html 'sidebar', :partial => 'sidebar' # page.remove "person-#{@person.id}" - # page.visual_effect :highlight, 'user-list' + # page.visual_effect :highlight, 'user-list' # # This refreshes the sidebar, removes a person element and highlights the user list. - # + # # See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details. class Base include ERB::Util - attr_reader :finder attr_accessor :base_path, :assigns, :template_extension, :first_render attr_accessor :controller - + attr_writer :template_format attr_accessor :current_render_extension - # Specify trim mode for the ERB compiler. Defaults to '-'. - # See ERb documentation for suitable values. - @@erb_trim_mode = '-' - cattr_accessor :erb_trim_mode + attr_accessor :output_buffer + + class << self + delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB' + end # Specify whether file modification times should be checked to see if a template needs recompilation @@cache_template_loading = false cattr_accessor :cache_template_loading - + def self.cache_template_extensions=(*args) ActiveSupport::Deprecation.warn("config.action_view.cache_template_extensions option has been deprecated and has no effect. " << "Please remove it from your config files.", caller) end # Specify whether RJS responses should be wrapped in a try/catch block - # that alert()s the caught exception (and then re-raises it). + # that alert()s the caught exception (and then re-raises it). @@debug_rjs = false cattr_accessor :debug_rjs - - @@erb_variable = '_erbout' - cattr_accessor :erb_variable - + attr_internal :request delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers, - :flash, :logger, :action_name, :to => :controller - + :flash, :logger, :action_name, :controller_name, :to => :controller + module CompiledTemplates #:nodoc: # holds compiled template code end @@ -222,11 +219,17 @@ module ActionView #:nodoc: @assigns = assigns_for_first_render @assigns_added = nil @controller = controller - @finder = TemplateFinder.new(self, view_paths) + self.view_paths = view_paths + end + + attr_reader :view_paths + + def view_paths=(paths) + @view_paths = ViewLoadPaths.new(Array(paths)) end - # Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true, - # it's relative to the view_paths array, otherwise it's absolute. The hash in <tt>local_assigns</tt> + # Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true, + # it's relative to the view_paths array, otherwise it's absolute. The hash in <tt>local_assigns</tt> # is made available as local variables. def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc: if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/") @@ -241,11 +244,11 @@ If you are rendering a subtemplate, you must now use controller-like partial syn render :partial => 'signup' # no mailer_name necessary END_ERROR end - + Template.new(self, template_path, use_full_path, local_assigns).render_template end - - # Renders the template present at <tt>template_path</tt> (relative to the view_paths array). + + # Renders the template present at <tt>template_path</tt> (relative to the view_paths array). # The hash in <tt>local_assigns</tt> is made available as local variables. def render(options = {}, local_assigns = {}, &block) #:nodoc: if options.is_a?(String) @@ -253,12 +256,13 @@ If you are rendering a subtemplate, you must now use controller-like partial syn elsif options == :update update_page(&block) elsif options.is_a?(Hash) + use_full_path = options[:use_full_path] options = options.reverse_merge(:locals => {}, :use_full_path => true) if partial_layout = options.delete(:layout) if block_given? - wrap_content_for_layout capture(&block) do - concat(render(options.merge(:partial => partial_layout)), block.binding) + wrap_content_for_layout capture(&block) do + concat(render(options.merge(:partial => partial_layout))) end else wrap_content_for_layout render(options) do @@ -266,7 +270,7 @@ If you are rendering a subtemplate, you must now use controller-like partial syn end end elsif options[:file] - render_file(options[:file], options[:use_full_path], options[:locals]) + render_file(options[:file], use_full_path || false, options[:locals]) elsif options[:partial] && options[:collection] render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals]) elsif options[:partial] @@ -314,11 +318,16 @@ If you are rendering a subtemplate, you must now use controller-like partial syn end end + def file_exists?(template_path) + view_paths.template_exists?(template_file_from_name(template_path)) + end + private def wrap_content_for_layout(content) - original_content_for_layout = @content_for_layout - @content_for_layout = content - returning(yield) { @content_for_layout = original_content_for_layout } + original_content_for_layout, @content_for_layout = @content_for_layout, content + yield + ensure + @content_for_layout = original_content_for_layout end # Evaluate the local assigns and pushes them to the view. @@ -333,11 +342,43 @@ If you are rendering a subtemplate, you must now use controller-like partial syn def assign_variables_from_controller @assigns.each { |key, value| instance_variable_set("@#{key}", value) } end - + def execute(template) send(template.method, template.locals) do |*names| instance_variable_get "@content_for_#{names.first || 'layout'}" - end + end + end + + def template_file_from_name(template_name) + template_name = TemplateFile.from_path(template_name) + pick_template_extension(template_name) unless template_name.extension + end + + # Gets the extension for an existing template with the given template_path. + # Returns the format with the extension if that template exists. + # + # pick_template_extension('users/show') + # # => 'html.erb' + # + # pick_template_extension('users/legacy') + # # => "rhtml" + # + def pick_template_extension(file) + if f = self.view_paths.find_template_file_for_path(file.dup_with_extension(template_format)) || file_from_first_render(file) + f + elsif template_format == :js && f = self.view_paths.find_template_file_for_path(file.dup_with_extension(:html)) + @template_format = :html + f + else + nil + end + end + + # Determine the template extension from the <tt>@first_render</tt> filename + def file_from_first_render(file) + if extension = File.basename(@first_render.to_s)[/^[^.]+\.(.+)$/, 1] + file.dup_with_extension(extension) + end end end end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 9ea06568cf..990c30b90d 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -31,20 +31,16 @@ module ActionView # </body></html> # def capture(*args, &block) - # execute the block - begin - buffer = eval(ActionView::Base.erb_variable, block.binding) - rescue - buffer = nil - end - - if buffer.nil? - capture_block(*args, &block).to_s + # Return captured buffer in erb. + if block_called_from_erb?(block) + with_output_buffer { block.call(*args) } + + # Return block result otherwise, but protect buffer also. else - capture_erb_with_buffer(buffer, *args, &block).to_s + with_output_buffer { return block.call(*args) } end end - + # Calling content_for stores a block of markup in an identifier for later use. # You can make subsequent calls to the stored content in other templates or the layout # by passing the identifier as an argument to <tt>yield</tt>. @@ -121,40 +117,19 @@ module ActionView # named <tt>@content_for_#{name_of_the_content_block}</tt>. The preferred usage is now # <tt><%= yield :footer %></tt>. def content_for(name, content = nil, &block) - existing_content_for = instance_variable_get("@content_for_#{name}").to_s - new_content_for = existing_content_for + (block_given? ? capture(&block) : content) - instance_variable_set("@content_for_#{name}", new_content_for) + ivar = "@content_for_#{name}" + content = capture(&block) if block_given? + instance_variable_set(ivar, "#{instance_variable_get(ivar)}#{content}") + nil end private - def capture_block(*args, &block) - block.call(*args) - end - - def capture_erb(*args, &block) - buffer = eval(ActionView::Base.erb_variable, block.binding) - capture_erb_with_buffer(buffer, *args, &block) - end - - def capture_erb_with_buffer(buffer, *args, &block) - pos = buffer.length - block.call(*args) - - # extract the block - data = buffer[pos..-1] - - # replace it in the original with empty string - buffer[pos..-1] = '' - - data - end - - def erb_content_for(name, &block) - eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_erb(&block)" - end - - def block_content_for(name, &block) - eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_block(&block)" + def with_output_buffer(buf = '') + self.output_buffer, old_buffer = buf, output_buffer + yield + output_buffer + ensure + self.output_buffer = old_buffer end end end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index af43397cb7..f952ada691 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -249,9 +249,9 @@ module ActionView args.unshift object end - concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding) + concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {})) fields_for(object_name, *(args << options), &proc) - concat('</form>', proc.binding) + concat('</form>') end def apply_form_for_options!(object_or_array, options) #:nodoc: diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index c0cba24be4..f22c12c9ad 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -306,7 +306,7 @@ module ActionView # # NOTE: Only the option tags are returned, you have to wrap this call in # a regular HTML select tag. - def time_zone_options_for_select(selected = nil, priority_zones = nil, model = TimeZone) + def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone) zone_options = "" zones = model.all @@ -419,7 +419,7 @@ module ActionView value = value(object) content_tag("select", add_options( - time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || TimeZone), + time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, value ), html_options ) diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index ca58f4ba26..ccebec3692 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -6,7 +6,7 @@ module ActionView # Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like # FormHelper does. Instead, you provide the names and values manually. # - # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying + # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying # <tt>:disabled => true</tt> will give <tt>disabled="disabled"</tt>. module FormTagHelper # Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like @@ -20,15 +20,15 @@ module ActionView # * A list of parameters to feed to the URL the form will be posted to. # # ==== Examples - # form_tag('/posts') + # form_tag('/posts') # # => <form action="/posts" method="post"> # - # form_tag('/posts/1', :method => :put) + # form_tag('/posts/1', :method => :put) # # => <form action="/posts/1" method="put"> # - # form_tag('/upload', :multipart => true) + # form_tag('/upload', :multipart => true) # # => <form action="/upload" method="post" enctype="multipart/form-data"> - # + # # <% form_tag '/posts' do -%> # <div><%= submit_tag 'Save' %></div> # <% end -%> @@ -88,7 +88,7 @@ module ActionView # * <tt>:size</tt> - The number of visible characters that will fit in the input. # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter. # * Any other key creates standard HTML attributes for the tag. - # + # # ==== Examples # text_field_tag 'name' # # => <input id="name" name="name" type="text" /> @@ -146,13 +146,13 @@ module ActionView # # => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" /> # # hidden_field_tag 'collected_input', '', :onchange => "alert('Input collected!')" - # # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')" + # # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')" # # type="hidden" value="" /> def hidden_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.stringify_keys.update("type" => "hidden")) end - # Creates a file upload field. If you are using file uploads then you will also need + # Creates a file upload field. If you are using file uploads then you will also need # to set the multipart option for the form tag: # # <%= form_tag { :action => "post" }, { :multipart => true } %> @@ -160,7 +160,7 @@ module ActionView # <%= submit_tag %> # <%= end_form_tag %> # - # The specified URL will then be passed a File object containing the selected file, or if the field + # The specified URL will then be passed a File object containing the selected file, or if the field # was left blank, a StringIO object. # # ==== Options @@ -181,7 +181,7 @@ module ActionView # # => <input id="resume" name="resume" type="file" value="~/resume.doc" /> # # file_field_tag 'user_pic', :accept => 'image/png,image/gif,image/jpeg' - # # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" /> + # # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" /> # # file_field_tag 'file', :accept => 'text/html', :class => 'upload', :value => 'index.html' # # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" /> @@ -286,7 +286,7 @@ module ActionView tag :input, html_options end - # Creates a radio button; use groups of radio buttons named the same to allow users to + # Creates a radio button; use groups of radio buttons named the same to allow users to # select from a group of options. # # ==== Options @@ -313,14 +313,14 @@ module ActionView tag :input, html_options end - # Creates a submit button with the text <tt>value</tt> as the caption. + # Creates a submit button with the text <tt>value</tt> as the caption. # # ==== Options # * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm # prompt with the question specified. If the user accepts, the form is # processed normally, otherwise no action is taken. # * <tt>:disabled</tt> - If true, the user will not be able to use this input. - # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a disabled version + # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a disabled version # of the submit button when the form is submitted. # * Any other key creates standard HTML options for the tag. # @@ -335,7 +335,7 @@ module ActionView # # => <input disabled="disabled" name="commit" type="submit" value="Save edits" /> # # submit_tag "Complete sale", :disable_with => "Please wait..." - # # => <input name="commit" onclick="this.disabled=true;this.value='Please wait...';this.form.submit();" + # # => <input name="commit" onclick="this.disabled=true;this.value='Please wait...';this.form.submit();" # # type="submit" value="Complete sale" /> # # submit_tag nil, :class => "form_submit" @@ -346,7 +346,7 @@ module ActionView # # name="commit" type="submit" value="Edit" /> def submit_tag(value = "Save changes", options = {}) options.stringify_keys! - + if disable_with = options.delete("disable_with") options["onclick"] = [ "this.setAttribute('originalValue', this.value)", @@ -358,15 +358,15 @@ module ActionView "return result;", ].join(";") end - + if confirm = options.delete("confirm") options["onclick"] ||= '' options["onclick"] += "return #{confirm_javascript_function(confirm)};" end - + tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys) end - + # Displays an image which when clicked will submit the form. # # <tt>source</tt> is passed to AssetTagHelper#image_path @@ -407,12 +407,12 @@ module ActionView # # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset> def field_set_tag(legend = nil, &block) content = capture(&block) - concat(tag(:fieldset, {}, true), block.binding) - concat(content_tag(:legend, legend), block.binding) unless legend.blank? - concat(content, block.binding) - concat("</fieldset>", block.binding) + concat(tag(:fieldset, {}, true)) + concat(content_tag(:legend, legend)) unless legend.blank? + concat(content) + concat("</fieldset>") end - + private def html_options_for_form(url_for_options, options, *parameters_for_url) returning options.stringify_keys do |html_options| @@ -420,7 +420,7 @@ module ActionView html_options["action"] = url_for(url_for_options, *parameters_for_url) end end - + def extra_tags_for_form(html_options) case method = html_options.delete("method").to_s when /^get$/i # must be case-insentive, but can't use downcase as might be nil @@ -434,17 +434,17 @@ module ActionView content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0') end end - + def form_tag_html(html_options) extra_tags = extra_tags_for_form(html_options) tag(:form, html_options, true) + extra_tags end - + def form_tag_in_block(html_options, &block) content = capture(&block) - concat(form_tag_html(html_options), block.binding) - concat(content, block.binding) - concat("</form>", block.binding) + concat(form_tag_html(html_options)) + concat(content) + concat("</form>") end def token_tag diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index ed931e064f..00092adc87 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -4,10 +4,10 @@ require 'action_view/helpers/prototype_helper' module ActionView module Helpers # Provides functionality for working with JavaScript in your views. - # + # # == Ajax, controls and visual effects - # - # * For information on using Ajax, see + # + # * For information on using Ajax, see # ActionView::Helpers::PrototypeHelper. # * For information on using controls and visual effects, see # ActionView::Helpers::ScriptaculousHelper. @@ -20,22 +20,22 @@ module ActionView # and ActionView::Helpers::ScriptaculousHelper), you must do one of the # following: # - # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD - # section of your page (recommended): This function will return + # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD + # section of your page (recommended): This function will return # references to the JavaScript files created by the +rails+ command in # your <tt>public/javascripts</tt> directory. Using it is recommended as - # the browser can then cache the libraries instead of fetching all the + # the browser can then cache the libraries instead of fetching all the # functions anew on every request. - # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but + # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but # will only include the Prototype core library, which means you are able - # to use all basic AJAX functionality. For the Scriptaculous-based - # JavaScript helpers, like visual effects, autocompletion, drag and drop + # to use all basic AJAX functionality. For the Scriptaculous-based + # JavaScript helpers, like visual effects, autocompletion, drag and drop # and so on, you should use the method described above. # * Use <tt><%= define_javascript_functions %></tt>: this will copy all the # JavaScript support functions within a single script block. Not # recommended. # - # For documentation on +javascript_include_tag+ see + # For documentation on +javascript_include_tag+ see # ActionView::Helpers::AssetTagHelper. module JavaScriptHelper unless const_defined? :JAVASCRIPT_PATH @@ -53,7 +53,7 @@ module ActionView # # The +function+ argument can be omitted in favor of an +update_page+ # block, which evaluates to a string when the template is rendered - # (instead of making an Ajax request first). + # (instead of making an Ajax request first). # # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button" # @@ -79,28 +79,23 @@ module ActionView # <a href="#" id="more_link" onclick="try { # $("details").visualEffect("toggle_blind"); # $("more_link").update("Show me less"); - # } - # catch (e) { - # alert('RJS error:\n\n' + e.toString()); + # } + # catch (e) { + # alert('RJS error:\n\n' + e.toString()); # alert('$(\"details\").visualEffect(\"toggle_blind\"); # \n$(\"more_link\").update(\"Show me less\");'); - # throw e + # throw e # }; # return false;">Show me more</a> # def link_to_function(name, *args, &block) - html_options = args.extract_options! - function = args[0] || '' - - html_options.symbolize_keys! - function = update_page(&block) if block_given? - content_tag( - "a", name, - html_options.merge({ - :href => html_options[:href] || "#", - :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function}; return false;" - }) - ) + html_options = args.extract_options!.symbolize_keys + + function = block_given? ? update_page(&block) : args[0] || '' + onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;" + href = html_options[:href] || '#' + + content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick)) end # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the @@ -112,7 +107,7 @@ module ActionView # # The +function+ argument can be omitted in favor of an +update_page+ # block, which evaluates to a string when the template is rendered - # (instead of making an Ajax request first). + # (instead of making an Ajax request first). # # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button" # @@ -128,45 +123,56 @@ module ActionView # page[:details].visual_effect :toggle_slide # end def button_to_function(name, *args, &block) - html_options = args.extract_options! - function = args[0] || '' - - html_options.symbolize_keys! - function = update_page(&block) if block_given? - tag(:input, html_options.merge({ - :type => "button", :value => name, - :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" - })) + html_options = args.extract_options!.symbolize_keys + + function = block_given? ? update_page(&block) : args[0] || '' + onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};" + + tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick)) end - # Includes the Action Pack JavaScript libraries inside a single <script> + # Includes the Action Pack JavaScript libraries inside a single <script> # tag. The function first includes prototype.js and then its core extensions, # (determined by filenames starting with "prototype"). # Afterwards, any additional scripts will be included in undefined order. # # Note: The recommended approach is to copy the contents of # lib/action_view/helpers/javascripts/ into your application's - # public/javascripts/ directory, and use +javascript_include_tag+ to + # public/javascripts/ directory, and use +javascript_include_tag+ to # create remote <script> links. def define_javascript_functions javascript = "<script type=\"#{Mime::JS}\">" - - # load prototype.js and its extensions first + + # load prototype.js and its extensions first prototype_libs = Dir.glob(File.join(JAVASCRIPT_PATH, 'prototype*')).sort.reverse - prototype_libs.each do |filename| + prototype_libs.each do |filename| javascript << "\n" << IO.read(filename) end - + # load other libraries - (Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename| + (Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename| javascript << "\n" << IO.read(filename) end javascript << '</script>' end + + JS_ESCAPE_MAP = { + '\\' => '\\\\', + '</' => '<\/', + "\r\n" => '\n', + "\n" => '\n', + "\r" => '\n', + '"' => '\\"', + "'" => "\\'" } + # Escape carrier returns and single and double quotes for JavaScript segments. def escape_javascript(javascript) - (javascript || '').gsub('\\','\0\0').gsub('</','<\/').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" } + if javascript + javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] } + else + '' + end end # Returns a JavaScript tag with the +content+ inside. Example: @@ -180,7 +186,7 @@ module ActionView # </script> # # +html_options+ may be a hash of attributes for the <script> tag. Example: - # javascript_tag "alert('All is good')", :defer => 'defer' + # javascript_tag "alert('All is good')", :defer => 'defer' # # => <script defer="defer" type="text/javascript">alert('All is good')</script> # # Instead of passing the content as an argument, you can also use a block @@ -189,46 +195,45 @@ module ActionView # alert('All is good') # <% end -%> def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block) - if block_given? - html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) - content = capture(&block) - else - content = content_or_options_with_block - end + content = + if block_given? + html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) + capture(&block) + else + content_or_options_with_block + end + + tag = content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS)) - javascript_tag = content_tag("script", javascript_cdata_section(content), html_options.merge(:type => Mime::JS)) - - if block_given? && block_is_within_action_view?(block) - concat(javascript_tag, block.binding) + if block_called_from_erb?(block) + concat(tag) else - javascript_tag + tag end end def javascript_cdata_section(content) #:nodoc: "\n//#{cdata_section("\n#{content}\n//")}\n" end - + protected def options_for_javascript(options) - '{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}' + if options.empty? + '{}' + else + "{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}" + end end - + def array_or_string_for_javascript(option) - js_option = if option.kind_of?(Array) + if option.kind_of?(Array) "['#{option.join('\',\'')}']" elsif !option.nil? "'#{option}'" end - js_option - end - - private - def block_is_within_action_view?(block) - eval("defined? _erbout", block.binding) end end - + JavascriptHelper = JavaScriptHelper unless const_defined? :JavascriptHelper end end diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index c731824f18..9f6e550c09 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -382,9 +382,9 @@ module ActionView args.unshift object end - concat(form_remote_tag(options), proc.binding) + concat(form_remote_tag(options)) fields_for(object_name, *(args << options), &proc) - concat('</form>', proc.binding) + concat('</form>') end alias_method :form_remote_for, :remote_form_for @@ -868,6 +868,16 @@ module ActionView record "window.location.href = #{url.inspect}" end + # Reloads the browser's current +location+ using JavaScript + # + # Examples: + # + # # Generates: window.location.reload(); + # page.reload + def reload + record 'window.location.reload()' + end + # Calls the JavaScript +function+, optionally with the given +arguments+. # # If a block is given, the block will be passed to a new JavaScriptGenerator; diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 66c596f3a9..9bb235175e 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -51,9 +51,8 @@ module ActionView prefix = args.first.is_a?(Hash) ? nil : args.shift options = args.first.is_a?(Hash) ? args.shift : {} concat content_tag(tag_name, capture(&block), - options.merge({ :class => "#{dom_class(record)} #{options[:class]}".strip, :id => dom_id(record, prefix) })), - block.binding + options.merge({ :class => "#{dom_class(record)} #{options[:class]}".strip, :id => dom_id(record, prefix) })) end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index 999cbfb52a..aeafd3906d 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -1,5 +1,6 @@ require 'cgi' require 'erb' +require 'set' module ActionView module Helpers #:nodoc: @@ -8,16 +9,17 @@ module ActionView module TagHelper include ERB::Util - BOOLEAN_ATTRIBUTES = Set.new(%w(disabled readonly multiple)) + BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple).to_set + BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym)) - # Returns an empty HTML tag of type +name+ which by default is XHTML - # compliant. Set +open+ to true to create an open tag compatible - # with HTML 4.0 and below. Add HTML attributes by passing an attributes + # Returns an empty HTML tag of type +name+ which by default is XHTML + # compliant. Set +open+ to true to create an open tag compatible + # with HTML 4.0 and below. Add HTML attributes by passing an attributes # hash to +options+. Set +escape+ to false to disable attribute value # escaping. # # ==== Options - # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and + # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and # <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use # symbols or strings for the attribute names. # @@ -28,7 +30,7 @@ module ActionView # tag("br", nil, true) # # => <br> # - # tag("input", { :type => 'text', :disabled => true }) + # tag("input", { :type => 'text', :disabled => true }) # # => <input type="text" disabled="disabled" /> # # tag("img", { :src => "open & shut.png" }) @@ -37,17 +39,17 @@ module ActionView # tag("img", { :src => "open & shut.png" }, false, false) # # => <img src="open & shut.png" /> def tag(name, options = nil, open = false, escape = true) - "<#{name}#{tag_options(options, escape) if options}" + (open ? ">" : " />") + "<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}" end # Returns an HTML block tag of type +name+ surrounding the +content+. Add - # HTML attributes by passing an attributes hash to +options+. + # HTML attributes by passing an attributes hash to +options+. # Instead of passing the content as an argument, you can also use a block # in which case, you pass your +options+ as the second parameter. # Set escape to false to disable attribute value escaping. # # ==== Options - # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and + # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and # <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use # symbols or strings for the attribute names. # @@ -66,12 +68,15 @@ module ActionView def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) if block_given? options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) - content = capture(&block) - content_tag = content_tag_string(name, content, options, escape) - block_is_within_action_view?(block) ? concat(content_tag, block.binding) : content_tag + content_tag = content_tag_string(name, capture(&block), options, escape) + + if block_called_from_erb?(block) + concat(content_tag) + else + content_tag + end else - content = content_or_options_with_block - content_tag_string(name, content, options, escape) + content_tag_string(name, content_or_options_with_block, options, escape) end end @@ -103,6 +108,16 @@ module ActionView end private + BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template' + + # Check whether we're called from an erb template. + # We'd return a string in any other case, but erb <%= ... %> + # can't take an <% end %> later on, so we have to use <% ... %> + # and implicitly concat. + def block_called_from_erb?(block) + block && eval(BLOCK_CALLED_FROM_ERB, block) + end + def content_tag_string(name, content, options, escape = true) tag_options = tag_options(options, escape) if options "<#{name}#{tag_options}>#{content}</#{name}>" @@ -114,7 +129,6 @@ module ActionView if escape options.each do |key, value| next unless value - key = key.to_s value = BOOLEAN_ATTRIBUTES.include?(key) ? key : escape_once(value) attrs << %(#{key}="#{value}") end @@ -124,10 +138,6 @@ module ActionView " #{attrs.sort * ' '}" unless attrs.empty? end end - - def block_is_within_action_view?(block) - eval("defined? _erbout", block.binding) - end end end end diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 669a285424..d98c5bd0d5 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -15,18 +15,22 @@ module ActionView # # ==== Examples # <% - # concat "hello", binding + # concat "hello" # # is the equivalent of <%= "hello" %> # # if (logged_in == true): - # concat "Logged in!", binding + # concat "Logged in!" # else - # concat link_to('login', :action => login), binding + # concat link_to('login', :action => login) # end # # will either display "Logged in!" or a login link # %> - def concat(string, binding) - eval(ActionView::Base.erb_variable, binding) << string + def concat(string, unused_binding = nil) + if unused_binding + ActiveSupport::Deprecation.warn("The binding argument of #concat is no longer needed. Please remove it from your views and helpers.") + end + + output_buffer << string end if RUBY_VERSION < '1.9' diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 7bb189420b..89166fe19a 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -90,6 +90,13 @@ module ActionView # link will be used in place of a referrer if none exists. If nil is passed as # a name, the link itself will become the name. # + # ==== Signatures + # + # link_to(name, options = {}, html_options = nil) + # link_to(options = {}, html_options = nil) do + # # name + # end + # # ==== Options # * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm # prompt with the question specified. If the user accepts, the link is @@ -147,6 +154,13 @@ module ActionView # link_to "Profiles", :controller => "profiles" # # => <a href="/profiles">Profiles</a> # + # You can use a block as well if your link target is hard to fit into the name parameter. ERb example: + # + # <% link_to(@profile) do %> + # <strong><%= @profile.name %></strong> -- <span>Check it out!!</span> + # <% end %> + # # => <a href="/profiles/1"><strong>David</strong> -- <span>Check it out!!</span></a> + # # Classes and ids for CSS are easy to produce: # # link_to "Articles", articles_path, :id => "news", :class => "article" @@ -189,27 +203,37 @@ module ActionView # f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; # var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); # m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;">Delete Image</a> - def link_to(name, options = {}, html_options = nil) - url = case options - when String - options - when :back - @controller.request.env["HTTP_REFERER"] || 'javascript:history.back()' + def link_to(*args, &block) + if block_given? + options = args.first || {} + html_options = args.second + concat(link_to(capture(&block), options, html_options)) + else + name = args.first + options = args.second || {} + html_options = args.third + + url = case options + when String + options + when :back + @controller.request.env["HTTP_REFERER"] || 'javascript:history.back()' + else + self.url_for(options) + end + + if html_options + html_options = html_options.stringify_keys + href = html_options['href'] + convert_options_to_javascript!(html_options, url) + tag_options = tag_options(html_options) else - self.url_for(options) + tag_options = nil end - - if html_options - html_options = html_options.stringify_keys - href = html_options['href'] - convert_options_to_javascript!(html_options, url) - tag_options = tag_options(html_options) - else - tag_options = nil + + href_attr = "href=\"#{url}\"" unless href + "<a #{href_attr}#{tag_options}>#{name || url}</a>" end - - href_attr = "href=\"#{url}\"" unless href - "<a #{href_attr}#{tag_options}>#{name || url}</a>" end # Generates a form containing a single button that submits to the URL created @@ -511,7 +535,7 @@ module ActionView when method "#{method_javascript_function(method, url, href)}return false;" when popup - popup_javascript_function(popup) + 'return false;' + "#{popup_javascript_function(popup)}return false;" else html_options["onclick"] end diff --git a/actionpack/lib/action_view/inline_template.rb b/actionpack/lib/action_view/inline_template.rb index 87c012d181..fd0ad48302 100644 --- a/actionpack/lib/action_view/inline_template.rb +++ b/actionpack/lib/action_view/inline_template.rb @@ -1,20 +1,17 @@ module ActionView #:nodoc: class InlineTemplate < Template #:nodoc: - def initialize(view, source, locals = {}, type = nil) @view = view - @finder = @view.finder - + @source = source @extension = type @locals = locals || {} - + @handler = self.class.handler_class_for_extension(@extension).new(@view) end - + def method_key @source end - end end diff --git a/actionpack/lib/action_view/partial_template.rb b/actionpack/lib/action_view/partial_template.rb index 1fb3aaee02..0cf996ca04 100644 --- a/actionpack/lib/action_view/partial_template.rb +++ b/actionpack/lib/action_view/partial_template.rb @@ -1,70 +1,70 @@ module ActionView #:nodoc: class PartialTemplate < Template #:nodoc: - attr_reader :variable_name, :object - + def initialize(view, partial_path, object = nil, locals = {}) - @path, @variable_name = extract_partial_name_and_path(view, partial_path) + @view_controller = view.controller if view.respond_to?(:controller) + set_path_and_variable_name!(partial_path) super(view, @path, true, locals) add_object_to_local_assigns!(object) # This is needed here in order to compile template with knowledge of 'counter' - initialize_counter - + initialize_counter! + # Prepare early. This is a performance optimization for partial collections prepare! end - + def render - ActionController::Base.benchmark("Rendered #{@path}", Logger::DEBUG, false) do + ActionController::Base.benchmark("Rendered #{@path.path_without_format_and_extension}", Logger::DEBUG, false) do @handler.render(self) end end - + def render_member(object) - @locals[@counter_name] += 1 @locals[:object] = @locals[@variable_name] = object - + template = render_template + @locals[@counter_name] += 1 @locals.delete(@variable_name) @locals.delete(:object) - + template end - + def counter=(num) @locals[@counter_name] = num end private + def add_object_to_local_assigns!(object) + @locals[:object] ||= + @locals[@variable_name] ||= + if object.is_a?(ActionView::Base::ObjectWrapper) + object.value + else + object + end || @view_controller.instance_variable_get("@#{variable_name}") + end - def add_object_to_local_assigns!(object) - @locals[:object] ||= - @locals[@variable_name] ||= - if object.is_a?(ActionView::Base::ObjectWrapper) - object.value - else - object - end || @view.controller.instance_variable_get("@#{variable_name}") - end - - def extract_partial_name_and_path(view, partial_path) - path, partial_name = partial_pieces(view, partial_path) - [File.join(path, "_#{partial_name}"), partial_name.split('/').last.split('.').first.to_sym] - end - - def partial_pieces(view, partial_path) - if partial_path.include?('/') - return File.dirname(partial_path), File.basename(partial_path) - else - return view.controller.class.controller_path, partial_path + def set_path_and_variable_name!(partial_path) + if partial_path.include?('/') + @variable_name = File.basename(partial_path) + @path = "#{File.dirname(partial_path)}/_#{@variable_name}" + elsif @view_controller + @variable_name = partial_path + @path = "#{@view_controller.class.controller_path}/_#{@variable_name}" + else + @variable_name = partial_path + @path = "_#{@variable_name}" + end + + @variable_name = @variable_name.sub(/\..*$/, '').to_sym + end + + def initialize_counter! + @counter_name ||= "#{@variable_name}_counter".to_sym + @locals[@counter_name] = 0 end - end - - def initialize_counter - @counter_name ||= "#{@variable_name}_counter".to_sym - @locals[@counter_name] = 0 - end - end end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 369526188f..4c3f252c10 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -1,19 +1,20 @@ module ActionView #:nodoc: class Template #:nodoc: + extend TemplateHandlers attr_accessor :locals - attr_reader :handler, :path, :extension, :filename, :path_without_extension, :method + attr_reader :handler, :path, :extension, :filename, :method def initialize(view, path, use_full_path, locals = {}) @view = view - @finder = @view.finder + @paths = view.view_paths - # Clear the forward slash at the beginning if exists - @path = use_full_path ? path.sub(/^\//, '') : path - @view.first_render ||= @path + @original_path = path + @path = TemplateFile.from_path(path, !use_full_path) + @view.first_render ||= @path.to_s @source = nil # Don't read the source until we know that it is required set_extension_and_file_name(use_full_path) - + @locals = locals || {} @handler = self.class.handler_class_for_extension(@extension).new(@view) end @@ -29,12 +30,16 @@ module ActionView #:nodoc: raise TemplateError.new(self, @view.assigns, e) end end - + def render prepare! @handler.render(self) end + def path_without_extension + @path.path_without_extension + end + def source @source ||= File.read(self.filename) end @@ -44,13 +49,13 @@ module ActionView #:nodoc: end def base_path_for_exception - @finder.find_base_path_for("#{@path_without_extension}.#{@extension}") || @finder.view_paths.first + (@paths.find_load_path_for_path(@path) || @paths.first).to_s end - + def prepare! @view.send :evaluate_assigns @view.current_render_extension = @extension - + if @handler.compilable? @handler.compile_template(self) # compile the given template, if necessary @method = @view.method_names[method_key] # Set the method name for this template and run it @@ -58,70 +63,32 @@ module ActionView #:nodoc: end private - - def set_extension_and_file_name(use_full_path) - @path_without_extension, @extension = @finder.path_and_extension(@path) - if use_full_path - if @extension - @filename = @finder.pick_template(@path_without_extension, @extension) + def set_extension_and_file_name(use_full_path) + @extension = @path.extension + + if use_full_path + unless @extension + @path = @view.send(:template_file_from_name, @path) + raise_missing_template_exception unless @path + @extension = @path.extension + end + + if @path = @paths.find_template_file_for_path(path) + @filename = @path.full_path + @extension = @path.extension + end else - @extension = @finder.pick_template_extension(@path).to_s - raise_missing_template_exception unless @extension - - @filename = @finder.pick_template(@path, @extension) - @extension = @extension.gsub(/^.+\./, '') # strip off any formats + @filename = @path.full_path end - else - @filename = @path - end - - raise_missing_template_exception if @filename.blank? - end - - def raise_missing_template_exception - full_template_path = @path.include?('.') ? @path : "#{@path}.#{@view.template_format}.erb" - display_paths = @finder.view_paths.join(':') - template_type = (@path =~ /layouts/i) ? 'layout' : 'template' - raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}") - end - - # Template Handlers - - @@template_handlers = HashWithIndifferentAccess.new - @@default_template_handlers = nil - - # Register a class that knows how to handle template files with the given - # extension. This can be used to implement new template types. - # The constructor for the class must take the ActiveView::Base instance - # as a parameter, and the class must implement a +render+ method that - # takes the contents of the template to render as well as the Hash of - # local assigns available to the template. The +render+ method ought to - # return the rendered template as a string. - def self.register_template_handler(extension, klass) - @@template_handlers[extension.to_sym] = klass - TemplateFinder.update_extension_cache_for(extension.to_s) - end - def self.template_handler_extensions - @@template_handlers.keys.map(&:to_s).sort - end - - def self.register_default_template_handler(extension, klass) - register_template_handler(extension, klass) - @@default_template_handlers = klass - end - - def self.handler_class_for_extension(extension) - (extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers - end - - register_default_template_handler :erb, TemplateHandlers::ERB - register_template_handler :rjs, TemplateHandlers::RJS - register_template_handler :builder, TemplateHandlers::Builder + raise_missing_template_exception if @filename.blank? + end - # TODO: Depreciate old template extensions - register_template_handler :rhtml, TemplateHandlers::ERB - register_template_handler :rxml, TemplateHandlers::Builder - + def raise_missing_template_exception + full_template_path = @original_path.include?('.') ? @original_path : "#{@original_path}.#{@view.template_format}.erb" + display_paths = @paths.join(':') + template_type = (@original_path =~ /layouts/i) ? 'layout' : 'template' + raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}") + end end end diff --git a/actionpack/lib/action_view/template_file.rb b/actionpack/lib/action_view/template_file.rb new file mode 100644 index 0000000000..dd66482b3c --- /dev/null +++ b/actionpack/lib/action_view/template_file.rb @@ -0,0 +1,88 @@ +module ActionView #:nodoc: + # TemplateFile abstracts the pattern of querying a file path for its + # path with or without its extension. The path is only the partial path + # from the load path root e.g. "hello/index.html.erb" not + # "app/views/hello/index.html.erb" + class TemplateFile + def self.from_path(path, use_full_path = false) + path.is_a?(self) ? path : new(path, use_full_path) + end + + def self.from_full_path(load_path, full_path) + file = new(full_path.split(load_path).last) + file.load_path = load_path + file.freeze + end + + attr_accessor :load_path, :base_path, :name, :format, :extension + delegate :to_s, :inspect, :to => :path + + def initialize(path, use_full_path = false) + path = path.dup + + # Clear the forward slash in the beginning unless using full path + trim_forward_slash!(path) unless use_full_path + + @base_path, @name, @format, @extension = split(path) + end + + def freeze + @load_path.freeze + @base_path.freeze + @name.freeze + @format.freeze + @extension.freeze + super + end + + def format_and_extension + extensions = [format, extension].compact.join(".") + extensions.blank? ? nil : extensions + end + + def full_path + if load_path + "#{load_path}/#{path}" + else + path + end + end + + def path + base_path.to_s + [name, format, extension].compact.join(".") + end + + def path_without_extension + base_path.to_s + [name, format].compact.join(".") + end + + def path_without_format_and_extension + "#{base_path}#{name}" + end + + def dup_with_extension(extension) + file = dup + file.extension = extension ? extension.to_s : nil + file + end + + private + def trim_forward_slash!(path) + path.sub!(/^\//, '') + end + + # Returns file split into an array + # [base_path, name, format, extension] + def split(file) + if m = file.match(/^(.*\/)?(\w+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/) + if m[5] # Mulipart formats + [m[1], m[2], "#{m[3]}.#{m[4]}", m[5]] + elsif m[4] # Single format + [m[1], m[2], m[3], m[4]] + else # No format + [m[1], m[2], nil, m[3]] + end + end + end + end +end diff --git a/actionpack/lib/action_view/template_finder.rb b/actionpack/lib/action_view/template_finder.rb deleted file mode 100644 index 83b7e27c09..0000000000 --- a/actionpack/lib/action_view/template_finder.rb +++ /dev/null @@ -1,177 +0,0 @@ -module ActionView #:nodoc: - class TemplateFinder #:nodoc: - - class InvalidViewPath < StandardError #:nodoc: - attr_reader :unprocessed_path - def initialize(path) - @unprocessed_path = path - super("Unprocessed view path found: #{@unprocessed_path.inspect}. Set your view paths with #append_view_path, #prepend_view_path, or #view_paths=.") - end - end - - cattr_reader :processed_view_paths - @@processed_view_paths = Hash.new {|hash, key| hash[key] = []} - - cattr_reader :file_extension_cache - @@file_extension_cache = Hash.new {|hash, key| - hash[key] = Hash.new {|hash, key| hash[key] = []} - } - - class << self #:nodoc: - - # This method is not thread safe. Mutex should be used whenever this is accessed from an instance method - def process_view_paths(*view_paths) - view_paths.flatten.compact.each do |dir| - next if @@processed_view_paths.has_key?(dir) - @@processed_view_paths[dir] = [] - - # - # Dir.glob("#{dir}/**/*/**") reads all the directories in view path and templates inside those directories - # Dir.glob("#{dir}/**") reads templates residing at top level of view path - # - (Dir.glob("#{dir}/**/*/**") | Dir.glob("#{dir}/**")).each do |file| - unless File.directory?(file) - @@processed_view_paths[dir] << file.split(dir).last.sub(/^\//, '') - - # Build extension cache - extension = file.split(".").last - if template_handler_extensions.include?(extension) - key = file.split(dir).last.sub(/^\//, '').sub(/\.(\w+)$/, '') - @@file_extension_cache[dir][key] << extension - end - end - end - end - end - - def update_extension_cache_for(extension) - @@processed_view_paths.keys.each do |dir| - Dir.glob("#{dir}/**/*.#{extension}").each do |file| - key = file.split(dir).last.sub(/^\//, '').sub(/\.(\w+)$/, '') - @@file_extension_cache[dir][key] << extension - end - end - end - - def template_handler_extensions - ActionView::Template.template_handler_extensions - end - - def reload! - view_paths = @@processed_view_paths.keys - - @@processed_view_paths = Hash.new {|hash, key| hash[key] = []} - @@file_extension_cache = Hash.new {|hash, key| - hash[key] = Hash.new {|hash, key| hash[key] = []} - } - - process_view_paths(view_paths) - end - end - - attr_accessor :view_paths - - def initialize(*args) - @template = args.shift - - @view_paths = args.flatten - @view_paths = @view_paths.respond_to?(:find) ? @view_paths.dup : [*@view_paths].compact - check_view_paths(@view_paths) - end - - def prepend_view_path(path) - @view_paths.unshift(*path) - - self.class.process_view_paths(path) - end - - def append_view_path(path) - @view_paths.push(*path) - - self.class.process_view_paths(path) - end - - def view_paths=(path) - @view_paths = path - self.class.process_view_paths(path) - end - - def pick_template(template_path, extension) - file_name = "#{template_path}.#{extension}" - base_path = find_base_path_for(file_name) - base_path.blank? ? false : "#{base_path}/#{file_name}" - end - alias_method :template_exists?, :pick_template - - def file_exists?(template_path) - # Clear the forward slash in the beginning if exists - template_path = template_path.sub(/^\//, '') - - template_file_name, template_file_extension = path_and_extension(template_path) - - if template_file_extension - template_exists?(template_file_name, template_file_extension) - else - template_exists?(template_file_name, pick_template_extension(template_path)) - end - end - - def find_base_path_for(template_file_name) - @view_paths.find { |path| @@processed_view_paths[path].include?(template_file_name) } - end - - # Returns the view path that the full path resides in. - def extract_base_path_from(full_path) - @view_paths.find { |p| full_path[0..p.size - 1] == p } - end - - # Gets the extension for an existing template with the given template_path. - # Returns the format with the extension if that template exists. - # - # pick_template_extension('users/show') - # # => 'html.erb' - # - # pick_template_extension('users/legacy') - # # => "rhtml" - # - def pick_template_extension(template_path) - if extension = find_template_extension_from_handler(template_path, @template.template_format) || find_template_extension_from_first_render - extension - elsif @template.template_format == :js && extension = find_template_extension_from_handler(template_path, :html) - @template.template_format = :html - extension - end - end - - def find_template_extension_from_handler(template_path, template_format = @template.template_format) - formatted_template_path = "#{template_path}.#{template_format}" - - view_paths.each do |path| - if (extensions = @@file_extension_cache[path][formatted_template_path]).any? - return "#{template_format}.#{extensions.first}" - elsif (extensions = @@file_extension_cache[path][template_path]).any? - return extensions.first.to_s - end - end - nil - end - - # Splits the path and extension from the given template_path and returns as an array. - def path_and_extension(template_path) - template_path_without_extension = template_path.sub(/\.(\w+)$/, '') - [ template_path_without_extension, $1 ] - end - - # Determine the template extension from the <tt>@first_render</tt> filename - def find_template_extension_from_first_render - File.basename(@template.first_render.to_s)[/^[^.]+\.(.+)$/, 1] - end - - private - def check_view_paths(view_paths) - view_paths.each do |path| - raise InvalidViewPath.new(path) unless @@processed_view_paths.has_key?(path) - end - end - end -end diff --git a/actionpack/lib/action_view/template_handler.rb b/actionpack/lib/action_view/template_handler.rb index ec407e3fb3..39e578e586 100644 --- a/actionpack/lib/action_view/template_handler.rb +++ b/actionpack/lib/action_view/template_handler.rb @@ -1,6 +1,5 @@ module ActionView class TemplateHandler - def self.line_offset 0 end diff --git a/actionpack/lib/action_view/template_handlers.rb b/actionpack/lib/action_view/template_handlers.rb new file mode 100644 index 0000000000..1471e99e01 --- /dev/null +++ b/actionpack/lib/action_view/template_handlers.rb @@ -0,0 +1,46 @@ +require 'action_view/template_handler' +require 'action_view/template_handlers/compilable' +require 'action_view/template_handlers/builder' +require 'action_view/template_handlers/erb' +require 'action_view/template_handlers/rjs' + +module ActionView #:nodoc: + module TemplateHandlers #:nodoc: + def self.extended(base) + base.register_default_template_handler :erb, TemplateHandlers::ERB + base.register_template_handler :rjs, TemplateHandlers::RJS + base.register_template_handler :builder, TemplateHandlers::Builder + + # TODO: Depreciate old template extensions + base.register_template_handler :rhtml, TemplateHandlers::ERB + base.register_template_handler :rxml, TemplateHandlers::Builder + end + + @@template_handlers = {} + @@default_template_handlers = nil + + # Register a class that knows how to handle template files with the given + # extension. This can be used to implement new template types. + # The constructor for the class must take the ActiveView::Base instance + # as a parameter, and the class must implement a +render+ method that + # takes the contents of the template to render as well as the Hash of + # local assigns available to the template. The +render+ method ought to + # return the rendered template as a string. + def register_template_handler(extension, klass) + @@template_handlers[extension.to_sym] = klass + end + + def template_handler_extensions + @@template_handlers.keys.map(&:to_s).sort + end + + def register_default_template_handler(extension, klass) + register_template_handler(extension, klass) + @@default_template_handlers = klass + end + + def handler_class_for_extension(extension) + (extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers + end + end +end diff --git a/actionpack/lib/action_view/template_handlers/builder.rb b/actionpack/lib/action_view/template_handlers/builder.rb index f76d89777a..ee02ce1a6f 100644 --- a/actionpack/lib/action_view/template_handlers/builder.rb +++ b/actionpack/lib/action_view/template_handlers/builder.rb @@ -11,10 +11,11 @@ module ActionView def compile(template) content_type_handler = (@view.send!(:controller).respond_to?(:response) ? "controller.response" : "controller") + "#{content_type_handler}.content_type ||= Mime::XML\n" + - "xml = ::Builder::XmlMarkup.new(:indent => 2)\n" + - template.source + - "\nxml.target!\n" + "xml = ::Builder::XmlMarkup.new(:indent => 2)\n" + + template.source + + "\nxml.target!\n" end def cache_fragment(block, name = {}, options = nil) diff --git a/actionpack/lib/action_view/template_handlers/compilable.rb b/actionpack/lib/action_view/template_handlers/compilable.rb index 25bd0fea7f..783456ab53 100644 --- a/actionpack/lib/action_view/template_handlers/compilable.rb +++ b/actionpack/lib/action_view/template_handlers/compilable.rb @@ -106,7 +106,13 @@ module ActionView locals_code << "#{key} = local_assigns[:#{key}]\n" end - "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend" + <<-end_src + def #{render_symbol}(local_assigns) + old_output_buffer = output_buffer;#{locals_code}#{body} + ensure + self.output_buffer = old_output_buffer + end + end_src end # Return true if the given template was compiled for a superset of the keys in local_assigns @@ -125,4 +131,4 @@ module ActionView end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/template_handlers/erb.rb b/actionpack/lib/action_view/template_handlers/erb.rb index 15a9064461..ac715e30c2 100644 --- a/actionpack/lib/action_view/template_handlers/erb.rb +++ b/actionpack/lib/action_view/template_handlers/erb.rb @@ -42,13 +42,19 @@ module ActionView class ERB < TemplateHandler include Compilable + # Specify trim mode for the ERB compiler. Defaults to '-'. + # See ERb documentation for suitable values. + cattr_accessor :erb_trim_mode + self.erb_trim_mode = '-' + def compile(template) - ::ERB.new(template.source, nil, @view.erb_trim_mode).src + src = ::ERB.new(template.source, nil, erb_trim_mode, '@output_buffer').src + "__in_erb_template=true;#{src}" end def cache_fragment(block, name = {}, options = nil) #:nodoc: @view.fragment_for(block, name, options) do - eval(ActionView::Base.erb_variable, block.binding) + @view.response.template.output_buffer end end end diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 16fedd9732..1a3c93c283 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -37,6 +37,8 @@ module ActionView if helper_class && !self.class.ancestors.include?(helper_class) self.class.send(:include, helper_class) end + + self.output_buffer = '' end class TestController < ActionController::Base @@ -48,6 +50,9 @@ module ActionView end end + protected + attr_accessor :output_buffer + private def method_missing(selector, *args) controller = TestController.new diff --git a/actionpack/lib/action_view/view_load_paths.rb b/actionpack/lib/action_view/view_load_paths.rb new file mode 100644 index 0000000000..e873d96aa0 --- /dev/null +++ b/actionpack/lib/action_view/view_load_paths.rb @@ -0,0 +1,103 @@ +module ActionView #:nodoc: + class ViewLoadPaths < Array #:nodoc: + def self.type_cast(obj) + obj.is_a?(String) ? LoadPath.new(obj) : obj + end + + class LoadPath #:nodoc: + attr_reader :path, :paths + delegate :to_s, :inspect, :to => :path + + def initialize(path) + @path = path.freeze + reload! + end + + def eql?(view_path) + view_path.is_a?(ViewPath) && @path == view_path.path + end + + def hash + @path.hash + end + + # Rebuild load path directory cache + def reload! + @paths = {} + + files.each do |file| + @paths[file.path] = file + @paths[file.path_without_extension] ||= file + end + + @paths.freeze + end + + # Tries to find the extension for the template name. + # If it does not it exist, tries again without the format extension + # find_template_file_for_partial_path('users/show') => 'html.erb' + # find_template_file_for_partial_path('users/legacy') => 'rhtml' + def find_template_file_for_partial_path(file) + @paths[file.path] || @paths[file.path_without_extension] || @paths[file.path_without_format_and_extension] + end + + private + # Get all the files and directories in the path + def files_in_path + Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**") + end + + # Create an array of all the files within the path + def files + files_in_path.map do |file| + TemplateFile.from_full_path(@path, file) unless File.directory?(file) + end.compact + end + end + + def initialize(*args) + super(*args).map! { |obj| self.class.type_cast(obj) } + end + + def reload! + each { |path| path.reload! } + end + + def <<(obj) + super(self.class.type_cast(obj)) + end + + def push(*objs) + delete_paths!(objs) + super(*objs.map { |obj| self.class.type_cast(obj) }) + end + + def unshift(*objs) + delete_paths!(objs) + super(*objs.map { |obj| self.class.type_cast(obj) }) + end + + def template_exists?(file) + find_load_path_for_path(file) ? true : false + end + + def find_load_path_for_path(file) + find { |path| path.paths[file.to_s] } + end + + def find_template_file_for_path(file) + file = TemplateFile.from_path(file) + each do |path| + if f = path.find_template_file_for_partial_path(file) + return f + end + end + nil + end + + private + def delete_paths!(paths) + paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } } + end + end +end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index f152b1d19c..c25e9e1df6 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -137,6 +137,9 @@ class AssertResponseWithUnexpectedErrorController < ActionController::Base end end +class UserController < ActionController::Base +end + module Admin class InnerModuleController < ActionController::Base def index @@ -174,7 +177,7 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase # let's get this party started def setup ActionController::Routing::Routes.reload - ActionController::Routing.use_controllers!(%w(action_pack_assertions admin/inner_module content admin/user)) + ActionController::Routing.use_controllers!(%w(action_pack_assertions admin/inner_module user content admin/user)) @controller = ActionPackAssertionsController.new @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new end @@ -268,7 +271,7 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase assert_redirected_to admin_inner_module_path end end - + def test_assert_redirected_to_top_level_named_route_from_nested_controller with_routing do |set| set.draw do |map| @@ -277,11 +280,25 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase end @controller = Admin::InnerModuleController.new process :redirect_to_top_level_named_route - # passes -> assert_redirected_to "http://test.host/action_pack_assertions/foo" + # assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return assert_redirected_to "/action_pack_assertions/foo" end end + def test_assert_redirected_to_top_level_named_route_with_same_controller_name_in_both_namespaces + with_routing do |set| + set.draw do |map| + # this controller exists in the admin namespace as well which is the only difference from previous test + map.top_level '/user/:id', :controller => 'user', :action => 'index' + map.connect ':controller/:action/:id' + end + @controller = Admin::InnerModuleController.new + process :redirect_to_top_level_named_route + # assert_redirected_to top_level_url('foo') would pass because of exact match early return + assert_redirected_to top_level_path('foo') + end + end + # -- standard request/response object testing -------------------------------- # make sure that the template objects exist diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index b28717597e..34c0200fe8 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -169,6 +169,22 @@ class DefaultUrlOptionsTest < Test::Unit::TestCase end end +class EmptyUrlOptionsTest < Test::Unit::TestCase + def setup + @controller = NonEmptyController.new + + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + + @request.host = 'www.example.com' + end + + def test_ensure_url_for_works_as_expected_when_called_with_no_options_if_default_url_options_is_not_set + get :public_action + assert_equal "http://www.example.com/non_empty/public_action", @controller.url_for + end +end + class EnsureNamedRoutesWorksTicket22BugTest < Test::Unit::TestCase def test_named_routes_still_work ActionController::Routing::Routes.draw do |map| @@ -180,4 +196,4 @@ class EnsureNamedRoutesWorksTicket22BugTest < Test::Unit::TestCase ensure ActionController::Routing::Routes.load! end -end
\ No newline at end of file +end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index f9b6b87bc6..14cf0a86a1 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -156,6 +156,7 @@ class ActionCachingTestController < ActionController::Base caches_action :show, :cache_path => 'http://test.host/custom/show' caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" } caches_action :with_layout + caches_action :layout_false, :layout => false layout 'talk_from_action.erb' @@ -181,11 +182,16 @@ class ActionCachingTestController < ActionController::Base alias_method :show, :index alias_method :edit, :index alias_method :destroy, :index + alias_method :layout_false, :with_layout def expire expire_action :controller => 'action_caching_test', :action => 'index' render :nothing => true end + def expire_xml + expire_action :controller => 'action_caching_test', :action => 'index', :format => 'xml' + render :nothing => true + end end class MockTime < Time @@ -211,6 +217,7 @@ class ActionCachingMockController mocked_path = @mock_path Object.new.instance_eval(<<-EVAL) def path; '#{@mock_path}' end + def format; 'all' end self EVAL end @@ -263,6 +270,19 @@ class ActionCacheTest < Test::Unit::TestCase assert_equal @response.body, read_fragment('hostname.com/action_caching_test/with_layout') end + def test_action_cache_with_layout_and_layout_cache_false + get :layout_false + cached_time = content_to_cache + assert_not_equal cached_time, @response.body + assert fragment_exist?('hostname.com/action_caching_test/layout_false') + reset! + + get :layout_false + assert_not_equal cached_time, @response.body + + assert_equal cached_time, read_fragment('hostname.com/action_caching_test/layout_false') + end + def test_action_cache_conditional_options @request.env['HTTP_ACCEPT'] = 'application/json' get :index @@ -311,6 +331,20 @@ class ActionCacheTest < Test::Unit::TestCase assert_equal new_cached_time, @response.body end + def test_cache_expiration_isnt_affected_by_request_format + get :index + cached_time = content_to_cache + reset! + + @request.set_REQUEST_URI "/action_caching_test/expire.xml" + get :expire, :format => :xml + reset! + + get :index + new_cached_time = content_to_cache + assert_not_equal cached_time, @response.body + end + def test_cache_is_scoped_by_subdomain @request.host = 'jamis.hostname.com' get :index @@ -355,11 +389,35 @@ class ActionCacheTest < Test::Unit::TestCase end def test_xml_version_of_resource_is_treated_as_different_cache - @mock_controller.mock_url_for = 'http://example.org/posts/' - @mock_controller.mock_path = '/posts/index.xml' - path_object = @path_class.new(@mock_controller, {}) - assert_equal 'xml', path_object.extension - assert_equal 'example.org/posts/index.xml', path_object.path + with_routing do |set| + ActionController::Routing::Routes.draw do |map| + map.connect ':controller/:action.:format' + map.connect ':controller/:action' + end + + get :index, :format => 'xml' + cached_time = content_to_cache + assert_equal cached_time, @response.body + assert fragment_exist?('hostname.com/action_caching_test/index.xml') + reset! + + get :index, :format => 'xml' + assert_equal cached_time, @response.body + assert_equal 'application/xml', @response.content_type + reset! + + @request.env['HTTP_ACCEPT'] = "application/xml" + get :index + assert_equal cached_time, @response.body + assert_equal 'application/xml', @response.content_type + reset! + + get :expire_xml + reset! + + get :index, :format => 'xml' + assert_not_equal cached_time, @response.body + end end def test_correct_content_type_is_returned_for_cache_hit @@ -421,6 +479,8 @@ class FragmentCachingTest < Test::Unit::TestCase @controller.request = @request @controller.response = @response @controller.send(:initialize_current_url) + @controller.send(:initialize_template_class, @response) + @controller.send(:assign_shortcuts, @request, @response) end def test_fragment_cache_key @@ -510,7 +570,7 @@ class FragmentCachingTest < Test::Unit::TestCase def test_cache_erb_fragment @store.write('views/expensive', 'fragment content') - _erbout = 'generated till now -> ' + @controller.response.template.output_buffer = 'generated till now -> ' assert_equal( 'generated till now -> fragment content', ActionView::TemplateHandlers::ERB.new(@controller).cache_fragment(Proc.new{ }, 'expensive')) diff --git a/actionpack/test/controller/capture_test.rb b/actionpack/test/controller/capture_test.rb index aaafea3920..2604844b84 100644 --- a/actionpack/test/controller/capture_test.rb +++ b/actionpack/test/controller/capture_test.rb @@ -11,16 +11,8 @@ class CaptureController < ActionController::Base def content_for_with_parameter render :layout => "talk_from_action" end - - def content_for_concatenated - render :layout => "talk_from_action" - end - def erb_content_for - render :layout => "talk_from_action" - end - - def block_content_for + def content_for_concatenated render :layout => "talk_from_action" end @@ -62,21 +54,11 @@ class CaptureTest < Test::Unit::TestCase assert_equal expected_content_for_output, @response.body end - def test_erb_content_for - get :erb_content_for - assert_equal expected_content_for_output, @response.body - end - def test_should_set_content_for_with_parameter get :content_for_with_parameter assert_equal expected_content_for_output, @response.body end - def test_block_content_for - get :block_content_for - assert_equal expected_content_for_output, @response.body - end - def test_non_erb_block_content_for get :non_erb_block_content_for assert_equal expected_content_for_output, @response.body diff --git a/actionpack/test/controller/cgi_test.rb b/actionpack/test/controller/cgi_test.rb index f0f3a4b826..1b1ded4615 100755 --- a/actionpack/test/controller/cgi_test.rb +++ b/actionpack/test/controller/cgi_test.rb @@ -115,35 +115,37 @@ class CgiRequestNeedsRewoundTest < BaseCgiTest end end -class CgiResponseTest < BaseCgiTest - def setup - super - @fake_cgi.expects(:header).returns("HTTP/1.0 200 OK\nContent-Type: text/html\n") - @response = ActionController::CgiResponse.new(@fake_cgi) - @output = StringIO.new('') - end - - def test_simple_output - @response.body = "Hello, World!" +uses_mocha 'CGI Response' do + class CgiResponseTest < BaseCgiTest + def setup + super + @fake_cgi.expects(:header).returns("HTTP/1.0 200 OK\nContent-Type: text/html\n") + @response = ActionController::CgiResponse.new(@fake_cgi) + @output = StringIO.new('') + end - @response.out(@output) - assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\nHello, World!", @output.string - end + def test_simple_output + @response.body = "Hello, World!" - def test_head_request - @fake_cgi.env_table['REQUEST_METHOD'] = 'HEAD' - @response.body = "Hello, World!" + @response.out(@output) + assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\nHello, World!", @output.string + end - @response.out(@output) - assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\n", @output.string - end + def test_head_request + @fake_cgi.env_table['REQUEST_METHOD'] = 'HEAD' + @response.body = "Hello, World!" - def test_streaming_block - @response.body = Proc.new do |response, output| - 5.times { |n| output.write(n) } + @response.out(@output) + assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\n", @output.string end - @response.out(@output) - assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\n01234", @output.string + def test_streaming_block + @response.body = Proc.new do |response, output| + 5.times { |n| output.write(n) } + end + + @response.out(@output) + assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\n01234", @output.string + end end end diff --git a/actionpack/test/controller/dispatcher_test.rb b/actionpack/test/controller/dispatcher_test.rb index eea0813ed5..911fcab67b 100644 --- a/actionpack/test/controller/dispatcher_test.rb +++ b/actionpack/test/controller/dispatcher_test.rb @@ -27,14 +27,14 @@ class DispatcherTest < Test::Unit::TestCase def test_clears_dependencies_after_dispatch_if_in_loading_mode ActionController::Routing::Routes.expects(:reload).once - Dependencies.expects(:clear).once + ActiveSupport::Dependencies.expects(:clear).once dispatch(@output, false) end def test_leaves_dependencies_after_dispatch_if_not_in_loading_mode ActionController::Routing::Routes.expects(:reload).never - Dependencies.expects(:clear).never + ActiveSupport::Dependencies.expects(:clear).never dispatch end diff --git a/actionpack/test/controller/html-scanner/document_test.rb b/actionpack/test/controller/html-scanner/document_test.rb index 0519533dbd..1c3facb9e3 100644 --- a/actionpack/test/controller/html-scanner/document_test.rb +++ b/actionpack/test/controller/html-scanner/document_test.rb @@ -120,4 +120,29 @@ HTML assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => "") assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => nil) end + + def test_parse_invalid_document + assert_nothing_raised do + doc = HTML::Document.new("<html> + <table> + <tr> + <td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td> + </tr> + </table> + </html>") + end + end + + def test_invalid_document_raises_exception_when_strict + assert_raises RuntimeError do + doc = HTML::Document.new("<html> + <table> + <tr> + <td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td> + </tr> + </table> + </html>", true) + end + end + end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 62c00c5b9a..475e13897b 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'action_controller/integration' +require 'action_controller/routing' uses_mocha 'integration' do @@ -12,12 +13,12 @@ end class SessionTest < Test::Unit::TestCase include IntegrationSessionStubbing - + def setup @session = ActionController::Integration::Session.new stub_integration_session(@session) end - + def test_https_bang_works_and_sets_truth_by_default assert !@session.https? @session.https! @@ -196,7 +197,7 @@ class SessionTest < Test::Unit::TestCase @session.expects(:process).with(:head,path,params,headers_after_xhr) @session.xml_http_request(:head,path,params,headers) end - + def test_xml_http_request_override_accept path = "/index"; params = "blah"; headers = {:location => 'blah', "Accept" => "application/xml"} headers_after_xhr = headers.merge( @@ -227,7 +228,6 @@ class IntegrationTestTest < Test::Unit::TestCase assert_equal ::ActionController::Integration::Session, session2.class assert_not_equal session1, session2 end - end # Tests that integration tests don't call Controller test methods for processing. @@ -246,7 +246,90 @@ class IntegrationTestUsesCorrectClass < ActionController::IntegrationTest assert_nothing_raised("'#{verb}' should use integration test methods") { send!(verb, '/') } end end +end + +class IntegrationProcessTest < ActionController::IntegrationTest + class IntegrationController < ActionController::Base + session :off + + def get + render :text => "OK", :status => 200 + end + + def post + render :text => "Created", :status => 201 + end + + def cookie_monster + cookies["cookie_1"] = nil + cookies["cookie_3"] = "chocolate" + render :text => "Gone", :status => 410 + end + end + def test_get + with_test_route_set do + get '/get' + assert_equal 200, status + assert_equal "OK", status_message + assert_equal "200 OK", response.headers["Status"] + assert_equal ["200 OK"], headers["status"] + assert_equal [], response.headers["cookie"] + assert_equal [], headers["cookie"] + assert_equal({}, cookies) + assert_equal "OK", response.body + assert_kind_of HTML::Document, html_document + assert_equal 1, request_count + end + end + + def test_post + with_test_route_set do + post '/post' + assert_equal 201, status + assert_equal "Created", status_message + assert_equal "201 Created", response.headers["Status"] + assert_equal ["201 Created"], headers["status"] + assert_equal [], response.headers["cookie"] + assert_equal [], headers["cookie"] + assert_equal({}, cookies) + assert_equal "Created", response.body + assert_kind_of HTML::Document, html_document + assert_equal 1, request_count + end + end + + def test_cookie_monster + with_test_route_set do + self.cookies['cookie_1'] = "sugar" + self.cookies['cookie_2'] = "oatmeal" + get '/cookie_monster' + assert_equal 410, status + assert_equal "Gone", status_message + assert_equal "410 Gone", response.headers["Status"] + assert_equal ["410 Gone"], headers["status"] + assert_equal nil, response.headers["Set-Cookie"] + assert_equal ["cookie_1=; path=/", "cookie_3=chocolate; path=/"], headers['set-cookie'] + assert_equal [[], ["chocolate"]], response.headers["cookie"] + assert_equal [], headers["cookie"] + assert_equal({"cookie_1"=>"", "cookie_2"=>"oatmeal", "cookie_3"=>"chocolate"}, cookies) + assert_equal "Gone", response.body + end + end + + private + def with_test_route_set + with_routing do |set| + set.draw do |map| + map.with_options :controller => "IntegrationProcessTest::Integration" do |c| + c.connect '/get', :action => "get" + c.connect '/post', :action => "post" + c.connect '/cookie_monster', :action => "cookie_monster" + end + end + yield + end + end end -end # uses_mocha +end diff --git a/actionpack/test/controller/integration_upload_test.rb b/actionpack/test/controller/integration_upload_test.rb index 33df1131cb..4af9b7e697 100644 --- a/actionpack/test/controller/integration_upload_test.rb +++ b/actionpack/test/controller/integration_upload_test.rb @@ -28,7 +28,7 @@ class SessionUploadTest < ActionController::IntegrationTest # end def test_post_with_upload uses_mocha "test_post_with_upload" do - Dependencies.stubs(:load?).returns(false) + ActiveSupport::Dependencies.stubs(:load?).returns(false) with_routing do |set| set.draw do |map| map.update 'update', :controller => "upload_test", :action => "update", :method => :post diff --git a/actionpack/test/controller/new_render_test.rb b/actionpack/test/controller/new_render_test.rb index 6e2c6d90c6..b77b3ceffa 100644 --- a/actionpack/test/controller/new_render_test.rb +++ b/actionpack/test/controller/new_render_test.rb @@ -68,6 +68,11 @@ class NewRenderTestController < ActionController::Base path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb') render :file => path end + + def render_file_from_template + @secret = 'in the sauce' + @path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')) + end def render_file_with_locals path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.erb') @@ -215,7 +220,7 @@ class NewRenderTestController < ActionController::Base render :action => "test/hello_world" end - def render_to_string_with_partial + def render_to_string_with_partial @partial_only = render_to_string :partial => "partial_only" @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") } render :action => "test/hello_world" @@ -246,11 +251,15 @@ class NewRenderTestController < ActionController::Base def accessing_logger_in_template render :inline => "<%= logger.class %>" end - + def accessing_action_name_in_template render :inline => "<%= action_name %>" end + def accessing_controller_name_in_template + render :inline => "<%= controller_name %>" + end + def accessing_params_in_template_with_layout render :layout => nil, :inline => "Hello: <%= params[:name] %>" end @@ -531,6 +540,11 @@ class NewRenderTest < Test::Unit::TestCase get :render_file_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 + end def test_attempt_to_access_object_method assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone } @@ -549,12 +563,17 @@ class NewRenderTest < Test::Unit::TestCase get :accessing_logger_in_template assert_equal "Logger", @response.body end - + def test_access_to_action_name_in_view get :accessing_action_name_in_template assert_equal "accessing_action_name_in_template", @response.body end + def test_access_to_controller_name_in_view + get :accessing_controller_name_in_template + assert_equal "test", @response.body # name is explicitly set to 'test' inside the controller. + end + def test_render_xml get :render_xml_hello assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body @@ -742,7 +761,7 @@ EOS def test_partial_collection_with_counter get :partial_collection_with_counter - assert_equal "david1mary2", @response.body + assert_equal "david0mary1", @response.body end def test_partial_collection_with_locals @@ -762,7 +781,7 @@ EOS def test_partial_collection_shorthand_with_different_types_of_records get :partial_collection_shorthand_with_different_types_of_records - assert_equal "Bonjour bad customer: mark1Bonjour good customer: craig2Bonjour bad customer: john3Bonjour good customer: zach4Bonjour good customer: brandon5Bonjour bad customer: dan6", @response.body + assert_equal "Bonjour bad customer: mark0Bonjour good customer: craig1Bonjour bad customer: john2Bonjour good customer: zach3Bonjour good customer: brandon4Bonjour bad customer: dan5", @response.body end def test_empty_partial_collection diff --git a/actionpack/test/controller/polymorphic_routes_test.rb b/actionpack/test/controller/polymorphic_routes_test.rb index 4ec0d3cd4e..3f52526f08 100644 --- a/actionpack/test/controller/polymorphic_routes_test.rb +++ b/actionpack/test/controller/polymorphic_routes_test.rb @@ -118,6 +118,39 @@ uses_mocha 'polymorphic URL helpers' do polymorphic_url([:site, :admin, @article, @response, @tag]) end + def test_nesting_with_array_ending_in_singleton_resource + expects(:article_response_url).with(@article) + polymorphic_url([@article, :response]) + end + + def test_nesting_with_array_containing_singleton_resource + @tag = Tag.new + @tag.save + expects(:article_response_tag_url).with(@article, @tag) + polymorphic_url([@article, :response, @tag]) + end + + def test_nesting_with_array_containing_namespace_and_singleton_resource + @tag = Tag.new + @tag.save + expects(:admin_article_response_tag_url).with(@article, @tag) + polymorphic_url([:admin, @article, :response, @tag]) + end + + def test_nesting_with_array_containing_singleton_resource_and_format + @tag = Tag.new + @tag.save + expects(:formatted_article_response_tag_url).with(@article, @tag, :pdf) + formatted_polymorphic_url([@article, :response, @tag, :pdf]) + end + + def test_nesting_with_array_containing_singleton_resource_and_format_option + @tag = Tag.new + @tag.save + expects(:article_response_tag_url).with(@article, @tag, :pdf) + polymorphic_url([@article, :response, @tag], :format => :pdf) + end + # TODO: Needs to be updated to correctly know about whether the object is in a hash or not def xtest_with_hash expects(:article_url).with(@article) diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb index cd4151783e..856f24bbdb 100644 --- a/actionpack/test/controller/rack_test.rb +++ b/actionpack/test/controller/rack_test.rb @@ -3,11 +3,40 @@ require 'action_controller/rack_process' class BaseRackTest < Test::Unit::TestCase def setup - @env = {"HTTP_MAX_FORWARDS"=>"10", "SERVER_NAME"=>"glu.ttono.us:8007", "FCGI_ROLE"=>"RESPONDER", "HTTP_X_FORWARDED_HOST"=>"glu.ttono.us", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", "PATH_INFO"=>"", "HTTP_ACCEPT_LANGUAGE"=>"en", "HTTP_HOST"=>"glu.ttono.us:8007", "SERVER_PROTOCOL"=>"HTTP/1.1", "REDIRECT_URI"=>"/dispatch.fcgi", "SCRIPT_NAME"=>"/dispatch.fcgi", "SERVER_ADDR"=>"207.7.108.53", "REMOTE_ADDR"=>"207.7.108.53", "SERVER_SOFTWARE"=>"lighttpd/1.4.5", "HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes", "HTTP_X_FORWARDED_SERVER"=>"glu.ttono.us", "REQUEST_URI"=>"/admin", "DOCUMENT_ROOT"=>"/home/kevinc/sites/typo/public", "SERVER_PORT"=>"8007", "QUERY_STRING"=>"", "REMOTE_PORT"=>"63137", "GATEWAY_INTERFACE"=>"CGI/1.1", "HTTP_X_FORWARDED_FOR"=>"65.88.180.234", "HTTP_ACCEPT"=>"*/*", "SCRIPT_FILENAME"=>"/home/kevinc/sites/typo/public/dispatch.fcgi", "REDIRECT_STATUS"=>"200", "REQUEST_METHOD"=>"GET"} + @env = { + "HTTP_MAX_FORWARDS" => "10", + "SERVER_NAME" => "glu.ttono.us:8007", + "FCGI_ROLE" => "RESPONDER", + "HTTP_X_FORWARDED_HOST" => "glu.ttono.us", + "HTTP_ACCEPT_ENCODING" => "gzip, deflate", + "HTTP_USER_AGENT" => "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)", + "PATH_INFO" => "", + "HTTP_ACCEPT_LANGUAGE" => "en", + "HTTP_HOST" => "glu.ttono.us:8007", + "SERVER_PROTOCOL" => "HTTP/1.1", + "REDIRECT_URI" => "/dispatch.fcgi", + "SCRIPT_NAME" => "/dispatch.fcgi", + "SERVER_ADDR" => "207.7.108.53", + "REMOTE_ADDR" => "207.7.108.53", + "SERVER_SOFTWARE" => "lighttpd/1.4.5", + "HTTP_COOKIE" => "_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes", + "HTTP_X_FORWARDED_SERVER" => "glu.ttono.us", + "REQUEST_URI" => "/admin", + "DOCUMENT_ROOT" => "/home/kevinc/sites/typo/public", + "SERVER_PORT" => "8007", + "QUERY_STRING" => "", + "REMOTE_PORT" => "63137", + "GATEWAY_INTERFACE" => "CGI/1.1", + "HTTP_X_FORWARDED_FOR" => "65.88.180.234", + "HTTP_ACCEPT" => "*/*", + "SCRIPT_FILENAME" => "/home/kevinc/sites/typo/public/dispatch.fcgi", + "REDIRECT_STATUS" => "200", + "REQUEST_METHOD" => "GET" + } + @request = ActionController::RackRequest.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_hash = {"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"} - @request = ActionController::RackRequest.new(@env) + @alt_cookie_fmt_request = ActionController::RackRequest.new(@env.merge({"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"})) end def default_test; end @@ -71,11 +100,11 @@ class RackRequestTest < BaseRackTest end def test_cookie_syntax_resilience - cookies = CGI::Cookie::parse(@env["HTTP_COOKIE"]); + cookies = @request.cookies assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"], cookies.inspect assert_equal ["yes"], cookies["is_admin"], cookies.inspect - alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]); + alt_cookies = @alt_cookie_fmt_request.cookies assert_equal ["c84ace847,96670c052c6ceb2451fb0f2"], alt_cookies["_session_id"], alt_cookies.inspect assert_equal ["yes"], alt_cookies["is_admin"], alt_cookies.inspect end @@ -118,7 +147,7 @@ end class RackResponseTest < BaseRackTest def setup super - @response = ActionController::RackResponse.new + @response = ActionController::RackResponse.new(@request) @output = StringIO.new('') end @@ -127,7 +156,7 @@ class RackResponseTest < BaseRackTest status, headers, body = @response.out(@output) assert_equal 200, status - assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => ""}, headers) + assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers) parts = [] body.each { |part| parts << part } @@ -141,10 +170,29 @@ class RackResponseTest < BaseRackTest status, headers, body = @response.out(@output) assert_equal 200, status - assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => ""}, headers) + assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers) parts = [] body.each { |part| parts << part } assert_equal ["0", "1", "2", "3", "4"], parts end + + def test_set_session_cookie + cookie = CGI::Cookie.new({"name" => "name", "value" => "Josh"}) + @request.cgi.send :instance_variable_set, '@output_cookies', [cookie] + + @response.body = "Hello, World!" + + status, headers, body = @response.out(@output) + assert_equal 200, status + assert_equal({ + "Content-Type" => "text/html", + "Cache-Control" => "no-cache", + "Set-Cookie" => ["name=Josh; path="] + }, headers) + + parts = [] + body.each { |part| parts << part } + assert_equal ["Hello, World!"], parts + end end diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb index 82ddfec8e8..2bd489b2c7 100644 --- a/actionpack/test/controller/request_test.rb +++ b/actionpack/test/controller/request_test.rb @@ -59,6 +59,9 @@ class RequestTest < Test::Unit::TestCase assert_match /HTTP_X_FORWARDED_FOR="9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4"/, e.message assert_match /HTTP_CLIENT_IP="8.8.8.8"/, e.message + @request.env['HTTP_X_FORWARDED_FOR'] = '8.8.8.8, 9.9.9.9' + assert_equal '8.8.8.8', @request.remote_ip + @request.env.delete 'HTTP_CLIENT_IP' @request.env.delete 'HTTP_X_FORWARDED_FOR' end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 5e5503fd52..07c13ebbf7 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -59,647 +59,30 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase end end -class LegacyRouteSetTests < Test::Unit::TestCase - attr_reader :rs - def setup - # These tests assume optimisation is on, so re-enable it. - ActionController::Base.optimise_named_routes = true - - @rs = ::ActionController::Routing::RouteSet.new - @rs.draw {|m| m.connect ':controller/:action/:id' } - - ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed) - end - - def test_default_setup - assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content")) - assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list")) - assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10")) - - assert_equal({:controller => "admin/user", :action => 'show', :id => '10'}, rs.recognize_path("/admin/user/show/10")) - - assert_equal '/admin/user/show/10', rs.generate(:controller => 'admin/user', :action => 'show', :id => 10) - - assert_equal '/admin/user/show', rs.generate({:action => 'show'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) - assert_equal '/admin/user/list/10', rs.generate({}, {:controller => 'admin/user', :action => 'list', :id => '10'}) - - assert_equal '/admin/stuff', rs.generate({:controller => 'stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) - assert_equal '/stuff', rs.generate({:controller => '/stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) - end - - def test_ignores_leading_slash - @rs.draw {|m| m.connect '/:controller/:action/:id'} - test_default_setup - end - - def test_time_recognition - # We create many routes to make situation more realistic - @rs = ::ActionController::Routing::RouteSet.new - @rs.draw { |map| - map.frontpage '', :controller => 'search', :action => 'new' - map.resources :videos do |video| - video.resources :comments - video.resource :file, :controller => 'video_file' - video.resource :share, :controller => 'video_shares' - video.resource :abuse, :controller => 'video_abuses' - end - map.resources :abuses, :controller => 'video_abuses' - map.resources :video_uploads - map.resources :video_visits - - map.resources :users do |user| - user.resource :settings - user.resources :videos - end - map.resources :channels do |channel| - channel.resources :videos, :controller => 'channel_videos' - end - map.resource :session - map.resource :lost_password - map.search 'search', :controller => 'search' - map.resources :pages - map.connect ':controller/:action/:id' - } - n = 1000 - if RunTimeTests - GC.start - rectime = Benchmark.realtime do - n.times do - rs.recognize_path("/videos/1234567", {:method => :get}) - rs.recognize_path("/videos/1234567/abuse", {:method => :get}) - rs.recognize_path("/users/1234567/settings", {:method => :get}) - rs.recognize_path("/channels/1234567", {:method => :get}) - rs.recognize_path("/session/new", {:method => :get}) - rs.recognize_path("/admin/user/show/10", {:method => :get}) - end - end - puts "\n\nRecognition (#{rs.routes.size} routes):" - per_url = rectime / (n * 6) - puts "#{per_url * 1000} ms/url" - puts "#{1 / per_url} url/s\n\n" - end - end - def test_time_generation - n = 5000 - if RunTimeTests - GC.start - pairs = [ - [{:controller => 'content', :action => 'index'}, {:controller => 'content', :action => 'show'}], - [{:controller => 'content'}, {:controller => 'content', :action => 'index'}], - [{:controller => 'content', :action => 'list'}, {:controller => 'content', :action => 'index'}], - [{:controller => 'content', :action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'}], - [{:controller => 'admin/user', :action => 'index'}, {:controller => 'admin/user', :action => 'show'}], - [{:controller => 'admin/user'}, {:controller => 'admin/user', :action => 'index'}], - [{:controller => 'admin/user', :action => 'list'}, {:controller => 'admin/user', :action => 'index'}], - [{:controller => 'admin/user', :action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'}], - ] - p = nil - gentime = Benchmark.realtime do - n.times do - pairs.each {|(a, b)| rs.generate(a, b)} - end - end - - puts "\n\nGeneration (RouteSet): (#{(n * 8)} urls)" - per_url = gentime / (n * 8) - puts "#{per_url * 1000} ms/url" - puts "#{1 / per_url} url/s\n\n" - end - end - - def test_route_with_colon_first - rs.draw do |map| - map.connect '/:controller/:action/:id', :action => 'index', :id => nil - map.connect ':url', :controller => 'tiny_url', :action => 'translate' - end - end - - def test_route_with_regexp_for_controller - rs.draw do |map| - map.connect ':controller/:admintoken/:action/:id', :controller => /admin\/.+/ - map.connect ':controller/:action/:id' - end - assert_equal({:controller => "admin/user", :admintoken => "foo", :action => "index"}, - rs.recognize_path("/admin/user/foo")) - assert_equal({:controller => "content", :action => "foo"}, rs.recognize_path("/content/foo")) - assert_equal '/admin/user/foo', rs.generate(:controller => "admin/user", :admintoken => "foo", :action => "index") - assert_equal '/content/foo', rs.generate(:controller => "content", :action => "foo") - end - - def test_route_with_regexp_and_dot - rs.draw do |map| - map.connect ':controller/:action/:file', - :controller => /admin|user/, - :action => /upload|download/, - :defaults => {:file => nil}, - :requirements => {:file => %r{[^/]+(\.[^/]+)?}} - end - # Without a file extension - assert_equal '/user/download/file', - rs.generate(:controller => "user", :action => "download", :file => "file") - assert_equal( - {:controller => "user", :action => "download", :file => "file"}, - rs.recognize_path("/user/download/file")) - - # Now, let's try a file with an extension, really a dot (.) - assert_equal '/user/download/file.jpg', - rs.generate( - :controller => "user", :action => "download", :file => "file.jpg") - assert_equal( - {:controller => "user", :action => "download", :file => "file.jpg"}, - rs.recognize_path("/user/download/file.jpg")) - end - - 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/", - x.send(:home_url)) - end - - def test_basic_named_route_with_relative_url_root - rs.add_named_route :home, '', :controller => 'content', :action => 'list' - x = setup_for_named_route - x.relative_url_root="/foo" - assert_equal("http://named.route.test/foo/", - x.send(:home_url)) - assert_equal "/foo/", x.send(:home_path) - end - - 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", - 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", - x.send(:page_url, :title => "AboutRails")) - - 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", - x.send(:users_url)) - end - - uses_mocha "named route optimisation" do - def test_optimised_named_route_call_never_uses_url_for - rs.add_named_route :users, 'admin/user', :controller => '/admin/user', :action => 'index' - rs.add_named_route :user, 'admin/user/:id', :controller=>'/admin/user', :action=>'show' - x = setup_for_named_route - x.expects(:url_for).never - x.send(:users_url) - x.send(:users_path) - x.send(:user_url, 2, :foo=>"bar") - x.send(:user_path, 3, :bar=>"foo") - end - - def test_optimised_named_route_with_host - rs.add_named_route :pages, 'pages', :controller => 'content', :action => 'show_page', :host => 'foo.com' - x = setup_for_named_route - x.expects(:url_for).with(:host => 'foo.com', :only_path => false, :controller => 'content', :action => 'show_page', :use_route => :pages).once - x.send(:pages_url) - end - end - - def setup_for_named_route - klass = Class.new(MockController) - rs.install_helpers(klass) - klass.new(rs) - end - - def test_named_route_without_hash - rs.draw do |map| - map.normal ':controller/:action/:id' - end - end - - def test_named_route_root - rs.draw do |map| - map.root :controller => "hello" - end - x = setup_for_named_route - assert_equal("http://named.route.test/", x.send(:root_url)) - assert_equal("/", x.send(:root_path)) - end - - def test_named_route_with_regexps - rs.draw do |map| - map.article 'page/:year/:month/:day/:title', :controller => 'page', :action => 'show', - :year => /\d+/, :month => /\d+/, :day => /\d+/ - map.connect ':controller/:action/:id' - end - x = setup_for_named_route - # assert_equal( - # {:controller => 'page', :action => 'show', :title => 'hi', :use_route => :article, :only_path => false}, - # x.send(:article_url, :title => 'hi') - # ) - assert_equal( - "http://named.route.test/page/2005/6/10/hi", - x.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6) - ) - end - - def test_changing_controller - assert_equal '/admin/stuff/show/10', rs.generate( - {:controller => 'stuff', :action => 'show', :id => 10}, - {:controller => 'admin/user', :action => 'index'} - ) - end - - def test_paths_escaped - rs.draw do |map| - map.path 'file/*path', :controller => 'content', :action => 'show_file' - map.connect ':controller/:action/:id' - end - - # No + to space in URI escaping, only for query params. - results = rs.recognize_path "/file/hello+world/how+are+you%3F" - assert results, "Recognition should have succeeded" - assert_equal ['hello+world', 'how+are+you?'], results[:path] - - # Use %20 for space instead. - results = rs.recognize_path "/file/hello%20world/how%20are%20you%3F" - assert results, "Recognition should have succeeded" - assert_equal ['hello world', 'how are you?'], results[:path] - - results = rs.recognize_path "/file" - assert results, "Recognition should have succeeded" - assert_equal [], results[:path] - end - - def test_paths_slashes_unescaped_with_ordered_parameters - rs.add_named_route :path, '/file/*path', :controller => 'content' - - # No / to %2F in URI, only for query params. - x = setup_for_named_route - assert_equal("/file/hello/world", x.send(:path_path, 'hello/world')) - end - - def test_non_controllers_cannot_be_matched - rs.draw do |map| - map.connect ':controller/:action/:id' - end - assert_raises(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") } - end - - def test_paths_do_not_accept_defaults - assert_raises(ActionController::RoutingError) do - rs.draw do |map| - map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default) - map.connect ':controller/:action/:id' - end - end - - rs.draw do |map| - map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => [] - map.connect ':controller/:action/:id' - end - end - - def test_should_list_options_diff_when_routing_requirements_dont_match - rs.draw do |map| - map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/} - end - exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post") } - assert_match /^post_url failed to generate/, exception.message - from_match = exception.message.match(/from \{[^\}]+\}/).to_s - assert_match /:bad_param=>"foo"/, from_match - assert_match /:action=>"show"/, from_match - assert_match /:controller=>"post"/, from_match - - expected_match = exception.message.match(/expected: \{[^\}]+\}/).to_s - assert_no_match /:bad_param=>"foo"/, expected_match - assert_match /:action=>"show"/, expected_match - assert_match /:controller=>"post"/, expected_match - - diff_match = exception.message.match(/diff: \{[^\}]+\}/).to_s - assert_match /:bad_param=>"foo"/, diff_match - assert_no_match /:action=>"show"/, diff_match - assert_no_match /:controller=>"post"/, diff_match - end - - # this specifies the case where your formerly would get a very confusing error message with an empty diff - def test_should_have_better_error_message_when_options_diff_is_empty - rs.draw do |map| - map.content '/content/:query', :controller => 'content', :action => 'show' - end - - exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'content', :action => 'show', :use_route => "content") } - assert_match %r[:action=>"show"], exception.message - assert_match %r[:controller=>"content"], exception.message - assert_match %r[you may have ambiguous routes, or you may need to supply additional parameters for this route], exception.message - assert_match %r[content_url has the following required parameters: \["content", :query\] - are they all satisfied?], exception.message - end - - def test_dynamic_path_allowed - rs.draw do |map| - map.connect '*path', :controller => 'content', :action => 'show_file' - end - - assert_equal '/pages/boo', rs.generate(:controller => 'content', :action => 'show_file', :path => %w(pages boo)) - end - - def test_dynamic_recall_paths_allowed - rs.draw do |map| - map.connect '*path', :controller => 'content', :action => 'show_file' - end - - recall_path = ActionController::Routing::PathSegment::Result.new(%w(pages boo)) - assert_equal '/pages/boo', rs.generate({}, :controller => 'content', :action => 'show_file', :path => recall_path) - end - - def test_backwards - rs.draw do |map| - map.connect 'page/:id/:action', :controller => 'pages', :action => 'show' - map.connect ':controller/:action/:id' - end - - assert_equal '/page/20', rs.generate({:id => 20}, {:controller => 'pages', :action => 'show'}) - assert_equal '/page/20', rs.generate(:controller => 'pages', :id => 20, :action => 'show') - assert_equal '/pages/boo', rs.generate(:controller => 'pages', :action => 'boo') - end - - def test_route_with_fixnum_default - rs.draw do |map| - map.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1 - map.connect ':controller/:action/:id' - end - - assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page') - assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page', :id => 1) - assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page', :id => '1') - assert_equal '/page/10', rs.generate(:controller => 'content', :action => 'show_page', :id => 10) - - assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page")) - assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page/1")) - assert_equal({:controller => "content", :action => 'show_page', :id => '10'}, rs.recognize_path("/page/10")) - end - - # For newer revision - def test_route_with_text_default - rs.draw do |map| - map.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1 - map.connect ':controller/:action/:id' - end - - assert_equal '/page/foo', rs.generate(:controller => 'content', :action => 'show_page', :id => 'foo') - assert_equal({:controller => "content", :action => 'show_page', :id => 'foo'}, rs.recognize_path("/page/foo")) - - token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in russian - escaped_token = CGI::escape(token) - - assert_equal '/page/' + escaped_token, rs.generate(:controller => 'content', :action => 'show_page', :id => token) - assert_equal({:controller => "content", :action => 'show_page', :id => token}, rs.recognize_path("/page/#{escaped_token}")) - end - - def test_action_expiry - assert_equal '/content', rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) - end - - def test_recognition_with_uppercase_controller_name - assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/Content")) - assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/ConTent/list")) - assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/CONTENT/show/10")) - - # these used to work, before the routes rewrite, but support for this was pulled in the new version... - #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/NewsFeed")) - #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/News_Feed")) - end - - def test_requirement_should_prevent_optional_id - rs.draw do |map| - map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/} - end - - assert_equal '/post/10', rs.generate(:controller => 'post', :action => 'show', :id => 10) - - assert_raises ActionController::RoutingError do - rs.generate(:controller => 'post', :action => 'show') - end - end - - def test_both_requirement_and_optional - rs.draw do |map| - map.blog('test/:year', :controller => 'post', :action => 'show', - :defaults => { :year => nil }, - :requirements => { :year => /\d{4}/ } - ) - map.connect ':controller/:action/:id' - end - - assert_equal '/test', rs.generate(:controller => 'post', :action => 'show') - assert_equal '/test', rs.generate(:controller => 'post', :action => 'show', :year => nil) - - x = setup_for_named_route - assert_equal("http://named.route.test/test", - x.send(:blog_url)) - end - - def test_set_to_nil_forgets - rs.draw do |map| - map.connect 'pages/:year/:month/:day', :controller => 'content', :action => 'list_pages', :month => nil, :day => nil - map.connect ':controller/:action/:id' - end - - assert_equal '/pages/2005', - rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005) - assert_equal '/pages/2005/6', - rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6) - assert_equal '/pages/2005/6/12', - rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6, :day => 12) - - assert_equal '/pages/2005/6/4', - rs.generate({:day => 4}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) - - assert_equal '/pages/2005/6', - rs.generate({:day => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) - - assert_equal '/pages/2005', - rs.generate({:day => nil, :month => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) - end - - def test_url_with_no_action_specified - rs.draw do |map| - map.connect '', :controller => 'content' - map.connect ':controller/:action/:id' - end - - assert_equal '/', rs.generate(:controller => 'content', :action => 'index') - assert_equal '/', rs.generate(:controller => 'content') - end - - def test_named_url_with_no_action_specified - rs.draw do |map| - map.home '', :controller => 'content' - map.connect ':controller/:action/:id' - end - - assert_equal '/', rs.generate(:controller => 'content', :action => 'index') - assert_equal '/', rs.generate(:controller => 'content') - - x = setup_for_named_route - assert_equal("http://named.route.test/", - x.send(:home_url)) - end - - def test_url_generated_when_forgetting_action - [{:controller => 'content', :action => 'index'}, {:controller => 'content'}].each do |hash| - rs.draw do |map| - map.home '', hash - map.connect ':controller/:action/:id' - end - assert_equal '/', rs.generate({:action => nil}, {:controller => 'content', :action => 'hello'}) - assert_equal '/', rs.generate({:controller => 'content'}) - assert_equal '/content/hi', rs.generate({:controller => 'content', :action => 'hi'}) - end - end - - def test_named_route_method - rs.draw do |map| - map.categories 'categories', :controller => 'content', :action => 'categories' - map.connect ':controller/:action/:id' - end - - assert_equal '/categories', rs.generate(:controller => 'content', :action => 'categories') - assert_equal '/content/hi', rs.generate({:controller => 'content', :action => 'hi'}) - end - - def test_named_routes_array - test_named_route_method - assert_equal [:categories], rs.named_routes.names - end - - def test_nil_defaults - rs.draw do |map| - map.connect 'journal', - :controller => 'content', - :action => 'list_journal', - :date => nil, :user_id => nil - map.connect ':controller/:action/:id' - end - - assert_equal '/journal', rs.generate(:controller => 'content', :action => 'list_journal', :date => nil, :user_id => nil) - end - - def setup_request_method_routes_for(method) - @request = ActionController::TestRequest.new - @request.env["REQUEST_METHOD"] = method - @request.request_uri = "/match" - - rs.draw do |r| - r.connect '/match', :controller => 'books', :action => 'get', :conditions => { :method => :get } - r.connect '/match', :controller => 'books', :action => 'post', :conditions => { :method => :post } - r.connect '/match', :controller => 'books', :action => 'put', :conditions => { :method => :put } - r.connect '/match', :controller => 'books', :action => 'delete', :conditions => { :method => :delete } - end - end - - %w(GET POST PUT DELETE).each do |request_method| - define_method("test_request_method_recognized_with_#{request_method}") do - begin - Object.const_set(:BooksController, Class.new(ActionController::Base)) - - setup_request_method_routes_for(request_method) - - assert_nothing_raised { rs.recognize(@request) } - assert_equal request_method.downcase, @request.path_parameters[:action] - ensure - Object.send(:remove_const, :BooksController) rescue nil - end - end - end - - def test_subpath_recognized - Object.const_set(:SubpathBooksController, Class.new(ActionController::Base)) - - rs.draw do |r| - r.connect '/books/:id/edit', :controller => 'subpath_books', :action => 'edit' - r.connect '/items/:id/:action', :controller => 'subpath_books' - r.connect '/posts/new/:action', :controller => 'subpath_books' - r.connect '/posts/:id', :controller => 'subpath_books', :action => "show" - end - - hash = rs.recognize_path "/books/17/edit" - assert_not_nil hash - assert_equal %w(subpath_books 17 edit), [hash[:controller], hash[:id], hash[:action]] - - hash = rs.recognize_path "/items/3/complete" - assert_not_nil hash - assert_equal %w(subpath_books 3 complete), [hash[:controller], hash[:id], hash[:action]] - - hash = rs.recognize_path "/posts/new/preview" - assert_not_nil hash - assert_equal %w(subpath_books preview), [hash[:controller], hash[:action]] - - hash = rs.recognize_path "/posts/7" - assert_not_nil hash - assert_equal %w(subpath_books show 7), [hash[:controller], hash[:action], hash[:id]] - ensure - Object.send(:remove_const, :SubpathBooksController) rescue nil - end - - def test_subpath_generated - Object.const_set(:SubpathBooksController, Class.new(ActionController::Base)) - - rs.draw do |r| - r.connect '/books/:id/edit', :controller => 'subpath_books', :action => 'edit' - r.connect '/items/:id/:action', :controller => 'subpath_books' - r.connect '/posts/new/:action', :controller => 'subpath_books' - end - - assert_equal "/books/7/edit", rs.generate(:controller => "subpath_books", :id => 7, :action => "edit") - assert_equal "/items/15/complete", rs.generate(:controller => "subpath_books", :id => 15, :action => "complete") - assert_equal "/posts/new/preview", rs.generate(:controller => "subpath_books", :action => "preview") - ensure - Object.send(:remove_const, :SubpathBooksController) rescue nil - end - - def test_failed_requirements_raises_exception_with_violated_requirements - rs.draw do |r| - r.foo_with_requirement 'foos/:id', :controller=>'foos', :requirements=>{:id=>/\d+/} - end - - x = setup_for_named_route - assert_raises(ActionController::RoutingError) do - x.send(:foo_with_requirement_url, "I am Against the requirements") - end - end -end - class SegmentTest < Test::Unit::TestCase - def test_first_segment_should_interpolate_for_structure s = ROUTING::Segment.new def s.interpolation_statement(array) 'hello' end assert_equal 'hello', s.continue_string_structure([]) end - + def test_interpolation_statement s = ROUTING::StaticSegment.new s.value = "Hello" assert_equal "Hello", eval(s.interpolation_statement([])) assert_equal "HelloHello", eval(s.interpolation_statement([s])) - + s2 = ROUTING::StaticSegment.new s2.value = "-" assert_equal "Hello-Hello", eval(s.interpolation_statement([s, s2])) - + s3 = ROUTING::StaticSegment.new s3.value = "World" assert_equal "Hello-World", eval(s3.interpolation_statement([s, s2])) end - end class StaticSegmentTest < Test::Unit::TestCase - def test_interpolation_chunk_should_respect_raw s = ROUTING::StaticSegment.new s.value = 'Hello World' @@ -713,28 +96,26 @@ class StaticSegmentTest < Test::Unit::TestCase def test_regexp_chunk_should_escape_specials s = ROUTING::StaticSegment.new - + s.value = 'Hello*World' assert_equal 'Hello\*World', s.regexp_chunk - + s.value = 'HelloWorld' assert_equal 'HelloWorld', s.regexp_chunk end - + def test_regexp_chunk_should_add_question_mark_for_optionals s = ROUTING::StaticSegment.new s.value = "/" s.is_optional = true assert_equal "/?", s.regexp_chunk - + s.value = "hello" assert_equal "(?:hello)?", s.regexp_chunk end - end class DynamicSegmentTest < Test::Unit::TestCase - def segment unless @segment @segment = ROUTING::DynamicSegment.new @@ -742,126 +123,126 @@ class DynamicSegmentTest < Test::Unit::TestCase end @segment end - + def test_extract_value s = ROUTING::DynamicSegment.new s.key = :a - + hash = {:a => '10', :b => '20'} assert_equal '10', eval(s.extract_value) - + hash = {:b => '20'} assert_equal nil, eval(s.extract_value) - + s.default = '20' assert_equal '20', eval(s.extract_value) end - + def test_default_local_name assert_equal 'a_value', segment.local_name, "Unexpected name -- all value_check tests will fail!" end - + def test_presence_value_check a_value = 10 assert eval(segment.value_check) end - + def test_regexp_value_check_rejects_nil segment.regexp = /\d+/ a_value = nil assert ! eval(segment.value_check) end - + def test_optional_regexp_value_check_should_accept_nil segment.regexp = /\d+/ segment.is_optional = true a_value = nil assert eval(segment.value_check) end - + def test_regexp_value_check_rejects_no_match segment.regexp = /\d+/ - + a_value = "Hello20World" assert ! eval(segment.value_check) - + a_value = "20Hi" assert ! eval(segment.value_check) end - + def test_regexp_value_check_accepts_match segment.regexp = /\d+/ - + a_value = "30" assert eval(segment.value_check) end - + def test_value_check_fails_on_nil a_value = nil assert ! eval(segment.value_check) end - + def test_optional_value_needs_no_check segment.is_optional = true a_value = nil assert_equal nil, segment.value_check end - + def test_regexp_value_check_should_accept_match_with_default segment.regexp = /\d+/ segment.default = '200' - + a_value = '100' assert eval(segment.value_check) end - + def test_expiry_should_not_trigger_once_expired expired = true hash = merged = {:a => 2, :b => 3} options = {:b => 3} expire_on = Hash.new { raise 'No!!!' } - + eval(segment.expiry_statement) rescue RuntimeError flunk "Expiry check should not have occurred!" end - + def test_expiry_should_occur_according_to_expire_on expired = false hash = merged = {:a => 2, :b => 3} options = {:b => 3} - + expire_on = {:b => true, :a => false} eval(segment.expiry_statement) assert !expired assert_equal({:a => 2, :b => 3}, hash) - + expire_on = {:b => true, :a => true} eval(segment.expiry_statement) assert expired assert_equal({:b => 3}, hash) end - + def test_extraction_code_should_return_on_nil hash = merged = {:b => 3} options = {:b => 3} a_value = nil - + # Local jump because of return inside eval. assert_raises(LocalJumpError) { eval(segment.extraction_code) } end - + def test_extraction_code_should_return_on_mismatch segment.regexp = /\d+/ hash = merged = {:a => 'Hi', :b => '3'} options = {:b => '3'} a_value = nil - + # Local jump because of return inside eval. assert_raises(LocalJumpError) { eval(segment.extraction_code) } end - + def test_extraction_code_should_accept_value_and_set_local hash = merged = {:a => 'Hi', :b => '3'} options = {:b => '3'} @@ -871,45 +252,45 @@ class DynamicSegmentTest < Test::Unit::TestCase eval(segment.extraction_code) assert_equal 'Hi', a_value end - + def test_extraction_should_work_without_value_check segment.default = 'hi' hash = merged = {:b => '3'} options = {:b => '3'} a_value = nil expired = true - + eval(segment.extraction_code) assert_equal 'hi', a_value end - + def test_extraction_code_should_perform_expiry expired = false hash = merged = {:a => 'Hi', :b => '3'} options = {:b => '3'} expire_on = {:a => true} a_value = nil - + eval(segment.extraction_code) assert_equal 'Hi', a_value assert expired assert_equal options, hash end - + def test_interpolation_chunk_should_replace_value a_value = 'Hi' assert_equal a_value, eval(%("#{segment.interpolation_chunk}")) end - + def test_interpolation_chunk_should_accept_nil a_value = nil assert_equal '', eval(%("#{segment.interpolation_chunk('a_value')}")) end - + def test_value_regexp_should_be_nil_without_regexp assert_equal nil, segment.value_regexp end - + def test_value_regexp_should_match_exacly segment.regexp = /\d+/ assert_no_match segment.value_regexp, "Hello 10 World" @@ -917,12 +298,12 @@ class DynamicSegmentTest < Test::Unit::TestCase assert_no_match segment.value_regexp, "10 World" assert_match segment.value_regexp, "10" end - + def test_regexp_chunk_should_return_string segment.regexp = /\d+/ assert_kind_of String, segment.regexp_chunk end - + def test_build_pattern_non_optional_with_no_captures # Non optional a_segment = ROUTING::DynamicSegment.new @@ -958,264 +339,23 @@ class DynamicSegmentTest < Test::Unit::TestCase end class ControllerSegmentTest < Test::Unit::TestCase - def test_regexp_should_only_match_possible_controllers ActionController::Routing.with_controllers %w(admin/accounts admin/users account pages) do cs = ROUTING::ControllerSegment.new :controller regexp = %r{\A#{cs.regexp_chunk}\Z} - + ActionController::Routing.possible_controllers.each do |name| assert_match regexp, name assert_no_match regexp, "#{name}_fake" - + match = regexp.match name assert_equal name, match[1] end end end - -end - -uses_mocha 'RouteTest' do - - class MockController - attr_accessor :routes - - def initialize(routes) - self.routes = routes - end - - def url_for(options) - only_path = options.delete(:only_path) - - port = options.delete(:port) || 80 - port_string = port == 80 ? '' : ":#{port}" - - host = options.delete(:host) || "named.route.test" - anchor = "##{options.delete(:anchor)}" if options.key?(:anchor) - - path = routes.generate(options) - - only_path ? "#{path}#{anchor}" : "http://#{host}#{port_string}#{path}#{anchor}" - end - - def request - @request ||= MockRequest.new(:host => "named.route.test", :method => :get) - end - - def relative_url_root=(value) - request.relative_url_root=value - end - end - - class MockRequest - attr_accessor :path, :path_parameters, :host, :subdomains, :domain, - :method, :relative_url_root - - 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 - end - end - -class RouteTest < Test::Unit::TestCase - - def setup - @route = ROUTING::Route.new - end - - def slash_segment(is_optional = false) - returning ROUTING::DividerSegment.new('/') do |s| - s.is_optional = is_optional - end - end - - def default_route - unless defined?(@default_route) - @default_route = ROUTING::Route.new - - @default_route.segments << (s = ROUTING::StaticSegment.new) - s.value = '/' - s.raw = true - - @default_route.segments << (s = ROUTING::DynamicSegment.new) - s.key = :controller - - @default_route.segments << slash_segment(:optional) - @default_route.segments << (s = ROUTING::DynamicSegment.new) - s.key = :action - s.default = 'index' - s.is_optional = true - - @default_route.segments << slash_segment(:optional) - @default_route.segments << (s = ROUTING::DynamicSegment.new) - s.key = :id - s.is_optional = true - - @default_route.segments << slash_segment(:optional) - end - @default_route - end - - def test_default_route_recognition - expected = {:controller => 'accounts', :action => 'show', :id => '10'} - assert_equal expected, default_route.recognize('/accounts/show/10') - assert_equal expected, default_route.recognize('/accounts/show/10/') - - expected[:id] = 'jamis' - assert_equal expected, default_route.recognize('/accounts/show/jamis/') - - expected.delete :id - assert_equal expected, default_route.recognize('/accounts/show') - assert_equal expected, default_route.recognize('/accounts/show/') - - expected[:action] = 'index' - assert_equal expected, default_route.recognize('/accounts/') - assert_equal expected, default_route.recognize('/accounts') - - assert_equal nil, default_route.recognize('/') - assert_equal nil, default_route.recognize('/accounts/how/goood/it/is/to/be/free') - end - - def test_default_route_should_omit_default_action - o = {:controller => 'accounts', :action => 'index'} - assert_equal '/accounts', default_route.generate(o, o, {}) - end - - def test_default_route_should_include_default_action_when_id_present - o = {:controller => 'accounts', :action => 'index', :id => '20'} - assert_equal '/accounts/index/20', default_route.generate(o, o, {}) - end - - def test_default_route_should_work_with_action_but_no_id - o = {:controller => 'accounts', :action => 'list_all'} - assert_equal '/accounts/list_all', default_route.generate(o, o, {}) - end - - def test_default_route_should_uri_escape_pluses - expected = { :controller => 'accounts', :action => 'show', :id => 'hello world' } - assert_equal expected, default_route.recognize('/accounts/show/hello world') - assert_equal expected, default_route.recognize('/accounts/show/hello%20world') - assert_equal '/accounts/show/hello%20world', default_route.generate(expected, expected, {}) - - expected[:id] = 'hello+world' - assert_equal expected, default_route.recognize('/accounts/show/hello+world') - assert_equal expected, default_route.recognize('/accounts/show/hello%2Bworld') - assert_equal '/accounts/show/hello+world', default_route.generate(expected, expected, {}) - end - - def test_matches_controller_and_action - # requirement_for should only be called for the action and controller _once_ - @route.expects(:requirement_for).with(:controller).times(1).returns('pages') - @route.expects(:requirement_for).with(:action).times(1).returns('show') - - @route.requirements = {:controller => 'pages', :action => 'show'} - assert @route.matches_controller_and_action?('pages', 'show') - assert !@route.matches_controller_and_action?('not_pages', 'show') - assert !@route.matches_controller_and_action?('pages', 'not_show') - end - - def test_parameter_shell - page_url = ROUTING::Route.new - page_url.requirements = {:controller => 'pages', :action => 'show', :id => /\d+/} - assert_equal({:controller => 'pages', :action => 'show'}, page_url.parameter_shell) - end - - def test_defaults - route = ROUTING::RouteBuilder.new.build '/users/:id.:format', :controller => "users", :action => "show", :format => "html" - assert_equal( - { :controller => "users", :action => "show", :format => "html" }, - route.defaults) - end - - def test_builder_complains_without_controller - assert_raises(ArgumentError) do - ROUTING::RouteBuilder.new.build '/contact', :contoller => "contact", :action => "index" - end - end - - def test_significant_keys_for_default_route - keys = default_route.significant_keys.sort_by {|k| k.to_s } - assert_equal [:action, :controller, :id], keys - end - - def test_significant_keys - user_url = ROUTING::Route.new - user_url.segments << (s = ROUTING::StaticSegment.new) - s.value = '/' - s.raw = true - - user_url.segments << (s = ROUTING::StaticSegment.new) - s.value = 'user' - - user_url.segments << (s = ROUTING::StaticSegment.new) - s.value = '/' - s.raw = true - s.is_optional = true - - user_url.segments << (s = ROUTING::DynamicSegment.new) - s.key = :user - - user_url.segments << (s = ROUTING::StaticSegment.new) - s.value = '/' - s.raw = true - s.is_optional = true - - user_url.requirements = {:controller => 'users', :action => 'show'} - - keys = user_url.significant_keys.sort_by { |k| k.to_s } - assert_equal [:action, :controller, :user], keys - end - - def test_build_empty_query_string - assert_equal '', @route.build_query_string({}) - end - - def test_build_query_string_with_nil_value - assert_equal '', @route.build_query_string({:x => nil}) - end - - def test_simple_build_query_string - assert_equal '?x=1&y=2', order_query_string(@route.build_query_string(:x => '1', :y => '2')) - end - - def test_convert_ints_build_query_string - assert_equal '?x=1&y=2', order_query_string(@route.build_query_string(:x => 1, :y => 2)) - end - - def test_escape_spaces_build_query_string - assert_equal '?x=hello+world&y=goodbye+world', order_query_string(@route.build_query_string(:x => 'hello world', :y => 'goodbye world')) - end - - def test_expand_array_build_query_string - assert_equal '?x%5B%5D=1&x%5B%5D=2', order_query_string(@route.build_query_string(:x => [1, 2])) - end - - def test_escape_spaces_build_query_string_selected_keys - assert_equal '?x=hello+world', order_query_string(@route.build_query_string({:x => 'hello world', :y => 'goodbye world'}, [:x])) - end - - private - def order_query_string(qs) - '?' + qs[1..-1].split('&').sort.join('&') - end end -end # uses_mocha - class RouteBuilderTest < Test::Unit::TestCase - def builder @builder ||= ROUTING::RouteBuilder.new end @@ -1244,7 +384,7 @@ class RouteBuilderTest < Test::Unit::TestCase assert_kind_of ROUTING::StaticSegment, segment assert_equal 'ulysses', segment.value end - + def test_segment_for_action segment, rest = builder.segment_for ':action' assert_equal '', rest @@ -1252,7 +392,7 @@ class RouteBuilderTest < Test::Unit::TestCase assert_equal :action, segment.key assert_equal 'index', segment.default end - + def test_segment_for_dynamic segment, rest = builder.segment_for ':login' assert_equal '', rest @@ -1261,7 +401,7 @@ class RouteBuilderTest < Test::Unit::TestCase assert_equal nil, segment.default assert ! segment.optional? end - + def test_segment_for_with_rest segment, rest = builder.segment_for ':login/:action' assert_equal :login, segment.key @@ -1273,105 +413,104 @@ class RouteBuilderTest < Test::Unit::TestCase assert_equal :action, segment.key assert_equal '', rest end - + def test_segments_for segments = builder.segments_for_route_path '/:controller/:action/:id' - + assert_kind_of ROUTING::DividerSegment, segments[0] assert_equal '/', segments[2].value - + assert_kind_of ROUTING::DynamicSegment, segments[1] assert_equal :controller, segments[1].key - + assert_kind_of ROUTING::DividerSegment, segments[2] assert_equal '/', segments[2].value - + assert_kind_of ROUTING::DynamicSegment, segments[3] assert_equal :action, segments[3].key - + assert_kind_of ROUTING::DividerSegment, segments[4] assert_equal '/', segments[4].value - + assert_kind_of ROUTING::DynamicSegment, segments[5] assert_equal :id, segments[5].key end - + def test_segment_for_action s, r = builder.segment_for(':action/something/else') assert_equal '/something/else', r assert_equal :action, s.key end - + def test_action_default_should_not_trigger_on_prefix s, r = builder.segment_for ':action_name/something/else' assert_equal '/something/else', r assert_equal :action_name, s.key assert_equal nil, s.default end - + def test_divide_route_options segments = builder.segments_for_route_path '/cars/:action/:person/:car/' defaults, requirements = builder.divide_route_options(segments, :action => 'buy', :person => /\w+/, :car => /\w+/, :defaults => {:person => nil, :car => nil} ) - + assert_equal({:action => 'buy', :person => nil, :car => nil}, defaults) assert_equal({:person => /\w+/, :car => /\w+/}, requirements) end - + def test_assign_route_options segments = builder.segments_for_route_path '/cars/:action/:person/:car/' defaults = {:action => 'buy', :person => nil, :car => nil} requirements = {:person => /\w+/, :car => /\w+/} - + route_requirements = builder.assign_route_options(segments, defaults, requirements) assert_equal({}, route_requirements) - + assert_equal :action, segments[3].key assert_equal 'buy', segments[3].default - + assert_equal :person, segments[5].key assert_equal %r/\w+/, segments[5].regexp assert segments[5].optional? - + assert_equal :car, segments[7].key assert_equal %r/\w+/, segments[7].regexp assert segments[7].optional? end - + def test_assign_route_options_with_anchor_chars segments = builder.segments_for_route_path '/cars/:action/:person/:car/' defaults = {:action => 'buy', :person => nil, :car => nil} requirements = {:person => /\w+/, :car => /^\w+$/} - + assert_raises ArgumentError do route_requirements = builder.assign_route_options(segments, defaults, requirements) end - + requirements[:car] = /[^\/]+/ route_requirements = builder.assign_route_options(segments, defaults, requirements) end - def test_optional_segments_preceding_required_segments segments = builder.segments_for_route_path '/cars/:action/:person/:car/' defaults = {:action => 'buy', :person => nil, :car => "model-t"} assert builder.assign_route_options(segments, defaults, {}).empty? - + 0.upto(1) { |i| assert !segments[i].optional?, "segment #{i} is optional and it shouldn't be" } assert segments[2].optional? - + assert_equal nil, builder.warn_output # should only warn on the :person segment end - + def test_segmentation_of_dot_path segments = builder.segments_for_route_path '/books/:action.rss' assert builder.assign_route_options(segments, {}, {}).empty? assert_equal 6, segments.length # "/", "books", "/", ":action", ".", "rss" assert !segments.any? { |seg| seg.optional? } end - + def test_segmentation_of_dynamic_dot_path segments = builder.segments_for_route_path '/books/:action.:format' assert builder.assign_route_options(segments, {}, {}).empty? @@ -1379,56 +518,56 @@ class RouteBuilderTest < Test::Unit::TestCase assert !segments.any? { |seg| seg.optional? } assert_kind_of ROUTING::DynamicSegment, segments.last end - + def test_assignment_of_default_options segments = builder.segments_for_route_path '/:controller/:action/:id/' action, id = segments[-4], segments[-2] - + assert_equal :action, action.key assert_equal :id, id.key assert ! action.optional? assert ! id.optional? - + builder.assign_default_route_options(segments) - + assert_equal 'index', action.default assert action.optional? assert id.optional? end - + def test_assignment_of_default_options_respects_existing_defaults segments = builder.segments_for_route_path '/:controller/:action/:id/' action, id = segments[-4], segments[-2] - + assert_equal :action, action.key assert_equal :id, id.key action.default = 'show' action.is_optional = true - + id.default = 'Welcome' id.is_optional = true - + builder.assign_default_route_options(segments) - + assert_equal 'show', action.default assert action.optional? assert_equal 'Welcome', id.default assert id.optional? end - + def test_assignment_of_default_options_respects_regexps segments = builder.segments_for_route_path '/:controller/:action/:id/' action = segments[-4] - + assert_equal :action, action.key action.regexp = /show|in/ # Use 'in' to check partial matches - + builder.assign_default_route_options(segments) - + assert_equal nil, action.default assert ! action.optional? end - + def test_assignment_of_is_optional_when_default segments = builder.segments_for_route_path '/books/:action.rss' assert_equal segments[3].key, :action @@ -1436,44 +575,44 @@ class RouteBuilderTest < Test::Unit::TestCase builder.ensure_required_segments(segments) assert ! segments[3].optional? end - + def test_is_optional_is_assigned_to_default_segments segments = builder.segments_for_route_path '/books/:action' builder.assign_route_options(segments, {:action => 'index'}, {}) - + assert_equal segments[3].key, :action assert segments[3].optional? assert_kind_of ROUTING::DividerSegment, segments[2] assert segments[2].optional? end - + # XXX is optional not being set right? # /blah/:defaulted_segment <-- is the second slash optional? it should be. - + def test_route_build ActionController::Routing.with_controllers %w(users pages) do r = builder.build '/:controller/:action/:id/', :action => nil - + [0, 2, 4].each do |i| assert_kind_of ROUTING::DividerSegment, r.segments[i] assert_equal '/', r.segments[i].value assert r.segments[i].optional? if i > 1 end - + assert_kind_of ROUTING::DynamicSegment, r.segments[1] assert_equal :controller, r.segments[1].key assert_equal nil, r.segments[1].default - + assert_kind_of ROUTING::DynamicSegment, r.segments[3] assert_equal :action, r.segments[3].key assert_equal 'index', r.segments[3].default - + assert_kind_of ROUTING::DynamicSegment, r.segments[5] assert_equal :id, r.segments[5].key assert r.segments[5].optional? end end - + def test_slashes_are_implied routes = [ builder.build('/:controller/:action/:id/', :action => nil), @@ -1487,866 +626,1699 @@ class RouteBuilderTest < Test::Unit::TestCase assert_equal expected, found, "Route #{i + 1} has #{found} segments, expected #{expected}" end end - end +class RoutingTest < Test::Unit::TestCase + def test_possible_controllers + true_controller_paths = ActionController::Routing.controller_paths + ActionController::Routing.use_controllers! nil + silence_warnings do + Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures') + end -class RouteSetTest < Test::Unit::TestCase + ActionController::Routing.controller_paths = [ + RAILS_ROOT, RAILS_ROOT + '/app/controllers', RAILS_ROOT + '/vendor/plugins/bad_plugin/lib' + ] - def set - @set ||= ROUTING::RouteSet.new + assert_equal ["admin/user", "plugin", "user"], ActionController::Routing.possible_controllers.sort + ensure + if true_controller_paths + ActionController::Routing.controller_paths = true_controller_paths + end + ActionController::Routing.use_controllers! nil + Object.send(:remove_const, :RAILS_ROOT) rescue nil end - def request - @request ||= MockRequest.new(:host => "named.routes.test", :method => :get) + def test_possible_controllers_are_reset_on_each_load + true_possible_controllers = ActionController::Routing.possible_controllers + true_controller_paths = ActionController::Routing.controller_paths + + ActionController::Routing.use_controllers! nil + root = File.dirname(__FILE__) + '/controller_fixtures' + + ActionController::Routing.controller_paths = [] + assert_equal [], ActionController::Routing.possible_controllers + + ActionController::Routing::Routes.load! + ActionController::Routing.controller_paths = [ + root, root + '/app/controllers', root + '/vendor/plugins/bad_plugin/lib' + ] + + assert_equal ["admin/user", "plugin", "user"], ActionController::Routing.possible_controllers.sort + ensure + ActionController::Routing.controller_paths = true_controller_paths + ActionController::Routing.use_controllers! true_possible_controllers + Object.send(:remove_const, :RAILS_ROOT) rescue nil + + ActionController::Routing::Routes.clear! + ActionController::Routing::Routes.load_routes! end - def test_generate_extras - set.draw { |m| m.connect ':controller/:action/:id' } - path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") - assert_equal "/foo/bar/15", path - assert_equal %w(that this), extras.map(&:to_s).sort + def test_with_controllers + c = %w(admin/accounts admin/users account pages) + ActionController::Routing.with_controllers c do + assert_equal c, ActionController::Routing.possible_controllers + end end - def test_extra_keys - set.draw { |m| m.connect ':controller/:action/:id' } - extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") - assert_equal %w(that this), extras.map(&:to_s).sort + def test_normalize_unix_paths + load_paths = %w(. config/../app/controllers config/../app//helpers script/../config/../vendor/rails/actionpack/lib vendor/rails/railties/builtin/rails_info app/models lib script/../config/../foo/bar/../../app/models .foo/../.bar foo.bar/../config) + paths = ActionController::Routing.normalize_paths(load_paths) + assert_equal %w(vendor/rails/railties/builtin/rails_info vendor/rails/actionpack/lib app/controllers app/helpers app/models config .bar lib .), paths end - - def test_generate_extras_not_first - set.draw do |map| - map.connect ':controller/:action/:id.:format' - map.connect ':controller/:action/:id' - end - path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") - assert_equal "/foo/bar/15", path - assert_equal %w(that this), extras.map(&:to_s).sort + + def test_normalize_windows_paths + load_paths = %w(. config\\..\\app\\controllers config\\..\\app\\\\helpers script\\..\\config\\..\\vendor\\rails\\actionpack\\lib vendor\\rails\\railties\\builtin\\rails_info app\\models lib script\\..\\config\\..\\foo\\bar\\..\\..\\app\\models .foo\\..\\.bar foo.bar\\..\\config) + paths = ActionController::Routing.normalize_paths(load_paths) + assert_equal %w(vendor\\rails\\railties\\builtin\\rails_info vendor\\rails\\actionpack\\lib app\\controllers app\\helpers app\\models config .bar lib .), paths end - - def test_generate_not_first - set.draw do |map| - map.connect ':controller/:action/:id.:format' - map.connect ':controller/:action/:id' - end - assert_equal "/foo/bar/15?this=hello", set.generate(:controller => "foo", :action => "bar", :id => 15, :this => "hello") + + def test_routing_helper_module + assert_kind_of Module, ActionController::Routing::Helpers + + h = ActionController::Routing::Helpers + c = Class.new + assert ! c.ancestors.include?(h) + ActionController::Routing::Routes.install_helpers c + assert c.ancestors.include?(h) end - - def test_extra_keys_not_first - set.draw do |map| - map.connect ':controller/:action/:id.:format' - map.connect ':controller/:action/:id' +end + +uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do + class MockController + attr_accessor :routes + + def initialize(routes) + self.routes = routes end - extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") - assert_equal %w(that this), extras.map(&:to_s).sort - end - def test_draw - assert_equal 0, set.routes.size - set.draw do |map| - map.connect '/hello/world', :controller => 'a', :action => 'b' + def url_for(options) + only_path = options.delete(:only_path) + + port = options.delete(:port) || 80 + port_string = port == 80 ? '' : ":#{port}" + + host = options.delete(:host) || "named.route.test" + anchor = "##{options.delete(:anchor)}" if options.key?(:anchor) + + path = routes.generate(options) + + only_path ? "#{path}#{anchor}" : "http://#{host}#{port_string}#{path}#{anchor}" end - assert_equal 1, set.routes.size - end - - def test_named_draw - assert_equal 0, set.routes.size - set.draw do |map| - map.hello '/hello/world', :controller => 'a', :action => 'b' + + def request + @request ||= MockRequest.new(:host => "named.route.test", :method => :get) end - assert_equal 1, set.routes.size - assert_equal set.routes.first, set.named_routes[:hello] - end - - def test_later_named_routes_take_precedence - set.draw do |map| - map.hello '/hello/world', :controller => 'a', :action => 'b' - map.hello '/hello', :controller => 'a', :action => 'b' + + def relative_url_root=(value) + request.relative_url_root=value end - assert_equal set.routes.last, set.named_routes[:hello] end - def setup_named_route_test - set.draw do |map| - map.show '/people/:id', :controller => 'people', :action => 'show' - map.index '/people', :controller => 'people', :action => 'index' - map.multi '/people/go/:foo/:bar/joe/:id', :controller => 'people', :action => 'multi' - map.users '/admin/users', :controller => 'admin/users', :action => 'index' + class MockRequest + attr_accessor :path, :path_parameters, :host, :subdomains, :domain, + :method, :relative_url_root + + 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 - klass = Class.new(MockController) - set.install_helpers(klass) - klass.new(set) + def host_with_port + (subdomains * '.') + '.' + domain + end end - def test_named_route_hash_access_method - controller = setup_named_route_test + class LegacyRouteSetTests < Test::Unit::TestCase + attr_reader :rs - assert_equal( - { :controller => 'people', :action => 'show', :id => 5, :use_route => :show, :only_path => false }, - controller.send(:hash_for_show_url, :id => 5)) + def setup + # These tests assume optimisation is on, so re-enable it. + ActionController::Base.optimise_named_routes = true - assert_equal( - { :controller => 'people', :action => 'index', :use_route => :index, :only_path => false }, - controller.send(:hash_for_index_url)) - - assert_equal( - { :controller => 'people', :action => 'show', :id => 5, :use_route => :show, :only_path => true }, - controller.send(:hash_for_show_path, :id => 5) - ) - end + @rs = ::ActionController::Routing::RouteSet.new + @rs.draw {|m| m.connect ':controller/:action/:id' } - 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 "/people/5", controller.send(:show_path, :id => 5) - - assert_equal "http://named.route.test/people", controller.send(:index_url) - assert_equal "/people", controller.send(:index_path) + ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed) + end - assert_equal "http://named.route.test/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 + def test_default_setup + assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content")) + assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list")) + assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10")) - def test_named_route_url_method_with_anchor - controller = setup_named_route_test + assert_equal({:controller => "admin/user", :action => 'show', :id => '10'}, rs.recognize_path("/admin/user/show/10")) - assert_equal "http://named.route.test/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 '/admin/user/show/10', rs.generate(:controller => 'admin/user', :action => 'show', :id => 10) - assert_equal "http://named.route.test/people#location", controller.send(:index_url, :anchor => 'location') - assert_equal "/people#location", controller.send(:index_path, :anchor => 'location') + assert_equal '/admin/user/show', rs.generate({:action => 'show'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) + assert_equal '/admin/user/list/10', rs.generate({}, {:controller => 'admin/user', :action => 'list', :id => '10'}) - assert_equal "http://named.route.test/admin/users#location", controller.send(:users_url, :anchor => 'location') - assert_equal '/admin/users#location', controller.send(:users_path, :anchor => 'location') + assert_equal '/admin/stuff', rs.generate({:controller => 'stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) + assert_equal '/stuff', rs.generate({:controller => '/stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) + end - assert_equal "http://named.route.test/people/go/7/hello/joe/5#location", - controller.send(:multi_url, 7, "hello", 5, :anchor => 'location') + def test_ignores_leading_slash + @rs.draw {|m| m.connect '/:controller/:action/:id'} + test_default_setup + end - assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar#location", - controller.send(:multi_url, 7, "hello", 5, :baz => "bar", :anchor => 'location') + def test_time_recognition + # We create many routes to make situation more realistic + @rs = ::ActionController::Routing::RouteSet.new + @rs.draw { |map| + map.frontpage '', :controller => 'search', :action => 'new' + map.resources :videos do |video| + video.resources :comments + video.resource :file, :controller => 'video_file' + video.resource :share, :controller => 'video_shares' + video.resource :abuse, :controller => 'video_abuses' + end + map.resources :abuses, :controller => 'video_abuses' + map.resources :video_uploads + map.resources :video_visits - assert_equal "http://named.route.test/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) - end - - def test_named_route_url_method_with_host - controller = setup_named_route_test - assert_equal "http://some.example.com/people/5", controller.send(:show_url, 5, :host=>"some.example.com") - end - + map.resources :users do |user| + user.resource :settings + user.resources :videos + end + map.resources :channels do |channel| + channel.resources :videos, :controller => 'channel_videos' + end + map.resource :session + map.resource :lost_password + map.search 'search', :controller => 'search' + map.resources :pages + map.connect ':controller/:action/:id' + } + n = 1000 + if RunTimeTests + GC.start + rectime = Benchmark.realtime do + n.times do + rs.recognize_path("/videos/1234567", {:method => :get}) + rs.recognize_path("/videos/1234567/abuse", {:method => :get}) + rs.recognize_path("/users/1234567/settings", {:method => :get}) + rs.recognize_path("/channels/1234567", {:method => :get}) + rs.recognize_path("/session/new", {:method => :get}) + rs.recognize_path("/admin/user/show/10", {:method => :get}) + end + end + puts "\n\nRecognition (#{rs.routes.size} routes):" + per_url = rectime / (n * 6) + puts "#{per_url * 1000} ms/url" + puts "#{1 / per_url} url/s\n\n" + end + end + def test_time_generation + n = 5000 + if RunTimeTests + GC.start + pairs = [ + [{:controller => 'content', :action => 'index'}, {:controller => 'content', :action => 'show'}], + [{:controller => 'content'}, {:controller => 'content', :action => 'index'}], + [{:controller => 'content', :action => 'list'}, {:controller => 'content', :action => 'index'}], + [{:controller => 'content', :action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'}], + [{:controller => 'admin/user', :action => 'index'}, {:controller => 'admin/user', :action => 'show'}], + [{:controller => 'admin/user'}, {:controller => 'admin/user', :action => 'index'}], + [{:controller => 'admin/user', :action => 'list'}, {:controller => 'admin/user', :action => 'index'}], + [{:controller => 'admin/user', :action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'}], + ] + p = nil + gentime = Benchmark.realtime do + n.times do + pairs.each {|(a, b)| rs.generate(a, b)} + end + 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", - controller.send(:multi_url, 7, "hello", 5) - end + puts "\n\nGeneration (RouteSet): (#{(n * 8)} urls)" + per_url = gentime / (n * 8) + puts "#{per_url * 1000} ms/url" + puts "#{1 / per_url} url/s\n\n" + end + 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", - controller.send(:multi_url, 7, "hello", 5, :baz => "bar") - 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", - controller.send(:index_url, :baz => "bar") - end - - def test_draw_default_route - ActionController::Routing.with_controllers(['users']) do - set.draw do |map| - map.connect '/:controller/:action/:id' + def test_route_with_colon_first + rs.draw do |map| + map.connect '/:controller/:action/:id', :action => 'index', :id => nil + map.connect ':url', :controller => 'tiny_url', :action => 'translate' end + end - assert_equal 1, set.routes.size - route = set.routes.first + def test_route_with_regexp_for_controller + rs.draw do |map| + map.connect ':controller/:admintoken/:action/:id', :controller => /admin\/.+/ + map.connect ':controller/:action/:id' + end + assert_equal({:controller => "admin/user", :admintoken => "foo", :action => "index"}, + rs.recognize_path("/admin/user/foo")) + assert_equal({:controller => "content", :action => "foo"}, rs.recognize_path("/content/foo")) + assert_equal '/admin/user/foo', rs.generate(:controller => "admin/user", :admintoken => "foo", :action => "index") + assert_equal '/content/foo', rs.generate(:controller => "content", :action => "foo") + end - assert route.segments.last.optional? + def test_route_with_regexp_and_dot + rs.draw do |map| + map.connect ':controller/:action/:file', + :controller => /admin|user/, + :action => /upload|download/, + :defaults => {:file => nil}, + :requirements => {:file => %r{[^/]+(\.[^/]+)?}} + end + # Without a file extension + assert_equal '/user/download/file', + rs.generate(:controller => "user", :action => "download", :file => "file") + assert_equal( + {:controller => "user", :action => "download", :file => "file"}, + rs.recognize_path("/user/download/file")) + + # Now, let's try a file with an extension, really a dot (.) + assert_equal '/user/download/file.jpg', + rs.generate( + :controller => "user", :action => "download", :file => "file.jpg") + assert_equal( + {:controller => "user", :action => "download", :file => "file.jpg"}, + rs.recognize_path("/user/download/file.jpg")) + end + + 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/", + x.send(:home_url)) + end - assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10) - assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10) + def test_basic_named_route_with_relative_url_root + rs.add_named_route :home, '', :controller => 'content', :action => 'list' + x = setup_for_named_route + x.relative_url_root="/foo" + assert_equal("http://named.route.test/foo/", + x.send(:home_url)) + assert_equal "/foo/", x.send(:home_path) + end - assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10')) - assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/')) + 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", + x.send(:page_url, :title => 'new stuff')) end - end - def test_draw_default_route_with_default_controller - ActionController::Routing.with_controllers(['users']) do - set.draw do |map| - map.connect '/:controller/:action/:id', :controller => 'users' - end - assert_equal({:controller => 'users', :action => 'index'}, set.recognize_path('/')) + 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", + x.send(:page_url, :title => "AboutRails")) + end - end - def test_route_with_parameter_shell - ActionController::Routing.with_controllers(['users', 'pages']) do - set.draw do |map| - map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ - map.connect '/:controller/:action/:id' + 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", + x.send(:users_url)) + end + + def test_optimised_named_route_call_never_uses_url_for + rs.add_named_route :users, 'admin/user', :controller => '/admin/user', :action => 'index' + rs.add_named_route :user, 'admin/user/:id', :controller=>'/admin/user', :action=>'show' + x = setup_for_named_route + x.expects(:url_for).never + x.send(:users_url) + x.send(:users_path) + x.send(:user_url, 2, :foo=>"bar") + x.send(:user_path, 3, :bar=>"foo") + end + + def test_optimised_named_route_with_host + rs.add_named_route :pages, 'pages', :controller => 'content', :action => 'show_page', :host => 'foo.com' + x = setup_for_named_route + x.expects(:url_for).with(:host => 'foo.com', :only_path => false, :controller => 'content', :action => 'show_page', :use_route => :pages).once + x.send(:pages_url) + end + + def setup_for_named_route + klass = Class.new(MockController) + rs.install_helpers(klass) + klass.new(rs) + end + + def test_named_route_without_hash + rs.draw do |map| + map.normal ':controller/:action/:id' + end + end + + def test_named_route_root + rs.draw do |map| + map.root :controller => "hello" end + x = setup_for_named_route + assert_equal("http://named.route.test/", x.send(:root_url)) + assert_equal("/", x.send(:root_path)) + end - assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages')) - assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index')) - assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list')) + def test_named_route_with_regexps + rs.draw do |map| + map.article 'page/:year/:month/:day/:title', :controller => 'page', :action => 'show', + :year => /\d+/, :month => /\d+/, :day => /\d+/ + map.connect ':controller/:action/:id' + end + x = setup_for_named_route + # assert_equal( + # {:controller => 'page', :action => 'show', :title => 'hi', :use_route => :article, :only_path => false}, + # x.send(:article_url, :title => 'hi') + # ) + assert_equal( + "http://named.route.test/page/2005/6/10/hi", + x.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6) + ) + end - assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10')) - assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10')) + def test_changing_controller + assert_equal '/admin/stuff/show/10', rs.generate( + {:controller => 'stuff', :action => 'show', :id => 10}, + {:controller => 'admin/user', :action => 'index'} + ) end - end - def test_route_requirements_with_anchor_chars_are_invalid - assert_raises ArgumentError do - set.draw do |map| - map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /^\d+/ + def test_paths_escaped + rs.draw do |map| + map.path 'file/*path', :controller => 'content', :action => 'show_file' + map.connect ':controller/:action/:id' end + + # No + to space in URI escaping, only for query params. + results = rs.recognize_path "/file/hello+world/how+are+you%3F" + assert results, "Recognition should have succeeded" + assert_equal ['hello+world', 'how+are+you?'], results[:path] + + # Use %20 for space instead. + results = rs.recognize_path "/file/hello%20world/how%20are%20you%3F" + assert results, "Recognition should have succeeded" + assert_equal ['hello world', 'how are you?'], results[:path] + + results = rs.recognize_path "/file" + assert results, "Recognition should have succeeded" + assert_equal [], results[:path] end - assert_raises ArgumentError do - set.draw do |map| - map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\A\d+/ + + def test_paths_slashes_unescaped_with_ordered_parameters + rs.add_named_route :path, '/file/*path', :controller => 'content' + + # No / to %2F in URI, only for query params. + x = setup_for_named_route + assert_equal("/file/hello/world", x.send(:path_path, 'hello/world')) + end + + def test_non_controllers_cannot_be_matched + rs.draw do |map| + map.connect ':controller/:action/:id' end + assert_raises(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") } end - assert_raises ArgumentError do - set.draw do |map| - map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+$/ + + def test_paths_do_not_accept_defaults + assert_raises(ActionController::RoutingError) do + rs.draw do |map| + map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default) + map.connect ':controller/:action/:id' + end + end + + rs.draw do |map| + map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => [] + map.connect ':controller/:action/:id' end end - assert_raises ArgumentError do - set.draw do |map| - map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\Z/ + + def test_should_list_options_diff_when_routing_requirements_dont_match + rs.draw do |map| + map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/} end + exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post") } + assert_match /^post_url failed to generate/, exception.message + from_match = exception.message.match(/from \{[^\}]+\}/).to_s + assert_match /:bad_param=>"foo"/, from_match + assert_match /:action=>"show"/, from_match + assert_match /:controller=>"post"/, from_match + + expected_match = exception.message.match(/expected: \{[^\}]+\}/).to_s + assert_no_match /:bad_param=>"foo"/, expected_match + assert_match /:action=>"show"/, expected_match + assert_match /:controller=>"post"/, expected_match + + diff_match = exception.message.match(/diff: \{[^\}]+\}/).to_s + assert_match /:bad_param=>"foo"/, diff_match + assert_no_match /:action=>"show"/, diff_match + assert_no_match /:controller=>"post"/, diff_match + end + + # this specifies the case where your formerly would get a very confusing error message with an empty diff + def test_should_have_better_error_message_when_options_diff_is_empty + rs.draw do |map| + map.content '/content/:query', :controller => 'content', :action => 'show' + end + + exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'content', :action => 'show', :use_route => "content") } + assert_match %r[:action=>"show"], exception.message + assert_match %r[:controller=>"content"], exception.message + assert_match %r[you may have ambiguous routes, or you may need to supply additional parameters for this route], exception.message + assert_match %r[content_url has the following required parameters: \["content", :query\] - are they all satisfied?], exception.message end - assert_raises ArgumentError do - set.draw do |map| - map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\z/ + + def test_dynamic_path_allowed + rs.draw do |map| + map.connect '*path', :controller => 'content', :action => 'show_file' + end + + assert_equal '/pages/boo', rs.generate(:controller => 'content', :action => 'show_file', :path => %w(pages boo)) + end + + def test_dynamic_recall_paths_allowed + rs.draw do |map| + map.connect '*path', :controller => 'content', :action => 'show_file' end + + recall_path = ActionController::Routing::PathSegment::Result.new(%w(pages boo)) + assert_equal '/pages/boo', rs.generate({}, :controller => 'content', :action => 'show_file', :path => recall_path) end - assert_nothing_raised do - set.draw do |map| - map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/, :name => /^(david|jamis)/ + + def test_backwards + rs.draw do |map| + map.connect 'page/:id/:action', :controller => 'pages', :action => 'show' + map.connect ':controller/:action/:id' + end + + assert_equal '/page/20', rs.generate({:id => 20}, {:controller => 'pages', :action => 'show'}) + assert_equal '/page/20', rs.generate(:controller => 'pages', :id => 20, :action => 'show') + assert_equal '/pages/boo', rs.generate(:controller => 'pages', :action => 'boo') + end + + def test_route_with_fixnum_default + rs.draw do |map| + map.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1 + map.connect ':controller/:action/:id' + end + + assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page') + assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page', :id => 1) + assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page', :id => '1') + assert_equal '/page/10', rs.generate(:controller => 'content', :action => 'show_page', :id => 10) + + assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page")) + assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page/1")) + assert_equal({:controller => "content", :action => 'show_page', :id => '10'}, rs.recognize_path("/page/10")) + end + + # For newer revision + def test_route_with_text_default + rs.draw do |map| + map.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1 + map.connect ':controller/:action/:id' + end + + assert_equal '/page/foo', rs.generate(:controller => 'content', :action => 'show_page', :id => 'foo') + assert_equal({:controller => "content", :action => 'show_page', :id => 'foo'}, rs.recognize_path("/page/foo")) + + token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in russian + escaped_token = CGI::escape(token) + + assert_equal '/page/' + escaped_token, rs.generate(:controller => 'content', :action => 'show_page', :id => token) + assert_equal({:controller => "content", :action => 'show_page', :id => token}, rs.recognize_path("/page/#{escaped_token}")) + end + + def test_action_expiry + assert_equal '/content', rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) + end + + def test_recognition_with_uppercase_controller_name + assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/Content")) + assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/ConTent/list")) + assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/CONTENT/show/10")) + + # these used to work, before the routes rewrite, but support for this was pulled in the new version... + #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/NewsFeed")) + #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/News_Feed")) + end + + def test_requirement_should_prevent_optional_id + rs.draw do |map| + map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/} end + + assert_equal '/post/10', rs.generate(:controller => 'post', :action => 'show', :id => 10) + assert_raises ActionController::RoutingError do - set.generate :controller => 'pages', :action => 'show', :id => 10 + rs.generate(:controller => 'post', :action => 'show') end end - end - def test_non_path_route_requirements_match_all - set.draw do |map| - map.connect 'page/37s', :controller => 'pages', :action => 'show', :name => /(jamis|david)/ + def test_both_requirement_and_optional + rs.draw do |map| + map.blog('test/:year', :controller => 'post', :action => 'show', + :defaults => { :year => nil }, + :requirements => { :year => /\d{4}/ } + ) + map.connect ':controller/:action/:id' + end + + assert_equal '/test', rs.generate(:controller => 'post', :action => 'show') + assert_equal '/test', rs.generate(:controller => 'post', :action => 'show', :year => nil) + + x = setup_for_named_route + assert_equal("http://named.route.test/test", + x.send(:blog_url)) end - assert_equal '/page/37s', set.generate(:controller => 'pages', :action => 'show', :name => 'jamis') - assert_raises ActionController::RoutingError do - set.generate(:controller => 'pages', :action => 'show', :name => 'not_jamis') + + def test_set_to_nil_forgets + rs.draw do |map| + map.connect 'pages/:year/:month/:day', :controller => 'content', :action => 'list_pages', :month => nil, :day => nil + map.connect ':controller/:action/:id' + end + + assert_equal '/pages/2005', + rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005) + assert_equal '/pages/2005/6', + rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6) + assert_equal '/pages/2005/6/12', + rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6, :day => 12) + + assert_equal '/pages/2005/6/4', + rs.generate({:day => 4}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) + + assert_equal '/pages/2005/6', + rs.generate({:day => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) + + assert_equal '/pages/2005', + rs.generate({:day => nil, :month => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) end - assert_raises ActionController::RoutingError do - set.generate(:controller => 'pages', :action => 'show', :name => 'nor_jamis_and_david') + + def test_url_with_no_action_specified + rs.draw do |map| + map.connect '', :controller => 'content' + map.connect ':controller/:action/:id' + end + + assert_equal '/', rs.generate(:controller => 'content', :action => 'index') + assert_equal '/', rs.generate(:controller => 'content') end - end - - def test_recognize_with_encoded_id_and_regex - set.draw do |map| - map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /[a-zA-Z0-9\+]+/ + + def test_named_url_with_no_action_specified + rs.draw do |map| + map.home '', :controller => 'content' + map.connect ':controller/:action/:id' + end + + assert_equal '/', rs.generate(:controller => 'content', :action => 'index') + assert_equal '/', rs.generate(:controller => 'content') + + x = setup_for_named_route + assert_equal("http://named.route.test/", + x.send(:home_url)) end - assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10')) - assert_equal({:controller => 'pages', :action => 'show', :id => 'hello+world'}, set.recognize_path('/page/hello+world')) - end + def test_url_generated_when_forgetting_action + [{:controller => 'content', :action => 'index'}, {:controller => 'content'}].each do |hash| + rs.draw do |map| + map.home '', hash + map.connect ':controller/:action/:id' + end + assert_equal '/', rs.generate({:action => nil}, {:controller => 'content', :action => 'hello'}) + assert_equal '/', rs.generate({:controller => 'content'}) + assert_equal '/content/hi', rs.generate({:controller => 'content', :action => 'hi'}) + end + end - def test_recognize_with_conditions - Object.const_set(:PeopleController, Class.new) + def test_named_route_method + rs.draw do |map| + map.categories 'categories', :controller => 'content', :action => 'categories' + map.connect ':controller/:action/:id' + end - set.draw do |map| - map.with_options(:controller => "people") do |people| - people.people "/people", :action => "index", :conditions => { :method => :get } - people.connect "/people", :action => "create", :conditions => { :method => :post } - people.person "/people/:id", :action => "show", :conditions => { :method => :get } - people.connect "/people/:id", :action => "update", :conditions => { :method => :put } - people.connect "/people/:id", :action => "destroy", :conditions => { :method => :delete } + assert_equal '/categories', rs.generate(:controller => 'content', :action => 'categories') + assert_equal '/content/hi', rs.generate({:controller => 'content', :action => 'hi'}) + end + + def test_named_routes_array + test_named_route_method + assert_equal [:categories], rs.named_routes.names + end + + def test_nil_defaults + rs.draw do |map| + map.connect 'journal', + :controller => 'content', + :action => 'list_journal', + :date => nil, :user_id => nil + map.connect ':controller/:action/:id' end + + assert_equal '/journal', rs.generate(:controller => 'content', :action => 'list_journal', :date => nil, :user_id => nil) end - request.path = "/people" - request.method = :get - assert_nothing_raised { set.recognize(request) } - assert_equal("index", request.path_parameters[:action]) - - request.method = :post - assert_nothing_raised { set.recognize(request) } - assert_equal("create", request.path_parameters[:action]) - - request.method = :put - assert_nothing_raised { set.recognize(request) } - assert_equal("update", request.path_parameters[:action]) + def setup_request_method_routes_for(method) + @request = ActionController::TestRequest.new + @request.env["REQUEST_METHOD"] = method + @request.request_uri = "/match" - begin - request.method = :bacon - set.recognize(request) - flunk 'Should have raised NotImplemented' - rescue ActionController::NotImplemented => e - assert_equal [:get, :post, :put, :delete], e.allowed_methods + rs.draw do |r| + r.connect '/match', :controller => 'books', :action => 'get', :conditions => { :method => :get } + r.connect '/match', :controller => 'books', :action => 'post', :conditions => { :method => :post } + r.connect '/match', :controller => 'books', :action => 'put', :conditions => { :method => :put } + r.connect '/match', :controller => 'books', :action => 'delete', :conditions => { :method => :delete } + end end - request.path = "/people/5" - request.method = :get - assert_nothing_raised { set.recognize(request) } - assert_equal("show", request.path_parameters[:action]) - assert_equal("5", request.path_parameters[:id]) + %w(GET POST PUT DELETE).each do |request_method| + define_method("test_request_method_recognized_with_#{request_method}") do + begin + Object.const_set(:BooksController, Class.new(ActionController::Base)) + + setup_request_method_routes_for(request_method) - request.method = :put - assert_nothing_raised { set.recognize(request) } - assert_equal("update", request.path_parameters[:action]) - assert_equal("5", request.path_parameters[:id]) + assert_nothing_raised { rs.recognize(@request) } + assert_equal request_method.downcase, @request.path_parameters[:action] + ensure + Object.send(:remove_const, :BooksController) rescue nil + end + end + end - request.method = :delete - assert_nothing_raised { set.recognize(request) } - assert_equal("destroy", request.path_parameters[:action]) - assert_equal("5", request.path_parameters[:id]) + def test_subpath_recognized + Object.const_set(:SubpathBooksController, Class.new(ActionController::Base)) - begin - request.method = :post - set.recognize(request) - flunk 'Should have raised MethodNotAllowed' - rescue ActionController::MethodNotAllowed => e - assert_equal [:get, :put, :delete], e.allowed_methods + rs.draw do |r| + r.connect '/books/:id/edit', :controller => 'subpath_books', :action => 'edit' + r.connect '/items/:id/:action', :controller => 'subpath_books' + r.connect '/posts/new/:action', :controller => 'subpath_books' + r.connect '/posts/:id', :controller => 'subpath_books', :action => "show" + end + + hash = rs.recognize_path "/books/17/edit" + assert_not_nil hash + assert_equal %w(subpath_books 17 edit), [hash[:controller], hash[:id], hash[:action]] + + hash = rs.recognize_path "/items/3/complete" + assert_not_nil hash + assert_equal %w(subpath_books 3 complete), [hash[:controller], hash[:id], hash[:action]] + + hash = rs.recognize_path "/posts/new/preview" + assert_not_nil hash + assert_equal %w(subpath_books preview), [hash[:controller], hash[:action]] + + hash = rs.recognize_path "/posts/7" + assert_not_nil hash + assert_equal %w(subpath_books show 7), [hash[:controller], hash[:action], hash[:id]] + ensure + Object.send(:remove_const, :SubpathBooksController) rescue nil end - ensure - Object.send(:remove_const, :PeopleController) - end - - def test_recognize_with_alias_in_conditions - Object.const_set(:PeopleController, Class.new) + def test_subpath_generated + Object.const_set(:SubpathBooksController, Class.new(ActionController::Base)) + + rs.draw do |r| + r.connect '/books/:id/edit', :controller => 'subpath_books', :action => 'edit' + r.connect '/items/:id/:action', :controller => 'subpath_books' + r.connect '/posts/new/:action', :controller => 'subpath_books' + end - set.draw do |map| - map.people "/people", :controller => 'people', :action => "index", - :conditions => { :method => :get } - map.root :people + assert_equal "/books/7/edit", rs.generate(:controller => "subpath_books", :id => 7, :action => "edit") + assert_equal "/items/15/complete", rs.generate(:controller => "subpath_books", :id => 15, :action => "complete") + assert_equal "/posts/new/preview", rs.generate(:controller => "subpath_books", :action => "preview") + ensure + Object.send(:remove_const, :SubpathBooksController) rescue nil end - request.path = "/people" - request.method = :get - assert_nothing_raised { set.recognize(request) } - assert_equal("people", request.path_parameters[:controller]) - assert_equal("index", request.path_parameters[:action]) + def test_failed_requirements_raises_exception_with_violated_requirements + rs.draw do |r| + r.foo_with_requirement 'foos/:id', :controller=>'foos', :requirements=>{:id=>/\d+/} + end - request.path = "/" - request.method = :get - assert_nothing_raised { set.recognize(request) } - assert_equal("people", request.path_parameters[:controller]) - assert_equal("index", request.path_parameters[:action]) - ensure - Object.send(:remove_const, :PeopleController) - end - - def test_typo_recognition - Object.const_set(:ArticlesController, Class.new) - - set.draw do |map| - map.connect 'articles/:year/:month/:day/:title', - :controller => 'articles', :action => 'permalink', - :year => /\d{4}/, :day => /\d{1,2}/, :month => /\d{1,2}/ - end - - request.path = "/articles/2005/11/05/a-very-interesting-article" - request.method = :get - assert_nothing_raised { set.recognize(request) } - assert_equal("permalink", request.path_parameters[:action]) - assert_equal("2005", request.path_parameters[:year]) - assert_equal("11", request.path_parameters[:month]) - assert_equal("05", request.path_parameters[:day]) - assert_equal("a-very-interesting-article", request.path_parameters[:title]) - - ensure - Object.send(:remove_const, :ArticlesController) + x = setup_for_named_route + assert_raises(ActionController::RoutingError) do + x.send(:foo_with_requirement_url, "I am Against the requirements") + end + end end - def test_routing_traversal_does_not_load_extra_classes - assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded" - set.draw do |map| - map.connect '/profile', :controller => 'profile' + class RouteTest < Test::Unit::TestCase + def setup + @route = ROUTING::Route.new end - request.path = '/profile' + def slash_segment(is_optional = false) + returning ROUTING::DividerSegment.new('/') do |s| + s.is_optional = is_optional + end + end - set.recognize(request) rescue nil - - assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded" - end + def default_route + unless defined?(@default_route) + @default_route = ROUTING::Route.new + + @default_route.segments << (s = ROUTING::StaticSegment.new) + s.value = '/' + s.raw = true + + @default_route.segments << (s = ROUTING::DynamicSegment.new) + s.key = :controller + + @default_route.segments << slash_segment(:optional) + @default_route.segments << (s = ROUTING::DynamicSegment.new) + s.key = :action + s.default = 'index' + s.is_optional = true - def test_recognize_with_conditions_and_format - Object.const_set(:PeopleController, Class.new) + @default_route.segments << slash_segment(:optional) + @default_route.segments << (s = ROUTING::DynamicSegment.new) + s.key = :id + s.is_optional = true - set.draw do |map| - map.with_options(:controller => "people") do |people| - people.person "/people/:id", :action => "show", :conditions => { :method => :get } - people.connect "/people/:id", :action => "update", :conditions => { :method => :put } - people.connect "/people/:id.:_format", :action => "show", :conditions => { :method => :get } + @default_route.segments << slash_segment(:optional) end + @default_route end - request.path = "/people/5" - request.method = :get - assert_nothing_raised { set.recognize(request) } - assert_equal("show", request.path_parameters[:action]) - assert_equal("5", request.path_parameters[:id]) + def test_default_route_recognition + expected = {:controller => 'accounts', :action => 'show', :id => '10'} + assert_equal expected, default_route.recognize('/accounts/show/10') + assert_equal expected, default_route.recognize('/accounts/show/10/') - request.method = :put - assert_nothing_raised { set.recognize(request) } - assert_equal("update", request.path_parameters[:action]) + expected[:id] = 'jamis' + assert_equal expected, default_route.recognize('/accounts/show/jamis/') - request.path = "/people/5.png" - request.method = :get - assert_nothing_raised { set.recognize(request) } - assert_equal("show", request.path_parameters[:action]) - assert_equal("5", request.path_parameters[:id]) - assert_equal("png", request.path_parameters[:_format]) - ensure - Object.send(:remove_const, :PeopleController) - end + expected.delete :id + assert_equal expected, default_route.recognize('/accounts/show') + assert_equal expected, default_route.recognize('/accounts/show/') - def test_generate_with_default_action - set.draw do |map| - map.connect "/people", :controller => "people" - map.connect "/people/list", :controller => "people", :action => "list" + expected[:action] = 'index' + assert_equal expected, default_route.recognize('/accounts/') + assert_equal expected, default_route.recognize('/accounts') + + assert_equal nil, default_route.recognize('/') + assert_equal nil, default_route.recognize('/accounts/how/goood/it/is/to/be/free') end - url = set.generate(:controller => "people", :action => "list") - assert_equal "/people/list", url - end - - def test_root_map - Object.const_set(:PeopleController, Class.new) + def test_default_route_should_omit_default_action + o = {:controller => 'accounts', :action => 'index'} + assert_equal '/accounts', default_route.generate(o, o, {}) + end - set.draw { |map| map.root :controller => "people" } + def test_default_route_should_include_default_action_when_id_present + o = {:controller => 'accounts', :action => 'index', :id => '20'} + assert_equal '/accounts/index/20', default_route.generate(o, o, {}) + end - request.path = "" - request.method = :get - assert_nothing_raised { set.recognize(request) } - assert_equal("people", request.path_parameters[:controller]) - assert_equal("index", request.path_parameters[:action]) - ensure - Object.send(:remove_const, :PeopleController) - end - - - def test_namespace - Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) }) + def test_default_route_should_work_with_action_but_no_id + o = {:controller => 'accounts', :action => 'list_all'} + assert_equal '/accounts/list_all', default_route.generate(o, o, {}) + end + + def test_default_route_should_uri_escape_pluses + expected = { :controller => 'accounts', :action => 'show', :id => 'hello world' } + assert_equal expected, default_route.recognize('/accounts/show/hello world') + assert_equal expected, default_route.recognize('/accounts/show/hello%20world') + assert_equal '/accounts/show/hello%20world', default_route.generate(expected, expected, {}) + + expected[:id] = 'hello+world' + assert_equal expected, default_route.recognize('/accounts/show/hello+world') + assert_equal expected, default_route.recognize('/accounts/show/hello%2Bworld') + assert_equal '/accounts/show/hello+world', default_route.generate(expected, expected, {}) + end - set.draw do |map| - - map.namespace 'api' do |api| - api.route 'inventory', :controller => "products", :action => 'inventory' + def test_matches_controller_and_action + # requirement_for should only be called for the action and controller _once_ + @route.expects(:requirement_for).with(:controller).times(1).returns('pages') + @route.expects(:requirement_for).with(:action).times(1).returns('show') + + @route.requirements = {:controller => 'pages', :action => 'show'} + assert @route.matches_controller_and_action?('pages', 'show') + assert !@route.matches_controller_and_action?('not_pages', 'show') + assert !@route.matches_controller_and_action?('pages', 'not_show') + end + + def test_parameter_shell + page_url = ROUTING::Route.new + page_url.requirements = {:controller => 'pages', :action => 'show', :id => /\d+/} + assert_equal({:controller => 'pages', :action => 'show'}, page_url.parameter_shell) + end + + def test_defaults + route = ROUTING::RouteBuilder.new.build '/users/:id.:format', :controller => "users", :action => "show", :format => "html" + assert_equal( + { :controller => "users", :action => "show", :format => "html" }, + route.defaults) + end + + def test_builder_complains_without_controller + assert_raises(ArgumentError) do + ROUTING::RouteBuilder.new.build '/contact', :contoller => "contact", :action => "index" end - end - request.path = "/api/inventory" - request.method = :get - assert_nothing_raised { set.recognize(request) } - assert_equal("api/products", request.path_parameters[:controller]) - assert_equal("inventory", request.path_parameters[:action]) - ensure - Object.send(:remove_const, :Api) + def test_significant_keys_for_default_route + keys = default_route.significant_keys.sort_by {|k| k.to_s } + assert_equal [:action, :controller, :id], keys + end + + def test_significant_keys + user_url = ROUTING::Route.new + user_url.segments << (s = ROUTING::StaticSegment.new) + s.value = '/' + s.raw = true + + user_url.segments << (s = ROUTING::StaticSegment.new) + s.value = 'user' + + user_url.segments << (s = ROUTING::StaticSegment.new) + s.value = '/' + s.raw = true + s.is_optional = true + + user_url.segments << (s = ROUTING::DynamicSegment.new) + s.key = :user + + user_url.segments << (s = ROUTING::StaticSegment.new) + s.value = '/' + s.raw = true + s.is_optional = true + + user_url.requirements = {:controller => 'users', :action => 'show'} + + keys = user_url.significant_keys.sort_by { |k| k.to_s } + assert_equal [:action, :controller, :user], keys + end + + def test_build_empty_query_string + assert_equal '', @route.build_query_string({}) + end + + def test_build_query_string_with_nil_value + assert_equal '', @route.build_query_string({:x => nil}) + end + + def test_simple_build_query_string + assert_equal '?x=1&y=2', order_query_string(@route.build_query_string(:x => '1', :y => '2')) + end + + def test_convert_ints_build_query_string + assert_equal '?x=1&y=2', order_query_string(@route.build_query_string(:x => 1, :y => 2)) + end + + def test_escape_spaces_build_query_string + assert_equal '?x=hello+world&y=goodbye+world', order_query_string(@route.build_query_string(:x => 'hello world', :y => 'goodbye world')) + end + + def test_expand_array_build_query_string + assert_equal '?x%5B%5D=1&x%5B%5D=2', order_query_string(@route.build_query_string(:x => [1, 2])) + end + + def test_escape_spaces_build_query_string_selected_keys + assert_equal '?x=hello+world', order_query_string(@route.build_query_string({:x => 'hello world', :y => 'goodbye world'}, [:x])) + end + + private + def order_query_string(qs) + '?' + qs[1..-1].split('&').sort.join('&') + end end - - def test_namespaced_root_map - Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) }) + class RouteSetTest < Test::Unit::TestCase + def set + @set ||= ROUTING::RouteSet.new + end + + def request + @request ||= MockRequest.new(:host => "named.routes.test", :method => :get) + end + + def test_generate_extras + set.draw { |m| m.connect ':controller/:action/:id' } + path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") + assert_equal "/foo/bar/15", path + assert_equal %w(that this), extras.map(&:to_s).sort + end + + def test_extra_keys + set.draw { |m| m.connect ':controller/:action/:id' } + extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") + assert_equal %w(that this), extras.map(&:to_s).sort + end - set.draw do |map| - - map.namespace 'api' do |api| - api.root :controller => "products" + def test_generate_extras_not_first + set.draw do |map| + map.connect ':controller/:action/:id.:format' + map.connect ':controller/:action/:id' end - + path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") + assert_equal "/foo/bar/15", path + assert_equal %w(that this), extras.map(&:to_s).sort end - request.path = "/api" - request.method = :get - assert_nothing_raised { set.recognize(request) } - assert_equal("api/products", request.path_parameters[:controller]) - assert_equal("index", request.path_parameters[:action]) - ensure - Object.send(:remove_const, :Api) - end + def test_generate_not_first + set.draw do |map| + map.connect ':controller/:action/:id.:format' + map.connect ':controller/:action/:id' + end + assert_equal "/foo/bar/15?this=hello", set.generate(:controller => "foo", :action => "bar", :id => 15, :this => "hello") + end - def test_generate_finds_best_fit - set.draw do |map| - map.connect "/people", :controller => "people", :action => "index" - map.connect "/ws/people", :controller => "people", :action => "index", :ws => true + def test_extra_keys_not_first + set.draw do |map| + map.connect ':controller/:action/:id.:format' + map.connect ':controller/:action/:id' + end + extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") + assert_equal %w(that this), extras.map(&:to_s).sort end - url = set.generate(:controller => "people", :action => "index", :ws => true) - assert_equal "/ws/people", url - end + def test_draw + assert_equal 0, set.routes.size + set.draw do |map| + map.connect '/hello/world', :controller => 'a', :action => 'b' + end + assert_equal 1, set.routes.size + end - def test_generate_changes_controller_module - set.draw { |map| map.connect ':controller/:action/:id' } - current = { :controller => "bling/bloop", :action => "bap", :id => 9 } - url = set.generate({:controller => "foo/bar", :action => "baz", :id => 7}, current) - assert_equal "/foo/bar/baz/7", url - end + def test_named_draw + assert_equal 0, set.routes.size + set.draw do |map| + map.hello '/hello/world', :controller => 'a', :action => 'b' + end + assert_equal 1, set.routes.size + assert_equal set.routes.first, set.named_routes[:hello] + end - def test_id_is_not_impossibly_sticky - set.draw do |map| - map.connect 'foo/:number', :controller => "people", :action => "index" - map.connect ':controller/:action/:id' + def test_later_named_routes_take_precedence + set.draw do |map| + map.hello '/hello/world', :controller => 'a', :action => 'b' + map.hello '/hello', :controller => 'a', :action => 'b' + end + assert_equal set.routes.last, set.named_routes[:hello] end - url = set.generate({:controller => "people", :action => "index", :number => 3}, - {:controller => "people", :action => "index", :id => "21"}) - assert_equal "/foo/3", url - end + def setup_named_route_test + set.draw do |map| + map.show '/people/:id', :controller => 'people', :action => 'show' + map.index '/people', :controller => 'people', :action => 'index' + map.multi '/people/go/:foo/:bar/joe/:id', :controller => 'people', :action => 'multi' + map.users '/admin/users', :controller => 'admin/users', :action => 'index' + end - def test_id_is_sticky_when_it_ought_to_be - set.draw do |map| - map.connect ':controller/:id/:action' + klass = Class.new(MockController) + set.install_helpers(klass) + klass.new(set) end - url = set.generate({:action => "destroy"}, {:controller => "people", :action => "show", :id => "7"}) - assert_equal "/people/7/destroy", url - end + def test_named_route_hash_access_method + controller = setup_named_route_test + + assert_equal( + { :controller => 'people', :action => 'show', :id => 5, :use_route => :show, :only_path => false }, + controller.send(:hash_for_show_url, :id => 5)) + + assert_equal( + { :controller => 'people', :action => 'index', :use_route => :index, :only_path => false }, + controller.send(:hash_for_index_url)) - def test_use_static_path_when_possible - set.draw do |map| - map.connect 'about', :controller => "welcome", :action => "about" - map.connect ':controller/:action/:id' + assert_equal( + { :controller => 'people', :action => 'show', :id => 5, :use_route => :show, :only_path => true }, + controller.send(:hash_for_show_path, :id => 5) + ) end - url = set.generate({:controller => "welcome", :action => "about"}, - {:controller => "welcome", :action => "get", :id => "7"}) - assert_equal "/about", url - end + def test_named_route_url_method + controller = setup_named_route_test - def test_generate - set.draw { |map| map.connect ':controller/:action/:id' } + assert_equal "http://named.route.test/people/5", controller.send(:show_url, :id => 5) + assert_equal "/people/5", controller.send(:show_path, :id => 5) - args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" } - assert_equal "/foo/bar/7?x=y", set.generate(args) - assert_equal ["/foo/bar/7", [:x]], set.generate_extras(args) - assert_equal [:x], set.extra_keys(args) - end + assert_equal "http://named.route.test/people", controller.send(:index_url) + assert_equal "/people", controller.send(:index_path) - def test_named_routes_are_never_relative_to_modules - set.draw do |map| - map.connect "/connection/manage/:action", :controller => 'connection/manage' - map.connect "/connection/connection", :controller => "connection/connection" - map.family_connection "/connection", :controller => "connection" + assert_equal "http://named.route.test/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 - url = set.generate({:controller => "connection"}, {:controller => 'connection/manage'}) - assert_equal "/connection/connection", url + def test_named_route_url_method_with_anchor + controller = setup_named_route_test - url = set.generate({:use_route => :family_connection, :controller => "connection"}, {:controller => 'connection/manage'}) - assert_equal "/connection", url - end - - def test_action_left_off_when_id_is_recalled - set.draw do |map| - map.connect ':controller/:action/:id' + assert_equal "http://named.route.test/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 "/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 '/admin/users#location', controller.send(:users_path, :anchor => 'location') + + assert_equal "http://named.route.test/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", + controller.send(:multi_url, 7, "hello", 5, :baz => "bar", :anchor => 'location') + + assert_equal "http://named.route.test/people?baz=bar#location", + controller.send(:index_url, :baz => "bar", :anchor => 'location') end - assert_equal '/post', set.generate( - {:controller => 'post', :action => 'index'}, - {:controller => 'post', :action => 'show', :id => '10'} - ) - end - - def test_query_params_will_be_shown_when_recalled - set.draw do |map| - map.connect 'show_post/:parameter', :controller => 'post', :action => 'show' - map.connect ':controller/:action/:id' + + 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) end - assert_equal '/post/edit?parameter=1', set.generate( - {:action => 'edit', :parameter => 1}, - {:controller => 'post', :action => 'show', :parameter => 1} - ) - end - def test_expiry_determination_should_consider_values_with_to_param - set.draw { |map| map.connect 'projects/:project_id/:controller/:action' } - assert_equal '/projects/1/post/show', set.generate( - {:action => 'show', :project_id => 1}, - {:controller => 'post', :action => 'show', :project_id => '1'}) - end + def test_named_route_url_method_with_host + controller = setup_named_route_test + assert_equal "http://some.example.com/people/5", controller.send(:show_url, 5, :host=>"some.example.com") + end - def test_generate_all - set.draw do |map| - map.connect 'show_post/:id', :controller => 'post', :action => 'show' - map.connect ':controller/:action/:id' + 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", + controller.send(:multi_url, 7, "hello", 5) end - all = set.generate( - {:action => 'show', :id => 10, :generate_all => true}, - {:controller => 'post', :action => 'show'} - ) - assert_equal 2, all.length - assert_equal '/show_post/10', all.first - assert_equal '/post/show/10', all.last - end - - def test_named_route_in_nested_resource - set.draw do |map| - map.resources :projects do |project| - project.milestones 'milestones', :controller => 'milestones', :action => 'index' - end - end - - request.path = "/projects/1/milestones" - request.method = :get - assert_nothing_raised { set.recognize(request) } - assert_equal("milestones", request.path_parameters[:controller]) - assert_equal("index", request.path_parameters[:action]) - end - - def test_setting_root_in_namespace_using_symbol - assert_nothing_raised do + + 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", + controller.send(:multi_url, 7, "hello", 5, :baz => "bar") + 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", + controller.send(:index_url, :baz => "bar") + end + + def test_draw_default_route + ActionController::Routing.with_controllers(['users']) do + set.draw do |map| + map.connect '/:controller/:action/:id' + end + + assert_equal 1, set.routes.size + route = set.routes.first + + assert route.segments.last.optional? + + assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10) + assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10) + + assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10')) + assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/')) + end + end + + def test_draw_default_route_with_default_controller + ActionController::Routing.with_controllers(['users']) do + set.draw do |map| + map.connect '/:controller/:action/:id', :controller => 'users' + end + assert_equal({:controller => 'users', :action => 'index'}, set.recognize_path('/')) + end + end + + def test_route_with_parameter_shell + ActionController::Routing.with_controllers(['users', 'pages']) do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ + map.connect '/:controller/:action/:id' + end + + assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages')) + assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index')) + assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list')) + + assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10')) + assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10')) + end + end + + def test_route_requirements_with_anchor_chars_are_invalid + assert_raises ArgumentError do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /^\d+/ + end + end + assert_raises ArgumentError do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\A\d+/ + end + end + assert_raises ArgumentError do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+$/ + end + end + assert_raises ArgumentError do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\Z/ + end + end + assert_raises ArgumentError do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\z/ + end + end + assert_nothing_raised do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/, :name => /^(david|jamis)/ + end + assert_raises ActionController::RoutingError do + set.generate :controller => 'pages', :action => 'show', :id => 10 + end + end + end + + def test_non_path_route_requirements_match_all set.draw do |map| - map.namespace :admin do |admin| - admin.root :controller => 'home' + map.connect 'page/37s', :controller => 'pages', :action => 'show', :name => /(jamis|david)/ + end + assert_equal '/page/37s', set.generate(:controller => 'pages', :action => 'show', :name => 'jamis') + assert_raises ActionController::RoutingError do + set.generate(:controller => 'pages', :action => 'show', :name => 'not_jamis') + end + assert_raises ActionController::RoutingError do + set.generate(:controller => 'pages', :action => 'show', :name => 'nor_jamis_and_david') + end + end + + def test_recognize_with_encoded_id_and_regex + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /[a-zA-Z0-9\+]+/ + end + + assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10')) + assert_equal({:controller => 'pages', :action => 'show', :id => 'hello+world'}, set.recognize_path('/page/hello+world')) + end + + def test_recognize_with_conditions + Object.const_set(:PeopleController, Class.new) + + set.draw do |map| + map.with_options(:controller => "people") do |people| + people.people "/people", :action => "index", :conditions => { :method => :get } + people.connect "/people", :action => "create", :conditions => { :method => :post } + people.person "/people/:id", :action => "show", :conditions => { :method => :get } + people.connect "/people/:id", :action => "update", :conditions => { :method => :put } + people.connect "/people/:id", :action => "destroy", :conditions => { :method => :delete } end end + + request.path = "/people" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("index", request.path_parameters[:action]) + + request.method = :post + assert_nothing_raised { set.recognize(request) } + assert_equal("create", request.path_parameters[:action]) + + request.method = :put + assert_nothing_raised { set.recognize(request) } + assert_equal("update", request.path_parameters[:action]) + + begin + 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.path = "/people/5" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("show", request.path_parameters[:action]) + assert_equal("5", request.path_parameters[:id]) + + request.method = :put + assert_nothing_raised { set.recognize(request) } + assert_equal("update", request.path_parameters[:action]) + assert_equal("5", request.path_parameters[:id]) + + request.method = :delete + assert_nothing_raised { set.recognize(request) } + assert_equal("destroy", request.path_parameters[:action]) + assert_equal("5", request.path_parameters[:id]) + + begin + request.method = :post + set.recognize(request) + flunk 'Should have raised MethodNotAllowed' + rescue ActionController::MethodNotAllowed => e + assert_equal [:get, :put, :delete], e.allowed_methods + end + + ensure + Object.send(:remove_const, :PeopleController) end - end - - def test_setting_root_in_namespace_using_string - assert_nothing_raised do + + def test_recognize_with_alias_in_conditions + Object.const_set(:PeopleController, Class.new) + + set.draw do |map| + map.people "/people", :controller => 'people', :action => "index", + :conditions => { :method => :get } + map.root :people + end + + request.path = "/people" + 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 + assert_nothing_raised { set.recognize(request) } + assert_equal("people", request.path_parameters[:controller]) + assert_equal("index", request.path_parameters[:action]) + ensure + Object.send(:remove_const, :PeopleController) + end + + def test_typo_recognition + Object.const_set(:ArticlesController, Class.new) + set.draw do |map| - map.namespace 'admin' do |admin| - admin.root :controller => 'home' + map.connect 'articles/:year/:month/:day/:title', + :controller => 'articles', :action => 'permalink', + :year => /\d{4}/, :day => /\d{1,2}/, :month => /\d{1,2}/ + end + + request.path = "/articles/2005/11/05/a-very-interesting-article" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("permalink", request.path_parameters[:action]) + assert_equal("2005", request.path_parameters[:year]) + assert_equal("11", request.path_parameters[:month]) + assert_equal("05", request.path_parameters[:day]) + assert_equal("a-very-interesting-article", request.path_parameters[:title]) + + ensure + Object.send(:remove_const, :ArticlesController) + end + + def test_routing_traversal_does_not_load_extra_classes + assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded" + set.draw do |map| + map.connect '/profile', :controller => 'profile' + end + + request.path = '/profile' + + set.recognize(request) rescue nil + + assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded" + end + + def test_recognize_with_conditions_and_format + Object.const_set(:PeopleController, Class.new) + + set.draw do |map| + map.with_options(:controller => "people") do |people| + people.person "/people/:id", :action => "show", :conditions => { :method => :get } + people.connect "/people/:id", :action => "update", :conditions => { :method => :put } + people.connect "/people/:id.:_format", :action => "show", :conditions => { :method => :get } end end + + request.path = "/people/5" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("show", request.path_parameters[:action]) + assert_equal("5", request.path_parameters[:id]) + + request.method = :put + assert_nothing_raised { set.recognize(request) } + assert_equal("update", request.path_parameters[:action]) + + request.path = "/people/5.png" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("show", request.path_parameters[:action]) + assert_equal("5", request.path_parameters[:id]) + assert_equal("png", request.path_parameters[:_format]) + ensure + Object.send(:remove_const, :PeopleController) end - end - def test_route_requirements_with_unsupported_regexp_options_must_error - assert_raises ArgumentError do + def test_generate_with_default_action set.draw do |map| - map.connect 'page/:name', :controller => 'pages', - :action => 'show', - :requirements => {:name => /(david|jamis)/m} + map.connect "/people", :controller => "people" + map.connect "/people/list", :controller => "people", :action => "list" end + + url = set.generate(:controller => "people", :action => "list") + assert_equal "/people/list", url end - end - def test_route_requirements_with_supported_options_must_not_error - assert_nothing_raised do + def test_root_map + Object.const_set(:PeopleController, Class.new) + + set.draw { |map| map.root :controller => "people" } + + request.path = "" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("people", request.path_parameters[:controller]) + assert_equal("index", request.path_parameters[:action]) + ensure + Object.send(:remove_const, :PeopleController) + end + + def test_namespace + Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) }) + set.draw do |map| - map.connect 'page/:name', :controller => 'pages', - :action => 'show', - :requirements => {:name => /(david|jamis)/i} + + map.namespace 'api' do |api| + api.route 'inventory', :controller => "products", :action => 'inventory' + end + end + + request.path = "/api/inventory" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("api/products", request.path_parameters[:controller]) + assert_equal("inventory", request.path_parameters[:action]) + ensure + Object.send(:remove_const, :Api) end - assert_nothing_raised do + + def test_namespaced_root_map + Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) }) + set.draw do |map| - map.connect 'page/:name', :controller => 'pages', - :action => 'show', - :requirements => {:name => / # Desperately overcommented regexp - ( #Either - david #The Creator - | #Or - jamis #The Deployer - )/x} + + map.namespace 'api' do |api| + api.root :controller => "products" + end + end + + request.path = "/api" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("api/products", request.path_parameters[:controller]) + assert_equal("index", request.path_parameters[:action]) + ensure + Object.send(:remove_const, :Api) end - end - def test_route_requirement_recognize_with_ignore_case - set.draw do |map| - map.connect 'page/:name', :controller => 'pages', - :action => 'show', - :requirements => {:name => /(david|jamis)/i} + def test_generate_finds_best_fit + set.draw do |map| + map.connect "/people", :controller => "people", :action => "index" + map.connect "/ws/people", :controller => "people", :action => "index", :ws => true + end + + url = set.generate(:controller => "people", :action => "index", :ws => true) + assert_equal "/ws/people", url end - assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis')) - assert_raises ActionController::RoutingError do - set.recognize_path('/page/davidjamis') + + def test_generate_changes_controller_module + set.draw { |map| map.connect ':controller/:action/:id' } + current = { :controller => "bling/bloop", :action => "bap", :id => 9 } + url = set.generate({:controller => "foo/bar", :action => "baz", :id => 7}, current) + assert_equal "/foo/bar/baz/7", url end - assert_equal({:controller => 'pages', :action => 'show', :name => 'DAVID'}, set.recognize_path('/page/DAVID')) - end - def test_route_requirement_generate_with_ignore_case - set.draw do |map| - map.connect 'page/:name', :controller => 'pages', - :action => 'show', - :requirements => {:name => /(david|jamis)/i} + def test_id_is_not_impossibly_sticky + set.draw do |map| + map.connect 'foo/:number', :controller => "people", :action => "index" + map.connect ':controller/:action/:id' + end + + url = set.generate({:controller => "people", :action => "index", :number => 3}, + {:controller => "people", :action => "index", :id => "21"}) + assert_equal "/foo/3", url end - url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) - assert_equal "/page/david", url - assert_raises ActionController::RoutingError do - url = set.generate({:controller => 'pages', :action => 'show', :name => 'davidjamis'}) + + def test_id_is_sticky_when_it_ought_to_be + set.draw do |map| + map.connect ':controller/:id/:action' + end + + url = set.generate({:action => "destroy"}, {:controller => "people", :action => "show", :id => "7"}) + assert_equal "/people/7/destroy", url end - url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) - assert_equal "/page/JAMIS", url - end - def test_route_requirement_recognize_with_extended_syntax - set.draw do |map| - map.connect 'page/:name', :controller => 'pages', - :action => 'show', - :requirements => {:name => / # Desperately overcommented regexp - ( #Either - david #The Creator - | #Or - jamis #The Deployer - )/x} + def test_use_static_path_when_possible + set.draw do |map| + map.connect 'about', :controller => "welcome", :action => "about" + map.connect ':controller/:action/:id' + end + + url = set.generate({:controller => "welcome", :action => "about"}, + {:controller => "welcome", :action => "get", :id => "7"}) + assert_equal "/about", url end - assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis')) - assert_equal({:controller => 'pages', :action => 'show', :name => 'david'}, set.recognize_path('/page/david')) - assert_raises ActionController::RoutingError do - set.recognize_path('/page/david #The Creator') + + def test_generate + set.draw { |map| map.connect ':controller/:action/:id' } + + args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" } + assert_equal "/foo/bar/7?x=y", set.generate(args) + assert_equal ["/foo/bar/7", [:x]], set.generate_extras(args) + assert_equal [:x], set.extra_keys(args) end - assert_raises ActionController::RoutingError do - set.recognize_path('/page/David') + + def test_named_routes_are_never_relative_to_modules + set.draw do |map| + map.connect "/connection/manage/:action", :controller => 'connection/manage' + map.connect "/connection/connection", :controller => "connection/connection" + map.family_connection "/connection", :controller => "connection" + end + + url = set.generate({:controller => "connection"}, {:controller => 'connection/manage'}) + assert_equal "/connection/connection", url + + url = set.generate({:use_route => :family_connection, :controller => "connection"}, {:controller => 'connection/manage'}) + assert_equal "/connection", url end - end - def test_route_requirement_generate_with_extended_syntax - set.draw do |map| - map.connect 'page/:name', :controller => 'pages', - :action => 'show', - :requirements => {:name => / # Desperately overcommented regexp - ( #Either - david #The Creator - | #Or - jamis #The Deployer - )/x} + def test_action_left_off_when_id_is_recalled + set.draw do |map| + map.connect ':controller/:action/:id' + end + assert_equal '/post', set.generate( + {:controller => 'post', :action => 'index'}, + {:controller => 'post', :action => 'show', :id => '10'} + ) end - url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) - assert_equal "/page/david", url - assert_raises ActionController::RoutingError do - url = set.generate({:controller => 'pages', :action => 'show', :name => 'davidjamis'}) + + def test_query_params_will_be_shown_when_recalled + set.draw do |map| + map.connect 'show_post/:parameter', :controller => 'post', :action => 'show' + map.connect ':controller/:action/:id' + end + assert_equal '/post/edit?parameter=1', set.generate( + {:action => 'edit', :parameter => 1}, + {:controller => 'post', :action => 'show', :parameter => 1} + ) end - assert_raises ActionController::RoutingError do - url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) + + def test_expiry_determination_should_consider_values_with_to_param + set.draw { |map| map.connect 'projects/:project_id/:controller/:action' } + assert_equal '/projects/1/post/show', set.generate( + {:action => 'show', :project_id => 1}, + {:controller => 'post', :action => 'show', :project_id => '1'}) end - end - def test_route_requirement_generate_with_xi_modifiers - set.draw do |map| - map.connect 'page/:name', :controller => 'pages', - :action => 'show', - :requirements => {:name => / # Desperately overcommented regexp - ( #Either - david #The Creator - | #Or - jamis #The Deployer - )/xi} + def test_generate_all + set.draw do |map| + map.connect 'show_post/:id', :controller => 'post', :action => 'show' + map.connect ':controller/:action/:id' + end + all = set.generate( + {:action => 'show', :id => 10, :generate_all => true}, + {:controller => 'post', :action => 'show'} + ) + assert_equal 2, all.length + assert_equal '/show_post/10', all.first + assert_equal '/post/show/10', all.last end - url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) - assert_equal "/page/JAMIS", url - end - def test_route_requirement_recognize_with_xi_modifiers - set.draw do |map| - map.connect 'page/:name', :controller => 'pages', - :action => 'show', - :requirements => {:name => / # Desperately overcommented regexp - ( #Either - david #The Creator - | #Or - jamis #The Deployer - )/xi} + def test_named_route_in_nested_resource + set.draw do |map| + map.resources :projects do |project| + project.milestones 'milestones', :controller => 'milestones', :action => 'index' + end + end + + request.path = "/projects/1/milestones" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("milestones", request.path_parameters[:controller]) + assert_equal("index", request.path_parameters[:action]) end - assert_equal({:controller => 'pages', :action => 'show', :name => 'JAMIS'}, set.recognize_path('/page/JAMIS')) - end - -end + def test_setting_root_in_namespace_using_symbol + assert_nothing_raised do + set.draw do |map| + map.namespace :admin do |admin| + admin.root :controller => 'home' + end + end + end + end -class RoutingTest < Test::Unit::TestCase - - def test_possible_controllers - true_controller_paths = ActionController::Routing.controller_paths + def test_setting_root_in_namespace_using_string + assert_nothing_raised do + set.draw do |map| + map.namespace 'admin' do |admin| + admin.root :controller => 'home' + end + end + end + end - ActionController::Routing.use_controllers! nil + def test_route_requirements_with_unsupported_regexp_options_must_error + assert_raises ArgumentError do + set.draw do |map| + map.connect 'page/:name', :controller => 'pages', + :action => 'show', + :requirements => {:name => /(david|jamis)/m} + end + end + end - silence_warnings do - Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures') + def test_route_requirements_with_supported_options_must_not_error + assert_nothing_raised do + set.draw do |map| + map.connect 'page/:name', :controller => 'pages', + :action => 'show', + :requirements => {:name => /(david|jamis)/i} + end + end + assert_nothing_raised do + set.draw do |map| + map.connect 'page/:name', :controller => 'pages', + :action => 'show', + :requirements => {:name => / # Desperately overcommented regexp + ( #Either + david #The Creator + | #Or + jamis #The Deployer + )/x} + end + end end - ActionController::Routing.controller_paths = [ - RAILS_ROOT, RAILS_ROOT + '/app/controllers', RAILS_ROOT + '/vendor/plugins/bad_plugin/lib' - ] - - assert_equal ["admin/user", "plugin", "user"], ActionController::Routing.possible_controllers.sort - ensure - if true_controller_paths - ActionController::Routing.controller_paths = true_controller_paths + def test_route_requirement_recognize_with_ignore_case + set.draw do |map| + map.connect 'page/:name', :controller => 'pages', + :action => 'show', + :requirements => {:name => /(david|jamis)/i} + end + assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis')) + assert_raises ActionController::RoutingError do + set.recognize_path('/page/davidjamis') + end + assert_equal({:controller => 'pages', :action => 'show', :name => 'DAVID'}, set.recognize_path('/page/DAVID')) end - ActionController::Routing.use_controllers! nil - Object.send(:remove_const, :RAILS_ROOT) rescue nil - end - - def test_possible_controllers_are_reset_on_each_load - true_possible_controllers = ActionController::Routing.possible_controllers - true_controller_paths = ActionController::Routing.controller_paths - - ActionController::Routing.use_controllers! nil - root = File.dirname(__FILE__) + '/controller_fixtures' - - ActionController::Routing.controller_paths = [] - assert_equal [], ActionController::Routing.possible_controllers - - ActionController::Routing::Routes.load! - ActionController::Routing.controller_paths = [ - root, root + '/app/controllers', root + '/vendor/plugins/bad_plugin/lib' - ] - - assert_equal ["admin/user", "plugin", "user"], ActionController::Routing.possible_controllers.sort - ensure - ActionController::Routing.controller_paths = true_controller_paths - ActionController::Routing.use_controllers! true_possible_controllers - Object.send(:remove_const, :RAILS_ROOT) rescue nil - - ActionController::Routing::Routes.clear! - ActionController::Routing::Routes.load_routes! - end - - def test_with_controllers - c = %w(admin/accounts admin/users account pages) - ActionController::Routing.with_controllers c do - assert_equal c, ActionController::Routing.possible_controllers + + def test_route_requirement_generate_with_ignore_case + set.draw do |map| + map.connect 'page/:name', :controller => 'pages', + :action => 'show', + :requirements => {:name => /(david|jamis)/i} + end + url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) + assert_equal "/page/david", url + assert_raises ActionController::RoutingError do + url = set.generate({:controller => 'pages', :action => 'show', :name => 'davidjamis'}) + end + url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) + assert_equal "/page/JAMIS", url end - end - def test_normalize_unix_paths - load_paths = %w(. config/../app/controllers config/../app//helpers script/../config/../vendor/rails/actionpack/lib vendor/rails/railties/builtin/rails_info app/models lib script/../config/../foo/bar/../../app/models .foo/../.bar foo.bar/../config) - paths = ActionController::Routing.normalize_paths(load_paths) - assert_equal %w(vendor/rails/railties/builtin/rails_info vendor/rails/actionpack/lib app/controllers app/helpers app/models config .bar lib .), paths - end + def test_route_requirement_recognize_with_extended_syntax + set.draw do |map| + map.connect 'page/:name', :controller => 'pages', + :action => 'show', + :requirements => {:name => / # Desperately overcommented regexp + ( #Either + david #The Creator + | #Or + jamis #The Deployer + )/x} + end + assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis')) + assert_equal({:controller => 'pages', :action => 'show', :name => 'david'}, set.recognize_path('/page/david')) + assert_raises ActionController::RoutingError do + set.recognize_path('/page/david #The Creator') + end + assert_raises ActionController::RoutingError do + set.recognize_path('/page/David') + end + end - def test_normalize_windows_paths - load_paths = %w(. config\\..\\app\\controllers config\\..\\app\\\\helpers script\\..\\config\\..\\vendor\\rails\\actionpack\\lib vendor\\rails\\railties\\builtin\\rails_info app\\models lib script\\..\\config\\..\\foo\\bar\\..\\..\\app\\models .foo\\..\\.bar foo.bar\\..\\config) - paths = ActionController::Routing.normalize_paths(load_paths) - assert_equal %w(vendor\\rails\\railties\\builtin\\rails_info vendor\\rails\\actionpack\\lib app\\controllers app\\helpers app\\models config .bar lib .), paths - end - - def test_routing_helper_module - assert_kind_of Module, ActionController::Routing::Helpers - - h = ActionController::Routing::Helpers - c = Class.new - assert ! c.ancestors.include?(h) - ActionController::Routing::Routes.install_helpers c - assert c.ancestors.include?(h) - end + def test_route_requirement_generate_with_extended_syntax + set.draw do |map| + map.connect 'page/:name', :controller => 'pages', + :action => 'show', + :requirements => {:name => / # Desperately overcommented regexp + ( #Either + david #The Creator + | #Or + jamis #The Deployer + )/x} + end + url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) + assert_equal "/page/david", url + assert_raises ActionController::RoutingError do + url = set.generate({:controller => 'pages', :action => 'show', :name => 'davidjamis'}) + end + assert_raises ActionController::RoutingError do + url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) + end + end -end + def test_route_requirement_generate_with_xi_modifiers + set.draw do |map| + map.connect 'page/:name', :controller => 'pages', + :action => 'show', + :requirements => {:name => / # Desperately overcommented regexp + ( #Either + david #The Creator + | #Or + jamis #The Deployer + )/xi} + end + url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) + assert_equal "/page/JAMIS", url + end -uses_mocha 'route loading' do - class RouteLoadingTest < Test::Unit::TestCase + def test_route_requirement_recognize_with_xi_modifiers + set.draw do |map| + map.connect 'page/:name', :controller => 'pages', + :action => 'show', + :requirements => {:name => / # Desperately overcommented regexp + ( #Either + david #The Creator + | #Or + jamis #The Deployer + )/xi} + end + assert_equal({:controller => 'pages', :action => 'show', :name => 'JAMIS'}, set.recognize_path('/page/JAMIS')) + end + end + class RouteLoadingTest < Test::Unit::TestCase def setup routes.instance_variable_set '@routes_last_modified', nil silence_warnings { Object.const_set :RAILS_ROOT, '.' } @@ -2392,12 +2364,12 @@ uses_mocha 'route loading' do end def test_adding_inflections_forces_reload - Inflector::Inflections.instance.expects(:uncountable).with('equipment') + ActiveSupport::Inflector::Inflections.instance.expects(:uncountable).with('equipment') routes.expects(:reload!) - Inflector.inflections { |inflect| inflect.uncountable('equipment') } + ActiveSupport::Inflector.inflections { |inflect| inflect.uncountable('equipment') } end - + def test_load_with_configuration routes.configuration_file = "foobarbaz" File.expects(:stat).returns(@stat) @@ -2407,9 +2379,8 @@ uses_mocha 'route loading' do end private - def routes - ActionController::Routing::Routes - end - + def routes + ActionController::Routing::Routes + end end end diff --git a/actionpack/test/controller/verification_test.rb b/actionpack/test/controller/verification_test.rb index d6fde35f83..b289443129 100644 --- a/actionpack/test/controller/verification_test.rb +++ b/actionpack/test/controller/verification_test.rb @@ -21,10 +21,10 @@ class VerificationTest < Test::Unit::TestCase verify :only => :guarded_by_method, :method => :post, :redirect_to => { :action => "unguarded" } - + verify :only => :guarded_by_xhr, :xhr => true, :redirect_to => { :action => "unguarded" } - + verify :only => :guarded_by_not_xhr, :xhr => false, :redirect_to => { :action => "unguarded" } @@ -39,10 +39,13 @@ class VerificationTest < Test::Unit::TestCase verify :only => :no_default_action, :params => "santa" + verify :only => :guarded_with_back, :method => :post, + :redirect_to => :back + def guarded_one render :text => "#{params[:one]}" end - + def guarded_one_for_named_route_test render :text => "#{params[:one]}" end @@ -70,11 +73,11 @@ class VerificationTest < Test::Unit::TestCase def guarded_by_method render :text => "#{request.method}" end - + def guarded_by_xhr render :text => "#{request.xhr?}" end - + def guarded_by_not_xhr render :text => "#{request.xhr?}" end @@ -86,15 +89,19 @@ class VerificationTest < Test::Unit::TestCase def two_redirects render :nothing => true end - + def must_be_post render :text => "Was a post!" end - + + def guarded_with_back + render :text => "#{params[:one]}" + end + def no_default_action # Will never run end - + protected def rescue_action(e) raise end @@ -109,7 +116,17 @@ class VerificationTest < Test::Unit::TestCase @response = ActionController::TestResponse.new ActionController::Routing::Routes.add_named_route :foo, '/foo', :controller => 'test', :action => 'foo' end - + + def test_using_symbol_back_with_no_referrer + assert_raise(ActionController::RedirectBackError) { get :guarded_with_back } + end + + def test_using_symbol_back_redirects_to_referrer + @request.env["HTTP_REFERER"] = "/foo" + get :guarded_with_back + assert_redirected_to '/foo' + end + def test_no_deprecation_warning_for_named_route assert_not_deprecated do get :guarded_one_for_named_route_test, :two => "not one" @@ -209,44 +226,44 @@ class VerificationTest < Test::Unit::TestCase get :guarded_by_method assert_redirected_to :action => "unguarded" end - + def test_guarded_by_xhr_with_prereqs xhr :post, :guarded_by_xhr assert_equal "true", @response.body end - + def test_guarded_by_xhr_without_prereqs get :guarded_by_xhr assert_redirected_to :action => "unguarded" end - + def test_guarded_by_not_xhr_with_prereqs get :guarded_by_not_xhr assert_equal "false", @response.body end - + def test_guarded_by_not_xhr_without_prereqs xhr :post, :guarded_by_not_xhr assert_redirected_to :action => "unguarded" end - + def test_guarded_post_and_calls_render_succeeds post :must_be_post assert_equal "Was a post!", @response.body end - + def test_default_failure_should_be_a_bad_request post :no_default_action assert_response :bad_request end - + def test_guarded_post_and_calls_render_fails_and_sets_allow_header get :must_be_post assert_response 405 assert_equal "Must be post", @response.body assert_equal "POST", @response.headers["Allow"] end - + def test_second_redirect assert_nothing_raised { get :two_redirects } end diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb index 0516da32d8..1b4c1fae2f 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionpack/test/controller/view_paths_test.rb @@ -1,30 +1,30 @@ require 'abstract_unit' class ViewLoadPathsTest < Test::Unit::TestCase - LOAD_PATH_ROOT = File.join(File.dirname(__FILE__), '..', 'fixtures') - ActionController::Base.view_paths = [ LOAD_PATH_ROOT ] + ActionController::Base.view_paths = [LOAD_PATH_ROOT] class TestController < ActionController::Base def self.controller_path() "test" end def rescue_action(e) raise end - + before_filter :add_view_path, :only => :hello_world_at_request_time - + def hello_world() end def hello_world_at_request_time() render(:action => 'hello_world') end + private - def add_view_path - prepend_view_path "#{LOAD_PATH_ROOT}/override" - end + def add_view_path + prepend_view_path "#{LOAD_PATH_ROOT}/override" + end end - + class Test::SubController < ActionController::Base layout 'test/sub' def hello_world; render(:template => 'test/hello_world'); end end - + def setup TestController.view_paths = nil @@ -41,68 +41,80 @@ class ViewLoadPathsTest < Test::Unit::TestCase @last_message = nil ActiveSupport::Deprecation.behavior = Proc.new { |message, callback| @last_message = message } end - + def teardown ActiveSupport::Deprecation.behavior = @old_behavior end - + def test_template_load_path_was_set_correctly - assert_equal [ LOAD_PATH_ROOT ], @controller.view_paths + assert_equal [ LOAD_PATH_ROOT ], @controller.view_paths.map(&:to_s) end - + def test_controller_appends_view_path_correctly @controller.append_view_path 'foo' - assert_equal [LOAD_PATH_ROOT, 'foo'], @controller.view_paths - + assert_equal [LOAD_PATH_ROOT, 'foo'], @controller.view_paths.map(&:to_s) + @controller.append_view_path(%w(bar baz)) - assert_equal [LOAD_PATH_ROOT, 'foo', 'bar', 'baz'], @controller.view_paths + assert_equal [LOAD_PATH_ROOT, 'foo', 'bar', 'baz'], @controller.view_paths.map(&:to_s) + + @controller.append_view_path(LOAD_PATH_ROOT) + assert_equal ['foo', 'bar', 'baz', LOAD_PATH_ROOT], @controller.view_paths.map(&:to_s) + + @controller.append_view_path([LOAD_PATH_ROOT]) + assert_equal ['foo', 'bar', 'baz', LOAD_PATH_ROOT], @controller.view_paths.map(&:to_s) end - + def test_controller_prepends_view_path_correctly @controller.prepend_view_path 'baz' - assert_equal ['baz', LOAD_PATH_ROOT], @controller.view_paths - + assert_equal ['baz', LOAD_PATH_ROOT], @controller.view_paths.map(&:to_s) + @controller.prepend_view_path(%w(foo bar)) - assert_equal ['foo', 'bar', 'baz', LOAD_PATH_ROOT], @controller.view_paths + assert_equal ['foo', 'bar', 'baz', LOAD_PATH_ROOT], @controller.view_paths.map(&:to_s) + + @controller.prepend_view_path(LOAD_PATH_ROOT) + assert_equal [LOAD_PATH_ROOT, 'foo', 'bar', 'baz'], @controller.view_paths.map(&:to_s) + + @controller.prepend_view_path([LOAD_PATH_ROOT]) + assert_equal [LOAD_PATH_ROOT, 'foo', 'bar', 'baz'], @controller.view_paths.map(&:to_s) end - + def test_template_appends_view_path_correctly @controller.instance_variable_set :@template, ActionView::Base.new(TestController.view_paths, {}, @controller) class_view_paths = TestController.view_paths @controller.append_view_path 'foo' - assert_equal [LOAD_PATH_ROOT, 'foo'], @controller.view_paths - + assert_equal [LOAD_PATH_ROOT, 'foo'], @controller.view_paths.map(&:to_s) + @controller.append_view_path(%w(bar baz)) - assert_equal [LOAD_PATH_ROOT, 'foo', 'bar', 'baz'], @controller.view_paths + assert_equal [LOAD_PATH_ROOT, 'foo', 'bar', 'baz'], @controller.view_paths.map(&:to_s) assert_equal class_view_paths, TestController.view_paths end - + def test_template_prepends_view_path_correctly @controller.instance_variable_set :@template, ActionView::Base.new(TestController.view_paths, {}, @controller) class_view_paths = TestController.view_paths - + @controller.prepend_view_path 'baz' - assert_equal ['baz', LOAD_PATH_ROOT], @controller.view_paths - + assert_equal ['baz', LOAD_PATH_ROOT], @controller.view_paths.map(&:to_s) + @controller.prepend_view_path(%w(foo bar)) - assert_equal ['foo', 'bar', 'baz', LOAD_PATH_ROOT], @controller.view_paths + assert_equal ['foo', 'bar', 'baz', LOAD_PATH_ROOT], @controller.view_paths.map(&:to_s) assert_equal class_view_paths, TestController.view_paths end - + def test_view_paths get :hello_world assert_response :success assert_equal "Hello world!", @response.body end - + def test_view_paths_override TestController.prepend_view_path "#{LOAD_PATH_ROOT}/override" get :hello_world assert_response :success assert_equal "Hello overridden world!", @response.body end - + def test_view_paths_override_for_layouts_in_controllers_with_a_module @controller = Test::SubController.new Test::SubController.view_paths = [ "#{LOAD_PATH_ROOT}/override", LOAD_PATH_ROOT, "#{LOAD_PATH_ROOT}/override2" ] @@ -110,31 +122,44 @@ class ViewLoadPathsTest < Test::Unit::TestCase assert_response :success assert_equal "layout: Hello overridden world!", @response.body end - + def test_view_paths_override_at_request_time get :hello_world_at_request_time assert_response :success assert_equal "Hello overridden world!", @response.body end - + def test_inheritance original_load_paths = ActionController::Base.view_paths - + self.class.class_eval %{ class A < ActionController::Base; end class B < A; end class C < ActionController::Base; end } - - A.view_paths = [ 'a/path' ] - - assert_equal [ 'a/path' ], A.view_paths - assert_equal A.view_paths, B.view_paths + + A.view_paths = ['a/path'] + + assert_equal ['a/path'], A.view_paths.map(&:to_s) + assert_equal A.view_paths, B.view_paths assert_equal original_load_paths, C.view_paths - + C.view_paths = [] assert_nothing_raised { C.view_paths << 'c/path' } - assert_equal ['c/path'], C.view_paths + assert_equal ['c/path'], C.view_paths.map(&:to_s) + end + + def test_find_template_file_for_path + assert_equal "test/hello_world.erb", @controller.view_paths.find_template_file_for_path("test/hello_world.erb").to_s + assert_equal "test/hello.builder", @controller.view_paths.find_template_file_for_path("test/hello.builder").to_s + assert_equal nil, @controller.view_paths.find_template_file_for_path("test/missing.erb") + end + + def test_view_paths_find_template_file_for_path + assert_equal "test/formatted_html_erb.html.erb", @controller.view_paths.find_template_file_for_path("test/formatted_html_erb.html").to_s + assert_equal "test/formatted_xml_erb.xml.erb", @controller.view_paths.find_template_file_for_path("test/formatted_xml_erb.xml").to_s + assert_equal "test/hello_world.erb", @controller.view_paths.find_template_file_for_path("test/hello_world.html").to_s + assert_equal "test/hello_world.erb", @controller.view_paths.find_template_file_for_path("test/hello_world.xml").to_s + assert_equal nil, @controller.view_paths.find_template_file_for_path("test/missing.html") end - end diff --git a/actionpack/test/fixtures/test/block_content_for.erb b/actionpack/test/fixtures/test/block_content_for.erb deleted file mode 100644 index 9510337365..0000000000 --- a/actionpack/test/fixtures/test/block_content_for.erb +++ /dev/null @@ -1,2 +0,0 @@ -<% block_content_for :title do 'Putting stuff in the title!' end %> -Great stuff!
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/erb_content_for.erb b/actionpack/test/fixtures/test/erb_content_for.erb deleted file mode 100644 index c3bdd13643..0000000000 --- a/actionpack/test/fixtures/test/erb_content_for.erb +++ /dev/null @@ -1,2 +0,0 @@ -<% erb_content_for :title do %>Putting stuff in the title!<% end %> -Great stuff!
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/non_erb_block_content_for.builder b/actionpack/test/fixtures/test/non_erb_block_content_for.builder index 6ff6db0f95..a94643561c 100644 --- a/actionpack/test/fixtures/test/non_erb_block_content_for.builder +++ b/actionpack/test/fixtures/test/non_erb_block_content_for.builder @@ -1,4 +1,4 @@ content_for :title do 'Putting stuff in the title!' end -xml << "\nGreat stuff!"
\ No newline at end of file +xml << "\nGreat stuff!" diff --git a/actionpack/test/fixtures/test/render_file_from_template.html.erb b/actionpack/test/fixtures/test/render_file_from_template.html.erb new file mode 100644 index 0000000000..fde9f4bb64 --- /dev/null +++ b/actionpack/test/fixtures/test/render_file_from_template.html.erb @@ -0,0 +1 @@ +<%= render :file => @path %>
\ No newline at end of file diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 0a7b19ba96..11b3bdb3fa 100755 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -1002,17 +1002,15 @@ class DateHelperTest < ActionView::TestCase @post = Post.new @post.written_on = Date.new(2004, 6, 15) - _erbout = '' - fields_for :post, @post do |f| - _erbout.concat f.date_select(:written_on) + concat f.date_select(:written_on) end expected = "<select id='post_written_on_1i' name='post[written_on(1i)]'>\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 selected='selected' value='2004'>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</select>\n" expected << "<select id='post_written_on_2i' name='post[written_on(2i)]'>\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 selected='selected' value='6'>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</select>\n" expected << "<select id='post_written_on_3i' name='post[written_on(3i)]'>\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 selected='selected' 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</select>\n" - assert_dom_equal(expected, _erbout) + assert_dom_equal(expected, output_buffer) end def test_date_select_with_index @@ -1287,10 +1285,8 @@ class DateHelperTest < ActionView::TestCase @post = Post.new @post.updated_at = Time.local(2004, 6, 15, 16, 35) - _erbout = '' - fields_for :post, @post do |f| - _erbout.concat f.datetime_select(:updated_at) + concat f.datetime_select(:updated_at) end expected = "<select id='post_updated_at_1i' name='post[updated_at(1i)]'>\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 selected='selected' value='2004'>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</select>\n" @@ -1299,7 +1295,7 @@ class DateHelperTest < ActionView::TestCase expected << " — <select id='post_updated_at_4i' name='post[updated_at(4i)]'>\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 selected='selected' 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</select>\n" expected << " : <select id='post_updated_at_5i' name='post[updated_at(5i)]'>\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 selected='selected' 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</select>\n" - assert_dom_equal(expected, _erbout) + assert_dom_equal(expected, output_buffer) end def test_date_select_with_zero_value_and_no_start_year diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index af99e6243d..39649c3622 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -337,14 +337,12 @@ class FormHelperTest < ActionView::TestCase end def test_form_for - _erbout = '' - form_for(:post, @post, :html => { :id => 'create-post' }) do |f| - _erbout.concat f.label(:title) - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) - _erbout.concat f.submit('Create post') + concat f.label(:title) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) + concat f.submit('Create post') end expected = @@ -357,16 +355,14 @@ class FormHelperTest < ActionView::TestCase "<input name='commit' id='post_submit' type='submit' value='Create post' />" + "</form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_form_for_with_method - _erbout = '' - form_for(:post, @post, :html => { :id => 'create-post', :method => :put }) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -378,16 +374,14 @@ class FormHelperTest < ActionView::TestCase "<input name='post[secret]' type='hidden' value='0' />" + "</form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_form_for_without_object - _erbout = '' - form_for(:post, :html => { :id => 'create-post' }) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -398,17 +392,15 @@ class FormHelperTest < ActionView::TestCase "<input name='post[secret]' type='hidden' value='0' />" + "</form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_form_for_with_index - _erbout = '' - form_for("post[]", @post) do |f| - _erbout.concat f.label(:title) - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.label(:title) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -420,16 +412,14 @@ class FormHelperTest < ActionView::TestCase "<input name='post[123][secret]' type='hidden' value='0' />" + "</form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_form_for_with_nil_index_option_override - _erbout = '' - form_for("post[]", @post, :index => nil) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -440,14 +430,13 @@ class FormHelperTest < ActionView::TestCase "<input name='post[][secret]' type='hidden' value='0' />" + "</form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_nested_fields_for - _erbout = '' form_for(:post, @post) do |f| f.fields_for(:comment, @post) do |c| - _erbout.concat c.text_field(:title) + concat c.text_field(:title) end end @@ -455,16 +444,14 @@ class FormHelperTest < ActionView::TestCase "<input name='post[comment][title]' size='30' type='text' id='post_comment_title' value='Hello World' />" + "</form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_fields_for - _erbout = '' - fields_for(:post, @post) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -473,16 +460,14 @@ class FormHelperTest < ActionView::TestCase "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<input name='post[secret]' type='hidden' value='0' />" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_fields_for_with_index - _erbout = '' - fields_for("post[]", @post) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -491,16 +476,14 @@ class FormHelperTest < ActionView::TestCase "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />" + "<input name='post[123][secret]' type='hidden' value='0' />" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_fields_for_with_nil_index_option_override - _erbout = '' - fields_for("post[]", @post, :index => nil) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -509,16 +492,14 @@ class FormHelperTest < ActionView::TestCase "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />" + "<input name='post[][secret]' type='hidden' value='0' />" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_fields_for_with_index_option_override - _erbout = '' - fields_for("post[]", @post, :index => "abc") do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -527,15 +508,14 @@ class FormHelperTest < ActionView::TestCase "<input name='post[abc][secret]' checked='checked' type='checkbox' id='post_abc_secret' value='1' />" + "<input name='post[abc][secret]' type='hidden' value='0' />" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_fields_for_without_object - _erbout = '' fields_for(:post) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -544,15 +524,14 @@ class FormHelperTest < ActionView::TestCase "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<input name='post[secret]' type='hidden' value='0' />" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_fields_for_with_only_object - _erbout = '' fields_for(@post) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -561,31 +540,29 @@ class FormHelperTest < ActionView::TestCase "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<input name='post[secret]' type='hidden' value='0' />" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_fields_for_object_with_bracketed_name - _erbout = '' fields_for("author[post]", @post) do |f| - _erbout.concat f.label(:title) - _erbout.concat f.text_field(:title) + concat f.label(:title) + concat f.text_field(:title) end assert_dom_equal "<label for=\"author_post_title\">Title</label>" + "<input name='author[post][title]' size='30' type='text' id='author_post_title' value='Hello World' />", - _erbout + output_buffer end def test_fields_for_object_with_bracketed_name_and_index - _erbout = '' fields_for("author[post]", @post, :index => 1) do |f| - _erbout.concat f.label(:title) - _erbout.concat f.text_field(:title) + concat f.label(:title) + concat f.text_field(:title) end assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" + "<input name='author[post][1][title]' size='30' type='text' id='author_post_1_title' value='Hello World' />", - _erbout + output_buffer end def test_form_builder_does_not_have_form_for_method @@ -593,14 +570,12 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_and_fields_for - _erbout = '' - form_for(:post, @post, :html => { :id => 'create-post' }) do |post_form| - _erbout.concat post_form.text_field(:title) - _erbout.concat post_form.text_area(:body) + concat post_form.text_field(:title) + concat post_form.text_area(:body) fields_for(:parent_post, @post) do |parent_fields| - _erbout.concat parent_fields.check_box(:secret) + concat parent_fields.check_box(:secret) end end @@ -612,18 +587,16 @@ class FormHelperTest < ActionView::TestCase "<input name='parent_post[secret]' type='hidden' value='0' />" + "</form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_form_for_and_fields_for_with_object - _erbout = '' - form_for(:post, @post, :html => { :id => 'create-post' }) do |post_form| - _erbout.concat post_form.text_field(:title) - _erbout.concat post_form.text_area(:body) + concat post_form.text_field(:title) + concat post_form.text_area(:body) post_form.fields_for(@comment) do |comment_fields| - _erbout.concat comment_fields.text_field(:name) + concat comment_fields.text_field(:name) end end @@ -634,7 +607,7 @@ class FormHelperTest < ActionView::TestCase "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />" + "</form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end class LabelledFormBuilder < ActionView::Helpers::FormBuilder @@ -649,12 +622,10 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_labelled_builder - _erbout = '' - form_for(:post, @post, :builder => LabelledFormBuilder) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -665,18 +636,17 @@ class FormHelperTest < ActionView::TestCase "<input name='post[secret]' type='hidden' value='0' /><br/>" + "</form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_default_form_builder old_default_form_builder, ActionView::Base.default_form_builder = ActionView::Base.default_form_builder, LabelledFormBuilder - _erbout = '' form_for(:post, @post) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -687,17 +657,15 @@ class FormHelperTest < ActionView::TestCase "<input name='post[secret]' type='hidden' value='0' /><br/>" + "</form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer ensure ActionView::Base.default_form_builder = old_default_form_builder end def test_default_form_builder_with_active_record_helpers - - _erbout = '' form_for(:post, @post) do |f| - _erbout.concat f.error_message_on('author_name') - _erbout.concat f.error_messages + concat f.error_message_on('author_name') + concat f.error_messages end expected = %(<form action='http://www.example.com' method='post'>) + @@ -705,7 +673,7 @@ class FormHelperTest < ActionView::TestCase %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>) + %(</form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end @@ -713,10 +681,9 @@ class FormHelperTest < ActionView::TestCase post = @post @post = nil - _erbout = '' form_for(:post, post) do |f| - _erbout.concat f.error_message_on('author_name') - _erbout.concat f.error_messages + concat f.error_message_on('author_name') + concat f.error_messages end expected = %(<form action='http://www.example.com' method='post'>) + @@ -724,19 +691,18 @@ class FormHelperTest < ActionView::TestCase %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>) + %(</form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end # Perhaps this test should be moved to prototype helper tests. def test_remote_form_for_with_labelled_builder self.extend ActionView::Helpers::PrototypeHelper - _erbout = '' remote_form_for(:post, @post, :builder => LabelledFormBuilder) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -747,16 +713,14 @@ class FormHelperTest < ActionView::TestCase "<input name='post[secret]' type='hidden' value='0' /><br/>" + "</form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_fields_for_with_labelled_builder - _erbout = '' - fields_for(:post, @post, :builder => LabelledFormBuilder) do |f| - _erbout.concat f.text_field(:title) - _erbout.concat f.text_area(:body) - _erbout.concat f.check_box(:secret) + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) end expected = @@ -765,29 +729,23 @@ class FormHelperTest < ActionView::TestCase "<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<input name='post[secret]' type='hidden' value='0' /><br/>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_form_for_with_html_options_adds_options_to_form_tag - _erbout = '' - form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\"></form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_form_for_with_string_url_option - _erbout = '' - form_for(:post, @post, :url => 'http://www.otherdomain.com') do |f| end - assert_equal '<form action="http://www.otherdomain.com" method="post"></form>', _erbout + assert_equal '<form action="http://www.otherdomain.com" method="post"></form>', output_buffer end def test_form_for_with_hash_url_option - _erbout = '' - form_for(:post, @post, :url => {:controller => 'controller', :action => 'action'}) do |f| end assert_equal 'controller', @controller.url_for_options[:controller] @@ -795,26 +753,20 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_record_url_option - _erbout = '' - form_for(:post, @post, :url => @post) do |f| end expected = "<form action=\"/posts/123\" method=\"post\"></form>" - assert_equal expected, _erbout + assert_equal expected, output_buffer end def test_form_for_with_existing_object - _erbout = '' - form_for(@post) do |f| end expected = "<form action=\"/posts/123\" class=\"edit_post\" id=\"edit_post_123\" method=\"post\"><div style=\"margin:0;padding:0\"><input name=\"_method\" type=\"hidden\" value=\"put\" /></div></form>" - assert_equal expected, _erbout + assert_equal expected, output_buffer end def test_form_for_with_new_object - _erbout = '' - post = Post.new post.new_record = true def post.id() nil end @@ -822,64 +774,61 @@ class FormHelperTest < ActionView::TestCase form_for(post) do |f| end expected = "<form action=\"/posts\" class=\"new_post\" id=\"new_post\" method=\"post\"></form>" - assert_equal expected, _erbout + assert_equal expected, output_buffer end def test_form_for_with_existing_object_in_list @post.new_record = false @comment.save - _erbout = '' + form_for([@post, @comment]) {} expected = %(<form action="#{comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0"><input name="_method" type="hidden" value="put" /></div></form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_form_for_with_new_object_in_list @post.new_record = false - _erbout = '' + form_for([@post, @comment]) {} expected = %(<form action="#{comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_form_for_with_existing_object_and_namespace_in_list @post.new_record = false @comment.save - _erbout = '' + form_for([:admin, @post, @comment]) {} expected = %(<form action="#{admin_comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0"><input name="_method" type="hidden" value="put" /></div></form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_form_for_with_new_object_and_namespace_in_list @post.new_record = false - _erbout = '' + form_for([:admin, @post, @comment]) {} expected = %(<form action="#{admin_comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_form_for_with_existing_object_and_custom_url - _erbout = '' - form_for(@post, :url => "/super_posts") do |f| end expected = "<form action=\"/super_posts\" class=\"edit_post\" id=\"edit_post_123\" method=\"post\"><div style=\"margin:0;padding:0\"><input name=\"_method\" type=\"hidden\" value=\"put\" /></div></form>" - assert_equal expected, _erbout + assert_equal expected, output_buffer end def test_remote_form_for_with_html_options_adds_options_to_form_tag self.extend ActionView::Helpers::PrototypeHelper - _erbout = '' remote_form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\" onsubmit=\"new Ajax.Request('http://www.example.com', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\"></form>" - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 48a26deea9..3f89a5e426 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -20,8 +20,6 @@ class MockTimeZone end end -ActionView::Helpers::FormOptionsHelper::TimeZone = MockTimeZone - class FormOptionsHelperTest < ActionView::TestCase tests ActionView::Helpers::FormOptionsHelper @@ -31,6 +29,8 @@ class FormOptionsHelperTest < ActionView::TestCase Country = Struct.new('Country', :country_id, :country_name) Firm = Struct.new('Firm', :time_zone) Album = Struct.new('Album', :id, :title, :genre) + + ActiveSupport::TimeZone = MockTimeZone end def test_collection_options @@ -183,7 +183,7 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_time_zone_options_with_priority_zones - zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ] + zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ] opts = time_zone_options_for_select( nil, zones ) assert_dom_equal "<option value=\"B\">B</option>\n" + "<option value=\"E\">E</option>" + @@ -195,7 +195,7 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_time_zone_options_with_selected_priority_zones - zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ] + zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ] opts = time_zone_options_for_select( "E", zones ) assert_dom_equal "<option value=\"B\">B</option>\n" + "<option value=\"E\" selected=\"selected\">E</option>" + @@ -207,7 +207,7 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_time_zone_options_with_unselected_priority_zones - zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ] + zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ] opts = time_zone_options_for_select( "C", zones ) assert_dom_equal "<option value=\"B\">B</option>\n" + "<option value=\"E\">E</option>" + @@ -230,16 +230,14 @@ class FormOptionsHelperTest < ActionView::TestCase def test_select_under_fields_for @post = Post.new @post.category = "<mus>" - - _erbout = '' - + fields_for :post, @post do |f| - _erbout.concat f.select(:category, %w( abe <mus> hest)) + concat f.select(:category, %w( abe <mus> hest)) end assert_dom_equal( "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"<mus>\" selected=\"selected\"><mus></option>\n<option value=\"hest\">hest</option></select>", - _erbout + output_buffer ) end @@ -352,16 +350,14 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.author_name = "Babe" - - _erbout = '' - + fields_for :post, @post do |f| - _erbout.concat f.collection_select(:author_name, @posts, :author_name, :author_name) + concat f.collection_select(:author_name, @posts, :author_name, :author_name) end assert_dom_equal( "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>", - _erbout + output_buffer ) end @@ -1194,11 +1190,9 @@ COUNTRIES def test_time_zone_select_under_fields_for @firm = Firm.new("D") - - _erbout = '' - + fields_for :firm, @firm do |f| - _erbout.concat f.time_zone_select(:time_zone) + concat f.time_zone_select(:time_zone) end assert_dom_equal( @@ -1209,7 +1203,7 @@ COUNTRIES "<option value=\"D\" selected=\"selected\">D</option>\n" + "<option value=\"E\">E</option>" + "</select>", - _erbout + output_buffer ) end @@ -1293,7 +1287,7 @@ COUNTRIES def test_time_zone_select_with_priority_zones @firm = Firm.new("D") - zones = [ TimeZone.new("A"), TimeZone.new("D") ] + zones = [ ActiveSupport::TimeZone.new("A"), ActiveSupport::TimeZone.new("D") ] html = time_zone_select("firm", "time_zone", zones ) assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" + "<option value=\"A\">A</option>\n" + diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 73a8bd4d87..eabbe9c8a0 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -35,27 +35,27 @@ class FormTagHelperTest < ActionView::TestCase expected = %(<form action="http://www.example.com" method="post"><div style='margin:0;padding:0'><input type="hidden" name="_method" value="put" /></div>) assert_dom_equal expected, actual end - + def test_form_tag_with_method_delete actual = form_tag({}, { :method => :delete }) expected = %(<form action="http://www.example.com" method="post"><div style='margin:0;padding:0'><input type="hidden" name="_method" value="delete" /></div>) assert_dom_equal expected, actual end - def test_form_tag_with_block - _erbout = '' - form_tag("http://example.com") { _erbout.concat "Hello world!" } + def test_form_tag_with_block_in_erb + __in_erb_template = '' + form_tag("http://example.com") { concat "Hello world!" } expected = %(<form action="http://example.com" method="post">Hello world!</form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end - def test_form_tag_with_block_and_method - _erbout = '' - form_tag("http://example.com", :method => :put) { _erbout.concat "Hello world!" } + def test_form_tag_with_block_and_method_in_erb + __in_erb_template = '' + form_tag("http://example.com", :method => :put) { concat "Hello world!" } expected = %(<form action="http://example.com" method="post"><div style='margin:0;padding:0'><input type="hidden" name="_method" value="put" /></div>Hello world!</form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_hidden_field_tag @@ -90,11 +90,11 @@ class FormTagHelperTest < ActionView::TestCase actual = radio_button_tag("gender", "m") + radio_button_tag("gender", "f") expected = %(<input id="gender_m" name="gender" type="radio" value="m" /><input id="gender_f" name="gender" type="radio" value="f" />) assert_dom_equal expected, actual - + actual = radio_button_tag("opinion", "-1") + radio_button_tag("opinion", "1") expected = %(<input id="opinion_-1" name="opinion" type="radio" value="-1" /><input id="opinion_1" name="opinion" type="radio" value="1" />) assert_dom_equal expected, actual - + actual = radio_button_tag("person[gender]", "m") expected = %(<input id="person_gender_m" name="person[gender]" type="radio" value="m" />) assert_dom_equal expected, actual @@ -105,13 +105,13 @@ class FormTagHelperTest < ActionView::TestCase expected = %(<select id="people" name="people"><option>david</option></select>) assert_dom_equal expected, actual end - + def test_select_tag_with_multiple actual = select_tag "colors", "<option>Red</option><option>Blue</option><option>Green</option>", :multiple => :true expected = %(<select id="colors" multiple="multiple" name="colors"><option>Red</option><option>Blue</option><option>Green</option></select>) assert_dom_equal expected, actual end - + def test_select_tag_disabled actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>", :disabled => :true expected = %(<select id="places" disabled="disabled" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>) @@ -147,37 +147,37 @@ class FormTagHelperTest < ActionView::TestCase expected = %(<input class="admin" id="title" name="title" type="text" value="Hello!" />) assert_dom_equal expected, actual end - + def test_text_field_tag_size_symbol actual = text_field_tag "title", "Hello!", :size => 75 expected = %(<input id="title" name="title" size="75" type="text" value="Hello!" />) assert_dom_equal expected, actual end - + def test_text_field_tag_size_string actual = text_field_tag "title", "Hello!", "size" => "75" expected = %(<input id="title" name="title" size="75" type="text" value="Hello!" />) assert_dom_equal expected, actual end - + def test_text_field_tag_maxlength_symbol actual = text_field_tag "title", "Hello!", :maxlength => 75 expected = %(<input id="title" name="title" maxlength="75" type="text" value="Hello!" />) assert_dom_equal expected, actual end - + def test_text_field_tag_maxlength_string actual = text_field_tag "title", "Hello!", "maxlength" => "75" expected = %(<input id="title" name="title" maxlength="75" type="text" value="Hello!" />) assert_dom_equal expected, actual end - + def test_text_field_disabled actual = text_field_tag "title", "Hello!", :disabled => :true expected = %(<input id="title" name="title" disabled="disabled" type="text" value="Hello!" />) assert_dom_equal expected, actual end - + def test_text_field_tag_with_multiple_options actual = text_field_tag "title", "Hello!", :size => 70, :maxlength => 80 expected = %(<input id="title" name="title" size="70" maxlength="80" type="text" value="Hello!" />) @@ -228,29 +228,29 @@ class FormTagHelperTest < ActionView::TestCase submit_tag("Save", :confirm => "Are you sure?") ) end - + def test_pass assert_equal 1, 1 end - def test_field_set_tag - _erbout = '' - field_set_tag("Your details") { _erbout.concat "Hello world!" } + def test_field_set_tag_in_erb + __in_erb_template = '' + field_set_tag("Your details") { concat "Hello world!" } expected = %(<fieldset><legend>Your details</legend>Hello world!</fieldset>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer - _erbout = '' - field_set_tag { _erbout.concat "Hello world!" } + self.output_buffer = '' + field_set_tag { concat "Hello world!" } expected = %(<fieldset>Hello world!</fieldset>) - assert_dom_equal expected, _erbout - - _erbout = '' - field_set_tag('') { _erbout.concat "Hello world!" } + assert_dom_equal expected, output_buffer + + self.output_buffer = '' + field_set_tag('') { concat "Hello world!" } expected = %(<fieldset>Hello world!</fieldset>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def protect_against_forgery? diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index f18adb990c..d6d398d224 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -48,7 +48,7 @@ class JavaScriptHelperTest < ActionView::TestCase end def test_link_to_function_with_href - assert_dom_equal %(<a href="http://example.com/" onclick="alert('Hello world!'); return false;">Greeting</a>), + assert_dom_equal %(<a href="http://example.com/" onclick="alert('Hello world!'); return false;">Greeting</a>), link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/') end @@ -82,8 +82,12 @@ class JavaScriptHelperTest < ActionView::TestCase end def test_javascript_tag + self.output_buffer = 'foo' + assert_dom_equal "<script type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", javascript_tag("alert('hello')") + + assert_equal 'foo', output_buffer, 'javascript_tag without a block should not concat to output_buffer' end def test_javascript_tag_with_options @@ -91,16 +95,16 @@ class JavaScriptHelperTest < ActionView::TestCase javascript_tag("alert('hello')", :id => "the_js_tag") end - def test_javascript_tag_with_block - _erbout = '' - javascript_tag { _erbout.concat "alert('hello')" } - assert_dom_equal "<script type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", _erbout + def test_javascript_tag_with_block_in_erb + __in_erb_template = '' + javascript_tag { concat "alert('hello')" } + assert_dom_equal "<script type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", output_buffer end - def test_javascript_tag_with_block_and_options - _erbout = '' - javascript_tag(:id => "the_js_tag") { _erbout.concat "alert('hello')" } - assert_dom_equal "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", _erbout + def test_javascript_tag_with_block_and_options_in_erb + __in_erb_template = '' + javascript_tag(:id => "the_js_tag") { concat "alert('hello')" } + assert_dom_equal "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", output_buffer end def test_javascript_cdata_section diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb index 53a250f9d5..60b83b476d 100644 --- a/actionpack/test/template/prototype_helper_test.rb +++ b/actionpack/test/template/prototype_helper_test.rb @@ -54,7 +54,7 @@ class PrototypeHelperBaseTest < ActionView::TestCase end def create_generator - block = Proc.new { |*args| yield *args if block_given? } + block = Proc.new { |*args| yield *args if block_given? } JavaScriptGenerator.new self, &block end end @@ -70,7 +70,7 @@ class PrototypeHelperTest < PrototypeHelperBaseTest assert_dom_equal %(<a class=\"fine\" href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true}); return false;\">Remote outauthor</a>), link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }}, { :class => "fine" }) assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onComplete:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>), - link_to_remote("Remote outauthor", :complete => "alert(request.responseText)", :url => { :action => "whatnot" }) + link_to_remote("Remote outauthor", :complete => "alert(request.responseText)", :url => { :action => "whatnot" }) assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onSuccess:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>), link_to_remote("Remote outauthor", :success => "alert(request.responseText)", :url => { :action => "whatnot" }) assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>), @@ -78,12 +78,12 @@ class PrototypeHelperTest < PrototypeHelperBaseTest assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot?a=10&b=20', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>), link_to_remote("Remote outauthor", :failure => "alert(request.responseText)", :url => { :action => "whatnot", :a => '10', :b => '20' }) end - + def test_link_to_remote_html_options assert_dom_equal %(<a class=\"fine\" href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true}); return false;\">Remote outauthor</a>), link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }, :html => { :class => "fine" } }) end - + def test_link_to_remote_url_quote_escaping assert_dom_equal %(<a href="#" onclick="new Ajax.Request('http://www.example.com/whatnot\\\'s', {asynchronous:true, evalScripts:true}); return false;">Remote</a>), link_to_remote("Remote", { :url => { :action => "whatnot's" } }) @@ -93,14 +93,14 @@ class PrototypeHelperTest < PrototypeHelperBaseTest assert_dom_equal %(<script type="text/javascript">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Updater('schremser_bier', 'http://www.example.com/mehr_bier', {asynchronous:true, evalScripts:true})}, 10)\n//]]>\n</script>), periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" }) end - + def test_periodically_call_remote_with_frequency assert_dom_equal( "<script type=\"text/javascript\">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true})}, 2)\n//]]>\n</script>", periodically_call_remote(:frequency => 2) ) end - + def test_form_remote_tag assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">), form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }) @@ -117,53 +117,48 @@ class PrototypeHelperTest < PrototypeHelperBaseTest form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :html => { :method => :put }) end - def test_form_remote_tag_with_block - _erbout = '' - form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }) { _erbout.concat "Hello world!" } - assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">Hello world!</form>), _erbout + def test_form_remote_tag_with_block_in_erb + __in_erb_template = '' + form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }) { concat "Hello world!" } + assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">Hello world!</form>), output_buffer end def test_remote_form_for_with_record_identification_with_new_record - _erbout = '' remote_form_for(@record, {:html => { :id => 'create-author' }}) {} - + expected = %(<form action='#{authors_path}' onsubmit="new Ajax.Request('#{authors_path}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='new_author' id='create-author' method='post'></form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_remote_form_for_with_record_identification_without_html_options - _erbout = '' remote_form_for(@record) {} - + expected = %(<form action='#{authors_path}' onsubmit="new Ajax.Request('#{authors_path}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='new_author' method='post' id='new_author'></form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_remote_form_for_with_record_identification_with_existing_record @record.save - _erbout = '' remote_form_for(@record) {} - + expected = %(<form action='#{author_path(@record)}' id='edit_author_1' method='post' onsubmit="new Ajax.Request('#{author_path(@record)}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='edit_author'><div style='margin:0;padding:0'><input name='_method' type='hidden' value='put' /></div></form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_remote_form_for_with_new_object_in_list - _erbout = '' remote_form_for([@author, @article]) {} - + expected = %(<form action='#{author_articles_path(@author)}' onsubmit="new Ajax.Request('#{author_articles_path(@author)}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='new_article' method='post' id='new_article'></form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end - + def test_remote_form_for_with_existing_object_in_list @author.save @article.save - _erbout = '' remote_form_for([@author, @article]) {} - + expected = %(<form action='#{author_article_path(@author, @article)}' id='edit_article_1' method='post' onsubmit="new Ajax.Request('#{author_article_path(@author, @article)}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='edit_article'><div style='margin:0;padding:0'><input name='_method' type='hidden' value='put' /></div></form>) - assert_dom_equal expected, _erbout + assert_dom_equal expected, output_buffer end def test_on_callbacks @@ -178,18 +173,18 @@ class PrototypeHelperTest < PrototypeHelperBaseTest assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer',failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">), form_remote_tag(:update => { :success => "glass_of_beer", :failure => "glass_of_water" }, :url => { :action => :fast }, callback=>"monkeys();") end - + #HTTP status codes 200 up to 599 have callbacks #these should work 100.upto(599) do |callback| assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">), form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();") end - + #test 200 and 404 assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, parameters:Form.serialize(this)}); return false;">), form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, 200=>"monkeys();", 404=>"bananas();") - + #these shouldn't 1.upto(99) do |callback| assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">), @@ -199,44 +194,44 @@ class PrototypeHelperTest < PrototypeHelperBaseTest assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">), form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();") end - + #test ultimate combo assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, onComplete:function(request){c();}, onFailure:function(request){f();}, onLoading:function(request){c1()}, onSuccess:function(request){s()}, parameters:Form.serialize(this)}); return false;\">), form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :loading => "c1()", :success => "s()", :failure => "f();", :complete => "c();", 200=>"monkeys();", 404=>"bananas();") - + end - + def test_submit_to_remote assert_dom_equal %(<input name=\"More beer!\" onclick=\"new Ajax.Updater('empty_bottle', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); return false;\" type=\"button\" value=\"1000000\" />), submit_to_remote("More beer!", 1_000_000, :update => "empty_bottle") end - + def test_observe_field assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/reorder_if_empty', {asynchronous:true, evalScripts:true, parameters:value})})\n//]]>\n</script>), observe_field("glass", :frequency => 5.minutes, :url => { :action => "reorder_if_empty" }) end - + def test_observe_field_using_with_option expected = %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/check_value', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(value)})})\n//]]>\n</script>) assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => 'id') assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "'id=' + encodeURIComponent(value)") end - + def test_observe_field_using_json_in_with_option expected = %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/check_value', {asynchronous:true, evalScripts:true, parameters:{'id':value}})})\n//]]>\n</script>) - assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "{'id':value}") + assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "{'id':value}") end - + def test_observe_field_using_function_for_callback assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {alert('Element changed')})\n//]]>\n</script>), observe_field("glass", :frequency => 5.minutes, :function => "alert('Element changed')") end - + def test_observe_form assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {new Ajax.Request('http://www.example.com/cart_changed', {asynchronous:true, evalScripts:true, parameters:value})})\n//]]>\n</script>), observe_form("cart", :frequency => 2, :url => { :action => "cart_changed" }) end - + def test_observe_form_using_function_for_callback assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {alert('Form changed')})\n//]]>\n</script>), observe_form("cart", :frequency => 2, :function => "alert('Form changed')") @@ -251,7 +246,7 @@ class PrototypeHelperTest < PrototypeHelperBaseTest block = Proc.new { |page| page.replace_html('foo', 'bar') } assert_equal create_generator(&block).to_s, update_page(&block) end - + def test_update_page_tag block = Proc.new { |page| page.replace_html('foo', 'bar') } assert_equal javascript_tag(create_generator(&block).to_s), update_page_tag(&block) @@ -267,15 +262,15 @@ class PrototypeHelperTest < PrototypeHelperBaseTest def author_path(record) "/authors/#{record.id}" end - + def authors_path "/authors" end - + def author_articles_path(author) "/authors/#{author.id}/articles" end - + def author_article_path(author, article) "/authors/#{author.id}/articles/#{article.id}" end @@ -286,7 +281,7 @@ class JavaScriptGeneratorTest < PrototypeHelperBaseTest super @generator = create_generator end - + def test_insert_html_with_string assert_equal 'new Insertion.Top("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");', @generator.insert_html(:top, 'element', '<p>This is a test</p>') @@ -297,70 +292,75 @@ class JavaScriptGeneratorTest < PrototypeHelperBaseTest assert_equal 'new Insertion.After("element", "\\u003Cp\u003EThis is a test\\u003C/p\u003E");', @generator.insert_html(:after, 'element', '<p>This is a test</p>') end - + def test_replace_html_with_string assert_equal 'Element.update("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");', @generator.replace_html('element', '<p>This is a test</p>') end - + def test_replace_element_with_string assert_equal 'Element.replace("element", "\\u003Cdiv id=\"element\"\\u003E\\u003Cp\\u003EThis is a test\\u003C/p\\u003E\\u003C/div\\u003E");', @generator.replace('element', '<div id="element"><p>This is a test</p></div>') end - + def test_remove assert_equal 'Element.remove("foo");', @generator.remove('foo') assert_equal '["foo", "bar", "baz"].each(Element.remove);', @generator.remove('foo', 'bar', 'baz') end - + def test_show assert_equal 'Element.show("foo");', @generator.show('foo') assert_equal '["foo", "bar", "baz"].each(Element.show);', - @generator.show('foo', 'bar', 'baz') + @generator.show('foo', 'bar', 'baz') end - + def test_hide assert_equal 'Element.hide("foo");', @generator.hide('foo') assert_equal '["foo", "bar", "baz"].each(Element.hide);', - @generator.hide('foo', 'bar', 'baz') + @generator.hide('foo', 'bar', 'baz') end - + def test_toggle assert_equal 'Element.toggle("foo");', @generator.toggle('foo') assert_equal '["foo", "bar", "baz"].each(Element.toggle);', - @generator.toggle('foo', 'bar', 'baz') + @generator.toggle('foo', 'bar', 'baz') end - + def test_alert assert_equal 'alert("hello");', @generator.alert('hello') end - + def test_redirect_to assert_equal 'window.location.href = "http://www.example.com/welcome";', @generator.redirect_to(:action => 'welcome') assert_equal 'window.location.href = "http://www.example.com/welcome?a=b&c=d";', @generator.redirect_to("http://www.example.com/welcome?a=b&c=d") end - + + def test_reload + assert_equal 'window.location.reload();', + @generator.reload + end + def test_delay @generator.delay(20) do @generator.hide('foo') end - + assert_equal "setTimeout(function() {\n;\nElement.hide(\"foo\");\n}, 20000);", @generator.to_s end - + def test_to_s @generator.insert_html(:top, 'element', '<p>This is a test</p>') @generator.insert_html(:bottom, 'element', '<p>This is a test</p>') @generator.remove('foo', 'bar') @generator.replace_html('baz', '<p>This is a test</p>') - + assert_equal <<-EOS.chomp, @generator.to_s new Insertion.Top("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E"); new Insertion.Bottom("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E"); @@ -382,12 +382,12 @@ Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E"); @generator['hello'].hide assert_equal %($("hello").hide();), @generator.to_s end - + def test_element_proxy_variable_access @generator['hello']['style'] assert_equal %($("hello").style;), @generator.to_s end - + def test_element_proxy_variable_access_with_assignment @generator['hello']['style']['color'] = 'red' assert_equal %($("hello").style.color = "red";), @generator.to_s @@ -402,7 +402,7 @@ Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E"); @generator['hello'].hide("first").clean_whitespace assert_equal %($("hello").hide("first").cleanWhitespace();), @generator.to_s end - + def test_select_access assert_equal %($$("div.hello");), @generator.select('div.hello') end @@ -411,29 +411,29 @@ Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E"); @generator.select('p.welcome b').first.hide assert_equal %($$("p.welcome b").first().hide();), @generator.to_s end - + def test_visual_effect - assert_equal %(new Effect.Puff("blah",{});), + assert_equal %(new Effect.Puff("blah",{});), @generator.visual_effect(:puff,'blah') - end - + end + def test_visual_effect_toggle - assert_equal %(Effect.toggle("blah",'appear',{});), + assert_equal %(Effect.toggle("blah",'appear',{});), @generator.visual_effect(:toggle_appear,'blah') end - + def test_sortable - assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("blah")})}});), + assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("blah")})}});), @generator.sortable('blah', :url => { :action => "order" }) end - + def test_draggable - assert_equal %(new Draggable("blah", {});), + assert_equal %(new Draggable("blah", {});), @generator.draggable('blah') end - + def test_drop_receiving - assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});), + assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});), @generator.drop_receiving('blah', :url => { :action => "order" }) end @@ -535,7 +535,7 @@ return array.reverse(); }); EOS end - + def test_collection_proxy_with_find_all @generator.select('p').find_all 'a' do |value, index| @generator << '(value.className == "welcome")' @@ -547,7 +547,7 @@ return (value.className == "welcome"); }); EOS end - + def test_collection_proxy_with_in_groups_of @generator.select('p').in_groups_of('a', 3) @generator.select('p').in_groups_of('a', 3, 'x') @@ -556,13 +556,13 @@ var a = $$("p").inGroupsOf(3); var a = $$("p").inGroupsOf(3, "x"); EOS end - + def test_collection_proxy_with_each_slice @generator.select('p').each_slice('a', 3) @generator.select('p').each_slice('a', 3) do |group, index| group.reverse end - + assert_equal <<-EOS.strip, @generator.to_s var a = $$("p").eachSlice(3); var a = $$("p").eachSlice(3, function(value, index) { @@ -570,7 +570,7 @@ return value.reverse(); }); EOS end - + def test_debug_rjs ActionView::Base.debug_rjs = true @generator['welcome'].replace_html 'Welcome' @@ -578,7 +578,7 @@ return value.reverse(); ensure ActionView::Base.debug_rjs = false end - + def test_literal literal = @generator.literal("function() {}") assert_equal "function() {}", literal.to_json @@ -589,7 +589,7 @@ return value.reverse(); @generator.form.focus('my_field') assert_equal "Form.focus(\"my_field\");", @generator.to_s end - + def test_call_with_block @generator.call(:before) @generator.call(:my_method) do |p| @@ -602,7 +602,7 @@ return value.reverse(); end assert_equal "before();\nmy_method(function() { $(\"one\").show();\n$(\"two\").hide(); });\nin_between();\nmy_method_with_arguments(true, \"hello\", function() { $(\"three\").visualEffect(\"highlight\"); });", @generator.to_s end - + def test_class_proxy_call_with_block @generator.my_object.my_method do |p| p[:one].show diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb index 0afbb54f57..34a200b933 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionpack/test/template/record_tag_helper_test.rb @@ -2,7 +2,7 @@ require 'abstract_unit' class Post def id - 45 + 45 end def body "What a wonderful world!" @@ -15,40 +15,36 @@ class RecordTagHelperTest < ActionView::TestCase def setup @post = Post.new end - + def test_content_tag_for - _erbout = '' expected = %(<li class="post bar" id="post_45"></li>) actual = content_tag_for(:li, @post, :class => 'bar') { } assert_dom_equal expected, actual end - + def test_content_tag_for_prefix - _erbout = '' expected = %(<ul class="post" id="archived_post_45"></ul>) actual = content_tag_for(:ul, @post, :archived) { } - assert_dom_equal expected, actual + assert_dom_equal expected, actual end - + def test_content_tag_for_with_extra_html_tags - _erbout = '' expected = %(<tr class="post bar" id="post_45" style='background-color: #f0f0f0'></tr>) actual = content_tag_for(:tr, @post, {:class => "bar", :style => "background-color: #f0f0f0"}) { } - assert_dom_equal expected, actual + assert_dom_equal expected, actual end - - def test_block_works_with_content_tag_for - _erbout = '' + + def test_block_works_with_content_tag_for_in_erb + __in_erb_template = '' expected = %(<tr class="post" id="post_45">#{@post.body}</tr>) - actual = content_tag_for(:tr, @post) { _erbout.concat @post.body } - assert_dom_equal expected, actual + actual = content_tag_for(:tr, @post) { concat @post.body } + assert_dom_equal expected, actual end - - def test_div_for - _erbout = '' + + def test_div_for_in_erb + __in_erb_template = '' expected = %(<div class="post bar" id="post_45">#{@post.body}</div>) - actual = div_for(@post, :class => "bar") { _erbout.concat @post.body } + actual = div_for(@post, :class => "bar") { concat @post.body } assert_dom_equal expected, actual - end - + end end diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb index 4da6116095..fc49d340ef 100644 --- a/actionpack/test/template/tag_helper_test.rb +++ b/actionpack/test/template/tag_helper_test.rb @@ -33,24 +33,40 @@ class TagHelperTest < ActionView::TestCase assert_equal content_tag("a", "Create", "href" => "create"), content_tag("a", "Create", :href => "create") end - - def test_content_tag_with_block - _erbout = '' - content_tag(:div) { _erbout.concat "Hello world!" } - assert_dom_equal "<div>Hello world!</div>", _erbout + + def test_content_tag_with_block_in_erb + __in_erb_template = '' + content_tag(:div) { concat "Hello world!" } + assert_dom_equal "<div>Hello world!</div>", output_buffer end - - def test_content_tag_with_block_and_options - _erbout = '' - content_tag(:div, :class => "green") { _erbout.concat "Hello world!" } - assert_dom_equal %(<div class="green">Hello world!</div>), _erbout + + def test_content_tag_with_block_and_options_in_erb + __in_erb_template = '' + content_tag(:div, :class => "green") { concat "Hello world!" } + assert_dom_equal %(<div class="green">Hello world!</div>), output_buffer end - - def test_content_tag_with_block_and_options_outside_of_action_view + + def test_content_tag_with_block_and_options_out_of_erb + assert_dom_equal %(<div class="green">Hello world!</div>), content_tag(:div, :class => "green") { "Hello world!" } + end + + def test_content_tag_with_block_and_options_outside_out_of_erb assert_equal content_tag("a", "Create", :href => "create"), - content_tag("a", "href" => "create") { "Create" } + content_tag("a", "href" => "create") { "Create" } end - + + def test_content_tag_nested_in_content_tag_out_of_erb + assert_equal content_tag("p", content_tag("b", "Hello")), + content_tag("p") { content_tag("b", "Hello") }, + output_buffer + end + + def test_content_tag_nested_in_content_tag_in_erb + __in_erb_template = true + content_tag("p") { concat content_tag("b", "Hello") } + assert_equal '<p><b>Hello</b></p>', output_buffer + end + def test_cdata_section assert_equal "<![CDATA[<hello world>]]>", cdata_section("<hello world>") end diff --git a/actionpack/test/template/template_file_test.rb b/actionpack/test/template/template_file_test.rb new file mode 100644 index 0000000000..d14a966c1c --- /dev/null +++ b/actionpack/test/template/template_file_test.rb @@ -0,0 +1,95 @@ +require 'abstract_unit' + +class TemplateFileTest < Test::Unit::TestCase + LOAD_PATH_ROOT = File.join(File.dirname(__FILE__), '..', 'fixtures') + + def setup + @template = ActionView::TemplateFile.new("test/hello_world.html.erb") + @another_template = ActionView::TemplateFile.new("test/hello_world.erb") + @file_only = ActionView::TemplateFile.new("hello_world.erb") + @full_path = ActionView::TemplateFile.new("/u/app/scales/config/../app/views/test/hello_world.erb", true) + @layout = ActionView::TemplateFile.new("layouts/hello") + @multipart = ActionView::TemplateFile.new("test_mailer/implicitly_multipart_example.text.html.erb") + end + + def test_path + assert_equal "test/hello_world.html.erb", @template.path + assert_equal "test/hello_world.erb", @another_template.path + assert_equal "hello_world.erb", @file_only.path + assert_equal "/u/app/scales/config/../app/views/test/hello_world.erb", @full_path.path + assert_equal "layouts/hello", @layout.path + assert_equal "test_mailer/implicitly_multipart_example.text.html.erb", @multipart.path + end + + def test_path_without_extension + assert_equal "test/hello_world.html", @template.path_without_extension + assert_equal "test/hello_world", @another_template.path_without_extension + assert_equal "hello_world", @file_only.path_without_extension + assert_equal "layouts/hello", @layout.path_without_extension + assert_equal "test_mailer/implicitly_multipart_example.text.html", @multipart.path_without_extension + end + + def test_path_without_format_and_extension + assert_equal "test/hello_world", @template.path_without_format_and_extension + assert_equal "test/hello_world", @another_template.path_without_format_and_extension + assert_equal "hello_world", @file_only.path_without_format_and_extension + assert_equal "layouts/hello", @layout.path_without_format_and_extension + assert_equal "test_mailer/implicitly_multipart_example", @multipart.path_without_format_and_extension + end + + def test_name + assert_equal "hello_world", @template.name + assert_equal "hello_world", @another_template.name + assert_equal "hello_world", @file_only.name + assert_equal "hello_world", @full_path.name + assert_equal "hello", @layout.name + assert_equal "implicitly_multipart_example", @multipart.name + end + + def test_format + assert_equal "html", @template.format + assert_equal nil, @another_template.format + assert_equal nil, @layout.format + assert_equal "text.html", @multipart.format + end + + def test_extension + assert_equal "erb", @template.extension + assert_equal "erb", @another_template.extension + assert_equal nil, @layout.extension + assert_equal "erb", @multipart.extension + end + + def test_format_and_extension + assert_equal "html.erb", @template.format_and_extension + assert_equal "erb", @another_template.format_and_extension + assert_equal nil, @layout.format_and_extension + assert_equal "text.html.erb", @multipart.format_and_extension + end + + def test_new_file_with_extension + file = @template.dup_with_extension(:haml) + assert_equal "test/hello_world.html", file.path_without_extension + assert_equal "haml", file.extension + assert_equal "test/hello_world.html.haml", file.path + + file = @another_template.dup_with_extension(:haml) + assert_equal "test/hello_world", file.path_without_extension + assert_equal "haml", file.extension + assert_equal "test/hello_world.haml", file.path + + file = @another_template.dup_with_extension(nil) + assert_equal "test/hello_world", file.path_without_extension + assert_equal nil, file.extension + assert_equal "test/hello_world", file.path + end + + def test_freezes_entire_contents + @template.freeze + assert @template.frozen? + assert @template.base_path.frozen? + assert @template.name.frozen? + assert @template.format.frozen? + assert @template.extension.frozen? + end +end diff --git a/actionpack/test/template/template_finder_test.rb b/actionpack/test/template/template_finder_test.rb deleted file mode 100644 index 3d6baff5fb..0000000000 --- a/actionpack/test/template/template_finder_test.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'abstract_unit' - -class TemplateFinderTest < Test::Unit::TestCase - - LOAD_PATH_ROOT = File.join(File.dirname(__FILE__), '..', 'fixtures') - - def setup - ActionView::TemplateFinder.process_view_paths(LOAD_PATH_ROOT) - ActionView::Template::register_template_handler :mab, Class.new(ActionView::TemplateHandler) - @template = ActionView::Base.new - @finder = ActionView::TemplateFinder.new(@template, LOAD_PATH_ROOT) - end - - def test_should_raise_exception_for_unprocessed_view_path - assert_raises ActionView::TemplateFinder::InvalidViewPath do - ActionView::TemplateFinder.new(@template, File.dirname(__FILE__)) - end - end - - def test_should_cache_file_extension_properly - assert_equal ["builder", "erb", "rhtml", "rjs", "rxml", "mab"].sort, - ActionView::TemplateFinder.file_extension_cache[LOAD_PATH_ROOT].values.flatten.uniq.sort - - assert_equal (Dir.glob("#{LOAD_PATH_ROOT}/**/*/*.{erb,rjs,rhtml,builder,rxml,mab}") | - Dir.glob("#{LOAD_PATH_ROOT}/**.{erb,rjs,rhtml,builder,rxml,mab}")).size, - ActionView::TemplateFinder.file_extension_cache[LOAD_PATH_ROOT].keys.size - end - - def test_should_cache_dir_content_properly - assert ActionView::TemplateFinder.processed_view_paths[LOAD_PATH_ROOT] - assert_equal (Dir.glob("#{LOAD_PATH_ROOT}/**/*/**") | Dir.glob("#{LOAD_PATH_ROOT}/**")).find_all {|f| !File.directory?(f) }.size, - ActionView::TemplateFinder.processed_view_paths[LOAD_PATH_ROOT].size - end - - def test_find_template_extension_from_first_render - assert_nil @finder.send(:find_template_extension_from_first_render) - - { - nil => nil, - '' => nil, - 'foo' => nil, - '/foo' => nil, - 'foo.rb' => 'rb', - 'foo.bar.rb' => 'bar.rb', - 'baz/foo.rb' => 'rb', - 'baz/foo.bar.rb' => 'bar.rb', - 'baz/foo.o/foo.rb' => 'rb', - 'baz/foo.o/foo.bar.rb' => 'bar.rb', - }.each do |input,expectation| - @template.instance_variable_set('@first_render', input) - assert_equal expectation, @finder.send(:find_template_extension_from_first_render) - end - end - - def test_should_report_file_exists_correctly - assert_nil @finder.send(:find_template_extension_from_first_render) - assert_equal false, @finder.send(:file_exists?, 'test.rhtml') - assert_equal false, @finder.send(:file_exists?, 'test.rb') - - @template.instance_variable_set('@first_render', 'foo.rb') - - assert_equal 'rb', @finder.send(:find_template_extension_from_first_render) - assert_equal false, @finder.send(:file_exists?, 'baz') - assert_equal false, @finder.send(:file_exists?, 'baz.rb') - end - - uses_mocha 'Template finder tests' do - def test_should_update_extension_cache_when_template_handler_is_registered - ActionView::TemplateFinder.expects(:update_extension_cache_for).with("funky") - ActionView::Template::register_template_handler :funky, Class.new(ActionView::TemplateHandler) - end - end -end diff --git a/actionpack/test/template/template_object_test.rb b/actionpack/test/template/template_object_test.rb index afb5c5cc16..2cfc4523c6 100644 --- a/actionpack/test/template/template_object_test.rb +++ b/actionpack/test/template/template_object_test.rb @@ -2,60 +2,57 @@ require 'abstract_unit' class TemplateObjectTest < Test::Unit::TestCase LOAD_PATH_ROOT = File.join(File.dirname(__FILE__), '..', 'fixtures') - ActionView::TemplateFinder.process_view_paths(LOAD_PATH_ROOT) - + class TemplateTest < Test::Unit::TestCase def setup @view = ActionView::Base.new(LOAD_PATH_ROOT) @path = "test/hello_world.erb" end - + def test_should_create_valid_template template = ActionView::Template.new(@view, @path, true) - + assert_kind_of ActionView::TemplateHandlers::ERB, template.handler - assert_equal "test/hello_world.erb", template.path + assert_equal "test/hello_world.erb", template.path.to_s assert_nil template.instance_variable_get(:"@source") assert_equal "erb", template.extension end - + uses_mocha 'Template preparation tests' do - def test_should_prepare_template_properly template = ActionView::Template.new(@view, @path, true) view = template.instance_variable_get(:"@view") - + view.expects(:evaluate_assigns) template.handler.expects(:compile_template).with(template) view.expects(:method_names).returns({}) - + template.prepare! end - end end - + class PartialTemplateTest < Test::Unit::TestCase def setup @view = ActionView::Base.new(LOAD_PATH_ROOT) @path = "test/partial_only" end - + def test_should_create_valid_partial_template template = ActionView::PartialTemplate.new(@view, @path, nil) - - assert_equal "test/_partial_only", template.path + + assert_equal "test/_partial_only", template.path.path_without_format_and_extension assert_equal :partial_only, template.variable_name - + assert template.locals.has_key?(:object) assert template.locals.has_key?(:partial_only) end - + def test_partial_with_errors template = ActionView::PartialTemplate.new(@view, 'test/raise', nil) assert_raise(ActionView::TemplateError) { template.render_template } end - + uses_mocha 'Partial template preparation tests' do def test_should_prepare_on_initialization ActionView::PartialTemplate.any_instance.expects(:prepare!) @@ -63,7 +60,7 @@ class TemplateObjectTest < Test::Unit::TestCase end end end - + class PartialTemplateFallbackTest < Test::Unit::TestCase def setup @view = ActionView::Base.new(LOAD_PATH_ROOT) @@ -72,7 +69,7 @@ class TemplateObjectTest < Test::Unit::TestCase def test_default template = ActionView::PartialTemplate.new(@view, @path, nil) - assert_equal 'test/_layout_for_partial', template.path + assert_equal 'test/_layout_for_partial', template.path.path_without_format_and_extension assert_equal 'erb', template.extension assert_equal :html, @view.template_format end @@ -80,7 +77,7 @@ class TemplateObjectTest < Test::Unit::TestCase def test_js @view.template_format = :js template = ActionView::PartialTemplate.new(@view, @path, nil) - assert_equal 'test/_layout_for_partial', template.path + assert_equal 'test/_layout_for_partial', template.path.path_without_format_and_extension assert_equal 'erb', template.extension assert_equal :html, @view.template_format end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 62cdca03d1..df6be3bb13 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -11,6 +11,12 @@ class TextHelperTest < ActionView::TestCase @_cycles = nil if (defined? @_cycles) end + def test_concat + self.output_buffer = 'foo' + assert_equal 'foobar', concat('bar') + assert_equal 'foobar', output_buffer + end + def test_simple_format assert_equal "<p></p>", simple_format(nil) diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index d45ea08a6f..44ceed6661 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -80,7 +80,7 @@ class UrlHelperTest < ActionView::TestCase def test_link_tag_with_straight_url assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", "http://www.example.com") end - + def test_link_tag_without_host_option ActionController::Base.class_eval { attr_accessor :url } url = {:controller => 'weblog', :action => 'show'} @@ -121,7 +121,7 @@ class UrlHelperTest < ActionView::TestCase @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'}) assert_dom_equal "<a href=\"http://www.example.com/referer\">go back</a>", link_to('go back', :back) end - + def test_link_tag_with_back_and_no_referer @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {}) assert_dom_equal "<a href=\"javascript:history.back()\">go back</a>", link_to('go back', :back) @@ -211,7 +211,15 @@ class UrlHelperTest < ActionView::TestCase def test_link_tag_using_post_javascript_and_popup assert_raises(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") } end - + + def test_link_tag_using_block_in_erb + __in_erb_template = '' + + link_to("http://example.com") { concat("Example site") } + + assert_equal '<a href="http://example.com">Example site</a>', output_buffer + end + def test_link_to_unless assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog") assert_dom_equal "<a href=\"http://www.example.com\">Listing</a>", link_to_unless(false, "Listing", :action => "list", :controller => "weblog") @@ -285,7 +293,7 @@ class UrlHelperTest < ActionView::TestCase assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain(dot)com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)") assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") end - + def protect_against_forgery? false end @@ -412,11 +420,11 @@ class Workshop def initialize(id, new_record) @id, @new_record = id, new_record end - + def new_record? @new_record end - + def to_s id.to_s end diff --git a/activemodel/Rakefile b/activemodel/Rakefile index 87e9b547f3..99c9fc3bd1 100644 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -10,7 +10,7 @@ Rake::RDocTask.new { |rdoc| rdoc.title = "Active Model" rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' rdoc.options << '--charset' << 'utf-8' - rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' rdoc.rdoc_files.include('README', 'CHANGES') rdoc.rdoc_files.include('lib/**/*.rb') -}
\ No newline at end of file +} diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index a65771648e..4e05d0fcfd 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,23 @@ *Edge* +* Always treat integer :limit as byte length. #420 [Tarmo Tänav] + +* Partial updates don't update lock_version if nothing changed. #426 [Daniel Morrison] + +* Fix column collision with named_scope and :joins. #46 [Duncan Beevers, Mark Catley] + +* db:migrate:down and :up update schema_migrations. #369 [Michael Raidel, RaceCondition] + +* PostgreSQL: support :conditions => [':foo::integer', { :foo => 1 }] without treating the ::integer typecast as a bind variable. [Tarmo Tänav] + +* MySQL: rename_column preserves column defaults. #466 [Diego Algorta] + +* Add :from option to calculations. #397 [Ben Munat] + +* Add :validate option to associations to enable/disable the automatic validation of associated models. Resolves #301. [Jan De Poorter] + +* PostgreSQL: use 'INSERT ... RETURNING id' for 8.2 and later. [Jeremy Kemper] + * Added SQL escaping for :limit and :offset in MySQL [Jonathan Wiess] diff --git a/activerecord/Rakefile b/activerecord/Rakefile index fc068b16c3..60b17e02b9 100755 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -141,7 +141,7 @@ Rake::RDocTask.new { |rdoc| rdoc.title = "Active Record -- Object-relation mapping put on rails" rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' rdoc.options << '--charset' << 'utf-8' - rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG') rdoc.rdoc_files.include('lib/**/*.rb') rdoc.rdoc_files.exclude('lib/active_record/vendor/*') @@ -225,13 +225,13 @@ end desc "Publish the beta gem" task :pgem => [:package] do - Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload - `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'` + Rake::SshFilePublisher.new("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload + `ssh wrath.rubyonrails.org './gemupdate.sh'` end desc "Publish the API documentation" task :pdoc => [:rdoc] do - Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ar", "doc").upload + Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload end desc "Publish the release files to RubyForge." diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 087ed2a587..194c4ee5db 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -103,10 +103,10 @@ module ActiveRecord associated_records = reflection.klass.find(:all, :conditions => [conditions, ids], :include => options[:include], :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} as 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 _parent_record_id", + :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id", :order => options[:order]) - set_association_collection_records(id_to_record_map, reflection.name, associated_records, '_parent_record_id') + 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={}) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 9479296b90..d2253cb61c 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -691,6 +691,7 @@ module ActiveRecord # association is a polymorphic +belongs_to+. # * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>. # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association. + # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. true by default. # # Option examples: # has_many :comments, :order => "posted_on" @@ -711,7 +712,7 @@ module ActiveRecord configure_dependency_for_has_many(reflection) - add_multiple_associated_save_callbacks(reflection.name) + add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false add_association_callbacks(reflection.name, reflection.options) if options[:through] @@ -770,6 +771,7 @@ module ActiveRecord # * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_one :through</tt> queries where the source # association is a polymorphic +belongs_to+. # * <tt>:readonly</tt> - If true, the associated object is readonly through the association. + # * <tt>:validate</tt> - If false, don't validate the associated object when saving the parent object. +false+ by default. # # Option examples: # has_one :credit_card, :dependent => :destroy # destroys the associated credit card @@ -800,7 +802,7 @@ module ActiveRecord end after_save method_name - add_single_associated_save_callbacks(reflection.name) + add_single_associated_save_callbacks(reflection.name) if options[:validate] == true association_accessor_methods(reflection, HasOneAssociation) association_constructor_method(:build, reflection, HasOneAssociation) association_constructor_method(:create, reflection, HasOneAssociation) @@ -858,6 +860,7 @@ module ActiveRecord # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>). # * <tt>:readonly</tt> - If true, the associated object is readonly through the association. + # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +false+ by default. # # Option examples: # belongs_to :firm, :foreign_key => "client_of" @@ -938,6 +941,8 @@ module ActiveRecord ) end + add_single_associated_save_callbacks(reflection.name) if options[:validate] == true + configure_dependency_for_belongs_to(reflection) end @@ -1026,6 +1031,7 @@ module ActiveRecord # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error. # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association. + # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +true+ by default. # # Option examples: # has_and_belongs_to_many :projects @@ -1038,7 +1044,7 @@ module ActiveRecord def has_and_belongs_to_many(association_id, options = {}, &extension) reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension) - add_multiple_associated_save_callbacks(reflection.name) + add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false collection_accessor_methods(reflection, HasAndBelongsToManyAssociation) # Don't use a before_destroy callback since users' before_destroy @@ -1344,7 +1350,8 @@ module ActiveRecord :uniq, :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove, - :extend, :readonly + :extend, :readonly, + :validate ) options[:extend] = create_extension_modules(association_id, extension, options[:extend]) @@ -1354,7 +1361,7 @@ module ActiveRecord def create_has_one_reflection(association_id, options) options.assert_valid_keys( - :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly + :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate ) create_reflection(:has_one, association_id, options, self) @@ -1362,7 +1369,7 @@ module ActiveRecord def create_has_one_through_reflection(association_id, options) options.assert_valid_keys( - :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type + :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate ) create_reflection(:has_one, association_id, options, self) end @@ -1370,7 +1377,7 @@ module ActiveRecord def create_belongs_to_reflection(association_id, options) options.assert_valid_keys( :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent, - :counter_cache, :extend, :polymorphic, :readonly + :counter_cache, :extend, :polymorphic, :readonly, :validate ) reflection = create_reflection(:belongs_to, association_id, options, self) @@ -1389,7 +1396,8 @@ module ActiveRecord :uniq, :finder_sql, :delete_sql, :insert_sql, :before_add, :after_add, :before_remove, :after_remove, - :extend, :readonly + :extend, :readonly, + :validate ) options[:extend] = create_extension_modules(association_id, extension, options[:extend]) @@ -1507,7 +1515,7 @@ module ActiveRecord end def order_tables(options) - order = options[:order] + order = [options[:order], scope(:find, :order) ].join(", ") return [] unless order && order.is_a?(String) order.scan(/([\.\w]+).?\./).flatten end @@ -1639,7 +1647,9 @@ module ActiveRecord end def join_for_table_name(table_name) - @joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil + join = (@joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first) rescue nil + return join unless join.nil? + @joins.select{|j|j.is_a?(JoinAssociation) && j.aliased_join_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil end def joins_for_table_name(table_name) diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index 4fa8e9d0a8..918404eac6 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -87,6 +87,7 @@ module ActiveRecord :joins => @join_sql, :readonly => false, :order => @reflection.options[:order], + :include => @reflection.options[:include], :limit => @reflection.options[:limit] } } end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index f584a97cbb..295beb2966 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -100,7 +100,7 @@ module ActiveRecord create_scoping = {} set_belongs_to_association_for(create_scoping) { - :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit] }, + :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]}, :create => create_scoping } end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index cfc6a5693a..c2a8d3ec3d 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -372,7 +372,7 @@ module ActiveRecord #:nodoc: def self.reset_subclasses #:nodoc: nonreloadables = [] subclasses.each do |klass| - unless Dependencies.autoloaded? klass + unless ActiveSupport::Dependencies.autoloaded? klass nonreloadables << klass next end @@ -1296,6 +1296,20 @@ module ActiveRecord #:nodoc: store_full_sti_class ? name : name.demodulize end + # Merges conditions so that the result is a valid +condition+ + def merge_conditions(*conditions) + segments = [] + + conditions.each do |condition| + unless condition.blank? + sql = sanitize_sql(condition) + segments << sql unless sql.blank? + end + end + + "(#{segments.join(') AND (')})" unless segments.empty? + end + private def find_initial(options) options.update(:limit => 1) @@ -1464,7 +1478,7 @@ module ActiveRecord #:nodoc: def construct_finder_sql(options) scope = scope(:find) - sql = "SELECT #{options[:select] || (scope && scope[:select]) || (options[:joins] && quoted_table_name + '.*') || '*'} " + sql = "SELECT #{options[:select] || (scope && scope[:select]) || ((options[:joins] || (scope && scope[:joins])) && quoted_table_name + '.*') || '*'} " sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} " add_joins!(sql, options, scope) @@ -1483,20 +1497,6 @@ module ActiveRecord #:nodoc: (safe_to_array(first) + safe_to_array(second)).uniq end - # Merges conditions so that the result is a valid +condition+ - def merge_conditions(*conditions) - segments = [] - - conditions.each do |condition| - unless condition.blank? - sql = sanitize_sql(condition) - segments << sql unless sql.blank? - end - end - - "(#{segments.join(') AND (')})" unless segments.empty? - end - # Object#to_a is deprecated, though it does have the desired behavior def safe_to_array(o) case o @@ -1902,10 +1902,12 @@ module ActiveRecord #:nodoc: # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass. def compute_type(type_name) modularized_name = type_name_with_module(type_name) - begin - class_eval(modularized_name, __FILE__, __LINE__) - rescue NameError - class_eval(type_name, __FILE__, __LINE__) + silence_warnings do + begin + class_eval(modularized_name, __FILE__, __LINE__) + rescue NameError + class_eval(type_name, __FILE__, __LINE__) + end end end @@ -2052,9 +2054,10 @@ module ActiveRecord #:nodoc: end def replace_named_bind_variables(statement, bind_vars) #:nodoc: - statement.gsub(/:([a-zA-Z]\w*)/) do - match = $1.to_sym - if bind_vars.include?(match) + statement.gsub(/(:?):([a-zA-Z]\w*)/) do + if $1 == ':' # skip postgresql casts + $& # return the whole match + elsif bind_vars.include?(match = $2.to_sym) quote_bound_value(bind_vars[match]) else raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}" @@ -2063,13 +2066,18 @@ module ActiveRecord #:nodoc: end def expand_range_bind_variables(bind_vars) #:nodoc: - bind_vars.sum do |var| + expanded = [] + + bind_vars.each do |var| if var.is_a?(Range) - [var.first, var.last] + expanded << var.first + expanded << var.last else - [var] + expanded << var end end + + expanded end def quote_bound_value(value) #:nodoc: @@ -2161,11 +2169,11 @@ module ActiveRecord #:nodoc: def cache_key case when new_record? - "#{self.class.name.tableize}/new" - when self[:updated_at] - "#{self.class.name.tableize}/#{id}-#{updated_at.to_s(:number)}" + "#{self.class.model_name.cache_key}/new" + when timestamp = self[:updated_at] + "#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}" else - "#{self.class.name.tableize}/#{id}" + "#{self.class.model_name.cache_key}/#{id}" end end @@ -2246,12 +2254,12 @@ module ActiveRecord #:nodoc: end end - # Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records. - # Note: This method is overwritten by the Validation module that'll make sure that updates made with this method - # aren't subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid. + # Updates a single attribute and saves the record without going through the normal validation procedure. + # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method + # in Base is replaced with this when the validations module is mixed in, which it is by default. def update_attribute(name, value) send(name.to_s + '=', value) - save + save(false) end # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 10e8330d1c..2ca1a0aaa3 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -1,6 +1,6 @@ module ActiveRecord module Calculations #:nodoc: - CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include] + CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include, :from] def self.included(base) base.extend(ClassMethods) end @@ -27,6 +27,8 @@ module ActiveRecord # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not # include the joined columns. # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ... + # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name + # of a database view). # # Examples for counting all: # Person.count # returns the total count of all people @@ -71,7 +73,7 @@ module ActiveRecord # # Person.sum('age') def sum(column_name, options = {}) - calculate(:sum, column_name, options) || 0 + calculate(:sum, column_name, options) end # This calculates aggregate values in the given column. Methods for count, sum, average, minimum, and maximum have been added as shortcuts. @@ -178,8 +180,12 @@ module ActiveRecord sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] - sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround - sql << " FROM #{connection.quote_table_name(table_name)} " + if options[:from] + sql << " FROM #{options[:from]} " + else + sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround + sql << " FROM #{connection.quote_table_name(table_name)} " + end if merged_includes.any? join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins]) sql << join_dependency.join_associations.collect{|join| join.association_join }.join @@ -266,6 +272,7 @@ module ActiveRecord operation = operation.to_s.downcase case operation when 'count' then value.to_i + when 'sum' then value =~ /\./ ? value.to_f : value.to_i when 'avg' then value && value.to_f else column ? column.type_cast(value) : value end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 6c2f65df05..5883cdfbe1 100755 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -293,14 +293,14 @@ module ActiveRecord private def callback(method) - notify(method) - result = run_callbacks(method) { |result, object| result == false } if result != false && respond_to_without_attributes?(method) result = send(method) end + notify(method) + return result end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index a2a1bb8c82..0f60a91ef1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -331,21 +331,32 @@ module ActiveRecord end def assume_migrated_upto_version(version) + version = version.to_i sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name) + migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i) versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename| filename.split('/').last.split('_').first.to_i end - execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" unless migrated.include?(version.to_i) - (versions - migrated).select { |v| v < version.to_i }.each do |v| - execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" + unless migrated.include?(version) + execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" + end + + inserted = Set.new + (versions - migrated).each do |v| + if inserted.include?(v) + raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict." + elsif v < version + execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" + inserted << v + end end end def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: if native = native_database_types[type] - column_type_sql = native.is_a?(Hash) ? native[:name] : native + column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup if type == :decimal # ignore limit, use precision and scale scale ||= native[:scale] @@ -360,7 +371,7 @@ module ActiveRecord raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified" end - elsif limit ||= native.is_a?(Hash) && native[:limit] + elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit]) column_type_sql << "(#{limit})" end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 653b45021d..dd54950790 100755 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -42,30 +42,6 @@ end module ActiveRecord class Base - def self.require_mysql - # Include the MySQL driver if one hasn't already been loaded - unless defined? Mysql - begin - require_library_or_gem 'mysql' - rescue LoadError => cannot_require_mysql - # Use the bundled Ruby/MySQL driver if no driver is already in place - begin - ActiveRecord::Base.logger.info( - "WARNING: You're using the Ruby-based MySQL library that ships with Rails. This library is not suited for production. " + - "Please install the C-based MySQL library instead (gem install mysql)." - ) if ActiveRecord::Base.logger - - require 'active_record/vendor/mysql' - rescue LoadError - raise cannot_require_mysql - end - end - end - - # Define Mysql::Result.all_hashes - MysqlCompat.define_all_hashes_method! - end - # Establishes a connection to the database that's used by all Active Record objects. def self.mysql_connection(config) # :nodoc: config = config.symbolize_keys @@ -81,7 +57,17 @@ module ActiveRecord raise ArgumentError, "No database specified. Missing argument: database." end - require_mysql + # Require the MySQL driver and define Mysql::Result.all_hashes + unless defined? Mysql + begin + require_library_or_gem('mysql') + rescue LoadError + $stderr.puts '!!! The bundled mysql.rb driver has been removed from Rails 2.2. Please install the mysql gem and try again: gem install mysql.' + raise + end + end + MysqlCompat.define_all_hashes_method! + mysql = Mysql.init mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey] @@ -113,7 +99,8 @@ module ActiveRecord end def extract_limit(sql_type) - if sql_type =~ /blob|text/i + case sql_type + when /blob|text/i case sql_type when /tiny/i 255 @@ -124,6 +111,11 @@ module ActiveRecord else super # we could return 65535 here, but we leave it undecorated by default end + when /^bigint/i; 8 + when /^int/i; 4 + when /^mediumint/i; 3 + when /^smallint/i; 2 + when /^tinyint/i; 1 else super end @@ -165,8 +157,10 @@ module ActiveRecord # # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false class MysqlAdapter < AbstractAdapter - @@emulate_booleans = true cattr_accessor :emulate_booleans + self.emulate_booleans = true + + ADAPTER_NAME = 'MySQL'.freeze LOST_CONNECTION_ERROR_MESSAGES = [ "Server shutdown in progress", @@ -174,7 +168,22 @@ module ActiveRecord "Lost connection to MySQL server during query", "MySQL server has gone away" ] - QUOTED_TRUE, QUOTED_FALSE = '1', '0' + QUOTED_TRUE, QUOTED_FALSE = '1'.freeze, '0'.freeze + + NATIVE_DATABASE_TYPES = { + :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze, + :string => { :name => "varchar", :limit => 255 }, + :text => { :name => "text" }, + :integer => { :name => "int", :limit => 4 }, + :float => { :name => "float" }, + :decimal => { :name => "decimal" }, + :datetime => { :name => "datetime" }, + :timestamp => { :name => "datetime" }, + :time => { :name => "time" }, + :date => { :name => "date" }, + :binary => { :name => "blob" }, + :boolean => { :name => "tinyint", :limit => 1 } + } def initialize(connection, logger, connection_options, config) super(connection, logger) @@ -184,7 +193,7 @@ module ActiveRecord end def adapter_name #:nodoc: - 'MySQL' + ADAPTER_NAME end def supports_migrations? #:nodoc: @@ -192,20 +201,7 @@ module ActiveRecord end def native_database_types #:nodoc: - { - :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY", - :string => { :name => "varchar", :limit => 255 }, - :text => { :name => "text" }, - :integer => { :name => "int"}, - :float => { :name => "float" }, - :decimal => { :name => "decimal" }, - :datetime => { :name => "datetime" }, - :timestamp => { :name => "datetime" }, - :time => { :name => "time" }, - :date => { :name => "date" }, - :binary => { :name => "blob" }, - :boolean => { :name => "tinyint", :limit => 1 } - } + NATIVE_DATABASE_TYPES end @@ -460,8 +456,16 @@ module ActiveRecord end def rename_column(table_name, column_name, new_column_name) #:nodoc: + options = {} + if column = columns(table_name).find { |c| c.name == column_name.to_s } + options[:default] = column.default + else + raise ActiveRecordError, "No such column: #{table_name}.#{column_name}" + end current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"] - execute "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}" + rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}" + add_column_options!(rename_column_sql, options) + execute(rename_column_sql) end # Maps logical Rails types to MySQL-specific data types. @@ -469,14 +473,11 @@ module ActiveRecord return super unless type.to_s == 'integer' case limit - when 0..3 - "smallint(#{limit})" - when 4..8 - "int(#{limit})" - when 9..20 - "bigint(#{limit})" - else - 'int(11)' + when 1; 'tinyint' + when 2; 'smallint' + when 3; 'mediumint' + when 4, nil; 'int(11)' + else; 'bigint' end end @@ -498,12 +499,17 @@ module ActiveRecord private def connect + @connection.reconnect = true if @connection.respond_to?(:reconnect=) + encoding = @config[:encoding] if encoding @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil end + @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey] + @connection.real_connect(*@connection_options) + execute("SET NAMES '#{encoding}'") if encoding # By default, MySQL 'where id is null' selects the last inserted id. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 7dc7398b0a..88f703d813 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -47,6 +47,12 @@ module ActiveRecord end private + def extract_limit(sql_type) + return 8 if sql_type =~ /^bigint/i + return 2 if sql_type =~ /^smallint/i + super + end + # Extracts the scale from PostgreSQL-specific data types. def extract_scale(sql_type) # Money type has a fixed scale of 2. @@ -238,9 +244,26 @@ module ActiveRecord # * <tt>:min_messages</tt> - An optional client min messages that is used in a <tt>SET client_min_messages TO <min_messages></tt> call on the connection. # * <tt>:allow_concurrency</tt> - If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods. class PostgreSQLAdapter < AbstractAdapter + ADAPTER_NAME = 'PostgreSQL'.freeze + + NATIVE_DATABASE_TYPES = { + :primary_key => "serial primary key".freeze, + :string => { :name => "character varying", :limit => 255 }, + :text => { :name => "text" }, + :integer => { :name => "integer" }, + :float => { :name => "float" }, + :decimal => { :name => "decimal" }, + :datetime => { :name => "timestamp" }, + :timestamp => { :name => "timestamp" }, + :time => { :name => "time" }, + :date => { :name => "date" }, + :binary => { :name => "bytea" }, + :boolean => { :name => "boolean" } + } + # Returns 'PostgreSQL' as adapter name for identification purposes. def adapter_name - 'PostgreSQL' + ADAPTER_NAME end # Initializes and connects a PostgreSQL adapter. @@ -282,20 +305,7 @@ module ActiveRecord end def native_database_types #:nodoc: - { - :primary_key => "serial primary key", - :string => { :name => "character varying", :limit => 255 }, - :text => { :name => "text" }, - :integer => { :name => "integer" }, - :float => { :name => "float" }, - :decimal => { :name => "decimal" }, - :datetime => { :name => "timestamp" }, - :timestamp => { :name => "timestamp" }, - :time => { :name => "time" }, - :date => { :name => "date" }, - :binary => { :name => "bytea" }, - :boolean => { :name => "boolean" } - } + NATIVE_DATABASE_TYPES end # Does PostgreSQL support migrations? @@ -319,6 +329,10 @@ module ActiveRecord has_support end + def supports_insert_with_returning? + postgresql_version >= 80200 + end + # Returns the configured supported identifier length supported by PostgreSQL, # or report the default of 63 on PostgreSQL 7.x. def table_alias_length @@ -411,8 +425,34 @@ module ActiveRecord # Executes an INSERT query and returns the new record's ID def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) + # Extract the table from the insert sql. Yuck. table = sql.split(" ", 4)[2].gsub('"', '') - super || pk && last_insert_id(table, sequence_name || default_sequence_name(table, pk)) + + # Try an insert with 'returning id' if available (PG >= 8.2) + if supports_insert_with_returning? + pk, sequence_name = *pk_and_sequence_for(table) unless pk + if pk + id = select_value("#{sql} RETURNING #{quote_column_name(pk)}") + clear_query_cache + return id + end + end + + # Otherwise, insert then grab last_insert_id. + if insert_id = super + insert_id + else + # If neither pk nor sequence name is given, look them up. + unless pk || sequence_name + pk, sequence_name = *pk_and_sequence_for(table) + end + + # If a pk is given, fallback to default sequence name. + # Don't fetch last insert id for a table without a pk. + if pk && sequence_name ||= default_sequence_name(table, pk) + last_insert_id(table, sequence_name) + end + end end # create a 2D array representing the result set @@ -506,7 +546,7 @@ module ActiveRecord end end - execute "CREATE DATABASE #{name}#{option_string}" + execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}" end # Drops a PostgreSQL database @@ -514,7 +554,15 @@ module ActiveRecord # Example: # drop_database 'matt_development' def drop_database(name) #:nodoc: - execute "DROP DATABASE IF EXISTS #{name}" + if postgresql_version >= 80200 + execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}" + else + begin + execute "DROP DATABASE #{quote_table_name(name)}" + rescue ActiveRecord::StatementInvalid + @logger.warn "#{name} database doesn't exist." if @logger + end + end end @@ -676,7 +724,7 @@ module ActiveRecord # Renames a table. def rename_table(name, new_name) - execute "ALTER TABLE #{name} RENAME TO #{new_name}" + execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" end # Adds a new column to the named table. @@ -743,12 +791,10 @@ module ActiveRecord def type_to_sql(type, limit = nil, precision = nil, scale = nil) return super unless type.to_s == 'integer' - if limit.nil? || limit == 4 - 'integer' - elsif limit < 4 - 'smallint' - else - 'bigint' + case limit + when 1..2; 'smallint' + when 3..4, nil; 'integer' + when 5..8; 'bigint' end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index c4cbe5d52f..e19614e31f 100755 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -547,7 +547,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash) @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter @class_name = class_name || (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize) - @table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix + @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}" @table_name = class_name.table_name if class_name.respond_to?(:table_name) @connection = class_name.connection if class_name.respond_to?(:connection) read_fixture_files diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index c66034d18b..ff9899d032 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -68,6 +68,7 @@ module ActiveRecord def update_with_lock(attribute_names = @attributes.keys) #:nodoc: return update_without_lock(attribute_names) unless locking_enabled? + return 0 if attribute_names.empty? lock_col = self.class.locking_column previous_value = send(lock_col).to_i diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index b47b01e99a..e095b3c766 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -399,7 +399,10 @@ module ActiveRecord def run target = migrations.detect { |m| m.version == @target_version } raise UnknownMigrationVersionError.new(@target_version) if target.nil? - target.migrate(@direction) + unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i)) + target.migrate(@direction) + record_version_state_after_migrating(target.version) + end end def migrate diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index b0c8a8b815..eac61e9e43 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -82,6 +82,7 @@ module ActiveRecord # expected_options = { :conditions => { :colored => 'red' } } # assert_equal expected_options, Shirt.colored('red').proxy_options def named_scope(name, options = {}, &block) + name = name.to_sym scopes[name] = lambda do |parent_scope, *args| Scope.new(parent_scope, case options when Hash diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb index 6e55e36b7d..25e0e61c69 100644 --- a/activerecord/lib/active_record/observer.rb +++ b/activerecord/lib/active_record/observer.rb @@ -189,7 +189,7 @@ module ActiveRecord def add_observer!(klass) klass.add_observer(self) - klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find) + klass.class_eval 'def after_find() end' unless klass.method_defined?(:after_find) end end end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 52805ea851..8196442fe5 100755 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -277,7 +277,6 @@ module ActiveRecord base.class_eval do alias_method_chain :save, :validation alias_method_chain :save!, :validation - alias_method_chain :update_attribute, :validation_skipping end base.send :include, ActiveSupport::Callbacks @@ -914,14 +913,6 @@ module ActiveRecord end end - # Updates a single attribute and saves the record without going through the normal validation procedure. - # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method - # in Base is replaced with this when the validations module is mixed in, which it is by default. - def update_attribute_with_validation_skipping(name, value) - send(name.to_s + '=', value) - save(false) - end - # Runs +validate+ and +validate_on_create+ or +validate_on_update+ and returns true if no errors were added otherwise false. def valid? errors.clear diff --git a/activerecord/lib/active_record/vendor/db2.rb b/activerecord/lib/active_record/vendor/db2.rb deleted file mode 100644 index 812c8cc517..0000000000 --- a/activerecord/lib/active_record/vendor/db2.rb +++ /dev/null @@ -1,362 +0,0 @@ -require 'db2/db2cli.rb' - -module DB2 - module DB2Util - include DB2CLI - - def free() SQLFreeHandle(@handle_type, @handle); end - def handle() @handle; end - - def check_rc(rc) - if ![SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NO_DATA_FOUND].include?(rc) - rec = 1 - msg = '' - loop do - a = SQLGetDiagRec(@handle_type, @handle, rec, 500) - break if a[0] != SQL_SUCCESS - msg << a[3] if !a[3].nil? and a[3] != '' # Create message. - rec += 1 - end - raise "DB2 error: #{msg}" - end - end - end - - class Environment - include DB2Util - - def initialize - @handle_type = SQL_HANDLE_ENV - rc, @handle = SQLAllocHandle(@handle_type, SQL_NULL_HANDLE) - check_rc(rc) - end - - def data_sources(buffer_length = 1024) - retval = [] - max_buffer_length = buffer_length - - a = SQLDataSources(@handle, SQL_FETCH_FIRST, SQL_MAX_DSN_LENGTH + 1, buffer_length) - retval << [a[1], a[3]] - max_buffer_length = [max_buffer_length, a[4]].max - - loop do - a = SQLDataSources(@handle, SQL_FETCH_NEXT, SQL_MAX_DSN_LENGTH + 1, buffer_length) - break if a[0] == SQL_NO_DATA_FOUND - - retval << [a[1], a[3]] - max_buffer_length = [max_buffer_length, a[4]].max - end - - if max_buffer_length > buffer_length - get_data_sources(max_buffer_length) - else - retval - end - end - end - - class Connection - include DB2Util - - def initialize(environment) - @env = environment - @handle_type = SQL_HANDLE_DBC - rc, @handle = SQLAllocHandle(@handle_type, @env.handle) - check_rc(rc) - end - - def connect(server_name, user_name = '', auth = '') - check_rc(SQLConnect(@handle, server_name, user_name.to_s, auth.to_s)) - end - - def set_connect_attr(attr, value) - value += "\0" if value.class == String - check_rc(SQLSetConnectAttr(@handle, attr, value)) - end - - def set_auto_commit_on - set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON) - end - - def set_auto_commit_off - set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF) - end - - def disconnect - check_rc(SQLDisconnect(@handle)) - end - - def rollback - check_rc(SQLEndTran(@handle_type, @handle, SQL_ROLLBACK)) - end - - def commit - check_rc(SQLEndTran(@handle_type, @handle, SQL_COMMIT)) - end - end - - class Statement - include DB2Util - - def initialize(connection) - @conn = connection - @handle_type = SQL_HANDLE_STMT - @parms = [] #yun - @sql = '' #yun - @numParms = 0 #yun - @prepared = false #yun - @parmArray = [] #yun. attributes of the parameter markers - rc, @handle = SQLAllocHandle(@handle_type, @conn.handle) - check_rc(rc) - end - - def columns(table_name, schema_name = '%') - check_rc(SQLColumns(@handle, '', schema_name.upcase, table_name.upcase, '%')) - fetch_all - end - - def tables(schema_name = '%') - check_rc(SQLTables(@handle, '', schema_name.upcase, '%', 'TABLE')) - fetch_all - end - - def indexes(table_name, schema_name = '') - check_rc(SQLStatistics(@handle, '', schema_name.upcase, table_name.upcase, SQL_INDEX_ALL, SQL_ENSURE)) - fetch_all - end - - def prepare(sql) - @sql = sql - check_rc(SQLPrepare(@handle, sql)) - rc, @numParms = SQLNumParams(@handle) #number of question marks - check_rc(rc) - #-------------------------------------------------------------------------- - # parameter attributes are stored in instance variable @parmArray so that - # they are available when execute method is called. - #-------------------------------------------------------------------------- - if @numParms > 0 # get parameter marker attributes - 1.upto(@numParms) do |i| # parameter number starts from 1 - rc, type, size, decimalDigits = SQLDescribeParam(@handle, i) - check_rc(rc) - @parmArray << Parameter.new(type, size, decimalDigits) - end - end - @prepared = true - self - end - - def execute(*parms) - raise "The statement was not prepared" if @prepared == false - - if parms.size == 1 and parms[0].class == Array - parms = parms[0] - end - - if @numParms != parms.size - raise "Number of parameters supplied does not match with the SQL statement" - end - - if @numParms > 0 #need to bind parameters - #-------------------------------------------------------------------- - #calling bindParms may not be safe. Look comment below. - #-------------------------------------------------------------------- - #bindParms(parms) - - valueArray = [] - 1.upto(@numParms) do |i| # parameter number starts from 1 - type = @parmArray[i - 1].class - size = @parmArray[i - 1].size - decimalDigits = @parmArray[i - 1].decimalDigits - - if parms[i - 1].class == String - valueArray << parms[i - 1] - else - valueArray << parms[i - 1].to_s - end - - rc = SQLBindParameter(@handle, i, type, size, decimalDigits, valueArray[i - 1]) - check_rc(rc) - end - end - - check_rc(SQLExecute(@handle)) - - if @numParms != 0 - check_rc(SQLFreeStmt(@handle, SQL_RESET_PARAMS)) # Reset parameters - end - - self - end - - #------------------------------------------------------------------------------- - # The last argument(value) to SQLBindParameter is a deferred argument, that is, - # it should be available when SQLExecute is called. Even though "value" is - # local to bindParms method, it seems that it is available when SQLExecute - # is called. I am not sure whether it would still work if garbage collection - # is done between bindParms call and SQLExecute call inside the execute method - # above. - #------------------------------------------------------------------------------- - def bindParms(parms) # This is the real thing. It uses SQLBindParms - 1.upto(@numParms) do |i| # parameter number starts from 1 - rc, dataType, parmSize, decimalDigits = SQLDescribeParam(@handle, i) - check_rc(rc) - if parms[i - 1].class == String - value = parms[i - 1] - else - value = parms[i - 1].to_s - end - rc = SQLBindParameter(@handle, i, dataType, parmSize, decimalDigits, value) - check_rc(rc) - end - end - - #------------------------------------------------------------------------------ - # bind method does not use DB2's SQLBindParams, but replaces "?" in the - # SQL statement with the value before passing the SQL statement to DB2. - # It is not efficient and can handle only strings since it puts everything in - # quotes. - #------------------------------------------------------------------------------ - def bind(sql, args) #does not use SQLBindParams - arg_index = 0 - result = "" - tokens(sql).each do |part| - case part - when '?' - result << "'" + (args[arg_index]) + "'" #put it into quotes - arg_index += 1 - when '??' - result << "?" - else - result << part - end - end - if arg_index < args.size - raise "Too many SQL parameters" - elsif arg_index > args.size - raise "Not enough SQL parameters" - end - result - end - - ## Break the sql string into parts. - # - # This is NOT a full lexer for SQL. It just breaks up the SQL - # string enough so that question marks, double question marks and - # quoted strings are separated. This is used when binding - # arguments to "?" in the SQL string. Note: comments are not - # handled. - # - def tokens(sql) - toks = sql.scan(/('([^'\\]|''|\\.)*'|"([^"\\]|""|\\.)*"|\?\??|[^'"?]+)/) - toks.collect { |t| t[0] } - end - - def exec_direct(sql) - check_rc(SQLExecDirect(@handle, sql)) - self - end - - def set_cursor_name(name) - check_rc(SQLSetCursorName(@handle, name)) - self - end - - def get_cursor_name - rc, name = SQLGetCursorName(@handle) - check_rc(rc) - name - end - - def row_count - rc, rowcount = SQLRowCount(@handle) - check_rc(rc) - rowcount - end - - def num_result_cols - rc, cols = SQLNumResultCols(@handle) - check_rc(rc) - cols - end - - def fetch_all - if block_given? - while row = fetch do - yield row - end - else - res = [] - while row = fetch do - res << row - end - res - end - end - - def fetch - cols = get_col_desc - rc = SQLFetch(@handle) - if rc == SQL_NO_DATA_FOUND - SQLFreeStmt(@handle, SQL_CLOSE) # Close cursor - SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters - return nil - end - raise "ERROR" unless rc == SQL_SUCCESS - - retval = [] - cols.each_with_index do |c, i| - rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2] - retval << adjust_content(content) - end - retval - end - - def fetch_as_hash - cols = get_col_desc - rc = SQLFetch(@handle) - if rc == SQL_NO_DATA_FOUND - SQLFreeStmt(@handle, SQL_CLOSE) # Close cursor - SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters - return nil - end - raise "ERROR" unless rc == SQL_SUCCESS - - retval = {} - cols.each_with_index do |c, i| - rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2] - retval[c[0]] = adjust_content(content) - end - retval - end - - def get_col_desc - rc, nr_cols = SQLNumResultCols(@handle) - cols = (1..nr_cols).collect do |c| - rc, name, bl, type, col_sz = SQLDescribeCol(@handle, c, 1024) - [name.downcase, type, col_sz] - end - end - - def adjust_content(c) - case c.class.to_s - when 'DB2CLI::NullClass' - return nil - when 'DB2CLI::Time' - "%02d:%02d:%02d" % [c.hour, c.minute, c.second] - when 'DB2CLI::Date' - "%04d-%02d-%02d" % [c.year, c.month, c.day] - when 'DB2CLI::Timestamp' - "%04d-%02d-%02d %02d:%02d:%02d" % [c.year, c.month, c.day, c.hour, c.minute, c.second] - else - return c - end - end - end - - class Parameter - attr_reader :type, :size, :decimalDigits - def initialize(type, size, decimalDigits) - @type, @size, @decimalDigits = type, size, decimalDigits - end - end -end diff --git a/activerecord/lib/active_record/vendor/mysql.rb b/activerecord/lib/active_record/vendor/mysql.rb deleted file mode 100644 index 1c3294c719..0000000000 --- a/activerecord/lib/active_record/vendor/mysql.rb +++ /dev/null @@ -1,1214 +0,0 @@ -# $Id: mysql.rb,v 1.24 2005/02/12 11:37:15 tommy Exp $ -# -# Copyright (C) 2003-2005 TOMITA Masahiro -# tommy@tmtm.org -# - -class Mysql - - VERSION = "4.0-ruby-0.2.6-plus-changes" - - require "socket" - require "digest/sha1" - - MAX_PACKET_LENGTH = 256*256*256-1 - MAX_ALLOWED_PACKET = 1024*1024*1024 - - MYSQL_UNIX_ADDR = "/tmp/mysql.sock" - MYSQL_PORT = 3306 - PROTOCOL_VERSION = 10 - - SCRAMBLE_LENGTH = 20 - SCRAMBLE_LENGTH_323 = 8 - - # Command - COM_SLEEP = 0 - COM_QUIT = 1 - COM_INIT_DB = 2 - COM_QUERY = 3 - COM_FIELD_LIST = 4 - COM_CREATE_DB = 5 - COM_DROP_DB = 6 - COM_REFRESH = 7 - COM_SHUTDOWN = 8 - COM_STATISTICS = 9 - COM_PROCESS_INFO = 10 - COM_CONNECT = 11 - COM_PROCESS_KILL = 12 - COM_DEBUG = 13 - COM_PING = 14 - COM_TIME = 15 - COM_DELAYED_INSERT = 16 - COM_CHANGE_USER = 17 - COM_BINLOG_DUMP = 18 - COM_TABLE_DUMP = 19 - COM_CONNECT_OUT = 20 - COM_REGISTER_SLAVE = 21 - - # Client flag - CLIENT_LONG_PASSWORD = 1 - CLIENT_FOUND_ROWS = 1 << 1 - CLIENT_LONG_FLAG = 1 << 2 - CLIENT_CONNECT_WITH_DB= 1 << 3 - CLIENT_NO_SCHEMA = 1 << 4 - CLIENT_COMPRESS = 1 << 5 - CLIENT_ODBC = 1 << 6 - CLIENT_LOCAL_FILES = 1 << 7 - CLIENT_IGNORE_SPACE = 1 << 8 - CLIENT_PROTOCOL_41 = 1 << 9 - CLIENT_INTERACTIVE = 1 << 10 - CLIENT_SSL = 1 << 11 - CLIENT_IGNORE_SIGPIPE = 1 << 12 - CLIENT_TRANSACTIONS = 1 << 13 - CLIENT_RESERVED = 1 << 14 - CLIENT_SECURE_CONNECTION = 1 << 15 - CLIENT_CAPABILITIES = CLIENT_LONG_PASSWORD|CLIENT_LONG_FLAG|CLIENT_TRANSACTIONS - PROTO_AUTH41 = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION - - # Connection Option - OPT_CONNECT_TIMEOUT = 0 - OPT_COMPRESS = 1 - OPT_NAMED_PIPE = 2 - INIT_COMMAND = 3 - READ_DEFAULT_FILE = 4 - READ_DEFAULT_GROUP = 5 - SET_CHARSET_DIR = 6 - SET_CHARSET_NAME = 7 - OPT_LOCAL_INFILE = 8 - - # Server Status - SERVER_STATUS_IN_TRANS = 1 - SERVER_STATUS_AUTOCOMMIT = 2 - - # Refresh parameter - REFRESH_GRANT = 1 - REFRESH_LOG = 2 - REFRESH_TABLES = 4 - REFRESH_HOSTS = 8 - REFRESH_STATUS = 16 - REFRESH_THREADS = 32 - REFRESH_SLAVE = 64 - REFRESH_MASTER = 128 - - def initialize(*args) - @client_flag = 0 - @max_allowed_packet = MAX_ALLOWED_PACKET - @query_with_result = true - @status = :STATUS_READY - if args[0] != :INIT then - real_connect(*args) - end - end - - def real_connect(host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=nil) - @server_status = SERVER_STATUS_AUTOCOMMIT - if (host == nil or host == "localhost") and defined? UNIXSocket then - unix_socket = socket || ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_ADDR - sock = UNIXSocket::new(unix_socket) - @host_info = Error::err(Error::CR_LOCALHOST_CONNECTION) - @unix_socket = unix_socket - else - sock = TCPSocket::new(host, port||ENV["MYSQL_TCP_PORT"]||(Socket::getservbyname("mysql","tcp") rescue MYSQL_PORT)) - @host_info = sprintf Error::err(Error::CR_TCP_CONNECTION), host - end - @host = host ? host.dup : nil - sock.setsockopt Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true - @net = Net::new sock - - a = read - @protocol_version = a.slice!(0) - @server_version, a = a.split(/\0/,2) - @thread_id, @scramble_buff = a.slice!(0,13).unpack("La8") - if a.size >= 2 then - @server_capabilities, = a.slice!(0,2).unpack("v") - end - if a.size >= 16 then - @server_language, @server_status = a.slice!(0,3).unpack("cv") - end - - flag = 0 if flag == nil - flag |= @client_flag | CLIENT_CAPABILITIES - flag |= CLIENT_CONNECT_WITH_DB if db - - @pre_411 = (0 == @server_capabilities & PROTO_AUTH41) - if @pre_411 - data = Net::int2str(flag)+Net::int3str(@max_allowed_packet)+ - (user||"")+"\0"+ - scramble(passwd, @scramble_buff, @protocol_version==9) - else - dummy, @salt2 = a.unpack("a13a12") - @scramble_buff += @salt2 - flag |= PROTO_AUTH41 - data = Net::int4str(flag) + Net::int4str(@max_allowed_packet) + - ([8] + Array.new(23, 0)).pack("c24") + (user||"")+"\0"+ - scramble41(passwd, @scramble_buff) - end - - if db and @server_capabilities & CLIENT_CONNECT_WITH_DB != 0 - data << "\0" if @pre_411 - data << db - @db = db.dup - end - write data - pkt = read - handle_auth_fallback(pkt, passwd) - ObjectSpace.define_finalizer(self, Mysql.finalizer(@net)) - self - end - alias :connect :real_connect - - def handle_auth_fallback(pkt, passwd) - # A packet like this means that we need to send an old-format password - if pkt.size == 1 and pkt[0] == 254 and - @server_capabilities & CLIENT_SECURE_CONNECTION != 0 then - data = scramble(passwd, @scramble_buff, @protocol_version == 9) - write data + "\0" - read - end - end - - def escape_string(str) - Mysql::escape_string str - end - alias :quote :escape_string - - def get_client_info() - VERSION - end - alias :client_info :get_client_info - - def options(option, arg=nil) - if option == OPT_LOCAL_INFILE then - if arg == false or arg == 0 then - @client_flag &= ~CLIENT_LOCAL_FILES - else - @client_flag |= CLIENT_LOCAL_FILES - end - else - raise "not implemented" - end - end - - def real_query(query) - command COM_QUERY, query, true - read_query_result - self - end - - def use_result() - if @status != :STATUS_GET_RESULT then - error Error::CR_COMMANDS_OUT_OF_SYNC - end - res = Result::new self, @fields, @field_count - @status = :STATUS_USE_RESULT - res - end - - def store_result() - if @status != :STATUS_GET_RESULT then - error Error::CR_COMMANDS_OUT_OF_SYNC - end - @status = :STATUS_READY - data = read_rows @field_count - res = Result::new self, @fields, @field_count, data - @fields = nil - @affected_rows = data.length - res - end - - def change_user(user="", passwd="", db="") - if @pre_411 - data = user+"\0"+scramble(passwd, @scramble_buff, @protocol_version==9)+"\0"+db - else - data = user+"\0"+scramble41(passwd, @scramble_buff)+db - end - pkt = command COM_CHANGE_USER, data - handle_auth_fallback(pkt, passwd) - @user = user - @passwd = passwd - @db = db - end - - def character_set_name() - raise "not implemented" - end - - def close() - @status = :STATUS_READY - command COM_QUIT, nil, true - @net.close - self - end - - def create_db(db) - command COM_CREATE_DB, db - self - end - - def drop_db(db) - command COM_DROP_DB, db - self - end - - def dump_debug_info() - command COM_DEBUG - self - end - - def get_host_info() - @host_info - end - alias :host_info :get_host_info - - def get_proto_info() - @protocol_version - end - alias :proto_info :get_proto_info - - def get_server_info() - @server_version - end - alias :server_info :get_server_info - - def kill(id) - command COM_PROCESS_KILL, Net::int4str(id) - self - end - - def list_dbs(db=nil) - real_query "show databases #{db}" - @status = :STATUS_READY - read_rows(1).flatten - end - - def list_fields(table, field=nil) - command COM_FIELD_LIST, "#{table}\0#{field}", true - if @pre_411 - f = read_rows 6 - else - f = read_rows 7 - end - fields = unpack_fields(f, @server_capabilities & CLIENT_LONG_FLAG != 0) - res = Result::new self, fields, f.length - res.eof = true - res - end - - def list_processes() - data = command COM_PROCESS_INFO - @field_count = get_length data - if @pre_411 - fields = read_rows 5 - else - fields = read_rows 7 - end - @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0) - @status = :STATUS_GET_RESULT - store_result - end - - def list_tables(table=nil) - real_query "show tables #{table}" - @status = :STATUS_READY - read_rows(1).flatten - end - - def ping() - command COM_PING - self - end - - def query(query) - real_query query - if not @query_with_result then - return self - end - if @field_count == 0 then - return nil - end - store_result - end - - def refresh(r) - command COM_REFRESH, r.chr - self - end - - def reload() - refresh REFRESH_GRANT - self - end - - def select_db(db) - command COM_INIT_DB, db - @db = db - self - end - - def shutdown() - command COM_SHUTDOWN - self - end - - def stat() - command COM_STATISTICS - end - - attr_reader :info, :insert_id, :affected_rows, :field_count, :thread_id - attr_accessor :query_with_result, :status - - def read_one_row(field_count) - data = read - if data[0] == 254 and data.length == 1 ## EOF - return - elsif data[0] == 254 and data.length == 5 - return - end - rec = [] - field_count.times do - len = get_length data - if len == nil then - rec << len - else - rec << data.slice!(0,len) - end - end - rec - end - - def skip_result() - if @status == :STATUS_USE_RESULT then - loop do - data = read - break if data[0] == 254 and data.length == 1 - end - @status = :STATUS_READY - end - end - - def inspect() - "#<#{self.class}>" - end - - private - - def read_query_result() - data = read - @field_count = get_length(data) - if @field_count == nil then # LOAD DATA LOCAL INFILE - File::open(data) do |f| - write f.read - end - write "" # mark EOF - data = read - @field_count = get_length(data) - end - if @field_count == 0 then - @affected_rows = get_length(data, true) - @insert_id = get_length(data, true) - if @server_capabilities & CLIENT_TRANSACTIONS != 0 then - a = data.slice!(0,2) - @server_status = a[0]+a[1]*256 - end - if data.size > 0 and get_length(data) then - @info = data - end - else - @extra_info = get_length(data, true) - if @pre_411 - fields = read_rows(5) - else - fields = read_rows(7) - end - @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0) - @status = :STATUS_GET_RESULT - end - self - end - - def unpack_fields(data, long_flag_protocol) - ret = [] - data.each do |f| - if @pre_411 - table = org_table = f[0] - name = f[1] - length = f[2][0]+f[2][1]*256+f[2][2]*256*256 - type = f[3][0] - if long_flag_protocol then - flags = f[4][0]+f[4][1]*256 - decimals = f[4][2] - else - flags = f[4][0] - decimals = f[4][1] - end - def_value = f[5] - max_length = 0 - else - catalog = f[0] - db = f[1] - table = f[2] - org_table = f[3] - name = f[4] - org_name = f[5] - length = f[6][2]+f[6][3]*256+f[6][4]*256*256 - type = f[6][6] - flags = f[6][7]+f[6][8]*256 - decimals = f[6][9] - def_value = "" - max_length = 0 - end - ret << Field::new(table, org_table, name, length, type, flags, decimals, def_value, max_length) - end - ret - end - - def read_rows(field_count) - ret = [] - while rec = read_one_row(field_count) do - ret << rec - end - ret - end - - def get_length(data, longlong=nil) - return if data.length == 0 - c = data.slice!(0) - case c - when 251 - return nil - when 252 - a = data.slice!(0,2) - return a[0]+a[1]*256 - when 253 - a = data.slice!(0,3) - return a[0]+a[1]*256+a[2]*256**2 - when 254 - a = data.slice!(0,8) - if longlong then - return a[0]+a[1]*256+a[2]*256**2+a[3]*256**3+ - a[4]*256**4+a[5]*256**5+a[6]*256**6+a[7]*256**7 - else - return a[0]+a[1]*256+a[2]*256**2+a[3]*256**3 - end - else - c - end - end - - def command(cmd, arg=nil, skip_check=nil) - unless @net then - error Error::CR_SERVER_GONE_ERROR - end - if @status != :STATUS_READY then - error Error::CR_COMMANDS_OUT_OF_SYNC - end - @net.clear - write cmd.chr+(arg||"") - read unless skip_check - end - - def read() - unless @net then - error Error::CR_SERVER_GONE_ERROR - end - a = @net.read - if a[0] == 255 then - if a.length > 3 then - @errno = a[1]+a[2]*256 - @error = a[3 .. -1] - else - @errno = Error::CR_UNKNOWN_ERROR - @error = Error::err @errno - end - raise Error::new(@errno, @error) - end - a - end - - def write(arg) - unless @net then - error Error::CR_SERVER_GONE_ERROR - end - @net.write arg - end - - def hash_password(password) - nr = 1345345333 - add = 7 - nr2 = 0x12345671 - password.each_byte do |i| - next if i == 0x20 or i == 9 - nr ^= (((nr & 63) + add) * i) + (nr << 8) - nr2 += (nr2 << 8) ^ nr - add += i - end - [nr & ((1 << 31) - 1), nr2 & ((1 << 31) - 1)] - end - - def scramble(password, message, old_ver) - return "" if password == nil or password == "" - raise "old version password is not implemented" if old_ver - hash_pass = hash_password password - hash_message = hash_password message.slice(0,SCRAMBLE_LENGTH_323) - rnd = Random::new hash_pass[0] ^ hash_message[0], hash_pass[1] ^ hash_message[1] - to = [] - 1.upto(SCRAMBLE_LENGTH_323) do - to << ((rnd.rnd*31)+64).floor - end - extra = (rnd.rnd*31).floor - to.map! do |t| (t ^ extra).chr end - to.join - end - - def scramble41(password, message) - return 0x00.chr if password.nil? or password.empty? - buf = [0x14] - s1 = Digest::SHA1.digest(password) - s2 = Digest::SHA1.digest(s1) - x = Digest::SHA1.digest(message + s2) - (0..s1.length - 1).each {|i| buf.push(s1[i] ^ x[i])} - buf.pack("C*") - end - - def error(errno) - @errno = errno - @error = Error::err errno - raise Error::new(@errno, @error) - end - - class Result - def initialize(mysql, fields, field_count, data=nil) - @handle = mysql - @fields = fields - @field_count = field_count - @data = data - @current_field = 0 - @current_row = 0 - @eof = false - @row_count = 0 - end - attr_accessor :eof - - def data_seek(n) - @current_row = n - end - - def fetch_field() - return if @current_field >= @field_count - f = @fields[@current_field] - @current_field += 1 - f - end - - def fetch_fields() - @fields - end - - def fetch_field_direct(n) - @fields[n] - end - - def fetch_lengths() - @data ? @data[@current_row].map{|i| i ? i.length : 0} : @lengths - end - - def fetch_row() - if @data then - if @current_row >= @data.length then - @handle.status = :STATUS_READY - return - end - ret = @data[@current_row] - @current_row += 1 - else - return if @eof - ret = @handle.read_one_row @field_count - if ret == nil then - @eof = true - return - end - @lengths = ret.map{|i| i ? i.length : 0} - @row_count += 1 - end - ret - end - - def fetch_hash(with_table=nil) - row = fetch_row - return if row == nil - hash = {} - @fields.each_index do |i| - f = with_table ? @fields[i].table+"."+@fields[i].name : @fields[i].name - hash[f] = row[i] - end - hash - end - - def field_seek(n) - @current_field = n - end - - def field_tell() - @current_field - end - - def free() - @handle.skip_result - @handle = @fields = @data = nil - end - - def num_fields() - @field_count - end - - def num_rows() - @data ? @data.length : @row_count - end - - def row_seek(n) - @current_row = n - end - - def row_tell() - @current_row - end - - def each() - while row = fetch_row do - yield row - end - end - - def each_hash(with_table=nil) - while hash = fetch_hash(with_table) do - yield hash - end - end - - def inspect() - "#<#{self.class}>" - end - - end - - class Field - # Field type - TYPE_DECIMAL = 0 - TYPE_TINY = 1 - TYPE_SHORT = 2 - TYPE_LONG = 3 - TYPE_FLOAT = 4 - TYPE_DOUBLE = 5 - TYPE_NULL = 6 - TYPE_TIMESTAMP = 7 - TYPE_LONGLONG = 8 - TYPE_INT24 = 9 - TYPE_DATE = 10 - TYPE_TIME = 11 - TYPE_DATETIME = 12 - TYPE_YEAR = 13 - TYPE_NEWDATE = 14 - TYPE_ENUM = 247 - TYPE_SET = 248 - TYPE_TINY_BLOB = 249 - TYPE_MEDIUM_BLOB = 250 - TYPE_LONG_BLOB = 251 - TYPE_BLOB = 252 - TYPE_VAR_STRING = 253 - TYPE_STRING = 254 - TYPE_GEOMETRY = 255 - TYPE_CHAR = TYPE_TINY - TYPE_INTERVAL = TYPE_ENUM - - # Flag - NOT_NULL_FLAG = 1 - PRI_KEY_FLAG = 2 - UNIQUE_KEY_FLAG = 4 - MULTIPLE_KEY_FLAG = 8 - BLOB_FLAG = 16 - UNSIGNED_FLAG = 32 - ZEROFILL_FLAG = 64 - BINARY_FLAG = 128 - ENUM_FLAG = 256 - AUTO_INCREMENT_FLAG = 512 - TIMESTAMP_FLAG = 1024 - SET_FLAG = 2048 - NUM_FLAG = 32768 - PART_KEY_FLAG = 16384 - GROUP_FLAG = 32768 - UNIQUE_FLAG = 65536 - - def initialize(table, org_table, name, length, type, flags, decimals, def_value, max_length) - @table = table - @org_table = org_table - @name = name - @length = length - @type = type - @flags = flags - @decimals = decimals - @def = def_value - @max_length = max_length - if (type <= TYPE_INT24 and (type != TYPE_TIMESTAMP or length == 14 or length == 8)) or type == TYPE_YEAR then - @flags |= NUM_FLAG - end - end - attr_reader :table, :org_table, :name, :length, :type, :flags, :decimals, :def, :max_length - - def inspect() - "#<#{self.class}:#{@name}>" - end - end - - class Error < StandardError - # Server Error - ER_HASHCHK = 1000 - ER_NISAMCHK = 1001 - ER_NO = 1002 - ER_YES = 1003 - ER_CANT_CREATE_FILE = 1004 - ER_CANT_CREATE_TABLE = 1005 - ER_CANT_CREATE_DB = 1006 - ER_DB_CREATE_EXISTS = 1007 - ER_DB_DROP_EXISTS = 1008 - ER_DB_DROP_DELETE = 1009 - ER_DB_DROP_RMDIR = 1010 - ER_CANT_DELETE_FILE = 1011 - ER_CANT_FIND_SYSTEM_REC = 1012 - ER_CANT_GET_STAT = 1013 - ER_CANT_GET_WD = 1014 - ER_CANT_LOCK = 1015 - ER_CANT_OPEN_FILE = 1016 - ER_FILE_NOT_FOUND = 1017 - ER_CANT_READ_DIR = 1018 - ER_CANT_SET_WD = 1019 - ER_CHECKREAD = 1020 - ER_DISK_FULL = 1021 - ER_DUP_KEY = 1022 - ER_ERROR_ON_CLOSE = 1023 - ER_ERROR_ON_READ = 1024 - ER_ERROR_ON_RENAME = 1025 - ER_ERROR_ON_WRITE = 1026 - ER_FILE_USED = 1027 - ER_FILSORT_ABORT = 1028 - ER_FORM_NOT_FOUND = 1029 - ER_GET_ERRNO = 1030 - ER_ILLEGAL_HA = 1031 - ER_KEY_NOT_FOUND = 1032 - ER_NOT_FORM_FILE = 1033 - ER_NOT_KEYFILE = 1034 - ER_OLD_KEYFILE = 1035 - ER_OPEN_AS_READONLY = 1036 - ER_OUTOFMEMORY = 1037 - ER_OUT_OF_SORTMEMORY = 1038 - ER_UNEXPECTED_EOF = 1039 - ER_CON_COUNT_ERROR = 1040 - ER_OUT_OF_RESOURCES = 1041 - ER_BAD_HOST_ERROR = 1042 - ER_HANDSHAKE_ERROR = 1043 - ER_DBACCESS_DENIED_ERROR = 1044 - ER_ACCESS_DENIED_ERROR = 1045 - ER_NO_DB_ERROR = 1046 - ER_UNKNOWN_COM_ERROR = 1047 - ER_BAD_NULL_ERROR = 1048 - ER_BAD_DB_ERROR = 1049 - ER_TABLE_EXISTS_ERROR = 1050 - ER_BAD_TABLE_ERROR = 1051 - ER_NON_UNIQ_ERROR = 1052 - ER_SERVER_SHUTDOWN = 1053 - ER_BAD_FIELD_ERROR = 1054 - ER_WRONG_FIELD_WITH_GROUP = 1055 - ER_WRONG_GROUP_FIELD = 1056 - ER_WRONG_SUM_SELECT = 1057 - ER_WRONG_VALUE_COUNT = 1058 - ER_TOO_LONG_IDENT = 1059 - ER_DUP_FIELDNAME = 1060 - ER_DUP_KEYNAME = 1061 - ER_DUP_ENTRY = 1062 - ER_WRONG_FIELD_SPEC = 1063 - ER_PARSE_ERROR = 1064 - ER_EMPTY_QUERY = 1065 - ER_NONUNIQ_TABLE = 1066 - ER_INVALID_DEFAULT = 1067 - ER_MULTIPLE_PRI_KEY = 1068 - ER_TOO_MANY_KEYS = 1069 - ER_TOO_MANY_KEY_PARTS = 1070 - ER_TOO_LONG_KEY = 1071 - ER_KEY_COLUMN_DOES_NOT_EXITS = 1072 - ER_BLOB_USED_AS_KEY = 1073 - ER_TOO_BIG_FIELDLENGTH = 1074 - ER_WRONG_AUTO_KEY = 1075 - ER_READY = 1076 - ER_NORMAL_SHUTDOWN = 1077 - ER_GOT_SIGNAL = 1078 - ER_SHUTDOWN_COMPLETE = 1079 - ER_FORCING_CLOSE = 1080 - ER_IPSOCK_ERROR = 1081 - ER_NO_SUCH_INDEX = 1082 - ER_WRONG_FIELD_TERMINATORS = 1083 - ER_BLOBS_AND_NO_TERMINATED = 1084 - ER_TEXTFILE_NOT_READABLE = 1085 - ER_FILE_EXISTS_ERROR = 1086 - ER_LOAD_INFO = 1087 - ER_ALTER_INFO = 1088 - ER_WRONG_SUB_KEY = 1089 - ER_CANT_REMOVE_ALL_FIELDS = 1090 - ER_CANT_DROP_FIELD_OR_KEY = 1091 - ER_INSERT_INFO = 1092 - ER_INSERT_TABLE_USED = 1093 - ER_NO_SUCH_THREAD = 1094 - ER_KILL_DENIED_ERROR = 1095 - ER_NO_TABLES_USED = 1096 - ER_TOO_BIG_SET = 1097 - ER_NO_UNIQUE_LOGFILE = 1098 - ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099 - ER_TABLE_NOT_LOCKED = 1100 - ER_BLOB_CANT_HAVE_DEFAULT = 1101 - ER_WRONG_DB_NAME = 1102 - ER_WRONG_TABLE_NAME = 1103 - ER_TOO_BIG_SELECT = 1104 - ER_UNKNOWN_ERROR = 1105 - ER_UNKNOWN_PROCEDURE = 1106 - ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107 - ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108 - ER_UNKNOWN_TABLE = 1109 - ER_FIELD_SPECIFIED_TWICE = 1110 - ER_INVALID_GROUP_FUNC_USE = 1111 - ER_UNSUPPORTED_EXTENSION = 1112 - ER_TABLE_MUST_HAVE_COLUMNS = 1113 - ER_RECORD_FILE_FULL = 1114 - ER_UNKNOWN_CHARACTER_SET = 1115 - ER_TOO_MANY_TABLES = 1116 - ER_TOO_MANY_FIELDS = 1117 - ER_TOO_BIG_ROWSIZE = 1118 - ER_STACK_OVERRUN = 1119 - ER_WRONG_OUTER_JOIN = 1120 - ER_NULL_COLUMN_IN_INDEX = 1121 - ER_CANT_FIND_UDF = 1122 - ER_CANT_INITIALIZE_UDF = 1123 - ER_UDF_NO_PATHS = 1124 - ER_UDF_EXISTS = 1125 - ER_CANT_OPEN_LIBRARY = 1126 - ER_CANT_FIND_DL_ENTRY = 1127 - ER_FUNCTION_NOT_DEFINED = 1128 - ER_HOST_IS_BLOCKED = 1129 - ER_HOST_NOT_PRIVILEGED = 1130 - ER_PASSWORD_ANONYMOUS_USER = 1131 - ER_PASSWORD_NOT_ALLOWED = 1132 - ER_PASSWORD_NO_MATCH = 1133 - ER_UPDATE_INFO = 1134 - ER_CANT_CREATE_THREAD = 1135 - ER_WRONG_VALUE_COUNT_ON_ROW = 1136 - ER_CANT_REOPEN_TABLE = 1137 - ER_INVALID_USE_OF_NULL = 1138 - ER_REGEXP_ERROR = 1139 - ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140 - ER_NONEXISTING_GRANT = 1141 - ER_TABLEACCESS_DENIED_ERROR = 1142 - ER_COLUMNACCESS_DENIED_ERROR = 1143 - ER_ILLEGAL_GRANT_FOR_TABLE = 1144 - ER_GRANT_WRONG_HOST_OR_USER = 1145 - ER_NO_SUCH_TABLE = 1146 - ER_NONEXISTING_TABLE_GRANT = 1147 - ER_NOT_ALLOWED_COMMAND = 1148 - ER_SYNTAX_ERROR = 1149 - ER_DELAYED_CANT_CHANGE_LOCK = 1150 - ER_TOO_MANY_DELAYED_THREADS = 1151 - ER_ABORTING_CONNECTION = 1152 - ER_NET_PACKET_TOO_LARGE = 1153 - ER_NET_READ_ERROR_FROM_PIPE = 1154 - ER_NET_FCNTL_ERROR = 1155 - ER_NET_PACKETS_OUT_OF_ORDER = 1156 - ER_NET_UNCOMPRESS_ERROR = 1157 - ER_NET_READ_ERROR = 1158 - ER_NET_READ_INTERRUPTED = 1159 - ER_NET_ERROR_ON_WRITE = 1160 - ER_NET_WRITE_INTERRUPTED = 1161 - ER_TOO_LONG_STRING = 1162 - ER_TABLE_CANT_HANDLE_BLOB = 1163 - ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164 - ER_DELAYED_INSERT_TABLE_LOCKED = 1165 - ER_WRONG_COLUMN_NAME = 1166 - ER_WRONG_KEY_COLUMN = 1167 - ER_WRONG_MRG_TABLE = 1168 - ER_DUP_UNIQUE = 1169 - ER_BLOB_KEY_WITHOUT_LENGTH = 1170 - ER_PRIMARY_CANT_HAVE_NULL = 1171 - ER_TOO_MANY_ROWS = 1172 - ER_REQUIRES_PRIMARY_KEY = 1173 - ER_NO_RAID_COMPILED = 1174 - ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175 - ER_KEY_DOES_NOT_EXITS = 1176 - ER_CHECK_NO_SUCH_TABLE = 1177 - ER_CHECK_NOT_IMPLEMENTED = 1178 - ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179 - ER_ERROR_DURING_COMMIT = 1180 - ER_ERROR_DURING_ROLLBACK = 1181 - ER_ERROR_DURING_FLUSH_LOGS = 1182 - ER_ERROR_DURING_CHECKPOINT = 1183 - ER_NEW_ABORTING_CONNECTION = 1184 - ER_DUMP_NOT_IMPLEMENTED = 1185 - ER_FLUSH_MASTER_BINLOG_CLOSED = 1186 - ER_INDEX_REBUILD = 1187 - ER_MASTER = 1188 - ER_MASTER_NET_READ = 1189 - ER_MASTER_NET_WRITE = 1190 - ER_FT_MATCHING_KEY_NOT_FOUND = 1191 - ER_LOCK_OR_ACTIVE_TRANSACTION = 1192 - ER_UNKNOWN_SYSTEM_VARIABLE = 1193 - ER_CRASHED_ON_USAGE = 1194 - ER_CRASHED_ON_REPAIR = 1195 - ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196 - ER_TRANS_CACHE_FULL = 1197 - ER_SLAVE_MUST_STOP = 1198 - ER_SLAVE_NOT_RUNNING = 1199 - ER_BAD_SLAVE = 1200 - ER_MASTER_INFO = 1201 - ER_SLAVE_THREAD = 1202 - ER_TOO_MANY_USER_CONNECTIONS = 1203 - ER_SET_CONSTANTS_ONLY = 1204 - ER_LOCK_WAIT_TIMEOUT = 1205 - ER_LOCK_TABLE_FULL = 1206 - ER_READ_ONLY_TRANSACTION = 1207 - ER_DROP_DB_WITH_READ_LOCK = 1208 - ER_CREATE_DB_WITH_READ_LOCK = 1209 - ER_WRONG_ARGUMENTS = 1210 - ER_NO_PERMISSION_TO_CREATE_USER = 1211 - ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212 - ER_LOCK_DEADLOCK = 1213 - ER_TABLE_CANT_HANDLE_FULLTEXT = 1214 - ER_CANNOT_ADD_FOREIGN = 1215 - ER_NO_REFERENCED_ROW = 1216 - ER_ROW_IS_REFERENCED = 1217 - ER_CONNECT_TO_MASTER = 1218 - ER_QUERY_ON_MASTER = 1219 - ER_ERROR_WHEN_EXECUTING_COMMAND = 1220 - ER_WRONG_USAGE = 1221 - ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222 - ER_CANT_UPDATE_WITH_READLOCK = 1223 - ER_MIXING_NOT_ALLOWED = 1224 - ER_DUP_ARGUMENT = 1225 - ER_USER_LIMIT_REACHED = 1226 - ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227 - ER_LOCAL_VARIABLE = 1228 - ER_GLOBAL_VARIABLE = 1229 - ER_NO_DEFAULT = 1230 - ER_WRONG_VALUE_FOR_VAR = 1231 - ER_WRONG_TYPE_FOR_VAR = 1232 - ER_VAR_CANT_BE_READ = 1233 - ER_CANT_USE_OPTION_HERE = 1234 - ER_NOT_SUPPORTED_YET = 1235 - ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236 - ER_SLAVE_IGNORED_TABLE = 1237 - ER_ERROR_MESSAGES = 238 - - # Client Error - CR_MIN_ERROR = 2000 - CR_MAX_ERROR = 2999 - CR_UNKNOWN_ERROR = 2000 - CR_SOCKET_CREATE_ERROR = 2001 - CR_CONNECTION_ERROR = 2002 - CR_CONN_HOST_ERROR = 2003 - CR_IPSOCK_ERROR = 2004 - CR_UNKNOWN_HOST = 2005 - CR_SERVER_GONE_ERROR = 2006 - CR_VERSION_ERROR = 2007 - CR_OUT_OF_MEMORY = 2008 - CR_WRONG_HOST_INFO = 2009 - CR_LOCALHOST_CONNECTION = 2010 - CR_TCP_CONNECTION = 2011 - CR_SERVER_HANDSHAKE_ERR = 2012 - CR_SERVER_LOST = 2013 - CR_COMMANDS_OUT_OF_SYNC = 2014 - CR_NAMEDPIPE_CONNECTION = 2015 - CR_NAMEDPIPEWAIT_ERROR = 2016 - CR_NAMEDPIPEOPEN_ERROR = 2017 - CR_NAMEDPIPESETSTATE_ERROR = 2018 - CR_CANT_READ_CHARSET = 2019 - CR_NET_PACKET_TOO_LARGE = 2020 - CR_EMBEDDED_CONNECTION = 2021 - CR_PROBE_SLAVE_STATUS = 2022 - CR_PROBE_SLAVE_HOSTS = 2023 - CR_PROBE_SLAVE_CONNECT = 2024 - CR_PROBE_MASTER_CONNECT = 2025 - CR_SSL_CONNECTION_ERROR = 2026 - CR_MALFORMED_PACKET = 2027 - - CLIENT_ERRORS = [ - "Unknown MySQL error", - "Can't create UNIX socket (%d)", - "Can't connect to local MySQL server through socket '%-.64s' (%d)", - "Can't connect to MySQL server on '%-.64s' (%d)", - "Can't create TCP/IP socket (%d)", - "Unknown MySQL Server Host '%-.64s' (%d)", - "MySQL server has gone away", - "Protocol mismatch. Server Version = %d Client Version = %d", - "MySQL client run out of memory", - "Wrong host info", - "Localhost via UNIX socket", - "%-.64s via TCP/IP", - "Error in server handshake", - "Lost connection to MySQL server during query", - "Commands out of sync; You can't run this command now", - "%-.64s via named pipe", - "Can't wait for named pipe to host: %-.64s pipe: %-.32s (%lu)", - "Can't open named pipe to host: %-.64s pipe: %-.32s (%lu)", - "Can't set state of named pipe to host: %-.64s pipe: %-.32s (%lu)", - "Can't initialize character set %-.64s (path: %-.64s)", - "Got packet bigger than 'max_allowed_packet'", - "Embedded server", - "Error on SHOW SLAVE STATUS:", - "Error on SHOW SLAVE HOSTS:", - "Error connecting to slave:", - "Error connecting to master:", - "SSL connection error", - "Malformed packet" - ] - - def initialize(errno, error) - @errno = errno - @error = error - super error - end - attr_reader :errno, :error - - def Error::err(errno) - CLIENT_ERRORS[errno - Error::CR_MIN_ERROR] - end - end - - class Net - def initialize(sock) - @sock = sock - @pkt_nr = 0 - end - - def clear() - @pkt_nr = 0 - end - - def read() - buf = [] - len = nil - @sock.sync = false - while len == nil or len == MAX_PACKET_LENGTH do - a = @sock.read(4) - len = a[0]+a[1]*256+a[2]*256*256 - pkt_nr = a[3] - if @pkt_nr != pkt_nr then - raise "Packets out of order: #{@pkt_nr}<>#{pkt_nr}" - end - @pkt_nr = @pkt_nr + 1 & 0xff - buf << @sock.read(len) - end - @sock.sync = true - buf.join - rescue - errno = Error::CR_SERVER_LOST - raise Error::new(errno, Error::err(errno)) - end - - def write(data) - if data.is_a? Array then - data = data.join - end - @sock.sync = false - ptr = 0 - while data.length >= MAX_PACKET_LENGTH do - @sock.write Net::int3str(MAX_PACKET_LENGTH)+@pkt_nr.chr+data[ptr, MAX_PACKET_LENGTH] - @pkt_nr = @pkt_nr + 1 & 0xff - ptr += MAX_PACKET_LENGTH - end - @sock.write Net::int3str(data.length-ptr)+@pkt_nr.chr+data[ptr .. -1] - @pkt_nr = @pkt_nr + 1 & 0xff - @sock.sync = true - @sock.flush - rescue - errno = Error::CR_SERVER_LOST - raise Error::new(errno, Error::err(errno)) - end - - def close() - @sock.close - end - - def Net::int2str(n) - [n].pack("v") - end - - def Net::int3str(n) - [n%256, n>>8].pack("cv") - end - - def Net::int4str(n) - [n].pack("V") - end - - end - - class Random - def initialize(seed1, seed2) - @max_value = 0x3FFFFFFF - @seed1 = seed1 % @max_value - @seed2 = seed2 % @max_value - end - - def rnd() - @seed1 = (@seed1*3+@seed2) % @max_value - @seed2 = (@seed1+@seed2+33) % @max_value - @seed1.to_f / @max_value - end - end - -end - -class << Mysql - def init() - Mysql::new :INIT - end - - def real_connect(*args) - Mysql::new(*args) - end - alias :connect :real_connect - - def finalizer(net) - proc { - net.clear - begin - net.write(Mysql::COM_QUIT.chr) - net.close - rescue # Ignore IOError if socket is already closed. - end - } - end - - def escape_string(str) - str.gsub(/([\0\n\r\032\'\"\\])/) do - case $1 - when "\0" then "\\0" - when "\n" then "\\n" - when "\r" then "\\r" - when "\032" then "\\Z" - else "\\"+$1 - end - end - end - alias :quote :escape_string - - def get_client_info() - Mysql::VERSION - end - alias :client_info :get_client_info - - def debug(str) - raise "not implemented" - end -end - -# -# for compatibility -# - -MysqlRes = Mysql::Result -MysqlField = Mysql::Field -MysqlError = Mysql::Error diff --git a/activerecord/test/cases/active_schema_test_postgresql.rb b/activerecord/test/cases/active_schema_test_postgresql.rb index db325e3fb4..af80f724f2 100644 --- a/activerecord/test/cases/active_schema_test_postgresql.rb +++ b/activerecord/test/cases/active_schema_test_postgresql.rb @@ -13,8 +13,8 @@ class PostgresqlActiveSchemaTest < Test::Unit::TestCase end def test_create_database_with_encoding - assert_equal "CREATE DATABASE matt ENCODING = 'utf8'", create_database(:matt) - assert_equal "CREATE DATABASE aimonetti ENCODING = 'latin1'", create_database(:aimonetti, :encoding => :latin1) + assert_equal %(CREATE DATABASE "matt" ENCODING = 'utf8'), create_database(:matt) + assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1) end private diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index e0da8bfb7a..9c718c4fef 100755 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -409,4 +409,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase sponsor.sponsorable = new_member assert_equal nil, sponsor.sponsorable_id end + + def test_save_fails_for_invalid_belongs_to + assert log = AuditLog.create(:developer_id=>0,:message=>"") + + log.developer = Developer.new + assert !log.developer.valid? + assert !log.valid? + assert !log.save + assert_equal "is invalid", log.errors.on("developer") + end + + def test_save_succeeds_for_invalid_belongs_to_with_validate_false + assert log = AuditLog.create(:developer_id=>0,:message=>"") + + log.unvalidated_developer = Developer.new + assert !log.unvalidated_developer.valid? + assert log.valid? + assert log.save + end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 3a3358e39b..f65ada550b 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -14,11 +14,14 @@ require 'models/job' require 'models/subscriber' require 'models/subscription' require 'models/book' +require 'models/developer' +require 'models/project' class EagerAssociationTest < ActiveRecord::TestCase fixtures :posts, :comments, :authors, :categories, :categories_posts, :companies, :accounts, :tags, :taggings, :people, :readers, - :owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books + :owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books, + :developers, :projects def test_loading_with_one_association posts = Post.find(:all, :include => :comments) @@ -609,4 +612,12 @@ class EagerAssociationTest < ActiveRecord::TestCase Comment.find :all, :include => :post end end + + def test_conditions_on_join_table_with_include_and_limit + assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).size + end + + def test_order_on_join_table_with_include_and_limit + assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size + end end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 294b993c55..b29df68d22 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -681,4 +681,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal developer, project.developers.find(:first) assert_equal project, developer.projects.find(:first) end + + def test_dynamic_find_should_respect_association_include + # SQL error in sort clause if :include is not included + # due to Unknown column 'authors.id' + assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog') + 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 dbfa025efb..b638143c5a 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -37,15 +37,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_counting_with_single_conditions - assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1') + assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => ['name=?', "Microsoft"]) end def test_counting_with_single_hash - assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1') + assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => {:name => "Microsoft"}) end def test_counting_with_column_name_and_hash - assert_equal 2, Firm.find(:first).plain_clients.count(:all, :conditions => '1=1') + assert_equal 2, Firm.find(:first).plain_clients.count(:name) end def test_finding @@ -342,6 +342,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert new_firm.new_record? end + def test_invalid_adding_with_validate_false + firm = Firm.find(:first) + client = Client.new + firm.unvalidated_clients_of_firm << Client.new + + assert firm.valid? + assert !client.valid? + assert firm.save + assert client.new_record? + end + def test_build company = companies(:first_firm) new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") } diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 05155f6303..be5170f830 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -187,4 +187,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase post.people_with_callbacks.clear assert_equal (%w(Michael David Julian Roger) * 2).sort, log.last(8).collect(&:last).sort end + + def test_dynamic_find_should_respect_association_include + # SQL error in sort clause if :include is not included + # due to Unknown column 'comments.id' + assert Person.find(1).posts_with_comments_sorted_by_comment_id.find_by_title('Welcome to the weblog') + end end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index abc7ee7e9d..d3ca0cae41 100755 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -275,6 +275,18 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal "is invalid", firm.errors.on("account") end + + def test_save_succeeds_for_invalid_has_one_with_validate_false + firm = Firm.find(:first) + assert firm.valid? + + firm.unvalidated_account = Account.new + + assert !firm.unvalidated_account.valid? + assert firm.valid? + assert firm.save + end + def test_assignment_before_either_saved firm = Firm.new("name" => "GlobalMegaCorp") firm.account = a = Account.new("credit_limit" => 1000) diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index c336fd9afb..7999e29264 100755 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -137,7 +137,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end end - + def test_time_attributes_are_retrieved_in_current_time_zone in_time_zone "Pacific Time (US & Canada)" do utc_time = Time.utc(2008, 1, 1) @@ -145,7 +145,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase record[:written_on] = utc_time assert_equal utc_time, record.written_on # record.written on is equal to (i.e., simultaneous with) utc_time assert_kind_of ActiveSupport::TimeWithZone, record.written_on # but is a TimeWithZone - assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone # and is in the current Time.zone + assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone # and is in the current Time.zone assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time # and represents time values adjusted accordingly end end @@ -156,7 +156,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase record = @target.new record.written_on = utc_time assert_equal utc_time, record.written_on - assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone + assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time end end @@ -168,7 +168,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase record = @target.new record.written_on = cst_time assert_equal utc_time, record.written_on - assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone + assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time end end @@ -181,12 +181,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase record = @target.new record.written_on = time_string assert_equal Time.zone.parse(time_string), record.written_on - assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone + assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time end end end - + def test_setting_time_zone_aware_attribute_to_blank_string_returns_nil in_time_zone "Pacific Time (US & Canada)" do record = @target.new @@ -202,7 +202,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase record = @target.new record.written_on = time_string assert_equal Time.zone.parse(time_string), record.written_on - assert_equal TimeZone[timezone_offset], record.written_on.time_zone + assert_equal ActiveSupport::TimeZone[timezone_offset], record.written_on.time_zone assert_equal Time.utc(2008, 1, 1), record.written_on.time end end @@ -214,7 +214,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase record = @target.new record.written_on = utc_time.in_time_zone assert_equal utc_time, record.written_on - assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone + assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time end end @@ -223,12 +223,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase def time_related_columns_on_topic Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name) end - + def in_time_zone(zone) old_zone = Time.zone old_tz = ActiveRecord::Base.time_zone_aware_attributes - Time.zone = zone ? TimeZone[zone] : nil + Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil ActiveRecord::Base.time_zone_aware_attributes = !zone.nil? yield ensure diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index e07ec50c46..a4be629fbd 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -133,19 +133,19 @@ class BasicsTest < ActiveRecord::TestCase category_attrs = {"name"=>"Test categoty", "type" => nil} assert_equal category_attrs , category.attributes_before_type_cast end - + if current_adapter?(:MysqlAdapter) def test_read_attributes_before_type_cast_on_boolean bool = Booleantest.create({ "value" => false }) assert_equal 0, bool.attributes_before_type_cast["value"] end end - + def test_read_attributes_before_type_cast_on_datetime developer = Developer.find(:first) assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] end - + def test_hash_content topic = Topic.new topic.content = { "one" => 1, "two" => 2 } @@ -251,7 +251,7 @@ class BasicsTest < ActiveRecord::TestCase topic = Topic.create("title" => "New Topic") topicReloaded = Topic.find(topic.id) assert_equal(topic, topicReloaded) - end + end def test_create_through_factory_with_block topic = Topic.create("title" => "New Topic") do |t| @@ -576,7 +576,7 @@ class BasicsTest < ActiveRecord::TestCase def test_destroy_all original_count = Topic.count topics_by_mary = Topic.count(:conditions => mary = "author_name = 'Mary'") - + Topic.destroy_all mary assert_equal original_count - topics_by_mary, Topic.count end @@ -665,7 +665,7 @@ class BasicsTest < ActiveRecord::TestCase def test_delete_all assert Topic.count > 0 - + assert_equal Topic.count, Topic.delete_all end @@ -970,7 +970,7 @@ class BasicsTest < ActiveRecord::TestCase topic.attributes = attributes assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on end - + def test_multiparameter_attributes_on_time_with_old_date attributes = { "written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24", @@ -998,7 +998,7 @@ class BasicsTest < ActiveRecord::TestCase def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes ActiveRecord::Base.time_zone_aware_attributes = true ActiveRecord::Base.default_timezone = :utc - Time.zone = TimeZone[-28800] + Time.zone = ActiveSupport::TimeZone[-28800] attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" @@ -1016,7 +1016,7 @@ class BasicsTest < ActiveRecord::TestCase def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false ActiveRecord::Base.time_zone_aware_attributes = false - Time.zone = TimeZone[-28800] + Time.zone = ActiveSupport::TimeZone[-28800] attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" @@ -1032,7 +1032,7 @@ class BasicsTest < ActiveRecord::TestCase def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes ActiveRecord::Base.time_zone_aware_attributes = true ActiveRecord::Base.default_timezone = :utc - Time.zone = TimeZone[-28800] + Time.zone = ActiveSupport::TimeZone[-28800] Topic.skip_time_zone_conversion_for_attributes = [:written_on] attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", @@ -1647,7 +1647,7 @@ class BasicsTest < ActiveRecord::TestCase last = Developer.find :last assert_equal last, Developer.find(:first, :order => 'id desc') end - + def test_last assert_equal Developer.find(:first, :order => 'id desc'), Developer.last end @@ -1655,7 +1655,7 @@ class BasicsTest < ActiveRecord::TestCase def test_all_with_conditions assert_equal Developer.find(:all, :order => 'id desc'), Developer.all(:order => 'id desc') end - + def test_find_ordered_last last = Developer.find :last, :order => 'developers.salary ASC' assert_equal last, Developer.find(:all, :order => 'developers.salary ASC').last @@ -1670,14 +1670,14 @@ class BasicsTest < ActiveRecord::TestCase last = Developer.find :last, :order => 'developers.name, developers.salary DESC' assert_equal last, Developer.find(:all, :order => 'developers.name, developers.salary DESC').last end - + def test_find_scoped_ordered_last last_developer = Developer.with_scope(:find => { :order => 'developers.salary ASC' }) do Developer.find(:last) end assert_equal last_developer, Developer.find(:all, :order => 'developers.salary ASC').last end - + def test_abstract_class assert !ActiveRecord::Base.abstract_class? assert LoosePerson.abstract_class? diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index a87fc26905..754fd58f35 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -1,6 +1,7 @@ require "cases/helper" require 'models/company' require 'models/topic' +require 'models/edge' Company.has_many :accounts @@ -99,6 +100,12 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_return_zero_if_sum_conditions_return_nothing assert_equal 0, Account.sum(:credit_limit, :conditions => '1 = 2') + assert_equal 0, companies(:rails_core).companies.sum(:id, :conditions => '1 = 2') + end + + def test_sum_should_return_valid_values_for_decimals + NumericData.create(:bank_balance => 19.83) + assert_equal 19.83, NumericData.sum(:bank_balance) end def test_should_group_by_summed_field_with_conditions @@ -266,6 +273,51 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_sum_expression - assert_equal "636", Account.sum("2 * credit_limit") + assert_equal 636, Account.sum("2 * credit_limit") + end + + def test_count_with_from_option + assert_equal Company.count(:all), Company.count(:all, :from => 'companies') + assert_equal Account.count(:all, :conditions => "credit_limit = 50"), + Account.count(:all, :from => 'accounts', :conditions => "credit_limit = 50") + assert_equal Company.count(:type, :conditions => {:type => "Firm"}), + Company.count(:type, :conditions => {:type => "Firm"}, :from => 'companies') + end + + def test_sum_with_from_option + assert_equal Account.sum(:credit_limit), Account.sum(:credit_limit, :from => 'accounts') + assert_equal Account.sum(:credit_limit, :conditions => "credit_limit > 50"), + Account.sum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") + end + + def test_average_with_from_option + assert_equal Account.average(:credit_limit), Account.average(:credit_limit, :from => 'accounts') + assert_equal Account.average(:credit_limit, :conditions => "credit_limit > 50"), + Account.average(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") + end + + def test_minimum_with_from_option + assert_equal Account.minimum(:credit_limit), Account.minimum(:credit_limit, :from => 'accounts') + assert_equal Account.minimum(:credit_limit, :conditions => "credit_limit > 50"), + Account.minimum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") + end + + def test_maximum_with_from_option + assert_equal Account.maximum(:credit_limit), Account.maximum(:credit_limit, :from => 'accounts') + assert_equal Account.maximum(:credit_limit, :conditions => "credit_limit > 50"), + Account.maximum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") end + + def test_from_option_with_specified_index + if Edge.connection.adapter_name == 'MySQL' + assert_equal Edge.count(:all), Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)') + assert_equal Edge.count(:all, :conditions => 'sink_id < 5'), + Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)', :conditions => 'sink_id < 5') + end + end + + def test_from_option_with_table_different_than_class + assert_equal Account.count(:all), Company.count(:all, :from => 'accounts') + end + end diff --git a/activerecord/test/cases/callbacks_observers_test.rb b/activerecord/test/cases/callbacks_observers_test.rb new file mode 100644 index 0000000000..87de524923 --- /dev/null +++ b/activerecord/test/cases/callbacks_observers_test.rb @@ -0,0 +1,38 @@ +require "cases/helper" + +class Comment < ActiveRecord::Base + attr_accessor :callers + + before_validation :record_callers + + def after_validation + record_callers + end + + def record_callers + callers << self.class if callers + end +end + +class CommentObserver < ActiveRecord::Observer + attr_accessor :callers + + def after_validation(model) + callers << self.class if callers + end +end + +class CallbacksObserversTest < ActiveRecord::TestCase + def test_model_callbacks_fire_before_observers_are_notified + callers = [] + + comment = Comment.new + comment.callers = callers + + CommentObserver.instance.callers = callers + + comment.valid? + + assert_equal [Comment, Comment, CommentObserver], callers, "model callbacks did not fire before observers were notified" + end +end diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb new file mode 100644 index 0000000000..6274d5250f --- /dev/null +++ b/activerecord/test/cases/database_statements_test.rb @@ -0,0 +1,12 @@ +require "cases/helper" + +class DatabaseStatementsTest < ActiveRecord::TestCase + def setup + @connection = ActiveRecord::Base.connection + end + + def test_insert_should_return_the_inserted_id + id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") + assert_not_nil id + end +end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index c011ffaf57..d70a787208 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -2,6 +2,7 @@ require 'cases/helper' require 'models/topic' # For booleans require 'models/pirate' # For timestamps require 'models/parrot' +require 'models/person' # For optimistic locking class Pirate # Just reopening it, not defining it attr_accessor :detected_changes_in_after_update # Boolean for if changes are detected @@ -125,6 +126,24 @@ class DirtyTest < ActiveRecord::TestCase end end + def test_partial_update_with_optimistic_locking + person = Person.new(:first_name => 'foo') + old_lock_version = 1 + + with_partial_updates Person, false do + assert_queries(2) { 2.times { person.save! } } + Person.update_all({ :first_name => 'baz' }, :id => person.id) + end + + with_partial_updates Person, true do + assert_queries(0) { 2.times { person.save! } } + assert_equal old_lock_version, person.reload.lock_version + + assert_queries(1) { person.first_name = 'bar'; person.save! } + assert_not_equal old_lock_version, person.reload.lock_version + end + end + def test_changed_attributes_should_be_preserved_if_save_failure pirate = Pirate.new pirate.parrot_id = 1 diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 80936d51f3..f48b62ba6b 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'models/author' +require 'models/categorization' require 'models/comment' require 'models/company' require 'models/topic' @@ -394,6 +395,12 @@ class FinderTest < ActiveRecord::TestCase assert_equal '1,1,1', bind('?', os) end + def test_named_bind_with_postgresql_type_casts + l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') } + assert_nothing_raised(&l) + assert_equal "#{ActiveRecord::Base.quote_value('10')}::integer '2009-01-01'::date", l.call + end + def test_string_sanitation assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table") diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index f09b617e6d..47fb5c608f 100755 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -223,11 +223,11 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase fixtures :companies def setup - Dependencies.log_activity = true + ActiveSupport::Dependencies.log_activity = true end def teardown - Dependencies.log_activity = false + ActiveSupport::Dependencies.log_activity = false self.class.const_remove :FirmOnTheFly rescue nil Firm.const_remove :FirmOnTheFly rescue nil end diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb index ddcacacc3a..258f7c7a0f 100755 --- a/activerecord/test/cases/lifecycle_test.rb +++ b/activerecord/test/cases/lifecycle_test.rb @@ -2,6 +2,7 @@ require "cases/helper" require 'models/topic' require 'models/developer' require 'models/reply' +require 'models/minimalistic' class Topic; def after_find() end end class Developer; def after_find() end end @@ -44,6 +45,14 @@ class TopicObserver < ActiveRecord::Observer end end +class MinimalisticObserver < ActiveRecord::Observer + attr_reader :minimalistic + + def after_find(minimalistic) + @minimalistic = minimalistic + end +end + class MultiObserver < ActiveRecord::Observer attr_reader :record @@ -134,6 +143,14 @@ class LifecycleTest < ActiveRecord::TestCase assert_equal developer.name, multi_observer.record.name end + def test_observing_after_find_when_not_defined_on_the_model + observer = MinimalisticObserver.instance + assert_equal Minimalistic, MinimalisticObserver.observed_class + + minimalistic = Minimalistic.find(1) + assert_equal minimalistic, observer.minimalistic + end + def test_invalid_observer assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers } end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 7db6c570b5..701187223f 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -29,10 +29,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version + p1.first_name = 'stu' p1.save! assert_equal 1, p1.lock_version assert_equal 0, p2.lock_version + p2.first_name = 'sue' assert_raises(ActiveRecord::StaleObjectError) { p2.save! } end @@ -42,11 +44,14 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version + p1.first_name = 'stu' p1.save! assert_equal 1, p1.lock_version assert_equal 0, p2.lock_version + p2.first_name = 'sue' assert_raises(ActiveRecord::StaleObjectError) { p2.save! } + p2.first_name = 'sue2' assert_raises(ActiveRecord::StaleObjectError) { p2.save! } end @@ -54,15 +59,18 @@ class OptimisticLockingTest < ActiveRecord::TestCase p1 = Person.new(:first_name => 'anika') assert_equal 0, p1.lock_version + p1.first_name = 'anika2' p1.save! p2 = Person.find(p1.id) assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version + p1.first_name = 'anika3' p1.save! assert_equal 1, p1.lock_version assert_equal 0, p2.lock_version + p2.first_name = 'sue' assert_raises(ActiveRecord::StaleObjectError) { p2.save! } end @@ -81,10 +89,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, t1.version assert_equal 0, t2.version + t1.tps_report_number = 700 t1.save! assert_equal 1, t1.version assert_equal 0, t2.version + t2.tps_report_number = 800 assert_raises(ActiveRecord::StaleObjectError) { t2.save! } end @@ -93,6 +103,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, p1.lock_version assert_equal p1.lock_version, Person.new(p1.attributes).lock_version + p1.first_name = 'bianca2' p1.save! assert_equal 1, p1.lock_version assert_equal p1.lock_version, Person.new(p1.attributes).lock_version @@ -146,6 +157,15 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert ref.save end + # Useful for partial updates, don't only update the lock_version if there + # is nothing else being updated. + def test_update_without_attributes_does_not_only_update_lock_version + assert_nothing_raised do + p1 = Person.new(:first_name => 'anika') + p1.send(:update_with_lock, []) + end + end + private def add_counter_column_to(model) diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 1a9a875730..d6b3e341df 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -87,6 +87,16 @@ class MethodScopingTest < ActiveRecord::TestCase assert_equal 1, scoped_developers.size end + def test_scoped_find_joins + scoped_developers = Developer.with_scope(:find => { :joins => 'JOIN developers_projects ON id = developer_id' } ) do + Developer.find(:all, :conditions => 'developers_projects.project_id = 2') + end + assert scoped_developers.include?(developers(:david)) + assert !scoped_developers.include?(developers(:jamis)) + assert_equal 1, scoped_developers.size + assert_equal developers(:david).attributes, scoped_developers.first.attributes + end + def test_scoped_count_include # with the include, will retrieve only developers for the given project Developer.with_scope(:find => { :include => :projects }) do diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index f36255e209..908951590c 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -173,6 +173,11 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal 'smallint', one.sql_type assert_equal 'integer', four.sql_type assert_equal 'bigint', eight.sql_type + elsif current_adapter?(:MysqlAdapter) + assert_match /^int\(\d+\)/, default.sql_type + assert_match /^tinyint\(\d+\)/, one.sql_type + assert_match /^int\(\d+\)/, four.sql_type + assert_match /^bigint\(\d+\)/, eight.sql_type elsif current_adapter?(:OracleAdapter) assert_equal 'NUMBER(38)', default.sql_type assert_equal 'NUMBER(1)', one.sql_type @@ -483,6 +488,32 @@ if ActiveRecord::Base.connection.supports_migrations? end end + def test_rename_column_preserves_default_value_not_null + begin + default_before = Developer.connection.columns("developers").find { |c| c.name == "salary" }.default + assert_equal 70000, default_before + Developer.connection.rename_column "developers", "salary", "anual_salary" + Developer.reset_column_information + assert Developer.column_names.include?("anual_salary") + default_after = Developer.connection.columns("developers").find { |c| c.name == "anual_salary" }.default + assert_equal 70000, default_after + ensure + Developer.connection.rename_column "developers", "anual_salary", "salary" + Developer.reset_column_information + end + end + + def test_rename_nonexistent_column + ActiveRecord::Base.connection.create_table(:hats) do |table| + table.column :hat_name, :string, :default => nil + end + assert_raises(ActiveRecord::ActiveRecordError) do + Person.connection.rename_column "hats", "nonexistent", "should_fail" + end + ensure + ActiveRecord::Base.connection.drop_table(:hats) + end + def test_rename_column_with_sql_reserved_word begin assert_nothing_raised { Person.connection.rename_column "people", "first_name", "group" } @@ -799,6 +830,21 @@ if ActiveRecord::Base.connection.supports_migrations? assert !Reminder.table_exists? end + def test_migrator_double_up + assert_equal(0, ActiveRecord::Migrator.current_version) + ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) + assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) } + assert_equal(1, ActiveRecord::Migrator.current_version) + end + + def test_migrator_double_down + assert_equal(0, ActiveRecord::Migrator.current_version) + ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) + ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1) + assert_nothing_raised { ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1) } + assert_equal(0, ActiveRecord::Migrator.current_version) + end + def test_finds_migrations migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations [['1', 'people_have_last_names'], @@ -888,16 +934,6 @@ if ActiveRecord::Base.connection.supports_migrations? ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") assert_equal(0, ActiveRecord::Migrator.current_version) end - - def test_migrator_run - assert_equal(0, ActiveRecord::Migrator.current_version) - ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 3) - assert_equal(0, ActiveRecord::Migrator.current_version) - - assert_equal(0, ActiveRecord::Migrator.current_version) - ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 3) - assert_equal(0, ActiveRecord::Migrator.current_version) - end def test_schema_migrations_table_name ActiveRecord::Base.table_name_prefix = "prefix_" diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index 236e4710a4..eb3e43c8ac 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -51,7 +51,7 @@ class MultipleDbTest < ActiveRecord::TestCase def test_course_connection_should_survive_dependency_reload assert Course.connection - Dependencies.clear + ActiveSupport::Dependencies.clear Object.send(:remove_const, :Course) require_dependency 'models/course' diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index d890cf7936..393ba086c9 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -6,7 +6,7 @@ require 'models/reply' require 'models/author' class NamedScopeTest < ActiveRecord::TestCase - fixtures :posts, :authors, :topics, :comments + fixtures :posts, :authors, :topics, :comments, :author_addresses def test_implements_enumerable assert !Topic.find(:all).empty? @@ -59,6 +59,12 @@ class NamedScopeTest < ActiveRecord::TestCase assert_equal Topic.count(:conditions => {:approved => true}), Topic.approved.count end + def test_scopes_with_string_name_can_be_composed + # NOTE that scopes defined with a string as a name worked on their own + # but when called on another scope the other scope was completely replaced + assert_equal Topic.replied.approved, Topic.replied.approved_as_string + end + def test_scopes_are_composable assert_equal (approved = Topic.find(:all, :conditions => {:approved => true})), Topic.approved assert_equal (replied = Topic.find(:all, :conditions => 'replies_count > 0')), Topic.replied @@ -77,6 +83,25 @@ class NamedScopeTest < ActiveRecord::TestCase assert_equal topics_written_before_the_second, Topic.written_before(topics(:second).written_on) end + def test_scopes_with_joins + address = author_addresses(:david_address) + posts_with_authors_at_address = Post.find( + :all, :joins => 'JOIN authors ON authors.id = posts.author_id', + :conditions => [ 'authors.author_address_id = ?', address.id ] + ) + assert_equal posts_with_authors_at_address, Post.with_authors_at_address(address) + end + + def test_scopes_with_joins_respects_custom_select + address = author_addresses(:david_address) + posts_with_authors_at_address_titles = Post.find(:all, + :select => 'title', + :joins => 'JOIN authors ON authors.id = posts.author_id', + :conditions => [ 'authors.author_address_id = ?', address.id ] + ) + assert_equal posts_with_authors_at_address_titles, Post.with_authors_at_address(address).find(:all, :select => 'title') + end + def test_extensions assert_equal 1, Topic.anonymous_extension.one assert_equal 2, Topic.named_extension.two diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 8b4d232554..0c57b79401 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -160,9 +160,9 @@ class ReflectionTest < ActiveRecord::TestCase def test_reflection_of_all_associations # FIXME these assertions bust a lot - assert_equal 20, Firm.reflect_on_all_associations.size - assert_equal 16, Firm.reflect_on_all_associations(:has_many).size - assert_equal 4, Firm.reflect_on_all_associations(:has_one).size + assert_equal 22, Firm.reflect_on_all_associations.size + assert_equal 17, Firm.reflect_on_all_associations(:has_many).size + assert_equal 5, Firm.reflect_on_all_associations(:has_one).size assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index c42b0efba0..ee7e285a73 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -72,6 +72,52 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{:null => false}, output end + def test_schema_dump_includes_limit_constraint_for_integer_columns + stream = StringIO.new + + ActiveRecord::SchemaDumper.ignore_tables = [/^(?!integer_limits)/] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + output = stream.string + + if current_adapter?(:PostgreSQLAdapter) + assert_match %r{c_int_1.*:limit => 2}, output + assert_match %r{c_int_2.*:limit => 2}, output + + # int 3 is 4 bytes in postgresql + assert_match %r{c_int_3.*}, output + assert_no_match %r{c_int_3.*:limit}, output + + assert_match %r{c_int_4.*}, output + assert_no_match %r{c_int_4.*:limit}, output + elsif current_adapter?(:MysqlAdapter) + assert_match %r{c_int_1.*:limit => 1}, output + assert_match %r{c_int_2.*:limit => 2}, output + assert_match %r{c_int_3.*:limit => 3}, output + + assert_match %r{c_int_4.*}, output + assert_no_match %r{c_int_4.*:limit}, output + elsif current_adapter?(:SQLiteAdapter) + assert_match %r{c_int_1.*:limit => 1}, output + assert_match %r{c_int_2.*:limit => 2}, output + assert_match %r{c_int_3.*:limit => 3}, output + assert_match %r{c_int_4.*:limit => 4}, output + end + assert_match %r{c_int_without_limit.*}, output + assert_no_match %r{c_int_without_limit.*:limit}, output + + if current_adapter?(:SQLiteAdapter) + assert_match %r{c_int_5.*:limit => 5}, output + assert_match %r{c_int_6.*:limit => 6}, output + assert_match %r{c_int_7.*:limit => 7}, output + assert_match %r{c_int_8.*:limit => 8}, output + else + assert_match %r{c_int_5.*:limit => 8}, output + assert_match %r{c_int_6.*:limit => 8}, output + assert_match %r{c_int_7.*:limit => 8}, output + assert_match %r{c_int_8.*:limit => 8}, output + end + end + def test_schema_dump_with_string_ignored_table stream = StringIO.new diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index f63af27403..82e069005a 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -1,6 +1,7 @@ class Author < ActiveRecord::Base has_many :posts has_many :posts_with_comments, :include => :comments, :class_name => "Post" + has_many :posts_with_comments_sorted_by_comment_id, :include => :comments, :class_name => "Post", :order => 'comments.id' has_many :posts_with_categories, :include => :categories, :class_name => "Post" has_many :posts_with_comments_and_categories, :include => [ :comments, :categories ], :order => "posts.id", :class_name => "Post" has_many :posts_containing_the_letter_a, :class_name => "Post" diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb index f1d2e4805a..1660c61682 100644 --- a/activerecord/test/models/category.rb +++ b/activerecord/test/models/category.rb @@ -2,6 +2,7 @@ class Category < ActiveRecord::Base has_and_belongs_to_many :posts has_and_belongs_to_many :special_posts, :class_name => "Post" has_and_belongs_to_many :other_posts, :class_name => "Post" + has_and_belongs_to_many :posts_with_authors_sorted_by_author_id, :class_name => "Post", :include => :authors, :order => "authors.id" has_and_belongs_to_many(:select_testing_posts, :class_name => 'Post', diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 70f83fa8e6..9fa810ac68 100755 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -26,6 +26,7 @@ class Firm < Company "AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )" has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC" has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id" + has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all has_many :limited_clients, :class_name => "Client", :order => "id", :limit => 1 @@ -46,7 +47,8 @@ class Firm < Company has_many :plain_clients, :class_name => 'Client' has_many :readonly_clients, :class_name => 'Client', :readonly => true - has_one :account, :foreign_key => "firm_id", :dependent => :destroy + has_one :account, :foreign_key => "firm_id", :dependent => :destroy, :validate => true + has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account' has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index f77fd0e96d..9f26cacdec 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -56,7 +56,8 @@ class Developer < ActiveRecord::Base end class AuditLog < ActiveRecord::Base - belongs_to :developer + belongs_to :developer, :validate => true + belongs_to :unvalidated_developer, :class_name => 'Developer' end DeveloperSalary = Struct.new(:amount) diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 4f4d695b24..430d0b38f7 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -6,5 +6,5 @@ class Person < ActiveRecord::Base has_many :references 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' end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index d9101706b5..3adbc0ce1f 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -1,6 +1,11 @@ class Post < ActiveRecord::Base named_scope :containing_the_letter_a, :conditions => "body LIKE '%a%'" - + named_scope :with_authors_at_address, lambda { |address| { + :conditions => [ 'authors.author_address_id = ?', address.id ], + :joins => 'JOIN authors ON authors.id = posts.author_id' + } + } + belongs_to :author do def greeting "hello" diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index f63e862cfd..423b6fe52b 100755 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -4,6 +4,7 @@ class Topic < ActiveRecord::Base { :conditions => ['written_on < ?', time] } } named_scope :approved, :conditions => {:approved => true} + named_scope 'approved_as_string', :conditions => {:approved => true} named_scope :replied, :conditions => ['replies_count > 0'] named_scope :anonymous_extension do def one diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 423929fd55..234c43494a 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -407,6 +407,13 @@ ActiveRecord::Schema.define do t.column :key, :string end + create_table :integer_limits, :force => true do |t| + t.integer :"c_int_without_limit" + (1..8).each do |i| + t.integer :"c_int_#{i}", :limit => i + end + end + except 'SQLite' do # fk_test_has_fk should be before fk_test_has_pk create_table :fk_test_has_fk, :force => true do |t| diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index e124e40dd1..673e20de28 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -1,3 +1,8 @@ +*Edge* + +* Fixed Base#exists? to check status code as integer [#299 state:resolved] (Wes Oldenbeuving) + + *2.1.0 (May 31st, 2008)* * Fixed response logging to use length instead of the entire thing (seangeo) [#27] diff --git a/activeresource/Rakefile b/activeresource/Rakefile index 9fd0ec4921..8c3ad36a02 100644 --- a/activeresource/Rakefile +++ b/activeresource/Rakefile @@ -42,9 +42,10 @@ Rake::RDocTask.new { |rdoc| rdoc.title = "Active Resource -- Object-oriented REST services" rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' rdoc.options << '--charset' << 'utf-8' - rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' rdoc.rdoc_files.include('README', 'CHANGELOG') rdoc.rdoc_files.include('lib/**/*.rb') + rdoc.rdoc_files.exclude('lib/activeresource.rb') } @@ -114,13 +115,13 @@ end desc "Publish the beta gem" task :pgem => [:package] do - Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload - `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'` + Rake::SshFilePublisher.new("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload + `ssh wrath.rubyonrails.org './gemupdate.sh'` end desc "Publish the API documentation" task :pdoc => [:rdoc] do - Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ar", "doc").upload + Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload end desc "Publish the release files to RubyForge." diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 59e0888c2b..492ab27bef 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -538,7 +538,7 @@ module ActiveResource prefix_options, query_options = split_options(options[:params]) path = element_path(id, prefix_options, query_options) response = connection.head(path, headers) - response.code == 200 + response.code.to_i == 200 end # id && !find_single(id, options).nil? rescue ActiveResource::ResourceNotFound @@ -988,7 +988,11 @@ module ActiveResource self.class.const_get(resource_name) end rescue NameError - resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base)) + if self.class.const_defined?(resource_name) + resource = self.class.const_get(resource_name) + else + resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base)) + end resource.prefix = self.class.prefix resource.site = self.class.site resource diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb index 9e2f6c1831..4addd52636 100644 --- a/activeresource/test/base_test.rb +++ b/activeresource/test/base_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require "fixtures/person" +require "fixtures/customer" require "fixtures/street_address" require "fixtures/beast" @@ -15,6 +16,37 @@ class BaseTest < Test::Unit::TestCase @people_david = [{ :id => 2, :name => 'David' }].to_xml(:root => 'people') @addresses = [{ :id => 1, :street => '12345 Street' }].to_xml(:root => 'addresses') + # - deep nested resource - + # - Luis (Customer) + # - JK (Customer::Friend) + # - Mateo (Customer::Friend::Brother) + # - Edith (Customer::Friend::Brother::Child) + # - Martha (Customer::Friend::Brother::Child) + # - Felipe (Customer::Friend::Brother) + # - Bryan (Customer::Friend::Brother::Child) + # - Luke (Customer::Friend::Brother::Child) + # - Eduardo (Customer::Friend) + # - Sebas (Customer::Friend::Brother) + # - Andres (Customer::Friend::Brother::Child) + # - Jorge (Customer::Friend::Brother::Child) + # - Elsa (Customer::Friend::Brother) + # - Natacha (Customer::Friend::Brother::Child) + # - Milena (Customer::Friend::Brother) + # + @luis = {:id => 1, :name => 'Luis', + :friends => [{:name => 'JK', + :brothers => [{:name => 'Mateo', + :children => [{:name => 'Edith'},{:name => 'Martha'}]}, + {:name => 'Felipe', + :children => [{:name => 'Bryan'},{:name => 'Luke'}]}]}, + {:name => 'Eduardo', + :brothers => [{:name => 'Sebas', + :children => [{:name => 'Andres'},{:name => 'Jorge'}]}, + {:name => 'Elsa', + :children => [{:name => 'Natacha'}]}, + {:name => 'Milena', + :children => []}]}]}.to_xml(:root => 'customer') + ActiveResource::HttpMock.respond_to do |mock| mock.get "/people/1.xml", {}, @matz mock.get "/people/2.xml", {}, @david @@ -46,6 +78,8 @@ class BaseTest < Test::Unit::TestCase mock.head "/people/1/addresses/2.xml", {}, nil, 404 mock.head "/people/2/addresses/1.xml", {}, nil, 404 mock.head "/people/Greg/addresses/1.xml", {}, nil, 200 + # customer + mock.get "/customers/1.xml", {}, @luis end Person.user = nil @@ -788,4 +822,18 @@ class BaseTest < Test::Unit::TestCase matz = Person.find(1) assert_equal '1', matz.to_param end + + def test_parse_deep_nested_resources + luis = Customer.find(1) + assert_kind_of Customer, luis + luis.friends.each do |friend| + assert_kind_of Customer::Friend, friend + friend.brothers.each do |brother| + assert_kind_of Customer::Friend::Brother, brother + brother.children.each do |child| + assert_kind_of Customer::Friend::Brother::Child, child + end + end + end + end end diff --git a/activeresource/test/fixtures/customer.rb b/activeresource/test/fixtures/customer.rb new file mode 100644 index 0000000000..845d5d11cb --- /dev/null +++ b/activeresource/test/fixtures/customer.rb @@ -0,0 +1,3 @@ +class Customer < ActiveResource::Base + self.site = "http://37s.sunrise.i:3000" +end diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 690bb987dc..4f61311d95 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,3 +1,22 @@ +*Edge* + +* Added Array#second through Array#tenth as aliases for Array#[1] through Array#[9] [DHH] + +* Added test/do declaration style testing to ActiveSupport::TestCase [DHH via Jay Fields] + +* Added Object#present? which is equivalent to !Object#blank? [DHH] + +* Added Enumberable#many? to encapsulate collection.size > 1 [DHH/Damian Janowski] + +* Add more standard Hash methods to ActiveSupport::OrderedHash [Steve Purcell] + +* Namespace Inflector, Dependencies, OrderedOptions, and TimeZone under ActiveSupport [Josh Peek] + +* Added StringInquirer for doing things like StringInquirer.new("production").production? # => true and StringInquirer.new("production").development? # => false [DHH] + +* Fixed Date#end_of_quarter to not blow up on May 31st [#289 state:resolved] (Danger) + + *2.1.0 (May 31st, 2008)* * TimeZone#to_s shows offset as GMT instead of UTC, because GMT will be more familiar to end users (see time zone selects used by Windows OS, google.com and yahoo.com.) Reverts [8370] [Geoff Buesing] @@ -166,7 +185,6 @@ * Introduce ActiveSupport::TimeWithZone, for wrapping Time instances with a TimeZone. Introduce instance methods to Time for creating TimeWithZone instances, and class methods for managing a global time zone. [Geoff Buesing] ->>>>>>> .r8815 * Replace non-dst-aware TimeZone class with dst-aware class from tzinfo_timezone plugin. TimeZone#adjust and #unadjust are no longer available; tzinfo gem must now be present in order to perform time zone calculations, via #local_to_utc and #utc_to_local methods. [Geoff Buesing] * Extract ActiveSupport::Callbacks from Active Record, test case setup and teardown, and ActionController::Dispatcher. #10727 [Josh Peek] diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 38ca03b658..f2885c69a4 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -33,11 +33,11 @@ Rake::RDocTask.new { |rdoc| rdoc.title = "Active Support -- Utility classes and standard library extensions from Rails" rdoc.options << '--line-numbers' << '--inline-source' rdoc.options << '--charset' << 'utf-8' - rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' rdoc.rdoc_files.include('README', 'CHANGELOG') rdoc.rdoc_files.include('lib/active_support.rb') - rdoc.rdoc_files.include('lib/active_support/*.rb') rdoc.rdoc_files.include('lib/active_support/**/*.rb') + rdoc.rdoc_files.exclude('lib/active_support/vendor/*') } spec = Gem::Specification.new do |s| @@ -65,13 +65,13 @@ end desc "Publish the beta gem" task :pgem => [:package] do - Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload - `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'` + Rake::SshFilePublisher.new("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload + `ssh wrath.rubyonrails.org './gemupdate.sh'` end desc "Publish the API documentation" task :pdoc => [:rdoc] do - Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/as", "doc").upload + Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/as", "doc").upload end desc "Publish the release files to RubyForge." @@ -119,7 +119,7 @@ namespace :tzinfo do task :copy_definitions => :unpack_gem do definitions_path = "#{destination_path}/tzinfo/definitions/" mkdir_p definitions_path - TimeZone::MAPPING.values.each do |zone| + ActiveSupport::TimeZone::MAPPING.values.each do |zone| subdir = nil if /\// === zone subdir = zone.sub(/\w+$/, '') @@ -172,4 +172,4 @@ namespace :tzinfo do def excluded_classes %w(country country_index_definition country_info country_timezone timezone_index_definition timezone_proxy tzdataparser) end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index e4cb145c98..1a8603e892 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -43,6 +43,8 @@ require 'active_support/ordered_hash' require 'active_support/ordered_options' require 'active_support/option_merger' +require 'active_support/string_inquirer' + require 'active_support/values/time_zone' require 'active_support/duration' @@ -53,3 +55,7 @@ require 'active_support/multibyte' require 'active_support/base64' require 'active_support/time_with_zone' + +Inflector = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Inflector', 'ActiveSupport::Inflector') +Dependencies = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Dependencies', 'ActiveSupport::Dependencies') +TimeZone = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('TimeZone', 'ActiveSupport::TimeZone') diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 2f1143e610..3e3dc18263 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -19,19 +19,19 @@ module ActiveSupport def self.expand_cache_key(key, namespace = nil) expanded_cache_key = namespace ? "#{namespace}/" : "" - + if ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] expanded_cache_key << "#{ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]}/" end expanded_cache_key << case - when key.respond_to?(:cache_key) - key.cache_key - when key.is_a?(Array) - key.collect { |element| expand_cache_key(element) }.to_param - when key.respond_to?(:to_param) - key.to_param - end + when key.respond_to?(:cache_key) + key.cache_key + when key.is_a?(Array) + key.collect { |element| expand_cache_key(element) }.to_param + when key + key.to_param + end.to_s expanded_cache_key end diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 21a8584bb9..4ac95efdc7 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -20,6 +20,51 @@ module ActiveSupport #:nodoc: def to(position) self[0..position] end + + # Equal to self[1] + def second + self[1] + end + + # Equal to self[2] + def third + self[2] + end + + # Equal to self[3] + def fourth + self[3] + end + + # Equal to self[4] + def fifth + self[4] + end + + # Equal to self[5] + def sixth + self[5] + end + + # Equal to self[6] + def seventh + self[6] + end + + # Equal to self[7] + def eighth + self[7] + end + + # Equal to self[8] + def ninth + self[8] + end + + # Equal to self[9] + def tenth + self[9] + end end end end diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index a9882828ca..49ada8f174 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -27,7 +27,7 @@ module ActiveSupport #:nodoc: # Calls <tt>to_param</tt> on all its elements and joins the result with # slashes. This is used by <tt>url_for</tt> in Action Pack. def to_param - map(&:to_param).join '/' + collect { |e| e.to_param }.join '/' end # Converts an array into a string suitable for use as a URL query string, @@ -35,7 +35,8 @@ module ActiveSupport #:nodoc: # # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding" def to_query(key) - collect { |value| value.to_query("#{key}[]") } * '&' + prefix = "#{key}[]" + collect { |value| value.to_query(prefix) }.join '&' end def self.included(base) #:nodoc: diff --git a/activesupport/lib/active_support/core_ext/blank.rb b/activesupport/lib/active_support/core_ext/blank.rb index dfe33162e8..4f8dc4e281 100644 --- a/activesupport/lib/active_support/core_ext/blank.rb +++ b/activesupport/lib/active_support/core_ext/blank.rb @@ -12,6 +12,11 @@ class Object def blank? respond_to?(:empty?) ? empty? : !self end + + # An object is present if it's not blank. + def present? + !blank? + end end class NilClass #:nodoc: diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 1e2dbf118e..b5180c9592 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -184,7 +184,7 @@ module ActiveSupport #:nodoc: # Returns a new Date/DateTime representing the end of the quarter (last day of march, june, september, december; DateTime objects will have time set to 23:59:59) def end_of_quarter - change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month + beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month end alias :at_end_of_quarter :end_of_quarter diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index f1469aa0e3..e451e9933a 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -1,3 +1,5 @@ +require 'active_support/ordered_hash' + module Enumerable # Ruby 1.8.7 introduces group_by, but the result isn't ordered. Override it. remove_method(:group_by) if [].respond_to?(:group_by) && RUBY_VERSION < '1.9' @@ -18,10 +20,19 @@ module Enumerable # "2006-02-24 -> Transcript, Transcript" # "2006-02-23 -> Transcript" def group_by - inject ActiveSupport::OrderedHash.new do |grouped, element| - (grouped[yield(element)] ||= []) << element - grouped + assoc = ActiveSupport::OrderedHash.new + + each do |element| + key = yield(element) + + if assoc.has_key?(key) + assoc[key] << element + else + assoc[key] = [element] + end end + + assoc end unless [].respond_to?(:group_by) # Calculates a sum from the elements. Examples: @@ -66,4 +77,11 @@ module Enumerable accum end end + + # Returns true if the collection has more than 1 element. Functionally equivalent to collection.size > 1. + # Works with a block too ala any?, so people.many? { |p| p.age > 26 } # => returns true if more than 1 person is over 26. + def many?(&block) + size = block_given? ? select(&block).size : self.size + size > 1 + end end diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb index 64d690965a..f26d01553d 100644 --- a/activesupport/lib/active_support/core_ext/hash/except.rb +++ b/activesupport/lib/active_support/core_ext/hash/except.rb @@ -10,13 +10,14 @@ module ActiveSupport #:nodoc: module Except # Returns a new hash without the given keys. def except(*keys) - rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys) - reject { |key,| rejected.include?(key) } + clone.except!(*keys) end # Replaces the hash without the given keys. def except!(*keys) - replace(except(*keys)) + keys.map! { |key| convert_key(key) } if respond_to?(:convert_key) + keys.each { |key| delete(key) } + self end 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 6fe5e05330..be4dec6e53 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -1,5 +1,3 @@ -require 'set' - module ActiveSupport #:nodoc: module CoreExtensions #:nodoc: module Hash #:nodoc: @@ -14,8 +12,10 @@ module ActiveSupport #:nodoc: module Slice # Returns a new hash with only the given keys. def slice(*keys) - allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys) - reject { |key,| !allowed.include?(key) } + keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key) + hash = {} + keys.each { |k| hash[k] = self[k] if has_key?(k) } + hash end # Replaces the hash with only the given keys. diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index da8d5b3762..34fcbd124b 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -6,3 +6,8 @@ require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/introspection' require 'active_support/core_ext/module/loading' require 'active_support/core_ext/module/aliasing' +require 'active_support/core_ext/module/model_naming' + +class Module + include ActiveSupport::CoreExt::Module::ModelNaming +end diff --git a/activesupport/lib/active_support/core_ext/module/model_naming.rb b/activesupport/lib/active_support/core_ext/module/model_naming.rb new file mode 100644 index 0000000000..abb02f1f70 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/model_naming.rb @@ -0,0 +1,23 @@ +module ActiveSupport + class ModelName < String + attr_reader :singular, :plural, :cache_key, :partial_path + + def initialize(name) + super + @singular = underscore.tr('/', '_').freeze + @plural = @singular.pluralize.freeze + @cache_key = tableize + @partial_path = "#{@cache_key}/#{demodulize.underscore}".freeze + end + end + + module CoreExt + module Module + module ModelNaming + def model_name + @model_name ||= ModelName.new(name) + end + end + end + end +end diff --git a/activesupport/lib/active_support/core_ext/object/extending.rb b/activesupport/lib/active_support/core_ext/object/extending.rb index 43a2be916e..082e98a297 100644 --- a/activesupport/lib/active_support/core_ext/object/extending.rb +++ b/activesupport/lib/active_support/core_ext/object/extending.rb @@ -3,17 +3,18 @@ class Object Class.remove_class(*subclasses_of(*superclasses)) end + # Exclude this class unless it's a subclass of our supers and is defined. + # We check defined? in case we find a removed class that has yet to be + # garbage collected. This also fails for anonymous classes -- please + # submit a patch if you have a workaround. def subclasses_of(*superclasses) #:nodoc: subclasses = [] - # Exclude this class unless it's a subclass of our supers and is defined. - # We check defined? in case we find a removed class that has yet to be - # garbage collected. This also fails for anonymous classes -- please - # submit a patch if you have a workaround. - ObjectSpace.each_object(Class) do |k| - if superclasses.any? { |superclass| k < superclass } && - (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id")) - subclasses << k + superclasses.each do |sup| + ObjectSpace.each_object(class << sup; self; end) do |k| + if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id")) + subclasses << k + end end end diff --git a/activesupport/lib/active_support/core_ext/string/unicode.rb b/activesupport/lib/active_support/core_ext/string/unicode.rb index 5e20534d1d..666f7bcb65 100644 --- a/activesupport/lib/active_support/core_ext/string/unicode.rb +++ b/activesupport/lib/active_support/core_ext/string/unicode.rb @@ -1,16 +1,16 @@ module ActiveSupport #:nodoc: module CoreExtensions #:nodoc: module String #:nodoc: - unless '1.9'.respond_to?(:force_encoding) - # Define methods for handling unicode data. - module Unicode - def self.append_features(base) - if '1.8.7'.respond_to?(:chars) - base.class_eval { remove_method :chars } - end - super + # Define methods for handling unicode data. + module Unicode + def self.append_features(base) + if '1.8.7 and later'.respond_to?(:chars) + base.class_eval { remove_method :chars } end + super + end + unless '1.9'.respond_to?(:force_encoding) # +chars+ is a Unicode safe proxy for string methods. It creates and returns an instance of the # ActiveSupport::Multibyte::Chars class which encapsulates the original string. A Unicode safe version of all # the String methods are defined on this proxy class. Undefined methods are forwarded to String, so all of the @@ -44,14 +44,12 @@ module ActiveSupport #:nodoc: def is_utf8? ActiveSupport::Multibyte::Handlers::UTF8Handler.consumes?(self) end - end - else - module Unicode #:nodoc: - def chars + else + def chars #:nodoc: self end - def is_utf8? + def is_utf8? #:nodoc: case encoding when Encoding::UTF_8 valid_encoding? diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 8f3aa4f848..7a8c4d0326 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -3,458 +3,459 @@ require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/load_error' require 'active_support/core_ext/kernel' -module Dependencies #:nodoc: - extend self - - # Should we turn on Ruby warnings on the first load of dependent files? - mattr_accessor :warnings_on_first_load - self.warnings_on_first_load = false - - # All files ever loaded. - mattr_accessor :history - self.history = Set.new - - # All files currently loaded. - mattr_accessor :loaded - self.loaded = Set.new - - # Should we load files or require them? - mattr_accessor :mechanism - self.mechanism = :load - - # The set of directories from which we may automatically load files. Files - # under these directories will be reloaded on each request in development mode, - # unless the directory also appears in load_once_paths. - mattr_accessor :load_paths - self.load_paths = [] - - # The set of directories from which automatically loaded constants are loaded - # only once. All directories in this set must also be present in +load_paths+. - mattr_accessor :load_once_paths - self.load_once_paths = [] - - # An array of qualified constant names that have been loaded. Adding a name to - # this array will cause it to be unloaded the next time Dependencies are cleared. - mattr_accessor :autoloaded_constants - self.autoloaded_constants = [] - - # An array of constant names that need to be unloaded on every request. Used - # to allow arbitrary constants to be marked for unloading. - mattr_accessor :explicitly_unloadable_constants - self.explicitly_unloadable_constants = [] - - # Set to true to enable logging of const_missing and file loads - mattr_accessor :log_activity - self.log_activity = false - - # An internal stack used to record which constants are loaded by any block. - mattr_accessor :constant_watch_stack - self.constant_watch_stack = [] - - def load? - mechanism == :load - end - - def depend_on(file_name, swallow_load_errors = false) - path = search_for_file(file_name) - require_or_load(path || file_name) - rescue LoadError - raise unless swallow_load_errors - end - - def associate_with(file_name) - depend_on(file_name, true) - end +module ActiveSupport #:nodoc: + module Dependencies #:nodoc: + extend self + + # Should we turn on Ruby warnings on the first load of dependent files? + mattr_accessor :warnings_on_first_load + self.warnings_on_first_load = false + + # All files ever loaded. + mattr_accessor :history + self.history = Set.new + + # All files currently loaded. + mattr_accessor :loaded + self.loaded = Set.new + + # Should we load files or require them? + mattr_accessor :mechanism + self.mechanism = :load + + # The set of directories from which we may automatically load files. Files + # under these directories will be reloaded on each request in development mode, + # unless the directory also appears in load_once_paths. + mattr_accessor :load_paths + self.load_paths = [] + + # The set of directories from which automatically loaded constants are loaded + # only once. All directories in this set must also be present in +load_paths+. + mattr_accessor :load_once_paths + self.load_once_paths = [] + + # An array of qualified constant names that have been loaded. Adding a name to + # this array will cause it to be unloaded the next time Dependencies are cleared. + mattr_accessor :autoloaded_constants + self.autoloaded_constants = [] + + # An array of constant names that need to be unloaded on every request. Used + # to allow arbitrary constants to be marked for unloading. + mattr_accessor :explicitly_unloadable_constants + self.explicitly_unloadable_constants = [] + + # Set to true to enable logging of const_missing and file loads + mattr_accessor :log_activity + self.log_activity = false + + # An internal stack used to record which constants are loaded by any block. + mattr_accessor :constant_watch_stack + self.constant_watch_stack = [] + + def load? + mechanism == :load + end - def clear - log_call - loaded.clear - remove_unloadable_constants! - end + def depend_on(file_name, swallow_load_errors = false) + path = search_for_file(file_name) + require_or_load(path || file_name) + rescue LoadError + raise unless swallow_load_errors + end - def require_or_load(file_name, const_path = nil) - log_call file_name, const_path - file_name = $1 if file_name =~ /^(.*)\.rb$/ - expanded = File.expand_path(file_name) - return if loaded.include?(expanded) + def associate_with(file_name) + depend_on(file_name, true) + end - # Record that we've seen this file *before* loading it to avoid an - # infinite loop with mutual dependencies. - loaded << expanded + def clear + log_call + loaded.clear + remove_unloadable_constants! + end - begin - if load? - log "loading #{file_name}" + def require_or_load(file_name, const_path = nil) + log_call file_name, const_path + file_name = $1 if file_name =~ /^(.*)\.rb$/ + expanded = File.expand_path(file_name) + return if loaded.include?(expanded) - # Enable warnings iff this file has not been loaded before and - # warnings_on_first_load is set. - load_args = ["#{file_name}.rb"] - load_args << const_path unless const_path.nil? + # Record that we've seen this file *before* loading it to avoid an + # infinite loop with mutual dependencies. + loaded << expanded - if !warnings_on_first_load or history.include?(expanded) - result = load_file(*load_args) + begin + if load? + log "loading #{file_name}" + + # Enable warnings iff this file has not been loaded before and + # warnings_on_first_load is set. + load_args = ["#{file_name}.rb"] + load_args << const_path unless const_path.nil? + + if !warnings_on_first_load or history.include?(expanded) + result = load_file(*load_args) + else + enable_warnings { result = load_file(*load_args) } + end else - enable_warnings { result = load_file(*load_args) } + log "requiring #{file_name}" + result = require file_name end - else - log "requiring #{file_name}" - result = require file_name + rescue Exception + loaded.delete expanded + raise end - rescue Exception - loaded.delete expanded - raise - end - # Record history *after* loading so first load gets warnings. - history << expanded - return result - end + # Record history *after* loading so first load gets warnings. + history << expanded + return result + end - # Is the provided constant path defined? - def qualified_const_defined?(path) - raise NameError, "#{path.inspect} is not a valid constant name!" unless - /^(::)?([A-Z]\w*)(::[A-Z]\w*)*$/ =~ path + # Is the provided constant path defined? + def qualified_const_defined?(path) + raise NameError, "#{path.inspect} is not a valid constant name!" unless + /^(::)?([A-Z]\w*)(::[A-Z]\w*)*$/ =~ path - names = path.to_s.split('::') - names.shift if names.first.empty? + names = path.to_s.split('::') + names.shift if names.first.empty? - # We can't use defined? because it will invoke const_missing for the parent - # of the name we are checking. - names.inject(Object) do |mod, name| - return false unless uninherited_const_defined?(mod, name) - mod.const_get name + # We can't use defined? because it will invoke const_missing for the parent + # of the name we are checking. + names.inject(Object) do |mod, name| + return false unless uninherited_const_defined?(mod, name) + mod.const_get name + end + return true end - return true - end - if Module.method(:const_defined?).arity == 1 - # Does this module define this constant? - # Wrapper to accommodate changing Module#const_defined? in Ruby 1.9 - def uninherited_const_defined?(mod, const) - mod.const_defined?(const) - end - else - def uninherited_const_defined?(mod, const) #:nodoc: - mod.const_defined?(const, false) + if Module.method(:const_defined?).arity == 1 + # Does this module define this constant? + # Wrapper to accomodate changing Module#const_defined? in Ruby 1.9 + def uninherited_const_defined?(mod, const) + mod.const_defined?(const) + end + else + def uninherited_const_defined?(mod, const) #:nodoc: + mod.const_defined?(const, false) + end end - end - - # Given +path+, a filesystem path to a ruby file, return an array of constant - # paths which would cause Dependencies to attempt to load this file. - def loadable_constants_for_path(path, bases = load_paths) - path = $1 if path =~ /\A(.*)\.rb\Z/ - expanded_path = File.expand_path(path) - - bases.collect do |root| - expanded_root = File.expand_path(root) - next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path - - nesting = expanded_path[(expanded_root.size)..-1] - nesting = nesting[1..-1] if nesting && nesting[0] == ?/ - next if nesting.blank? - - [ - nesting.camelize, - # Special case: application.rb might define ApplicationControlller. - ('ApplicationController' if nesting == 'application') - ] - end.flatten.compact.uniq - end - # Search for a file in load_paths matching the provided suffix. - def search_for_file(path_suffix) - path_suffix = path_suffix + '.rb' unless path_suffix.ends_with? '.rb' - load_paths.each do |root| - path = File.join(root, path_suffix) - return path if File.file? path + # Given +path+, a filesystem path to a ruby file, return an array of constant + # paths which would cause Dependencies to attempt to load this file. + def loadable_constants_for_path(path, bases = load_paths) + path = $1 if path =~ /\A(.*)\.rb\Z/ + expanded_path = File.expand_path(path) + + bases.collect do |root| + expanded_root = File.expand_path(root) + next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path + + nesting = expanded_path[(expanded_root.size)..-1] + nesting = nesting[1..-1] if nesting && nesting[0] == ?/ + next if nesting.blank? + + [ + nesting.camelize, + # Special case: application.rb might define ApplicationControlller. + ('ApplicationController' if nesting == 'application') + ] + end.flatten.compact.uniq end - nil # Gee, I sure wish we had first_match ;-) - end - # Does the provided path_suffix correspond to an autoloadable module? - # Instead of returning a boolean, the autoload base for this module is returned. - def autoloadable_module?(path_suffix) - load_paths.each do |load_path| - return load_path if File.directory? File.join(load_path, path_suffix) + # Search for a file in load_paths matching the provided suffix. + def search_for_file(path_suffix) + path_suffix = path_suffix + '.rb' unless path_suffix.ends_with? '.rb' + load_paths.each do |root| + path = File.join(root, path_suffix) + return path if File.file? path + end + nil # Gee, I sure wish we had first_match ;-) end - nil - end - - def load_once_path?(path) - load_once_paths.any? { |base| path.starts_with? base } - end - # Attempt to autoload the provided module name by searching for a directory - # matching the expect path suffix. If found, the module is created and assigned - # to +into+'s constants with the name +const_name+. Provided that the directory - # was loaded from a reloadable base path, it is added to the set of constants - # that are to be unloaded. - def autoload_module!(into, const_name, qualified_name, path_suffix) - return nil unless base_path = autoloadable_module?(path_suffix) - mod = Module.new - into.const_set const_name, mod - autoloaded_constants << qualified_name unless load_once_paths.include?(base_path) - return mod - end - - # Load the file at the provided path. +const_paths+ is a set of qualified - # constant names. When loading the file, Dependencies will watch for the - # addition of these constants. Each that is defined will be marked as - # autoloaded, and will be removed when Dependencies.clear is next called. - # - # If the second parameter is left off, then Dependencies will construct a set - # of names that the file at +path+ may define. See - # +loadable_constants_for_path+ for more details. - def load_file(path, const_paths = loadable_constants_for_path(path)) - log_call path, const_paths - const_paths = [const_paths].compact unless const_paths.is_a? Array - parent_paths = const_paths.collect { |const_path| /(.*)::[^:]+\Z/ =~ const_path ? $1 : :Object } - - result = nil - newly_defined_paths = new_constants_in(*parent_paths) do - result = load_without_new_constant_marking path + # Does the provided path_suffix correspond to an autoloadable module? + # Instead of returning a boolean, the autoload base for this module is returned. + def autoloadable_module?(path_suffix) + load_paths.each do |load_path| + return load_path if File.directory? File.join(load_path, path_suffix) + end + nil end - autoloaded_constants.concat newly_defined_paths unless load_once_path?(path) - autoloaded_constants.uniq! - log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty? - return result - end + def load_once_path?(path) + load_once_paths.any? { |base| path.starts_with? base } + end - # Return the constant path for the provided parent and constant name. - def qualified_name_for(mod, name) - mod_name = to_constant_name mod - (%w(Object Kernel).include? mod_name) ? name.to_s : "#{mod_name}::#{name}" - end + # Attempt to autoload the provided module name by searching for a directory + # matching the expect path suffix. If found, the module is created and assigned + # to +into+'s constants with the name +const_name+. Provided that the directory + # was loaded from a reloadable base path, it is added to the set of constants + # that are to be unloaded. + def autoload_module!(into, const_name, qualified_name, path_suffix) + return nil unless base_path = autoloadable_module?(path_suffix) + mod = Module.new + into.const_set const_name, mod + autoloaded_constants << qualified_name unless load_once_paths.include?(base_path) + return mod + end - # Load the constant named +const_name+ which is missing from +from_mod+. If - # it is not possible to load the constant into from_mod, try its parent module - # using const_missing. - def load_missing_constant(from_mod, const_name) - log_call from_mod, const_name - if from_mod == Kernel - if ::Object.const_defined?(const_name) - log "Returning Object::#{const_name} for Kernel::#{const_name}" - return ::Object.const_get(const_name) - else - log "Substituting Object for Kernel" - from_mod = Object + # Load the file at the provided path. +const_paths+ is a set of qualified + # constant names. When loading the file, Dependencies will watch for the + # addition of these constants. Each that is defined will be marked as + # autoloaded, and will be removed when Dependencies.clear is next called. + # + # If the second parameter is left off, then Dependencies will construct a set + # of names that the file at +path+ may define. See + # +loadable_constants_for_path+ for more details. + def load_file(path, const_paths = loadable_constants_for_path(path)) + log_call path, const_paths + const_paths = [const_paths].compact unless const_paths.is_a? Array + parent_paths = const_paths.collect { |const_path| /(.*)::[^:]+\Z/ =~ const_path ? $1 : :Object } + + result = nil + newly_defined_paths = new_constants_in(*parent_paths) do + result = load_without_new_constant_marking path end - end - # If we have an anonymous module, all we can do is attempt to load from Object. - from_mod = Object if from_mod.name.blank? + autoloaded_constants.concat newly_defined_paths unless load_once_path?(path) + autoloaded_constants.uniq! + log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty? + return result + end - unless qualified_const_defined?(from_mod.name) && from_mod.name.constantize.object_id == from_mod.object_id - raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" + # Return the constant path for the provided parent and constant name. + def qualified_name_for(mod, name) + mod_name = to_constant_name mod + (%w(Object Kernel).include? mod_name) ? name.to_s : "#{mod_name}::#{name}" end - raise ArgumentError, "#{from_mod} is not missing constant #{const_name}!" if uninherited_const_defined?(from_mod, const_name) + # Load the constant named +const_name+ which is missing from +from_mod+. If + # it is not possible to load the constant into from_mod, try its parent module + # using const_missing. + def load_missing_constant(from_mod, const_name) + log_call from_mod, const_name + if from_mod == Kernel + if ::Object.const_defined?(const_name) + log "Returning Object::#{const_name} for Kernel::#{const_name}" + return ::Object.const_get(const_name) + else + log "Substituting Object for Kernel" + from_mod = Object + end + end - qualified_name = qualified_name_for from_mod, const_name - path_suffix = qualified_name.underscore - name_error = NameError.new("uninitialized constant #{qualified_name}") + # If we have an anonymous module, all we can do is attempt to load from Object. + from_mod = Object if from_mod.name.blank? - file_path = search_for_file(path_suffix) - if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load - require_or_load file_path - raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless uninherited_const_defined?(from_mod, const_name) - return from_mod.const_get(const_name) - elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix) - return mod - elsif (parent = from_mod.parent) && parent != from_mod && - ! from_mod.parents.any? { |p| uninherited_const_defined?(p, const_name) } - # If our parents do not have a constant named +const_name+ then we are free - # to attempt to load upwards. If they do have such a constant, then this - # const_missing must be due to from_mod::const_name, which should not - # return constants from from_mod's parents. - begin - return parent.const_missing(const_name) - rescue NameError => e - raise unless e.missing_name? qualified_name_for(parent, const_name) + unless qualified_const_defined?(from_mod.name) && from_mod.name.constantize.object_id == from_mod.object_id + raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" + end + + raise ArgumentError, "#{from_mod} is not missing constant #{const_name}!" if uninherited_const_defined?(from_mod, const_name) + + qualified_name = qualified_name_for from_mod, const_name + path_suffix = qualified_name.underscore + name_error = NameError.new("uninitialized constant #{qualified_name}") + + file_path = search_for_file(path_suffix) + if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load + require_or_load file_path + raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless uninherited_const_defined?(from_mod, const_name) + return from_mod.const_get(const_name) + elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix) + return mod + elsif (parent = from_mod.parent) && parent != from_mod && + ! from_mod.parents.any? { |p| uninherited_const_defined?(p, const_name) } + # If our parents do not have a constant named +const_name+ then we are free + # to attempt to load upwards. If they do have such a constant, then this + # const_missing must be due to from_mod::const_name, which should not + # return constants from from_mod's parents. + begin + return parent.const_missing(const_name) + rescue NameError => e + raise unless e.missing_name? qualified_name_for(parent, const_name) + raise name_error + end + else raise name_error end - else - raise name_error end - end - # Remove the constants that have been autoloaded, and those that have been - # marked for unloading. - def remove_unloadable_constants! - autoloaded_constants.each { |const| remove_constant const } - autoloaded_constants.clear - explicitly_unloadable_constants.each { |const| remove_constant const } - end + # Remove the constants that have been autoloaded, and those that have been + # marked for unloading. + def remove_unloadable_constants! + autoloaded_constants.each { |const| remove_constant const } + autoloaded_constants.clear + explicitly_unloadable_constants.each { |const| remove_constant const } + end - # Determine if the given constant has been automatically loaded. - def autoloaded?(desc) - # No name => anonymous module. - return false if desc.is_a?(Module) && desc.name.blank? - name = to_constant_name desc - return false unless qualified_const_defined? name - return autoloaded_constants.include?(name) - end + # Determine if the given constant has been automatically loaded. + def autoloaded?(desc) + # No name => anonymous module. + return false if desc.is_a?(Module) && desc.name.blank? + name = to_constant_name desc + return false unless qualified_const_defined? name + return autoloaded_constants.include?(name) + end - # Will the provided constant descriptor be unloaded? - def will_unload?(const_desc) - autoloaded?(desc) || - explicitly_unloadable_constants.include?(to_constant_name(const_desc)) - end + # Will the provided constant descriptor be unloaded? + def will_unload?(const_desc) + autoloaded?(desc) || + explicitly_unloadable_constants.include?(to_constant_name(const_desc)) + end - # Mark the provided constant name for unloading. This constant will be - # unloaded on each request, not just the next one. - def mark_for_unload(const_desc) - name = to_constant_name const_desc - if explicitly_unloadable_constants.include? name - return false - else - explicitly_unloadable_constants << name - return true + # Mark the provided constant name for unloading. This constant will be + # unloaded on each request, not just the next one. + def mark_for_unload(const_desc) + name = to_constant_name const_desc + if explicitly_unloadable_constants.include? name + return false + else + explicitly_unloadable_constants << name + return true + end end - end - # Run the provided block and detect the new constants that were loaded during - # its execution. Constants may only be regarded as 'new' once -- so if the - # block calls +new_constants_in+ again, then the constants defined within the - # inner call will not be reported in this one. - # - # If the provided block does not run to completion, and instead raises an - # exception, any new constants are regarded as being only partially defined - # and will be removed immediately. - def new_constants_in(*descs) - log_call(*descs) - - # Build the watch frames. Each frame is a tuple of - # [module_name_as_string, constants_defined_elsewhere] - watch_frames = descs.collect do |desc| - if desc.is_a? Module - mod_name = desc.name - initial_constants = desc.local_constant_names - elsif desc.is_a?(String) || desc.is_a?(Symbol) - mod_name = desc.to_s - - # Handle the case where the module has yet to be defined. - initial_constants = if qualified_const_defined?(mod_name) - mod_name.constantize.local_constant_names + # Run the provided block and detect the new constants that were loaded during + # its execution. Constants may only be regarded as 'new' once -- so if the + # block calls +new_constants_in+ again, then the constants defined within the + # inner call will not be reported in this one. + # + # If the provided block does not run to completion, and instead raises an + # exception, any new constants are regarded as being only partially defined + # and will be removed immediately. + def new_constants_in(*descs) + log_call(*descs) + + # Build the watch frames. Each frame is a tuple of + # [module_name_as_string, constants_defined_elsewhere] + watch_frames = descs.collect do |desc| + if desc.is_a? Module + mod_name = desc.name + initial_constants = desc.local_constant_names + elsif desc.is_a?(String) || desc.is_a?(Symbol) + mod_name = desc.to_s + + # Handle the case where the module has yet to be defined. + initial_constants = if qualified_const_defined?(mod_name) + mod_name.constantize.local_constant_names + else + [] + end else - [] + raise Argument, "#{desc.inspect} does not describe a module!" end - else - raise Argument, "#{desc.inspect} does not describe a module!" - end - [mod_name, initial_constants] - end + [mod_name, initial_constants] + end - constant_watch_stack.concat watch_frames + constant_watch_stack.concat watch_frames - aborting = true - begin - yield # Now yield to the code that is to define new constants. - aborting = false - ensure - # Find the new constants. - new_constants = watch_frames.collect do |mod_name, prior_constants| - # Module still doesn't exist? Treat it as if it has no constants. - next [] unless qualified_const_defined?(mod_name) - - mod = mod_name.constantize - next [] unless mod.is_a? Module - new_constants = mod.local_constant_names - prior_constants - - # Make sure no other frames takes credit for these constants. - constant_watch_stack.each do |frame_name, constants| - constants.concat new_constants if frame_name == mod_name + aborting = true + begin + yield # Now yield to the code that is to define new constants. + aborting = false + ensure + # Find the new constants. + new_constants = watch_frames.collect do |mod_name, prior_constants| + # Module still doesn't exist? Treat it as if it has no constants. + next [] unless qualified_const_defined?(mod_name) + + mod = mod_name.constantize + next [] unless mod.is_a? Module + new_constants = mod.local_constant_names - prior_constants + + # Make sure no other frames takes credit for these constants. + constant_watch_stack.each do |frame_name, constants| + constants.concat new_constants if frame_name == mod_name + end + + new_constants.collect do |suffix| + mod_name == "Object" ? suffix : "#{mod_name}::#{suffix}" + end + end.flatten + + log "New constants: #{new_constants * ', '}" + + if aborting + log "Error during loading, removing partially loaded constants " + new_constants.each { |name| remove_constant name } + new_constants.clear end + end - new_constants.collect do |suffix| - mod_name == "Object" ? suffix : "#{mod_name}::#{suffix}" + return new_constants + ensure + # Remove the stack frames that we added. + if defined?(watch_frames) && ! watch_frames.blank? + frame_ids = watch_frames.collect(&:object_id) + constant_watch_stack.delete_if do |watch_frame| + frame_ids.include? watch_frame.object_id end - end.flatten - - log "New constants: #{new_constants * ', '}" - - if aborting - log "Error during loading, removing partially loaded constants " - new_constants.each { |name| remove_constant name } - new_constants.clear end end - return new_constants - ensure - # Remove the stack frames that we added. - if defined?(watch_frames) && ! watch_frames.blank? - frame_ids = watch_frames.collect(&:object_id) - constant_watch_stack.delete_if do |watch_frame| - frame_ids.include? watch_frame.object_id + class LoadingModule #:nodoc: + # Old style environment.rb referenced this method directly. Please note, it doesn't + # actually *do* anything any more. + def self.root(*args) + if defined?(RAILS_DEFAULT_LOGGER) + RAILS_DEFAULT_LOGGER.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases." + RAILS_DEFAULT_LOGGER.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19" + end end end - end - class LoadingModule #:nodoc: - # Old style environment.rb referenced this method directly. Please note, it doesn't - # actually *do* anything any more. - def self.root(*args) - if defined?(RAILS_DEFAULT_LOGGER) - RAILS_DEFAULT_LOGGER.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases." - RAILS_DEFAULT_LOGGER.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19" + # Convert the provided const desc to a qualified constant name (as a string). + # A module, class, symbol, or string may be provided. + def to_constant_name(desc) #:nodoc: + name = case desc + when String then desc.starts_with?('::') ? desc[2..-1] : desc + when Symbol then desc.to_s + when Module + raise ArgumentError, "Anonymous modules have no name to be referenced by" if desc.name.blank? + desc.name + else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" end end - end - # Convert the provided const desc to a qualified constant name (as a string). - # A module, class, symbol, or string may be provided. - def to_constant_name(desc) #:nodoc: - name = case desc - when String then desc.starts_with?('::') ? desc[2..-1] : desc - when Symbol then desc.to_s - when Module - raise ArgumentError, "Anonymous modules have no name to be referenced by" if desc.name.blank? - desc.name - else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" - end - end + def remove_constant(const) #:nodoc: + return false unless qualified_const_defined? const - def remove_constant(const) #:nodoc: - return false unless qualified_const_defined? const + const = $1 if /\A::(.*)\Z/ =~ const.to_s + names = const.to_s.split('::') + if names.size == 1 # It's under Object + parent = Object + else + parent = (names[0..-2] * '::').constantize + end - const = $1 if /\A::(.*)\Z/ =~ const.to_s - names = const.to_s.split('::') - if names.size == 1 # It's under Object - parent = Object - else - parent = (names[0..-2] * '::').constantize + log "removing constant #{const}" + parent.instance_eval { remove_const names.last } + return true end - log "removing constant #{const}" - parent.instance_eval { remove_const names.last } - return true - end - -protected - def log_call(*args) - if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER && log_activity - arg_str = args.collect(&:inspect) * ', ' - /in `([a-z_\?\!]+)'/ =~ caller(1).first - selector = $1 || '<unknown>' - log "called #{selector}(#{arg_str})" - end - end + protected + def log_call(*args) + if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER && log_activity + arg_str = args.collect(&:inspect) * ', ' + /in `([a-z_\?\!]+)'/ =~ caller(1).first + selector = $1 || '<unknown>' + log "called #{selector}(#{arg_str})" + end + end - def log(msg) - if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER && log_activity - RAILS_DEFAULT_LOGGER.debug "Dependencies: #{msg}" - end + def log(msg) + if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER && log_activity + RAILS_DEFAULT_LOGGER.debug "Dependencies: #{msg}" + end + end end - end Object.instance_eval do - define_method(:require_or_load) { |file_name| Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load) - define_method(:require_dependency) { |file_name| Dependencies.depend_on(file_name) } unless Object.respond_to?(:require_dependency) - define_method(:require_association) { |file_name| Dependencies.associate_with(file_name) } unless Object.respond_to?(:require_association) + define_method(:require_or_load) { |file_name| ActiveSupport::Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load) + define_method(:require_dependency) { |file_name| ActiveSupport::Dependencies.depend_on(file_name) } unless Object.respond_to?(:require_dependency) + define_method(:require_association) { |file_name| ActiveSupport::Dependencies.associate_with(file_name) } unless Object.respond_to?(:require_association) end class Module #:nodoc: @@ -464,7 +465,7 @@ class Module #:nodoc: # Use const_missing to autoload associations so we don't have to # require_association when using single-table inheritance. def const_missing(class_id) - Dependencies.load_missing_constant self, class_id + ActiveSupport::Dependencies.load_missing_constant self, class_id end def unloadable(const_desc = self) @@ -480,15 +481,15 @@ class Class else begin begin - Dependencies.load_missing_constant self, const_name + ActiveSupport::Dependencies.load_missing_constant self, const_name rescue NameError parent.send :const_missing, const_name end rescue NameError => e # Make sure that the name we are missing is the one that caused the error - parent_qualified_name = Dependencies.qualified_name_for parent, const_name + parent_qualified_name = ActiveSupport::Dependencies.qualified_name_for parent, const_name raise unless e.missing_name? parent_qualified_name - qualified_name = Dependencies.qualified_name_for self, const_name + qualified_name = ActiveSupport::Dependencies.qualified_name_for self, const_name raise NameError.new("uninitialized constant #{qualified_name}").copy_blame!(e) end end @@ -499,14 +500,14 @@ class Object alias_method :load_without_new_constant_marking, :load def load(file, *extras) #:nodoc: - Dependencies.new_constants_in(Object) { super } + ActiveSupport::Dependencies.new_constants_in(Object) { super } rescue Exception => exception # errors from loading file exception.blame_file! file raise end def require(file, *extras) #:nodoc: - Dependencies.new_constants_in(Object) { super } + ActiveSupport::Dependencies.new_constants_in(Object) { super } rescue Exception => exception # errors from required file exception.blame_file! file raise @@ -526,7 +527,7 @@ class Object # Returns true if the constant was not previously marked for unloading, false # otherwise. def unloadable(const_desc) - Dependencies.mark_for_unload const_desc + ActiveSupport::Dependencies.mark_for_unload const_desc end end diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index 758aef5445..ebdaf86146 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -144,17 +144,11 @@ module ActiveSupport end end - # Stand-in for <tt>@request</tt>, <tt>@attributes</tt>, <tt>@params</tt>, etc. - # which emits deprecation warnings on any method call (except +inspect+). - class DeprecatedInstanceVariableProxy #:nodoc: + class DeprecationProxy #:nodoc: silence_warnings do instance_methods.each { |m| undef_method m unless m =~ /^__/ } end - def initialize(instance, method, var = "@#{method}") - @instance, @method, @var = instance, method, var - end - # Don't give a deprecation warning on inspect since test/unit and error # logs rely on it for diagnostics. def inspect @@ -166,7 +160,16 @@ module ActiveSupport warn caller, called, args target.__send__(called, *args, &block) end + end + # Stand-in for <tt>@request</tt>, <tt>@attributes</tt>, <tt>@params</tt>, etc. + # which emits deprecation warnings on any method call (except +inspect+). + class DeprecatedInstanceVariableProxy < DeprecationProxy #:nodoc: + def initialize(instance, method, var = "@#{method}") + @instance, @method, @var = instance, method, var + end + + private def target @instance.__send__(@method) end @@ -176,6 +179,21 @@ module ActiveSupport end end + class DeprecatedConstantProxy < DeprecationProxy #:nodoc: + def initialize(old_const, new_const) + @old_const = old_const + @new_const = new_const + end + + private + def target + @new_const.to_s.constantize + end + + def warn(callstack, called, args) + ActiveSupport::Deprecation.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack) + end + end end end diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index 967722c2bf..b6d276953a 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -1,53 +1,55 @@ -Inflector.inflections do |inflect| - inflect.plural(/$/, 's') - inflect.plural(/s$/i, 's') - inflect.plural(/(ax|test)is$/i, '\1es') - inflect.plural(/(octop|vir)us$/i, '\1i') - inflect.plural(/(alias|status)$/i, '\1es') - inflect.plural(/(bu)s$/i, '\1ses') - inflect.plural(/(buffal|tomat)o$/i, '\1oes') - inflect.plural(/([ti])um$/i, '\1a') - inflect.plural(/sis$/i, 'ses') - inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves') - inflect.plural(/(hive)$/i, '\1s') - inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies') - inflect.plural(/(x|ch|ss|sh)$/i, '\1es') - inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices') - inflect.plural(/([m|l])ouse$/i, '\1ice') - inflect.plural(/^(ox)$/i, '\1en') - inflect.plural(/(quiz)$/i, '\1zes') +module ActiveSupport + Inflector.inflections do |inflect| + inflect.plural(/$/, 's') + inflect.plural(/s$/i, 's') + inflect.plural(/(ax|test)is$/i, '\1es') + inflect.plural(/(octop|vir)us$/i, '\1i') + inflect.plural(/(alias|status)$/i, '\1es') + inflect.plural(/(bu)s$/i, '\1ses') + inflect.plural(/(buffal|tomat)o$/i, '\1oes') + inflect.plural(/([ti])um$/i, '\1a') + inflect.plural(/sis$/i, 'ses') + inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves') + inflect.plural(/(hive)$/i, '\1s') + inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies') + inflect.plural(/(x|ch|ss|sh)$/i, '\1es') + inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices') + inflect.plural(/([m|l])ouse$/i, '\1ice') + inflect.plural(/^(ox)$/i, '\1en') + inflect.plural(/(quiz)$/i, '\1zes') - inflect.singular(/s$/i, '') - inflect.singular(/(n)ews$/i, '\1ews') - inflect.singular(/([ti])a$/i, '\1um') - inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis') - inflect.singular(/(^analy)ses$/i, '\1sis') - inflect.singular(/([^f])ves$/i, '\1fe') - inflect.singular(/(hive)s$/i, '\1') - inflect.singular(/(tive)s$/i, '\1') - inflect.singular(/([lr])ves$/i, '\1f') - inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y') - inflect.singular(/(s)eries$/i, '\1eries') - inflect.singular(/(m)ovies$/i, '\1ovie') - inflect.singular(/(x|ch|ss|sh)es$/i, '\1') - inflect.singular(/([m|l])ice$/i, '\1ouse') - inflect.singular(/(bus)es$/i, '\1') - inflect.singular(/(o)es$/i, '\1') - inflect.singular(/(shoe)s$/i, '\1') - inflect.singular(/(cris|ax|test)es$/i, '\1is') - inflect.singular(/(octop|vir)i$/i, '\1us') - inflect.singular(/(alias|status)es$/i, '\1') - inflect.singular(/^(ox)en/i, '\1') - inflect.singular(/(vert|ind)ices$/i, '\1ex') - inflect.singular(/(matr)ices$/i, '\1ix') - inflect.singular(/(quiz)zes$/i, '\1') + inflect.singular(/s$/i, '') + inflect.singular(/(n)ews$/i, '\1ews') + inflect.singular(/([ti])a$/i, '\1um') + inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis') + inflect.singular(/(^analy)ses$/i, '\1sis') + inflect.singular(/([^f])ves$/i, '\1fe') + inflect.singular(/(hive)s$/i, '\1') + inflect.singular(/(tive)s$/i, '\1') + inflect.singular(/([lr])ves$/i, '\1f') + inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y') + inflect.singular(/(s)eries$/i, '\1eries') + inflect.singular(/(m)ovies$/i, '\1ovie') + inflect.singular(/(x|ch|ss|sh)es$/i, '\1') + inflect.singular(/([m|l])ice$/i, '\1ouse') + inflect.singular(/(bus)es$/i, '\1') + inflect.singular(/(o)es$/i, '\1') + inflect.singular(/(shoe)s$/i, '\1') + inflect.singular(/(cris|ax|test)es$/i, '\1is') + inflect.singular(/(octop|vir)i$/i, '\1us') + inflect.singular(/(alias|status)es$/i, '\1') + inflect.singular(/^(ox)en/i, '\1') + inflect.singular(/(vert|ind)ices$/i, '\1ex') + inflect.singular(/(matr)ices$/i, '\1ix') + inflect.singular(/(quiz)zes$/i, '\1') - inflect.irregular('person', 'people') - inflect.irregular('man', 'men') - inflect.irregular('child', 'children') - inflect.irregular('sex', 'sexes') - inflect.irregular('move', 'moves') - inflect.irregular('cow', 'kine') + inflect.irregular('person', 'people') + inflect.irregular('man', 'men') + inflect.irregular('child', 'children') + inflect.irregular('sex', 'sexes') + inflect.irregular('move', 'moves') + inflect.irregular('cow', 'kine') - inflect.uncountable(%w(equipment information rice money species series fish sheep)) + inflect.uncountable(%w(equipment information rice money species series fish sheep)) + end end diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb index d7f44cb2b9..47bd6e1767 100644 --- a/activesupport/lib/active_support/inflector.rb +++ b/activesupport/lib/active_support/inflector.rb @@ -1,308 +1,310 @@ require 'singleton' -# The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, -# and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept -# in inflections.rb. -# -# The Rails core team has stated patches for the inflections library will not be accepted -# in order to avoid breaking legacy applications which may be relying on errant inflections. -# If you discover an incorrect inflection and require it for your application, you'll need -# to correct it yourself (explained below). -module Inflector - # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional - # inflection rules. Examples: +module ActiveSupport + # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, + # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept + # in inflections.rb. # - # Inflector.inflections do |inflect| - # inflect.plural /^(ox)$/i, '\1\2en' - # inflect.singular /^(ox)en/i, '\1' - # - # inflect.irregular 'octopus', 'octopi' - # - # inflect.uncountable "equipment" - # end - # - # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the - # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may - # already have been loaded. - class Inflections - include Singleton + # The Rails core team has stated patches for the inflections library will not be accepted + # in order to avoid breaking legacy applications which may be relying on errant inflections. + # If you discover an incorrect inflection and require it for your application, you'll need + # to correct it yourself (explained below). + module Inflector + extend self - attr_reader :plurals, :singulars, :uncountables + # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional + # inflection rules. Examples: + # + # ActiveSupport::Inflector.inflections do |inflect| + # inflect.plural /^(ox)$/i, '\1\2en' + # inflect.singular /^(ox)en/i, '\1' + # + # inflect.irregular 'octopus', 'octopi' + # + # inflect.uncountable "equipment" + # end + # + # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the + # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may + # already have been loaded. + class Inflections + include Singleton - def initialize - @plurals, @singulars, @uncountables = [], [], [] - end + attr_reader :plurals, :singulars, :uncountables + + def initialize + @plurals, @singulars, @uncountables = [], [], [] + end + + # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression. + # The replacement should always be a string that may include references to the matched data from the rule. + def plural(rule, replacement) + @plurals.insert(0, [rule, replacement]) + end + + # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression. + # The replacement should always be a string that may include references to the matched data from the rule. + def singular(rule, replacement) + @singulars.insert(0, [rule, replacement]) + end + + # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used + # for strings, not regular expressions. You simply pass the irregular in singular and plural form. + # + # Examples: + # irregular 'octopus', 'octopi' + # irregular 'person', 'people' + def irregular(singular, plural) + if singular[0,1].upcase == plural[0,1].upcase + plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1]) + singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1]) + else + plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1]) + plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1]) + singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1]) + singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1]) + end + end - # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression. - # The replacement should always be a string that may include references to the matched data from the rule. - def plural(rule, replacement) - @plurals.insert(0, [rule, replacement]) + # Add uncountable words that shouldn't be attempted inflected. + # + # Examples: + # uncountable "money" + # uncountable "money", "information" + # uncountable %w( money information rice ) + def uncountable(*words) + (@uncountables << words).flatten! + end + + # Clears the loaded inflections within a given scope (default is <tt>:all</tt>). + # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>, + # <tt>:singulars</tt>, <tt>:uncountables</tt>. + # + # Examples: + # clear :all + # clear :plurals + def clear(scope = :all) + case scope + when :all + @plurals, @singulars, @uncountables = [], [], [] + else + instance_variable_set "@#{scope}", [] + end + end end - # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression. - # The replacement should always be a string that may include references to the matched data from the rule. - def singular(rule, replacement) - @singulars.insert(0, [rule, replacement]) + # Yields a singleton instance of Inflector::Inflections so you can specify additional + # inflector rules. + # + # Example: + # ActiveSupport::Inflector.inflections do |inflect| + # inflect.uncountable "rails" + # end + def inflections + if block_given? + yield Inflections.instance + else + Inflections.instance + end end - # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used - # for strings, not regular expressions. You simply pass the irregular in singular and plural form. + # Returns the plural form of the word in the string. # # Examples: - # irregular 'octopus', 'octopi' - # irregular 'person', 'people' - def irregular(singular, plural) - if singular[0,1].upcase == plural[0,1].upcase - plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1]) - singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1]) + # "post".pluralize # => "posts" + # "octopus".pluralize # => "octopi" + # "sheep".pluralize # => "sheep" + # "words".pluralize # => "words" + # "the blue mailman".pluralize # => "the blue mailmen" + # "CamelOctopus".pluralize # => "CamelOctopi" + def pluralize(word) + result = word.to_s.dup + + if word.empty? || inflections.uncountables.include?(result.downcase) + result else - plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1]) - plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1]) - singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1]) - singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1]) + inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result end end - # Add uncountable words that shouldn't be attempted inflected. + # The reverse of +pluralize+, returns the singular form of a word in a string. # # Examples: - # uncountable "money" - # uncountable "money", "information" - # uncountable %w( money information rice ) - def uncountable(*words) - (@uncountables << words).flatten! + # "posts".singularize # => "post" + # "octopi".singularize # => "octopus" + # "sheep".singluarize # => "sheep" + # "word".singularize # => "word" + # "the blue mailmen".singularize # => "the blue mailman" + # "CamelOctopi".singularize # => "CamelOctopus" + def singularize(word) + result = word.to_s.dup + + if inflections.uncountables.include?(result.downcase) + result + else + inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end end - # Clears the loaded inflections within a given scope (default is <tt>:all</tt>). - # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>, - # <tt>:singulars</tt>, <tt>:uncountables</tt>. + # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+ + # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase. + # + # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces. # # Examples: - # clear :all - # clear :plurals - def clear(scope = :all) - case scope - when :all - @plurals, @singulars, @uncountables = [], [], [] - else - instance_variable_set "@#{scope}", [] + # "active_record".camelize # => "ActiveRecord" + # "active_record".camelize(:lower) # => "activeRecord" + # "active_record/errors".camelize # => "ActiveRecord::Errors" + # "active_record/errors".camelize(:lower) # => "activeRecord::Errors" + def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) + if first_letter_in_uppercase + lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } + else + lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1] end end - end - - extend self - # Yields a singleton instance of Inflector::Inflections so you can specify additional - # inflector rules. - # - # Example: - # Inflector.inflections do |inflect| - # inflect.uncountable "rails" - # end - def inflections - if block_given? - yield Inflections.instance - else - Inflections.instance + # Capitalizes all the words and replaces some characters in the string to create + # a nicer looking title. +titleize+ is meant for creating pretty output. It is not + # used in the Rails internals. + # + # +titleize+ is also aliased as as +titlecase+. + # + # Examples: + # "man from the boondocks".titleize # => "Man From The Boondocks" + # "x-men: the last stand".titleize # => "X Men: The Last Stand" + def titleize(word) + humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } end - end - - # Returns the plural form of the word in the string. - # - # Examples: - # "post".pluralize # => "posts" - # "octopus".pluralize # => "octopi" - # "sheep".pluralize # => "sheep" - # "words".pluralize # => "words" - # "the blue mailman".pluralize # => "the blue mailmen" - # "CamelOctopus".pluralize # => "CamelOctopi" - def pluralize(word) - result = word.to_s.dup - if word.empty? || inflections.uncountables.include?(result.downcase) - result - else - inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result + # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string. + # + # Changes '::' to '/' to convert namespaces to paths. + # + # Examples: + # "ActiveRecord".underscore # => "active_record" + # "ActiveRecord::Errors".underscore # => active_record/errors + def underscore(camel_cased_word) + camel_cased_word.to_s.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + tr("-", "_"). + downcase end - end - - # The reverse of +pluralize+, returns the singular form of a word in a string. - # - # Examples: - # "posts".singularize # => "post" - # "octopi".singularize # => "octopus" - # "sheep".singularize # => "sheep" - # "word".singularize # => "word" - # "the blue mailmen".singularize # => "the blue mailman" - # "CamelOctopi".singularize # => "CamelOctopus" - def singularize(word) - result = word.to_s.dup - if inflections.uncountables.include?(result.downcase) - result - else - inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result + # Replaces underscores with dashes in the string. + # + # Example: + # "puni_puni" # => "puni-puni" + def dasherize(underscored_word) + underscored_word.gsub(/_/, '-') end - end - # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+ - # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase. - # - # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces. - # - # Examples: - # "active_record".camelize # => "ActiveRecord" - # "active_record".camelize(:lower) # => "activeRecord" - # "active_record/errors".camelize # => "ActiveRecord::Errors" - # "active_record/errors".camelize(:lower) # => "activeRecord::Errors" - def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) - if first_letter_in_uppercase - lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } - else - lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1] + # Capitalizes the first word and turns underscores into spaces and strips a + # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. + # + # Examples: + # "employee_salary" # => "Employee salary" + # "author_id" # => "Author" + def humanize(lower_case_and_underscored_word) + lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize end - end - # Capitalizes all the words and replaces some characters in the string to create - # a nicer looking title. +titleize+ is meant for creating pretty output. It is not - # used in the Rails internals. - # - # +titleize+ is also aliased as as +titlecase+. - # - # Examples: - # "man from the boondocks".titleize # => "Man From The Boondocks" - # "x-men: the last stand".titleize # => "X Men: The Last Stand" - def titleize(word) - humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } - end - - # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string. - # - # Changes '::' to '/' to convert namespaces to paths. - # - # Examples: - # "ActiveRecord".underscore # => "active_record" - # "ActiveRecord::Errors".underscore # => active_record/errors - def underscore(camel_cased_word) - camel_cased_word.to_s.gsub(/::/, '/'). - gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). - gsub(/([a-z\d])([A-Z])/,'\1_\2'). - tr("-", "_"). - downcase - end - - # Replaces underscores with dashes in the string. - # - # Example: - # "puni_puni" # => "puni-puni" - def dasherize(underscored_word) - underscored_word.gsub(/_/, '-') - end - - # Capitalizes the first word and turns underscores into spaces and strips a - # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. - # - # Examples: - # "employee_salary" # => "Employee salary" - # "author_id" # => "Author" - def humanize(lower_case_and_underscored_word) - lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize - end + # Removes the module part from the expression in the string. + # + # Examples: + # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections" + # "Inflections".demodulize # => "Inflections" + def demodulize(class_name_in_module) + class_name_in_module.to_s.gsub(/^.*::/, '') + end - # Removes the module part from the expression in the string. - # - # Examples: - # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections" - # "Inflections".demodulize # => "Inflections" - def demodulize(class_name_in_module) - class_name_in_module.to_s.gsub(/^.*::/, '') - end + # Create the name of a table like Rails does for models to table names. This method + # uses the +pluralize+ method on the last word in the string. + # + # Examples + # "RawScaledScorer".tableize # => "raw_scaled_scorers" + # "egg_and_ham".tableize # => "egg_and_hams" + # "fancyCategory".tableize # => "fancy_categories" + def tableize(class_name) + pluralize(underscore(class_name)) + end - # Create the name of a table like Rails does for models to table names. This method - # uses the +pluralize+ method on the last word in the string. - # - # Examples - # "RawScaledScorer".tableize # => "raw_scaled_scorers" - # "egg_and_ham".tableize # => "egg_and_hams" - # "fancyCategory".tableize # => "fancy_categories" - def tableize(class_name) - pluralize(underscore(class_name)) - end + # Create a class name from a plural table name like Rails does for table names to models. + # Note that this returns a string and not a Class. (To convert to an actual class + # follow +classify+ with +constantize+.) + # + # Examples: + # "egg_and_hams".classify # => "EggAndHam" + # "posts".classify # => "Post" + # + # Singular names are not handled correctly: + # "business".classify # => "Busines" + def classify(table_name) + # strip out any leading schema name + camelize(singularize(table_name.to_s.sub(/.*\./, ''))) + end - # Create a class name from a plural table name like Rails does for table names to models. - # Note that this returns a string and not a Class. (To convert to an actual class - # follow +classify+ with +constantize+.) - # - # Examples: - # "egg_and_hams".classify # => "EggAndHam" - # "posts".classify # => "Post" - # - # Singular names are not handled correctly: - # "business".classify # => "Busines" - def classify(table_name) - # strip out any leading schema name - camelize(singularize(table_name.to_s.sub(/.*\./, ''))) - end + # Creates a foreign key name from a class name. + # +separate_class_name_and_id_with_underscore+ sets whether + # the method should put '_' between the name and 'id'. + # + # Examples: + # "Message".foreign_key # => "message_id" + # "Message".foreign_key(false) # => "messageid" + # "Admin::Post".foreign_key # => "post_id" + def foreign_key(class_name, separate_class_name_and_id_with_underscore = true) + underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") + end - # Creates a foreign key name from a class name. - # +separate_class_name_and_id_with_underscore+ sets whether - # the method should put '_' between the name and 'id'. - # - # Examples: - # "Message".foreign_key # => "message_id" - # "Message".foreign_key(false) # => "messageid" - # "Admin::Post".foreign_key # => "post_id" - def foreign_key(class_name, separate_class_name_and_id_with_underscore = true) - underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") - end + # Tries to find a constant with the name specified in the argument string: + # + # "Module".constantize # => Module + # "Test::Unit".constantize # => Test::Unit + # + # The name is assumed to be the one of a top-level constant, no matter whether + # it starts with "::" or not. No lexical context is taken into account: + # + # C = 'outside' + # module M + # C = 'inside' + # C # => 'inside' + # "C".constantize # => 'outside', same as ::C + # end + # + # NameError is raised when the name is not in CamelCase or the constant is + # unknown. + def constantize(camel_cased_word) + unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word + raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!" + end - # Tries to find a constant with the name specified in the argument string: - # - # "Module".constantize # => Module - # "Test::Unit".constantize # => Test::Unit - # - # The name is assumed to be the one of a top-level constant, no matter whether - # it starts with "::" or not. No lexical context is taken into account: - # - # C = 'outside' - # module M - # C = 'inside' - # C # => 'inside' - # "C".constantize # => 'outside', same as ::C - # end - # - # NameError is raised when the name is not in CamelCase or the constant is - # unknown. - def constantize(camel_cased_word) - unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word - raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!" + Object.module_eval("::#{$1}", __FILE__, __LINE__) end - Object.module_eval("::#{$1}", __FILE__, __LINE__) - end - - # Turns a number into an ordinal string used to denote the position in an - # ordered sequence such as 1st, 2nd, 3rd, 4th. - # - # Examples: - # ordinalize(1) # => "1st" - # ordinalize(2) # => "2nd" - # ordinalize(1002) # => "1002nd" - # ordinalize(1003) # => "1003rd" - def ordinalize(number) - if (11..13).include?(number.to_i % 100) - "#{number}th" - else - case number.to_i % 10 - when 1; "#{number}st" - when 2; "#{number}nd" - when 3; "#{number}rd" - else "#{number}th" + # Turns a number into an ordinal string used to denote the position in an + # ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # Examples: + # ordinalize(1) # => "1st" + # ordinalize(2) # => "2nd" + # ordinalize(1002) # => "1002nd" + # ordinalize(1003) # => "1003rd" + def ordinalize(number) + if (11..13).include?(number.to_i % 100) + "#{number}th" + else + case number.to_i % 10 + when 1; "#{number}st" + when 2; "#{number}nd" + when 3; "#{number}rd" + else "#{number}th" + end end end end end -require File.dirname(__FILE__) + '/inflections' +require 'active_support/inflections' diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 6993621ef9..9757054e43 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -12,6 +12,7 @@ module ActiveSupport else self << [key, value] end + value end def [](key) @@ -38,6 +39,20 @@ module ActiveSupport each { |array| hash[array[0]] = array[1] } end end + + def has_key?(k) + !assoc(k).nil? + end + + alias_method :key?, :has_key? + alias_method :include?, :has_key? + alias_method :member?, :has_key? + + def has_value?(v) + any? { |key, value| value == v } + end + + alias_method :value?, :has_value? end end end diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 306376e9ae..642045186f 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -1,17 +1,19 @@ -class OrderedOptions < ActiveSupport::OrderedHash #:nodoc: - def []=(key, value) - super(key.to_sym, value) - end +module ActiveSupport #:nodoc: + class OrderedOptions < OrderedHash #:nodoc: + def []=(key, value) + super(key.to_sym, value) + end - def [](key) - super(key.to_sym) - end + def [](key) + super(key.to_sym) + end - def method_missing(name, *args) - if name.to_s =~ /(.*)=$/ - self[$1.to_sym] = args.first - else - self[name] + def method_missing(name, *args) + if name.to_s =~ /(.*)=$/ + self[$1.to_sym] = args.first + else + self[name] + end end end end diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb new file mode 100644 index 0000000000..65545748df --- /dev/null +++ b/activesupport/lib/active_support/string_inquirer.rb @@ -0,0 +1,11 @@ +module ActiveSupport + class StringInquirer < String + def method_missing(method_name, *arguments) + if method_name.to_s.ends_with?("?") + self == method_name.to_s[0..-2] + else + super + end + end + end +end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 67cde1556c..2fd02d5313 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -9,5 +9,14 @@ end module ActiveSupport class TestCase < Test::Unit::TestCase + # test "verify something" do + # ... + # end + def self.test(name, &block) + test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym + defined = instance_method(test_name) rescue false + raise "#{test_name} is already defined in #{self}" if defined + define_method(test_name, &block) + end end end diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb new file mode 100644 index 0000000000..5f2027eb3b --- /dev/null +++ b/activesupport/lib/active_support/testing/performance.rb @@ -0,0 +1,397 @@ +require 'rubygems' +gem 'ruby-prof', '>= 0.6.1' +require 'ruby-prof' + +require 'fileutils' +require 'rails/version' + +module ActiveSupport + module Testing + module Performance + DEFAULTS = + if benchmark = ARGV.include?('--benchmark') # HAX for rake test + { :benchmark => true, + :runs => 10, + :metrics => [:process_time, :memory, :objects, :gc_runs, :gc_time], + :output => 'tmp/performance' } + else + { :benchmark => false, + :runs => 1, + :min_percent => 0.02, + :metrics => [:process_time, :memory, :objects], + :formats => [:flat, :graph_html, :call_tree], + :output => 'tmp/performance' } + end + + def self.included(base) + base.class_inheritable_hash :profile_options + base.profile_options = DEFAULTS.dup + end + + def full_test_name + "#{self.class.name}##{method_name}" + end + + def run(result) + return if method_name =~ /^default_test$/ + + yield(self.class::STARTED, name) + @_result = result + + run_warmup + profile_options[:metrics].each do |metric_name| + if klass = Metrics[metric_name.to_sym] + run_profile(klass.new) + result.add_run + else + $stderr.puts '%20s: unsupported' % metric_name.to_s + end + end + + yield(self.class::FINISHED, name) + end + + def run_test(metric, mode) + run_callbacks :setup + setup + metric.send(mode) { __send__ @method_name } + rescue ::Test::Unit::AssertionFailedError => e + add_failure(e.message, e.backtrace) + rescue StandardError, ScriptError + add_error($!) + ensure + begin + teardown + run_callbacks :teardown, :enumerator => :reverse_each + rescue ::Test::Unit::AssertionFailedError => e + add_failure(e.message, e.backtrace) + rescue StandardError, ScriptError + add_error($!) + end + end + + protected + def run_warmup + 5.times { GC.start } + + time = Metrics::Time.new + run_test(time, :benchmark) + puts "%s (%s warmup)" % [full_test_name, time.format(time.total)] + + 5.times { GC.start } + end + + def run_profile(metric) + klass = profile_options[:benchmark] ? Benchmarker : Profiler + performer = klass.new(self, metric) + + performer.run + puts performer.report + performer.record + end + + class Performer + delegate :run_test, :profile_options, :full_test_name, :to => :@harness + + def initialize(harness, metric) + @harness, @metric = harness, metric + end + + def report + rate = @total / profile_options[:runs] + '%20s: %s' % [@metric.name, @metric.format(rate)] + end + + protected + def output_filename + "#{profile_options[:output]}/#{full_test_name}_#{@metric.name}" + end + end + + class Benchmarker < Performer + def run + profile_options[:runs].to_i.times { run_test(@metric, :benchmark) } + @total = @metric.total + end + + def record + avg = @metric.total / profile_options[:runs].to_i + now = Time.now.utc.xmlschema + with_output_file do |file| + file.puts "#{avg},#{now},#{environment}" + end + end + + def environment + unless defined? @env + app = "#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/ + + rails = Rails::VERSION::STRING + if File.directory?('vendor/rails/.git') + Dir.chdir('vendor/rails') do + rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/ + end + end + + ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby' + ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" + + @env = [app, rails, ruby, RUBY_PLATFORM] * ',' + end + + @env + end + + protected + HEADER = 'measurement,created_at,app,rails,ruby,platform' + + def with_output_file + fname = output_filename + + if new = !File.exist?(fname) + FileUtils.mkdir_p(File.dirname(fname)) + end + + File.open(fname, 'ab') do |file| + file.puts(HEADER) if new + yield file + end + end + + def output_filename + "#{super}.csv" + end + end + + class Profiler < Performer + def run + RubyProf.measure_mode = @metric.measure_mode + RubyProf.start + RubyProf.pause + profile_options[:runs].to_i.times { run_test(@metric, :profile) } + @data = RubyProf.stop + @total = @data.threads.values.sum(0) { |method_infos| method_infos.sort.last.total_time } + end + + def record + klasses = profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact + + klasses.each do |klass| + fname = output_filename(klass) + FileUtils.mkdir_p(File.dirname(fname)) + File.open(fname, 'wb') do |file| + klass.new(@data).print(file, profile_options.slice(:min_percent)) + end + end + end + + protected + def output_filename(printer_class) + suffix = + case printer_class.name.demodulize + when 'FlatPrinter'; 'flat.txt' + when 'GraphPrinter'; 'graph.txt' + when 'GraphHtmlPrinter'; 'graph.html' + when 'CallTreePrinter'; 'tree.txt' + else printer_class.name.sub(/Printer$/, '').underscore + end + + "#{super()}_#{suffix}" + end + end + + module Metrics + def self.[](name) + klass = const_get(name.to_s.camelize) + klass if klass::Mode + rescue NameError + nil + end + + class Base + attr_reader :total + + def initialize + @total = 0 + end + + def name + @name ||= self.class.name.demodulize.underscore + end + + def measure_mode + self.class::Mode + end + + def measure + 0 + end + + def benchmark + with_gc_stats do + before = measure + yield + @total += (measure - before) + end + end + + def profile + RubyProf.resume + yield + ensure + RubyProf.pause + end + + protected + if GC.respond_to?(:enable_stats) + def with_gc_stats + GC.enable_stats + yield + ensure + GC.disable_stats + end + else + def with_gc_stats + yield + end + end + end + + class Time < Base + def measure + ::Time.now.to_f + end + + def format(measurement) + if measurement < 2 + '%d ms' % (measurement * 1000) + else + '%.2f sec' % measurement + end + end + end + + class ProcessTime < Time + Mode = RubyProf::PROCESS_TIME + + def measure + RubyProf.measure_process_time + end + end + + class WallTime < Time + Mode = RubyProf::WALL_TIME + + def measure + RubyProf.measure_wall_time + end + end + + class CpuTime < Time + Mode = RubyProf::CPU_TIME if RubyProf.const_defined?(:CPU_TIME) + + def initialize(*args) + # FIXME: yeah my CPU is 2.33 GHz + RubyProf.cpu_frequency = 2.33e9 + super + end + + def measure + RubyProf.measure_cpu_time + end + end + + class Memory < Base + Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY) + + # ruby-prof wrapper + if RubyProf.respond_to?(:measure_memory) + def measure + RubyProf.measure_memory / 1024.0 + end + + # Ruby 1.8 + adymo patch + elsif GC.respond_to?(:allocated_size) + def measure + GC.allocated_size / 1024.0 + end + + # Ruby 1.8 + lloyd patch + elsif GC.respond_to?(:heap_info) + def measure + GC.heap_info['heap_current_memory'] / 1024.0 + end + + # Ruby 1.9 unpatched + elsif GC.respond_to?(:malloc_allocated_size) + def measure + GC.malloc_allocated_size / 1024.0 + end + end + + def format(measurement) + '%.2f KB' % measurement + end + end + + class Objects < Base + Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS) + + if RubyProf.respond_to?(:measure_allocations) + def measure + RubyProf.measure_allocations + end + elsif ObjectSpace.respond_to?(:allocated_objects) + def measure + ObjectSpace.allocated_objects + end + end + + def format(measurement) + measurement.to_i.to_s + end + end + + class GcRuns < Base + Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS) + + if RubyProf.respond_to?(:measure_gc_runs) + def measure + RubyProf.measure_gc_runs + end + elsif GC.respond_to?(:collections) + def measure + GC.collections + end + elsif GC.respond_to?(:heap_info) + def measure + GC.heap_info['num_gc_passes'] + end + end + + def format(measurement) + measurement.to_i.to_s + end + end + + class GcTime < Base + Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME) + + if RubyProf.respond_to?(:measure_gc_time) + def measure + RubyProf.measure_gc_time + end + elsif GC.respond_to?(:time) + def measure + GC.time + end + end + + def format(measurement) + '%d ms' % (measurement / 1000) + end + end + end + end + end +end diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index ed8e34510a..21d71eb92a 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -10,19 +10,43 @@ module ActiveSupport end def self.included(base) - base.send :include, ActiveSupport::Callbacks - base.define_callbacks :setup, :teardown + base.class_eval do + include ActiveSupport::Callbacks + define_callbacks :setup, :teardown + if defined?(::Mini) + alias_method :run, :run_with_callbacks_and_miniunit + else + begin + require 'mocha' + alias_method :run, :run_with_callbacks_and_mocha + rescue LoadError + alias_method :run, :run_with_callbacks_and_testunit + end + end + end + end + + def run_with_callbacks_and_miniunit(runner) + result = '.' begin - require 'mocha' - base.alias_method_chain :run, :callbacks_and_mocha - rescue LoadError - base.alias_method_chain :run, :callbacks + run_callbacks :setup + result = super + rescue Exception => e + result = runner.puke(self.class, self.name, e) + ensure + begin + teardown + run_callbacks :teardown, :enumerator => :reverse_each + rescue Exception => e + result = runner.puke(self.class, self.name, e) + end end + result end # This redefinition is unfortunate but test/unit shows us no alternative. - def run_with_callbacks(result) #:nodoc: + def run_with_callbacks_and_testunit(result) #:nodoc: return if @method_name.to_s == "default_test" yield(Test::Unit::TestCase::STARTED, name) diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 64d734f039..5b2d42aa3c 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -4,7 +4,7 @@ # * Retrieve and display zones with a friendlier name (e.g., "Eastern Time (US & Canada)" instead of "America/New_York"). # * Lazily load TZInfo::Timezone instances only when they're needed. # * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, +parse+, +at+ and +now+ methods. -# +# # If you set <tt>config.time_zone</tt> in the Rails Initializer, you can access this TimeZone object via <tt>Time.zone</tt>: # # # environment.rb: @@ -16,379 +16,381 @@ # Time.zone.name # => "Eastern Time (US & Canada)" # Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00 # -# The version of TZInfo bundled with Active Support only includes the definitions necessary to support the zones +# The version of TZInfo bundled with Active Support only includes the definitions necessary to support the zones # defined by the TimeZone class. If you need to use zones that aren't defined by TimeZone, you'll need to install the TZInfo gem # (if a recent version of the gem is installed locally, this will be used instead of the bundled version.) -class TimeZone - unless const_defined?(:MAPPING) - # Keys are Rails TimeZone names, values are TZInfo identifiers - MAPPING = { - "International Date Line West" => "Pacific/Midway", - "Midway Island" => "Pacific/Midway", - "Samoa" => "Pacific/Pago_Pago", - "Hawaii" => "Pacific/Honolulu", - "Alaska" => "America/Juneau", - "Pacific Time (US & Canada)" => "America/Los_Angeles", - "Tijuana" => "America/Tijuana", - "Mountain Time (US & Canada)" => "America/Denver", - "Arizona" => "America/Phoenix", - "Chihuahua" => "America/Chihuahua", - "Mazatlan" => "America/Mazatlan", - "Central Time (US & Canada)" => "America/Chicago", - "Saskatchewan" => "America/Regina", - "Guadalajara" => "America/Mexico_City", - "Mexico City" => "America/Mexico_City", - "Monterrey" => "America/Monterrey", - "Central America" => "America/Guatemala", - "Eastern Time (US & Canada)" => "America/New_York", - "Indiana (East)" => "America/Indiana/Indianapolis", - "Bogota" => "America/Bogota", - "Lima" => "America/Lima", - "Quito" => "America/Lima", - "Atlantic Time (Canada)" => "America/Halifax", - "Caracas" => "America/Caracas", - "La Paz" => "America/La_Paz", - "Santiago" => "America/Santiago", - "Newfoundland" => "America/St_Johns", - "Brasilia" => "America/Argentina/Buenos_Aires", - "Buenos Aires" => "America/Argentina/Buenos_Aires", - "Georgetown" => "America/Argentina/San_Juan", - "Greenland" => "America/Godthab", - "Mid-Atlantic" => "Atlantic/South_Georgia", - "Azores" => "Atlantic/Azores", - "Cape Verde Is." => "Atlantic/Cape_Verde", - "Dublin" => "Europe/Dublin", - "Edinburgh" => "Europe/Dublin", - "Lisbon" => "Europe/Lisbon", - "London" => "Europe/London", - "Casablanca" => "Africa/Casablanca", - "Monrovia" => "Africa/Monrovia", - "UTC" => "Etc/UTC", - "Belgrade" => "Europe/Belgrade", - "Bratislava" => "Europe/Bratislava", - "Budapest" => "Europe/Budapest", - "Ljubljana" => "Europe/Ljubljana", - "Prague" => "Europe/Prague", - "Sarajevo" => "Europe/Sarajevo", - "Skopje" => "Europe/Skopje", - "Warsaw" => "Europe/Warsaw", - "Zagreb" => "Europe/Zagreb", - "Brussels" => "Europe/Brussels", - "Copenhagen" => "Europe/Copenhagen", - "Madrid" => "Europe/Madrid", - "Paris" => "Europe/Paris", - "Amsterdam" => "Europe/Amsterdam", - "Berlin" => "Europe/Berlin", - "Bern" => "Europe/Berlin", - "Rome" => "Europe/Rome", - "Stockholm" => "Europe/Stockholm", - "Vienna" => "Europe/Vienna", - "West Central Africa" => "Africa/Algiers", - "Bucharest" => "Europe/Bucharest", - "Cairo" => "Africa/Cairo", - "Helsinki" => "Europe/Helsinki", - "Kyev" => "Europe/Kiev", - "Riga" => "Europe/Riga", - "Sofia" => "Europe/Sofia", - "Tallinn" => "Europe/Tallinn", - "Vilnius" => "Europe/Vilnius", - "Athens" => "Europe/Athens", - "Istanbul" => "Europe/Istanbul", - "Minsk" => "Europe/Minsk", - "Jerusalem" => "Asia/Jerusalem", - "Harare" => "Africa/Harare", - "Pretoria" => "Africa/Johannesburg", - "Moscow" => "Europe/Moscow", - "St. Petersburg" => "Europe/Moscow", - "Volgograd" => "Europe/Moscow", - "Kuwait" => "Asia/Kuwait", - "Riyadh" => "Asia/Riyadh", - "Nairobi" => "Africa/Nairobi", - "Baghdad" => "Asia/Baghdad", - "Tehran" => "Asia/Tehran", - "Abu Dhabi" => "Asia/Muscat", - "Muscat" => "Asia/Muscat", - "Baku" => "Asia/Baku", - "Tbilisi" => "Asia/Tbilisi", - "Yerevan" => "Asia/Yerevan", - "Kabul" => "Asia/Kabul", - "Ekaterinburg" => "Asia/Yekaterinburg", - "Islamabad" => "Asia/Karachi", - "Karachi" => "Asia/Karachi", - "Tashkent" => "Asia/Tashkent", - "Chennai" => "Asia/Kolkata", - "Kolkata" => "Asia/Kolkata", - "Mumbai" => "Asia/Kolkata", - "New Delhi" => "Asia/Kolkata", - "Kathmandu" => "Asia/Katmandu", - "Astana" => "Asia/Dhaka", - "Dhaka" => "Asia/Dhaka", - "Sri Jayawardenepura" => "Asia/Dhaka", - "Almaty" => "Asia/Almaty", - "Novosibirsk" => "Asia/Novosibirsk", - "Rangoon" => "Asia/Rangoon", - "Bangkok" => "Asia/Bangkok", - "Hanoi" => "Asia/Bangkok", - "Jakarta" => "Asia/Jakarta", - "Krasnoyarsk" => "Asia/Krasnoyarsk", - "Beijing" => "Asia/Shanghai", - "Chongqing" => "Asia/Chongqing", - "Hong Kong" => "Asia/Hong_Kong", - "Urumqi" => "Asia/Urumqi", - "Kuala Lumpur" => "Asia/Kuala_Lumpur", - "Singapore" => "Asia/Singapore", - "Taipei" => "Asia/Taipei", - "Perth" => "Australia/Perth", - "Irkutsk" => "Asia/Irkutsk", - "Ulaan Bataar" => "Asia/Ulaanbaatar", - "Seoul" => "Asia/Seoul", - "Osaka" => "Asia/Tokyo", - "Sapporo" => "Asia/Tokyo", - "Tokyo" => "Asia/Tokyo", - "Yakutsk" => "Asia/Yakutsk", - "Darwin" => "Australia/Darwin", - "Adelaide" => "Australia/Adelaide", - "Canberra" => "Australia/Melbourne", - "Melbourne" => "Australia/Melbourne", - "Sydney" => "Australia/Sydney", - "Brisbane" => "Australia/Brisbane", - "Hobart" => "Australia/Hobart", - "Vladivostok" => "Asia/Vladivostok", - "Guam" => "Pacific/Guam", - "Port Moresby" => "Pacific/Port_Moresby", - "Magadan" => "Asia/Magadan", - "Solomon Is." => "Asia/Magadan", - "New Caledonia" => "Pacific/Noumea", - "Fiji" => "Pacific/Fiji", - "Kamchatka" => "Asia/Kamchatka", - "Marshall Is." => "Pacific/Majuro", - "Auckland" => "Pacific/Auckland", - "Wellington" => "Pacific/Auckland", - "Nuku'alofa" => "Pacific/Tongatapu" - }.each { |name, zone| name.freeze; zone.freeze } - MAPPING.freeze - end - - include Comparable - attr_reader :name +module ActiveSupport + class TimeZone + unless const_defined?(:MAPPING) + # Keys are Rails TimeZone names, values are TZInfo identifiers + MAPPING = { + "International Date Line West" => "Pacific/Midway", + "Midway Island" => "Pacific/Midway", + "Samoa" => "Pacific/Pago_Pago", + "Hawaii" => "Pacific/Honolulu", + "Alaska" => "America/Juneau", + "Pacific Time (US & Canada)" => "America/Los_Angeles", + "Tijuana" => "America/Tijuana", + "Mountain Time (US & Canada)" => "America/Denver", + "Arizona" => "America/Phoenix", + "Chihuahua" => "America/Chihuahua", + "Mazatlan" => "America/Mazatlan", + "Central Time (US & Canada)" => "America/Chicago", + "Saskatchewan" => "America/Regina", + "Guadalajara" => "America/Mexico_City", + "Mexico City" => "America/Mexico_City", + "Monterrey" => "America/Monterrey", + "Central America" => "America/Guatemala", + "Eastern Time (US & Canada)" => "America/New_York", + "Indiana (East)" => "America/Indiana/Indianapolis", + "Bogota" => "America/Bogota", + "Lima" => "America/Lima", + "Quito" => "America/Lima", + "Atlantic Time (Canada)" => "America/Halifax", + "Caracas" => "America/Caracas", + "La Paz" => "America/La_Paz", + "Santiago" => "America/Santiago", + "Newfoundland" => "America/St_Johns", + "Brasilia" => "America/Argentina/Buenos_Aires", + "Buenos Aires" => "America/Argentina/Buenos_Aires", + "Georgetown" => "America/Argentina/San_Juan", + "Greenland" => "America/Godthab", + "Mid-Atlantic" => "Atlantic/South_Georgia", + "Azores" => "Atlantic/Azores", + "Cape Verde Is." => "Atlantic/Cape_Verde", + "Dublin" => "Europe/Dublin", + "Edinburgh" => "Europe/Dublin", + "Lisbon" => "Europe/Lisbon", + "London" => "Europe/London", + "Casablanca" => "Africa/Casablanca", + "Monrovia" => "Africa/Monrovia", + "UTC" => "Etc/UTC", + "Belgrade" => "Europe/Belgrade", + "Bratislava" => "Europe/Bratislava", + "Budapest" => "Europe/Budapest", + "Ljubljana" => "Europe/Ljubljana", + "Prague" => "Europe/Prague", + "Sarajevo" => "Europe/Sarajevo", + "Skopje" => "Europe/Skopje", + "Warsaw" => "Europe/Warsaw", + "Zagreb" => "Europe/Zagreb", + "Brussels" => "Europe/Brussels", + "Copenhagen" => "Europe/Copenhagen", + "Madrid" => "Europe/Madrid", + "Paris" => "Europe/Paris", + "Amsterdam" => "Europe/Amsterdam", + "Berlin" => "Europe/Berlin", + "Bern" => "Europe/Berlin", + "Rome" => "Europe/Rome", + "Stockholm" => "Europe/Stockholm", + "Vienna" => "Europe/Vienna", + "West Central Africa" => "Africa/Algiers", + "Bucharest" => "Europe/Bucharest", + "Cairo" => "Africa/Cairo", + "Helsinki" => "Europe/Helsinki", + "Kyev" => "Europe/Kiev", + "Riga" => "Europe/Riga", + "Sofia" => "Europe/Sofia", + "Tallinn" => "Europe/Tallinn", + "Vilnius" => "Europe/Vilnius", + "Athens" => "Europe/Athens", + "Istanbul" => "Europe/Istanbul", + "Minsk" => "Europe/Minsk", + "Jerusalem" => "Asia/Jerusalem", + "Harare" => "Africa/Harare", + "Pretoria" => "Africa/Johannesburg", + "Moscow" => "Europe/Moscow", + "St. Petersburg" => "Europe/Moscow", + "Volgograd" => "Europe/Moscow", + "Kuwait" => "Asia/Kuwait", + "Riyadh" => "Asia/Riyadh", + "Nairobi" => "Africa/Nairobi", + "Baghdad" => "Asia/Baghdad", + "Tehran" => "Asia/Tehran", + "Abu Dhabi" => "Asia/Muscat", + "Muscat" => "Asia/Muscat", + "Baku" => "Asia/Baku", + "Tbilisi" => "Asia/Tbilisi", + "Yerevan" => "Asia/Yerevan", + "Kabul" => "Asia/Kabul", + "Ekaterinburg" => "Asia/Yekaterinburg", + "Islamabad" => "Asia/Karachi", + "Karachi" => "Asia/Karachi", + "Tashkent" => "Asia/Tashkent", + "Chennai" => "Asia/Kolkata", + "Kolkata" => "Asia/Kolkata", + "Mumbai" => "Asia/Kolkata", + "New Delhi" => "Asia/Kolkata", + "Kathmandu" => "Asia/Katmandu", + "Astana" => "Asia/Dhaka", + "Dhaka" => "Asia/Dhaka", + "Sri Jayawardenepura" => "Asia/Dhaka", + "Almaty" => "Asia/Almaty", + "Novosibirsk" => "Asia/Novosibirsk", + "Rangoon" => "Asia/Rangoon", + "Bangkok" => "Asia/Bangkok", + "Hanoi" => "Asia/Bangkok", + "Jakarta" => "Asia/Jakarta", + "Krasnoyarsk" => "Asia/Krasnoyarsk", + "Beijing" => "Asia/Shanghai", + "Chongqing" => "Asia/Chongqing", + "Hong Kong" => "Asia/Hong_Kong", + "Urumqi" => "Asia/Urumqi", + "Kuala Lumpur" => "Asia/Kuala_Lumpur", + "Singapore" => "Asia/Singapore", + "Taipei" => "Asia/Taipei", + "Perth" => "Australia/Perth", + "Irkutsk" => "Asia/Irkutsk", + "Ulaan Bataar" => "Asia/Ulaanbaatar", + "Seoul" => "Asia/Seoul", + "Osaka" => "Asia/Tokyo", + "Sapporo" => "Asia/Tokyo", + "Tokyo" => "Asia/Tokyo", + "Yakutsk" => "Asia/Yakutsk", + "Darwin" => "Australia/Darwin", + "Adelaide" => "Australia/Adelaide", + "Canberra" => "Australia/Melbourne", + "Melbourne" => "Australia/Melbourne", + "Sydney" => "Australia/Sydney", + "Brisbane" => "Australia/Brisbane", + "Hobart" => "Australia/Hobart", + "Vladivostok" => "Asia/Vladivostok", + "Guam" => "Pacific/Guam", + "Port Moresby" => "Pacific/Port_Moresby", + "Magadan" => "Asia/Magadan", + "Solomon Is." => "Asia/Magadan", + "New Caledonia" => "Pacific/Noumea", + "Fiji" => "Pacific/Fiji", + "Kamchatka" => "Asia/Kamchatka", + "Marshall Is." => "Pacific/Majuro", + "Auckland" => "Pacific/Auckland", + "Wellington" => "Pacific/Auckland", + "Nuku'alofa" => "Pacific/Tongatapu" + }.each { |name, zone| name.freeze; zone.freeze } + MAPPING.freeze + end - # Create a new TimeZone object with the given name and offset. The - # offset is the number of seconds that this time zone is offset from UTC - # (GMT). Seconds were chosen as the offset unit because that is the unit that - # Ruby uses to represent time zone offsets (see Time#utc_offset). - def initialize(name, utc_offset, tzinfo = nil) - @name = name - @utc_offset = utc_offset - @tzinfo = tzinfo - end + include Comparable + attr_reader :name - def utc_offset - @utc_offset ||= tzinfo.current_period.utc_offset - end - - # Returns the offset of this time zone as a formatted string, of the - # format "+HH:MM". - def formatted_offset(colon=true, alternate_utc_string = nil) - utc_offset == 0 && alternate_utc_string || utc_offset.to_utc_offset_s(colon) - end + # Create a new TimeZone object with the given name and offset. The + # offset is the number of seconds that this time zone is offset from UTC + # (GMT). Seconds were chosen as the offset unit because that is the unit that + # Ruby uses to represent time zone offsets (see Time#utc_offset). + def initialize(name, utc_offset, tzinfo = nil) + @name = name + @utc_offset = utc_offset + @tzinfo = tzinfo + end - # Compare this time zone to the parameter. The two are compared first on - # their offsets, and then by name. - def <=>(zone) - result = (utc_offset <=> zone.utc_offset) - result = (name <=> zone.name) if result == 0 - result - end + def utc_offset + @utc_offset ||= tzinfo.current_period.utc_offset + end - # Returns a textual representation of this time zone. - def to_s - "(GMT#{formatted_offset}) #{name}" - end + # Returns the offset of this time zone as a formatted string, of the + # format "+HH:MM". + def formatted_offset(colon=true, alternate_utc_string = nil) + utc_offset == 0 && alternate_utc_string || utc_offset.to_utc_offset_s(colon) + end - # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from given values. Example: - # - # Time.zone = "Hawaii" # => "Hawaii" - # Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00 - def local(*args) - time = Time.utc_time(*args) - ActiveSupport::TimeWithZone.new(nil, self, time) - end + # Compare this time zone to the parameter. The two are comapred first on + # their offsets, and then by name. + def <=>(zone) + result = (utc_offset <=> zone.utc_offset) + result = (name <=> zone.name) if result == 0 + result + end - # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from number of seconds since the Unix epoch. Example: - # - # Time.zone = "Hawaii" # => "Hawaii" - # Time.utc(2000).to_f # => 946684800.0 - # Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00 - def at(secs) - utc = Time.at(secs).utc rescue DateTime.civil(1970).since(secs) - utc.in_time_zone(self) - end + # Returns a textual representation of this time zone. + def to_s + "(GMT#{formatted_offset}) #{name}" + end - # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string. Example: - # - # Time.zone = "Hawaii" # => "Hawaii" - # Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 - # - # If upper components are missing from the string, they are supplied from TimeZone#now: - # - # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00 - # Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00 - def parse(str, now=now) - date_parts = Date._parse(str) - return if date_parts.blank? - time = Time.parse(str, now) rescue DateTime.parse(str) - if date_parts[:offset].nil? + # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from given values. Example: + # + # Time.zone = "Hawaii" # => "Hawaii" + # Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00 + def local(*args) + time = Time.utc_time(*args) ActiveSupport::TimeWithZone.new(nil, self, time) - else - time.in_time_zone(self) end - end - # Returns an ActiveSupport::TimeWithZone instance representing the current time - # in the time zone represented by +self+. Example: - # - # Time.zone = 'Hawaii' # => "Hawaii" - # Time.zone.now # => Wed, 23 Jan 2008 20:24:27 HST -10:00 - def now - Time.now.utc.in_time_zone(self) - end + # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from number of seconds since the Unix epoch. Example: + # + # Time.zone = "Hawaii" # => "Hawaii" + # Time.utc(2000).to_f # => 946684800.0 + # Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + def at(secs) + utc = Time.at(secs).utc rescue DateTime.civil(1970).since(secs) + utc.in_time_zone(self) + end - # Return the current date in this time zone. - def today - tzinfo.now.to_date - end + # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string. Example: + # + # Time.zone = "Hawaii" # => "Hawaii" + # Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If upper components are missing from the string, they are supplied from TimeZone#now: + # + # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00 + def parse(str, now=now) + date_parts = Date._parse(str) + return if date_parts.blank? + time = Time.parse(str, now) rescue DateTime.parse(str) + if date_parts[:offset].nil? + ActiveSupport::TimeWithZone.new(nil, self, time) + else + time.in_time_zone(self) + end + end - # Adjust the given time to the simultaneous time in the time zone represented by +self+. Returns a - # Time.utc() instance -- if you want an ActiveSupport::TimeWithZone instance, use Time#in_time_zone() instead. - def utc_to_local(time) - tzinfo.utc_to_local(time) - end + # Returns an ActiveSupport::TimeWithZone instance representing the current time + # in the time zone represented by +self+. Example: + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.now # => Wed, 23 Jan 2008 20:24:27 HST -10:00 + def now + Time.now.utc.in_time_zone(self) + end - # Adjust the given time to the simultaneous time in UTC. Returns a Time.utc() instance. - def local_to_utc(time, dst=true) - tzinfo.local_to_utc(time, dst) - end + # Return the current date in this time zone. + def today + tzinfo.now.to_date + end - # Available so that TimeZone instances respond like TZInfo::Timezone instances - def period_for_utc(time) - tzinfo.period_for_utc(time) - end + # Adjust the given time to the simultaneous time in the time zone represented by +self+. Returns a + # Time.utc() instance -- if you want an ActiveSupport::TimeWithZone instance, use Time#in_time_zone() instead. + def utc_to_local(time) + tzinfo.utc_to_local(time) + end - # Available so that TimeZone instances respond like TZInfo::Timezone instances - def period_for_local(time, dst=true) - tzinfo.period_for_local(time, dst) - end + # Adjust the given time to the simultaneous time in UTC. Returns a Time.utc() instance. + def local_to_utc(time, dst=true) + tzinfo.local_to_utc(time, dst) + end - # TODO: Preload instead of lazy load for thread safety - def tzinfo - @tzinfo ||= TZInfo::Timezone.get(MAPPING[name]) - end + # Available so that TimeZone instances respond like TZInfo::Timezone instances + def period_for_utc(time) + tzinfo.period_for_utc(time) + end - unless const_defined?(:ZONES) - ZONES = [] - ZONES_MAP = {} - [[-39_600, "International Date Line West", "Midway Island", "Samoa" ], - [-36_000, "Hawaii" ], - [-32_400, "Alaska" ], - [-28_800, "Pacific Time (US & Canada)", "Tijuana" ], - [-25_200, "Mountain Time (US & Canada)", "Chihuahua", "Mazatlan", - "Arizona" ], - [-21_600, "Central Time (US & Canada)", "Saskatchewan", "Guadalajara", - "Mexico City", "Monterrey", "Central America" ], - [-18_000, "Eastern Time (US & Canada)", "Indiana (East)", "Bogota", - "Lima", "Quito" ], - [-14_400, "Atlantic Time (Canada)", "Caracas", "La Paz", "Santiago" ], - [-12_600, "Newfoundland" ], - [-10_800, "Brasilia", "Buenos Aires", "Georgetown", "Greenland" ], - [ -7_200, "Mid-Atlantic" ], - [ -3_600, "Azores", "Cape Verde Is." ], - [ 0, "Dublin", "Edinburgh", "Lisbon", "London", "Casablanca", - "Monrovia", "UTC" ], - [ 3_600, "Belgrade", "Bratislava", "Budapest", "Ljubljana", "Prague", - "Sarajevo", "Skopje", "Warsaw", "Zagreb", "Brussels", - "Copenhagen", "Madrid", "Paris", "Amsterdam", "Berlin", - "Bern", "Rome", "Stockholm", "Vienna", - "West Central Africa" ], - [ 7_200, "Bucharest", "Cairo", "Helsinki", "Kyev", "Riga", "Sofia", - "Tallinn", "Vilnius", "Athens", "Istanbul", "Minsk", - "Jerusalem", "Harare", "Pretoria" ], - [ 10_800, "Moscow", "St. Petersburg", "Volgograd", "Kuwait", "Riyadh", - "Nairobi", "Baghdad" ], - [ 12_600, "Tehran" ], - [ 14_400, "Abu Dhabi", "Muscat", "Baku", "Tbilisi", "Yerevan" ], - [ 16_200, "Kabul" ], - [ 18_000, "Ekaterinburg", "Islamabad", "Karachi", "Tashkent" ], - [ 19_800, "Chennai", "Kolkata", "Mumbai", "New Delhi" ], - [ 20_700, "Kathmandu" ], - [ 21_600, "Astana", "Dhaka", "Sri Jayawardenepura", "Almaty", - "Novosibirsk" ], - [ 23_400, "Rangoon" ], - [ 25_200, "Bangkok", "Hanoi", "Jakarta", "Krasnoyarsk" ], - [ 28_800, "Beijing", "Chongqing", "Hong Kong", "Urumqi", - "Kuala Lumpur", "Singapore", "Taipei", "Perth", "Irkutsk", - "Ulaan Bataar" ], - [ 32_400, "Seoul", "Osaka", "Sapporo", "Tokyo", "Yakutsk" ], - [ 34_200, "Darwin", "Adelaide" ], - [ 36_000, "Canberra", "Melbourne", "Sydney", "Brisbane", "Hobart", - "Vladivostok", "Guam", "Port Moresby" ], - [ 39_600, "Magadan", "Solomon Is.", "New Caledonia" ], - [ 43_200, "Fiji", "Kamchatka", "Marshall Is.", "Auckland", - "Wellington" ], - [ 46_800, "Nuku'alofa" ]]. - each do |offset, *places| - places.each do |place| - place.freeze - zone = new(place, offset) - ZONES << zone - ZONES_MAP[place] = zone - end + # Available so that TimeZone instances respond like TZInfo::Timezone instances + def period_for_local(time, dst=true) + tzinfo.period_for_local(time, dst) end - ZONES.sort! - ZONES.freeze - ZONES_MAP.freeze - US_ZONES = ZONES.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ } - US_ZONES.freeze - end + # TODO: Preload instead of lazy load for thread safety + def tzinfo + @tzinfo ||= TZInfo::Timezone.get(MAPPING[name]) + end - class << self - alias_method :create, :new + unless const_defined?(:ZONES) + ZONES = [] + ZONES_MAP = {} + [[-39_600, "International Date Line West", "Midway Island", "Samoa" ], + [-36_000, "Hawaii" ], + [-32_400, "Alaska" ], + [-28_800, "Pacific Time (US & Canada)", "Tijuana" ], + [-25_200, "Mountain Time (US & Canada)", "Chihuahua", "Mazatlan", + "Arizona" ], + [-21_600, "Central Time (US & Canada)", "Saskatchewan", "Guadalajara", + "Mexico City", "Monterrey", "Central America" ], + [-18_000, "Eastern Time (US & Canada)", "Indiana (East)", "Bogota", + "Lima", "Quito" ], + [-14_400, "Atlantic Time (Canada)", "Caracas", "La Paz", "Santiago" ], + [-12_600, "Newfoundland" ], + [-10_800, "Brasilia", "Buenos Aires", "Georgetown", "Greenland" ], + [ -7_200, "Mid-Atlantic" ], + [ -3_600, "Azores", "Cape Verde Is." ], + [ 0, "Dublin", "Edinburgh", "Lisbon", "London", "Casablanca", + "Monrovia", "UTC" ], + [ 3_600, "Belgrade", "Bratislava", "Budapest", "Ljubljana", "Prague", + "Sarajevo", "Skopje", "Warsaw", "Zagreb", "Brussels", + "Copenhagen", "Madrid", "Paris", "Amsterdam", "Berlin", + "Bern", "Rome", "Stockholm", "Vienna", + "West Central Africa" ], + [ 7_200, "Bucharest", "Cairo", "Helsinki", "Kyev", "Riga", "Sofia", + "Tallinn", "Vilnius", "Athens", "Istanbul", "Minsk", + "Jerusalem", "Harare", "Pretoria" ], + [ 10_800, "Moscow", "St. Petersburg", "Volgograd", "Kuwait", "Riyadh", + "Nairobi", "Baghdad" ], + [ 12_600, "Tehran" ], + [ 14_400, "Abu Dhabi", "Muscat", "Baku", "Tbilisi", "Yerevan" ], + [ 16_200, "Kabul" ], + [ 18_000, "Ekaterinburg", "Islamabad", "Karachi", "Tashkent" ], + [ 19_800, "Chennai", "Kolkata", "Mumbai", "New Delhi" ], + [ 20_700, "Kathmandu" ], + [ 21_600, "Astana", "Dhaka", "Sri Jayawardenepura", "Almaty", + "Novosibirsk" ], + [ 23_400, "Rangoon" ], + [ 25_200, "Bangkok", "Hanoi", "Jakarta", "Krasnoyarsk" ], + [ 28_800, "Beijing", "Chongqing", "Hong Kong", "Urumqi", + "Kuala Lumpur", "Singapore", "Taipei", "Perth", "Irkutsk", + "Ulaan Bataar" ], + [ 32_400, "Seoul", "Osaka", "Sapporo", "Tokyo", "Yakutsk" ], + [ 34_200, "Darwin", "Adelaide" ], + [ 36_000, "Canberra", "Melbourne", "Sydney", "Brisbane", "Hobart", + "Vladivostok", "Guam", "Port Moresby" ], + [ 39_600, "Magadan", "Solomon Is.", "New Caledonia" ], + [ 43_200, "Fiji", "Kamchatka", "Marshall Is.", "Auckland", + "Wellington" ], + [ 46_800, "Nuku'alofa" ]]. + each do |offset, *places| + places.each do |place| + place.freeze + zone = new(place, offset) + ZONES << zone + ZONES_MAP[place] = zone + end + end + ZONES.sort! + ZONES.freeze + ZONES_MAP.freeze - # Return a TimeZone instance with the given name, or +nil+ if no - # such TimeZone instance exists. (This exists to support the use of - # this class with the +composed_of+ macro.) - def new(name) - self[name] + US_ZONES = ZONES.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ } + US_ZONES.freeze end - # Return an array of all TimeZone objects. There are multiple - # TimeZone objects per time zone, in many cases, to make it easier - # for users to find their own time zone. - def all - ZONES - end + class << self + alias_method :create, :new - # Locate a specific time zone object. If the argument is a string, it - # is interpreted to mean the name of the timezone to locate. If it is a - # numeric value it is either the hour offset, or the second offset, of the - # timezone to find. (The first one with that offset will be returned.) - # Returns +nil+ if no such time zone is known to the system. - def [](arg) - case arg - when String - ZONES_MAP[arg] - when Numeric, ActiveSupport::Duration - arg *= 3600 if arg.abs <= 13 - all.find { |z| z.utc_offset == arg.to_i } - else - raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" + # Return a TimeZone instance with the given name, or +nil+ if no + # such TimeZone instance exists. (This exists to support the use of + # this class with the +composed_of+ macro.) + def new(name) + self[name] end - end - # A convenience method for returning a collection of TimeZone objects - # for time zones in the USA. - def us_zones - US_ZONES + # Return an array of all TimeZone objects. There are multiple + # TimeZone objects per time zone, in many cases, to make it easier + # for users to find their own time zone. + def all + ZONES + end + + # Locate a specific time zone object. If the argument is a string, it + # is interpreted to mean the name of the timezone to locate. If it is a + # numeric value it is either the hour offset, or the second offset, of the + # timezone to find. (The first one with that offset will be returned.) + # Returns +nil+ if no such time zone is known to the system. + def [](arg) + case arg + when String + ZONES_MAP[arg] + when Numeric, ActiveSupport::Duration + arg *= 3600 if arg.abs <= 13 + all.find { |z| z.utc_offset == arg.to_i } + else + raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" + end + end + + # A convenience method for returning a collection of TimeZone objects + # for time zones in the USA. + def us_zones + US_ZONES + end end end end diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb index cdbcedc75a..a02e42f791 100644 --- a/activesupport/lib/active_support/vendor.rb +++ b/activesupport/lib/active_support/vendor.rb @@ -20,7 +20,7 @@ rescue Gem::LoadError end begin - gem 'tzinfo', '~> 0.3.8' + gem 'tzinfo', '~> 0.3.9' rescue Gem::LoadError - $:.unshift "#{File.dirname(__FILE__)}/vendor/tzinfo-0.3.8" + $:.unshift "#{File.dirname(__FILE__)}/vendor/tzinfo-0.3.9" end
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/offset_rationals.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/offset_rationals.rb deleted file mode 100644 index 32fa4123f1..0000000000 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/offset_rationals.rb +++ /dev/null @@ -1,95 +0,0 @@ -#-- -# Copyright (c) 2006 Philip Ross -# -# 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. -#++ - -module TZInfo - - # Provides a method for getting Rationals for a timezone offset in seconds. - # Pre-reduced rationals are returned for all the half-hour intervals between - # -14 and +14 hours to avoid having to call gcd at runtime. - module OffsetRationals #:nodoc: - @@rational_cache = { - -50400 => Rational(-7,12), - -48600 => Rational(-9,16), - -46800 => Rational(-13,24), - -45000 => Rational(-25,48), - -43200 => Rational(-1,2), - -41400 => Rational(-23,48), - -39600 => Rational(-11,24), - -37800 => Rational(-7,16), - -36000 => Rational(-5,12), - -34200 => Rational(-19,48), - -32400 => Rational(-3,8), - -30600 => Rational(-17,48), - -28800 => Rational(-1,3), - -27000 => Rational(-5,16), - -25200 => Rational(-7,24), - -23400 => Rational(-13,48), - -21600 => Rational(-1,4), - -19800 => Rational(-11,48), - -18000 => Rational(-5,24), - -16200 => Rational(-3,16), - -14400 => Rational(-1,6), - -12600 => Rational(-7,48), - -10800 => Rational(-1,8), - -9000 => Rational(-5,48), - -7200 => Rational(-1,12), - -5400 => Rational(-1,16), - -3600 => Rational(-1,24), - -1800 => Rational(-1,48), - 0 => Rational(0,1), - 1800 => Rational(1,48), - 3600 => Rational(1,24), - 5400 => Rational(1,16), - 7200 => Rational(1,12), - 9000 => Rational(5,48), - 10800 => Rational(1,8), - 12600 => Rational(7,48), - 14400 => Rational(1,6), - 16200 => Rational(3,16), - 18000 => Rational(5,24), - 19800 => Rational(11,48), - 21600 => Rational(1,4), - 23400 => Rational(13,48), - 25200 => Rational(7,24), - 27000 => Rational(5,16), - 28800 => Rational(1,3), - 30600 => Rational(17,48), - 32400 => Rational(3,8), - 34200 => Rational(19,48), - 36000 => Rational(5,12), - 37800 => Rational(7,16), - 39600 => Rational(11,24), - 41400 => Rational(23,48), - 43200 => Rational(1,2), - 45000 => Rational(25,48), - 46800 => Rational(13,24), - 48600 => Rational(9,16), - 50400 => Rational(7,12)} - - # Returns a Rational expressing the fraction of a day that offset in - # seconds represents (i.e. equivalent to Rational(offset, 86400)). - def rational_for_offset(offset) - @@rational_cache[offset] || Rational(offset, 86400) - end - module_function :rational_for_offset - end -end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo.rb index c8bdbeec5d..c8bdbeec5d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/data_timezone.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/data_timezone.rb index 2510e90b5b..2510e90b5b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/data_timezone.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/data_timezone.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/data_timezone_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/data_timezone_info.rb index d2a6620dd2..ad907af360 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/data_timezone_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/data_timezone_info.rb @@ -40,6 +40,8 @@ module TZInfo super(identifier) @offsets = {} @transitions = [] + @previous_offset = nil + @transitions_index = nil end # Defines a offset. The id uniquely identifies this offset within the diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Algiers.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Algiers.rb index 8c5f25577f..8c5f25577f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Algiers.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Algiers.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Cairo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Cairo.rb index 6e6daf3522..6e6daf3522 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Cairo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Cairo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Casablanca.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Casablanca.rb index 10d28e7613..dddf533f61 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Casablanca.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Casablanca.rb @@ -31,6 +31,8 @@ module TZInfo tz.transition 1978, 8, :o1, 271033200 tz.transition 1984, 3, :o3, 448243200 tz.transition 1985, 12, :o1, 504918000 + tz.transition 2008, 6, :o2, 1212278400 + tz.transition 2008, 9, :o1, 1222556400 end end end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Harare.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Harare.rb index 070c95ae0f..070c95ae0f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Harare.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Harare.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Johannesburg.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Johannesburg.rb index f0af0d8e33..f0af0d8e33 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Johannesburg.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Johannesburg.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Monrovia.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Monrovia.rb index 40e711fa44..40e711fa44 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Monrovia.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Monrovia.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Nairobi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Nairobi.rb index 7b0a2f43be..7b0a2f43be 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Africa/Nairobi.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Africa/Nairobi.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Argentina/Buenos_Aires.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Argentina/Buenos_Aires.rb index 7536f7553c..7536f7553c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Argentina/Buenos_Aires.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Argentina/Buenos_Aires.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Argentina/San_Juan.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Argentina/San_Juan.rb index a5413612ee..a5413612ee 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Argentina/San_Juan.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Argentina/San_Juan.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Bogota.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Bogota.rb index ef96435c6a..ef96435c6a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Bogota.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Bogota.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Caracas.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Caracas.rb index 27392a540a..27392a540a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Caracas.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Caracas.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Chicago.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Chicago.rb index 0996857cf0..0996857cf0 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Chicago.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Chicago.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Chihuahua.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Chihuahua.rb index 1710b57c79..1710b57c79 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Chihuahua.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Chihuahua.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Denver.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Denver.rb index 1c1efb5ff3..1c1efb5ff3 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Denver.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Denver.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Godthab.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Godthab.rb index 1e05518b0d..1e05518b0d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Godthab.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Godthab.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Guatemala.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Guatemala.rb index a2bf73401c..a2bf73401c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Guatemala.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Guatemala.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Halifax.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Halifax.rb index d25ae775b3..d25ae775b3 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Halifax.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Halifax.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Indiana/Indianapolis.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Indiana/Indianapolis.rb index f1430f6c24..f1430f6c24 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Indiana/Indianapolis.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Indiana/Indianapolis.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Juneau.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Juneau.rb index f646f3f54a..f646f3f54a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Juneau.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Juneau.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/La_Paz.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/La_Paz.rb index 45c907899f..45c907899f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/La_Paz.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/La_Paz.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Lima.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Lima.rb index af68ac29f7..af68ac29f7 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Lima.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Lima.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Los_Angeles.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Los_Angeles.rb index 16007fd675..16007fd675 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Los_Angeles.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Los_Angeles.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Mazatlan.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Mazatlan.rb index ba9e6efcf1..ba9e6efcf1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Mazatlan.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Mazatlan.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Mexico_City.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Mexico_City.rb index 2347fce647..2347fce647 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Mexico_City.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Mexico_City.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Monterrey.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Monterrey.rb index 5816a9eab1..5816a9eab1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Monterrey.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Monterrey.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/New_York.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/New_York.rb index 7d802bd2de..7d802bd2de 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/New_York.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/New_York.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Phoenix.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Phoenix.rb index b514e0c0f9..b514e0c0f9 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Phoenix.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Phoenix.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Regina.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Regina.rb index ebdb68814a..ebdb68814a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Regina.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Regina.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Santiago.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Santiago.rb index 0287c9ebc4..0287c9ebc4 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Santiago.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Santiago.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/St_Johns.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/St_Johns.rb index e4a3599d35..e4a3599d35 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/St_Johns.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/St_Johns.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Tijuana.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Tijuana.rb index 423059da46..423059da46 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/America/Tijuana.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/America/Tijuana.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Almaty.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Almaty.rb index 9ee18970f1..9ee18970f1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Almaty.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Almaty.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Baghdad.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Baghdad.rb index 774dca1587..774dca1587 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Baghdad.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Baghdad.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Baku.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Baku.rb index e86340ebfa..e86340ebfa 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Baku.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Baku.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Bangkok.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Bangkok.rb index 139194e5e5..139194e5e5 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Bangkok.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Bangkok.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Chongqing.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Chongqing.rb index 8c94b4ba86..8c94b4ba86 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Chongqing.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Chongqing.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Dhaka.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Dhaka.rb index ccd0265503..ccd0265503 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Dhaka.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Dhaka.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Hong_Kong.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Hong_Kong.rb index f1edd75ac8..f1edd75ac8 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Hong_Kong.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Hong_Kong.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Irkutsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Irkutsk.rb index 2d47d9580b..2d47d9580b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Irkutsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Irkutsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Jakarta.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Jakarta.rb index cc58fa173b..cc58fa173b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Jakarta.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Jakarta.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Jerusalem.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Jerusalem.rb index 9b737b899e..9b737b899e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Jerusalem.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Jerusalem.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Kabul.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Kabul.rb index 669c09790a..669c09790a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Kabul.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Kabul.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Kamchatka.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Kamchatka.rb index 2f1690b3a9..2f1690b3a9 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Kamchatka.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Kamchatka.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Karachi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Karachi.rb index baa0d41d73..bd7656ef2f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Karachi.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Karachi.rb @@ -21,6 +21,8 @@ module TZInfo tz.transition 1971, 3, :o4, 38775600 tz.transition 2002, 4, :o5, 1018119660 tz.transition 2002, 10, :o4, 1033840860 + tz.transition 2008, 5, :o5, 1212260400 + tz.transition 2008, 8, :o4, 1220205600 end end end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Katmandu.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Katmandu.rb index 37dbea1f41..37dbea1f41 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Katmandu.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Katmandu.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Kolkata.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Kolkata.rb index 1b6ffbd59d..1b6ffbd59d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Kolkata.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Kolkata.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Krasnoyarsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Krasnoyarsk.rb index d6c503c155..d6c503c155 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Krasnoyarsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Krasnoyarsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Kuala_Lumpur.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Kuala_Lumpur.rb index 77a0c206fa..77a0c206fa 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Kuala_Lumpur.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Kuala_Lumpur.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Kuwait.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Kuwait.rb index 5bd5283197..5bd5283197 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Kuwait.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Kuwait.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Magadan.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Magadan.rb index 302093693e..302093693e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Magadan.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Magadan.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Muscat.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Muscat.rb index 604f651dfa..604f651dfa 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Muscat.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Muscat.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Novosibirsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Novosibirsk.rb index a4e7796e75..a4e7796e75 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Novosibirsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Novosibirsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Rangoon.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Rangoon.rb index 759b82d77a..759b82d77a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Rangoon.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Rangoon.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Riyadh.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Riyadh.rb index 7add410620..7add410620 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Riyadh.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Riyadh.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Seoul.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Seoul.rb index 795d2a75df..795d2a75df 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Seoul.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Seoul.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Shanghai.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Shanghai.rb index 34b13d59ae..34b13d59ae 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Shanghai.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Shanghai.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Singapore.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Singapore.rb index b323a78f74..b323a78f74 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Singapore.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Singapore.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Taipei.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Taipei.rb index 3ba12108fb..3ba12108fb 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Taipei.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Taipei.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Tashkent.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Tashkent.rb index c205c7934d..c205c7934d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Tashkent.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Tashkent.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Tbilisi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Tbilisi.rb index 15792a5651..15792a5651 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Tbilisi.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Tbilisi.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Tehran.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Tehran.rb index d8df964a46..d8df964a46 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Tehran.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Tehran.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Tokyo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Tokyo.rb index 51c9e16421..51c9e16421 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Tokyo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Tokyo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Ulaanbaatar.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Ulaanbaatar.rb index 2854f5c5fd..2854f5c5fd 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Ulaanbaatar.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Ulaanbaatar.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Urumqi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Urumqi.rb index d793ff1341..d793ff1341 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Urumqi.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Urumqi.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Vladivostok.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Vladivostok.rb index bd9e3d60ec..bd9e3d60ec 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Vladivostok.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Vladivostok.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Yakutsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Yakutsk.rb index 56435a788f..56435a788f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Yakutsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Yakutsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Yekaterinburg.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Yekaterinburg.rb index 8ef8df4a41..8ef8df4a41 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Yekaterinburg.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Yekaterinburg.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Yerevan.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Yerevan.rb index e7f160861f..e7f160861f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Asia/Yerevan.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Asia/Yerevan.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Atlantic/Azores.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Atlantic/Azores.rb index 1bd16a75ac..1bd16a75ac 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Atlantic/Azores.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Atlantic/Azores.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Atlantic/Cape_Verde.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Atlantic/Cape_Verde.rb index 61c8c15043..61c8c15043 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Atlantic/Cape_Verde.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Atlantic/Cape_Verde.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Atlantic/South_Georgia.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Atlantic/South_Georgia.rb index 6a4cbafb9f..6a4cbafb9f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Atlantic/South_Georgia.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Atlantic/South_Georgia.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Adelaide.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Adelaide.rb index c5d561cc1e..c5d561cc1e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Adelaide.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Adelaide.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Brisbane.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Brisbane.rb index dd85ddae94..dd85ddae94 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Brisbane.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Brisbane.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Darwin.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Darwin.rb index 17de88124d..17de88124d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Darwin.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Darwin.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Hobart.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Hobart.rb index 11384b9840..11384b9840 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Hobart.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Hobart.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Melbourne.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Melbourne.rb index c1304488ea..c1304488ea 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Melbourne.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Melbourne.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Perth.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Perth.rb index d9e66f14a8..d9e66f14a8 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Perth.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Perth.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Sydney.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Sydney.rb index 9062bd7c3c..9062bd7c3c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Australia/Sydney.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Australia/Sydney.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Etc/UTC.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Etc/UTC.rb index 28b2c6a04c..28b2c6a04c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Etc/UTC.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Etc/UTC.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Amsterdam.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Amsterdam.rb index 2d0c95c4bc..2d0c95c4bc 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Amsterdam.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Amsterdam.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Athens.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Athens.rb index 4e21e535ca..4e21e535ca 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Athens.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Athens.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Belgrade.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Belgrade.rb index 4dbd893d75..4dbd893d75 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Belgrade.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Belgrade.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Berlin.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Berlin.rb index de2e4d951c..de2e4d951c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Berlin.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Berlin.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Bratislava.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Bratislava.rb index 7a731a0b6a..7a731a0b6a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Bratislava.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Bratislava.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Brussels.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Brussels.rb index 6b0a242944..6b0a242944 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Brussels.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Brussels.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Bucharest.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Bucharest.rb index 521c3c932e..521c3c932e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Bucharest.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Bucharest.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Budapest.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Budapest.rb index 1f3a9738b7..1f3a9738b7 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Budapest.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Budapest.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Copenhagen.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Copenhagen.rb index 47cbaf14a7..47cbaf14a7 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Copenhagen.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Copenhagen.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Dublin.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Dublin.rb index 0560bb5436..0560bb5436 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Dublin.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Dublin.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Helsinki.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Helsinki.rb index 13a806bcc7..13a806bcc7 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Helsinki.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Helsinki.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Istanbul.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Istanbul.rb index 8306c47536..8306c47536 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Istanbul.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Istanbul.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Kiev.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Kiev.rb index 513d3308be..513d3308be 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Kiev.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Kiev.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Lisbon.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Lisbon.rb index 1c6d2a3d30..1c6d2a3d30 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Lisbon.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Lisbon.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Ljubljana.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Ljubljana.rb index a9828e6ef8..a9828e6ef8 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Ljubljana.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Ljubljana.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/London.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/London.rb index 64ce41e900..64ce41e900 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/London.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/London.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Madrid.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Madrid.rb index 1fb568239a..1fb568239a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Madrid.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Madrid.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Minsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Minsk.rb index fa15816cc8..fa15816cc8 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Minsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Minsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Moscow.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Moscow.rb index ef269b675b..ef269b675b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Moscow.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Moscow.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Paris.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Paris.rb index e3236c0ba1..e3236c0ba1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Paris.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Paris.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Prague.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Prague.rb index bcabee96c1..bcabee96c1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Prague.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Prague.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Riga.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Riga.rb index 784837f758..784837f758 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Riga.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Riga.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Rome.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Rome.rb index aa7b43d9d2..aa7b43d9d2 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Rome.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Rome.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Sarajevo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Sarajevo.rb index 068c5fe6ad..068c5fe6ad 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Sarajevo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Sarajevo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Skopje.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Skopje.rb index 10b71f285e..10b71f285e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Skopje.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Skopje.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Sofia.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Sofia.rb index 38a70eceb9..38a70eceb9 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Sofia.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Sofia.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Stockholm.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Stockholm.rb index 43db70fa61..43db70fa61 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Stockholm.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Stockholm.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Tallinn.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Tallinn.rb index de5a8569f3..de5a8569f3 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Tallinn.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Tallinn.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Vienna.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Vienna.rb index 990aabab66..990aabab66 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Vienna.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Vienna.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Vilnius.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Vilnius.rb index d89d095a75..d89d095a75 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Vilnius.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Vilnius.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Warsaw.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Warsaw.rb index 7fa51c2691..7fa51c2691 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Warsaw.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Warsaw.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Zagreb.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Zagreb.rb index ecdd903d28..ecdd903d28 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Europe/Zagreb.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Europe/Zagreb.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Auckland.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Auckland.rb index a524fd6b6b..a524fd6b6b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Auckland.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Auckland.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Fiji.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Fiji.rb index 5fe9bbd9a6..5fe9bbd9a6 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Fiji.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Fiji.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Guam.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Guam.rb index d4c1a0a682..d4c1a0a682 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Guam.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Guam.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Honolulu.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Honolulu.rb index 204b226537..204b226537 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Honolulu.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Honolulu.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Majuro.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Majuro.rb index 32adad92c1..32adad92c1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Majuro.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Majuro.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Midway.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Midway.rb index 97784fcc10..97784fcc10 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Midway.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Midway.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Noumea.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Noumea.rb index 70173db8ab..70173db8ab 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Noumea.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Noumea.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Pago_Pago.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Pago_Pago.rb index c8fcd7d527..c8fcd7d527 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Pago_Pago.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Pago_Pago.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Port_Moresby.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Port_Moresby.rb index f06cf6d54f..f06cf6d54f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Port_Moresby.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Port_Moresby.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Tongatapu.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Tongatapu.rb index 7578d92f38..7578d92f38 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/definitions/Pacific/Tongatapu.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/definitions/Pacific/Tongatapu.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/info_timezone.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/info_timezone.rb index 001303c594..001303c594 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/info_timezone.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/info_timezone.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/linked_timezone.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/linked_timezone.rb index c757485b84..c757485b84 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/linked_timezone.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/linked_timezone.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/linked_timezone_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/linked_timezone_info.rb index 8197ff3e81..8197ff3e81 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/linked_timezone_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/linked_timezone_info.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/offset_rationals.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/offset_rationals.rb new file mode 100644 index 0000000000..c79eedb7ef --- /dev/null +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/offset_rationals.rb @@ -0,0 +1,95 @@ +#-- +# Copyright (c) 2006 Philip Ross +# +# 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. +#++ + +module TZInfo + + # Provides a method for getting Rationals for a timezone offset in seconds. + # Pre-reduced rationals are returned for all the half-hour intervals between + # -14 and +14 hours to avoid having to call gcd at runtime. + module OffsetRationals #:nodoc: + @@rational_cache = { + -50400 => Rational.send(:new!, -7,12), + -48600 => Rational.send(:new!, -9,16), + -46800 => Rational.send(:new!, -13,24), + -45000 => Rational.send(:new!, -25,48), + -43200 => Rational.send(:new!, -1,2), + -41400 => Rational.send(:new!, -23,48), + -39600 => Rational.send(:new!, -11,24), + -37800 => Rational.send(:new!, -7,16), + -36000 => Rational.send(:new!, -5,12), + -34200 => Rational.send(:new!, -19,48), + -32400 => Rational.send(:new!, -3,8), + -30600 => Rational.send(:new!, -17,48), + -28800 => Rational.send(:new!, -1,3), + -27000 => Rational.send(:new!, -5,16), + -25200 => Rational.send(:new!, -7,24), + -23400 => Rational.send(:new!, -13,48), + -21600 => Rational.send(:new!, -1,4), + -19800 => Rational.send(:new!, -11,48), + -18000 => Rational.send(:new!, -5,24), + -16200 => Rational.send(:new!, -3,16), + -14400 => Rational.send(:new!, -1,6), + -12600 => Rational.send(:new!, -7,48), + -10800 => Rational.send(:new!, -1,8), + -9000 => Rational.send(:new!, -5,48), + -7200 => Rational.send(:new!, -1,12), + -5400 => Rational.send(:new!, -1,16), + -3600 => Rational.send(:new!, -1,24), + -1800 => Rational.send(:new!, -1,48), + 0 => Rational.send(:new!, 0,1), + 1800 => Rational.send(:new!, 1,48), + 3600 => Rational.send(:new!, 1,24), + 5400 => Rational.send(:new!, 1,16), + 7200 => Rational.send(:new!, 1,12), + 9000 => Rational.send(:new!, 5,48), + 10800 => Rational.send(:new!, 1,8), + 12600 => Rational.send(:new!, 7,48), + 14400 => Rational.send(:new!, 1,6), + 16200 => Rational.send(:new!, 3,16), + 18000 => Rational.send(:new!, 5,24), + 19800 => Rational.send(:new!, 11,48), + 21600 => Rational.send(:new!, 1,4), + 23400 => Rational.send(:new!, 13,48), + 25200 => Rational.send(:new!, 7,24), + 27000 => Rational.send(:new!, 5,16), + 28800 => Rational.send(:new!, 1,3), + 30600 => Rational.send(:new!, 17,48), + 32400 => Rational.send(:new!, 3,8), + 34200 => Rational.send(:new!, 19,48), + 36000 => Rational.send(:new!, 5,12), + 37800 => Rational.send(:new!, 7,16), + 39600 => Rational.send(:new!, 11,24), + 41400 => Rational.send(:new!, 23,48), + 43200 => Rational.send(:new!, 1,2), + 45000 => Rational.send(:new!, 25,48), + 46800 => Rational.send(:new!, 13,24), + 48600 => Rational.send(:new!, 9,16), + 50400 => Rational.send(:new!, 7,12)} + + # Returns a Rational expressing the fraction of a day that offset in + # seconds represents (i.e. equivalent to Rational(offset, 86400)). + def rational_for_offset(offset) + @@rational_cache[offset] || Rational(offset, 86400) + end + module_function :rational_for_offset + end +end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/time_or_datetime.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/time_or_datetime.rb index 264517f3ee..264517f3ee 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/time_or_datetime.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/time_or_datetime.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/timezone.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/timezone.rb index 910eefca02..eb9ede2ab9 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/timezone.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/timezone.rb @@ -371,8 +371,8 @@ module TZInfo # type as utc. Any timezone information in utc is ignored (it is treated as # a UTC time). def utc_to_local(utc) - TimeOrDateTime.wrap(utc) {|utc| - period_for_utc(utc).to_local(utc) + TimeOrDateTime.wrap(utc) {|wrapped| + period_for_utc(wrapped).to_local(wrapped) } end @@ -410,14 +410,14 @@ module TZInfo # single period to use to convert the time or return nil or an empty array # to cause an AmbiguousTime exception to be raised. def local_to_utc(local, dst = nil) - TimeOrDateTime.wrap(local) {|local| + TimeOrDateTime.wrap(local) {|wrapped| if block_given? - period = period_for_local(local, dst) {|periods| yield periods } + period = period_for_local(wrapped, dst) {|periods| yield periods } else - period = period_for_local(local, dst) + period = period_for_local(wrapped, dst) end - period.to_utc(local) + period.to_utc(wrapped) } end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/timezone_definition.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/timezone_definition.rb index 39ca8bfa53..39ca8bfa53 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/timezone_definition.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/timezone_definition.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/timezone_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/timezone_info.rb index 68e38c35fb..68e38c35fb 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/timezone_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/timezone_info.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/timezone_offset_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/timezone_offset_info.rb index 913c4164af..6a0bbca46f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/timezone_offset_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/timezone_offset_info.rb @@ -56,15 +56,15 @@ module TZInfo # Converts a UTC DateTime to local time based on the offset of this period. def to_local(utc) - TimeOrDateTime.wrap(utc) {|utc| - utc + @utc_total_offset + TimeOrDateTime.wrap(utc) {|wrapped| + wrapped + @utc_total_offset } end # Converts a local DateTime to UTC based on the offset of this period. def to_utc(local) - TimeOrDateTime.wrap(local) {|local| - local - @utc_total_offset + TimeOrDateTime.wrap(local) {|wrapped| + wrapped - @utc_total_offset } end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/timezone_period.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/timezone_period.rb index 00888fcfdc..00888fcfdc 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/timezone_period.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/timezone_period.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/timezone_transition_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/timezone_transition_info.rb index 146c131708..781764f0b0 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.8/tzinfo/timezone_transition_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.9/tzinfo/timezone_transition_info.rb @@ -48,7 +48,7 @@ module TZInfo # seconds since the epoch. If denominator is specified numerator_or_time # and denominator are used to create a DateTime as follows: # - # DateTime.new!(Rational.new!(numerator_or_time, denominator), 0, Date::ITALY) + # DateTime.new!(Rational.send(:new!, numerator_or_time, denominator), 0, Date::ITALY) # # For performance reasons, the numerator and denominator must be specified # in their lowest form. @@ -70,7 +70,7 @@ module TZInfo unless @denominator @at = TimeOrDateTime.new(@numerator_or_time) else - r = Rational.new!(@numerator_or_time, @denominator) + r = Rational.send(:new!, @numerator_or_time, @denominator) # Ruby 1.8.6 introduced new! and deprecated new0. # Ruby 1.9.0 removed new0. diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 09b56525e0..f3220d27aa 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -1,5 +1,11 @@ require 'abstract_unit' +class CacheKeyTest < Test::Unit::TestCase + def test_expand_cache_key + assert_equal 'name/1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true], :name) + end +end + class CacheStoreSettingTest < Test::Unit::TestCase def test_file_fragment_cache_store store = ActiveSupport::Cache.lookup_store :file_store, "/path/to/cache/directory" diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index ccab0f70d0..7563be44f8 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -13,6 +13,20 @@ class ArrayExtAccessTests < Test::Unit::TestCase assert_equal %w( a b c ), %w( a b c d ).to(2) assert_equal %w( a b c d ), %w( a b c d ).to(10) end + + def test_second_through_tenth + array = (1..10).to_a + + assert_equal array[1], array.second + assert_equal array[2], array.third + assert_equal array[3], array.fourth + assert_equal array[4], array.fifth + assert_equal array[5], array.sixth + assert_equal array[6], array.seventh + assert_equal array[7], array.eighth + assert_equal array[8], array.ninth + assert_equal array[9], array.tenth + end end class ArrayExtToParamTests < Test::Unit::TestCase @@ -21,7 +35,7 @@ class ArrayExtToParamTests < Test::Unit::TestCase "#{self}1" end end - + def test_string_array assert_equal '', %w().to_param assert_equal 'hello/world', %w(hello world).to_param @@ -31,7 +45,7 @@ class ArrayExtToParamTests < Test::Unit::TestCase def test_number_array assert_equal '10/20', [10, 20].to_param end - + def test_to_param_array assert_equal 'custom1/param1', [ToParam.new('custom'), ToParam.new('param')].to_param end @@ -222,6 +236,11 @@ class ArrayToXmlTests < Test::Unit::TestCase assert xml.include?(%(<count>2</count>)), xml end + + def test_to_xml_with_empty + xml = [].to_xml + assert_match(/type="array"\/>/, xml) + end end class ArrayExtractOptionsTests < Test::Unit::TestCase @@ -234,17 +253,15 @@ class ArrayExtractOptionsTests < Test::Unit::TestCase end uses_mocha "ArrayExtRandomTests" do + class ArrayExtRandomTests < Test::Unit::TestCase + def test_random_element_from_array + assert_nil [].rand -class ArrayExtRandomTests < Test::Unit::TestCase - def test_random_element_from_array - assert_nil [].rand - - Kernel.expects(:rand).with(1).returns(0) - assert_equal 'x', ['x'].rand + Kernel.expects(:rand).with(1).returns(0) + assert_equal 'x', ['x'].rand - Kernel.expects(:rand).with(3).returns(1) - assert_equal 2, [1, 2, 3].rand + Kernel.expects(:rand).with(3).returns(1) + assert_equal 2, [1, 2, 3].rand + end end end - -end diff --git a/activesupport/test/core_ext/bigdecimal.rb b/activesupport/test/core_ext/bigdecimal.rb index 0417a2bca0..9faad9146f 100644 --- a/activesupport/test/core_ext/bigdecimal.rb +++ b/activesupport/test/core_ext/bigdecimal.rb @@ -5,5 +5,6 @@ class BigDecimalTest < Test::Unit::TestCase assert_equal("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) assert_equal("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) assert_equal("--- .NaN\n", BigDecimal.new('NaN').to_yaml) + assert_equal("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) end end
\ No newline at end of file diff --git a/activesupport/test/core_ext/blank_test.rb b/activesupport/test/core_ext/blank_test.rb index 061d940383..00fea74639 100644 --- a/activesupport/test/core_ext/blank_test.rb +++ b/activesupport/test/core_ext/blank_test.rb @@ -16,4 +16,9 @@ class BlankTest < Test::Unit::TestCase BLANK.each { |v| assert v.blank?, "#{v.inspect} should be blank" } NOT.each { |v| assert !v.blank?, "#{v.inspect} should not be blank" } end + + def test_present + BLANK.each { |v| assert !v.present?, "#{v.inspect} should not be present" } + NOT.each { |v| assert v.present?, "#{v.inspect} should be present" } + end end diff --git a/activesupport/test/core_ext/class_test.rb b/activesupport/test/core_ext/class_test.rb index 0346fad190..9c6071d478 100644 --- a/activesupport/test/core_ext/class_test.rb +++ b/activesupport/test/core_ext/class_test.rb @@ -38,9 +38,9 @@ class ClassTest < Test::Unit::TestCase @parent = eval("class D; end; D") @sub = eval("class E < D; end; E") @subofsub = eval("class F < E; end; F") - assert @parent.subclasses.all? { |i| [@sub.to_s, @subofsub.to_s].include?(i) } assert_equal 2, @parent.subclasses.size assert_equal [@subofsub.to_s], @sub.subclasses assert_equal [], @subofsub.subclasses + assert_equal [@sub.to_s, @subofsub.to_s].sort, @parent.subclasses.sort end end diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 5925ae3a61..ddfe1f904f 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -76,6 +76,7 @@ class DateExtCalculationsTest < Test::Unit::TestCase assert_equal Date.new(2008,3,31), Date.new(2008,3,31).end_of_quarter assert_equal Date.new(2008,12,31), Date.new(2008,10,8).end_of_quarter assert_equal Date.new(2008,6,30), Date.new(2008,4,14).end_of_quarter + assert_equal Date.new(2008,6,30), Date.new(2008,5,31).end_of_quarter assert_equal Date.new(2008,9,30), Date.new(2008,8,21).end_of_quarter end @@ -171,7 +172,7 @@ class DateExtCalculationsTest < Test::Unit::TestCase def test_last_month_on_31st assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).last_month - end + end def test_yesterday_constructor assert_equal Date.today - 1, Date.yesterday @@ -196,7 +197,11 @@ class DateExtCalculationsTest < Test::Unit::TestCase def test_end_of_day assert_equal Time.local(2005,2,21,23,59,59), Date.new(2005,2,21).end_of_day end - + + def test_date_acts_like_date + assert Date.new.acts_like_date? + end + def test_xmlschema with_env_tz 'US/Eastern' do assert_match(/^1980-02-28T00:00:00-05:?00$/, Date.new(1980, 2, 28).xmlschema) @@ -208,7 +213,7 @@ class DateExtCalculationsTest < Test::Unit::TestCase end end end - + uses_mocha 'TestDateCurrent' do def test_current_returns_date_today_when_zone_default_not_set with_env_tz 'US/Central' do @@ -217,10 +222,10 @@ class DateExtCalculationsTest < Test::Unit::TestCase assert_equal Date.new(1999, 12, 31), Date.current end end - + def test_current_returns_time_zone_today_when_zone_default_set silence_warnings do # silence warnings raised by tzinfo gem - Time.zone_default = TimeZone['Eastern Time (US & Canada)'] + Time.zone_default = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'US/Central' do Time.stubs(:now).returns Time.local(1999, 12, 31, 23) assert_equal Date.new(1999, 12, 31), Date.today @@ -238,5 +243,5 @@ class DateExtCalculationsTest < Test::Unit::TestCase yield ensure old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end + end end diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index ed45daf403..854a3a05e1 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -219,14 +219,14 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase assert_equal Rational(-6, 24), DateTime.local_offset end end - + def test_utc? assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12).utc? assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc? assert_equal false, DateTime.civil(2005, 2, 21, 10, 11, 12, 0.25).utc? assert_equal false, DateTime.civil(2005, 2, 21, 10, 11, 12, -0.25).utc? end - + def test_utc_offset assert_equal 0, DateTime.civil(2005, 2, 21, 10, 11, 12).utc_offset assert_equal 0, DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc_offset @@ -234,7 +234,7 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase assert_equal( -21600, DateTime.civil(2005, 2, 21, 10, 11, 12, -0.25).utc_offset ) assert_equal( -18000, DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).utc_offset ) end - + def test_utc assert_equal DateTime.civil(2005, 2, 21, 16, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc assert_equal DateTime.civil(2005, 2, 21, 15, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).utc @@ -242,37 +242,37 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase assert_equal DateTime.civil(2005, 2, 21, 9, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(1, 24)).utc assert_equal DateTime.civil(2005, 2, 21, 9, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(1, 24)).getutc end - + def test_formatted_offset_with_utc assert_equal '+00:00', DateTime.civil(2000).formatted_offset assert_equal '+0000', DateTime.civil(2000).formatted_offset(false) assert_equal 'UTC', DateTime.civil(2000).formatted_offset(true, 'UTC') end - + def test_formatted_offset_with_local dt = DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)) assert_equal '-05:00', dt.formatted_offset assert_equal '-0500', dt.formatted_offset(false) end - + def test_compare_with_time assert_equal 1, DateTime.civil(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59) assert_equal 0, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0) assert_equal(-1, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1)) end - + def test_compare_with_datetime assert_equal 1, DateTime.civil(2000) <=> DateTime.civil(1999, 12, 31, 23, 59, 59) assert_equal 0, DateTime.civil(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 0) assert_equal(-1, DateTime.civil(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 1)) end - + def test_compare_with_time_with_zone - assert_equal 1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), TimeZone['UTC'] ) - assert_equal 0, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), TimeZone['UTC'] ) - assert_equal(-1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), TimeZone['UTC'] )) + assert_equal 1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone['UTC'] ) + assert_equal 0, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'] ) + assert_equal(-1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) end - + def test_to_f assert_equal 946684800.0, DateTime.civil(2000).to_f assert_equal 946684800.0, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_f diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 7b17fe71db..f802ed8760 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -29,7 +29,7 @@ class DurationTest < Test::Unit::TestCase flunk("ArgumentError should be raised, but we got #{$!.class} instead") end end - + uses_mocha 'TestDurationSinceAndAgoWithCurrentTime' do def test_since_and_ago_anchored_to_time_now_when_time_zone_default_not_set Time.zone_default = nil @@ -43,10 +43,10 @@ class DurationTest < Test::Unit::TestCase assert_equal Time.local(1999,12,31,23,59,55), 5.seconds.ago end end - + def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_default_set silence_warnings do # silence warnings raised by tzinfo gem - Time.zone_default = TimeZone['Eastern Time (US & Canada)'] + Time.zone_default = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'US/Eastern' do Time.stubs(:now).returns Time.local(2000) # since @@ -63,7 +63,7 @@ class DurationTest < Test::Unit::TestCase Time.zone_default = nil end end - + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index f5facd54ad..2315d8f3db 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -63,4 +63,15 @@ class EnumerableTests < Test::Unit::TestCase assert_equal({ 5 => payments[0], 15 => payments[1], 10 => payments[2] }, payments.index_by { |p| p.price }) end + + def test_many + assert ![].many? + assert ![ 1 ].many? + assert [ 1, 2 ].many? + + assert ![].many? {|x| x > 1 } + assert ![ 2 ].many? {|x| x > 1 } + assert ![ 1, 2 ].many? {|x| x > 1 } + assert [ 1, 2, 2 ].many? {|x| x > 1 } + end end diff --git a/activesupport/test/core_ext/module/model_naming_test.rb b/activesupport/test/core_ext/module/model_naming_test.rb new file mode 100644 index 0000000000..fc73fa5c36 --- /dev/null +++ b/activesupport/test/core_ext/module/model_naming_test.rb @@ -0,0 +1,19 @@ +require 'abstract_unit' + +class ModelNamingTest < Test::Unit::TestCase + def setup + @name = ActiveSupport::ModelName.new('Post::TrackBack') + end + + def test_singular + assert_equal 'post_track_back', @name.singular + end + + def test_plural + assert_equal 'post_track_backs', @name.plural + end + + def test_partial_path + assert_equal 'post/track_backs/track_back', @name.partial_path + end +end diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index e53b7193ea..17a0968c0e 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -449,7 +449,7 @@ class TimeExtCalculationsTest < Test::Unit::TestCase assert_equal "17:44", time.to_s(:time) assert_equal "February 21, 2005 17:44", time.to_s(:long) assert_equal "February 21st, 2005 17:44", time.to_s(:long_ordinal) - with_env_tz "UTC" do + with_env_tz "UTC" do assert_equal "Mon, 21 Feb 2005 17:44:30 +0000", time.to_s(:rfc822) end end @@ -505,13 +505,13 @@ class TimeExtCalculationsTest < Test::Unit::TestCase assert_equal 30, Time.days_in_month(11, 2005) assert_equal 31, Time.days_in_month(12, 2005) end - + uses_mocha 'TestTimeDaysInMonthWithoutYearArg' do def test_days_in_month_feb_in_common_year_without_year_arg Time.stubs(:now).returns(Time.utc(2007)) assert_equal 28, Time.days_in_month(2) end - + def test_days_in_month_feb_in_leap_year_without_year_arg Time.stubs(:now).returns(Time.utc(2008)) assert_equal 29, Time.days_in_month(2) @@ -559,13 +559,13 @@ class TimeExtCalculationsTest < Test::Unit::TestCase def test_acts_like_time assert Time.new.acts_like_time? end - + def test_formatted_offset_with_utc assert_equal '+00:00', Time.utc(2000).formatted_offset assert_equal '+0000', Time.utc(2000).formatted_offset(false) assert_equal 'UTC', Time.utc(2000).formatted_offset(true, 'UTC') end - + def test_formatted_offset_with_local with_env_tz 'US/Eastern' do assert_equal '-05:00', Time.local(2000).formatted_offset @@ -574,27 +574,27 @@ class TimeExtCalculationsTest < Test::Unit::TestCase assert_equal '-0400', Time.local(2000, 7).formatted_offset(false) end end - + def test_compare_with_time assert_equal 1, Time.utc(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59, 999) assert_equal 0, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0) assert_equal(-1, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0, 001)) end - + def test_compare_with_datetime assert_equal 1, Time.utc(2000) <=> DateTime.civil(1999, 12, 31, 23, 59, 59) assert_equal 0, Time.utc(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 0) assert_equal(-1, Time.utc(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 1)) end - + def test_compare_with_time_with_zone - assert_equal 1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), TimeZone['UTC'] ) - assert_equal 0, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), TimeZone['UTC'] ) - assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), TimeZone['UTC'] )) + assert_equal 1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone['UTC'] ) + assert_equal 0, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'] ) + assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) end - + def test_minus_with_time_with_zone - assert_equal 86_400.0, Time.utc(2000, 1, 2) - ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), TimeZone['UTC'] ) + assert_equal 86_400.0, Time.utc(2000, 1, 2) - ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['UTC'] ) end def test_time_created_with_local_constructor_cannot_represent_times_during_hour_skipped_by_dst @@ -608,7 +608,7 @@ class TimeExtCalculationsTest < Test::Unit::TestCase def test_case_equality assert Time === Time.utc(2000) - assert Time === ActiveSupport::TimeWithZone.new(Time.utc(2000), TimeZone['UTC']) + assert Time === ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) assert_equal false, Time === DateTime.civil(2000) end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index c373bca88d..0977cd8e50 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -1,10 +1,10 @@ require 'abstract_unit' - + class TimeWithZoneTest < Test::Unit::TestCase def setup @utc = Time.utc(2000, 1, 1, 0) - @time_zone = TimeZone['Eastern Time (US & Canada)'] + @time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] @twz = ActiveSupport::TimeWithZone.new(@utc, @time_zone) end @@ -21,114 +21,114 @@ class TimeWithZoneTest < Test::Unit::TestCase def test_time_zone assert_equal @time_zone, @twz.time_zone end - + def test_in_time_zone Time.use_zone 'Alaska' do - assert_equal ActiveSupport::TimeWithZone.new(@utc, TimeZone['Alaska']), @twz.in_time_zone + assert_equal ActiveSupport::TimeWithZone.new(@utc, ActiveSupport::TimeZone['Alaska']), @twz.in_time_zone end end def test_in_time_zone_with_argument - assert_equal ActiveSupport::TimeWithZone.new(@utc, TimeZone['Alaska']), @twz.in_time_zone('Alaska') + assert_equal ActiveSupport::TimeWithZone.new(@utc, ActiveSupport::TimeZone['Alaska']), @twz.in_time_zone('Alaska') end - + def test_in_time_zone_with_new_zone_equal_to_old_zone_does_not_create_new_object - assert_equal @twz.object_id, @twz.in_time_zone(TimeZone['Eastern Time (US & Canada)']).object_id + assert_equal @twz.object_id, @twz.in_time_zone(ActiveSupport::TimeZone['Eastern Time (US & Canada)']).object_id end def test_utc? assert_equal false, @twz.utc? - assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), TimeZone['UTC']).utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']).utc? end - + def test_formatted_offset silence_warnings do # silence warnings raised by tzinfo gem assert_equal '-05:00', @twz.formatted_offset assert_equal '-04:00', ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).formatted_offset #dst end end - + def test_dst? silence_warnings do # silence warnings raised by tzinfo gem assert_equal false, @twz.dst? assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).dst? end end - + def test_zone silence_warnings do # silence warnings raised by tzinfo gem assert_equal 'EST', @twz.zone assert_equal 'EDT', ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone #dst end end - + def test_to_json silence_warnings do # silence warnings raised by tzinfo gem assert_equal "\"1999/12/31 19:00:00 -0500\"", @twz.to_json end end - + def test_to_json_with_use_standard_json_time_format_config_set_to_true old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, true assert_equal "\"1999-12-31T19:00:00-05:00\"", @twz.to_json ensure ActiveSupport.use_standard_json_time_format = old end - + def test_strftime silence_warnings do # silence warnings raised by tzinfo gem assert_equal '1999-12-31 19:00:00 EST -0500', @twz.strftime('%Y-%m-%d %H:%M:%S %Z %z') end end - + def test_inspect silence_warnings do # silence warnings raised by tzinfo gem assert_equal 'Fri, 31 Dec 1999 19:00:00 EST -05:00', @twz.inspect end end - + def test_to_s silence_warnings do # silence warnings raised by tzinfo gem assert_equal '1999-12-31 19:00:00 -0500', @twz.to_s end end - + def test_to_s_db silence_warnings do # silence warnings raised by tzinfo gem assert_equal '2000-01-01 00:00:00', @twz.to_s(:db) end end - + def test_xmlschema silence_warnings do # silence warnings raised by tzinfo gem assert_equal "1999-12-31T19:00:00-05:00", @twz.xmlschema end end - + def test_to_yaml silence_warnings do # silence warnings raised by tzinfo gem assert_equal "--- 1999-12-31 19:00:00 -05:00\n", @twz.to_yaml end end - + def test_ruby_to_yaml silence_warnings do assert_equal "--- \n:twz: 2000-01-01 00:00:00 Z\n", {:twz => @twz}.to_yaml end end - + def test_httpdate silence_warnings do # silence warnings raised by tzinfo gem assert_equal 'Sat, 01 Jan 2000 00:00:00 GMT', @twz.httpdate end end - + def test_rfc2822 silence_warnings do # silence warnings raised by tzinfo gem assert_equal "Fri, 31 Dec 1999 19:00:00 -0500", @twz.rfc2822 end end - + def test_compare_with_time assert_equal 1, @twz <=> Time.utc(1999, 12, 31, 23, 59, 59) assert_equal 0, @twz <=> Time.utc(2000, 1, 1, 0, 0, 0) @@ -142,27 +142,27 @@ class TimeWithZoneTest < Test::Unit::TestCase end def test_compare_with_time_with_zone - assert_equal 1, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), TimeZone['UTC'] ) - assert_equal 0, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), TimeZone['UTC'] ) - assert_equal(-1, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), TimeZone['UTC'] )) + assert_equal 1, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone['UTC'] ) + assert_equal 0, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'] ) + assert_equal(-1, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) end - + def test_between? assert @twz.between?(Time.utc(1999,12,31,23,59,59), Time.utc(2000,1,1,0,0,1)) assert_equal false, @twz.between?(Time.utc(2000,1,1,0,0,1), Time.utc(2000,1,1,0,0,2)) end - + def test_eql? assert @twz.eql?(Time.utc(2000)) - assert @twz.eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), TimeZone["Hawaii"]) ) + assert @twz.eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) ) end - + def test_plus_with_integer silence_warnings do # silence warnings raised by tzinfo gem assert_equal Time.utc(1999, 12, 31, 19, 0 ,5), (@twz + 5).time end end - + def test_plus_with_integer_when_self_wraps_datetime silence_warnings do # silence warnings raised by tzinfo gem datetime = DateTime.civil(2000, 1, 1, 0) @@ -170,26 +170,26 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal DateTime.civil(1999, 12, 31, 19, 0 ,5), (twz + 5).time end end - + def test_plus_when_crossing_time_class_limit silence_warnings do # silence warnings raised by tzinfo gem twz = ActiveSupport::TimeWithZone.new(Time.utc(2038, 1, 19), @time_zone) assert_equal [0, 0, 19, 19, 1, 2038], (twz + 86_400).to_a[0,6] end end - + def test_plus_with_duration silence_warnings do # silence warnings raised by tzinfo gem assert_equal Time.utc(2000, 1, 5, 19, 0 ,0), (@twz + 5.days).time end end - + def test_minus_with_integer silence_warnings do # silence warnings raised by tzinfo gem assert_equal Time.utc(1999, 12, 31, 18, 59 ,55), (@twz - 5).time end end - + def test_minus_with_integer_when_self_wraps_datetime silence_warnings do # silence warnings raised by tzinfo gem datetime = DateTime.civil(2000, 1, 1, 0) @@ -197,24 +197,24 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal DateTime.civil(1999, 12, 31, 18, 59 ,55), (twz - 5).time end end - + def test_minus_with_duration silence_warnings do # silence warnings raised by tzinfo gem assert_equal Time.utc(1999, 12, 26, 19, 0 ,0), (@twz - 5.days).time end end - + def test_minus_with_time - assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), TimeZone['UTC'] ) - Time.utc(2000, 1, 1) - assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), TimeZone['Hawaii'] ) - Time.utc(2000, 1, 1) + assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 1) + assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['Hawaii'] ) - Time.utc(2000, 1, 1) end - + def test_minus_with_time_with_zone - twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), TimeZone['UTC'] ) - twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), TimeZone['UTC'] ) + twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['UTC'] ) + twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) assert_equal 86_400.0, twz2 - twz1 end - + def test_plus_and_minus_enforce_spring_dst_rules silence_warnings do # silence warnings raised by tzinfo gem utc = Time.utc(2006,4,2,6,59,59) # == Apr 2 2006 01:59:59 EST; i.e., 1 second before daylight savings start @@ -232,7 +232,7 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal 'EST', twz.zone end end - + def test_plus_and_minus_enforce_fall_dst_rules silence_warnings do # silence warnings raised by tzinfo gem utc = Time.utc(2006,10,29,5,59,59) # == Oct 29 2006 01:59:59 EST; i.e., 1 second before daylight savings end @@ -250,25 +250,25 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal 'EDT', twz.zone end end - + def test_to_a silence_warnings do # silence warnings raised by tzinfo gem - assert_equal [45, 30, 5, 1, 2, 2000, 2, 32, false, "HST"], ActiveSupport::TimeWithZone.new( Time.utc(2000, 2, 1, 15, 30, 45), TimeZone['Hawaii'] ).to_a + assert_equal [45, 30, 5, 1, 2, 2000, 2, 32, false, "HST"], ActiveSupport::TimeWithZone.new( Time.utc(2000, 2, 1, 15, 30, 45), ActiveSupport::TimeZone['Hawaii'] ).to_a end end - + def test_to_f - result = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), TimeZone['Hawaii'] ).to_f + result = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii'] ).to_f assert_equal 946684800.0, result assert result.is_a?(Float) end - + def test_to_i - result = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), TimeZone['Hawaii'] ).to_i + result = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii'] ).to_i assert_equal 946684800, result assert result.is_a?(Integer) end - + def test_to_time assert_equal @twz, @twz.to_time end @@ -276,55 +276,55 @@ class TimeWithZoneTest < Test::Unit::TestCase def test_to_date silence_warnings do # silence warnings raised by tzinfo gem # 1 sec before midnight Jan 1 EST - assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 4, 59, 59), TimeZone['Eastern Time (US & Canada)'] ).to_date + assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 4, 59, 59), ActiveSupport::TimeZone['Eastern Time (US & Canada)'] ).to_date # midnight Jan 1 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 5, 0, 0), TimeZone['Eastern Time (US & Canada)'] ).to_date + assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 5, 0, 0), ActiveSupport::TimeZone['Eastern Time (US & Canada)'] ).to_date # 1 sec before midnight Jan 2 EST - assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 4, 59, 59), TimeZone['Eastern Time (US & Canada)'] ).to_date + assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 4, 59, 59), ActiveSupport::TimeZone['Eastern Time (US & Canada)'] ).to_date # midnight Jan 2 EST - assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 5, 0, 0), TimeZone['Eastern Time (US & Canada)'] ).to_date + assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 5, 0, 0), ActiveSupport::TimeZone['Eastern Time (US & Canada)'] ).to_date end end - + def test_to_datetime silence_warnings do # silence warnings raised by tzinfo gem assert_equal DateTime.civil(1999, 12, 31, 19, 0, 0, Rational(-18_000, 86_400)), @twz.to_datetime end end - + def test_acts_like_time assert @twz.acts_like?(:time) assert ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone).acts_like?(:time) end - + def test_acts_like_date assert_equal false, @twz.acts_like?(:date) assert_equal false, ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone).acts_like?(:date) end - + def test_is_a assert @twz.is_a?(Time) assert @twz.kind_of?(Time) assert @twz.is_a?(ActiveSupport::TimeWithZone) end - + def test_method_missing_with_time_return_value silence_warnings do # silence warnings raised by tzinfo gem assert_instance_of ActiveSupport::TimeWithZone, @twz.months_since(1) assert_equal Time.utc(2000, 1, 31, 19, 0 ,0), @twz.months_since(1).time end end - + def test_marshal_dump_and_load silence_warnings do # silence warnings raised by tzinfo gem marshal_str = Marshal.dump(@twz) mtime = Marshal.load(marshal_str) assert_equal Time.utc(2000, 1, 1, 0), mtime.utc - assert_equal TimeZone['Eastern Time (US & Canada)'], mtime.time_zone + assert_equal ActiveSupport::TimeZone['Eastern Time (US & Canada)'], mtime.time_zone assert_equal Time.utc(1999, 12, 31, 19), mtime.time end end - + def test_marshal_dump_and_load_with_tzinfo_identifier silence_warnings do # silence warnings raised by tzinfo gem twz = ActiveSupport::TimeWithZone.new(@utc, TZInfo::Timezone.get('America/New_York')) @@ -335,35 +335,37 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal Time.utc(1999, 12, 31, 19), mtime.time end end - - def test_method_missing_with_non_time_return_value - silence_warnings do # silence warnings raised by tzinfo gem - @twz.time.expects(:foo).returns('bar') - assert_equal 'bar', @twz.foo + + uses_mocha 'TestDatePartValueMethods' do + def test_method_missing_with_non_time_return_value + silence_warnings do # silence warnings raised by tzinfo gem + @twz.time.expects(:foo).returns('bar') + assert_equal 'bar', @twz.foo + end end - end - - def test_date_part_value_methods - silence_warnings do # silence warnings raised by tzinfo gem - twz = ActiveSupport::TimeWithZone.new(Time.utc(1999,12,31,19,18,17,500), @time_zone) - twz.stubs(:method_missing).returns(nil) #ensure these methods are defined directly on class - assert_equal 1999, twz.year - assert_equal 12, twz.month - assert_equal 31, twz.day - assert_equal 14, twz.hour - assert_equal 18, twz.min - assert_equal 17, twz.sec - assert_equal 500, twz.usec + + def test_date_part_value_methods + silence_warnings do # silence warnings raised by tzinfo gem + twz = ActiveSupport::TimeWithZone.new(Time.utc(1999,12,31,19,18,17,500), @time_zone) + twz.stubs(:method_missing).returns(nil) #ensure these methods are defined directly on class + assert_equal 1999, twz.year + assert_equal 12, twz.month + assert_equal 31, twz.day + assert_equal 14, twz.hour + assert_equal 18, twz.min + assert_equal 17, twz.sec + assert_equal 500, twz.usec + end end end - + def test_usec_returns_0_when_datetime_is_wrapped silence_warnings do # silence warnings raised by tzinfo gem twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone) assert_equal 0, twz.usec end end - + def test_utc_to_local_conversion_saves_period_in_instance_variable silence_warnings do # silence warnings raised by tzinfo gem assert_nil @twz.instance_variable_get('@period') @@ -371,14 +373,14 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_kind_of TZInfo::TimezonePeriod, @twz.instance_variable_get('@period') end end - + def test_instance_created_with_local_time_returns_correct_utc_time silence_warnings do # silence warnings raised by tzinfo gem twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(1999, 12, 31, 19)) assert_equal Time.utc(2000), twz.utc end end - + def test_instance_created_with_local_time_enforces_spring_dst_rules silence_warnings do # silence warnings raised by tzinfo gem twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,2)) # first second of DST @@ -387,7 +389,7 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal 'EDT', twz.zone end end - + def test_instance_created_with_local_time_enforces_fall_dst_rules silence_warnings do # silence warnings raised by tzinfo gem twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,1)) # 1AM can be either DST or non-DST; we'll pick DST @@ -396,14 +398,14 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal 'EDT', twz.zone end end - + def test_ruby_19_weekday_name_query_methods ruby_19_or_greater = RUBY_VERSION >= '1.9' %w(sunday? monday? tuesday? wednesday? thursday? friday? saturday?).each do |name| assert_equal ruby_19_or_greater, @twz.respond_to?(name) end end - + def test_utc_to_local_conversion_with_far_future_datetime silence_warnings do # silence warnings raised by tzinfo gem assert_equal [0,0,19,31,12,2049], ActiveSupport::TimeWithZone.new(DateTime.civil(2050), @time_zone).to_a[0,6] @@ -415,7 +417,7 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal DateTime.civil(2050).to_f, ActiveSupport::TimeWithZone.new(nil, @time_zone, DateTime.civil(2049,12,31,19)).to_f end end - + def test_change assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.change(:year => 2001).inspect @@ -426,7 +428,7 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal "Fri, 31 Dec 1999 19:15:00 EST -05:00", @twz.change(:min => 15).inspect assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.change(:sec => 30).inspect end - + def test_advance assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.advance(:years => 2).inspect @@ -436,77 +438,77 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal "Fri, 31 Dec 1999 19:15:00 EST -05:00", @twz.advance(:minutes => 15).inspect assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.advance(:seconds => 30).inspect end - + def beginning_of_year assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Fri, 01 Jan 1999 00:00:00 EST -05:00", @twz.beginning_of_year.inspect end - + def end_of_year assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_year.inspect end - + def beginning_of_month assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Fri, 01 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_month.inspect end - + def end_of_month assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_month.inspect end - + def beginning_of_day assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Fri, 31 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_day.inspect end - + def end_of_day assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_day.inspect end - + def test_since assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.since(1).inspect end - + def test_ago assert_equal "Fri, 31 Dec 1999 18:59:59 EST -05:00", @twz.ago(1).inspect end - + def test_seconds_since_midnight assert_equal 19 * 60 * 60, @twz.seconds_since_midnight end - + def test_advance_1_year_from_leap_day twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2004,2,29)) assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.advance(:years => 1).inspect assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.years_since(1).inspect assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", (twz + 1.year).inspect end - + def test_advance_1_month_from_last_day_of_january twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2005,1,31)) assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.advance(:months => 1).inspect assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.months_since(1).inspect assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", (twz + 1.month).inspect end - + def test_advance_1_month_from_last_day_of_january_during_leap_year twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2000,1,31)) assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.advance(:months => 1).inspect assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.months_since(1).inspect assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", (twz + 1.month).inspect end - + def test_advance_1_month_into_spring_dst_gap twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,3,2,2)) assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.advance(:months => 1).inspect assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.months_since(1).inspect assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1.month).inspect end - + def test_advance_1_second_into_spring_dst_gap twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,1,59,59)) assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.advance(:seconds => 1).inspect @@ -519,11 +521,11 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase def setup @t, @dt = Time.utc(2000), DateTime.civil(2000) end - + def teardown Time.zone = nil end - + def test_in_time_zone silence_warnings do # silence warnings raised by tzinfo gem Time.use_zone 'Alaska' do @@ -554,7 +556,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase end end end - + def test_in_time_zone_with_time_local_instance silence_warnings do # silence warnings raised by tzinfo gem with_env_tz 'US/Eastern' do @@ -563,85 +565,85 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase end end end - + def test_use_zone Time.zone = 'Alaska' Time.use_zone 'Hawaii' do - assert_equal TimeZone['Hawaii'], Time.zone + assert_equal ActiveSupport::TimeZone['Hawaii'], Time.zone end - assert_equal TimeZone['Alaska'], Time.zone + assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone end - + def test_use_zone_with_exception_raised Time.zone = 'Alaska' assert_raises RuntimeError do Time.use_zone('Hawaii') { raise RuntimeError } end - assert_equal TimeZone['Alaska'], Time.zone + assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone end - + def test_time_zone_getter_and_setter - Time.zone = TimeZone['Alaska'] - assert_equal TimeZone['Alaska'], Time.zone + Time.zone = ActiveSupport::TimeZone['Alaska'] + assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone Time.zone = 'Alaska' - assert_equal TimeZone['Alaska'], Time.zone + assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone Time.zone = -9.hours - assert_equal TimeZone['Alaska'], Time.zone + assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone Time.zone = nil assert_equal nil, Time.zone end - + def test_time_zone_getter_and_setter_with_zone_default - Time.zone_default = TimeZone['Alaska'] - assert_equal TimeZone['Alaska'], Time.zone - Time.zone = TimeZone['Hawaii'] - assert_equal TimeZone['Hawaii'], Time.zone + Time.zone_default = ActiveSupport::TimeZone['Alaska'] + assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone + Time.zone = ActiveSupport::TimeZone['Hawaii'] + assert_equal ActiveSupport::TimeZone['Hawaii'], Time.zone Time.zone = nil - assert_equal TimeZone['Alaska'], Time.zone + assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone ensure Time.zone_default = nil end - + def test_time_zone_setter_is_thread_safe Time.use_zone 'Paris' do t1 = Thread.new { Time.zone = 'Alaska' }.join t2 = Thread.new { Time.zone = 'Hawaii' }.join assert t1.stop?, "Thread 1 did not finish running" assert t2.stop?, "Thread 2 did not finish running" - assert_equal TimeZone['Paris'], Time.zone - assert_equal TimeZone['Alaska'], t1[:time_zone] - assert_equal TimeZone['Hawaii'], t2[:time_zone] + assert_equal ActiveSupport::TimeZone['Paris'], Time.zone + assert_equal ActiveSupport::TimeZone['Alaska'], t1[:time_zone] + assert_equal ActiveSupport::TimeZone['Hawaii'], t2[:time_zone] end end - + def test_time_zone_setter_with_tzinfo_timezone_object_wraps_in_rails_time_zone silence_warnings do # silence warnings raised by tzinfo gem tzinfo = TZInfo::Timezone.get('America/New_York') Time.zone = tzinfo - assert_kind_of TimeZone, Time.zone + assert_kind_of ActiveSupport::TimeZone, Time.zone assert_equal tzinfo, Time.zone.tzinfo assert_equal 'America/New_York', Time.zone.name assert_equal(-18_000, Time.zone.utc_offset) end end - + def test_time_zone_setter_with_tzinfo_timezone_identifier_does_lookup_and_wraps_in_rails_time_zone silence_warnings do # silence warnings raised by tzinfo gem Time.zone = 'America/New_York' - assert_kind_of TimeZone, Time.zone + assert_kind_of ActiveSupport::TimeZone, Time.zone assert_equal 'America/New_York', Time.zone.tzinfo.name assert_equal 'America/New_York', Time.zone.name assert_equal(-18_000, Time.zone.utc_offset) end end - + def test_time_zone_setter_with_non_identifying_argument_returns_nil Time.zone = 'foo' assert_equal nil, Time.zone Time.zone = -15.hours assert_equal nil, Time.zone end - + uses_mocha 'TestTimeCurrent' do def test_current_returns_time_now_when_zone_default_not_set with_env_tz 'US/Eastern' do @@ -650,10 +652,10 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase assert_equal Time.local(2000), Time.current end end - + def test_current_returns_time_zone_now_when_zone_default_set silence_warnings do # silence warnings raised by tzinfo gem - Time.zone_default = TimeZone['Eastern Time (US & Canada)'] + Time.zone_default = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'US/Eastern' do Time.stubs(:now).returns Time.local(2000) assert_equal true, Time.current.is_a?(ActiveSupport::TimeWithZone) @@ -665,7 +667,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase Time.zone_default = nil end end - + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 0309ab6858..038547a862 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -15,31 +15,31 @@ end class DependenciesTest < Test::Unit::TestCase def teardown - Dependencies.clear + ActiveSupport::Dependencies.clear end def with_loading(*from) - old_mechanism, Dependencies.mechanism = Dependencies.mechanism, :load + old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load dir = File.dirname(__FILE__) - prior_load_paths = Dependencies.load_paths - Dependencies.load_paths = from.collect { |f| "#{dir}/#{f}" } + prior_load_paths = ActiveSupport::Dependencies.load_paths + ActiveSupport::Dependencies.load_paths = from.collect { |f| "#{dir}/#{f}" } yield ensure - Dependencies.load_paths = prior_load_paths - Dependencies.mechanism = old_mechanism - Dependencies.explicitly_unloadable_constants = [] + ActiveSupport::Dependencies.load_paths = prior_load_paths + ActiveSupport::Dependencies.mechanism = old_mechanism + ActiveSupport::Dependencies.explicitly_unloadable_constants = [] end def test_tracking_loaded_files require_dependency 'dependencies/service_one' require_dependency 'dependencies/service_two' - assert_equal 2, Dependencies.loaded.size + assert_equal 2, ActiveSupport::Dependencies.loaded.size end def test_tracking_identical_loaded_files require_dependency 'dependencies/service_one' require_dependency 'dependencies/service_one' - assert_equal 1, Dependencies.loaded.size + assert_equal 1, ActiveSupport::Dependencies.loaded.size end def test_missing_dependency_raises_missing_source_file @@ -64,46 +64,46 @@ class DependenciesTest < Test::Unit::TestCase end assert_equal count + 1, $raises_exception_load_count - assert !Dependencies.loaded.include?(filename) - assert !Dependencies.history.include?(filename) + assert !ActiveSupport::Dependencies.loaded.include?(filename) + assert !ActiveSupport::Dependencies.history.include?(filename) end end end def test_warnings_should_be_enabled_on_first_load with_loading 'dependencies' do - old_warnings, Dependencies.warnings_on_first_load = Dependencies.warnings_on_first_load, true + old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true filename = "check_warnings" expanded = File.expand_path("test/dependencies/#{filename}") $check_warnings_load_count = 0 - assert !Dependencies.loaded.include?(expanded) - assert !Dependencies.history.include?(expanded) + assert !ActiveSupport::Dependencies.loaded.include?(expanded) + assert !ActiveSupport::Dependencies.history.include?(expanded) silence_warnings { require_dependency filename } assert_equal 1, $check_warnings_load_count assert_equal true, $checked_verbose, 'On first load warnings should be enabled.' - assert Dependencies.loaded.include?(expanded) - Dependencies.clear - assert !Dependencies.loaded.include?(expanded) - assert Dependencies.history.include?(expanded) + assert ActiveSupport::Dependencies.loaded.include?(expanded) + ActiveSupport::Dependencies.clear + assert !ActiveSupport::Dependencies.loaded.include?(expanded) + assert ActiveSupport::Dependencies.history.include?(expanded) silence_warnings { require_dependency filename } assert_equal 2, $check_warnings_load_count assert_equal nil, $checked_verbose, 'After first load warnings should be left alone.' - assert Dependencies.loaded.include?(expanded) - Dependencies.clear - assert !Dependencies.loaded.include?(expanded) - assert Dependencies.history.include?(expanded) + assert ActiveSupport::Dependencies.loaded.include?(expanded) + ActiveSupport::Dependencies.clear + assert !ActiveSupport::Dependencies.loaded.include?(expanded) + assert ActiveSupport::Dependencies.history.include?(expanded) enable_warnings { require_dependency filename } assert_equal 3, $check_warnings_load_count assert_equal true, $checked_verbose, 'After first load warnings should be left alone.' - assert Dependencies.loaded.include?(expanded) + assert ActiveSupport::Dependencies.loaded.include?(expanded) end end @@ -113,7 +113,7 @@ class DependenciesTest < Test::Unit::TestCase assert_nothing_raised { require_dependency 'mutual_one' } assert_equal 2, $mutual_dependencies_count - Dependencies.clear + ActiveSupport::Dependencies.clear $mutual_dependencies_count = 0 assert_nothing_raised { require_dependency 'mutual_two' } @@ -239,7 +239,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_loadable_constants_for_path_should_handle_empty_autoloads - assert_equal [], Dependencies.loadable_constants_for_path('hello') + assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path('hello') end def test_loadable_constants_for_path_should_handle_relative_paths @@ -247,7 +247,7 @@ class DependenciesTest < Test::Unit::TestCase relative_root = File.dirname(__FILE__) + '/dependencies' ['', '/'].each do |suffix| with_loading fake_root + suffix do - assert_equal ["A::B"], Dependencies.loadable_constants_for_path(relative_root + '/a/b') + assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(relative_root + '/a/b') end end end @@ -255,106 +255,106 @@ class DependenciesTest < Test::Unit::TestCase def test_loadable_constants_for_path_should_provide_all_results fake_root = '/usr/apps/backpack' with_loading fake_root, fake_root + '/lib' do - root = Dependencies.load_paths.first - assert_equal ["Lib::A::B", "A::B"], Dependencies.loadable_constants_for_path(root + '/lib/a/b') + root = ActiveSupport::Dependencies.load_paths.first + assert_equal ["Lib::A::B", "A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(root + '/lib/a/b') end end def test_loadable_constants_for_path_should_uniq_results fake_root = '/usr/apps/backpack/lib' with_loading fake_root, fake_root + '/' do - root = Dependencies.load_paths.first - assert_equal ["A::B"], Dependencies.loadable_constants_for_path(root + '/a/b') + root = ActiveSupport::Dependencies.load_paths.first + assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(root + '/a/b') end end def test_loadable_constants_with_load_path_without_trailing_slash path = File.dirname(__FILE__) + '/autoloading_fixtures/class_folder/inline_class.rb' with_loading 'autoloading_fixtures/class/' do - assert_equal [], Dependencies.loadable_constants_for_path(path) + assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path(path) end end def test_qualified_const_defined - assert Dependencies.qualified_const_defined?("Object") - assert Dependencies.qualified_const_defined?("::Object") - assert Dependencies.qualified_const_defined?("::Object::Kernel") - assert Dependencies.qualified_const_defined?("::Object::Dependencies") - assert Dependencies.qualified_const_defined?("::Test::Unit::TestCase") + assert ActiveSupport::Dependencies.qualified_const_defined?("Object") + assert ActiveSupport::Dependencies.qualified_const_defined?("::Object") + assert ActiveSupport::Dependencies.qualified_const_defined?("::Object::Kernel") + assert ActiveSupport::Dependencies.qualified_const_defined?("::Object::Dependencies") + assert ActiveSupport::Dependencies.qualified_const_defined?("::Test::Unit::TestCase") end def test_qualified_const_defined_should_not_call_method_missing ModuleWithMissing.missing_count = 0 - assert ! Dependencies.qualified_const_defined?("ModuleWithMissing::A") + assert ! ActiveSupport::Dependencies.qualified_const_defined?("ModuleWithMissing::A") assert_equal 0, ModuleWithMissing.missing_count - assert ! Dependencies.qualified_const_defined?("ModuleWithMissing::A::B") + assert ! ActiveSupport::Dependencies.qualified_const_defined?("ModuleWithMissing::A::B") assert_equal 0, ModuleWithMissing.missing_count end def test_autoloaded? with_loading 'autoloading_fixtures' do - assert ! Dependencies.autoloaded?("ModuleFolder") - assert ! Dependencies.autoloaded?("ModuleFolder::NestedClass") + assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder") + assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") - assert Dependencies.autoloaded?(ModuleFolder) + assert ActiveSupport::Dependencies.autoloaded?(ModuleFolder) - assert Dependencies.autoloaded?("ModuleFolder") - assert ! Dependencies.autoloaded?("ModuleFolder::NestedClass") + assert ActiveSupport::Dependencies.autoloaded?("ModuleFolder") + assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") - assert Dependencies.autoloaded?(ModuleFolder::NestedClass) + assert ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass) - assert Dependencies.autoloaded?("ModuleFolder") - assert Dependencies.autoloaded?("ModuleFolder::NestedClass") + assert ActiveSupport::Dependencies.autoloaded?("ModuleFolder") + assert ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") - assert Dependencies.autoloaded?("::ModuleFolder") - assert Dependencies.autoloaded?(:ModuleFolder) + assert ActiveSupport::Dependencies.autoloaded?("::ModuleFolder") + assert ActiveSupport::Dependencies.autoloaded?(:ModuleFolder) # Anonymous modules aren't autoloaded. - assert !Dependencies.autoloaded?(Module.new) + assert !ActiveSupport::Dependencies.autoloaded?(Module.new) nil_name = Module.new def nil_name.name() nil end - assert !Dependencies.autoloaded?(nil_name) + assert !ActiveSupport::Dependencies.autoloaded?(nil_name) Object.class_eval { remove_const :ModuleFolder } end end def test_qualified_name_for - assert_equal "A", Dependencies.qualified_name_for(Object, :A) - assert_equal "A", Dependencies.qualified_name_for(:Object, :A) - assert_equal "A", Dependencies.qualified_name_for("Object", :A) - assert_equal "A", Dependencies.qualified_name_for("::Object", :A) - assert_equal "A", Dependencies.qualified_name_for("::Kernel", :A) + assert_equal "A", ActiveSupport::Dependencies.qualified_name_for(Object, :A) + assert_equal "A", ActiveSupport::Dependencies.qualified_name_for(:Object, :A) + assert_equal "A", ActiveSupport::Dependencies.qualified_name_for("Object", :A) + assert_equal "A", ActiveSupport::Dependencies.qualified_name_for("::Object", :A) + assert_equal "A", ActiveSupport::Dependencies.qualified_name_for("::Kernel", :A) - assert_equal "Dependencies::A", Dependencies.qualified_name_for(:Dependencies, :A) - assert_equal "Dependencies::A", Dependencies.qualified_name_for(Dependencies, :A) + assert_equal "ActiveSupport::Dependencies::A", ActiveSupport::Dependencies.qualified_name_for(:'ActiveSupport::Dependencies', :A) + assert_equal "ActiveSupport::Dependencies::A", ActiveSupport::Dependencies.qualified_name_for(ActiveSupport::Dependencies, :A) end def test_file_search with_loading 'dependencies' do - root = Dependencies.load_paths.first - assert_equal nil, Dependencies.search_for_file('service_three') - assert_equal nil, Dependencies.search_for_file('service_three.rb') - assert_equal root + '/service_one.rb', Dependencies.search_for_file('service_one') - assert_equal root + '/service_one.rb', Dependencies.search_for_file('service_one.rb') + root = ActiveSupport::Dependencies.load_paths.first + assert_equal nil, ActiveSupport::Dependencies.search_for_file('service_three') + assert_equal nil, ActiveSupport::Dependencies.search_for_file('service_three.rb') + assert_equal root + '/service_one.rb', ActiveSupport::Dependencies.search_for_file('service_one') + assert_equal root + '/service_one.rb', ActiveSupport::Dependencies.search_for_file('service_one.rb') end end def test_file_search_uses_first_in_load_path with_loading 'dependencies', 'autoloading_fixtures' do - deps, autoload = Dependencies.load_paths + deps, autoload = ActiveSupport::Dependencies.load_paths assert_match %r/dependencies/, deps assert_match %r/autoloading_fixtures/, autoload - assert_equal deps + '/conflict.rb', Dependencies.search_for_file('conflict') + assert_equal deps + '/conflict.rb', ActiveSupport::Dependencies.search_for_file('conflict') end with_loading 'autoloading_fixtures', 'dependencies' do - autoload, deps = Dependencies.load_paths + autoload, deps = ActiveSupport::Dependencies.load_paths assert_match %r/dependencies/, deps assert_match %r/autoloading_fixtures/, autoload - assert_equal autoload + '/conflict.rb', Dependencies.search_for_file('conflict') + assert_equal autoload + '/conflict.rb', ActiveSupport::Dependencies.search_for_file('conflict') end end @@ -383,7 +383,7 @@ class DependenciesTest < Test::Unit::TestCase with_loading 'autoloading_fixtures' do require_dependency '././counting_loader' assert_equal 1, $counting_loaded_times - assert_raises(ArgumentError) { Dependencies.load_missing_constant Object, :CountingLoader } + assert_raises(ArgumentError) { ActiveSupport::Dependencies.load_missing_constant Object, :CountingLoader } assert_equal 1, $counting_loaded_times end end @@ -407,12 +407,12 @@ class DependenciesTest < Test::Unit::TestCase def test_removal_from_tree_should_be_detected with_loading 'dependencies' do - root = Dependencies.load_paths.first + root = ActiveSupport::Dependencies.load_paths.first c = ServiceOne - Dependencies.clear + ActiveSupport::Dependencies.clear assert ! defined?(ServiceOne) begin - Dependencies.load_missing_constant(c, :FakeMissing) + ActiveSupport::Dependencies.load_missing_constant(c, :FakeMissing) flunk "Expected exception" rescue ArgumentError => e assert_match %r{ServiceOne has been removed from the module tree}i, e.message @@ -430,25 +430,25 @@ class DependenciesTest < Test::Unit::TestCase def test_load_once_paths_do_not_add_to_autoloaded_constants with_loading 'autoloading_fixtures' do - Dependencies.load_once_paths = Dependencies.load_paths.dup + ActiveSupport::Dependencies.load_once_paths = ActiveSupport::Dependencies.load_paths.dup - assert ! Dependencies.autoloaded?("ModuleFolder") - assert ! Dependencies.autoloaded?("ModuleFolder::NestedClass") - assert ! Dependencies.autoloaded?(ModuleFolder) + assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder") + assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") + assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder) 1 if ModuleFolder::NestedClass # 1 if to avoid warning - assert ! Dependencies.autoloaded?(ModuleFolder::NestedClass) + assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass) end ensure Object.class_eval { remove_const :ModuleFolder } - Dependencies.load_once_paths = [] + ActiveSupport::Dependencies.load_once_paths = [] end def test_application_should_special_case_application_controller with_loading 'autoloading_fixtures' do require_dependency 'application' assert_equal 10, ApplicationController - assert Dependencies.autoloaded?(:ApplicationController) + assert ActiveSupport::Dependencies.autoloaded?(:ApplicationController) end end @@ -463,15 +463,15 @@ class DependenciesTest < Test::Unit::TestCase def test_preexisting_constants_are_not_marked_as_autoloaded with_loading 'autoloading_fixtures' do require_dependency 'e' - assert Dependencies.autoloaded?(:E) - Dependencies.clear + assert ActiveSupport::Dependencies.autoloaded?(:E) + ActiveSupport::Dependencies.clear end Object.const_set :E, Class.new with_loading 'autoloading_fixtures' do require_dependency 'e' - assert ! Dependencies.autoloaded?(:E), "E shouldn't be marked autoloaded!" - Dependencies.clear + assert ! ActiveSupport::Dependencies.autoloaded?(:E), "E shouldn't be marked autoloaded!" + ActiveSupport::Dependencies.clear end ensure @@ -483,11 +483,11 @@ class DependenciesTest < Test::Unit::TestCase Object.const_set :M, Module.new M.unloadable - Dependencies.clear + ActiveSupport::Dependencies.clear assert ! defined?(M) Object.const_set :M, Module.new - Dependencies.clear + ActiveSupport::Dependencies.clear assert ! defined?(M), "Dependencies should unload unloadable constants each time" end end @@ -508,24 +508,24 @@ class DependenciesTest < Test::Unit::TestCase end def test_new_contants_in_without_constants - assert_equal [], (Dependencies.new_constants_in(Object) { }) - assert Dependencies.constant_watch_stack.empty? + assert_equal [], (ActiveSupport::Dependencies.new_constants_in(Object) { }) + assert ActiveSupport::Dependencies.constant_watch_stack.empty? end def test_new_constants_in_with_a_single_constant - assert_equal ["Hello"], Dependencies.new_constants_in(Object) { + assert_equal ["Hello"], ActiveSupport::Dependencies.new_constants_in(Object) { Object.const_set :Hello, 10 }.map(&:to_s) - assert Dependencies.constant_watch_stack.empty? + assert ActiveSupport::Dependencies.constant_watch_stack.empty? ensure Object.class_eval { remove_const :Hello } end def test_new_constants_in_with_nesting - outer = Dependencies.new_constants_in(Object) do + outer = ActiveSupport::Dependencies.new_constants_in(Object) do Object.const_set :OuterBefore, 10 - assert_equal ["Inner"], Dependencies.new_constants_in(Object) { + assert_equal ["Inner"], ActiveSupport::Dependencies.new_constants_in(Object) { Object.const_set :Inner, 20 }.map(&:to_s) @@ -533,7 +533,7 @@ class DependenciesTest < Test::Unit::TestCase end assert_equal ["OuterAfter", "OuterBefore"], outer.sort.map(&:to_s) - assert Dependencies.constant_watch_stack.empty? + assert ActiveSupport::Dependencies.constant_watch_stack.empty? ensure %w(OuterBefore Inner OuterAfter).each do |name| Object.class_eval { remove_const name if const_defined?(name) } @@ -543,10 +543,10 @@ class DependenciesTest < Test::Unit::TestCase def test_new_constants_in_module Object.const_set :M, Module.new - outer = Dependencies.new_constants_in(M) do + outer = ActiveSupport::Dependencies.new_constants_in(M) do M.const_set :OuterBefore, 10 - inner = Dependencies.new_constants_in(M) do + inner = ActiveSupport::Dependencies.new_constants_in(M) do M.const_set :Inner, 20 end assert_equal ["M::Inner"], inner @@ -554,17 +554,17 @@ class DependenciesTest < Test::Unit::TestCase M.const_set :OuterAfter, 30 end assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort - assert Dependencies.constant_watch_stack.empty? + assert ActiveSupport::Dependencies.constant_watch_stack.empty? ensure Object.class_eval { remove_const :M } end def test_new_constants_in_module_using_name - outer = Dependencies.new_constants_in(:M) do + outer = ActiveSupport::Dependencies.new_constants_in(:M) do Object.const_set :M, Module.new M.const_set :OuterBefore, 10 - inner = Dependencies.new_constants_in(:M) do + inner = ActiveSupport::Dependencies.new_constants_in(:M) do M.const_set :Inner, 20 end assert_equal ["M::Inner"], inner @@ -572,13 +572,13 @@ class DependenciesTest < Test::Unit::TestCase M.const_set :OuterAfter, 30 end assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort - assert Dependencies.constant_watch_stack.empty? + assert ActiveSupport::Dependencies.constant_watch_stack.empty? ensure Object.class_eval { remove_const :M } end def test_new_constants_in_with_inherited_constants - m = Dependencies.new_constants_in(:Object) do + m = ActiveSupport::Dependencies.new_constants_in(:Object) do Object.class_eval { include ModuleWithConstant } end assert_equal [], m @@ -586,7 +586,7 @@ class DependenciesTest < Test::Unit::TestCase def test_new_constants_in_with_illegal_module_name_raises_correct_error assert_raises(NameError) do - Dependencies.new_constants_in("Illegal-Name") {} + ActiveSupport::Dependencies.new_constants_in("Illegal-Name") {} end end @@ -598,10 +598,10 @@ class DependenciesTest < Test::Unit::TestCase require_dependency 'multiple_constant_file' assert defined?(MultipleConstantFile) assert defined?(SiblingConstant) - assert Dependencies.autoloaded?(:MultipleConstantFile) - assert Dependencies.autoloaded?(:SiblingConstant) + assert ActiveSupport::Dependencies.autoloaded?(:MultipleConstantFile) + assert ActiveSupport::Dependencies.autoloaded?(:SiblingConstant) - Dependencies.clear + ActiveSupport::Dependencies.clear assert ! defined?(MultipleConstantFile) assert ! defined?(SiblingConstant) @@ -617,10 +617,10 @@ class DependenciesTest < Test::Unit::TestCase assert defined?(MultipleConstantFile) assert defined?(SiblingConstant) - assert Dependencies.autoloaded?(:MultipleConstantFile) - assert Dependencies.autoloaded?(:SiblingConstant) + assert ActiveSupport::Dependencies.autoloaded?(:MultipleConstantFile) + assert ActiveSupport::Dependencies.autoloaded?(:SiblingConstant) - Dependencies.clear + ActiveSupport::Dependencies.clear assert ! defined?(MultipleConstantFile) assert ! defined?(SiblingConstant) @@ -636,10 +636,10 @@ class DependenciesTest < Test::Unit::TestCase assert defined?(ClassFolder::NestedClass) assert defined?(ClassFolder::SiblingClass) - assert Dependencies.autoloaded?("ClassFolder::NestedClass") - assert Dependencies.autoloaded?("ClassFolder::SiblingClass") + assert ActiveSupport::Dependencies.autoloaded?("ClassFolder::NestedClass") + assert ActiveSupport::Dependencies.autoloaded?("ClassFolder::SiblingClass") - Dependencies.clear + ActiveSupport::Dependencies.clear assert ! defined?(ClassFolder::NestedClass) assert ! defined?(ClassFolder::SiblingClass) @@ -655,10 +655,10 @@ class DependenciesTest < Test::Unit::TestCase assert defined?(ClassFolder::NestedClass) assert defined?(ClassFolder::SiblingClass) - assert Dependencies.autoloaded?("ClassFolder::NestedClass") - assert Dependencies.autoloaded?("ClassFolder::SiblingClass") + assert ActiveSupport::Dependencies.autoloaded?("ClassFolder::NestedClass") + assert ActiveSupport::Dependencies.autoloaded?("ClassFolder::SiblingClass") - Dependencies.clear + ActiveSupport::Dependencies.clear assert ! defined?(ClassFolder::NestedClass) assert ! defined?(ClassFolder::SiblingClass) @@ -693,7 +693,7 @@ class DependenciesTest < Test::Unit::TestCase def test_autoload_doesnt_shadow_error_when_mechanism_not_set_to_load with_loading 'autoloading_fixtures' do - Dependencies.mechanism = :require + ActiveSupport::Dependencies.mechanism = :require 2.times do assert_raise(NameError) {"RaisesNameError".constantize} end @@ -727,39 +727,39 @@ class DependenciesTest < Test::Unit::TestCase def test_remove_constant_handles_double_colon_at_start Object.const_set 'DeleteMe', Module.new DeleteMe.const_set 'OrMe', Module.new - Dependencies.remove_constant "::DeleteMe::OrMe" + ActiveSupport::Dependencies.remove_constant "::DeleteMe::OrMe" assert ! defined?(DeleteMe::OrMe) assert defined?(DeleteMe) - Dependencies.remove_constant "::DeleteMe" + ActiveSupport::Dependencies.remove_constant "::DeleteMe" assert ! defined?(DeleteMe) end def test_load_once_constants_should_not_be_unloaded with_loading 'autoloading_fixtures' do - Dependencies.load_once_paths = Dependencies.load_paths + ActiveSupport::Dependencies.load_once_paths = ActiveSupport::Dependencies.load_paths ::A.to_s assert defined?(A) - Dependencies.clear + ActiveSupport::Dependencies.clear assert defined?(A) end ensure - Dependencies.load_once_paths = [] + ActiveSupport::Dependencies.load_once_paths = [] Object.class_eval { remove_const :A if const_defined?(:A) } end def test_load_once_paths_should_behave_when_recursively_loading with_loading 'dependencies', 'autoloading_fixtures' do - Dependencies.load_once_paths = [Dependencies.load_paths.last] + ActiveSupport::Dependencies.load_once_paths = [ActiveSupport::Dependencies.load_paths.last] assert !defined?(CrossSiteDependency) assert_nothing_raised { CrossSiteDepender.nil? } assert defined?(CrossSiteDependency) - assert !Dependencies.autoloaded?(CrossSiteDependency), + assert !ActiveSupport::Dependencies.autoloaded?(CrossSiteDependency), "CrossSiteDependency shouldn't be marked as autoloaded!" - Dependencies.clear + ActiveSupport::Dependencies.clear assert defined?(CrossSiteDependency), "CrossSiteDependency shouldn't have been unloaded!" end ensure - Dependencies.load_once_paths = [] + ActiveSupport::Dependencies.load_once_paths = [] end end diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index ebfa405947..27e9573ce2 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -24,6 +24,11 @@ class Deprecatee def d; end def e; end deprecate :a, :b, :c => :e, :d => "you now need to do something extra for this one" + + module B + C = 1 + end + A = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Deprecatee::A', 'Deprecatee::B::C') end @@ -83,6 +88,11 @@ class DeprecationTest < Test::Unit::TestCase assert_not_deprecated { assert_equal @dtc.request.inspect, @dtc.old_request.inspect } end + def test_deprecated_constant_proxy + assert_not_deprecated { Deprecatee::B::C } + assert_deprecated('Deprecatee::A') { assert_equal Deprecatee::B::C, Deprecatee::A } + end + def test_assert_deprecation_without_match assert_deprecated do @dtc.partially diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb new file mode 100644 index 0000000000..2a24c0bd0d --- /dev/null +++ b/activesupport/test/gzip_test.rb @@ -0,0 +1,7 @@ +require 'abstract_unit' + +class GzipTest < Test::Unit::TestCase + def test_compress_should_decompress_to_the_same_value + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World")) + end +end
\ No newline at end of file diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 26af245ff7..4ce9cbb705 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -12,188 +12,188 @@ class InflectorTest < Test::Unit::TestCase include InflectorTestCases def test_pluralize_plurals - assert_equal "plurals", Inflector.pluralize("plurals") - assert_equal "Plurals", Inflector.pluralize("Plurals") + assert_equal "plurals", ActiveSupport::Inflector.pluralize("plurals") + assert_equal "Plurals", ActiveSupport::Inflector.pluralize("Plurals") end def test_pluralize_empty_string - assert_equal "", Inflector.pluralize("") + assert_equal "", ActiveSupport::Inflector.pluralize("") end SingularToPlural.each do |singular, plural| define_method "test_pluralize_#{singular}" do - assert_equal(plural, Inflector.pluralize(singular)) - assert_equal(plural.capitalize, Inflector.pluralize(singular.capitalize)) + assert_equal(plural, ActiveSupport::Inflector.pluralize(singular)) + assert_equal(plural.capitalize, ActiveSupport::Inflector.pluralize(singular.capitalize)) end end SingularToPlural.each do |singular, plural| define_method "test_singularize_#{plural}" do - assert_equal(singular, Inflector.singularize(plural)) - assert_equal(singular.capitalize, Inflector.singularize(plural.capitalize)) + assert_equal(singular, ActiveSupport::Inflector.singularize(plural)) + assert_equal(singular.capitalize, ActiveSupport::Inflector.singularize(plural.capitalize)) end end MixtureToTitleCase.each do |before, titleized| define_method "test_titleize_#{before}" do - assert_equal(titleized, Inflector.titleize(before)) + assert_equal(titleized, ActiveSupport::Inflector.titleize(before)) end end def test_camelize CamelToUnderscore.each do |camel, underscore| - assert_equal(camel, Inflector.camelize(underscore)) + assert_equal(camel, ActiveSupport::Inflector.camelize(underscore)) end end def test_underscore CamelToUnderscore.each do |camel, underscore| - assert_equal(underscore, Inflector.underscore(camel)) + assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) end CamelToUnderscoreWithoutReverse.each do |camel, underscore| - assert_equal(underscore, Inflector.underscore(camel)) + assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) end end def test_camelize_with_module CamelWithModuleToUnderscoreWithSlash.each do |camel, underscore| - assert_equal(camel, Inflector.camelize(underscore)) + assert_equal(camel, ActiveSupport::Inflector.camelize(underscore)) end end def test_underscore_with_slashes CamelWithModuleToUnderscoreWithSlash.each do |camel, underscore| - assert_equal(underscore, Inflector.underscore(camel)) + assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) end end def test_demodulize - assert_equal "Account", Inflector.demodulize("MyApplication::Billing::Account") + assert_equal "Account", ActiveSupport::Inflector.demodulize("MyApplication::Billing::Account") end def test_foreign_key ClassNameToForeignKeyWithUnderscore.each do |klass, foreign_key| - assert_equal(foreign_key, Inflector.foreign_key(klass)) + assert_equal(foreign_key, ActiveSupport::Inflector.foreign_key(klass)) end ClassNameToForeignKeyWithoutUnderscore.each do |klass, foreign_key| - assert_equal(foreign_key, Inflector.foreign_key(klass, false)) + assert_equal(foreign_key, ActiveSupport::Inflector.foreign_key(klass, false)) end end def test_tableize ClassNameToTableName.each do |class_name, table_name| - assert_equal(table_name, Inflector.tableize(class_name)) + assert_equal(table_name, ActiveSupport::Inflector.tableize(class_name)) end end def test_classify ClassNameToTableName.each do |class_name, table_name| - assert_equal(class_name, Inflector.classify(table_name)) - assert_equal(class_name, Inflector.classify("table_prefix." + table_name)) + assert_equal(class_name, ActiveSupport::Inflector.classify(table_name)) + assert_equal(class_name, ActiveSupport::Inflector.classify("table_prefix." + table_name)) end end def test_classify_with_symbol assert_nothing_raised do - assert_equal 'FooBar', Inflector.classify(:foo_bars) + assert_equal 'FooBar', ActiveSupport::Inflector.classify(:foo_bars) end end def test_classify_with_leading_schema_name - assert_equal 'FooBar', Inflector.classify('schema.foo_bar') + assert_equal 'FooBar', ActiveSupport::Inflector.classify('schema.foo_bar') end def test_humanize UnderscoreToHuman.each do |underscore, human| - assert_equal(human, Inflector.humanize(underscore)) + assert_equal(human, ActiveSupport::Inflector.humanize(underscore)) end end def test_constantize - assert_nothing_raised { assert_equal Ace::Base::Case, Inflector.constantize("Ace::Base::Case") } - assert_nothing_raised { assert_equal Ace::Base::Case, Inflector.constantize("::Ace::Base::Case") } - assert_nothing_raised { assert_equal InflectorTest, Inflector.constantize("InflectorTest") } - assert_nothing_raised { assert_equal InflectorTest, Inflector.constantize("::InflectorTest") } - assert_raises(NameError) { Inflector.constantize("UnknownClass") } - assert_raises(NameError) { Inflector.constantize("An invalid string") } - assert_raises(NameError) { Inflector.constantize("InvalidClass\n") } + assert_nothing_raised { assert_equal Ace::Base::Case, ActiveSupport::Inflector.constantize("Ace::Base::Case") } + assert_nothing_raised { assert_equal Ace::Base::Case, ActiveSupport::Inflector.constantize("::Ace::Base::Case") } + assert_nothing_raised { assert_equal InflectorTest, ActiveSupport::Inflector.constantize("InflectorTest") } + assert_nothing_raised { assert_equal InflectorTest, ActiveSupport::Inflector.constantize("::InflectorTest") } + assert_raises(NameError) { ActiveSupport::Inflector.constantize("UnknownClass") } + assert_raises(NameError) { ActiveSupport::Inflector.constantize("An invalid string") } + assert_raises(NameError) { ActiveSupport::Inflector.constantize("InvalidClass\n") } end def test_constantize_does_lexical_lookup - assert_raises(NameError) { Inflector.constantize("Ace::Base::InflectorTest") } + assert_raises(NameError) { ActiveSupport::Inflector.constantize("Ace::Base::InflectorTest") } end def test_ordinal OrdinalNumbers.each do |number, ordinalized| - assert_equal(ordinalized, Inflector.ordinalize(number)) + assert_equal(ordinalized, ActiveSupport::Inflector.ordinalize(number)) end end def test_dasherize UnderscoresToDashes.each do |underscored, dasherized| - assert_equal(dasherized, Inflector.dasherize(underscored)) + assert_equal(dasherized, ActiveSupport::Inflector.dasherize(underscored)) end end def test_underscore_as_reverse_of_dasherize UnderscoresToDashes.each do |underscored, dasherized| - assert_equal(underscored, Inflector.underscore(Inflector.dasherize(underscored))) + assert_equal(underscored, ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.dasherize(underscored))) end end def test_underscore_to_lower_camel UnderscoreToLowerCamel.each do |underscored, lower_camel| - assert_equal(lower_camel, Inflector.camelize(underscored, false)) + assert_equal(lower_camel, ActiveSupport::Inflector.camelize(underscored, false)) end end - + %w{plurals singulars uncountables}.each do |inflection_type| class_eval " def test_clear_#{inflection_type} - cached_values = Inflector.inflections.#{inflection_type} - Inflector.inflections.clear :#{inflection_type} - assert Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\" - Inflector.inflections.instance_variable_set :@#{inflection_type}, cached_values + cached_values = ActiveSupport::Inflector.inflections.#{inflection_type} + ActiveSupport::Inflector.inflections.clear :#{inflection_type} + assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\" + ActiveSupport::Inflector.inflections.instance_variable_set :@#{inflection_type}, cached_values end " end - + def test_clear_all - cached_values = Inflector.inflections.plurals, Inflector.inflections.singulars, Inflector.inflections.uncountables - Inflector.inflections.clear :all - assert Inflector.inflections.plurals.empty? - assert Inflector.inflections.singulars.empty? - assert Inflector.inflections.uncountables.empty? - Inflector.inflections.instance_variable_set :@plurals, cached_values[0] - Inflector.inflections.instance_variable_set :@singulars, cached_values[1] - Inflector.inflections.instance_variable_set :@uncountables, cached_values[2] - end - + cached_values = ActiveSupport::Inflector.inflections.plurals, ActiveSupport::Inflector.inflections.singulars, ActiveSupport::Inflector.inflections.uncountables + ActiveSupport::Inflector.inflections.clear :all + assert ActiveSupport::Inflector.inflections.plurals.empty? + assert ActiveSupport::Inflector.inflections.singulars.empty? + assert ActiveSupport::Inflector.inflections.uncountables.empty? + ActiveSupport::Inflector.inflections.instance_variable_set :@plurals, cached_values[0] + ActiveSupport::Inflector.inflections.instance_variable_set :@singulars, cached_values[1] + ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_values[2] + end + def test_clear_with_default - cached_values = Inflector.inflections.plurals, Inflector.inflections.singulars, Inflector.inflections.uncountables - Inflector.inflections.clear - assert Inflector.inflections.plurals.empty? - assert Inflector.inflections.singulars.empty? - assert Inflector.inflections.uncountables.empty? - Inflector.inflections.instance_variable_set :@plurals, cached_values[0] - Inflector.inflections.instance_variable_set :@singulars, cached_values[1] - Inflector.inflections.instance_variable_set :@uncountables, cached_values[2] + cached_values = ActiveSupport::Inflector.inflections.plurals, ActiveSupport::Inflector.inflections.singulars, ActiveSupport::Inflector.inflections.uncountables + ActiveSupport::Inflector.inflections.clear + assert ActiveSupport::Inflector.inflections.plurals.empty? + assert ActiveSupport::Inflector.inflections.singulars.empty? + assert ActiveSupport::Inflector.inflections.uncountables.empty? + ActiveSupport::Inflector.inflections.instance_variable_set :@plurals, cached_values[0] + ActiveSupport::Inflector.inflections.instance_variable_set :@singulars, cached_values[1] + ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_values[2] end Irregularities.each do |irregularity| singular, plural = *irregularity - Inflector.inflections do |inflect| + ActiveSupport::Inflector.inflections do |inflect| define_method("test_irregularity_between_#{singular}_and_#{plural}") do inflect.irregular(singular, plural) - assert_equal singular, Inflector.singularize(plural) - assert_equal plural, Inflector.pluralize(singular) + assert_equal singular, ActiveSupport::Inflector.singularize(plural) + assert_equal plural, ActiveSupport::Inflector.pluralize(singular) end end end [ :all, [] ].each do |scope| - Inflector.inflections do |inflect| + ActiveSupport::Inflector.inflections do |inflect| define_method("test_clear_inflections_with_#{scope.kind_of?(Array) ? "no_arguments" : scope}") do # save all the inflections singulars, plurals, uncountables = inflect.singulars, inflect.plurals, inflect.uncountables @@ -218,7 +218,7 @@ class InflectorTest < Test::Unit::TestCase end { :singulars => :singular, :plurals => :plural, :uncountables => :uncountable }.each do |scope, method| - Inflector.inflections do |inflect| + ActiveSupport::Inflector.inflections do |inflect| define_method("test_clear_inflections_with_#{scope}") do # save the inflections values = inflect.send(scope) diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb index 14be48724e..98a6ad6b26 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -42,4 +42,23 @@ class OrderedHashTest < Test::Unit::TestCase assert_nil @ordered_hash.delete(bad_key) end + + def test_has_key + assert_equal true, @ordered_hash.has_key?('blue') + assert_equal true, @ordered_hash.key?('blue') + assert_equal true, @ordered_hash.include?('blue') + assert_equal true, @ordered_hash.member?('blue') + + assert_equal false, @ordered_hash.has_key?('indigo') + assert_equal false, @ordered_hash.key?('indigo') + assert_equal false, @ordered_hash.include?('indigo') + assert_equal false, @ordered_hash.member?('indigo') + end + + def test_has_value + assert_equal true, @ordered_hash.has_value?('000099') + assert_equal true, @ordered_hash.value?('000099') + assert_equal false, @ordered_hash.has_value?('ABCABC') + assert_equal false, @ordered_hash.value?('ABCABC') + end end diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb index fb7a58d0b0..e48425ca25 100644 --- a/activesupport/test/ordered_options_test.rb +++ b/activesupport/test/ordered_options_test.rb @@ -2,7 +2,7 @@ require 'abstract_unit' class OrderedOptionsTest < Test::Unit::TestCase def test_usage - a = OrderedOptions.new + a = ActiveSupport::OrderedOptions.new assert_nil a[:not_set] @@ -20,7 +20,7 @@ class OrderedOptionsTest < Test::Unit::TestCase end def test_looping - a = OrderedOptions.new + a = ActiveSupport::OrderedOptions.new a[:allow_concurreny] = true a["else_where"] = 56 @@ -34,7 +34,7 @@ class OrderedOptionsTest < Test::Unit::TestCase end def test_method_access - a = OrderedOptions.new + a = ActiveSupport::OrderedOptions.new assert_nil a.not_set diff --git a/activesupport/test/string_inquirer_test.rb b/activesupport/test/string_inquirer_test.rb new file mode 100644 index 0000000000..dda7850e6b --- /dev/null +++ b/activesupport/test/string_inquirer_test.rb @@ -0,0 +1,15 @@ +require 'abstract_unit' + +class StringInquirerTest < Test::Unit::TestCase + def test_match + assert ActiveSupport::StringInquirer.new("production").production? + end + + def test_miss + assert !ActiveSupport::StringInquirer.new("production").development? + end + + def test_missing_question_mark + assert_raises(NoMethodError) { ActiveSupport::StringInquirer.new("production").production } + end +end diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index 1e75e18602..26a45af255 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -84,6 +84,12 @@ class SetupAndTeardownTest < Test::Unit::TestCase assert_equal [:foo, :sentinel, :foo], self.class.teardown_callback_chain.map(&:method) end + def setup + end + + def teardown + end + protected def reset_callback_record @called_back = [] diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index f3069b589b..b42dcd17f8 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -1,10 +1,9 @@ require 'abstract_unit' class TimeZoneTest < Test::Unit::TestCase - def test_utc_to_local silence_warnings do # silence warnings raised by tzinfo gem - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] assert_equal Time.utc(1999, 12, 31, 19), zone.utc_to_local(Time.utc(2000, 1)) # standard offset -0500 assert_equal Time.utc(2000, 6, 30, 20), zone.utc_to_local(Time.utc(2000, 7)) # dst offset -0400 end @@ -12,41 +11,41 @@ class TimeZoneTest < Test::Unit::TestCase def test_local_to_utc silence_warnings do # silence warnings raised by tzinfo gem - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] assert_equal Time.utc(2000, 1, 1, 5), zone.local_to_utc(Time.utc(2000, 1)) # standard offset -0500 assert_equal Time.utc(2000, 7, 1, 4), zone.local_to_utc(Time.utc(2000, 7)) # dst offset -0400 end end - + def test_period_for_local silence_warnings do # silence warnings raised by tzinfo gem - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] assert_instance_of TZInfo::TimezonePeriod, zone.period_for_local(Time.utc(2000)) end end - - TimeZone::MAPPING.keys.each do |name| + + ActiveSupport::TimeZone::MAPPING.keys.each do |name| define_method("test_map_#{name.downcase.gsub(/[^a-z]/, '_')}_to_tzinfo") do silence_warnings do # silence warnings raised by tzinfo gem - zone = TimeZone[name] + zone = ActiveSupport::TimeZone[name] assert zone.tzinfo.respond_to?(:period_for_local) end end end def test_from_integer_to_map - assert_instance_of TimeZone, TimeZone[-28800] # PST + assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[-28800] # PST end def test_from_duration_to_map - assert_instance_of TimeZone, TimeZone[-480.minutes] # PST + assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[-480.minutes] # PST end - TimeZone.all.each do |zone| + ActiveSupport::TimeZone.all.each do |zone| name = zone.name.downcase.gsub(/[^a-z]/, '_') define_method("test_from_#{name}_to_map") do silence_warnings do # silence warnings raised by tzinfo gem - assert_instance_of TimeZone, TimeZone[zone.name] + assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[zone.name] end end @@ -62,62 +61,62 @@ class TimeZoneTest < Test::Unit::TestCase def test_now with_env_tz 'US/Eastern' do Time.stubs(:now).returns(Time.local(2000)) - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] assert_instance_of ActiveSupport::TimeWithZone, zone.now assert_equal Time.utc(2000,1,1,5), zone.now.utc assert_equal Time.utc(2000), zone.now.time assert_equal zone, zone.now.time_zone end end - + def test_now_enforces_spring_dst_rules with_env_tz 'US/Eastern' do Time.stubs(:now).returns(Time.local(2006,4,2,2)) # 2AM springs forward to 3AM - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] assert_equal Time.utc(2006,4,2,3), zone.now.time assert_equal true, zone.now.dst? end end - + def test_now_enforces_fall_dst_rules with_env_tz 'US/Eastern' do Time.stubs(:now).returns(Time.at(1162098000)) # equivalent to 1AM DST - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] assert_equal Time.utc(2006,10,29,1), zone.now.time assert_equal true, zone.now.dst? end end - + def test_today Time.stubs(:now).returns(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST - assert_equal Date.new(1999, 12, 31), TimeZone['Eastern Time (US & Canada)'].today + assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today Time.stubs(:now).returns(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST - assert_equal Date.new(2000, 1, 1), TimeZone['Eastern Time (US & Canada)'].today + assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today Time.stubs(:now).returns(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST - assert_equal Date.new(2000, 1, 1), TimeZone['Eastern Time (US & Canada)'].today + assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today Time.stubs(:now).returns(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST - assert_equal Date.new(2000, 1, 2), TimeZone['Eastern Time (US & Canada)'].today + assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today end end - + def test_local silence_warnings do # silence warnings raised by tzinfo gem - time = TimeZone["Hawaii"].local(2007, 2, 5, 15, 30, 45) + time = ActiveSupport::TimeZone["Hawaii"].local(2007, 2, 5, 15, 30, 45) assert_equal Time.utc(2007, 2, 5, 15, 30, 45), time.time - assert_equal TimeZone["Hawaii"], time.time_zone + assert_equal ActiveSupport::TimeZone["Hawaii"], time.time_zone end end def test_local_with_old_date silence_warnings do # silence warnings raised by tzinfo gem - time = TimeZone["Hawaii"].local(1850, 2, 5, 15, 30, 45) + time = ActiveSupport::TimeZone["Hawaii"].local(1850, 2, 5, 15, 30, 45) assert_equal [45,30,15,5,2,1850], time.to_a[0,6] - assert_equal TimeZone["Hawaii"], time.time_zone + assert_equal ActiveSupport::TimeZone["Hawaii"], time.time_zone end end def test_local_enforces_spring_dst_rules - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] twz = zone.local(2006,4,2,1,59,59) # 1 second before DST start assert_equal Time.utc(2006,4,2,1,59,59), twz.time assert_equal Time.utc(2006,4,2,6,59,59), twz.utc @@ -138,16 +137,16 @@ class TimeZoneTest < Test::Unit::TestCase def test_local_enforces_fall_dst_rules # 1AM during fall DST transition is ambiguous, it could be either DST or non-DST 1AM # Mirroring Time.local behavior, this method selects the DST time - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] twz = zone.local(2006,10,29,1) assert_equal Time.utc(2006,10,29,1), twz.time assert_equal Time.utc(2006,10,29,5), twz.utc - assert_equal true, twz.dst? + assert_equal true, twz.dst? assert_equal 'EDT', twz.zone end def test_at - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] secs = 946684800.0 twz = zone.at(secs) assert_equal Time.utc(1999,12,31,19), twz.time @@ -157,7 +156,7 @@ class TimeZoneTest < Test::Unit::TestCase end def test_at_with_old_date - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] secs = DateTime.civil(1850).to_f twz = zone.at(secs) assert_equal [1850, 1, 1, 0], [twz.utc.year, twz.utc.mon, twz.utc.day, twz.utc.hour] @@ -166,7 +165,7 @@ class TimeZoneTest < Test::Unit::TestCase end def test_parse - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] twz = zone.parse('1999-12-31 19:00:00') assert_equal Time.utc(1999,12,31,19), twz.time assert_equal Time.utc(2000), twz.utc @@ -175,7 +174,7 @@ class TimeZoneTest < Test::Unit::TestCase def test_parse_string_with_timezone (-11..13).each do |timezone_offset| - zone = TimeZone[timezone_offset] + zone = ActiveSupport::TimeZone[timezone_offset] twz = zone.parse('1999-12-31 19:00:00') assert_equal twz, zone.parse(twz.to_s) end @@ -183,25 +182,25 @@ class TimeZoneTest < Test::Unit::TestCase def test_parse_with_old_date silence_warnings do # silence warnings raised by tzinfo gem - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] twz = zone.parse('1850-12-31 19:00:00') assert_equal [0,0,19,31,12,1850], twz.to_a[0,6] assert_equal zone, twz.time_zone end end - + def test_parse_far_future_date_with_time_zone_offset_in_string silence_warnings do # silence warnings raised by tzinfo gem - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] twz = zone.parse('2050-12-31 19:00:00 -10:00') # i.e., 2050-01-01 05:00:00 UTC assert_equal [0,0,0,1,1,2051], twz.to_a[0,6] assert_equal zone, twz.time_zone end end - + def test_parse_returns_nil_when_string_without_date_information_is_passed_in silence_warnings do # silence warnings raised by tzinfo gem - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] assert_nil zone.parse('foobar') assert_nil zone.parse(' ') end @@ -209,80 +208,80 @@ class TimeZoneTest < Test::Unit::TestCase uses_mocha 'TestParseWithIncompleteDate' do def test_parse_with_incomplete_date - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] zone.stubs(:now).returns zone.local(1999,12,31) twz = zone.parse('19:00:00') assert_equal Time.utc(1999,12,31,19), twz.time end end - + def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize silence_warnings do # silence warnings raised by tzinfo gem tzinfo = TZInfo::Timezone.get('America/New_York') - zone = TimeZone.create(tzinfo.name, nil, tzinfo) + zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo) assert_equal nil, zone.instance_variable_get('@utc_offset') assert_equal(-18_000, zone.utc_offset) end end - + def test_formatted_offset_positive - zone = TimeZone['Moscow'] + zone = ActiveSupport::TimeZone['Moscow'] assert_equal "+03:00", zone.formatted_offset assert_equal "+0300", zone.formatted_offset(false) end - + def test_formatted_offset_negative - zone = TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] assert_equal "-05:00", zone.formatted_offset assert_equal "-0500", zone.formatted_offset(false) end - + def test_formatted_offset_zero - zone = TimeZone['London'] + zone = ActiveSupport::TimeZone['London'] assert_equal "+00:00", zone.formatted_offset assert_equal "UTC", zone.formatted_offset(true, 'UTC') end - + def test_zone_compare - zone1 = TimeZone['Central Time (US & Canada)'] # offset -0600 - zone2 = TimeZone['Eastern Time (US & Canada)'] # offset -0500 + zone1 = ActiveSupport::TimeZone['Central Time (US & Canada)'] # offset -0600 + zone2 = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] # offset -0500 assert zone1 < zone2 assert zone2 > zone1 assert zone1 == zone1 end - + def test_to_s - assert_equal "(GMT+03:00) Moscow", TimeZone['Moscow'].to_s + assert_equal "(GMT+03:00) Moscow", ActiveSupport::TimeZone['Moscow'].to_s end - + def test_all_sorted - all = TimeZone.all + all = ActiveSupport::TimeZone.all 1.upto( all.length-1 ) do |i| assert all[i-1] < all[i] end end - + def test_index - assert_nil TimeZone["bogus"] - assert_instance_of TimeZone, TimeZone["Central Time (US & Canada)"] - assert_instance_of TimeZone, TimeZone[8] - assert_raises(ArgumentError) { TimeZone[false] } + assert_nil ActiveSupport::TimeZone["bogus"] + assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone["Central Time (US & Canada)"] + assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[8] + assert_raises(ArgumentError) { ActiveSupport::TimeZone[false] } end - + def test_new - assert_equal TimeZone["Central Time (US & Canada)"], TimeZone.new("Central Time (US & Canada)") + assert_equal ActiveSupport::TimeZone["Central Time (US & Canada)"], ActiveSupport::TimeZone.new("Central Time (US & Canada)") end - + def test_us_zones - assert TimeZone.us_zones.include?(TimeZone["Hawaii"]) - assert !TimeZone.us_zones.include?(TimeZone["Kuala Lumpur"]) - end - + assert ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Hawaii"]) + assert !ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Kuala Lumpur"]) + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz yield ensure old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end + end end diff --git a/doc/template/horo.rb b/doc/template/horo.rb new file mode 100644 index 0000000000..e028422a2e --- /dev/null +++ b/doc/template/horo.rb @@ -0,0 +1,613 @@ +# Horo RDoc template +# Author: Hongli Lai - http://izumi.plan99.net/blog/ +# +# Based on the Jamis template: +# http://weblog.jamisbuck.org/2005/4/8/rdoc-template + +if defined?(RDoc::Diagram) + RDoc::Diagram.class_eval do + remove_const(:FONT) + const_set(:FONT, "\"Bitstream Vera Sans\"") + end +end + +module RDoc +module Page + +FONTS = "\"Bitstream Vera Sans\", Verdana, Arial, Helvetica, sans-serif" + +STYLE = <<CSS +a { + color: #00F; + text-decoration: none; +} + +a:hover { + color: #77F; + text-decoration: underline; +} + +body, td, p { + font-family: %fonts%; + background: #FFF; + color: #000; + margin: 0px; + font-size: small; +} + +p { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +#content { + margin: 2em; + margin-left: 3.5em; + margin-right: 3.5em; +} + +#description p { + margin-bottom: 0.5em; +} + +.sectiontitle { + margin-top: 1em; + margin-bottom: 1em; + padding: 0.5em; + padding-left: 2em; + background: #005; + color: #FFF; + font-weight: bold; +} + +.attr-rw { + padding-left: 1em; + padding-right: 1em; + text-align: center; + color: #055; +} + +.attr-name { + font-weight: bold; +} + +.attr-desc { +} + +.attr-value { + font-family: monospace; +} + +.file-title-prefix { + font-size: large; +} + +.file-title { + font-size: large; + font-weight: bold; + background: #005; + color: #FFF; +} + +.banner { + background: #005; + color: #FFF; + border: 1px solid black; + padding: 1em; +} + +.banner td { + background: transparent; + color: #FFF; +} + +h1 a, h2 a, .sectiontitle a, .banner a { + color: #FF0; +} + +h1 a:hover, h2 a:hover, .sectiontitle a:hover, .banner a:hover { + color: #FF7; +} + +.dyn-source { + display: none; + background: #fffde8; + color: #000; + border: #ffe0bb dotted 1px; + margin: 0.5em 2em 0.5em 2em; + padding: 0.5em; +} + +.dyn-source .cmt { + color: #00F; + font-style: italic; +} + +.dyn-source .kw { + color: #070; + font-weight: bold; +} + +.method { + margin-left: 1em; + margin-right: 1em; + margin-bottom: 1em; +} + +.description pre { + padding: 0.5em; + border: #ffe0bb dotted 1px; + background: #fffde8; +} + +.method .title { + font-family: monospace; + font-size: large; + border-bottom: 1px dashed black; + margin-bottom: 0.3em; + padding-bottom: 0.1em; +} + +.method .description, .method .sourcecode { + margin-left: 1em; +} + +.description p, .sourcecode p { + margin-bottom: 0.5em; +} + +.method .sourcecode p.source-link { + text-indent: 0em; + margin-top: 0.5em; +} + +.method .aka { + margin-top: 0.3em; + margin-left: 1em; + font-style: italic; + text-indent: 2em; +} + +h1 { + padding: 1em; + margin-left: -1.5em; + font-size: x-large; + font-weight: bold; + color: #FFF; + background: #007; +} + +h2 { + padding: 0.5em 1em 0.5em 1em; + margin-left: -1.5em; + font-size: large; + font-weight: bold; + color: #FFF; + background: #009; +} + +h3, h4, h5, h6 { + color: #220088; + border-bottom: #5522bb solid 1px; +} + +.sourcecode > pre { + padding: 0.5em; + border: 1px dotted black; + background: #FFE; +} + +dt { + font-weight: bold +} + +dd { + margin-bottom: 0.7em; +} +CSS + +XHTML_PREAMBLE = %{<?xml version="1.0" encoding="%charset%"?> +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +} + +XHTML_FRAMESET_PREAMBLE = %{ +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"> +} + +HEADER = XHTML_PREAMBLE + <<ENDHEADER +<html> + <head> + <title>%title%</title> + <meta http-equiv="Content-Type" content="text/html; charset=%charset%" /> + <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" /> + + <script language="JavaScript" type="text/javascript"> + // <![CDATA[ + + function toggleSource( id ) + { + var elem + var link + + if( document.getElementById ) + { + elem = document.getElementById( id ) + link = document.getElementById( "l_" + id ) + } + else if ( document.all ) + { + elem = eval( "document.all." + id ) + link = eval( "document.all.l_" + id ) + } + else + return false; + + if( elem.style.display == "block" ) + { + elem.style.display = "none" + link.innerHTML = "show source" + } + else + { + elem.style.display = "block" + link.innerHTML = "hide source" + } + } + + function openCode( url ) + { + window.open( url, "SOURCE_CODE", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=480,width=750" ).focus(); + } + // ]]> + </script> + </head> + + <body> +ENDHEADER + +FILE_PAGE = <<HTML +<table border='0' cellpadding='0' cellspacing='0' width="100%" class='banner'> + <tr><td> + <table width="100%" border='0' cellpadding='0' cellspacing='0'><tr> + <td class="file-title" colspan="2"><span class="file-title-prefix">File</span><br />%short_name%</td> + <td align="right"> + <table border='0' cellspacing="0" cellpadding="2"> + <tr> + <td>Path:</td> + <td>%full_path% +IF:cvsurl + (<a href="%cvsurl%">CVS</a>) +ENDIF:cvsurl + </td> + </tr> + <tr> + <td>Modified:</td> + <td>%dtm_modified%</td> + </tr> + </table> + </td></tr> + </table> + </td></tr> +</table><br /> +HTML + +################################################################### + +CLASS_PAGE = <<HTML +<table width="100%" border='0' cellpadding='0' cellspacing='0' class='banner'><tr> + <td class="file-title"><span class="file-title-prefix">%classmod%</span><br />%full_name%</td> + <td align="right"> + <table cellspacing="0" cellpadding="2"> + <tr valign="top"> + <td>In:</td> + <td> +START:infiles +HREF:full_path_url:full_path: +IF:cvsurl + (<a href="%cvsurl%">CVS</a>) +ENDIF:cvsurl +END:infiles + </td> + </tr> +IF:parent + <tr> + <td>Parent:</td> + <td> +IF:par_url + <a href="%par_url%"> +ENDIF:par_url +%parent% +IF:par_url + </a> +ENDIF:par_url + </td> + </tr> +ENDIF:parent + </table> + </td> + </tr> + </table> +HTML + +################################################################### + +METHOD_LIST = <<HTML + <div id="content"> +IF:diagram + <table cellpadding='0' cellspacing='0' border='0' width="100%"><tr><td align="center"> + %diagram% + </td></tr></table> +ENDIF:diagram + +IF:description + <div class="description">%description%</div> +ENDIF:description + +IF:requires + <div class="sectiontitle">Required Files</div> + <ul> +START:requires + <li>HREF:aref:name:</li> +END:requires + </ul> +ENDIF:requires + +IF:toc + <div class="sectiontitle">Contents</div> + <ul> +START:toc + <li><a href="#%href%">%secname%</a></li> +END:toc + </ul> +ENDIF:toc + +IF:methods + <div class="sectiontitle">Methods</div> + <ul> +START:methods + <li>HREF:aref:name:</li> +END:methods + </ul> +ENDIF:methods + +IF:includes +<div class="sectiontitle">Included Modules</div> +<ul> +START:includes + <li>HREF:aref:name:</li> +END:includes +</ul> +ENDIF:includes + +START:sections +IF:sectitle +<div class="sectiontitle"><a name="%secsequence%">%sectitle%</a></div> +IF:seccomment +<div class="description"> +%seccomment% +</div> +ENDIF:seccomment +ENDIF:sectitle + +IF:classlist + <div class="sectiontitle">Classes and Modules</div> + %classlist% +ENDIF:classlist + +IF:constants + <div class="sectiontitle">Constants</div> + <table border='0' cellpadding='5'> +START:constants + <tr valign='top'> + <td class="attr-name">%name%</td> + <td>=</td> + <td class="attr-value">%value%</td> + </tr> +IF:desc + <tr valign='top'> + <td> </td> + <td colspan="2" class="attr-desc">%desc%</td> + </tr> +ENDIF:desc +END:constants + </table> +ENDIF:constants + +IF:attributes + <div class="sectiontitle">Attributes</div> + <table border='0' cellpadding='5'> +START:attributes + <tr valign='top'> + <td class='attr-rw'> +IF:rw +[%rw%] +ENDIF:rw + </td> + <td class='attr-name'>%name%</td> + <td class='attr-desc'>%a_desc%</td> + </tr> +END:attributes + </table> +ENDIF:attributes + +IF:method_list +START:method_list +IF:methods +<div class="sectiontitle">%type% %category% methods</div> +START:methods +<div class="method"> + <div class="title"> +IF:callseq + <a name="%aref%"></a><b>%callseq%</b> +ENDIF:callseq +IFNOT:callseq + <a name="%aref%"></a><b>%name%</b>%params% +ENDIF:callseq +IF:codeurl +[ <a href="%codeurl%" target="SOURCE_CODE" onclick="javascript:openCode('%codeurl%'); return false;">source</a> ] +ENDIF:codeurl + </div> +IF:m_desc + <div class="description"> + %m_desc% + </div> +ENDIF:m_desc +IF:aka +<div class="aka"> + This method is also aliased as +START:aka + <a href="%aref%">%name%</a> +END:aka +</div> +ENDIF:aka +IF:sourcecode +<div class="sourcecode"> + <p class="source-link">[ <a href="javascript:toggleSource('%aref%_source')" id="l_%aref%_source">show source</a> ]</p> + <div id="%aref%_source" class="dyn-source"> +<pre> +%sourcecode% +</pre> + </div> +</div> +ENDIF:sourcecode +</div> +END:methods +ENDIF:methods +END:method_list +ENDIF:method_list +END:sections +</div> +HTML + +FOOTER = <<ENDFOOTER + </body> +</html> +ENDFOOTER + +BODY = HEADER + <<ENDBODY + !INCLUDE! <!-- banner header --> + + <div id="bodyContent"> + #{METHOD_LIST} + </div> + + #{FOOTER} +ENDBODY + +########################## Source code ########################## + +SRC_PAGE = XHTML_PREAMBLE + <<HTML +<html> +<head><title>%title%</title> +<meta http-equiv="Content-Type" content="text/html; charset=%charset%" /> +<style type="text/css"> +.ruby-comment { color: green; font-style: italic } +.ruby-constant { color: #4433aa; font-weight: bold; } +.ruby-identifier { color: #222222; } +.ruby-ivar { color: #2233dd; } +.ruby-keyword { color: #3333FF; font-weight: bold } +.ruby-node { color: #777777; } +.ruby-operator { color: #111111; } +.ruby-regexp { color: #662222; } +.ruby-value { color: #662222; font-style: italic } + .kw { color: #3333FF; font-weight: bold } + .cmt { color: green; font-style: italic } + .str { color: #662222; font-style: italic } + .re { color: #662222; } +</style> +</head> +<body bgcolor="white"> +<pre>%code%</pre> +</body> +</html> +HTML + +########################## Index ################################ + +FR_INDEX_BODY = <<HTML +!INCLUDE! +HTML + +FILE_INDEX = XHTML_PREAMBLE + <<HTML +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=%charset%" /> +<title>Index</title> +<style type="text/css"> +<!-- + body { + background-color: #EEE; + font-family: #{FONTS}; + color: #000; + margin: 0px; + } + .banner { + background: #005; + color: #FFF; + padding: 0.2em; + font-size: small; + font-weight: bold; + text-align: center; + } + .entries { + margin: 0.25em 1em 0 1em; + font-size: x-small; + } + a { + color: #00F; + text-decoration: none; + white-space: nowrap; + } + a:hover { + color: #77F; + text-decoration: underline; + } +--> +</style> +<base target="docwin" /> +</head> +<body> +<div class="banner">%list_title%</div> +<div class="entries"> +START:entries +<a href="%href%">%name%</a><br /> +END:entries +</div> +</body></html> +HTML + +CLASS_INDEX = FILE_INDEX +METHOD_INDEX = FILE_INDEX + +INDEX = XHTML_FRAMESET_PREAMBLE + <<HTML +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <title>%title%</title> + <meta http-equiv="Content-Type" content="text/html; charset=%charset%" /> +</head> + +<frameset cols="20%,*"> + <frameset rows="15%,55%,30%"> + <frame src="fr_file_index.html" title="Files" name="Files" /> + <frame src="fr_class_index.html" name="Classes" /> + <frame src="fr_method_index.html" name="Methods" /> + </frameset> + <frame src="%initial_page%" name="docwin" /> + <noframes> + <body bgcolor="white"> + Click <a href="html/index.html">here</a> for a non-frames + version of this page. + </body> + </noframes> +</frameset> + +</html> +HTML + +end +end + diff --git a/railties/CHANGELOG b/railties/CHANGELOG index fe517755ef..7e5c35da15 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,3 +1,20 @@ +*Edge* + +* Fix script/about in production mode. #370 [Cheah Chu Yeow, Xavier Noria, David Krmpotic] + +* Add the gem load paths before the framework is loaded, so certain gems like RedCloth and BlueCloth can be frozen. + +* Fix discrepancies with loading rails/init.rb from gems. + +* Plugins check for the gem init path (rails/init.rb) before the standard plugin init path (init.rb) [Jacek Becela] + +* Changed all generated tests to use the test/do declaration style [DHH] + +* Wrapped Rails.env in StringInquirer so you can do Rails.env.development? [DHH] + +* Fixed that RailsInfoController wasn't considering all requests local in development mode (Edgard Castro) [#310 state:resolved] + + *2.1.0 (May 31st, 2008)* * script/dbconsole fires up the command-line database client. #102 [Steve Purcell] diff --git a/railties/Rakefile b/railties/Rakefile index a1d1095c37..f64b60b0dd 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -264,9 +264,10 @@ Rake::RDocTask.new { |rdoc| rdoc.title = "Railties -- Gluing the Engine to the Rails" rdoc.options << '--line-numbers' << '--inline-source' << '--accessor' << 'cattr_accessor=object' rdoc.options << '--charset' << 'utf-8' - rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' rdoc.rdoc_files.include('README', 'CHANGELOG') rdoc.rdoc_files.include('lib/*.rb') + rdoc.rdoc_files.include('lib/rails/*.rb') rdoc.rdoc_files.include('lib/rails_generator/*.rb') rdoc.rdoc_files.include('lib/commands/**/*.rb') } @@ -331,10 +332,15 @@ end # Publishing ------------------------------------------------------- -desc "Publish the API documentation" +desc "Publish the rails gem" task :pgem => [:gem] do - Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload - `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'` + Rake::SshFilePublisher.new("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload + `ssh wrath.rubyonrails.org './gemupdate.sh'` +end + +desc "Publish the API documentation" +task :pdoc => :rdoc do + # railties API isn't separately published end desc "Publish the release files to RubyForge." diff --git a/railties/bin/about b/railties/bin/about index cd38a32abf..ed8deb0dfc 100644 --- a/railties/bin/about +++ b/railties/bin/about @@ -1,3 +1,4 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../config/boot' -require 'commands/about' +$LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info" +require 'commands/about'
\ No newline at end of file diff --git a/railties/builtin/rails_info/rails/info_controller.rb b/railties/builtin/rails_info/rails/info_controller.rb index 39f8b1f120..05745d606d 100644 --- a/railties/builtin/rails_info/rails/info_controller.rb +++ b/railties/builtin/rails_info/rails/info_controller.rb @@ -1,6 +1,6 @@ class Rails::InfoController < ActionController::Base def properties - if local_request? + if consider_all_requests_local || local_request? render :inline => Rails::Info.to_html else render :text => '<p>For security purposes, this information is only available to local requests.</p>', :status => 500 diff --git a/railties/configs/initializers/inflections.rb b/railties/configs/initializers/inflections.rb index 09158b865c..d531b8bb82 100644 --- a/railties/configs/initializers/inflections.rb +++ b/railties/configs/initializers/inflections.rb @@ -2,7 +2,7 @@ # Add new inflection rules using the following format # (all these examples are active by default): -# Inflector.inflections do |inflect| +# ActiveSupport::Inflector.inflections do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' diff --git a/railties/configs/initializers/new_rails_defaults.rb b/railties/configs/initializers/new_rails_defaults.rb index 5e60c0ade4..78e0117cc4 100644 --- a/railties/configs/initializers/new_rails_defaults.rb +++ b/railties/configs/initializers/new_rails_defaults.rb @@ -1,11 +1,13 @@ # These settings change the behavior of Rails 2 apps and will be defaults # for Rails 3. You can remove this initializer when Rails 3 is released. -# Include Active Record class name as root for JSON serialized output. -ActiveRecord::Base.include_root_in_json = true +if defined?(ActiveRecord) + # Include Active Record class name as root for JSON serialized output. + ActiveRecord::Base.include_root_in_json = true -# Store the full class name (including module namespace) in STI type column. -ActiveRecord::Base.store_full_sti_class = true + # Store the full class name (including module namespace) in STI type column. + ActiveRecord::Base.store_full_sti_class = true +end # Use ISO 8601 format for JSON serialized times and dates. ActiveSupport.use_standard_json_time_format = true diff --git a/railties/helpers/performance_test.rb b/railties/helpers/performance_test.rb new file mode 100644 index 0000000000..7c89816570 --- /dev/null +++ b/railties/helpers/performance_test.rb @@ -0,0 +1,8 @@ +require 'performance/test_helper' + +# Profiling results for each test method are written to tmp/performance. +class BrowsingTest < ActionController::PerformanceTest + def test_homepage + get '/' + end +end diff --git a/railties/helpers/performance_test_helper.rb b/railties/helpers/performance_test_helper.rb new file mode 100644 index 0000000000..3c4c7fb740 --- /dev/null +++ b/railties/helpers/performance_test_helper.rb @@ -0,0 +1,6 @@ +require 'test_helper' +require 'action_controller/performance_test' + +ActionController::Base.perform_caching = true +ActiveSupport::Dependencies.mechanism = :require +Rails.logger.level = ActiveSupport::BufferedLogger::INFO diff --git a/railties/lib/commands/about.rb b/railties/lib/commands/about.rb index 313bc18c6a..7f53ac8a2e 100644 --- a/railties/lib/commands/about.rb +++ b/railties/lib/commands/about.rb @@ -1,2 +1,3 @@ require 'environment' +require 'rails/info' puts Rails::Info diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index ec065e6f3c..bcf4cea10f 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -37,7 +37,7 @@ module Rails end def env - RAILS_ENV + ActiveSupport::StringInquirer.new(RAILS_ENV) end def cache @@ -79,7 +79,10 @@ module Rails # The set of loaded plugins. attr_reader :loaded_plugins - + + # Whether or not all the gem dependencies have been met + attr_reader :gems_dependencies_loaded + # Runs the initializer. By default, this will invoke the #process method, # which simply executes all of the initialization routines. Alternately, # you can specify explicitly which initialization routine you want: @@ -110,10 +113,10 @@ module Rails check_ruby_version install_gem_spec_stubs set_load_path - + add_gem_load_paths + require_frameworks set_autoload_paths - add_gem_load_paths add_plugin_load_paths load_environment @@ -201,10 +204,10 @@ module Rails # Set the paths from which Rails will automatically load source files, and # the load_once paths. def set_autoload_paths - Dependencies.load_paths = configuration.load_paths.uniq - Dependencies.load_once_paths = configuration.load_once_paths.uniq + ActiveSupport::Dependencies.load_paths = configuration.load_paths.uniq + ActiveSupport::Dependencies.load_once_paths = configuration.load_once_paths.uniq - extra = Dependencies.load_once_paths - Dependencies.load_paths + extra = ActiveSupport::Dependencies.load_once_paths - ActiveSupport::Dependencies.load_paths unless extra.empty? abort <<-end_error load_once_paths must be a subset of the load_paths. @@ -231,7 +234,7 @@ module Rails end # Adds all load paths from plugins to the global set of load paths, so that - # code from plugins can be required (explicitly or automatically via Dependencies). + # code from plugins can be required (explicitly or automatically via ActiveSupport::Dependencies). def add_plugin_load_paths plugin_loader.add_plugin_load_paths end @@ -239,12 +242,12 @@ module Rails def add_gem_load_paths unless @configuration.gems.empty? require "rubygems" - @configuration.gems.each &:add_load_paths + @configuration.gems.each { |gem| gem.add_load_paths } end end def load_gems - @configuration.gems.each(&:load) + @configuration.gems.each { |gem| gem.load } end def check_gem_dependencies @@ -307,7 +310,7 @@ module Rails end def load_observers - if @gems_dependencies_loaded && configuration.frameworks.include?(:active_record) + if gems_dependencies_loaded && configuration.frameworks.include?(:active_record) ActiveRecord::Base.instantiate_observers end end @@ -413,7 +416,7 @@ module Rails # Sets the dependency loading mechanism based on the value of # Configuration#cache_classes. def initialize_dependency_mechanism - Dependencies.mechanism = configuration.cache_classes ? :require : :load + ActiveSupport::Dependencies.mechanism = configuration.cache_classes ? :require : :load end # Loads support for "whiny nil" (noisy warnings when methods are invoked @@ -463,7 +466,7 @@ module Rails # Fires the user-supplied after_initialize block (Configuration#after_initialize) def after_initialize - if @gems_dependencies_loaded + if gems_dependencies_loaded configuration.after_initialize_blocks.each do |block| block.call end @@ -471,7 +474,7 @@ module Rails end def load_application_initializers - if @gems_dependencies_loaded + if gems_dependencies_loaded Dir["#{configuration.root_path}/config/initializers/**/*.rb"].sort.each do |initializer| load(initializer) end @@ -599,12 +602,12 @@ module Rails # If <tt>reload_plugins?</tt> is false, add this to your plugin's <tt>init.rb</tt> # to make it reloadable: # - # Dependencies.load_once_paths.delete lib_path + # ActiveSupport::Dependencies.load_once_paths.delete lib_path # # If <tt>reload_plugins?</tt> is true, add this to your plugin's <tt>init.rb</tt> # to only load it once: # - # Dependencies.load_once_paths << lib_path + # ActiveSupport::Dependencies.load_once_paths << lib_path # attr_accessor :reload_plugins diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb index 9f088a18dd..f8d97840c1 100644 --- a/railties/lib/rails/gem_dependency.rb +++ b/railties/lib/rails/gem_dependency.rb @@ -23,9 +23,13 @@ module Rails @unpack_directory = nil end + def unpacked_paths + Dir[File.join(self.class.unpacked_path, "#{@name}-#{@version || "*"}")] + end + def add_load_paths return if @loaded || @load_paths_added - unpacked_paths = Dir[File.join(self.class.unpacked_path, "#{@name}-#{@version || "*"}")] + unpacked_paths = self.unpacked_paths if unpacked_paths.empty? args = [@name] args << @requirement.to_s if @requirement @@ -39,7 +43,7 @@ module Rails @load_paths_added = true rescue Gem::LoadError end - + def dependencies all_dependencies = specification.dependencies.map do |dependency| GemDependency.new(dependency.name, :requirement => dependency.version_requirements) @@ -47,7 +51,7 @@ module Rails all_dependencies += all_dependencies.map(&:dependencies).flatten all_dependencies.uniq end - + def gem_dir(base_directory) File.join(base_directory, specification.full_name) end @@ -78,13 +82,13 @@ module Rails puts cmd puts %x(#{cmd}) end - + def unpack_to(directory) FileUtils.mkdir_p directory Dir.chdir directory do Gem::GemRunner.new.run(unpack_command) end - + # copy the gem's specification into GEMDIR/.specification so that # we can access information about the gem on deployment systems # without having the gem installed @@ -98,27 +102,26 @@ module Rails self.name == other.name && self.requirement == other.requirement end -private ################################################################### - def specification @spec ||= Gem.source_index.search(Gem::Dependency.new(@name, @requirement)).sort_by { |s| s.version }.last end - - def gem_command - RUBY_PLATFORM =~ /win32/ ? 'gem.bat' : 'gem' - end - def install_command - cmd = %w(install) << @name - cmd << "--version" << %("#{@requirement.to_s}") if @requirement - cmd << "--source" << @source if @source - cmd - end - - def unpack_command - cmd = %w(unpack) << @name - cmd << "--version" << "#{@requirement.to_s}" if @requirement - cmd - end + private + def gem_command + RUBY_PLATFORM =~ /win32/ ? 'gem.bat' : 'gem' + end + + def install_command + cmd = %w(install) << @name + cmd << "--version" << %("#{@requirement.to_s}") if @requirement + cmd << "--source" << @source if @source + cmd + end + + def unpack_command + cmd = %w(unpack) << @name + cmd << "--version" << %("#{@requirement.to_s}") if @requirement + cmd + end end -end
\ No newline at end of file +end diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index 256f4b0132..b8b2b57038 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -74,10 +74,18 @@ module Rails File.join(directory, 'lib') end - def init_path + def classic_init_path File.join(directory, 'init.rb') end + def gem_init_path + File.join(directory, 'rails', 'init.rb') + end + + def init_path + File.file?(gem_init_path) ? gem_init_path : classic_init_path + end + def has_lib_directory? File.directory?(lib_path) end @@ -87,14 +95,14 @@ module Rails end def evaluate_init_rb(initializer) - if has_init_file? - silence_warnings do - # Allow plugins to reference the current configuration object - config = initializer.configuration - - eval(IO.read(init_path), binding, init_path) - end - end + if has_init_file? + silence_warnings do + # Allow plugins to reference the current configuration object + config = initializer.configuration + + eval(IO.read(init_path), binding, init_path) + end + end end end @@ -103,8 +111,9 @@ module Rails # to Dependencies.load_paths. class GemPlugin < Plugin # Initialize this plugin from a Gem::Specification. - def initialize(spec) - super(File.join(spec.full_gem_path)) + def initialize(spec, gem) + directory = (gem.frozen? && gem.unpacked_paths.first) || File.join(spec.full_gem_path) + super(directory) @name = spec.name end diff --git a/railties/lib/rails/plugin/loader.rb b/railties/lib/rails/plugin/loader.rb index 1e542dd038..948d497007 100644 --- a/railties/lib/rails/plugin/loader.rb +++ b/railties/lib/rails/plugin/loader.rb @@ -45,9 +45,9 @@ module Rails plugins.each do |plugin| plugin.load_paths.each do |path| $LOAD_PATH.insert(application_lib_index + 1, path) - Dependencies.load_paths << path + ActiveSupport::Dependencies.load_paths << path unless Rails.configuration.reload_plugins? - Dependencies.load_once_paths << path + ActiveSupport::Dependencies.load_once_paths << path end end end diff --git a/railties/lib/rails/plugin/locator.rb b/railties/lib/rails/plugin/locator.rb index f06a51a572..79c07fccd1 100644 --- a/railties/lib/rails/plugin/locator.rb +++ b/railties/lib/rails/plugin/locator.rb @@ -78,8 +78,9 @@ module Rails # a <tt>rails/init.rb</tt> file. class GemLocator < Locator def plugins - specs = initializer.configuration.gems.map(&:specification) - specs += Gem.loaded_specs.values.select do |spec| + gem_index = initializer.configuration.gems.inject({}) { |memo, gem| memo.update gem.specification => gem } + specs = gem_index.keys + specs += Gem.loaded_specs.values.select do |spec| spec.loaded_from && # prune stubs File.exist?(File.join(spec.full_gem_path, "rails", "init.rb")) end @@ -91,7 +92,7 @@ module Rails deps.add(*specs) unless specs.empty? deps.dependency_order.collect do |spec| - Rails::GemPlugin.new(spec) + Rails::GemPlugin.new(spec, gem_index[spec]) end end end diff --git a/railties/lib/rails_generator/generators/applications/app/app_generator.rb b/railties/lib/rails_generator/generators/applications/app/app_generator.rb index 2f2dd82682..80e8eabfd3 100644 --- a/railties/lib/rails_generator/generators/applications/app/app_generator.rb +++ b/railties/lib/rails_generator/generators/applications/app/app_generator.rb @@ -51,6 +51,8 @@ class AppGenerator < Rails::Generator::Base m.template "helpers/application.rb", "app/controllers/application.rb", :assigns => { :app_name => @app_name, :app_secret => md5.hexdigest } m.template "helpers/application_helper.rb", "app/helpers/application_helper.rb" m.template "helpers/test_helper.rb", "test/test_helper.rb" + m.template "helpers/performance_test_helper.rb", "test/performance/test_helper.rb" + m.template "helpers/performance_test.rb", "test/performance/browsing_test.rb" # database.yml and routes.rb m.template "configs/databases/#{options[:db]}.yml", "config/database.yml", :assigns => { @@ -155,6 +157,7 @@ class AppGenerator < Rails::Generator::Base test/fixtures test/functional test/integration + test/performance test/unit vendor vendor/plugins diff --git a/railties/lib/rails_generator/generators/components/controller/templates/functional_test.rb b/railties/lib/rails_generator/generators/components/controller/templates/functional_test.rb index 38e0ae7123..62fa5d86fd 100644 --- a/railties/lib/rails_generator/generators/components/controller/templates/functional_test.rb +++ b/railties/lib/rails_generator/generators/components/controller/templates/functional_test.rb @@ -2,7 +2,7 @@ require 'test_helper' class <%= class_name %>ControllerTest < ActionController::TestCase # Replace this with your real tests. - def test_truth + test "the truth" do assert true end end diff --git a/railties/lib/rails_generator/generators/components/integration_test/templates/integration_test.rb b/railties/lib/rails_generator/generators/components/integration_test/templates/integration_test.rb index 149b987d81..2c57158b1c 100644 --- a/railties/lib/rails_generator/generators/components/integration_test/templates/integration_test.rb +++ b/railties/lib/rails_generator/generators/components/integration_test/templates/integration_test.rb @@ -1,10 +1,10 @@ require 'test_helper' class <%= class_name %>Test < ActionController::IntegrationTest - # fixtures :your, :models + fixtures :all # Replace this with your real tests. - def test_truth + test "the truth" do assert true end end diff --git a/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb b/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb index 0b4b2ec60a..1b7bcfef08 100644 --- a/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb +++ b/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb @@ -3,7 +3,7 @@ require 'test_helper' class <%= class_name %>Test < ActionMailer::TestCase tests <%= class_name %> <% for action in actions -%> - def test_<%= action %> + test "<%= action %>" do @expected.subject = '<%= class_name %>#<%= action %>' @expected.body = read_fixture('<%= action %>') @expected.date = Time.now @@ -14,7 +14,7 @@ class <%= class_name %>Test < ActionMailer::TestCase <% end -%> <% if actions.blank? -%> # replace this with your real tests - def test_truth + test "the truth" do assert true end <% end -%> diff --git a/railties/lib/rails_generator/generators/components/model/templates/unit_test.rb b/railties/lib/rails_generator/generators/components/model/templates/unit_test.rb index 96bd34adab..3e0bc29d3a 100644 --- a/railties/lib/rails_generator/generators/components/model/templates/unit_test.rb +++ b/railties/lib/rails_generator/generators/components/model/templates/unit_test.rb @@ -2,7 +2,7 @@ require 'test_helper' class <%= class_name %>Test < ActiveSupport::TestCase # Replace this with your real tests. - def test_truth + test "the truth" do assert true end end diff --git a/railties/lib/rails_generator/generators/components/observer/templates/unit_test.rb b/railties/lib/rails_generator/generators/components/observer/templates/unit_test.rb index 1faf8ed9ac..cae38e9a2a 100644 --- a/railties/lib/rails_generator/generators/components/observer/templates/unit_test.rb +++ b/railties/lib/rails_generator/generators/components/observer/templates/unit_test.rb @@ -2,7 +2,7 @@ require 'test_helper' class <%= class_name %>ObserverTest < Test::Unit::TestCase # Replace this with your real tests. - def test_truth + test "the truth" do assert true end end diff --git a/railties/lib/rails_generator/generators/components/performance_test/USAGE b/railties/lib/rails_generator/generators/components/performance_test/USAGE new file mode 100644 index 0000000000..d84051eb02 --- /dev/null +++ b/railties/lib/rails_generator/generators/components/performance_test/USAGE @@ -0,0 +1,8 @@ +Description: + Stubs out a new performance test. Pass the name of the test, either + CamelCased or under_scored, as an argument. The new test class is + generated in test/performance/testname_test.rb + +Example: + `./script/generate performance_test GeneralStories` creates a GeneralStories + performance test in test/performance/general_stories_test.rb diff --git a/railties/lib/rails_generator/generators/components/performance_test/performance_test_generator.rb b/railties/lib/rails_generator/generators/components/performance_test/performance_test_generator.rb new file mode 100644 index 0000000000..fbcc1cf683 --- /dev/null +++ b/railties/lib/rails_generator/generators/components/performance_test/performance_test_generator.rb @@ -0,0 +1,16 @@ +class PerformanceTestGenerator < Rails::Generator::NamedBase + default_options :skip_migration => false + + def manifest + record do |m| + # Check for class naming collisions. + m.class_collisions class_path, class_name, "#{class_name}Test" + + # performance test directory + m.directory File.join('test/performance', class_path) + + # performance test stub + m.template 'performance_test.rb', File.join('test/performance', class_path, "#{file_name}_test.rb") + end + end +end diff --git a/railties/lib/rails_generator/generators/components/performance_test/templates/performance_test.rb b/railties/lib/rails_generator/generators/components/performance_test/templates/performance_test.rb new file mode 100644 index 0000000000..352ff48054 --- /dev/null +++ b/railties/lib/rails_generator/generators/components/performance_test/templates/performance_test.rb @@ -0,0 +1,8 @@ +require 'performance/test_helper' + +class <%= class_name %>Test < ActionController::PerformanceTest + # Replace this with your real tests. + def test_homepage + get '/' + end +end diff --git a/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb b/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb index 9028b84b7d..6ede6ef1d2 100644 --- a/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb +++ b/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb @@ -2,7 +2,7 @@ require 'test/unit' class <%= class_name %>Test < Test::Unit::TestCase # Replace this with your real tests. - def test_this_plugin - flunk + test "the truth" do + assert true end end diff --git a/railties/lib/rails_generator/generators/components/resource/templates/functional_test.rb b/railties/lib/rails_generator/generators/components/resource/templates/functional_test.rb index fbb69fcca7..b1bb1dacbf 100644 --- a/railties/lib/rails_generator/generators/components/resource/templates/functional_test.rb +++ b/railties/lib/rails_generator/generators/components/resource/templates/functional_test.rb @@ -2,7 +2,7 @@ require 'test_helper' class <%= controller_class_name %>ControllerTest < ActionController::TestCase # Replace this with your real tests. - def test_truth + test "the truth" do assert true end end diff --git a/railties/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb b/railties/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb index 3b430a2061..2d9d635944 100644 --- a/railties/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb +++ b/railties/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb @@ -1,18 +1,18 @@ require 'test_helper' class <%= controller_class_name %>ControllerTest < ActionController::TestCase - def test_should_get_index + test "should get index" do get :index assert_response :success assert_not_nil assigns(:<%= table_name %>) end - def test_should_get_new + test "should get new" do get :new assert_response :success end - def test_should_create_<%= file_name %> + test "should create <%= file_name %>" do assert_difference('<%= class_name %>.count') do post :create, :<%= file_name %> => { } end @@ -20,22 +20,22 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>)) end - def test_should_show_<%= file_name %> + test "should show <%= file_name %>" do get :show, :id => <%= table_name %>(:one).id assert_response :success end - def test_should_get_edit + test "should get edit" do get :edit, :id => <%= table_name %>(:one).id assert_response :success end - def test_should_update_<%= file_name %> + test "should update <%= file_name %>" do put :update, :id => <%= table_name %>(:one).id, :<%= file_name %> => { } assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>)) end - def test_should_destroy_<%= file_name %> + test "should destroy <%= file_name %>" do assert_difference('<%= class_name %>.count', -1) do delete :destroy, :id => <%= table_name %>(:one).id end diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake index 8077d0a401..75fba8b45a 100644 --- a/railties/lib/tasks/databases.rake +++ b/railties/lib/tasks/databases.rake @@ -264,13 +264,15 @@ namespace :db do end namespace :test do - desc "Recreate the test database from the current environment's database schema" - task :clone => %w(db:schema:dump db:test:purge) do + desc "Recreate the test database from the current schema.rb" + task :load => 'db:test:purge' do ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) ActiveRecord::Schema.verbose = false Rake::Task["db:schema:load"].invoke end + desc "Recreate the test database from the current environment's database schema" + task :clone => %w(db:schema:dump db:test:load) desc "Recreate the test databases from the development structure" task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do @@ -337,10 +339,10 @@ namespace :db do end end - desc 'Prepare the test database and load the schema' - task :prepare => %w(environment db:abort_if_pending_migrations) do + desc 'Check for pending migrations and load the test schema' + task :prepare => 'db:abort_if_pending_migrations' do if defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank? - Rake::Task[{ :sql => "db:test:clone_structure", :ruby => "db:test:clone" }[ActiveRecord::Base.schema_format]].invoke + Rake::Task[{ :sql => "db:test:clone_structure", :ruby => "db:test:load" }[ActiveRecord::Base.schema_format]].invoke end end end diff --git a/railties/lib/tasks/rails.rb b/railties/lib/tasks/rails.rb index bfcf5bc493..8c2b7f9bde 100644 --- a/railties/lib/tasks/rails.rb +++ b/railties/lib/tasks/rails.rb @@ -4,5 +4,5 @@ $VERBOSE = nil Dir["#{File.dirname(__FILE__)}/*.rake"].each { |ext| load ext } # Load any custom rakefile extensions -Dir["#{RAILS_ROOT}/lib/tasks/**/*.rake"].sort.each { |ext| load ext } Dir["#{RAILS_ROOT}/vendor/plugins/*/**/tasks/**/*.rake"].sort.each { |ext| load ext } +Dir["#{RAILS_ROOT}/lib/tasks/**/*.rake"].sort.each { |ext| load ext } diff --git a/railties/lib/tasks/testing.rake b/railties/lib/tasks/testing.rake index cc2376cbb3..c8ba6eed94 100644 --- a/railties/lib/tasks/testing.rake +++ b/railties/lib/tasks/testing.rake @@ -103,6 +103,21 @@ namespace :test do end Rake::Task['test:integration'].comment = "Run the integration tests in test/integration" + Rake::TestTask.new(:benchmark => 'db:test:prepare') do |t| + t.libs << 'test' + t.pattern = 'test/performance/**/*_test.rb' + t.verbose = true + t.options = '-- --benchmark' + end + Rake::Task['test:benchmark'].comment = 'Benchmark the performance tests' + + Rake::TestTask.new(:profile => 'db:test:prepare') do |t| + t.libs << 'test' + t.pattern = 'test/performance/**/*_test.rb' + t.verbose = true + end + Rake::Task['test:profile'].comment = 'Profile the performance tests' + Rake::TestTask.new(:plugins => :environment) do |t| t.libs << "test" diff --git a/railties/lib/test_help.rb b/railties/lib/test_help.rb index 22ce9ab609..3cc61d7932 100644 --- a/railties/lib/test_help.rb +++ b/railties/lib/test_help.rb @@ -8,7 +8,6 @@ require 'test/unit' require 'active_support/test_case' require 'active_record/fixtures' require 'action_controller/test_case' -require 'action_controller/test_process' require 'action_controller/integration' require 'action_mailer/test_case' if defined?(ActionMailer) @@ -25,4 +24,4 @@ begin Debugger.settings[:autoeval] = true if Debugger.respond_to?(:settings) rescue LoadError # ruby-debug wasn't available so neither can the debugging be -end
\ No newline at end of file +end diff --git a/railties/lib/webrick_server.rb b/railties/lib/webrick_server.rb index ad4ca926ba..2f60151b22 100644 --- a/railties/lib/webrick_server.rb +++ b/railties/lib/webrick_server.rb @@ -43,8 +43,6 @@ end # can change this behavior by setting ActionController::Base.allow_concurrency # to true. class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet - REQUEST_MUTEX = Mutex.new - # Start the WEBrick server with the given options, mounting the # DispatchServlet at <tt>/</tt>. def self.dispatch(options = {}) @@ -73,15 +71,8 @@ class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet def service(req, res) #:nodoc: unless handle_file(req, res) - begin - REQUEST_MUTEX.lock unless ActionController::Base.allow_concurrency - unless handle_dispatch(req, res) - raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." - end - ensure - unless ActionController::Base.allow_concurrency - REQUEST_MUTEX.unlock if REQUEST_MUTEX.locked? - end + unless handle_dispatch(req, res) + raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." end end end diff --git a/railties/test/fixtures/plugins/default/gemlike/init.rb b/railties/test/fixtures/plugins/default/gemlike/init.rb new file mode 100644 index 0000000000..6a771b5b68 --- /dev/null +++ b/railties/test/fixtures/plugins/default/gemlike/init.rb @@ -0,0 +1 @@ +raise 'This init.rb should not be evaluated because rails/init.rb exists' diff --git a/railties/test/fixtures/plugins/default/gemlike/lib/gemlike.rb b/railties/test/fixtures/plugins/default/gemlike/lib/gemlike.rb new file mode 100644 index 0000000000..2088103e45 --- /dev/null +++ b/railties/test/fixtures/plugins/default/gemlike/lib/gemlike.rb @@ -0,0 +1,2 @@ +module Gemlike +end
\ No newline at end of file diff --git a/railties/test/fixtures/plugins/default/gemlike/rails/init.rb b/railties/test/fixtures/plugins/default/gemlike/rails/init.rb new file mode 100644 index 0000000000..171a293eb3 --- /dev/null +++ b/railties/test/fixtures/plugins/default/gemlike/rails/init.rb @@ -0,0 +1,7 @@ +# I have access to my directory and the Rails config. +raise 'directory expected but undefined in init.rb' unless defined? directory +raise 'config expected but undefined in init.rb' unless defined? config + +# My lib/ dir must be in the load path. +require 'gemlike' +raise 'missing mixin from my lib/ dir' unless defined? Gemlike diff --git a/railties/test/gem_dependency_test.rb b/railties/test/gem_dependency_test.rb index 3ae0189327..b5946aa7b8 100644 --- a/railties/test/gem_dependency_test.rb +++ b/railties/test/gem_dependency_test.rb @@ -10,13 +10,13 @@ uses_mocha "Plugin Tests" do @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_with_lib = Rails::GemDependency.new "aws-s3", :lib => "aws/s3" 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) + assert_equal [["install", "aws-s3", "--version", '"= 0.4.0"']], config.gems.collect(&:install_command) end def test_gem_creates_install_command @@ -28,7 +28,7 @@ uses_mocha "Plugin Tests" do end def test_gem_with_version_creates_install_command - assert_equal ["install", "hpricot", "--version", "= 0.6"], @gem_with_version.install_command + assert_equal ["install", "hpricot", "--version", '"= 0.6"'], @gem_with_version.install_command end def test_gem_creates_unpack_command @@ -36,26 +36,26 @@ uses_mocha "Plugin Tests" do end def test_gem_with_version_unpack_install_command - assert_equal ["unpack", "hpricot", "--version", "= 0.6"], @gem_with_version.unpack_command + assert_equal ["unpack", "hpricot", "--version", '"= 0.6"'], @gem_with_version.unpack_command end def test_gem_adds_load_paths @gem.expects(:gem).with(@gem.name) @gem.add_load_paths end - + def test_gem_with_version_adds_load_paths @gem_with_version.expects(:gem).with(@gem_with_version.name, @gem_with_version.requirement.to_s) @gem_with_version.add_load_paths end - + def test_gem_loading @gem.expects(:gem).with(@gem.name) @gem.expects(:require).with(@gem.name) @gem.add_load_paths @gem.load end - + def test_gem_with_lib_loading @gem_with_lib.expects(:gem).with(@gem_with_lib.name) @gem_with_lib.expects(:require).with(@gem_with_lib.lib) @@ -63,4 +63,4 @@ uses_mocha "Plugin Tests" do @gem_with_lib.load end end -end
\ No newline at end of file +end diff --git a/railties/test/initializer_test.rb b/railties/test/initializer_test.rb index 0df0164ca6..dee7abe05f 100644 --- a/railties/test/initializer_test.rb +++ b/railties/test/initializer_test.rb @@ -30,64 +30,66 @@ class Initializer_load_environment_Test < Test::Unit::TestCase end -class Initializer_after_initialize_with_blocks_environment_Test < Test::Unit::TestCase - def setup - config = ConfigurationMock.new("") - config.after_initialize do - $test_after_initialize_block1 = "success" - end - config.after_initialize do - $test_after_initialize_block2 = "congratulations" - end - assert_nil $test_after_initialize_block1 - assert_nil $test_after_initialize_block2 - - Rails::Initializer.run(:after_initialize, config) - end - - def teardown - $test_after_initialize_block1 = nil - $test_after_initialize_block2 = nil - end +uses_mocha 'Initializer after_initialize' do + class Initializer_after_initialize_with_blocks_environment_Test < Test::Unit::TestCase + def setup + config = ConfigurationMock.new("") + config.after_initialize do + $test_after_initialize_block1 = "success" + end + config.after_initialize do + $test_after_initialize_block2 = "congratulations" + end + assert_nil $test_after_initialize_block1 + assert_nil $test_after_initialize_block2 - def test_should_have_called_the_first_after_initialize_block - assert_equal "success", $test_after_initialize_block1 - end - - def test_should_have_called_the_second_after_initialize_block - assert_equal "congratulations", $test_after_initialize_block2 - end -end - -class Initializer_after_initialize_with_no_block_environment_Test < Test::Unit::TestCase + Rails::Initializer.any_instance.expects(:gems_dependencies_loaded).returns(true) + Rails::Initializer.run(:after_initialize, config) + end - def setup - config = ConfigurationMock.new("") - config.after_initialize do - $test_after_initialize_block1 = "success" + def teardown + $test_after_initialize_block1 = nil + $test_after_initialize_block2 = nil end - config.after_initialize # don't pass a block, this is what we're testing! - config.after_initialize do - $test_after_initialize_block2 = "congratulations" - end - assert_nil $test_after_initialize_block1 - Rails::Initializer.run(:after_initialize, config) - end + def test_should_have_called_the_first_after_initialize_block + assert_equal "success", $test_after_initialize_block1 + end - def teardown - $test_after_initialize_block1 = nil - $test_after_initialize_block2 = nil + def test_should_have_called_the_second_after_initialize_block + assert_equal "congratulations", $test_after_initialize_block2 + end end - def test_should_have_called_the_first_after_initialize_block - assert_equal "success", $test_after_initialize_block1, "should still get set" - end + class Initializer_after_initialize_with_no_block_environment_Test < Test::Unit::TestCase + def setup + config = ConfigurationMock.new("") + config.after_initialize do + $test_after_initialize_block1 = "success" + end + config.after_initialize # don't pass a block, this is what we're testing! + config.after_initialize do + $test_after_initialize_block2 = "congratulations" + end + assert_nil $test_after_initialize_block1 - def test_should_have_called_the_second_after_initialize_block - assert_equal "congratulations", $test_after_initialize_block2 - end + Rails::Initializer.any_instance.expects(:gems_dependencies_loaded).returns(true) + Rails::Initializer.run(:after_initialize, config) + end + + def teardown + $test_after_initialize_block1 = nil + $test_after_initialize_block2 = nil + end + def test_should_have_called_the_first_after_initialize_block + assert_equal "success", $test_after_initialize_block1, "should still get set" + end + + def test_should_have_called_the_second_after_initialize_block + assert_equal "congratulations", $test_after_initialize_block2 + end + end end uses_mocha 'framework paths' do @@ -95,7 +97,7 @@ uses_mocha 'framework paths' do def setup @config = Rails::Configuration.new @config.frameworks.clear - + File.stubs(:directory?).returns(true) @config.stubs(:framework_root_path).returns('') end @@ -112,7 +114,7 @@ uses_mocha 'framework paths' do def test_actioncontroller_or_actionview_add_actionpack @config.frameworks << :action_controller assert_framework_path '/actionpack/lib' - + @config.frameworks = [:action_view] assert_framework_path '/actionpack/lib' end @@ -171,7 +173,7 @@ uses_mocha "Initializer plugin loading tests" do def test_all_plugins_are_loaded_when_registered_plugin_list_is_untouched failure_tip = "It's likely someone has added a new plugin fixture without updating this list" load_plugins! - assert_plugins [:a, :acts_as_chunky_bacon, :plugin_with_no_lib_dir, :stubby], @initializer.loaded_plugins, failure_tip + assert_plugins [:a, :acts_as_chunky_bacon, :gemlike, :plugin_with_no_lib_dir, :stubby], @initializer.loaded_plugins, failure_tip end def test_all_plugins_loaded_when_all_is_used @@ -179,7 +181,7 @@ uses_mocha "Initializer plugin loading tests" do only_load_the_following_plugins! plugin_names load_plugins! failure_tip = "It's likely someone has added a new plugin fixture without updating this list" - assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :plugin_with_no_lib_dir], @initializer.loaded_plugins, failure_tip + assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :gemlike, :plugin_with_no_lib_dir], @initializer.loaded_plugins, failure_tip end def test_all_plugins_loaded_after_all @@ -187,7 +189,7 @@ uses_mocha "Initializer plugin loading tests" do only_load_the_following_plugins! plugin_names load_plugins! failure_tip = "It's likely someone has added a new plugin fixture without updating this list" - assert_plugins [:stubby, :a, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], @initializer.loaded_plugins, failure_tip + assert_plugins [:stubby, :a, :gemlike, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], @initializer.loaded_plugins, failure_tip end def test_plugin_names_may_be_strings @@ -204,22 +206,22 @@ uses_mocha "Initializer plugin loading tests" do load_plugins! end end - + def test_should_ensure_all_loaded_plugins_load_paths_are_added_to_the_load_path only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] @initializer.add_plugin_load_paths - + assert $LOAD_PATH.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) assert $LOAD_PATH.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) end - + private - + def load_plugins! @initializer.add_plugin_load_paths @initializer.load_plugins end end - -end + +end diff --git a/railties/test/plugin_loader_test.rb b/railties/test/plugin_loader_test.rb index 41bd6ec7ea..f429bae15c 100644 --- a/railties/test/plugin_loader_test.rb +++ b/railties/test/plugin_loader_test.rb @@ -48,16 +48,16 @@ uses_mocha "Plugin Loader Tests" do end def test_should_find_all_availble_plugins_and_return_as_all_plugins - assert_plugins [:stubby, :plugin_with_no_lib_dir, :acts_as_chunky_bacon, :a], @loader.all_plugins.reverse, @failure_tip + assert_plugins [:stubby, :plugin_with_no_lib_dir, :gemlike, :acts_as_chunky_bacon, :a], @loader.all_plugins.reverse, @failure_tip end def test_should_return_all_plugins_as_plugins_when_registered_plugin_list_is_untouched - assert_plugins [:a, :acts_as_chunky_bacon, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip + assert_plugins [:a, :acts_as_chunky_bacon, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip end def test_should_return_all_plugins_as_plugins_when_registered_plugin_list_is_nil @configuration.plugins = nil - assert_plugins [:a, :acts_as_chunky_bacon, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip + assert_plugins [:a, :acts_as_chunky_bacon, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip end def test_should_return_specific_plugins_named_in_config_plugins_array_if_set @@ -74,17 +74,17 @@ uses_mocha "Plugin Loader Tests" do def test_should_load_all_plugins_in_natural_order_when_all_is_used only_load_the_following_plugins! [:all] - assert_plugins [:a, :acts_as_chunky_bacon, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip + assert_plugins [:a, :acts_as_chunky_bacon, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip end def test_should_load_specified_plugins_in_order_and_then_all_remaining_plugins_when_all_is_used only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon, :all] - assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :plugin_with_no_lib_dir], @loader.plugins, @failure_tip + assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :gemlike, :plugin_with_no_lib_dir], @loader.plugins, @failure_tip end def test_should_be_able_to_specify_loading_of_plugins_loaded_after_all only_load_the_following_plugins! [:stubby, :all, :acts_as_chunky_bacon] - assert_plugins [:stubby, :a, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], @loader.plugins, @failure_tip + assert_plugins [:stubby, :a, :gemlike, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], @loader.plugins, @failure_tip end def test_should_accept_plugin_names_given_as_strings @@ -94,7 +94,7 @@ uses_mocha "Plugin Loader Tests" do def test_should_add_plugin_load_paths_to_global_LOAD_PATH_array only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] - stubbed_application_lib_index_in_LOAD_PATHS = 5 + stubbed_application_lib_index_in_LOAD_PATHS = 4 @loader.stubs(:application_lib_index).returns(stubbed_application_lib_index_in_LOAD_PATHS) @loader.add_plugin_load_paths @@ -108,8 +108,8 @@ uses_mocha "Plugin Loader Tests" do @loader.add_plugin_load_paths - assert Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) - assert Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) + assert ActiveSupport::Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) + assert ActiveSupport::Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) end def test_should_add_plugin_load_paths_to_Dependencies_load_once_paths @@ -117,8 +117,8 @@ uses_mocha "Plugin Loader Tests" do @loader.add_plugin_load_paths - assert Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) - assert Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) + assert ActiveSupport::Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) + assert ActiveSupport::Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) end def test_should_add_all_load_paths_from_a_plugin_to_LOAD_PATH_array diff --git a/railties/test/plugin_locator_test.rb b/railties/test/plugin_locator_test.rb index 5f1dd991ea..363fa27f15 100644 --- a/railties/test/plugin_locator_test.rb +++ b/railties/test/plugin_locator_test.rb @@ -47,12 +47,12 @@ uses_mocha "Plugin Locator Tests" do end def test_should_return_all_plugins_found_under_the_set_plugin_paths - assert_equal ["a", "acts_as_chunky_bacon", "plugin_with_no_lib_dir", "stubby"].sort, @locator.plugins.map(&:name).sort + assert_equal ["a", "acts_as_chunky_bacon", "gemlike", "plugin_with_no_lib_dir", "stubby"].sort, @locator.plugins.map(&:name).sort end def test_should_find_plugins_only_under_the_plugin_paths_set_in_configuration @configuration.plugin_paths = [File.join(plugin_fixture_root_path, "default")] - assert_equal ["acts_as_chunky_bacon", "plugin_with_no_lib_dir", "stubby"].sort, @locator.plugins.map(&:name).sort + assert_equal ["acts_as_chunky_bacon", "gemlike", "plugin_with_no_lib_dir", "stubby"].sort, @locator.plugins.map(&:name).sort @configuration.plugin_paths = [File.join(plugin_fixture_root_path, "alternate")] assert_equal ["a"], @locator.plugins.map(&:name) diff --git a/railties/test/plugin_test.rb b/railties/test/plugin_test.rb index 1445338f14..50124240a5 100644 --- a/railties/test/plugin_test.rb +++ b/railties/test/plugin_test.rb @@ -5,9 +5,10 @@ uses_mocha "Plugin Tests" do class PluginTest < Test::Unit::TestCase def setup - @initializer = Rails::Initializer.new(Rails::Configuration.new) - @valid_plugin_path = plugin_fixture_path('default/stubby') - @empty_plugin_path = plugin_fixture_path('default/empty') + @initializer = Rails::Initializer.new(Rails::Configuration.new) + @valid_plugin_path = plugin_fixture_path('default/stubby') + @empty_plugin_path = plugin_fixture_path('default/empty') + @gemlike_plugin_path = plugin_fixture_path('default/gemlike') end def test_should_determine_plugin_name_from_the_directory_of_the_plugin @@ -70,7 +71,14 @@ uses_mocha "Plugin Tests" do plugin.stubs(:evaluate_init_rb) plugin.send(:load, @initializer) end - + + # This path is fine so nothing is raised + assert_nothing_raised do + plugin = plugin_for(@gemlike_plugin_path) + plugin.stubs(:evaluate_init_rb) + plugin.send(:load, @initializer) + end + # This is an empty path so it raises assert_raises(LoadError) do plugin = plugin_for(@empty_plugin_path) diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index 17c7d9deea..e1872ebf33 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -30,6 +30,8 @@ class Rails::InfoControllerTest < Test::Unit::TestCase @controller = Rails::InfoController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new + + ActionController::Base.consider_all_requests_local = true end def test_rails_info_properties_table_rendered_for_local_request @@ -41,6 +43,8 @@ class Rails::InfoControllerTest < Test::Unit::TestCase def test_rails_info_properties_error_rendered_for_non_local_request Rails::InfoController.local_request = false + ActionController::Base.consider_all_requests_local = false + get :properties assert_tag :tag => 'p' assert_response 500 |