Merge commit 'rails/master'

This commit is contained in:
Xavier Noria 2010-03-31 07:47:58 -07:00
commit 824fa10f4d
146 changed files with 1043 additions and 437 deletions

3
.gitignore vendored

@ -15,7 +15,6 @@ activesupport/test/fixtures/isolation_test
railties/test/500.html
railties/test/fixtures/tmp
railties/test/initializer/root/log
railties/doc/guides/html/images
railties/doc/guides/html/stylesheets
railties/doc
railties/guides/output
railties/tmp

@ -11,6 +11,8 @@ group :mri do
if RUBY_VERSION < '1.9'
gem "system_timer"
gem "ruby-debug", ">= 0.10.3"
elsif RUBY_VERSION < '1.9.2'
gem "ruby-debug19"
end
end

@ -72,8 +72,8 @@ Rake::RDocTask.new do |rdoc|
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,generators/*.rb}')
rdoc.rdoc_files.exclude('railties/lib/vendor/*')
rdoc.rdoc_files.include('railties/lib/**/*.rb')
rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/*')
rdoc.rdoc_files.include('activerecord/README')
rdoc.rdoc_files.include('activerecord/CHANGELOG')
@ -88,6 +88,7 @@ Rake::RDocTask.new do |rdoc|
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_dispatch/**/*.rb')
rdoc.rdoc_files.include('actionpack/lib/action_view/**/*.rb')
rdoc.rdoc_files.exclude('actionpack/lib/action_controller/vendor/*')

@ -20,6 +20,6 @@
s.has_rdoc = true
s.add_dependency('actionpack', version)
s.add_dependency('mail', '~> 2.1.3')
s.add_dependency('mail', '~> 2.1.5.3')
s.add_dependency('text-format', '~> 1.0.0')
end

@ -2,6 +2,7 @@
require 'action_mailer/tmail_compat'
require 'action_mailer/collector'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
module ActionMailer #:nodoc:
# Action Mailer allows you to send email from your application using a mailer model and views.
@ -290,7 +291,7 @@ class Base < AbstractController::Base
:parts_order => [ "text/plain", "text/enriched", "text/html" ]
}.freeze
ActionMailer.run_base_hooks(self)
ActiveSupport.run_load_hooks(:action_mailer, self)
class << self

@ -1,4 +1,5 @@
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/blank'
module ActionMailer
module OldApi #:nodoc:

@ -6,18 +6,18 @@ class Railtie < Rails::Railtie
config.action_mailer = ActiveSupport::OrderedOptions.new
initializer "action_mailer.url_for", :before => :load_environment_config do |app|
ActionMailer.base_hook { include app.routes.url_helpers }
ActiveSupport.on_load(:action_mailer) { include app.routes.url_helpers }
end
require "action_mailer/railties/log_subscriber"
log_subscriber :action_mailer, ActionMailer::Railties::LogSubscriber.new
initializer "action_mailer.logger" do
ActionMailer.base_hook { self.logger ||= Rails.logger }
ActiveSupport.on_load(:action_mailer) { self.logger ||= Rails.logger }
end
initializer "action_mailer.set_configs" do |app|
ActionMailer.base_hook do
ActiveSupport.on_load(:action_mailer) do
app.config.action_mailer.each do |k,v|
send "#{k}=", v
end

@ -39,11 +39,18 @@ def attachment_with_content(hash = {})
end
def attachment_with_hash
attachments['invoice.jpg'] = { :data => "you smiling", :mime_type => "image/x-jpg",
attachments['invoice.jpg'] = { :data => "\312\213\254\232)b",
:mime_type => "image/x-jpg",
:transfer_encoding => "base64" }
mail
end
def attachment_with_hash_default_encoding
attachments['invoice.jpg'] = { :data => "\312\213\254\232)b",
:mime_type => "image/x-jpg" }
mail
end
def implicit_multipart(hash = {})
attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments)
mail(hash)
@ -137,7 +144,7 @@ def different_layout(layout_name='')
:date => @time)
assert_equal(['bcc@test.lindsaar.net'], email.bcc)
assert_equal(['cc@test.lindsaar.net'], email.cc)
assert_equal('multipart/mixed', email.content_type)
assert_equal('multipart/mixed; charset=iso-8559-1', email.content_type)
assert_equal('iso-8559-1', email.charset)
assert_equal('2.0', email.mime_version)
assert_equal(['reply-to@test.lindsaar.net'], email.reply_to)
@ -206,6 +213,15 @@ def different_layout(layout_name='')
assert_equal expected, email.attachments['invoice.jpg'].decoded
end
test "attachment with hash using default mail encoding" do
email = BaseMailer.attachment_with_hash_default_encoding
assert_equal(1, email.attachments.length)
assert_equal('invoice.jpg', email.attachments[0].filename)
expected = "\312\213\254\232)b"
expected.force_encoding(Encoding::BINARY) if '1.9'.respond_to?(:force_encoding)
assert_equal expected, email.attachments['invoice.jpg'].decoded
end
test "sets mime type to multipart/mixed when attachment is included" do
email = BaseMailer.attachment_with_content
assert_equal(1, email.attachments.length)

@ -784,7 +784,7 @@ def test_utf8_body_is_not_quoted
expected.date = Time.local 2004, 12, 12
created = TestMailer.utf8_body @recipient
assert_match(/åœö blah/, created.encoded)
assert_match(/åœö blah/, created.decoded)
end
def test_multiple_utf8_recipients
@ -1019,8 +1019,8 @@ def test_decode_message_with_unknown_charset
def test_empty_header_values_omitted
result = TestMailer.unnamed_attachment(@recipient).encoded
assert_match %r{Content-Type: application/octet-stream;}, result
assert_match %r{Content-Disposition: attachment;}, result
assert_match %r{Content-Type: application/octet-stream}, result
assert_match %r{Content-Disposition: attachment}, result
end
def test_headers_with_nonalpha_chars

@ -64,8 +64,7 @@ def self.filter_parameter_logging(*args, &block)
filter
end
ActionController.run_base_hooks(self)
ActiveSupport.run_load_hooks(:action_controller, self)
end
end

@ -66,6 +66,18 @@ def ip_spoofing_check
Rails.application.config.action_dispatch.ip_spoofing_check
end
def cookie_verifier_secret=(value)
ActiveSupport::Deprecation.warn "ActionController::Base.cookie_verifier_secret= is deprecated. " <<
"Please configure it on your application with config.cookie_secret=", caller
ActionController::Base.config.secret = value
end
def cookie_verifier_secret
ActiveSupport::Deprecation.warn "ActionController::Base.cookie_verifier_secret is deprecated. " <<
"Please use ActionController::Base.config.secret instead.", caller
ActionController::Base.config.secret
end
def trusted_proxies=(value)
ActiveSupport::Deprecation.warn "ActionController::Base.trusted_proxies= is deprecated. " <<
"Please configure it on your application with config.action_dispatch.trusted_proxies=", caller

@ -6,7 +6,6 @@ module Cookies
included do
helper_method :cookies
cattr_accessor :cookie_verifier_secret
end
private

@ -1,4 +1,5 @@
require 'active_support/base64'
require 'active_support/core_ext/object/blank'
module ActionController
module HttpAuthentication

@ -1,4 +1,5 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/blank'
module ActionController
def self.add_renderer(key, &block)

@ -41,7 +41,7 @@ class Railtie < Rails::Railtie
log_subscriber :action_controller, ActionController::Railties::LogSubscriber.new
initializer "action_controller.logger" do
ActionController.base_hook { self.logger ||= Rails.logger }
ActiveSupport.on_load(:action_controller) { self.logger ||= Rails.logger }
end
initializer "action_controller.set_configs" do |app|
@ -53,23 +53,23 @@ class Railtie < Rails::Railtie
ac.stylesheets_dir = paths.public.stylesheets.to_a.first
ac.secret = app.config.cookie_secret
ActionController.base_hook do
ActiveSupport.on_load(:action_controller) do
self.config.merge!(ac)
end
end
initializer "action_controller.initialize_framework_caches" do
ActionController.base_hook { self.cache_store ||= RAILS_CACHE }
ActiveSupport.on_load(:action_controller) { self.cache_store ||= RAILS_CACHE }
end
initializer "action_controller.set_helpers_path" do |app|
ActionController.base_hook do
ActiveSupport.on_load(:action_controller) do
self.helpers_path = app.config.paths.app.helpers.to_a
end
end
initializer "action_controller.url_helpers" do |app|
ActionController.base_hook do
ActiveSupport.on_load(:action_controller) do
extend ::ActionController::Railties::UrlHelpers.with(app.routes)
end

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActionController
module Railties
class LogSubscriber < Rails::LogSubscriber

@ -1,11 +1,11 @@
module ActionController
module Railties
module UrlHelpers
def self.with(router)
def self.with(routes)
Module.new do
define_method(:inherited) do |klass|
super(klass)
klass.send(:include, router.url_helpers)
klass.send(:include, routes.url_helpers)
end
end
end

@ -76,8 +76,8 @@ def dom_id(record, prefix = nil)
# method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
# make sure yourself that your dom ids are valid, in case you overwrite this method.
def record_key_for_dom_id(record)
return record.id unless record.respond_to?(:to_model)
key = record.to_model.to_key
record = record.to_model if record.respond_to?(:to_model)
key = record.to_key
key ? sanitize_dom_id(key.join('_')) : key
end

@ -1,4 +1,5 @@
require 'rack/session/abstract/id'
require 'active_support/core_ext/object/blank'
module ActionController
module TemplateAssertions
@ -117,9 +118,9 @@ def self.new_escaped(strings)
end
end
def assign_parameters(router, controller_path, action, parameters = {})
def assign_parameters(routes, controller_path, action, parameters = {})
parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
extra_keys = router.extra_keys(parameters)
extra_keys = routes.extra_keys(parameters)
non_path_parameters = get? ? query_parameters : request_parameters
parameters.each do |key, value|
if value.is_a? Fixnum
@ -321,7 +322,7 @@ def xml_http_request(request_method, action, parameters = nil, session = nil, fl
def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
# Sanity check for required instance variables so we can give an
# understandable error message.
%w(@router @controller @request @response).each do |iv_name|
%w(@routes @controller @request @response).each do |iv_name|
if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
end
@ -337,7 +338,7 @@ def process(action, parameters = nil, session = nil, flash = nil, http_method =
@request.env['REQUEST_METHOD'] = http_method
parameters ||= {}
@request.assign_parameters(@router, @controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)
@request.assign_parameters(@routes, @controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)
@request.session = ActionController::TestSession.new(session) unless session.nil?
@request.session["flash"] = @request.flash.update(flash || {})
@ -446,7 +447,7 @@ def build_request_uri(action, parameters)
:relative_url_root => nil,
:_path_segments => @request.symbolized_path_parameters)
url, query_string = @router.url_for(options).split("?", 2)
url, query_string = @routes.url_for(options).split("?", 2)
@request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
@request.env["PATH_INFO"] = url

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActionDispatch
module Http
module Cache

@ -1,5 +1,6 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/object/duplicable'
module ActionDispatch
module Http

@ -5,7 +5,7 @@ module MimeNegotiation
#
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
def content_mime_type
@env["action_dispatch.request.content_type"] ||= begin
if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
Mime::Type.lookup($1.strip.downcase)
@ -15,13 +15,17 @@ def content_type
end
end
def content_type
content_mime_type && content_mime_type.to_s
end
# Returns the accepted MIME type for the request.
def accepts
@env["action_dispatch.request.accepts"] ||= begin
header = @env['HTTP_ACCEPT'].to_s.strip
if header.empty?
[content_type]
[content_mime_type]
else
Mime::Type.parse(header)
end

@ -1,5 +1,6 @@
require 'set'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/object/blank'
module Mime
class Mimes < Array

@ -96,11 +96,11 @@ def headers
end
def forgery_whitelisted?
method == :get || xhr? || content_type.nil? || !content_type.verify_request?
method == :get || xhr? || content_mime_type.nil? || !content_mime_type.verify_request?
end
def media_type
content_type.to_s
content_mime_type.to_s
end
# Returns the content length of the request as an integer.
@ -157,7 +157,7 @@ def body
end
def form_data?
FORM_DATA_MEDIA_TYPES.include?(content_type.to_s)
FORM_DATA_MEDIA_TYPES.include?(content_mime_type.to_s)
end
def body_stream #:nodoc:

@ -1,5 +1,6 @@
require 'digest/md5'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/blank'
module ActionDispatch # :nodoc:
# Represents an HTTP response generated by a controller action. One can use

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActionDispatch
module Http
module UploadedFile

@ -168,12 +168,12 @@ def method_missing(method, *arguments, &block)
class SignedCookieJar < CookieJar #:nodoc:
def initialize(parent_jar)
unless ActionController::Base.cookie_verifier_secret
raise "You must set ActionController::Base.cookie_verifier_secret to use signed cookies"
unless ActionController::Base.config.secret
raise "You must set ActionController::Base.config.secret"
end
@parent_jar = parent_jar
@verifier = ActiveSupport::MessageVerifier.new(ActionController::Base.cookie_verifier_secret)
@verifier = ActiveSupport::MessageVerifier.new(ActionController::Base.config.secret)
end
def [](name)

@ -25,7 +25,9 @@ def parse_formatted_parameters(env)
return false if request.content_length.zero?
mime_type = content_type_from_legacy_post_data_format_header(env) || request.content_type
mime_type = content_type_from_legacy_post_data_format_header(env) ||
request.content_mime_type
strategy = @parsers[mime_type]
return false unless strategy
@ -53,7 +55,7 @@ def parse_formatted_parameters(env)
raise
{ "body" => request.raw_post,
"content_type" => request.content_type,
"content_type" => request.content_mime_type,
"content_length" => request.content_length,
"exception" => "#{e.message} (#{e.class})",
"backtrace" => e.backtrace }

@ -1,5 +1,6 @@
require 'rack/utils'
require 'rack/request'
require 'active_support/core_ext/object/blank'
module ActionDispatch
module Session

@ -1,4 +1,5 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/object/blank'
module ActionDispatch
module Session

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActionDispatch
module Routing
class RouteSet

@ -1,4 +1,5 @@
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/blank'
module ActionDispatch
module Routing
@ -258,6 +259,7 @@ def initialize(*args) #:nodoc:
def scope(*args)
options = args.extract_options!
options = options.dup
case args.first
when String
@ -423,8 +425,13 @@ def member_name
singular
end
# Checks for uncountable plurals, and appends "_index" if they're.
def collection_name
plural
uncountable? ? "#{plural}_index" : plural
end
def uncountable?
singular == plural
end
def name_for_action(action)
@ -439,6 +446,32 @@ def name_for_action(action)
def id_segment
":#{singular}_id"
end
def constraints
options[:constraints] || {}
end
def id_constraint?
options[:id] && options[:id].is_a?(Regexp) || constraints[:id] && constraints[:id].is_a?(Regexp)
end
def id_constraint
options[:id] || constraints[:id]
end
def collection_options
(options || {}).dup.tap do |options|
options.delete(:id)
options[:constraints] = options[:constraints].dup if options[:constraints]
options[:constraints].delete(:id) if options[:constraints].is_a?(Hash)
end
end
def nested_options
options = { :name_prefix => member_name }
options["#{singular}_id".to_sym] = id_constraint if id_constraint?
options
end
end
class SingletonResource < Resource #:nodoc:
@ -483,6 +516,7 @@ def resource(*resources, &block)
yield if block_given?
end
scope(resource.options) do
get :show if resource.actions.include?(:show)
post :create if resource.actions.include?(:create)
put :update if resource.actions.include?(:update)
@ -491,6 +525,7 @@ def resource(*resources, &block)
get :edit, :as => resource.name if resource.actions.include?(:edit)
end
end
end
self
end
@ -509,13 +544,16 @@ def resources(*resources, &block)
yield if block_given?
with_scope_level(:collection) do
scope(resource.collection_options) do
get :index if resource.actions.include?(:index)
post :create if resource.actions.include?(:create)
get :new, :as => resource.singular if resource.actions.include?(:new)
end
end
with_scope_level(:member) do
scope(':id') do
scope(resource.options) do
get :show if resource.actions.include?(:show)
put :update if resource.actions.include?(:update)
delete :destroy if resource.actions.include?(:destroy)
@ -524,6 +562,7 @@ def resources(*resources, &block)
end
end
end
end
self
end
@ -558,7 +597,7 @@ def nested
end
with_scope_level(:nested) do
scope(parent_resource.id_segment, :name_prefix => parent_resource.member_name) do
scope(parent_resource.id_segment, parent_resource.nested_options) do
yield
end
end

@ -80,7 +80,7 @@ def assert_generates(expected_path, options, defaults={}, extras = {}, message=n
expected_path = "/#{expected_path}" unless expected_path[0] == ?/
# Load routes.rb if it hasn't been loaded.
generated_path, extra_keys = @router.generate_extras(options, defaults)
generated_path, extra_keys = @routes.generate_extras(options, defaults)
found_extras = options.reject {|k, v| ! extra_keys.include? k}
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
@ -125,7 +125,7 @@ def assert_routing(path, options, defaults={}, extras={}, message=nil)
end
# A helper to make it easier to test different route configurations.
# This method temporarily replaces @router
# This method temporarily replaces @routes
# with a new RouteSet instance.
#
# The new instance is yielded to the passed block. Typically the block
@ -142,9 +142,9 @@ def assert_routing(path, options, defaults={}, extras={}, message=nil)
# end
#
def with_routing
old_routes, @router = @router, ActionDispatch::Routing::RouteSet.new
old_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new
old_controller, @controller = @controller, @controller.clone if @controller
_router = @router
_routes = @routes
# Unfortunately, there is currently an abstraction leak between AC::Base
# and AV::Base which requires having the URL helpers in both AC and AV.
@ -153,14 +153,14 @@ def with_routing
#
# TODO: Make this unnecessary
if @controller
@controller.singleton_class.send(:include, _router.url_helpers)
@controller.singleton_class.send(:include, _routes.url_helpers)
@controller.view_context_class = Class.new(@controller.view_context_class) do
include _router.url_helpers
include _routes.url_helpers
end
end
yield @router
yield @routes
ensure
@router = old_routes
@routes = old_routes
if @controller
@controller = old_controller
end
@ -168,7 +168,7 @@ def with_routing
# ROUTES TODO: These assertions should really work in an integration context
def method_missing(selector, *args, &block)
if @controller && @router.named_routes.helpers.include?(selector)
if @controller && @routes && @routes.named_routes.helpers.include?(selector)
@controller.send(selector, *args, &block)
else
super
@ -185,7 +185,7 @@ def recognized_request_for(path, request_method = nil)
request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
request.path = path
params = @router.recognize_path(path, { :method => request.method })
params = @routes.recognize_path(path, { :method => request.method })
request.path_parameters = params.with_indifferent_access
request

@ -1,7 +1,7 @@
require 'active_support/testing/performance'
require 'active_support/testing/default'
if defined?(ActiveSupport::Testing::Performance)
begin
module ActionDispatch
# An integration test that runs a code profiler on your test methods.
# Profiling output for combinations of each test method, measurement, and
@ -14,4 +14,6 @@ class PerformanceTest < ActionDispatch::IntegrationTest
include ActiveSupport::Testing::Default
end
end
rescue NameError
$stderr.puts "Specify ruby-prof as application's dependency in Gemfile to run benchmarks."
end

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActionDispatch
class TestRequest < Request
DEFAULT_ENV = Rack::MockRequest.env_for('/')

@ -173,7 +173,7 @@ class << self
delegate :logger, :to => 'ActionController::Base', :allow_nil => true
end
ActionView.run_base_hooks(self)
ActiveSupport.run_load_hooks(:action_view, self)
attr_accessor :base_path, :assigns, :template_extension, :lookup_context
attr_internal :captures, :request, :controller, :template, :config

@ -6,7 +6,7 @@
require 'active_support/core_ext/object/blank'
module ActionView
ActionView.base_hook do
ActiveSupport.on_load(:action_view) do
class ActionView::Base
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>".html_safe }
cattr_accessor :field_error_proc
@ -97,10 +97,10 @@ def form(record_name, options = {})
end
# Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
# This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a <tt>:prepend_text</tt>
# and/or <tt>:append_text</tt> (to properly explain the error), and a <tt>:css_class</tt> to style it
# accordingly. +object+ should either be the name of an instance variable or the actual object. The method can be
# passed in either as a string or a symbol.
# This error message is wrapped in a <tt>DIV</tt> tag by default or with <tt>:html_tag</tt> if specified,
# which can be extended to include a <tt>:prepend_text</tt> and/or <tt>:append_text</tt> (to properly explain
# the error), and a <tt>:css_class</tt> to style it accordingly. +object+ should either be the name of an
# instance variable or the actual object. The method can be passed in either as a string or a symbol.
# As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute:
#
# <%= error_message_on "post", "title" %>
@ -112,25 +112,28 @@ def form(record_name, options = {})
# <%= error_message_on "post", "title",
# :prepend_text => "Title simply ",
# :append_text => " (or it won't work).",
# :html_tag => "span",
# :css_class => "inputError" %>
# # => <span class="inputError">Title simply can't be empty (or it won't work).</span>
def error_message_on(object, method, *args)
options = args.extract_options!
unless args.empty?
ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate ' +
'prepend_text, append_text, and css_class arguments', caller)
'prepend_text, append_text, html_tag, and css_class arguments', caller)
options[:prepend_text] = args[0] || ''
options[:append_text] = args[1] || ''
options[:css_class] = args[2] || 'formError'
options[:html_tag] = args[2] || 'div'
options[:css_class] = args[3] || 'formError'
end
options.reverse_merge!(:prepend_text => '', :append_text => '', :css_class => 'formError')
options.reverse_merge!(:prepend_text => '', :append_text => '', :html_tag => 'div', :css_class => 'formError')
object = convert_to_model(object)
if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
(errors = obj.errors[method]).presence
content_tag("div",
"#{options[:prepend_text]}#{ERB::Util.h(errors.first)}#{options[:append_text]}".html_safe,
content_tag(options[:html_tag],
(options[:prepend_text].html_safe << errors.first).safe_concat(options[:append_text]),
:class => options[:css_class]
)
else

@ -108,7 +108,7 @@ module Helpers #:nodoc:
# "http://asset%d.example.com", "https://asset1.example.com"
# )
#
# === Using asset timestamps
# === Customizing the asset path
#
# By default, Rails appends asset's timestamps to all asset paths. This allows
# you to set a cache-expiration date for the asset far into the future, but
@ -133,6 +133,65 @@ module Helpers #:nodoc:
# will request the same assets over and over again even thought they didn't
# change. You can use something like Live HTTP Headers for Firefox to verify
# that the cache is indeed working.
#
# This strategy works well enough for most server setups and requires the
# least configuration, but if you deploy several application servers at
# different times - say to handle a temporary spike in load - then the
# asset time stamps will be out of sync. In a setup like this you may want
# to set the way that asset paths are generated yourself.
#
# Altering the asset paths that Rails generates can be done in two ways.
# The easiest is to define the RAILS_ASSET_ID environment variable. The
# contents of this variable will always be used in preference to
# calculated timestamps. A more complex but flexible way is to set
# <tt>ActionController::Base.config.asset_path</tt> to a proc
# that takes the unmodified asset path and returns the path needed for
# your asset caching to work. Typically you'd do something like this in
# <tt>config/environments/production.rb</tt>:
#
# # Normally you'd calculate RELEASE_NUMBER at startup.
# RELEASE_NUMBER = 12345
# config.action_controller.asset_path_template = proc { |asset_path|
# "/release-#{RELEASE_NUMBER}#{asset_path}"
# }
#
# This example would cause the following behaviour on all servers no
# matter when they were deployed:
#
# image_tag("rails.png")
# # => <img alt="Rails" src="/release-12345/images/rails.png" />
# stylesheet_link_tag("application")
# # => <link href="/release-12345/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
# Changing the asset_path does require that your web servers have
# knowledge of the asset template paths that you rewrite to so it's not
# suitable for out-of-the-box use. To use the example given above you
# could use something like this in your Apache VirtualHost configuration:
#
# <LocationMatch "^/release-\d+/(images|javascripts|stylesheets)/.*$">
# # Some browsers still send conditional-GET requests if there's a
# # Last-Modified header or an ETag header even if they haven't
# # reached the expiry date sent in the Expires header.
# Header unset Last-Modified
# Header unset ETag
# FileETag None
#
# # Assets requested using a cache-busting filename should be served
# # only once and then cached for a really long time. The HTTP/1.1
# # spec frowns on hugely-long expiration times though and suggests
# # that assets which never expire be served with an expiration date
# # 1 year from access.
# ExpiresActive On
# ExpiresDefault "access plus 1 year"
# </LocationMatch>
#
# # We use cached-busting location names with the far-future expires
# # headers to ensure that if a file does change it can force a new
# # request. The actual asset filenames are still the same though so we
# # need to rewrite the location from the cache-busting location to the
# # real asset location so that we can serve it.
# RewriteEngine On
# RewriteRule ^/release-\d+/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L]
module AssetTagHelper
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls', 'rails'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
@ -646,7 +705,7 @@ def compute_public_path(source, dir, ext = nil, include_host = true)
source += ".#{ext}" if rewrite_extension?(source, dir, ext)
source = "/#{dir}/#{source}" unless source[0] == ?/
source = rewrite_asset_path(source)
source = rewrite_asset_path(source, config.asset_path)
has_request = controller.respond_to?(:request)
if has_request && include_host && source !~ %r{^#{controller.config.relative_url_root}/}
@ -710,7 +769,13 @@ def rails_asset_id(source)
# Break out the asset path rewrite in case plugins wish to put the asset id
# someplace other than the query string.
def rewrite_asset_path(source)
def rewrite_asset_path(source, path = nil)
if path && path.respond_to?(:call)
return path.call(source)
elsif path && path.is_a?(String)
return path % [source]
end
asset_id = rails_asset_id(source)
if asset_id.blank?
source

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActionView
module Helpers
# CaptureHelper exposes methods to let you extract generated markup which

@ -4,6 +4,7 @@
require 'action_view/helpers/form_tag_helper'
require 'active_support/core_ext/class/inheritable_attributes'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/blank'
module ActionView
module Helpers
@ -1220,7 +1221,7 @@ def nested_child_index(name)
end
end
ActionView.base_hook do
ActiveSupport.on_load(:action_view) do
class ActionView::Base
cattr_accessor :default_form_builder
@@default_form_builder = ::ActionView::Helpers::FormBuilder

@ -1,6 +1,7 @@
require 'cgi'
require 'erb'
require 'action_view/helpers/form_helper'
require 'active_support/core_ext/object/blank'
module ActionView
module Helpers

@ -1,6 +1,7 @@
require 'cgi'
require 'action_view/helpers/tag_helper'
require 'active_support/core_ext/object/returning'
require 'active_support/core_ext/object/blank'
module ActionView
module Helpers

@ -1,5 +1,6 @@
require 'active_support/core_ext/big_decimal/conversions'
require 'active_support/core_ext/float/rounding'
require 'active_support/core_ext/object/blank'
module ActionView
module Helpers #:nodoc:

@ -1,6 +1,7 @@
require 'set'
require 'active_support/json'
require 'active_support/core_ext/object/returning'
require 'active_support/core_ext/object/blank'
module ActionView
module Helpers

@ -1,3 +1,4 @@
require 'active_support/core_ext/object/blank'
require 'set'
module ActionView

@ -1,3 +1,4 @@
require 'active_support/core_ext/object/blank'
require 'action_view/helpers/tag_helper'
module ActionView

@ -10,14 +10,14 @@ class Railtie < Rails::Railtie
initializer "action_view.cache_asset_timestamps" do |app|
unless app.config.cache_classes
ActionView.base_hook do
ActiveSupport.on_load(:action_view) do
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
end
end
end
initializer "action_view.set_configs" do |app|
ActionView.base_hook do
ActiveSupport.on_load(:action_view) do
app.config.action_view.each do |k,v|
send "#{k}=", v
end

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActionView
# There's also a convenience method for rendering sub templates within the current controller that depends on a
# single object (we call this kind of sub templates for partials). It relies on the fact that partials should

@ -35,12 +35,7 @@ def find_templates(name, prefix, partial, details)
def cached(key, prefix, name, partial)
return yield unless key && caching?
scope = @cached[key][prefix][name]
if scope.key?(partial)
scope[partial]
else
scope[partial] = yield
end
@cached[key][prefix][name][partial] ||= yield
end
end

@ -1,3 +1,4 @@
require 'active_support/core_ext/object/blank'
require 'action_controller/test_case'
require 'action_view'

@ -95,7 +95,7 @@ class TestCase
map.connect ':controller/:action/:id'
end
ActionController::IntegrationTest.app.router.draw do |map|
ActionController::IntegrationTest.app.routes.draw do |map|
# FIXME: match ':controller(/:action(/:id))'
map.connect ':controller/:action/:id'
end
@ -104,12 +104,11 @@ class TestCase
end
class RoutedRackApp
attr_reader :router
alias routes router
attr_reader :routes
def initialize(router, &blk)
@router = router
@stack = ActionDispatch::MiddlewareStack.new(&blk).build(@router)
def initialize(routes, &blk)
@routes = routes
@stack = ActionDispatch::MiddlewareStack.new(&blk).build(@routes)
end
def call(env)
@ -234,7 +233,7 @@ class TestCase
# Must repeat the setup because AV::TestCase is a duplication
# of AC::TestCase
setup do
@router = SharedTestRoutes
@routes = SharedTestRoutes
end
end
end
@ -250,7 +249,7 @@ class TestCase
include ActionDispatch::TestProcess
setup do
@router = SharedTestRoutes
@routes = SharedTestRoutes
end
end
end

@ -400,7 +400,7 @@ def with_test_routes(options = {})
map.resources :series
end
self.class.send(:include, @router.url_helpers)
self.class.send(:include, @routes.url_helpers)
yield
end
end
@ -422,7 +422,7 @@ def with_admin_test_routes(options = {})
end
end
self.class.send(:include, @router.url_helpers)
self.class.send(:include, @routes.url_helpers)
yield
end
end
@ -441,7 +441,7 @@ def with_admin_and_site_test_routes(options = {})
end
end
self.class.send(:include, @router.url_helpers)
self.class.send(:include, @routes.url_helpers)
yield
end
end

@ -81,9 +81,9 @@ def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_def
match '/', :to => 'posts#index', :as => :main
end
@params[:format] = 'rss'
assert_equal '/posts.rss', @router.url_for(@params)
assert_equal '/posts.rss', @routes.url_for(@params)
@params[:format] = nil
assert_equal '/', @router.url_for(@params)
assert_equal '/', @routes.url_for(@params)
end
end
@ -518,7 +518,7 @@ def reset!
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@controller = ActionCachingTestController.new
@controller.singleton_class.send(:include, @router.url_helpers)
@controller.singleton_class.send(:include, @routes.url_helpers)
@request.host = 'hostname.com'
end

@ -1,6 +1,6 @@
require 'abstract_unit'
ActionController::Base.cookie_verifier_secret = "thisISverySECRET123"
ActionController::Base.config.secret = "thisISverySECRET123"
class CookieTest < ActionController::TestCase
class TestController < ActionController::Base

@ -430,3 +430,50 @@ def test_generate_url_without_controller
assert_equal 'http://www.example.com/foo', url_for(:controller => "foo")
end
end
class ApplicationIntegrationTest < ActionController::IntegrationTest
class TestController < ActionController::Base
def index
render :text => "index"
end
end
def self.call(env)
routes.call(env)
end
def self.routes
@routes ||= ActionDispatch::Routing::RouteSet.new
end
routes.draw do
match 'foo', :to => 'application_integration_test/test#index', :as => :foo
match 'bar', :to => 'application_integration_test/test#index', :as => :bar
end
def app
self.class
end
test "includes route helpers" do
assert_equal '/foo', foo_path
assert_equal '/bar', bar_path
end
test "route helpers after controller access" do
get '/foo'
assert_equal '/foo', foo_path
get '/bar'
assert_equal '/bar', bar_path
end
test "missing route helper before controller access" do
assert_raise(NameError) { missing_path }
end
test "missing route helper after controller access" do
get '/foo'
assert_raise(NameError) { missing_path }
end
end

@ -126,7 +126,7 @@ def test_multiple_default_restful_routes
def test_with_custom_conditions
with_restful_routing :messages, :conditions => { :subdomain => 'app' } do
assert @router.recognize_path("/messages", :method => :get, :subdomain => 'app')
assert @routes.recognize_path("/messages", :method => :get, :subdomain => 'app')
end
end
@ -395,7 +395,7 @@ def test_override_new_method
assert_restful_routes_for :messages do |options|
assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :get)
assert_raise(ActionController::RoutingError) do
@router.recognize_path("/messages/new", :method => :post)
@routes.recognize_path("/messages/new", :method => :post)
end
end
end
@ -505,7 +505,7 @@ def test_shallow_nested_restful_routes_with_namespaces
def test_restful_routes_dont_generate_duplicates
with_restful_routing :messages do
routes = @router.routes
routes = @routes.routes
routes.each do |route|
routes.each do |r|
next if route === r # skip the comparison instance
@ -1169,8 +1169,8 @@ def assert_restful_routes_for(controller_name, options = {})
options[:shallow_options] = options[:options]
end
new_action = @router.resources_path_names[:new] || "new"
edit_action = @router.resources_path_names[:edit] || "edit"
new_action = @routes.resources_path_names[:new] || "new"
edit_action = @routes.resources_path_names[:edit] || "edit"
if options[:path_names]
new_action = options[:path_names][:new] if options[:path_names][:new]
@ -1237,7 +1237,7 @@ def assert_restful_named_routes_for(controller_name, singular_name = nil, option
end
@controller = "#{options[:options][:controller].camelize}Controller".constantize.new
@controller.singleton_class.send(:include, @router.url_helpers)
@controller.singleton_class.send(:include, @routes.url_helpers)
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
get :index, options[:options]
@ -1307,7 +1307,7 @@ def assert_singleton_routes_for(singleton_name, options = {})
def assert_singleton_named_routes_for(singleton_name, options = {})
(options[:options] ||= {})[:controller] ||= singleton_name.to_s.pluralize
@controller = "#{options[:options][:controller].camelize}Controller".constantize.new
@controller.singleton_class.send(:include, @router.url_helpers)
@controller.singleton_class.send(:include, @routes.url_helpers)
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
get :show, options[:options]

@ -478,8 +478,8 @@ def test_assert_realistic_path_parameters
end
def test_with_routing_places_routes_back
assert @router
routes_id = @router.object_id
assert @routes
routes_id = @routes.object_id
begin
with_routing { raise 'fail' }
@ -487,8 +487,8 @@ def test_with_routing_places_routes_back
rescue RuntimeError
end
assert @router
assert_equal routes_id, @router.object_id
assert @routes
assert_equal routes_id, @routes.object_id
end
def test_remote_addr

@ -10,8 +10,8 @@ def initialize(request)
}
end
def rewrite(router, options)
router.url_for(@options.merge(options))
def rewrite(routes, options)
routes.url_for(@options.merge(options))
end
end
@ -23,63 +23,63 @@ def setup
def test_port
assert_equal('http://test.host:1271/c/a/i',
@rewriter.rewrite(@router, :controller => 'c', :action => 'a', :id => 'i', :port => 1271)
@rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :port => 1271)
)
end
def test_protocol_with_and_without_separator
assert_equal('https://test.host/c/a/i',
@rewriter.rewrite(@router, :protocol => 'https', :controller => 'c', :action => 'a', :id => 'i')
@rewriter.rewrite(@routes, :protocol => 'https', :controller => 'c', :action => 'a', :id => 'i')
)
assert_equal('https://test.host/c/a/i',
@rewriter.rewrite(@router, :protocol => 'https://', :controller => 'c', :action => 'a', :id => 'i')
@rewriter.rewrite(@routes, :protocol => 'https://', :controller => 'c', :action => 'a', :id => 'i')
)
end
def test_user_name_and_password
assert_equal(
'http://david:secret@test.host/c/a/i',
@rewriter.rewrite(@router, :user => "david", :password => "secret", :controller => 'c', :action => 'a', :id => 'i')
@rewriter.rewrite(@routes, :user => "david", :password => "secret", :controller => 'c', :action => 'a', :id => 'i')
)
end
def test_user_name_and_password_with_escape_codes
assert_equal(
'http://openid.aol.com%2Fnextangler:one+two%3F@test.host/c/a/i',
@rewriter.rewrite(@router, :user => "openid.aol.com/nextangler", :password => "one two?", :controller => 'c', :action => 'a', :id => 'i')
@rewriter.rewrite(@routes, :user => "openid.aol.com/nextangler", :password => "one two?", :controller => 'c', :action => 'a', :id => 'i')
)
end
def test_anchor
assert_equal(
'http://test.host/c/a/i#anchor',
@rewriter.rewrite(@router, :controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor')
@rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor')
)
end
def test_anchor_should_call_to_param
assert_equal(
'http://test.host/c/a/i#anchor',
@rewriter.rewrite(@router, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anchor'))
@rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anchor'))
)
end
def test_anchor_should_be_cgi_escaped
assert_equal(
'http://test.host/c/a/i#anc%2Fhor',
@rewriter.rewrite(@router, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor'))
@rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor'))
)
end
def test_trailing_slash
options = {:controller => 'foo', :action => 'bar', :id => '3', :only_path => true}
assert_equal '/foo/bar/3', @rewriter.rewrite(@router, options)
assert_equal '/foo/bar/3?query=string', @rewriter.rewrite(@router, options.merge({:query => 'string'}))
assert_equal '/foo/bar/3', @rewriter.rewrite(@routes, options)
assert_equal '/foo/bar/3?query=string', @rewriter.rewrite(@routes, options.merge({:query => 'string'}))
options.update({:trailing_slash => true})
assert_equal '/foo/bar/3/', @rewriter.rewrite(@router, options)
assert_equal '/foo/bar/3/', @rewriter.rewrite(@routes, options)
options.update({:query => 'string'})
assert_equal '/foo/bar/3/?query=string', @rewriter.rewrite(@router, options)
assert_equal '/foo/bar/3/?query=string', @rewriter.rewrite(@routes, options)
end
end

@ -245,7 +245,7 @@ def test_typecast_as_yaml
private
def with_params_parsers(parsers = {})
old_session = @integration_session
@app = ActionDispatch::ParamsParser.new(app.router, parsers)
@app = ActionDispatch::ParamsParser.new(app.routes, parsers)
reset!
yield
ensure

@ -122,7 +122,7 @@ class RackRequestTest < BaseRackTest
test "cgi environment variables" do
assert_equal "Basic", @request.auth_type
assert_equal 0, @request.content_length
assert_equal nil, @request.content_type
assert_equal nil, @request.content_mime_type
assert_equal "CGI/1.1", @request.gateway_interface
assert_equal "*/*", @request.accept
assert_equal "UTF-8", @request.accept_charset
@ -177,12 +177,12 @@ class RackRequestParamsParsingTest < BaseRackTest
class RackRequestContentTypeTest < BaseRackTest
test "html content type verification" do
@request.env['CONTENT_TYPE'] = Mime::HTML.to_s
assert @request.content_type.verify_request?
assert @request.content_mime_type.verify_request?
end
test "xml content type verification" do
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
assert !@request.content_type.verify_request?
assert !@request.content_mime_type.verify_request?
end
end

@ -295,7 +295,7 @@ class RequestTest < ActiveSupport::TestCase
test "content type" do
request = stub_request 'CONTENT_TYPE' => 'text/html'
assert_equal Mime::HTML, request.content_type
assert_equal Mime::HTML, request.content_mime_type
end
test "can override format with parameter" do
@ -310,17 +310,17 @@ class RequestTest < ActiveSupport::TestCase
test "no content type" do
request = stub_request
assert_equal nil, request.content_type
assert_equal nil, request.content_mime_type
end
test "content type is XML" do
request = stub_request 'CONTENT_TYPE' => 'application/xml'
assert_equal Mime::XML, request.content_type
assert_equal Mime::XML, request.content_mime_type
end
test "content type with charset" do
request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8'
assert_equal Mime::XML, request.content_type
assert_equal Mime::XML, request.content_mime_type
end
test "user agent" do

@ -114,6 +114,8 @@ def self.matches?(request)
resources :comments, :except => :destroy
end
resources :sheep
match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
match 'people/:id/update', :to => 'people#update', :as => :update_person
@ -171,6 +173,12 @@ def self.matches?(request)
resources :descriptions
root :to => 'projects#index'
end
resources :products, :constraints => { :id => /\d{4}/ } do
resources :images
end
resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ }
end
end
@ -525,6 +533,23 @@ def test_resource_routes_with_only_and_except
end
end
def test_resource_with_slugs_in_ids
with_test_routes do
get '/posts/rails-rocks'
assert_equal 'posts#show', @response.body
assert_equal '/posts/rails-rocks', post_path(:id => 'rails-rocks')
end
end
def test_resources_for_uncountable_names
with_test_routes do
assert_equal '/sheep', sheep_index_path
assert_equal '/sheep/1', sheep_path(1)
assert_equal '/sheep/new', new_sheep_path
assert_equal '/sheep/1/edit', edit_sheep_path(1)
end
end
def test_path_names
with_test_routes do
get '/es/projeto'
@ -794,6 +819,26 @@ def test_default_params
end
end
def test_resource_constraints
with_test_routes do
assert_raise(ActionController::RoutingError) { get '/products/1' }
get '/products'
assert_equal 'products#index', @response.body
get '/products/0001'
assert_equal 'products#show', @response.body
assert_raise(ActionController::RoutingError) { get '/products/1/images' }
get '/products/0001/images'
assert_equal 'images#index', @response.body
get '/products/0001/images/1'
assert_equal 'images#show', @response.body
assert_raise(ActionController::RoutingError) { get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
get '/dashboard', {}, {'REMOTE_ADDR' => '192.168.1.100'}
assert_equal 'dashboards#show', @response.body
end
end
private
def with_test_routes
yield

@ -266,6 +266,10 @@ def test_error_message_on_with_options_hash
assert_dom_equal "<div class=\"differentError\">beforecan't be emptyafter</div>", error_message_on(:post, :author_name, :css_class => 'differentError', :prepend_text => 'before', :append_text => 'after')
end
def test_error_message_on_with_tag_option_in_options_hash
assert_dom_equal "<span class=\"differentError\">beforecan't be emptyafter</span>", error_message_on(:post, :author_name, :html_tag => "span", :css_class => 'differentError', :prepend_text => 'before', :append_text => 'after')
end
def test_error_message_on_handles_empty_errors
assert_equal "", error_message_on(@post, :tag)
end

@ -373,6 +373,22 @@ def test_timebased_asset_id
assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png")
end
def test_string_asset_id
@controller.config.asset_path = "/assets.v12345%s"
expected_path = "/assets.v12345/images/rails.png"
assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
end
def test_proc_asset_id
@controller.config.asset_path = Proc.new do |asset_path|
"/assets.v12345#{asset_path}"
end
expected_path = "/assets.v12345/images/rails.png"
assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
end
def test_timebased_asset_id_with_relative_url_root
@controller.config.relative_url_root = "/collaboration/hieraki"
expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s

@ -114,7 +114,7 @@ def render_from_helper
end
test "is able to use routes" do
controller.request.assign_parameters(@router, 'foo', 'index')
controller.request.assign_parameters(@routes, 'foo', 'index')
assert_equal '/foo', url_for
assert_equal '/bar', url_for(:controller => 'bar')
end

@ -1,4 +1,7 @@
require 'active_model/attribute_methods'
require 'active_support/concern'
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/duplicable'
module ActiveModel
# <tt>ActiveModel::Dirty</tt> provides a way to track changes in your

@ -1,5 +1,6 @@
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/object/blank'
require 'active_support/ordered_hash'
module ActiveModel

@ -1,5 +1,6 @@
require 'active_support/core_ext/array/wrap'
require "active_support/core_ext/module/anonymous"
require 'active_support/core_ext/object/blank'
module ActiveModel #:nodoc:
# A simple base class that can be used along with

@ -1,9 +1,11 @@
*Edge*
*Rails 3.0.0 [Beta 2] (pending)*
* To prefix the table names of all models in a module, define self.table_name_prefix on the module. #4032 [Andrew White]
* Silenced "SHOW FIELDS" and "SET SQL_AUTO_IS_NULL=0" statements from the MySQL driver to improve log signal to noise ration in development [DHH]
*Rails 3.0 [Beta] (February 4th, 2010)*
*Rails 3.0.0 [Beta 1] (February 4th, 2010)*
* PostgreSQLAdapter: set time_zone to UTC when Base.default_timezone == :utc so that Postgres doesn't incorrectly offset-adjust values inserted into TIMESTAMP WITH TIME ZONE columns. #3777 [Jack Christensen]

@ -111,10 +111,10 @@ module ConnectionAdapters
autoload :TestCase
autoload :TestFixtures, 'active_record/fixtures'
end
base_hook do
ActiveSupport.on_load(:active_record) do
Arel::Table.engine = Arel::Sql::Engine.new(self)
end
end
I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'

@ -159,6 +159,11 @@ def set_association_single_records(id_to_record_map, reflection_name, associated
association_proxy.__send__(:set_inverse_instance, associated_record, mapped_record)
end
end
id_to_record_map.each do |id, records|
next if seen_keys.include?(id.to_s)
records.each {|record| record.send("set_#{reflection_name}_target", nil) }
end
end
# Given a collection of ActiveRecord objects, constructs a Hash which maps
@ -324,7 +329,7 @@ def preload_belongs_to_association(records, reflection, preload_options={})
klass = klass_name.constantize
table_name = klass.quoted_table_name
primary_key = klass.primary_key
primary_key = reflection.options[:primary_key] || klass.primary_key
column_type = klass.columns.detect{|c| c.name == primary_key}.type
ids = id_map.keys.map do |id|

@ -1,5 +1,6 @@
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/object/blank'
module ActiveRecord
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
@ -85,6 +86,15 @@ def initialize(reflection)
end
end
# This error is raised when trying to destroy a parent instance in a N:1, 1:1 assosications
# (has_many, has_one) when there is at least 1 child assosociated instance.
# ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
class DeleteRestrictionError < ActiveRecordError #:nodoc:
def initialize(reflection)
super("Cannot delete record because of dependent #{reflection.name}")
end
end
# See ActiveRecord::Associations::ClassMethods for documentation.
module Associations # :nodoc:
extend ActiveSupport::Concern
@ -830,6 +840,8 @@ module ClassMethods
# objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
# objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. *Warning:* This option is ignored when also using
# the <tt>:through</tt> option.
# the <tt>:through</tt> option. If set to <tt>:restrict</tt>
# this object cannot be deleted if it has any associated object.
# [:finder_sql]
# Specify a complete SQL statement to fetch the association. This is a good way to go for complex
# associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
@ -1468,9 +1480,15 @@ def add_touch_callbacks(reflection, touch_attribute)
# Creates before_destroy callback methods that nullify, delete or destroy
# has_many associated objects, according to the defined :dependent rule.
# If the association is marked as :dependent => :restrict, create a callback
# that prevents deleting entirely.
#
# See HasManyAssociation#delete_records. Dependent associations
# delete children, otherwise foreign key is set to NULL.
# See HasManyAssociation#delete_records. Dependent associations
# delete children if the option is set to :destroy or :delete_all, set the
# foreign key to NULL if the option is set to :nullify, and do not touch the
# child records if the option is set to :restrict.
#
# The +extra_conditions+ parameter, which is not used within the main
# Active Record codebase, is meant to allow plugins to define extra
@ -1530,14 +1548,24 @@ def configure_dependency_for_has_many(reflection, extra_conditions = nil)
%@#{dependent_conditions}@)
end
CALLBACK
when :restrict
method_name = "has_many_dependent_restrict_for_#{reflection.name}".to_sym
define_method(method_name) do
unless send(reflection.name).empty?
raise DeleteRestrictionError.new(reflection)
end
end
before_destroy method_name
else
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})"
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
end
end
end
# Creates before_destroy callback methods that nullify, delete or destroy
# has_one associated objects, according to the defined :dependent rule.
# If the association is marked as :dependent => :restrict, create a callback
# that prevents deleting entirely.
def configure_dependency_for_has_one(reflection)
if reflection.options.include?(:dependent)
name = reflection.options[:dependent]
@ -1558,8 +1586,16 @@ def #{method_name}
association.update_attribute(#{reflection.primary_key_name.inspect}, nil) if association
end
eoruby
when :restrict
method_name = "has_one_dependent_restrict_for_#{reflection.name}".to_sym
define_method(method_name) do
unless send(reflection.name).nil?
raise DeleteRestrictionError.new(reflection)
end
end
before_destroy method_name
else
raise ArgumentError, "The :dependent option expects either :destroy, :delete or :nullify (#{reflection.options[:dependent].inspect})"
raise ArgumentError, "The :dependent option expects either :destroy, :delete, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
end
before_destroy method_name
@ -1995,7 +2031,7 @@ def association_join
[aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key])]
end
when :belongs_to
[aliased_table[reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name])]
[aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name])]
end
unless klass.descends_from_active_record?

@ -1,4 +1,5 @@
require "active_record/associations/through_association_scope"
require 'active_support/core_ext/object/blank'
module ActiveRecord
module Associations

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
module AttributeMethods
module Dirty

@ -3,6 +3,12 @@ module AttributeMethods
module PrimaryKey
extend ActiveSupport::Concern
# Returns this record's primary key value wrapped in an Array
# or nil if the record is a new_record?
def to_key
new_record? ? nil : [ send(self.class.primary_key) ]
end
module ClassMethods
# Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
# primary_key_prefix_type setting, though.
@ -39,22 +45,6 @@ def set_primary_key(value = nil, &block)
end
alias :primary_key= :set_primary_key
end
module InstanceMethods
# Returns this record's primary key value wrapped in an Array
# or nil if the record is a new_record?
# This is done to comply with the AMo interface that expects
# every AMo compliant object to respond_to?(:to_key) and return
# an Enumerable object from that call, or nil if new_record?
# This method also takes custom primary keys specified via
# the +set_primary_key+ into account.
def to_key
new_record? ? nil : [ self.primary_key ]
end
end
end
end
end

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
module AttributeMethods
module Query

@ -13,6 +13,8 @@
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/object/singleton_class'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/blank'
require 'arel'
require 'active_record/errors'
@ -336,6 +338,9 @@ def self.reset_subclasses #:nodoc:
# Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
# table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
# for tables in a shared database. By default, the prefix is the empty string.
#
# If you are organising your models within modules you can add a prefix to the models within a namespace by defining
# a singleton method in the parent module called table_name_prefix which returns your chosen prefix.
cattr_accessor :table_name_prefix, :instance_writer => false
@@table_name_prefix = ""
@ -763,7 +768,7 @@ def reset_table_name #:nodoc:
contained = contained.singularize if parent.pluralize_table_names
contained << '_'
end
name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
name = "#{full_table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
end
@quoted_table_name = nil
@ -771,6 +776,10 @@ def reset_table_name #:nodoc:
name
end
def full_table_name_prefix #:nodoc:
(parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
end
# Defines the column name for use with single table inheritance
# -- can be set in subclasses like so: self.inheritance_column = "type_id"
def inheritance_column
@ -1288,7 +1297,7 @@ def subclasses #:nodoc:
# <tt>options</tt> argument is the same as in find.
#
# class Person < ActiveRecord::Base
# default_scope :order => 'last_name, first_name'
# default_scope order('last_name, first_name')
# end
def default_scope(options = {})
self.default_scoping << construct_finder_arel(options)
@ -2214,6 +2223,7 @@ def object_from_yaml(string)
extend QueryCache::ClassMethods
extend ActiveSupport::Benchmarkable
include ActiveModel::Conversion
include Validations
include Locking::Optimistic, Locking::Pessimistic
include AttributeMethods
@ -2223,12 +2233,10 @@ def object_from_yaml(string)
include AttributeMethods::Dirty
include Callbacks, ActiveModel::Observing, Timestamp
include Associations, AssociationPreload, NamedScope
include ActiveModel::Conversion
# AutosaveAssociation needs to be included before Transactions, because we want
# #save_with_autosave_associations to be wrapped inside a transaction.
include AutosaveAssociation, NestedAttributes
include Aggregations, Transactions, Reflection, Serialization
NilClass.add_whiner(self) if NilClass.respond_to?(:add_whiner)
@ -2237,4 +2245,4 @@ def object_from_yaml(string)
# TODO: Remove this and make it work with LAZY flag
require 'active_record/connection_adapters/abstract_adapter'
ActiveRecord.run_base_hooks(ActiveRecord::Base)
ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/duplicable'
module ActiveRecord
module ConnectionAdapters # :nodoc:
module QueryCache

@ -1,3 +1,4 @@
require 'active_support/core_ext/object/blank'
require 'date'
require 'set'
require 'bigdecimal'

@ -1,5 +1,6 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_support/core_ext/kernel/requires'
require 'active_support/core_ext/object/blank'
require 'set'
module MysqlCompat #:nodoc:

@ -1,5 +1,6 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_support/core_ext/kernel/requires'
require 'active_support/core_ext/object/blank'
begin
require_library_or_gem 'pg'
@ -299,7 +300,7 @@ def table_alias_length
# QUOTING ==================================================
# Escapes binary strings for bytea input to the database.
def escape_bytea(value)
def escape_bytea(original_value)
if @connection.respond_to?(:escape_bytea)
self.class.instance_eval do
define_method(:escape_bytea) do |value|
@ -323,13 +324,13 @@ def escape_bytea(value)
end
end
end
escape_bytea(value)
escape_bytea(original_value)
end
# Unescapes bytea output from a database to the binary string it represents.
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
# on escaped binary output from database drive.
def unescape_bytea(value)
def unescape_bytea(original_value)
# In each case, check if the value actually is escaped PostgreSQL bytea output
# or an unescaped Active Record attribute that was just written.
if PGconn.respond_to?(:unescape_bytea)
@ -369,7 +370,7 @@ def unescape_bytea(value)
end
end
end
unescape_bytea(value)
unescape_bytea(original_value)
end
# Quotes PostgreSQL-specific data types for SQL input.
@ -394,7 +395,7 @@ def quote(value, column = nil) #:nodoc:
end
# Quotes strings for use in SQL input in the postgres driver for better performance.
def quote_string(s) #:nodoc:
def quote_string(original_value) #:nodoc:
if @connection.respond_to?(:escape)
self.class.instance_eval do
define_method(:quote_string) do |s|
@ -414,7 +415,7 @@ def quote_string(s) #:nodoc:
remove_method(:quote_string)
end
end
quote_string(s)
quote_string(original_value)
end
# Checks the following cases:

@ -4,6 +4,7 @@
require 'zlib'
require 'active_support/dependencies'
require 'active_support/core_ext/logger'
require 'active_support/core_ext/object/blank'
if RUBY_VERSION < '1.9'
module YAML #:nodoc:

@ -1,6 +1,7 @@
require 'active_support/core_ext/array'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/singleton_class'
require 'active_support/core_ext/object/blank'
module ActiveRecord
module NamedScope
@ -101,7 +102,8 @@ def scope(name, options = {}, &block)
name = name.to_sym
if !scopes[name] && respond_to?(name, true)
raise ArgumentError, "Cannot define scope :#{name} because #{self.name}.#{name} method already exists."
logger.warn "Creating scope :#{name}. " \
"Overwriting existing method #{self.name}.#{name}."
end
scopes[name] = lambda do |parent_scope, *args|
@ -165,7 +167,14 @@ def last(*args)
end
def ==(other)
other.respond_to?(:to_ary) ? to_a == other.to_a : false
case other
when Scope
to_sql == other.to_sql
when Relation
other == self
when Array
to_a == other.to_a
end
end
private

@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/blank'
module ActiveRecord
module NestedAttributes #:nodoc:

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
class QueryCache
module ClassMethods

@ -23,18 +23,18 @@ class Railtie < Rails::Railtie
log_subscriber :active_record, ActiveRecord::Railties::LogSubscriber.new
initializer "active_record.initialize_timezone" do
ActiveRecord.base_hook do
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
self.default_timezone = :utc
end
end
initializer "active_record.logger" do
ActiveRecord.base_hook { self.logger ||= ::Rails.logger }
ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
end
initializer "active_record.set_configs" do |app|
ActiveRecord.base_hook do
ActiveSupport.on_load(:active_record) do
app.config.active_record.each do |k,v|
send "#{k}=", v
end
@ -44,7 +44,7 @@ class Railtie < Rails::Railtie
# This sets the database configuration from Configuration#database_configuration
# and then establishes the connection.
initializer "active_record.initialize_database" do |app|
ActiveRecord.base_hook do
ActiveSupport.on_load(:active_record) do
self.configurations = app.config.database_configuration
establish_connection
end
@ -53,7 +53,7 @@ class Railtie < Rails::Railtie
# Expose database runtime to controller for logging.
initializer "active_record.log_runtime" do |app|
require "active_record/railties/controller_runtime"
ActionController.base_hook do
ActiveSupport.on_load(:action_controller) do
include ActiveRecord::Railties::ControllerRuntime
end
end
@ -71,9 +71,9 @@ class Railtie < Rails::Railtie
end
initializer "active_record.load_observers" do
ActiveRecord.base_hook { instantiate_observers }
ActiveSupport.on_load(:active_record) { instantiate_observers }
ActiveRecord.base_hook do
ActiveSupport.on_load(:active_record) do
ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do
ActiveRecord::Base.instantiate_observers
end
@ -81,7 +81,7 @@ class Railtie < Rails::Railtie
end
initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app|
ActiveRecord.base_hook do
ActiveSupport.on_load(:active_record) do
unless app.config.cache_classes
ActionDispatch::Callbacks.after do
ActiveRecord::Base.reset_subclasses

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
class Relation
JoinOperation = Struct.new(:relation, :join_class, :on)

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
module Batches # :nodoc:
# Yields each record that was found by the find +options+. The find is
@ -48,6 +50,10 @@ def find_each(options = {})
def find_in_batches(options = {})
relation = self
if orders.present? || taken.present?
ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
end
if (finder_options = options.except(:start, :batch_size)).present?
raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
module Calculations
# Count operates using three different approaches.

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
module FinderMethods
# Find operates with four different retrieval approaches:

@ -27,12 +27,7 @@ def build_from_hash(attributes, default_table)
values = value.to_a
attribute.in(values)
when Range
# TODO : Arel should handle ranges with excluded end.
if value.exclude_end?
[attribute.gteq(value.begin), attribute.lt(value.end)]
else
attribute.in(value)
end
else
attribute.eq(value)
end

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
module QueryMethods
extend ActiveSupport::Concern

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
module SpawnMethods
def merge(r)

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
# Allows programmers to programmatically define a schema in a portable
# DSL. This means you can define tables, indexes, etc. without using SQL

@ -67,13 +67,11 @@ def mount_sql_and_params(klass, table_name, attribute, value) #:nodoc:
if value.nil? || (options[:case_sensitive] || !column.text?)
sql = "#{sql_attribute} #{operator}"
params = [value]
else
sql = "LOWER(#{sql_attribute}) #{operator}"
params = [value.mb_chars.downcase]
sql = "LOWER(#{sql_attribute}) = LOWER(?)"
end
[sql, params]
[sql, [value]]
end
end

@ -32,6 +32,17 @@ def test_belongs_to_with_primary_key
assert_equal companies(:first_firm).name, client.firm_with_primary_key.name
end
def test_belongs_to_with_primary_key_joins_on_correct_column
sql = Client.joins(:firm_with_primary_key).to_sql
if current_adapter?(:MysqlAdapter)
assert_no_match /`firm_with_primary_keys_companies`\.`id`/, sql
assert_match /`firm_with_primary_keys_companies`\.`name`/, sql
else
assert_no_match /"firm_with_primary_keys_companies"\."id"/, sql
assert_match /"firm_with_primary_keys_companies"\."name"/, sql
end
end
def test_proxy_assignment
account = Account.find(1)
assert_nothing_raised { account.firm = account.firm }
@ -61,6 +72,13 @@ def test_natural_assignment_with_primary_key
assert_equal apple.name, citibank.firm_name
end
def test_eager_loading_with_primary_key
apple = Firm.create("name" => "Apple")
citibank = Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key)
assert_not_nil citibank_result.instance_variable_get("@firm_with_primary_key")
end
def test_no_unexpected_aliasing
first_firm = companies(:first_firm)
another_firm = companies(:another_firm)
@ -439,9 +457,15 @@ def test_dependent_delete_and_destroy_with_belongs_to
assert_equal [author_address.id], AuthorAddress.destroyed_author_address_ids
end
def test_invalid_belongs_to_dependent_option_raises_exception
def test_invalid_belongs_to_dependent_option_nullify_raises_exception
assert_raise ArgumentError do
Author.belongs_to :special_author_address, :dependent => :nullify
end
end
def test_invalid_belongs_to_dependent_option_restrict_raises_exception
assert_raise ArgumentError do
Author.belongs_to :special_author_address, :dependent => :restrict
end
end
end

@ -833,4 +833,10 @@ def test_include_has_one_using_primary_key
end
end
def test_preloading_empty_polymorphic_parent
t = Tagging.create!(:taggable_type => 'Post', :taggable_id => Post.maximum(:id) + 1, :tag => tags(:general))
assert_queries(2) { @tagging = Tagging.preload(:taggable).find(t.id) }
assert_no_queries { assert ! @tagging.taggable }
end
end

@ -836,6 +836,14 @@ def test_depends_and_nullify
assert_equal num_accounts, Account.count
end
def test_restrict
firm = RestrictedFirm.new(:name => 'restrict')
firm.save!
child_firm = firm.companies.create(:name => 'child')
assert !firm.companies.empty?
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
end
def test_included_in_collection
assert companies(:first_firm).clients.include?(Client.find(2))
end

@ -177,7 +177,15 @@ def test_dependence_with_nil_associate
assert_nothing_raised { firm.destroy }
end
def test_succesful_build_association
def test_dependence_with_restrict
firm = RestrictedFirm.new(:name => 'restrict')
firm.save!
account = firm.create_account(:credit_limit => 10)
assert !firm.account.nil?
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
end
def test_successful_build_association
firm = Firm.new("name" => "GlobalMegaCorp")
firm.save

@ -29,6 +29,16 @@ def test_each_should_raise_if_the_limit_is_set
end
end
def test_warn_if_limit_scope_is_set
ActiveRecord::Base.logger.expects(:warn)
Post.limit(1).find_each { |post| post }
end
def test_warn_if_order_scope_is_set
ActiveRecord::Base.logger.expects(:warn)
Post.order("title").find_each { |post| post }
end
def test_find_in_batches_should_return_batches
assert_queries(Post.count + 1) do
Post.find_in_batches(:batch_size => 1) do |batch|

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