Merge commit 'mainstream/master'

This commit is contained in:
Pratik Naik 2009-10-10 17:15:11 +01:00
commit 66ee2654ff
151 changed files with 2506 additions and 3818 deletions

1
.gitignore vendored

@ -30,5 +30,4 @@ railties/guides/output
actionpack/bin
vendor/gems/
*/vendor/gems/
bin/
railties/tmp

@ -1,5 +1,5 @@
module ExampleHelper
def example_format(text)
"<em><strong><small>#{text}</small></strong></em>"
"<em><strong><small>#{h(text)}</small></strong></em>".html_safe!
end
end

@ -4,6 +4,7 @@ gem "rack", "~> 1.0.0"
gem "rack-test", "~> 0.5.0"
gem "activesupport", "3.0.pre", :vendored_at => rails_root.join("activesupport")
gem "activemodel", "3.0.pre", :vendored_at => rails_root.join("activemodel")
gem "erubis", "~> 2.6.0"
only :test do
gem "mocha"

@ -34,22 +34,21 @@ end
desc "Run all unit tests"
task :test => [:test_action_pack, :test_active_record_integration]
TESTS_GLOB = "test/{abstract,controller,dispatch,new_base,template,html-scanner,view}/**/*_test.rb"
Rake::TestTask.new(:test_action_pack) do |t|
t.libs << 'test'
# make sure we include the tests in alphabetical order as on some systems
# this will not happen automatically and the tests (as a whole) will error
t.test_files = Dir.glob(TESTS_GLOB).sort
t.test_files = Dir.glob('test/{abstract,controller,dispatch,template}/**/*_test.rb').sort
t.verbose = true
# t.warning = true
end
task :isolated_test do
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
Dir.glob(TESTS_GLOB).all? { |file| system(ruby, '-Ilib:test', file) } or raise "Failures"
namespace :test do
Rake::TestTask.new(:isolated) do |t|
t.pattern = 'test/ts_isolated.rb'
end
end
desc 'ActiveRecord Integration Tests'

@ -50,7 +50,7 @@ def define_dispatcher_callbacks(cache_classes)
def new
# DEPRECATE Rails application fallback
Rails.application
Rails.application.new
end
end
end

@ -16,7 +16,7 @@ def session_store=(store)
self.session_store = ActiveRecord::SessionStore
else
@@session_store = store.is_a?(Symbol) ?
Session.const_get(store.to_s.camelize) :
ActionDispatch::Session.const_get(store.to_s.camelize) :
store
end
end

@ -39,6 +39,7 @@ module ActionDispatch
autoload :Rescue, 'action_dispatch/middleware/rescue'
autoload :ShowExceptions, 'action_dispatch/middleware/show_exceptions'
autoload :Static, 'action_dispatch/middleware/static'
autoload :StringCoercion, 'action_dispatch/middleware/string_coercion'
autoload :Assertions, 'action_dispatch/testing/assertions'
autoload :Integration, 'action_dispatch/testing/integration'

@ -10,7 +10,12 @@ def symbols
%w(<< concat shift unshift push pop []= clear compact! collect!
delete delete_at delete_if flatten! map! insert reject! reverse!
replace slice! sort! uniq!).each do |method|
define_method(method) {|*args| @symbols = nil; super(*args) }
module_eval <<-CODE
def #{method}(*args)
@symbols = nil
super
end
CODE
end
end

@ -0,0 +1,29 @@
module ActionDispatch
class StringCoercion
class UglyBody < ActiveSupport::BasicObject
def initialize(body)
@body = body
end
def each
@body.each do |part|
yield part.to_s
end
end
private
def method_missing(*args, &block)
@body.__send__(*args, &block)
end
end
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
[status, headers, UglyBody.new(body)]
end
end
end

@ -44,11 +44,11 @@ def self.load_all!
autoload :TextTemplate, 'action_view/template/text'
autoload :Helpers, 'action_view/helpers'
autoload :FileSystemResolverWithFallback, 'action_view/template/resolver'
autoload :SafeBuffer, 'action_view/safe_buffer'
end
class ERB
autoload :Util, 'action_view/erb/util'
end
require 'action_view/erb/util'
I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"

@ -236,7 +236,16 @@ def self.for_controller(controller)
# they are in AC.
if controller.class.respond_to?(:_helper_serial)
klass = @views[controller.class._helper_serial] ||= Class.new(self) do
Subclasses.const_set(controller.class.name.gsub(/::/, '__'), self)
const_set(:CONTROLLER_CLASS, controller.class)
# Try to make stack traces clearer
def self.name
"ActionView for #{CONTROLLER_CLASS}"
end
def inspect
"#<#{self.class.name}>"
end
if controller.respond_to?(:_helpers)
include controller._helpers
@ -255,7 +264,7 @@ def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil,
@assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) }
@controller = controller
@helpers = self.class.helpers || Module.new
@_content_for = Hash.new {|h,k| h[k] = "" }
@_content_for = Hash.new {|h,k| h[k] = ActionView::SafeBuffer.new }
self.view_paths = view_paths
end

@ -15,9 +15,19 @@ module Util
# puts html_escape("is a > 0 & a < 10?")
# # => is a &gt; 0 &amp; a &lt; 10?
def html_escape(s)
s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
s = s.to_s
if s.html_safe?
s
else
s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }.html_safe!
end
end
alias h html_escape
module_function :html_escape
module_function :h
# A utility method for escaping HTML entities in JSON strings.
# This method is also aliased as <tt>j</tt>.
#

@ -15,6 +15,7 @@ module Helpers #:nodoc:
autoload :JavaScriptHelper, 'action_view/helpers/javascript_helper'
autoload :NumberHelper, 'action_view/helpers/number_helper'
autoload :PrototypeHelper, 'action_view/helpers/prototype_helper'
autoload :RawOutputHelper, 'action_view/helpers/raw_output_helper'
autoload :RecordIdentificationHelper, 'action_view/helpers/record_identification_helper'
autoload :RecordTagHelper, 'action_view/helpers/record_tag_helper'
autoload :SanitizeHelper, 'action_view/helpers/sanitize_helper'
@ -46,6 +47,7 @@ module ClassMethods
include JavaScriptHelper
include NumberHelper
include PrototypeHelper
include RawOutputHelper
include RecordIdentificationHelper
include RecordTagHelper
include SanitizeHelper

@ -6,7 +6,7 @@
module ActionView
class Base
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>" }
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>".html_safe! }
cattr_accessor :field_error_proc
end
@ -91,6 +91,7 @@ def form(record_name, options = {})
yield contents if block_given?
contents << submit_tag(submit_value)
contents << '</form>'
contents.html_safe!
end
# Returns a string containing the error message attached to the +method+ on the +object+ if one exists.

@ -289,7 +289,7 @@ def javascript_include_tag(*sources)
else
sources = expand_javascript_sources(sources, recursive)
ensure_javascript_sources!(sources) if cache
sources.collect { |source| javascript_src_tag(source, options) }.join("\n")
sources.collect { |source| javascript_src_tag(source, options) }.join("\n").html_safe!
end
end
@ -440,7 +440,7 @@ def stylesheet_link_tag(*sources)
else
sources = expand_stylesheet_sources(sources, recursive)
ensure_stylesheet_sources!(sources) if cache
sources.collect { |source| stylesheet_tag(source, options) }.join("\n")
sources.collect { |source| stylesheet_tag(source, options) }.join("\n").html_safe!
end
end
@ -584,7 +584,7 @@ def video_tag(sources, options = {})
if sources.is_a?(Array)
content_tag("video", options) do
sources.map { |source| tag("source", :src => source) }.join
sources.map { |source| tag("source", :src => source) }.join.html_safe!
end
else
options[:src] = path_to_video(sources)

@ -143,7 +143,7 @@ def content_for?(name)
# Defaults to a new empty string.
def with_output_buffer(buf = nil) #:nodoc:
unless buf
buf = ''
buf = ActionView::SafeBuffer.new
buf.force_encoding(output_buffer.encoding) if buf.respond_to?(:force_encoding)
end
self.output_buffer, old_buffer = buf, output_buffer

@ -916,15 +916,15 @@ def separator(type)
class InstanceTag #:nodoc:
def to_date_select_tag(options = {}, html_options = {})
datetime_selector(options, html_options).select_date
datetime_selector(options, html_options).select_date.html_safe!
end
def to_time_select_tag(options = {}, html_options = {})
datetime_selector(options, html_options).select_time
datetime_selector(options, html_options).select_time.html_safe!
end
def to_datetime_select_tag(options = {}, html_options = {})
datetime_selector(options, html_options).select_datetime
datetime_selector(options, html_options).select_datetime.html_safe!
end
private

@ -282,7 +282,7 @@ def form_for(record_or_name_or_array, *args, &proc)
concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}))
fields_for(object_name, *(args << options), &proc)
concat('</form>')
concat('</form>'.html_safe!)
end
def apply_form_for_options!(object_or_array, options) #:nodoc:
@ -809,7 +809,7 @@ def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
add_default_name_and_id(options)
hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
checkbox = tag("input", options)
hidden + checkbox
(hidden + checkbox).html_safe!
end
def to_boolean_select_tag(options = {})

@ -296,7 +296,7 @@ def options_for_select(container, selected = nil)
options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}#{disabled_attribute}>#{html_escape(text.to_s)}</option>)
end
options_for_select.join("\n")
options_for_select.join("\n").html_safe!
end
# Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the

@ -440,7 +440,7 @@ def field_set_tag(legend = nil, options = nil, &block)
concat(tag(:fieldset, options, true))
concat(content_tag(:legend, legend)) unless legend.blank?
concat(content)
concat("</fieldset>")
concat("</fieldset>".html_safe!)
end
private
@ -467,14 +467,14 @@ def extra_tags_for_form(html_options)
def form_tag_html(html_options)
extra_tags = extra_tags_for_form(html_options)
tag(:form, html_options, true) + extra_tags
(tag(:form, html_options, true) + extra_tags).html_safe!
end
def form_tag_in_block(html_options, &block)
content = capture(&block)
concat(form_tag_html(html_options))
concat(content)
concat("</form>")
concat("</form>".html_safe!)
end
def token_tag

@ -1,3 +1,4 @@
require 'active_support/core_ext/big_decimal/conversions'
require 'active_support/core_ext/float/rounding'
module ActionView

@ -395,7 +395,7 @@ def remote_form_for(record_or_name_or_array, *args, &proc)
concat(form_remote_tag(options))
fields_for(object_name, *(args << options), &proc)
concat('</form>')
concat('</form>'.html_safe!)
end
alias_method :form_remote_for, :remote_form_for

@ -0,0 +1,9 @@
module ActionView #:nodoc:
module Helpers #:nodoc:
module RawOutputHelper
def raw(stringish)
stringish.to_s.html_safe!
end
end
end
end

@ -15,7 +15,7 @@ module RecordTagHelper
def div_for(record, *args, &block)
content_tag_for(:div, record, *args, &block)
end
# content_tag_for creates an HTML element with id and class parameters
# that relate to the specified Active Record object. For example:
#
@ -34,7 +34,7 @@ def div_for(record, *args, &block)
# <% content_tag_for(:tr, @person, :foo) do %> ...
#
# produces:
#
#
# <tr id="foo_person_123" class="person">...
#
# content_tag_for also accepts a hash of options, which will be converted to
@ -50,7 +50,7 @@ def div_for(record, *args, &block)
def content_tag_for(tag_name, record, *args, &block)
prefix = args.first.is_a?(Hash) ? nil : args.shift
options = args.extract_options!
options.merge!({ :class => "#{dom_class(record)} #{options[:class]}".strip, :id => dom_id(record, prefix) })
options.merge!({ :class => "#{dom_class(record, prefix)} #{options[:class]}".strip, :id => dom_id(record, prefix) })
content_tag(tag_name, options, &block)
end
end

@ -49,7 +49,11 @@ module SanitizeHelper
# confuse browsers.
#
def sanitize(html, options = {})
self.class.white_list_sanitizer.sanitize(html, options)
returning self.class.white_list_sanitizer.sanitize(html, options) do |sanitized|
if sanitized
sanitized.html_safe!
end
end
end
# Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
@ -72,7 +76,11 @@ def sanitize_css(style)
# strip_tags("<div id='top-bar'>Welcome to my website!</div>")
# # => Welcome to my website!
def strip_tags(html)
self.class.full_sanitizer.sanitize(html)
returning self.class.full_sanitizer.sanitize(html) do |sanitized|
if sanitized
sanitized.html_safe!
end
end
end
# Strips all link tags from +text+ leaving just the link text.

@ -41,7 +41,7 @@ module TagHelper
# tag("img", { :src => "open &amp; shut.png" }, false, false)
# # => <img src="open &amp; 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 ? ">" : " />"}".html_safe!
end
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
@ -94,7 +94,7 @@ def content_tag(name, content_or_options_with_block = nil, options = nil, escape
# cdata_section(File.read("hello_world.txt"))
# # => <![CDATA[<hello from a text file]]>
def cdata_section(content)
"<![CDATA[#{content}]]>"
"<![CDATA[#{content}]]>".html_safe!
end
# Returns an escaped version of +html+ without affecting existing escaped entities.
@ -128,7 +128,7 @@ def block_called_from_erb?(block)
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
"<#{name}#{tag_options}>#{content}</#{name}>"
"<#{name}#{tag_options}>#{content}</#{name}>".html_safe!
end
def tag_options(options, escape = true)
@ -143,7 +143,7 @@ def tag_options(options, escape = true)
attrs << %(#{key}="#{final_value}")
end
end
" #{attrs.sort * ' '}" unless attrs.empty?
" #{attrs.sort * ' '}".html_safe! unless attrs.empty?
end
end
end

@ -93,7 +93,7 @@ def url_for(options = {})
polymorphic_path(options)
end
escape ? escape_once(url) : url
(escape ? escape_once(url) : url).html_safe!
end
# Creates a link tag of the given +name+ using a URL created by the set
@ -220,7 +220,7 @@ def link_to(*args, &block)
if block_given?
options = args.first || {}
html_options = args.second
concat(link_to(capture(&block), options, html_options))
concat(link_to(capture(&block), options, html_options).html_safe!)
else
name = args[0]
options = args[1] || {}
@ -238,7 +238,7 @@ def link_to(*args, &block)
end
href_attr = "href=\"#{url}\"" unless href
"<a #{href_attr}#{tag_options}>#{name || url}</a>"
"<a #{href_attr}#{tag_options}>#{ERB::Util.h(name || url)}</a>".html_safe!
end
end
@ -309,8 +309,8 @@ def button_to(name, options = {}, html_options = {})
html_options.merge!("type" => "submit", "value" => name)
"<form method=\"#{form_method}\" action=\"#{escape_once url}\" class=\"button-to\"><div>" +
method_tag + tag("input", html_options) + request_token_tag + "</div></form>"
("<form method=\"#{form_method}\" action=\"#{escape_once url}\" class=\"button-to\"><div>" +
method_tag + tag("input", html_options) + request_token_tag + "</div></form>").html_safe!
end

@ -1,8 +1,11 @@
module ActionView #:nodoc:
class PathSet < Array #:nodoc:
def self.type_cast(obj)
def self.type_cast(obj, cache = nil)
# TODO: Clean this up
if obj.is_a?(String)
cache = !defined?(Rails) || !Rails.respond_to?(:configuration) || Rails.configuration.cache_classes
if cache.nil?
cache = !defined?(Rails) || Rails.application.config.cache_classes
end
FileSystemResolverWithFallback.new(obj, :cache => cache)
else
obj

@ -223,7 +223,7 @@ def render_collection
end
result = template ? collection_with_template(template) : collection_without_template
result.join(spacer)
result.join(spacer).html_safe!
end
def collection_with_template(template)
@ -296,7 +296,10 @@ def find_template(path = @path)
end
def _find_template(path)
prefix = @view.controller.controller_path unless path.include?(?/)
if controller = @view.controller
prefix = controller.controller_path unless path.include?(?/)
end
@view.find(path, {:formats => @view.formats}, prefix, true)
end

@ -14,6 +14,7 @@ def render(options = {}, locals = {}, &block) #:nodoc:
case options
when Hash
layout = options[:layout]
options[:locals] ||= {}
if block_given?
return concat(_render_partial(options.merge(:partial => layout), &block))
@ -25,11 +26,11 @@ def render(options = {}, locals = {}, &block) #:nodoc:
if file = options[:file]
template = find(file, {:formats => formats})
_render_template(template, layout, :locals => options[:locals] || {})
_render_template(template, layout, :locals => options[:locals])
elsif inline = options[:inline]
_render_inline(inline, layout, options)
elsif text = options[:text]
_render_text(text, layout, options)
_render_text(text, layout, options[:locals])
end
when :update
update_page(&block)
@ -80,16 +81,19 @@ def _layout_for(name = nil)
def _render_inline(inline, layout, options)
handler = Template.handler_class_for_extension(options[:type] || "erb")
template = Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {})
locals = options[:locals] || {}
template = Template.new(options[:inline],
"inline #{options[:inline].inspect}", handler, {})
locals = options[:locals]
content = template.render(self, locals)
content = layout.render(self, locals) {|*name| _layout_for(*name) { content } } if layout
content
_render_text(content, layout, locals)
end
def _render_text(text, layout, options)
text = layout.render(self, options[:locals]) { text } if layout
text
def _render_text(content, layout, locals)
content = layout.render(self, locals) do |*name|
_layout_for(*name) { content }
end if layout
content
end
# This is the API to render a ViewContext's template from a controller.

@ -0,0 +1,28 @@
module ActionView #:nodoc:
class SafeBuffer < String
def <<(value)
if value.html_safe?
super(value)
else
super(CGI.escapeHTML(value))
end
end
def concat(value)
self << value
end
def html_safe?
true
end
def html_safe!
self
end
def to_s
self
end
end
end

@ -1,7 +1,31 @@
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/string/output_safety'
require 'erubis'
module ActionView
module TemplateHandlers
class Erubis < ::Erubis::Eruby
def add_preamble(src)
src << "@output_buffer = ActionView::SafeBuffer.new;"
end
def add_text(src, text)
src << "@output_buffer << ('" << escape_text(text) << "'.html_safe!);"
end
def add_expr_literal(src, code)
src << '@output_buffer << ((' << code << ').to_s);'
end
def add_expr_escaped(src, code)
src << '@output_buffer << ' << escaped_expr(code) << ';'
end
def add_postamble(src)
src << '@output_buffer.to_s'
end
end
class ERB < TemplateHandler
include Compilable
@ -15,11 +39,9 @@ class ERB < TemplateHandler
self.default_format = Mime::HTML
def compile(template)
require 'erb'
magic = $1 if template.source =~ /\A(<%#.*coding[:=]\s*(\S+)\s*-?%>)/
erb = "#{magic}<% __in_erb_template=true %>#{template.source}"
::ERB.new(erb, nil, erb_trim_mode, '@output_buffer').src
Erubis.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src
end
end
end

@ -55,7 +55,7 @@ def initialize
setup :setup_with_controller
def setup_with_controller
@controller = TestController.new
@output_buffer = ''
@output_buffer = ActionView::SafeBuffer.new
@rendered = ''
self.class.send(:include_helper_modules!)

@ -51,23 +51,73 @@
ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
FIXTURES = Pathname.new(FIXTURE_LOAD_PATH)
class ActionController::IntegrationTest < ActiveSupport::TestCase
@@app = ActionDispatch::MiddlewareStack.new { |middleware|
middleware.use "ActionDispatch::ShowExceptions"
middleware.use "ActionDispatch::Callbacks"
middleware.use "ActionDispatch::ParamsParser"
middleware.use "Rack::Head"
}.build(ActionController::Routing::Routes)
end
module SetupOnce
extend ActiveSupport::Concern
module ActionView
class TestCase
setup do
ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action/:id'
included do
cattr_accessor :setup_once_block
self.setup_once_block = nil
setup :run_setup_once
end
module ClassMethods
def setup_once(&block)
self.setup_once_block = block
end
end
private
def run_setup_once
if self.setup_once_block
self.setup_once_block.call
self.setup_once_block = nil
end
end
end
class ActiveSupport::TestCase
include SetupOnce
# Hold off drawing routes until all the possible controller classes
# have been loaded.
setup_once do
ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action/:id'
end
end
end
class ActionController::IntegrationTest < ActiveSupport::TestCase
def self.build_app(routes = nil)
ActionDispatch::MiddlewareStack.new { |middleware|
middleware.use "ActionDispatch::StringCoercion"
middleware.use "ActionDispatch::ShowExceptions"
middleware.use "ActionDispatch::Callbacks"
middleware.use "ActionDispatch::ParamsParser"
middleware.use "Rack::Head"
}.build(routes || ActionController::Routing::Routes)
end
self.app = build_app
def with_routing(&block)
real_routes = ActionController::Routing::Routes
ActionController::Routing.module_eval { remove_const :Routes }
temporary_routes = ActionController::Routing::RouteSet.new
self.class.app = self.class.build_app(temporary_routes)
ActionController::Routing.module_eval { const_set :Routes, temporary_routes }
yield temporary_routes
ensure
if ActionController::Routing.const_defined? :Routes
ActionController::Routing.module_eval { remove_const :Routes }
end
ActionController::Routing.const_set(:Routes, real_routes) if real_routes
self.class.app = self.class.build_app
end
end
@ -138,18 +188,12 @@ def self.inherited(klass)
super
end
end
Base.view_paths = FIXTURE_LOAD_PATH
class TestCase
include TestProcess
setup do
ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action/:id'
end
end
def assert_template(options = {}, message = nil)
validate_request!
@ -192,11 +236,3 @@ def assert_template(options = {}, message = nil)
end
end
end
class SimpleRouteCase < Rack::TestCase
setup do
ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action/:id'
end
end
end

@ -158,7 +158,6 @@ def with_test_route_set(options = {})
map.connect "/:action", :controller => "active_record_store_test/test"
end
@app = ActiveRecord::SessionStore.new(set, options.reverse_merge(:key => '_session_id'))
reset!
yield
end
end

@ -46,14 +46,8 @@ def trailing_slash
class PageCachingTest < ActionController::TestCase
def setup
super
ActionController::Base.perform_caching = true
ActionController::Routing::Routes.draw do |map|
map.main '', :controller => 'posts', :format => nil
map.formatted_posts 'posts.:format', :controller => 'posts'
map.resources :posts
map.connect ':controller/:action/:id'
end
ActionController::Base.perform_caching = true
@request = ActionController::TestRequest.new
@request.host = 'hostname.com'
@ -74,10 +68,16 @@ def teardown
end
def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route
@params[:format] = 'rss'
assert_equal '/posts.rss', @rewriter.rewrite(@params)
@params[:format] = nil
assert_equal '/', @rewriter.rewrite(@params)
with_routing do |set|
set.draw do |map|
map.main '', :controller => 'posts', :format => nil
map.formatted_posts 'posts.:format', :controller => 'posts'
end
@params[:format] = 'rss'
assert_equal '/posts.rss', @rewriter.rewrite(@params)
@params[:format] = nil
assert_equal '/', @rewriter.rewrite(@params)
end
end
def test_should_cache_get_with_ok_status

@ -372,11 +372,8 @@ def test_generate_url_with_controller
def with_test_route_set
with_routing do |set|
set.draw do |map|
map.with_options :controller => "IntegrationProcessTest::Integration" do |c|
c.connect "/:action"
end
map.connect "/:action", :controller => "integration_process_test/integration"
end
reset!
yield
end
end

@ -527,12 +527,6 @@ def setup
super
ActionController::Base.use_accept_header = true
@request.host = "www.example.com"
ActionController::Routing::Routes.draw do |map|
map.resources :customers
map.resources :quiz_stores, :has_many => :customers
map.connect ":controller/:action/:id"
end
end
def teardown
@ -593,53 +587,59 @@ def test_using_resource
end
def test_using_resource_for_post_with_html
post :using_resource
assert_equal "text/html", @response.content_type
assert_equal 302, @response.status
assert_equal "http://www.example.com/customers/13", @response.location
assert @response.redirect?
with_test_route_set do
post :using_resource
assert_equal "text/html", @response.content_type
assert_equal 302, @response.status
assert_equal "http://www.example.com/customers/13", @response.location
assert @response.redirect?
errors = { :name => :invalid }
Customer.any_instance.stubs(:errors).returns(errors)
post :using_resource
assert_equal "text/html", @response.content_type
assert_equal 200, @response.status
assert_equal "New world!\n", @response.body
assert_nil @response.location
errors = { :name => :invalid }
Customer.any_instance.stubs(:errors).returns(errors)
post :using_resource
assert_equal "text/html", @response.content_type
assert_equal 200, @response.status
assert_equal "New world!\n", @response.body
assert_nil @response.location
end
end
def test_using_resource_for_post_with_xml
@request.accept = "application/xml"
with_test_route_set do
@request.accept = "application/xml"
post :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 201, @response.status
assert_equal "<name>david</name>", @response.body
assert_equal "http://www.example.com/customers/13", @response.location
post :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 201, @response.status
assert_equal "<name>david</name>", @response.body
assert_equal "http://www.example.com/customers/13", @response.location
errors = { :name => :invalid }
Customer.any_instance.stubs(:errors).returns(errors)
post :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 422, @response.status
assert_equal errors.to_xml, @response.body
assert_nil @response.location
errors = { :name => :invalid }
Customer.any_instance.stubs(:errors).returns(errors)
post :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 422, @response.status
assert_equal errors.to_xml, @response.body
assert_nil @response.location
end
end
def test_using_resource_for_put_with_html
put :using_resource
assert_equal "text/html", @response.content_type
assert_equal 302, @response.status
assert_equal "http://www.example.com/customers/13", @response.location
assert @response.redirect?
with_test_route_set do
put :using_resource
assert_equal "text/html", @response.content_type
assert_equal 302, @response.status
assert_equal "http://www.example.com/customers/13", @response.location
assert @response.redirect?
errors = { :name => :invalid }
Customer.any_instance.stubs(:errors).returns(errors)
put :using_resource
assert_equal "text/html", @response.content_type
assert_equal 200, @response.status
assert_equal "Edit world!\n", @response.body
assert_nil @response.location
errors = { :name => :invalid }
Customer.any_instance.stubs(:errors).returns(errors)
put :using_resource
assert_equal "text/html", @response.content_type
assert_equal 200, @response.status
assert_equal "Edit world!\n", @response.body
assert_nil @response.location
end
end
def test_using_resource_for_put_with_xml
@ -660,11 +660,13 @@ def test_using_resource_for_put_with_xml
end
def test_using_resource_for_delete_with_html
Customer.any_instance.stubs(:destroyed?).returns(true)
delete :using_resource
assert_equal "text/html", @response.content_type
assert_equal 302, @response.status
assert_equal "http://www.example.com/customers", @response.location
with_test_route_set do
Customer.any_instance.stubs(:destroyed?).returns(true)
delete :using_resource
assert_equal "text/html", @response.content_type
assert_equal 302, @response.status
assert_equal "http://www.example.com/customers", @response.location
end
end
def test_using_resource_for_delete_with_xml
@ -685,21 +687,23 @@ def test_using_resource_with_parent_for_get
end
def test_using_resource_with_parent_for_post
@request.accept = "application/xml"
with_test_route_set do
@request.accept = "application/xml"
post :using_resource_with_parent
assert_equal "application/xml", @response.content_type
assert_equal 201, @response.status
assert_equal "<name>david</name>", @response.body
assert_equal "http://www.example.com/quiz_stores/11/customers/13", @response.location
post :using_resource_with_parent
assert_equal "application/xml", @response.content_type
assert_equal 201, @response.status
assert_equal "<name>david</name>", @response.body
assert_equal "http://www.example.com/quiz_stores/11/customers/13", @response.location
errors = { :name => :invalid }
Customer.any_instance.stubs(:errors).returns(errors)
post :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 422, @response.status
assert_equal errors.to_xml, @response.body
assert_nil @response.location
errors = { :name => :invalid }
Customer.any_instance.stubs(:errors).returns(errors)
post :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 422, @response.status
assert_equal errors.to_xml, @response.body
assert_nil @response.location
end
end
def test_using_resource_with_collection
@ -773,6 +777,18 @@ def test_not_acceptable
get :default_overwritten
assert_equal 406, @response.status
end
private
def with_test_route_set
with_routing do |set|
set.draw do |map|
map.resources :customers
map.resources :quiz_stores, :has_many => :customers
map.connect ":controller/:action/:id"
end
yield
end
end
end
class AbstractPostController < ActionController::Base

@ -26,7 +26,7 @@ module Submodule
class ContainedEmptyController < ActionController::Base ; end
end
class BaseTest < SimpleRouteCase
class BaseTest < Rack::TestCase
# :api: plugin
test "simple dispatching" do
get "/dispatching/simple/index"

@ -9,7 +9,7 @@ class BasicController < ActionController::Base
)]
end
class TestContentNegotiation < SimpleRouteCase
class TestContentNegotiation < Rack::TestCase
test "A */* Accept header will return HTML" do
get "/content_negotiation/basic/hello", {}, "HTTP_ACCEPT" => "*/*"
assert_body "Hello world */*!"

@ -44,7 +44,7 @@ def set_as_nil_on_response_obj
end
end
class ExplicitContentTypeTest < SimpleRouteCase
class ExplicitContentTypeTest < Rack::TestCase
test "default response is HTML and UTF8" do
get "/content_type/base"
@ -67,7 +67,7 @@ class ExplicitContentTypeTest < SimpleRouteCase
end
end
class ImpliedContentTypeTest < SimpleRouteCase
class ImpliedContentTypeTest < Rack::TestCase
test "sets Content-Type as text/html when rendering *.html.erb" do
get "/content_type/implied/i_am_html_erb"
@ -93,7 +93,7 @@ class ImpliedContentTypeTest < SimpleRouteCase
end
end
class ExplicitCharsetTest < SimpleRouteCase
class ExplicitCharsetTest < Rack::TestCase
test "setting the charset of the response directly on the response object" do
get "/content_type/charset/set_on_response_obj"

@ -16,7 +16,7 @@ def with_layout
end
end
class EtagTest < SimpleRouteCase
class EtagTest < Rack::TestCase
describe "Rendering without any special etag options returns an etag that is an MD5 hash of its text"
test "an action without a layout" do

@ -45,7 +45,7 @@ def hello_world_with_custom_layout
end
class RenderActionTest < SimpleRouteCase
class RenderActionTest < Rack::TestCase
test "rendering an action using :action => <String>" do
get "/render_action/basic/hello_world"
@ -82,7 +82,7 @@ class RenderActionTest < SimpleRouteCase
end
end
class RenderLayoutTest < SimpleRouteCase
class RenderLayoutTest < Rack::TestCase
describe "Both <controller_path>.html.erb and application.html.erb are missing"
test "rendering with layout => true" do
@ -150,7 +150,7 @@ def with_builder_and_layout
end
end
class LayoutTest < SimpleRouteCase
class LayoutTest < Rack::TestCase
describe "Only application.html.erb is present and <controller_path>.html.erb is missing"
test "rendering implicit application.html.erb as layout" do
@ -189,7 +189,7 @@ class LayoutTest < SimpleRouteCase
end
end
class TestLayout < SimpleRouteCase
class TestLayout < Rack::TestCase
testing BasicController
test "builder works with layouts" do
@ -228,7 +228,7 @@ def hello_world_with_custom_layout
end
end
class ControllerLayoutTest < SimpleRouteCase
class ControllerLayoutTest < Rack::TestCase
describe "Only <controller_path>.html.erb is present and application.html.erb is missing"
test "render hello_world and implicitly use <controller_path>.html.erb as a layout." do
@ -286,7 +286,7 @@ def hello_world_with_layout_nil
end
end
class ControllerLayoutTest < SimpleRouteCase
class ControllerLayoutTest < Rack::TestCase
describe "Both <controller_path>.html.erb and application.html.erb are present"
test "rendering implicitly use <controller_path>.html.erb over application.html.erb as a layout" do

@ -1,110 +1,99 @@
require 'abstract_unit'
module RenderFile
class BasicController < ActionController::Base
self.view_paths = File.dirname(__FILE__)
self.view_paths = File.dirname(__FILE__)
def index
render :file => File.join(File.dirname(__FILE__), *%w[.. fixtures test hello_world])
render :file => File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world])
end
def with_instance_variables
@secret = 'in the sauce'
render :file => File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')
render :file => File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar.erb')
end
def without_file_key
render File.join(File.dirname(__FILE__), *%w[.. fixtures test hello_world])
render File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world])
end
def without_file_key_with_instance_variable
@secret = 'in the sauce'
render File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')
render File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar.erb')
end
def relative_path
@secret = 'in the sauce'
render :file => '../fixtures/test/render_file_with_ivar'
render :file => '../../fixtures/test/render_file_with_ivar'
end
def relative_path_with_dot
@secret = 'in the sauce'
render :file => '../fixtures/test/dot.directory/render_file_with_ivar'
render :file => '../../fixtures/test/dot.directory/render_file_with_ivar'
end
def pathname
@secret = 'in the sauce'
render :file => Pathname.new(File.dirname(__FILE__)).join(*%w[.. fixtures test dot.directory render_file_with_ivar.erb])
render :file => Pathname.new(File.dirname(__FILE__)).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar.erb])
end
def with_locals
path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.erb')
path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals.erb')
render :file => path, :locals => {:secret => 'in the sauce'}
end
def without_file_key_with_locals
path = File.expand_path('../fixtures/test/render_file_with_locals.erb')
path = FIXTURES.join('test/render_file_with_locals.erb').to_s
render path, :locals => {:secret => 'in the sauce'}
end
end
class TestBasic < SimpleRouteCase
class TestBasic < Rack::TestCase
testing RenderFile::BasicController
def setup
@old_pwd = Dir.pwd
Dir.chdir(File.dirname(__FILE__))
end
def teardown
Dir.chdir(@old_pwd)
end
test "rendering simple template" do
get :index
assert_response "Hello world!"
end
test "rendering template with ivar" do
get :with_instance_variables
assert_response "The secret is in the sauce\n"
end
test "rendering path without specifying the :file key" do
get :without_file_key
assert_response "Hello world!"
end
test "rendering path without specifying the :file key with ivar" do
get :without_file_key_with_instance_variable
assert_response "The secret is in the sauce\n"
end
test "rendering a relative path" do
get :relative_path
assert_response "The secret is in the sauce\n"
end
test "rendering a relative path with dot" do
get :relative_path_with_dot
assert_response "The secret is in the sauce\n"
end
test "rendering a Pathname" do
get :pathname
assert_response "The secret is in the sauce\n"
end
test "rendering file with locals" do
get :with_locals
assert_response "The secret is in the sauce\n"
end
test "rendering path without specifying the :file key with locals" do
get :without_file_key_with_locals
assert_response "The secret is in the sauce\n"
end
end
end

@ -10,7 +10,7 @@ class SimpleController < ::ApplicationController
def hello_world() end
end
class RenderImplicitActionTest < SimpleRouteCase
class RenderImplicitActionTest < Rack::TestCase
test "render a simple action with new explicit call to render" do
get "/render_implicit_action/simple/hello_world"

@ -36,7 +36,7 @@ def index
end
end
class RenderLayoutTest < SimpleRouteCase
class RenderLayoutTest < Rack::TestCase
test "rendering a normal template, but using the implicit layout" do
get "/controller_layouts/implicit/index"
@ -58,7 +58,7 @@ class RenderLayoutTest < SimpleRouteCase
end
class LayoutOptionsTest < SimpleRouteCase
class LayoutOptionsTest < Rack::TestCase
testing ControllerLayouts::ImplicitController
test "rendering with :layout => false leaves out the implicit layout" do
@ -79,7 +79,7 @@ def explicit
end
end
class MismatchFormatTest < SimpleRouteCase
class MismatchFormatTest < Rack::TestCase
testing ControllerLayouts::MismatchFormatController
test "if JS is selected, an HTML template is not also selected" do

@ -15,7 +15,7 @@ def changing
end
end
class TestPartial < SimpleRouteCase
class TestPartial < Rack::TestCase
testing BasicController
test "rendering a partial in ActionView doesn't pull the ivars again from the controller" do

@ -21,7 +21,7 @@ def index_locale
end
end
class TestBasic < SimpleRouteCase
class TestBasic < Rack::TestCase
testing BasicController
def setup

@ -39,7 +39,7 @@ def builder_template
end
end
class TestWithoutLayout < SimpleRouteCase
class TestWithoutLayout < Rack::TestCase
testing RenderTemplate::WithoutLayoutController
test "rendering a normal template with full path without layout" do
@ -107,7 +107,7 @@ def with_custom_layout
end
end
class TestWithLayout < SimpleRouteCase
class TestWithLayout < Rack::TestCase
describe "Rendering with :template using implicit or explicit layout"
test "rendering with implicit layout" do
@ -158,7 +158,7 @@ def with_forward_slash
end
end
class TestTemplateRenderWithForwardSlash < SimpleRouteCase
class TestTemplateRenderWithForwardSlash < Rack::TestCase
test "rendering a normal template with full path starting with a leading slash" do
get "/render_template/compatibility/without_layout/with_forward_slash"

@ -35,7 +35,7 @@ def index
end
end
class RenderTest < SimpleRouteCase
class RenderTest < Rack::TestCase
test "render with blank" do
get "/render/blank_render"
@ -50,7 +50,7 @@ class RenderTest < SimpleRouteCase
end
end
class TestOnlyRenderPublicActions < SimpleRouteCase
class TestOnlyRenderPublicActions < Rack::TestCase
describe "Only public methods on actual controllers are callable actions"
test "raises an exception when a method of Object is called" do
@ -66,7 +66,7 @@ class TestOnlyRenderPublicActions < SimpleRouteCase
end
end
class TestVariousObjectsAvailableInView < SimpleRouteCase
class TestVariousObjectsAvailableInView < Rack::TestCase
test "The request object is accessible in the view" do
get "/render/blank_render/access_request"
assert_body "The request: GET"

@ -62,7 +62,7 @@ def with_ivar_in_layout
end
end
class RenderTextTest < SimpleRouteCase
class RenderTextTest < Rack::TestCase
describe "Rendering text using render :text"
test "rendering text from a action with default options renders the text with the layout" do

@ -0,0 +1,19 @@
require 'abstract_unit'
class OutputEscapingTest < ActiveSupport::TestCase
test "escape_html shouldn't die when passed nil" do
assert ERB::Util.h(nil).blank?
end
test "escapeHTML should escape strings" do
assert_equal "&lt;&gt;&quot;", ERB::Util.h("<>\"")
end
test "escapeHTML shouldn't touch explicitly safe strings" do
# TODO this seems easier to compose and reason about, but
# this should be verified
assert_equal "<", ERB::Util.h("<".html_safe!)
end
end

@ -1050,7 +1050,7 @@ def test_rendering_with_conflicting_local_vars
def test_action_talk_to_layout
get :action_talk_to_layout
assert_equal "<title>Talking to the layout</title>\nAction was here!", @response.body
assert_equal "<title>Talking to the layout</title>\n\nAction was here!", @response.body
end
# :addressed:

@ -347,7 +347,6 @@ def with_test_routing
map.connect 'invalid', :controller => "rescue_test/test", :action => 'invalid'
map.connect 'b00m', :controller => "rescue_test/test", :action => 'b00m'
end
reset!
yield
end
end

@ -669,21 +669,13 @@ def setup_request_method_routes_for(method)
%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
setup_request_method_routes_for(request_method)
assert_nothing_raised { rs.recognize(@request) }
assert_equal request_method.downcase, @request.path_parameters[:action]
end
end
def test_recognize_array_of_methods
Object.const_set(:BooksController, Class.new(ActionController::Base))
rs.draw do |r|
r.connect '/match', :controller => 'books', :action => 'get_or_post', :conditions => { :method => [:get, :post] }
r.connect '/match', :controller => 'books', :action => 'not_get_or_post'
@ -701,13 +693,9 @@ def test_recognize_array_of_methods
@request.request_uri = "/match"
assert_nothing_raised { rs.recognize(@request) }
assert_equal 'not_get_or_post', @request.path_parameters[:action]
ensure
Object.send(:remove_const, :BooksController) rescue nil
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'
@ -730,13 +718,9 @@ def test_subpath_recognized
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'
@ -746,8 +730,6 @@ def test_subpath_generated
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
@ -1122,8 +1104,6 @@ def test_recognize_with_encoded_id_and_regex
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 }
@ -1183,14 +1163,9 @@ def test_recognize_with_conditions
assert_equal [:get, :put, :delete], e.allowed_methods
end
request.recycle!
ensure
Object.send(:remove_const, :PeopleController)
end
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 }
@ -1208,13 +1183,9 @@ def test_recognize_with_alias_in_conditions
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',
@ -1229,9 +1200,6 @@ def test_typo_recognition
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
@ -1248,8 +1216,6 @@ def test_routing_traversal_does_not_load_extra_classes
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 }
@ -1276,8 +1242,6 @@ def test_recognize_with_conditions_and_format
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
def test_generate_with_default_action
@ -1291,8 +1255,6 @@ def test_generate_with_default_action
end
def test_root_map
Object.const_set(:PeopleController, Class.new)
set.draw { |map| map.root :controller => "people" }
request.path = ""
@ -1300,13 +1262,9 @@ def test_root_map
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.namespace 'api' do |api|
@ -1320,13 +1278,9 @@ def test_namespace
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
def test_namespaced_root_map
Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) })
set.draw do |map|
map.namespace 'api' do |api|
@ -1340,13 +1294,9 @@ def test_namespaced_root_map
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_namespace_with_path_prefix
Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) })
set.draw do |map|
map.namespace 'api', :path_prefix => 'prefix' do |api|
api.route 'inventory', :controller => "products", :action => 'inventory'
@ -1358,13 +1308,9 @@ def test_namespace_with_path_prefix
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
def test_namespace_with_blank_path_prefix
Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) })
set.draw do |map|
map.namespace 'api', :path_prefix => '' do |api|
api.route 'inventory', :controller => "products", :action => 'inventory'
@ -1376,8 +1322,6 @@ def test_namespace_with_blank_path_prefix
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
def test_generate_changes_controller_module

@ -111,13 +111,6 @@ def unconditional_redirect
tests TestController
setup do
ActionController::Routing::Routes.draw do |map|
map.foo '/foo', :controller => 'test', :action => 'foo'
map.connect ":controller/:action/:id"
end
end
def test_using_symbol_back_with_no_referrer
assert_raise(ActionController::RedirectBackError) { get :guarded_with_back }
end
@ -130,8 +123,14 @@ def test_using_symbol_back_redirects_to_referrer
def test_no_deprecation_warning_for_named_route
assert_not_deprecated do
get :guarded_one_for_named_route_test, :two => "not one"
assert_redirected_to '/foo'
with_routing do |set|
set.draw do |map|
map.foo '/foo', :controller => 'test', :action => 'foo'
map.connect ":controller/:action/:id"
end
get :guarded_one_for_named_route_test, :two => "not one"
assert_redirected_to '/foo'
end
end
end

@ -259,7 +259,6 @@ def with_test_route_set
c.connect "/", :action => "assign_parameters"
end
end
reset!
yield
end
end

@ -59,7 +59,6 @@ def with_test_routing
set.draw do |map|
map.connect ':action', :controller => "json_params_parsing_test/test"
end
reset!
yield
end
end

@ -153,7 +153,6 @@ def with_test_routing
set.draw do |map|
map.connect ':action', :controller => "multipart_params_parsing_test/test"
end
reset!
yield
end
end

@ -111,7 +111,6 @@ def assert_parses(expected, actual)
set.draw do |map|
map.connect ':action', :controller => "query_string_parsing_test/test"
end
reset!
get "/parse", actual
assert_response :ok

@ -132,7 +132,6 @@ def with_test_routing
set.draw do |map|
map.connect ':action', :controller => "url_encoded_params_parsing_test/test"
end
reset!
yield
end
end

@ -86,7 +86,6 @@ def with_test_routing
set.draw do |map|
map.connect ':action', :controller => "xml_params_parsing_test/test"
end
reset!
yield
end
end

@ -223,7 +223,6 @@ def with_test_route_set(options = {})
end
options = {:key => SessionKey, :secret => SessionSecret}.merge(options)
@app = ActionDispatch::Session::CookieStore.new(set, options)
reset!
yield
end
end

@ -115,7 +115,6 @@ def with_test_route_set
map.connect "/:action", :controller => "mem_cache_store_test/test"
end
@app = ActionDispatch::Session::MemCacheStore.new(set, :key => '_session_id')
reset!
yield
end
end

@ -0,0 +1,40 @@
require 'abstract_unit'
class StringCoercionTest < ActiveSupport::TestCase
test "body responds to each" do
original_body = []
body = ActionDispatch::StringCoercion::UglyBody.new(original_body)
assert original_body.respond_to?(:each)
assert body.respond_to?(:each)
end
test "body responds to to_path" do
original_body = []
def original_body.to_path; end
body = ActionDispatch::StringCoercion::UglyBody.new(original_body)
assert original_body.respond_to?(:to_path)
assert body.respond_to?(:to_path)
end
test "body does not responds to to_path" do
original_body = []
body = ActionDispatch::StringCoercion::UglyBody.new(original_body)
assert !original_body.respond_to?(:to_path)
assert !body.respond_to?(:to_path)
end
test "calls to_s on body parts" do
app = lambda { |env|
[200, {'Content-Type' => 'html'}, [1, 2, 3]]
}
app = ActionDispatch::StringCoercion.new(app)
parts = []
status, headers, body = app.call({})
body.each { |part| parts << part }
assert_equal %w( 1 2 3 ), parts
end
end

@ -1,22 +1,32 @@
class << Object; alias_method :const_available?, :const_defined?; end
class ContentController < ActionController::Base
end
class NotAController
end
class ContentController < ActionController::Base; end
class NotAController; end
module Admin
class << self; alias_method :const_available?, :const_defined?; end
class UserController < ActionController::Base; end
class NewsFeedController < ActionController::Base; end
end
class ElsewhereController < ActionController::Base; end
module Api
class ProductsController < ActionController::Base; end
end
# TODO: Reduce the number of test controllers we use
class AddressesController < ActionController::Base; end
class SessionsController < ActionController::Base; end
class FooController < ActionController::Base; end
class CController < ActionController::Base; end
class HiController < ActionController::Base; end
class ArticlesController < ActionController::Base; end
class BarController < ActionController::Base; end
class BooksController < ActionController::Base; end
class BraveController < ActionController::Base; end
class CController < ActionController::Base; end
class ElsewhereController < ActionController::Base; end
class FooController < ActionController::Base; end
class HiController < ActionController::Base; end
class ImageController < ActionController::Base; end
class PeopleController < ActionController::Base; end
class SessionsController < ActionController::Base; end
class SubpathBooksController < ActionController::Base; end
class WeblogController < ActionController::Base; end
# For speed test
@ -34,8 +44,3 @@ class ChannelsController < SpeedController; end
class ChannelVideosController < SpeedController; end
class LostPasswordsController < SpeedController; end
class PagesController < SpeedController; end
ActionController::Routing::Routes.draw do |map|
map.route_one 'route_one', :controller => 'elsewhere', :action => 'flash_me'
map.connect ':controller/:action/:id'
end

@ -51,3 +51,99 @@ class Store < Question
end
end
class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost)
extend ActiveModel::Naming
include ActiveModel::Conversion
alias_method :secret?, :secret
def new_record=(boolean)
@new_record = boolean
end
def new_record?
@new_record
end
attr_accessor :author
def author_attributes=(attributes); end
attr_accessor :comments
def comments_attributes=(attributes); end
attr_accessor :tags
def tags_attributes=(attributes); end
end
class Comment
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :post_id
def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end
def save; @id = 1; @post_id = 1 end
def new_record?; @id.nil? end
def to_param; @id; end
def name
@id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"
end
attr_accessor :relevances
def relevances_attributes=(attributes); end
end
class Tag
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :post_id
def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end
def save; @id = 1; @post_id = 1 end
def new_record?; @id.nil? end
def to_param; @id; end
def value
@id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"
end
attr_accessor :relevances
def relevances_attributes=(attributes); end
end
class CommentRelevance
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :comment_id
def initialize(id = nil, comment_id = nil); @id, @comment_id = id, comment_id end
def save; @id = 1; @comment_id = 1 end
def new_record?; @id.nil? end
def to_param; @id; end
def value
@id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"
end
end
class TagRelevance
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :tag_id
def initialize(id = nil, tag_id = nil); @id, @tag_id = id, tag_id end
def save; @id = 1; @tag_id = 1 end
def new_record?; @id.nil? end
def to_param; @id; end
def value
@id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"
end
end
class Author < Comment
attr_accessor :post
def post_attributes=(attributes); end
end

@ -32,7 +32,7 @@ def link(options = {})
end
test "with no update" do
assert_html link, %w(href="/blog/destroy/3" Delete\ this\ post data-remote="true")
assert_html link, %w(href="/blog/destroy/4" Delete\ this\ post data-remote="true")
end
test "basic" do
@ -46,7 +46,7 @@ def link(options = {})
end
test "with :html options" do
expected = %{<a href="/blog/destroy/3" data-custom="me" data-update-success="#posts">Delete this post</a>}
expected = %{<a href="/blog/destroy/4" data-custom="me" data-remote="true" data-update-success="#posts">Delete this post</a>}
assert_equal expected, link(:update => "#posts", :html => {"data-custom" => "me"})
end
@ -74,7 +74,7 @@ def link(options)
end
test "basic link_to_remote with :url =>" do
expected = %{<a href="/blog/destroy/3" data-update-success="#posts">Delete this post</a>}
expected = %{<a href="/blog/destroy/3" data-remote="true" data-update-success="#posts">Delete this post</a>}
assert_equal expected,
link_to_remote("Delete this post", :url => "/blog/destroy/3", :update => "#posts")
end
@ -93,7 +93,7 @@ def button(options, html = {})
def url_for(*)
"/whatnot"
end
class StandardTest < ButtonToRemoteTest
test "basic" do
button = button({:url => {:action => "whatnot"}}, {:class => "fine"})
@ -103,13 +103,12 @@ class StandardTest < ButtonToRemoteTest
end
end
end
class LegacyButtonToRemoteTest < ButtonToRemoteTest
include ActionView::Helpers::AjaxHelper::Rails2Compatibility
assert_callbacks_work do |callback|
button(callback => "undoRequestCompleted(request)")
end
end
end
end

@ -231,6 +231,11 @@ def test_javascript_include_tag_with_given_asset_id
assert_dom_equal(%(<script src="/javascripts/prototype.js?1" type="text/javascript"></script>\n<script src="/javascripts/effects.js?1" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js?1" type="text/javascript"></script>\n<script src="/javascripts/controls.js?1" type="text/javascript"></script>\n<script src="/javascripts/application.js?1" type="text/javascript"></script>), javascript_include_tag(:defaults))
end
def test_javascript_include_tag_is_html_safe
assert javascript_include_tag(:defaults).html_safe?
assert javascript_include_tag("prototype").html_safe?
end
def test_register_javascript_include_default
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'bank'
@ -285,6 +290,13 @@ def test_stylesheet_link_tag_with_missing_source
}
end
def test_stylesheet_link_tag_is_html_safe
ENV["RAILS_ASSET_ID"] = ""
assert stylesheet_link_tag('dir/file').html_safe?
assert stylesheet_link_tag('dir/other/file', 'dir/file2').html_safe?
assert stylesheet_tag('dir/file', {}).html_safe?
end
def test_custom_stylesheet_expansions
ENV["RAILS_ASSET_ID"] = ''
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :robbery => ["bank", "robber"]

@ -15,6 +15,18 @@ class ErbUtilTest < Test::Unit::TestCase
end
end
def test_html_escape_is_html_safe
escaped = h("<p>")
assert_equal "&lt;p&gt;", escaped
assert escaped.html_safe?
end
def test_html_escape_passes_html_escpe_unmodified
escaped = h("<p>".html_safe!)
assert_equal "<p>", escaped
assert escaped.html_safe?
end
def test_rest_in_ascii
(0..127).to_a.map {|int| int.chr }.each do |chr|
next if %w(& " < >).include?(chr)

@ -1,103 +1,5 @@
require 'abstract_unit'
silence_warnings do
class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost)
extend ActiveModel::Naming
include ActiveModel::Conversion
alias_method :secret?, :secret
def new_record=(boolean)
@new_record = boolean
end
def new_record?
@new_record
end
attr_accessor :author
def author_attributes=(attributes); end
attr_accessor :comments
def comments_attributes=(attributes); end
attr_accessor :tags
def tags_attributes=(attributes); end
end
class Comment
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :post_id
def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end
def save; @id = 1; @post_id = 1 end
def new_record?; @id.nil? end
def to_param; @id; end
def name
@id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"
end
attr_accessor :relevances
def relevances_attributes=(attributes); end
end
class Tag
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :post_id
def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end
def save; @id = 1; @post_id = 1 end
def new_record?; @id.nil? end
def to_param; @id; end
def value
@id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"
end
attr_accessor :relevances
def relevances_attributes=(attributes); end
end
class CommentRelevance
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :comment_id
def initialize(id = nil, comment_id = nil); @id, @comment_id = id, comment_id end
def save; @id = 1; @comment_id = 1 end
def new_record?; @id.nil? end
def to_param; @id; end
def value
@id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"
end
end
class TagRelevance
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :tag_id
def initialize(id = nil, tag_id = nil); @id, @tag_id = id, tag_id end
def save; @id = 1; @tag_id = 1 end
def new_record?; @id.nil? end
def to_param; @id; end
def value
@id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"
end
end
class Author < Comment
attr_accessor :post
def post_attributes=(attributes); end
end
end
require 'controller/fake_models'
class FormHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormHelper
@ -1072,7 +974,7 @@ class LabelledFormBuilder < ActionView::Helpers::FormBuilder
(field_helpers - %w(hidden_field)).each do |selector|
src = <<-END_SRC
def #{selector}(field, *args, &proc)
"<label for='\#{field}'>\#{field.to_s.humanize}:</label> " + super + "<br/>"
("<label for='\#{field}'>\#{field.to_s.humanize}:</label> " + super + "<br/>").html_safe!
end
END_SRC
class_eval src, __FILE__, __LINE__

@ -0,0 +1,21 @@
require 'abstract_unit'
require 'testing_sandbox'
class RawOutputHelperTest < ActionView::TestCase
tests ActionView::Helpers::RawOutputHelper
include TestingSandbox
def setup
@string = "hello"
end
test "raw returns the safe string" do
result = raw(@string)
assert_equal @string, result
assert result.html_safe?
end
test "raw handles nil values correctly" do
assert_equal "", raw(nil)
end
end

@ -1,4 +1,5 @@
require 'abstract_unit'
require 'controller/fake_models'
class Post
extend ActiveModel::Naming
@ -26,7 +27,7 @@ def test_content_tag_for
end
def test_content_tag_for_prefix
expected = %(<ul class="post" id="archived_post_45"></ul>)
expected = %(<ul class="archived_post" id="archived_post_45"></ul>)
actual = content_tag_for(:ul, @post, :archived) { }
assert_dom_equal expected, actual
end

@ -229,7 +229,7 @@ def test_render_with_layout
end
def test_render_with_nested_layout
assert_equal %(<title>title</title>\n<div id="column">column</div>\n<div id="content">content</div>\n),
assert_equal %(<title>title</title>\n\n\n<div id="column">column</div>\n<div id="content">content</div>\n),
@view.render(:file => "test/nested_layout.erb", :layout => "layouts/yield")
end

@ -39,7 +39,16 @@ def test_strip_tags
%{This is a test.\n\n\nIt no longer contains any HTML.\n}, strip_tags(
%{<title>This is <b>a <a href="" target="_blank">test</a></b>.</title>\n\n<!-- it has a comment -->\n\n<p>It no <b>longer <strong>contains <em>any <strike>HTML</strike></em>.</strong></b></p>\n}))
assert_equal "This has a here.", strip_tags("This has a <!-- comment --> here.")
[nil, '', ' '].each { |blank| assert_equal blank, strip_tags(blank) }
[nil, '', ' '].each do |blank|
stripped = strip_tags(blank)
assert_equal blank, stripped
assert stripped.html_safe? unless blank.nil?
end
assert strip_tags("<script>").html_safe?
end
def test_sanitize_is_marked_safe
assert sanitize("<html><script></script></html>").html_safe?
end
def assert_sanitized(text, expected = nil)

@ -34,6 +34,7 @@ def test_tag_options_converts_boolean_option
def test_content_tag
assert_equal "<a href=\"create\">Create</a>", content_tag("a", "Create", "href" => "create")
assert content_tag("a", "Create", "href" => "create").html_safe?
assert_equal content_tag("a", "Create", "href" => "create"),
content_tag("a", "Create", :href => "create")
end

@ -1,4 +1,5 @@
require 'abstract_unit'
require 'controller/fake_controllers'
module ActionView
class TestCase
@ -154,7 +155,7 @@ def from_test_case; 'Word!'; end
class AssertionsTest < ActionView::TestCase
def render_from_helper
form_tag('/foo') do
concat render(:text => '<ul><li>foo</li></ul>')
concat render(:text => '<ul><li>foo</li></ul>').html_safe!
end
end
helper_method :render_from_helper

@ -19,32 +19,41 @@ def link_to_person(person)
end
class PeopleHelperTest < ActionView::TestCase
def setup
super
ActionController::Routing::Routes.draw do |map|
map.people 'people', :controller => 'people', :action => 'index'
map.connect ':controller/:action/:id'
end
end
def test_title
assert_equal "<h1>Ruby on Rails</h1>", title("Ruby on Rails")
end
def test_homepage_path
assert_equal "/people", homepage_path
with_test_route_set do
assert_equal "/people", homepage_path
end
end
def test_homepage_url
assert_equal "http://test.host/people", homepage_url
with_test_route_set do
assert_equal "http://test.host/people", homepage_url
end
end
def test_link_to_person
person = mock(:name => "David")
person.class.extend ActiveModel::Naming
expects(:mocha_mock_path).with(person).returns("/people/1")
assert_equal '<a href="/people/1">David</a>', link_to_person(person)
with_test_route_set do
person = mock(:name => "David")
person.class.extend ActiveModel::Naming
expects(:mocha_mock_path).with(person).returns("/people/1")
assert_equal '<a href="/people/1">David</a>', link_to_person(person)
end
end
private
def with_test_route_set
with_routing do |set|
set.draw do |map|
map.people 'people', :controller => 'people', :action => 'index'
map.connect ':controller/:action/:id'
end
yield
end
end
end
class CrazyHelperTest < ActionView::TestCase

@ -139,7 +139,7 @@ def test_link_tag_with_back_and_no_referer
end
def test_link_tag_with_img
assert_dom_equal "<a href=\"http://www.example.com\"><img src='/favicon.jpg' /></a>", link_to("<img src='/favicon.jpg' />", "http://www.example.com")
assert_dom_equal "<a href=\"http://www.example.com\"><img src='/favicon.jpg' alt=\"Favicon\" /></a>", link_to(image_tag("/favicon.jpg"), "http://www.example.com")
end
def test_link_with_nil_html_options

@ -0,0 +1,17 @@
$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')
require 'test/unit'
require 'rbconfig'
require 'active_support/core_ext/kernel/reporting'
class TestIsolated < Test::Unit::TestCase
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
Dir["#{File.dirname(__FILE__)}/{abstract,controller,dispatch,template}/**/*_test.rb"].each do |file|
define_method("test #{file}") do
command = "#{ruby} -Ilib:test #{file}"
silence_stderr { `#{command}` }
assert_equal 0, $?.to_i, command
end
end
end

@ -0,0 +1,41 @@
require 'abstract_unit'
class SafeBufferTest < ActionView::TestCase
def setup
@buffer = ActionView::SafeBuffer.new
end
test "Should look like a string" do
assert @buffer.is_a?(String)
assert_equal "", @buffer
end
test "Should escape a raw string which is passed to them" do
@buffer << "<script>"
assert_equal "&lt;script&gt;", @buffer
end
test "Should NOT escape a safe value passed to it" do
@buffer << "<script>".html_safe!
assert_equal "<script>", @buffer
end
test "Should not mess with an innocuous string" do
@buffer << "Hello"
assert_equal "Hello", @buffer
end
test "Should not mess with a previously escape test" do
@buffer << CGI.escapeHTML("<script>")
assert_equal "&lt;script&gt;", @buffer
end
test "Should be considered safe" do
assert @buffer.html_safe?
end
test "Should return a safe buffer when calling to_s" do
new_buffer = @buffer.to_s
assert_equal ActionView::SafeBuffer, new_buffer.class
end
end

@ -1,3 +1,6 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/class/inheritable_attributes'
module ActiveModel
class MissingAttributeError < NoMethodError
end
@ -162,6 +165,7 @@ def #{matcher.method_name(attr_name)}(*args)
end
end
end
@attribute_methods_generated = true
end
def undefine_attribute_methods
@ -173,7 +177,6 @@ def undefine_attribute_methods
def generated_attribute_methods #:nodoc:
@generated_attribute_methods ||= begin
@attribute_methods_generated = true
mod = Module.new
include mod
mod
@ -219,7 +222,7 @@ def method_missing_target
end
def attribute_method_matchers #:nodoc:
@@attribute_method_matchers ||= []
read_inheritable_attribute(:attribute_method_matchers) || write_inheritable_attribute(:attribute_method_matchers, [])
end
end

@ -1,6 +1,6 @@
# You can test whether an object is compliant with the ActiveModel API by
# calling ActiveModel::Lint.test(object). It will emit a Test::Unit
# output that tells you whether your object is fully compliant, or if not,
# including ActiveModel::Lint::Tests in your TestCase. It will included
# tests that tell you whether your object is fully compliant, or if not,
# which aspects of the API are not implemented.
#
# These tests do not attempt to determine the semantic correctness of the
@ -12,36 +12,15 @@
# call to to_model. It is perfectly fine for to_model to return self.
module ActiveModel
module Lint
def self.test(object, verbosity = 2, output = STDOUT)
require "test/unit"
require "test/unit/ui/console/testrunner"
test_class = Class.new(::Test::Unit::TestCase) do
include Test
define_method(:setup) do
assert object.respond_to?(:to_model), "The object should respond_to :to_model"
@object = object.to_model
super
end
end
::Test::Unit::UI::Console::TestRunner.new(test_class, verbosity, output).start
end
module Test
def assert_boolean(name, result)
assert result == true || result == false, "#{name} should be a boolean"
end
module Tests
# valid?
# ------
#
# Returns a boolean that specifies whether the object is in a valid or invalid
# state.
def test_valid?
assert @object.respond_to?(:valid?), "The model should respond to valid?"
assert_boolean "valid?", @object.valid?
assert model.respond_to?(:valid?), "The model should respond to valid?"
assert_boolean model.valid?, "valid?"
end
# new_record?
@ -53,13 +32,13 @@ def test_valid?
# collection. If it is persisted, a form for the object will put PUTed to the
# URL for the object.
def test_new_record?
assert @object.respond_to?(:new_record?), "The model should respond to new_record?"
assert_boolean "new_record?", @object.new_record?
assert model.respond_to?(:new_record?), "The model should respond to new_record?"
assert_boolean model.new_record?, "new_record?"
end
def test_destroyed?
assert @object.respond_to?(:destroyed?), "The model should respond to destroyed?"
assert_boolean "destroyed?", @object.destroyed?
assert model.respond_to?(:destroyed?), "The model should respond to destroyed?"
assert_boolean model.destroyed?, "destroyed?"
end
# errors
@ -67,29 +46,32 @@ def test_destroyed?
#
# Returns an object that has :[] and :full_messages defined on it. See below
# for more details.
def setup
assert @object.respond_to?(:errors), "The model should respond to errors"
@errors = @object.errors
# Returns an Array of Strings that are the errors for the attribute in
# question. If localization is used, the Strings should be localized
# for the current locale. If no error is present, this method should
# return an empty Array.
def test_errors_aref
assert model.respond_to?(:errors), "The model should respond to errors"
assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
end
# This module tests the #errors object
module Errors
# Returns an Array of Strings that are the errors for the attribute in
# question. If localization is used, the Strings should be localized
# for the current locale. If no error is present, this method should
# return an empty Array.
def test_errors_aref
assert @errors[:hello].is_a?(Array), "errors#[] should return an Array"
end
# Returns an Array of all error messages for the object. Each message
# should contain information about the field, if applicable.
def test_errors_full_messages
assert @errors.full_messages.is_a?(Array), "errors#full_messages should return an Array"
end
# Returns an Array of all error messages for the object. Each message
# should contain information about the field, if applicable.
def test_errors_full_messages
assert model.respond_to?(:errors), "The model should respond to errors"
assert model.errors.full_messages.is_a?(Array), "errors#full_messages should return an Array"
end
include Errors
private
def model
assert @model.respond_to?(:to_model), "The object should respond_to to_model"
@model.to_model
end
def assert_boolean(result, name)
assert result == true || result == false, "#{name} should be a boolean"
end
end
end
end

@ -0,0 +1,46 @@
require 'cases/helper'
class ModelWithAttributes
include ActiveModel::AttributeMethods
attribute_method_suffix ''
def attributes
{ :foo => 'value of foo' }
end
private
def attribute(name)
attributes[name.to_sym]
end
end
class ModelWithAttributes2
include ActiveModel::AttributeMethods
attribute_method_suffix '_test'
end
class AttributeMethodsTest < ActiveModel::TestCase
test 'unrelated classes should not share attribute method matchers' do
assert_not_equal ModelWithAttributes.send(:attribute_method_matchers),
ModelWithAttributes2.send(:attribute_method_matchers)
end
test '#define_attribute_methods generates attribute methods' do
ModelWithAttributes.define_attribute_methods([:foo])
assert ModelWithAttributes.attribute_methods_generated?
assert ModelWithAttributes.new.respond_to?(:foo)
assert_equal "value of foo", ModelWithAttributes.new.foo
end
test '#undefine_attribute_methods removes attribute methods' do
ModelWithAttributes.define_attribute_methods([:foo])
ModelWithAttributes.undefine_attribute_methods
assert !ModelWithAttributes.attribute_methods_generated?
assert !ModelWithAttributes.new.respond_to?(:foo)
assert_raises(NoMethodError) { ModelWithAttributes.new.foo }
end
end

@ -1,15 +1,17 @@
require "cases/helper"
class TestLint < ActiveModel::TestCase
class CompliantObject
class LintTest < ActiveModel::TestCase
include ActiveModel::Lint::Tests
class CompliantModel
def to_model
self
end
def valid?() true end
def new_record?() true end
def destroyed?() true end
def errors
obj = Object.new
def obj.[](key) [] end
@ -17,34 +19,8 @@ def obj.full_messages() [] end
obj
end
end
def assert_output(object, failures, errors, *test_names)
ActiveModel::Lint.test(object, 3, output = StringIO.new)
regex = %r{#{failures} failures, #{errors} errors}
assert_match regex, output.string
test_names.each do |test_name|
assert_match test_name, output.string
end
end
def test_valid
assert_output(CompliantObject.new, 0, 0, /test_valid/)
end
def test_new_record
assert_output(CompliantObject.new, 0, 0, /test_new_record?/)
end
def test_destroyed
assert_output(CompliantObject.new, 0, 0, /test_destroyed/)
end
def test_errors_aref
assert_output(CompliantObject.new, 0, 0, /test_errors_aref/)
end
def test_errors_full_messages
assert_output(CompliantObject.new, 0, 0, /test_errors_aref/)
def setup
@model = CompliantModel.new
end
end

@ -2427,6 +2427,29 @@ def initialize(attributes = nil)
result
end
# Cloned objects have no id assigned and are treated as new records. Note that this is a "shallow" clone
# as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
# application specific and is therefore left to the application to implement according to its need.
def initialize_copy(other)
# Think the assertion which fails if the after_initialize callback goes at the end of the method is wrong. The
# deleted clone method called new which therefore called the after_initialize callback. It then went on to copy
# over the attributes. But if it's copying the attributes afterwards then it hasn't finished initializing right?
# For example in the test suite the topic model's after_initialize method sets the author_email_address to
# test@test.com. I would have thought this would mean that all cloned models would have an author email address
# of test@test.com. However the test_clone test method seems to test that this is not the case. As a result the
# after_initialize callback has to be run *before* the copying of the atrributes rather than afterwards in order
# for all tests to pass. This makes no sense to me.
callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
cloned_attributes.delete(self.class.primary_key)
@attributes = cloned_attributes
clear_aggregation_cache
@attributes_cache = {}
@new_record = true
ensure_proper_type
self.class.send(:scope, :create).each { |att, value| self.send("#{att}=", value) } if self.class.send(:scoped?, :create)
end
# Returns a String, which Action Pack uses for constructing an URL to this
# object. The default implementation returns this record's id as a String,
# or nil if this record's unsaved.
@ -2555,19 +2578,6 @@ def destroy
freeze
end
# Returns a clone of the record that hasn't been assigned an id yet and
# is treated as a new record. Note that this is a "shallow" clone:
# it copies the object's attributes only, not its associations.
# The extent of a "deep" clone is application-specific and is therefore
# left to the application to implement according to its need.
def clone
attrs = clone_attributes(:read_attribute_before_type_cast)
attrs.delete(self.class.primary_key)
record = self.class.new
record.send :instance_variable_set, '@attributes', attrs
record
end
# Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
# single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
# identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt>
@ -2831,6 +2841,21 @@ def inspect
"#<#{self.class} #{attributes_as_nice_string}>"
end
protected
def clone_attributes(reader_method = :read_attribute, attributes = {})
self.attribute_names.inject(attributes) do |attrs, name|
attrs[name] = clone_attribute_value(reader_method, name)
attrs
end
end
def clone_attribute_value(reader_method, attribute_name)
value = send(reader_method, attribute_name)
value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
value
end
private
def create_or_update
raise ReadOnlyRecord if readonly?
@ -3093,20 +3118,6 @@ def object_from_yaml(string)
return string unless string.is_a?(String) && string =~ /^---/
YAML::load(string) rescue string
end
def clone_attributes(reader_method = :read_attribute, attributes = {})
self.attribute_names.inject(attributes) do |attrs, name|
attrs[name] = clone_attribute_value(reader_method, name)
attrs
end
end
def clone_attribute_value(reader_method, attribute_name)
value = send(reader_method, attribute_name)
value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
value
end
end
Base.class_eval do

@ -3,11 +3,14 @@
module ActiveRecord
module NestedAttributes #:nodoc:
class TooManyRecords < ActiveRecordError
end
extend ActiveSupport::Concern
included do
class_inheritable_accessor :reject_new_nested_attributes_procs, :instance_writer => false
self.reject_new_nested_attributes_procs = {}
class_inheritable_accessor :nested_attributes_options, :instance_writer => false
self.nested_attributes_options = {}
end
# == Nested Attributes
@ -127,6 +130,22 @@ module NestedAttributes #:nodoc:
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
#
# Alternatively, :reject_if also accepts a symbol for using methods:
#
# class Member < ActiveRecord::Base
# has_many :posts
# accepts_nested_attributes_for :posts, :reject_if => :new_record?
# end
#
# class Member < ActiveRecord::Base
# has_many :posts
# accepts_nested_attributes_for :posts, :reject_if => :reject_posts
#
# def reject_posts(attributed)
# attributed['title].blank?
# end
# end
#
# If the hash contains an <tt>id</tt> key that matches an already
# associated record, the matching record will be modified:
#
@ -179,13 +198,20 @@ module ClassMethods
# <tt>_destroy</tt> key and a value that evaluates to +true+
# (eg. 1, '1', true, or 'true'). This option is off by default.
# [:reject_if]
# Allows you to specify a Proc that checks whether a record should be
# built for a certain attribute hash. The hash is passed to the Proc
# and the Proc should return either +true+ or +false+. When no Proc
# is specified a record will be built for all attribute hashes that
# Allows you to specify a Proc or a Symbol pointing to a method
# that checks whether a record should be built for a certain attribute
# hash. The hash is passed to the supplied Proc or the method
# and it should return either +true+ or +false+. When no :reject_if
# is specified, a record will be built for all attribute hashes that
# do not have a <tt>_destroy</tt> value that evaluates to true.
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
# that will reject a record where all the attributes are blank.
# [:limit]
# Allows you to specify the maximum number of the associated records that
# can be processes with the nested attributes. If the size of the
# nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
# exception is raised. If omitted, any number associations can be processed.
# Note that the :limit option is only applicable to one-to-many associations.
#
# Examples:
# # creates avatar_attributes=
@ -197,7 +223,7 @@ module ClassMethods
def accepts_nested_attributes_for(*attr_names)
options = { :allow_destroy => false }
options.update(attr_names.extract_options!)
options.assert_valid_keys(:allow_destroy, :reject_if)
options.assert_valid_keys(:allow_destroy, :reject_if, :limit)
attr_names.each do |association_name|
if reflection = reflect_on_association(association_name)
@ -210,10 +236,10 @@ def accepts_nested_attributes_for(*attr_names)
reflection.options[:autosave] = true
self.reject_new_nested_attributes_procs[association_name.to_sym] = if options[:reject_if] == :all_blank
proc { |attributes| attributes.all? {|k,v| v.blank?} }
else
options[:reject_if]
self.nested_attributes_options[association_name.to_sym] = options
if options[:reject_if] == :all_blank
self.nested_attributes_options[association_name.to_sym][:reject_if] = proc { |attributes| attributes.all? {|k,v| v.blank?} }
end
# def pirate_attributes=(attributes)
@ -221,7 +247,7 @@ def accepts_nested_attributes_for(*attr_names)
# end
class_eval %{
def #{association_name}_attributes=(attributes)
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, #{options[:allow_destroy]})
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
end
}, __FILE__, __LINE__
else
@ -265,8 +291,9 @@ def _delete #:nodoc:
# If the given attributes include a matching <tt>:id</tt> attribute _and_ a
# <tt>:_destroy</tt> key set to a truthy value, then the existing record
# will be marked for destruction.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes, allow_destroy)
attributes = attributes.stringify_keys
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
options = self.nested_attributes_options[association_name]
attributes = attributes.with_indifferent_access
if attributes['id'].blank?
unless reject_new_record?(association_name, attributes)
@ -278,7 +305,7 @@ def assign_nested_attributes_for_one_to_one_association(association_name, attrib
end
end
elsif (existing_record = send(association_name)) && existing_record.id.to_s == attributes['id'].to_s
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
end
end
@ -309,24 +336,30 @@ def assign_nested_attributes_for_one_to_one_association(association_name, attrib
# { :name => 'John' },
# { :id => '2', :_destroy => true }
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection, allow_destroy)
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
options = self.nested_attributes_options[association_name]
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
end
if options[:limit] && attributes_collection.size > options[:limit]
raise TooManyRecords, "Maximum #{options[:limit]} records are allowed. Got #{attributes_collection.size} records instead."
end
if attributes_collection.is_a? Hash
attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes }
end
attributes_collection.each do |attributes|
attributes = attributes.stringify_keys
attributes = attributes.with_indifferent_access
if attributes['id'].blank?
unless reject_new_record?(association_name, attributes)
send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS))
end
elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes['id'].to_s }
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
end
end
end
@ -351,8 +384,18 @@ def has_destroy_flag?(hash)
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
# association and evaluates to +true+.
def reject_new_record?(association_name, attributes)
has_destroy_flag?(attributes) ||
self.class.reject_new_nested_attributes_procs[association_name].try(:call, attributes)
has_destroy_flag?(attributes) || call_reject_if(association_name, attributes)
end
def call_reject_if(association_name, attributes)
callback = self.nested_attributes_options[association_name][:reject_if]
case callback
when Symbol
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
when Proc
callback.try(:call, attributes)
end
end
end
end

@ -1352,7 +1352,7 @@ def test_clone
cloned_topic.title["a"] = "c"
assert_equal "b", topic.title["a"]
#test if attributes set as part of after_initialize are cloned correctly
# test if attributes set as part of after_initialize are cloned correctly
assert_equal topic.author_email_address, cloned_topic.author_email_address
# test if saved clone object differs from original

@ -29,13 +29,13 @@ def teardown
Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
end
def test_base_should_have_an_empty_reject_new_nested_attributes_procs
assert_equal Hash.new, ActiveRecord::Base.reject_new_nested_attributes_procs
def test_base_should_have_an_empty_nested_attributes_options
assert_equal Hash.new, ActiveRecord::Base.nested_attributes_options
end
def test_should_add_a_proc_to_reject_new_nested_attributes_procs
def test_should_add_a_proc_to_nested_attributes_options
[:parrots, :birds, :birds_with_reject_all_blank].each do |name|
assert_instance_of Proc, Pirate.reject_new_nested_attributes_procs[name]
assert_instance_of Proc, Pirate.nested_attributes_options[name][:reject_if]
end
end
@ -84,6 +84,34 @@ def test_underscore_delete_is_deprecated
ship = Ship.create!(:name => 'Nights Dirty Lightning')
ship._delete
end
def test_reject_if_method_without_arguments
Pirate.accepts_nested_attributes_for :ship, :reject_if => :new_record?
pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
pirate.ship_attributes = { :name => 'Black Pearl' }
assert_no_difference('Ship.count') { pirate.save! }
end
def test_reject_if_method_with_arguments
Pirate.accepts_nested_attributes_for :ship, :reject_if => :reject_empty_ships_on_create
pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true }
assert_no_difference('Ship.count') { pirate.save! }
# pirate.reject_empty_ships_on_create returns false for saved records
pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true }
assert_difference('Ship.count') { pirate.save! }
end
def test_reject_if_with_indifferent_keys
Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| attributes[:name].blank? }
pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
pirate.ship_attributes = { :name => 'Hello Pearl' }
assert_difference('Ship.count') { pirate.save! }
end
end
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
@ -575,3 +603,33 @@ def setup
include NestedAttributesOnACollectionAssociationTests
end
class TestNestedAttributesLimit < ActiveRecord::TestCase
def setup
Pirate.accepts_nested_attributes_for :parrots, :limit => 2
@pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
end
def teardown
Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
end
def test_limit_with_less_records
@pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Big Big Love' } } }
assert_difference('Parrot.count') { @pirate.save! }
end
def test_limit_with_number_exact_records
@pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' }, 'bar' => { :name => 'Blown Away' } } }
assert_difference('Parrot.count', 2) { @pirate.save! }
end
def test_limit_with_exceeding_records
assert_raises(ActiveRecord::NestedAttributes::TooManyRecords) do
@pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' },
'bar' => { :name => 'Blown Away' },
'car' => { :name => 'The Happening' }} }
end
end
end

@ -45,6 +45,10 @@ def ship_log
@ship_log ||= []
end
def reject_empty_ships_on_create(attributes)
attributes.delete('_reject_me_if_new').present? && new_record?
end
private
def log_before_add(record)
log(record, "before_adding_method")

Some files were not shown because too many files have changed in this diff Show More