aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile5
-rw-r--r--RAILS_VERSION2
-rw-r--r--actionmailer/CHANGELOG2
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/action_mailer/base.rb39
-rw-r--r--actionmailer/lib/action_mailer/mail_helper.rb5
-rw-r--r--actionmailer/lib/action_mailer/version.rb2
-rw-r--r--actionmailer/test/base_test.rb17
-rw-r--r--actionmailer/test/fixtures/base_mailer/inline_attachment.html.erb5
-rw-r--r--actionmailer/test/fixtures/base_mailer/inline_attachment.text.erb4
-rw-r--r--actionmailer/test/old_base/mail_service_test.rb6
-rw-r--r--actionpack/CHANGELOG12
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb8
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb128
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb5
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb2
-rw-r--r--actionpack/lib/action_pack/version.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb7
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb28
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb38
-rw-r--r--actionpack/lib/action_view/lookup_context.rb2
-rw-r--r--actionpack/test/controller/caching_test.rb15
-rw-r--r--actionpack/test/controller/new_base/render_rjs_test.rb3
-rw-r--r--actionpack/test/dispatch/routing_test.rb123
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb2
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb9
-rw-r--r--actionpack/test/template/tag_helper_test.rb2
-rw-r--r--actionpack/test/template/text_helper_test.rb170
-rw-r--r--activemodel/CHANGELOG2
-rw-r--r--activemodel/lib/active_model/serializers/json.rb9
-rw-r--r--activemodel/lib/active_model/version.rb2
-rw-r--r--activemodel/test/cases/serializeration/json_serialization_test.rb36
-rw-r--r--activerecord/CHANGELOG4
-rw-r--r--activerecord/activerecord.gemspec2
-rwxr-xr-xactiverecord/lib/active_record/associations.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb56
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb4
-rw-r--r--activerecord/lib/active_record/relation.rb7
-rw-r--r--activerecord/lib/active_record/transactions.rb118
-rw-r--r--activerecord/lib/active_record/version.rb2
-rw-r--r--activerecord/test/cases/adapter_test.rb17
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb10
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb2
-rwxr-xr-xactiverecord/test/cases/base_test.rb7
-rw-r--r--activerecord/test/cases/date_time_test.rb4
-rw-r--r--activerecord/test/cases/json_serialization_test.rb19
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb4
-rw-r--r--activerecord/test/cases/migration_test.rb33
-rw-r--r--activerecord/test/cases/relations_test.rb2
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb240
-rw-r--r--activerecord/test/cases/transactions_test.rb33
-rw-r--r--activerecord/test/connections/native_oracle/connection.rb2
-rw-r--r--activerecord/test/models/topic.rb6
-rw-r--r--activeresource/CHANGELOG2
-rw-r--r--activeresource/lib/active_resource/version.rb2
-rw-r--r--activesupport/CHANGELOG2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/file.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string.rb1
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb2
-rw-r--r--activesupport/lib/active_support/version.rb2
-rw-r--r--activesupport/test/abstract_unit.rb2
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb4
-rw-r--r--railties/CHANGELOG2
-rw-r--r--railties/guides/source/3_0_release_notes.textile1
-rw-r--r--railties/guides/source/action_mailer_basics.textile31
-rw-r--r--railties/lib/rails/configuration.rb4
-rw-r--r--railties/lib/rails/generators.rb6
-rw-r--r--railties/lib/rails/version.rb2
-rw-r--r--railties/test/application/middleware_test.rb6
-rw-r--r--railties/test/generators/app_generator_test.rb4
-rw-r--r--railties/test/isolation/abstract_unit.rb2
77 files changed, 721 insertions, 646 deletions
diff --git a/Gemfile b/Gemfile
index d57afc8630..6d878404d6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -29,10 +29,10 @@ gem "text-format", "~> 1.0.0"
# AR
if mri || RUBY_ENGINE == "rbx"
- gem "sqlite3-ruby", "= 1.3.0.beta.2", :require => 'sqlite3'
+ gem "sqlite3-ruby", "~> 1.3.0", :require => 'sqlite3'
group :db do
- gem "pg", ">= 0.9.0"
+ # gem "pg", ">= 0.9.0"
gem "mysql", ">= 2.8.1"
end
elsif RUBY_ENGINE == "jruby"
@@ -46,6 +46,7 @@ end
# AP
gem "RedCloth", ">= 4.2.2"
+gem "bluecloth", ">= 2.0.7"
group :documentation do
gem 'rdoc', '2.1'
diff --git a/RAILS_VERSION b/RAILS_VERSION
index d989233c8c..c3eddb1401 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-3.0.0.beta3
+3.0.0.beta4
diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG
index 7a17ff1df9..6e2c441e53 100644
--- a/actionmailer/CHANGELOG
+++ b/actionmailer/CHANGELOG
@@ -1,4 +1,4 @@
-*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
+*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* Changed encoding behaviour of mail, so updated tests in actionmailer and bumped mail version to 2.2.1 [ML]
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 4706b63b79..2b7c21b3f2 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -20,5 +20,5 @@ Gem::Specification.new do |s|
s.has_rdoc = true
s.add_dependency('actionpack', version)
- s.add_dependency('mail', '~> 2.2.1')
+ s.add_dependency('mail', '~> 2.2.3')
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 3a82979d35..7da033b6af 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -36,6 +36,9 @@ module ActionMailer #:nodoc:
# * <tt>attachments[]=</tt> - Allows you to add attachments to your email in an intuitive
# manner; <tt>attachments['filename.png'] = File.read('path/to/filename.png')</tt>
#
+ # * <tt>attachments.inline[]=</tt> - Allows you to add an inline attachment to your email
+ # in the same manner as <tt>attachments[]=</tt>
+ #
# * <tt>headers[]=</tt> - Allows you to specify any header field in your email such
# as <tt>headers['X-No-Spam'] = 'True'</tt>. Note, while most fields (like <tt>To:</tt>
# <tt>From:</tt> can only appear once in an email header, other fields like <tt>X-Anything</tt>
@@ -173,7 +176,7 @@ module ActionMailer #:nodoc:
#
# class ApplicationMailer < ActionMailer::Base
# def welcome(recipient)
- # attachments['free_book.pdf'] = { :data => File.read('path/to/file.pdf') }
+ # attachments['free_book.pdf'] = File.read('path/to/file.pdf')
# mail(:to => recipient, :subject => "New account information")
# end
# end
@@ -184,6 +187,34 @@ module ActionMailer #:nodoc:
# and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book
# with the filename +free_book.pdf+.
#
+ # = Inline Attachments
+ #
+ # You can also specify that a file should be displayed inline with other HTML. For example a
+ # corporate logo or a photo or the like.
+ #
+ # To do this is simple, in the Mailer:
+ #
+ # class ApplicationMailer < ActionMailer::Base
+ # def welcome(recipient)
+ # attachments.inline['photo.png'] = File.read('path/to/photo.png')
+ # mail(:to => recipient, :subject => "Here is what we look like")
+ # end
+ # end
+ #
+ # And then to reference the image in the view, you create a <tt>welcome.html.erb</tt> file and
+ # make a call to +image_tag+ passing in the attachment you want to display and then call
+ # +url+ on the attachment to get the relative content id path for the image source:
+ #
+ # <h1>Please Don't Cringe</h1>
+ #
+ # <%= image_tag attachments['photo.png'].url -%>
+ #
+ # As we are using ActionView's +image_tag+ method, you can pass in any other options you want:
+ #
+ # <h1>Please Don't Cringe</h1>
+ #
+ # <%= image_tag attachments['photo.png'].url, :alt => 'Our Photo', :class => 'photo' -%>
+ #
# = Observing and Intercepting Mails
#
# Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to
@@ -612,7 +643,11 @@ module ActionMailer #:nodoc:
when user_content_type.present?
user_content_type
when m.has_attachments?
- ["multipart", "mixed", params]
+ if m.attachments.detect { |a| a.inline? }
+ ["multipart", "related", params]
+ else
+ ["multipart", "mixed", params]
+ end
when m.multipart?
["multipart", "alternative", params]
else
diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb
index aab6e12387..b708881edf 100644
--- a/actionmailer/lib/action_mailer/mail_helper.rb
+++ b/actionmailer/lib/action_mailer/mail_helper.rb
@@ -32,5 +32,10 @@ module ActionMailer
def message
@_message
end
+
+ # Access the message attachments list.
+ def attachments
+ @_message.attachments
+ end
end
end
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index e66172c8d3..8c19082f61 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -3,7 +3,7 @@ module ActionMailer
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta3"
+ BUILD = "beta4"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 54bf3de6af..b9226196fd 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -33,6 +33,11 @@ class BaseTest < ActiveSupport::TestCase
mail(hash)
end
+ def inline_attachment
+ attachments.inline['logo.png'] = "\312\213\254\232"
+ mail
+ end
+
def attachment_with_content(hash = {})
attachments['invoice.pdf'] = 'This is test File content'
mail(hash)
@@ -264,6 +269,18 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("application/pdf", email.parts[1].mime_type)
assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded)
end
+
+ test "can embed an inline attachment" do
+ email = BaseMailer.inline_attachment
+ # Need to call #encoded to force the JIT sort on parts
+ email.encoded
+ assert_equal(2, email.parts.length)
+ assert_equal("multipart/related", email.mime_type)
+ assert_equal("multipart/alternative", email.parts[0].mime_type)
+ assert_equal("text/plain", email.parts[0].parts[0].mime_type)
+ assert_equal("text/html", email.parts[0].parts[1].mime_type)
+ assert_equal("logo.png", email.parts[1].filename)
+ end
# Defaults values
test "uses default charset from class" do
diff --git a/actionmailer/test/fixtures/base_mailer/inline_attachment.html.erb b/actionmailer/test/fixtures/base_mailer/inline_attachment.html.erb
new file mode 100644
index 0000000000..e200878127
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/inline_attachment.html.erb
@@ -0,0 +1,5 @@
+<h1>Inline Image</h1>
+
+<%= image_tag attachments['logo.png'].url %>
+
+<p>This is an image that is inline</p> \ No newline at end of file
diff --git a/actionmailer/test/fixtures/base_mailer/inline_attachment.text.erb b/actionmailer/test/fixtures/base_mailer/inline_attachment.text.erb
new file mode 100644
index 0000000000..e161d244d2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/inline_attachment.text.erb
@@ -0,0 +1,4 @@
+Inline Image
+
+No image for you
+
diff --git a/actionmailer/test/old_base/mail_service_test.rb b/actionmailer/test/old_base/mail_service_test.rb
index 7054e8052a..e8e8fcedc9 100644
--- a/actionmailer/test/old_base/mail_service_test.rb
+++ b/actionmailer/test/old_base/mail_service_test.rb
@@ -674,7 +674,7 @@ The body
EOF
mail = Mail.new(msg)
assert_equal "testing testing \326\244", mail.subject
- assert_equal "Subject: =?UTF-8?Q?testing_testing_=D6=A4?=\r\n", mail[:subject].encoded
+ assert_equal "Subject: testing testing =?UTF-8?Q?_=D6=A4=?=\r\n", mail[:subject].encoded
end
def test_unquote_7bit_subject
@@ -863,7 +863,7 @@ EOF
def test_multipart_with_utf8_subject
mail = TestMailer.multipart_with_utf8_subject(@recipient)
- regex = Regexp.escape('Subject: =?UTF-8?Q?Foo_=C3=A1=C3=AB=C3=B4_=C3=AE=C3=BC?=')
+ regex = Regexp.escape('Subject: Foo =?UTF-8?Q?=C3=A1=C3=AB=C3=B4=?= =?UTF-8?Q?_=C3=AE=C3=BC=?=')
assert_match(/#{regex}/, mail.encoded)
string = "Foo áëô îü"
assert_match(string, mail.subject)
@@ -871,7 +871,7 @@ EOF
def test_implicitly_multipart_with_utf8
mail = TestMailer.implicitly_multipart_with_utf8
- regex = Regexp.escape('Subject: =?UTF-8?Q?Foo_=C3=A1=C3=AB=C3=B4_=C3=AE=C3=BC?=')
+ regex = Regexp.escape('Subject: Foo =?UTF-8?Q?=C3=A1=C3=AB=C3=B4=?= =?UTF-8?Q?_=C3=AE=C3=BC=?=')
assert_match(/#{regex}/, mail.encoded)
string = "Foo áëô îü"
assert_match(string, mail.subject)
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index faa0d674dc..c3609958bc 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,4 +1,14 @@
-*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
+*Rails 3.0.0 [beta 4] (June 8th, 2010)*
+
+* Add shallow routes back to the new router [Diego Carrion]
+
+ resources :posts do
+ shallow do
+ resources :comments
+ end
+ end
+
+ You can now use comment_path for /comments/1 instead of post_comment_path for /posts/1/comments/1.
* Remove middleware laziness [José Valim]
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 10c7ca9021..c465035ca1 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -16,5 +16,13 @@ module ActionController
raise "In order to use #url_for, you must include the helpers of a particular " \
"router. For instance, `include Rails.application.routes.url_helpers"
end
+
+ module ClassMethods
+ def action_methods
+ @action_methods ||= begin
+ super - _router.named_routes.helper_names
+ end
+ end
+ end
end
end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
index 602411ed37..064ff3724d 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
@@ -23,6 +23,7 @@ module HTML #:nodoc:
# Create a new Tokenizer for the given text.
def initialize(text)
+ text.encode! if text.encoding_aware?
@scanner = StringScanner.new(text)
@position = 0
@line = 0
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index f9e81a02d3..8a2d8cd077 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -6,7 +6,7 @@ module ActionDispatch
# This middleware rescues any exception returned by the application and renders
# nice exception pages if it's being rescued locally.
class ShowExceptions
- LOCALHOST = ['127.0.0.1', '::1'].freeze
+ LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze
RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
@@ -114,7 +114,7 @@ module ActionDispatch
# True if the request came from localhost, 127.0.0.1.
def local_request?(request)
- LOCALHOST.any?{ |local_ip| request.remote_addr == local_ip && request.remote_ip == local_ip }
+ LOCALHOST.any? { |local_ip| local_ip === request.remote_addr && local_ip === request.remote_ip }
end
def status_code(exception)
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index ae4417b56c..e91a72cbe5 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -350,6 +350,10 @@ module ActionDispatch
scope(:constraints => constraints) { yield }
end
+ def shallow
+ scope(:shallow => true) { yield }
+ end
+
def defaults(defaults = {})
scope(:defaults => defaults) { yield }
end
@@ -374,12 +378,21 @@ module ActionDispatch
@scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
end
+ def merge_shallow_scope(parent, child)
+ parent or child
+ end
+
def merge_path_scope(parent, child)
- Mapper.normalize_path("#{parent}/#{child}")
+ parent_path = (@scope[:shallow] and child.eql?(':id')) ? parent.split('/').last : parent
+ Mapper.normalize_path "#{parent_path}/#{child}"
end
def merge_name_prefix_scope(parent, child)
- parent ? "#{parent}_#{child}" : child
+ if @scope[:shallow]
+ child
+ else
+ parent ? "#{parent}_#{child}" : child
+ end
end
def merge_module_scope(parent, child)
@@ -463,6 +476,10 @@ module ActionDispatch
name.to_s.singularize
end
+ def member_prefix
+ ':id'
+ end
+
def member_name
singular
end
@@ -509,11 +526,19 @@ module ActionDispatch
end
end
+ def nested_prefix
+ id_segment
+ end
+
def nested_options
options = { :name_prefix => member_name }
options["#{singular}_id".to_sym] = id_constraint if id_constraint?
options
end
+
+ def shallow?
+ options[:shallow]
+ end
end
class SingletonResource < Resource #:nodoc:
@@ -532,9 +557,21 @@ module ActionDispatch
end
end
+ def member_prefix
+ ''
+ end
+
def member_name
name
end
+
+ def nested_prefix
+ ''
+ end
+
+ def nested_options
+ { :name_prefix => member_name }
+ end
end
def initialize(*args) #:nodoc:
@@ -544,6 +581,7 @@ module ActionDispatch
def resource(*resources, &block)
options = resources.extract_options!
+ options = (@scope[:options] || {}).merge(options)
if apply_common_behavior_for(:resource, resources, options, &block)
return self
@@ -554,17 +592,17 @@ module ActionDispatch
scope(:path => resource.path, :controller => resource.controller) do
with_scope_level(:resource, resource) do
- scope(:name_prefix => resource.name.to_s, :as => "") do
- yield if block_given?
- end
+ yield if block_given?
- 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)
- delete :destroy if resource.actions.include?(:destroy)
- get :new, :as => resource.name if resource.actions.include?(:new)
- get :edit, :as => resource.name if resource.actions.include?(:edit)
+ with_scope_level(:member) do
+ 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)
+ delete :destroy if resource.actions.include?(:destroy)
+ get :new, :as => resource.name if resource.actions.include?(:new)
+ get :edit, :as => resource.name if resource.actions.include?(:edit)
+ end
end
end
end
@@ -574,6 +612,7 @@ module ActionDispatch
def resources(*resources, &block)
options = resources.extract_options!
+ options = (@scope[:options] || {}).merge(options)
if apply_common_behavior_for(:resources, resources, options, &block)
return self
@@ -581,8 +620,12 @@ module ActionDispatch
resource = Resource.new(resources.pop, options)
- scope(:path => resource.path, :controller => resource.controller) do
+ scope(:path => resource.path, :controller => resource.controller, :shallow => resource.shallow?) do
with_scope_level(:resources, resource) do
+ if @scope[:shallow] && @scope[:name_prefix]
+ @scope[:path] = "/#{@scope[:name_prefix].pluralize}/:#{@scope[:name_prefix]}_id/#{resource.path}"
+ end
+
yield if block_given?
with_scope_level(:collection) do
@@ -596,6 +639,8 @@ module ActionDispatch
with_scope_level(:member) do
scope(':id') do
scope(resource.options) do
+ @scope[:name_prefix] = nil if @scope[:shallow]
+
get :show if resource.actions.include?(:show)
put :update if resource.actions.include?(:update)
delete :destroy if resource.actions.include?(:destroy)
@@ -622,31 +667,36 @@ module ActionDispatch
end
def member
- unless [:resources, :resource].include?(@scope[:scope_level])
- raise ArgumentError, "You can't use member action outside resources and resource scope."
+ unless resource_scope?
+ raise ArgumentError, "can't use member outside resource(s) scope"
end
- case @scope[:scope_level]
- when :resources
- with_scope_level(:member) do
- scope(':id', :name_prefix => parent_resource.member_name, :as => "") do
- yield
- end
+ with_scope_level(:member) do
+ scope(parent_resource.member_prefix, :name_prefix => parent_resource.member_name, :as => "") do
+ yield
end
- when :resource
- with_scope_level(:member) do
+ end
+ end
+
+ def new
+ unless resource_scope?
+ raise ArgumentError, "can't use new outside resource(s) scope"
+ end
+
+ with_scope_level(:new) do
+ scope(new_scope_prefix, :name_prefix => parent_resource.member_name, :as => "") do
yield
end
end
end
def nested
- unless @scope[:scope_level] == :resources
- raise ArgumentError, "can't use nested outside resources scope"
+ unless resource_scope?
+ raise ArgumentError, "can't use nested outside resource(s) scope"
end
with_scope_level(:nested) do
- scope(parent_resource.id_segment, parent_resource.nested_options) do
+ scope(parent_resource.nested_prefix, parent_resource.nested_options) do
yield
end
end
@@ -678,7 +728,7 @@ module ActionDispatch
@scope[:path] = old_path
end
else
- with_exclusive_name_prefix(action) do
+ with_exclusive_name_prefix(action_name_prefix(action, options)) do
return match("#{action_path(action, path_names)}(.:format)", options.reverse_merge(:to => action))
end
end
@@ -691,10 +741,16 @@ module ActionDispatch
return collection { match(*args) }
when :member
return member { match(*args) }
+ when :new
+ return new { match(*args) }
+ end
+
+ if @scope[:scope_level] == :resource
+ return member { match(*args) }
end
- if @scope[:scope_level] == :resources
- raise ArgumentError, "can't define route directly in resources scope"
+ if resource_scope?
+ raise ArgumentError, "can't define route directly in resource(s) scope"
end
super
@@ -716,6 +772,10 @@ module ActionDispatch
path_names[name.to_sym] || name.to_s
end
+ def action_name_prefix(action, options = {})
+ (options[:on] == :new || @scope[:scope_level] == :new) ? "#{action}_new" : action
+ end
+
def apply_common_behavior_for(method, resources, options, &block)
if resources.length > 1
resources.each { |r| send(method, r, options, &block) }
@@ -729,7 +789,7 @@ module ActionDispatch
return true
end
- if @scope[:scope_level] == :resources
+ if resource_scope?
nested do
send(method, resources.pop, options, &block)
end
@@ -739,6 +799,14 @@ module ActionDispatch
false
end
+ def new_scope_prefix
+ @scope[:path_names][:new] || 'new'
+ end
+
+ def resource_scope?
+ [:resource, :resources].include?(@scope[:scope_level])
+ end
+
def with_exclusive_name_prefix(prefix)
begin
old_name_prefix = @scope[:name_prefix]
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 750912b251..57a73dde75 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -68,6 +68,10 @@ module ActionDispatch
clear!
end
+ def helper_names
+ self.module.instance_methods.map(&:to_s)
+ end
+
def clear!
@routes = {}
@helpers = []
@@ -176,7 +180,6 @@ module ActionDispatch
url_for(options)
end
- protected :#{selector}
END_EVAL
helpers << selector
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 9deabf5b3c..0e82b41590 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -267,14 +267,12 @@ module ActionDispatch
if match_with = equals[:text]
matches.delete_if do |match|
text = ""
- text.force_encoding(match_with.encoding) if text.respond_to?(:force_encoding)
stack = match.children.reverse
while node = stack.pop
if node.tag?
stack.concat node.children.reverse
else
content = node.content
- content.force_encoding(match_with.encoding) if content.respond_to?(:force_encoding)
text << content
end
end
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index 8f0c5d939f..532d060c06 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -3,7 +3,7 @@ module ActionPack
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta3"
+ BUILD = "beta4"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 626cc7d3b0..25426a5547 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -620,7 +620,10 @@ module ActionView
options.symbolize_keys!
src = options[:src] = path_to_image(source)
- options[:alt] = options.fetch(:alt){ File.basename(src, '.*').capitalize }
+
+ unless src =~ /^cid:/
+ options[:alt] = options.fetch(:alt){ File.basename(src, '.*').capitalize }
+ end
if size = options.delete(:size)
options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
@@ -754,7 +757,7 @@ module ActionView
end
def is_uri?(path)
- path =~ %r{^[-a-z]+://}
+ path =~ %r{^[-a-z]+://|^cid:}
end
# Pick an asset host for this source. Returns +nil+ if no host is set,
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index a904af56bb..8251ed18f4 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -32,27 +32,27 @@ module ActionView
# <i>Topics listed alphabetically</i>
# <% end %>
def cache(name = {}, options = nil, &block)
- safe_concat fragment_for(name, options, &block)
+ if controller.perform_caching
+ safe_concat(fragment_for(name, options, &block))
+ else
+ yield
+ end
+
nil
end
private
# TODO: Create an object that has caching read/write on it
def fragment_for(name = {}, options = nil, &block) #:nodoc:
- if controller.perform_caching
- if controller.fragment_exist?(name, options)
- controller.read_fragment(name, options)
- else
- # VIEW TODO: Make #capture usable outside of ERB
- # This dance is needed because Builder can't use capture
- pos = output_buffer.length
- yield
- fragment = output_buffer.slice!(pos..-1)
- controller.write_fragment(name, fragment, options)
- end
+ if controller.fragment_exist?(name, options)
+ controller.read_fragment(name, options)
else
- ret = yield
- ActiveSupport::SafeBuffer.new(ret) if ret.is_a?(String)
+ # VIEW TODO: Make #capture usable outside of ERB
+ # This dance is needed because Builder can't use capture
+ pos = output_buffer.length
+ yield
+ fragment = output_buffer.slice!(pos..-1)
+ controller.write_fragment(name, fragment, options)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index c09d01eeee..66277f79fe 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -110,7 +110,7 @@ module ActionView
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
- "<#{name}#{tag_options}>#{ERB::Util.h(content)}</#{name}>".html_safe
+ "<#{name}#{tag_options}>#{escape ? ERB::Util.h(content) : content}</#{name}>".html_safe
end
def tag_options(options, escape = true)
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index bfad9f8d31..8f63845d49 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -74,6 +74,7 @@ module ActionView
options.reverse_merge!(:length => 30)
+ text = sanitize(text) unless text.html_safe? || options[:safe]
text.truncate(options.delete(:length), options) if text
end
@@ -105,6 +106,7 @@ module ActionView
end
options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>')
+ text = sanitize(text) unless text.html_safe? || options[:safe]
if text.blank? || phrases.blank?
text
else
@@ -244,13 +246,14 @@ module ActionView
#
def textilize(text, *options)
options ||= [:hard_breaks]
+ text = sanitize(text) unless text.html_safe? || options.delete(:safe)
if text.blank?
""
else
textilized = RedCloth.new(text, options)
textilized.to_html
- end
+ end.html_safe
end
# Returns the text with all the Textile codes turned into HTML tags,
@@ -271,8 +274,8 @@ module ActionView
#
# textilize_without_paragraph("Visit the Rails website "here":http://www.rubyonrails.org/.)
# # => "Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>."
- def textilize_without_paragraph(text)
- textiled = textilize(text)
+ def textilize_without_paragraph(text, *options)
+ textiled = textilize(text, *options)
if textiled[0..2] == "<p>" then textiled = textiled[3..-1] end
if textiled[-4..-1] == "</p>" then textiled = textiled[0..-5] end
return textiled
@@ -295,8 +298,9 @@ module ActionView
#
# markdown('![The ROR logo](http://rubyonrails.com/images/rails.png "Ruby on Rails")')
# # => '<p><img src="http://rubyonrails.com/images/rails.png" alt="The ROR logo" title="Ruby on Rails" /></p>'
- def markdown(text)
- text.blank? ? "" : BlueCloth.new(text).to_html
+ def markdown(text, *options)
+ text = sanitize(text) unless text.html_safe? || options.delete(:safe)
+ (text.blank? ? "" : BlueCloth.new(text).to_html).html_safe
end
# Returns +text+ transformed into HTML using simple formatting rules.
@@ -320,14 +324,15 @@ module ActionView
#
# simple_format("Look ma! A class!", :class => 'description')
# # => "<p class='description'>Look ma! A class!</p>"
- def simple_format(text, html_options={})
+ def simple_format(text, html_options={}, options={})
+ text = '' if text.nil?
start_tag = tag('p', html_options, true)
- text = h(text)
+ text = sanitize(text) unless text.html_safe? || options[:safe]
text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph
text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
text.insert 0, start_tag
- text.safe_concat("</p>")
+ text.html_safe.safe_concat("</p>")
end
# Turns all URLs and e-mail addresses into clickable links. The <tt>:link</tt> option
@@ -368,7 +373,7 @@ module ActionView
# # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>.
# Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
def auto_link(text, *args, &block)#link = :all, html = {}, &block)
- return '' if text.blank?
+ return ''.html_safe if text.blank?
options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter
unless args.empty?
@@ -378,9 +383,9 @@ module ActionView
options.reverse_merge!(:link => :all, :html => {})
case options[:link].to_sym
- when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], &block), options[:html], &block)
+ when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], options, &block), options[:html], &block)
when :email_addresses then auto_link_email_addresses(text, options[:html], &block)
- when :urls then auto_link_urls(text, options[:html], &block)
+ when :urls then auto_link_urls(text, options[:html], options, &block)
end
end
@@ -544,7 +549,7 @@ module ActionView
# Turns all urls into clickable links. If a block is given, each url
# is yielded and the result is used as the link text.
- def auto_link_urls(text, html_options = {})
+ def auto_link_urls(text, html_options = {}, options = {})
link_attributes = html_options.stringify_keys
text.gsub(AUTO_LINK_RE) do
scheme, href = $1, $&
@@ -566,21 +571,22 @@ module ActionView
link_text = block_given?? yield(href) : href
href = 'http://' + href unless scheme
- content_tag(:a, link_text, link_attributes.merge('href' => href)) + punctuation.reverse.join('')
+ content_tag(:a, link_text, link_attributes.merge('href' => href), !(options[:safe] || text.html_safe?)) + punctuation.reverse.join('')
end
- end
+ end.html_safe
end
# Turns all email addresses into clickable links. If a block is given,
# each email is yielded and the result is used as the link text.
- def auto_link_email_addresses(text, html_options = {})
+ def auto_link_email_addresses(text, html_options = {}, options = {})
text.gsub(AUTO_EMAIL_RE) do
text = $&
if auto_linked?($`, $')
- text
+ text.html_safe
else
display_text = (block_given?) ? yield(text) : text
+ display_text = sanitize(display_text) unless options[:safe]
mail_to text, display_text, html_options
end
end
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 3aaa5e401c..823226cb9c 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -188,7 +188,7 @@ module ActionView
begin
yield
ensure
- _set_detail(:formats, formats)
+ _set_detail(:formats, old_formats)
end
end
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 4431eb2e2a..c161bea945 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -644,21 +644,6 @@ class FragmentCachingTest < ActionController::TestCase
assert_equal 'will not expire ;-)', @store.read('views/primalgrasp')
end
- def test_fragment_for_with_disabled_caching
- @controller.perform_caching = false
-
- @store.write('views/expensive', 'fragment content')
- fragment_computed = false
-
- view_context = @controller.view_context
-
- buffer = 'generated till now -> '.html_safe
- buffer << view_context.send(:fragment_for, 'expensive') { fragment_computed = true }
-
- assert fragment_computed
- assert_equal 'generated till now -> ', buffer
- end
-
def test_fragment_for
@store.write('views/expensive', 'fragment content')
fragment_computed = false
diff --git a/actionpack/test/controller/new_base/render_rjs_test.rb b/actionpack/test/controller/new_base/render_rjs_test.rb
index b602b9f8e9..74bf865b54 100644
--- a/actionpack/test/controller/new_base/render_rjs_test.rb
+++ b/actionpack/test/controller/new_base/render_rjs_test.rb
@@ -2,7 +2,10 @@ require 'abstract_unit'
module RenderRjs
class BasicController < ActionController::Base
+ layout "application", :only => :index_respond_to
+
self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.html.erb" => "",
"render_rjs/basic/index.js.rjs" => "page[:customer].replace_html render(:partial => 'customer')",
"render_rjs/basic/index_html.js.rjs" => "page[:customer].replace_html :partial => 'customer'",
"render_rjs/basic/index_no_js.js.erb" => "<%= render(:partial => 'developer') %>",
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index ffa4f50b00..a294535e88 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -34,6 +34,33 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ resources :users do
+ shallow do
+ resources :photos do
+ resources :types do
+ member do
+ post :preview
+ end
+ collection do
+ delete :erase
+ end
+ end
+ end
+ end
+ end
+
+ shallow do
+ resources :teams do
+ resources :players
+ end
+
+ resources :countries do
+ resources :cities do
+ resources :places
+ end
+ end
+ end
+
match 'account/logout' => redirect("/logout"), :as => :logout_redirect
match 'account/login', :to => redirect("/login")
@@ -69,8 +96,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
scope 'pt', :name_prefix => 'pt' do
- resources :projects, :path_names => { :edit => 'editar' }, :path => 'projetos'
- resource :admin, :path_names => { :new => 'novo' }, :path => 'administrador'
+ resources :projects, :path_names => { :edit => 'editar', :new => 'novo' }, :path => 'projetos' do
+ post :preview, :on => :new
+ end
+ resource :admin, :path_names => { :new => 'novo' }, :path => 'administrador' do
+ post :preview, :on => :new
+ end
+ resources :products, :path_names => { :new => 'novo' } do
+ new do
+ post :preview
+ end
+ end
end
resources :projects, :controller => :project do
@@ -119,6 +155,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
resources :replies do
+ new do
+ post :preview
+ end
+
member do
put :answer, :to => :mark_as_answer
delete :answer, :to => :unmark_as_answer
@@ -189,10 +229,13 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
root :to => 'projects#index'
end
- resources :products, :constraints => { :id => /\d{4}/ } do
- root :to => "products#root"
- get :favorite, :on => :collection
- resources :images
+ scope :only => [:index, :show] do
+ resources :products, :constraints => { :id => /\d{4}/ } do
+ root :to => "products#root"
+ get :favorite, :on => :collection
+ resources :images
+ end
+ resource :account
end
resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ }
@@ -207,6 +250,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
match "whatever/:controller(/:action(/:id))"
+
+ resource :profile do
+ get :settings
+
+ new do
+ post :preview
+ end
+ end
end
end
@@ -728,6 +779,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_shallow_routes
+ with_test_routes do
+ assert_equal '/photos/4', photo_path(4)
+ assert_equal '/types/10/edit', edit_type_path(10)
+ assert_equal '/types/5/preview', preview_type_path(5)
+ assert_equal '/photos/2/types', photo_types_path(2)
+ assert_equal '/cities/1/places', url_for(:controller => :places, :action => :index, :city_id => 1, :only_path => true)
+ assert_equal '/teams/new', url_for(:controller => :teams, :action => :new, :only_path => true)
+ assert_equal '/photos/11/types/erase', url_for(:controller => :types, :action => :erase, :photo_id => 11, :only_path => true)
+ end
+ end
+
def test_update_project_person
with_test_routes do
get '/projects/1/people/2/update'
@@ -1038,6 +1101,54 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_resource_new_actions
+ with_test_routes do
+ assert_equal '/replies/new/preview', preview_new_reply_path
+ assert_equal '/pt/projetos/novo/preview', preview_new_pt_project_path
+ assert_equal '/pt/administrador/novo/preview', preview_new_pt_admin_path
+ assert_equal '/pt/products/novo/preview', preview_new_pt_product_path
+ assert_equal '/profile/new/preview', preview_new_profile_path
+
+ post '/replies/new/preview'
+ assert_equal 'replies#preview', @response.body
+
+ post '/pt/projetos/novo/preview'
+ assert_equal 'projects#preview', @response.body
+
+ post '/pt/administrador/novo/preview'
+ assert_equal 'admins#preview', @response.body
+
+ post '/pt/products/novo/preview'
+ assert_equal 'products#preview', @response.body
+
+ post '/profile/new/preview'
+ assert_equal 'profiles#preview', @response.body
+ end
+ end
+
+ def test_resource_merges_options_from_scope
+ with_test_routes do
+ assert_raise(NameError) { new_account_path }
+
+ get '/account/new'
+ assert_equal 404, status
+ end
+ end
+
+ def test_resources_merges_options_from_scope
+ with_test_routes do
+ assert_raise(NoMethodError) { edit_product_path('1') }
+
+ get '/products/1/edit'
+ assert_equal 404, status
+
+ assert_raise(NoMethodError) { edit_product_image_path('1', '2') }
+
+ post '/products/1/images/2/edit'
+ assert_equal 404, status
+ end
+ end
+
private
def with_test_routes
yield
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index b447b0715c..4966527f4d 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -53,7 +53,7 @@ class ShowExceptionsTest < ActionController::IntegrationTest
test "rescue locally from a local request" do
@app = ProductionApp
- ['127.0.0.1', '::1'].each do |ip_address|
+ ['127.0.0.1', '127.0.0.127', '::1', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1%0'].each do |ip_address|
self.remote_addr = ip_address
get "/", {}, {'action_dispatch.show_exceptions' => true}
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index b6a6f52876..633641514e 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -404,6 +404,15 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
end
+ def test_image_tag_interpreting_email_cid_correctly
+ # An inline image has no need for an alt tag to be automatically generated from the cid:
+ assert_equal '<img src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid")
+ end
+
+ def test_image_tag_interpreting_email_adding_optional_alt_tag
+ assert_equal '<img alt="Image" src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid", :alt => "Image")
+ 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
diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb
index 256d9bdcde..ec5fe3d1d7 100644
--- a/actionpack/test/template/tag_helper_test.rb
+++ b/actionpack/test/template/tag_helper_test.rb
@@ -39,6 +39,8 @@ class TagHelperTest < ActionView::TestCase
content_tag("a", "Create", :href => "create")
assert_equal "<p>&lt;script&gt;evil_js&lt;/script&gt;</p>",
content_tag(:p, '<script>evil_js</script>')
+ assert_equal "<p><script>evil_js</script></p>",
+ content_tag(:p, '<script>evil_js</script>', nil, false)
end
def test_content_tag_with_block_in_erb
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index bb808b77a5..64f1d46413 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -7,6 +7,12 @@ rescue LoadError
$stderr.puts "Skipping textilize tests. `gem install RedCloth` to enable."
end
+begin
+ require 'bluecloth'
+rescue LoadError
+ $stderr.puts "Skipping markdown tests. 'gem install bluecloth' to enable."
+end
+
class TextHelperTest < ActionView::TestCase
tests ActionView::Helpers::TextHelper
include TestingSandbox
@@ -45,19 +51,42 @@ class TextHelperTest < ActionView::TestCase
assert simple_format("<b> test with html tags </b>").html_safe?
end
- def test_simple_format_should_escape_unsafe_input
- assert_equal "<p>&lt;b&gt; test with unsafe string &lt;/b&gt;</p>", simple_format("<b> test with unsafe string </b>")
+ def test_simple_format_should_sanitize_unsafe_input
+ assert_equal "<p><b> test with unsafe string </b></p>", simple_format("<b> test with unsafe string </b><script>code!</script>")
+ end
+
+ def test_simple_format_should_not_sanitize_input_if_safe_option
+ assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :safe => true)
end
- def test_simple_format_should_not_escape_safe_input
+ def test_simple_format_should_not_sanitize_safe_input
assert_equal "<p><b> test with safe string </b></p>", simple_format("<b> test with safe string </b>".html_safe)
end
+ def test_truncate_should_be_html_safe
+ assert truncate("Hello World!", :length => 12).html_safe?
+ end
+
def test_truncate
assert_equal "Hello World!", truncate("Hello World!", :length => 12)
assert_equal "Hello Wor...", truncate("Hello World!!", :length => 12)
end
+ def test_truncate_should_sanitize_unsafe_input
+ assert_equal "Hello World!", truncate("Hello <script>code!</script>World!", :length => 12)
+ assert_equal "Hello Wor...", truncate("Hello <script>code!</script>World!!", :length => 12)
+ end
+
+ def test_truncate_should_not_sanitize_input_if_safe_option
+ assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!", :length => 12, :safe => true)
+ assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12, :safe => true)
+ end
+
+ def test_truncate_should_not_sanitize_safe_input
+ assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!".html_safe, :length => 12)
+ assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!".html_safe, :length => 12)
+ end
+
def test_truncate_should_use_default_length_of_30
str = "This is a string that will go longer then the default truncate length of 30"
assert_equal str[0...27] + "...", truncate(str)
@@ -93,7 +122,11 @@ class TextHelperTest < ActionView::TestCase
end
end
- def test_highlighter
+ def test_highlight_should_be_html_safe
+ assert highlight("This is a beautiful morning", "beautiful").html_safe?
+ end
+
+ def test_highlight
assert_equal(
"This is a <strong class=\"highlight\">beautiful</strong> morning",
highlight("This is a beautiful morning", "beautiful")
@@ -117,6 +150,27 @@ class TextHelperTest < ActionView::TestCase
assert_equal ' ', highlight(' ', 'blank text is returned verbatim')
end
+ def test_highlight_should_sanitize_unsafe_input
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful</strong> morning",
+ highlight("This is a beautiful morning<script>code!</script>", "beautiful")
+ )
+ end
+
+ def test_highlight_should_not_sanitize_input_if_safe_option
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful</strong> morning<script>code!</script>",
+ highlight("This is a beautiful morning<script>code!</script>", "beautiful", :safe => true)
+ )
+ end
+
+ def test_highlight_should_not_sanitize_safe_input
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful</strong> morning<script>code!</script>",
+ highlight("This is a beautiful morning<script>code!</script>".html_safe, "beautiful")
+ )
+ end
+
def test_highlight_with_regexp
assert_equal(
"This is a <strong class=\"highlight\">beautiful!</strong> morning",
@@ -163,7 +217,7 @@ class TextHelperTest < ActionView::TestCase
highlight("<p class=\"beautiful\">This is a beautiful morning, but also a beautiful day</p>", "beautiful")
)
assert_equal(
- "<p>This is a <strong class=\"highlight\">beautiful</strong> <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ "<p>This is a <strong class=\"highlight\">beautiful</strong> <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&amp;when=now+then\">morning</a>, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
highlight("<p>This is a beautiful <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a beautiful day</p>", "beautiful")
)
end
@@ -286,7 +340,17 @@ class TextHelperTest < ActionView::TestCase
%{<a href="#{CGI::escapeHTML href}">#{CGI::escapeHTML link_text}</a>}
end
- def test_auto_linking
+ def test_auto_link_should_be_html_safe
+ email_raw = 'santiago@wyeworks.com'
+ link_raw = 'http://www.rubyonrails.org'
+
+ assert auto_link(nil).html_safe?
+ assert auto_link('').html_safe?
+ assert auto_link("#{link_raw} #{link_raw} #{link_raw}").html_safe?
+ assert auto_link("hello #{email_raw}").html_safe?
+ end
+
+ def test_auto_link
email_raw = 'david@loudthinking.com'
email_result = %{<a href="mailto:#{email_raw}">#{email_raw}</a>}
link_raw = 'http://www.rubyonrails.com'
@@ -378,6 +442,21 @@ class TextHelperTest < ActionView::TestCase
assert_equal %(<p>#{link10_result} Link</p>), auto_link("<p>#{link10_raw} Link</p>")
end
+ def test_auto_link_should_sanitize_unsafe_input
+ link_raw = %{http://www.rubyonrails.com?id=1&num=2}
+ assert_equal %{<a href="http://www.rubyonrails.com?id=1&amp;num=2">http://www.rubyonrails.com?id=1&amp;num=2</a>}, auto_link(link_raw)
+ end
+
+ def test_auto_link_should_sanitize_unsafe_input
+ link_raw = %{http://www.rubyonrails.com?id=1&num=2}
+ assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw, :safe => true)
+ end
+
+ def test_auto_link_should_not_sanitize_safe_input
+ link_raw = %{http://www.rubyonrails.com?id=1&num=2}
+ assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw.html_safe)
+ end
+
def test_auto_link_other_protocols
ftp_raw = 'ftp://example.com/file.txt'
assert_equal %(Download #{generate_result(ftp_raw)}), auto_link("Download #{ftp_raw}")
@@ -587,7 +666,12 @@ class TextHelperTest < ActionView::TestCase
assert_equal(%w{Specialized Fuji Giant}, @cycles)
end
+ # TODO test textilize_without_paragraph and markdown
if defined? RedCloth
+ def test_textilize_should_be_html_safe
+ assert textilize("*This is Textile!* Rejoice!").html_safe?
+ end
+
def test_textilize
assert_equal("<p><strong>This is Textile!</strong> Rejoice!</p>", textilize("*This is Textile!* Rejoice!"))
end
@@ -600,8 +684,82 @@ class TextHelperTest < ActionView::TestCase
assert_equal("<p>This is worded &lt;strong&gt;strongly&lt;/strong&gt;</p>", textilize("This is worded <strong>strongly</strong>", :filter_html))
end
+ def test_textilize_should_sanitize_unsafe_input
+ assert_equal("<p>This is worded <strong>strongly</strong></p>", textilize("This is worded <strong>strongly</strong><script>code!</script>"))
+ end
+
+ def test_textilize_should_not_sanitize_input_if_safe_option
+ assert_equal("<p>This is worded <strong>strongly</strong><script>code!</script></p>", textilize("This is worded <strong>strongly</strong><script>code!</script>", :safe))
+ end
+
+ def test_textilize_should_not_sanitize_safe_input
+ assert_equal("<p>This is worded <strong>strongly</strong><script>code!</script></p>", textilize("This is worded <strong>strongly</strong><script>code!</script>".html_safe))
+ end
+
def test_textilize_with_hard_breaks
assert_equal("<p>This is one scary world.<br />\n True.</p>", textilize("This is one scary world.\n True."))
end
+
+ def test_textilize_without_paragraph_should_be_html_safe
+ textilize_without_paragraph("*This is Textile!* Rejoice!").html_safe?
+ end
+
+ def test_textilize_without_paragraph
+ assert_equal("<strong>This is Textile!</strong> Rejoice!", textilize_without_paragraph("*This is Textile!* Rejoice!"))
+ end
+
+ def test_textilize_without_paragraph_with_blank
+ assert_equal("", textilize_without_paragraph(""))
+ end
+
+ def test_textilize_without_paragraph_with_options
+ assert_equal("This is worded &lt;strong&gt;strongly&lt;/strong&gt;", textilize_without_paragraph("This is worded <strong>strongly</strong>", :filter_html))
+ end
+
+ def test_textilize_without_paragraph_should_sanitize_unsafe_input
+ assert_equal("This is worded <strong>strongly</strong>", textilize_without_paragraph("This is worded <strong>strongly</strong><script>code!</script>"))
+ end
+
+ def test_textilize_without_paragraph_should_not_sanitize_input_if_safe_option
+ assert_equal("This is worded <strong>strongly</strong><script>code!</script>", textilize_without_paragraph("This is worded <strong>strongly</strong><script>code!</script>", :safe))
+ end
+
+ def test_textilize_without_paragraph_should_not_sanitize_safe_input
+ assert_equal("This is worded <strong>strongly</strong><script>code!</script>", textilize_without_paragraph("This is worded <strong>strongly</strong><script>code!</script>".html_safe))
+ end
+
+ def test_textilize_without_paragraph_with_hard_breaks
+ assert_equal("This is one scary world.<br />\n True.", textilize_without_paragraph("This is one scary world.\n True."))
+ end
+ end
+
+ if defined? BlueCloth
+ def test_markdown_should_be_html_safe
+ assert markdown("We are using __Markdown__ now!").html_safe?
+ end
+
+ def test_markdown
+ assert_equal("<p>We are using <strong>Markdown</strong> now!</p>", markdown("We are using __Markdown__ now!"))
+ end
+
+ def test_markdown_with_blank
+ assert_equal("", markdown(""))
+ end
+
+ def test_markdown_should_sanitize_unsafe_input
+ assert_equal("<p>This is worded <strong>strongly</strong></p>", markdown("This is worded <strong>strongly</strong><script>code!</script>"))
+ end
+
+ def test_markdown_should_not_sanitize_input_if_safe_option
+ assert_equal("<p>This is worded <strong>strongly</strong><script>code!</script></p>", markdown("This is worded <strong>strongly</strong><script>code!</script>", :safe))
+ end
+
+ def test_markdown_should_not_sanitize_safe_input
+ assert_equal("<p>This is worded <strong>strongly</strong><script>code!</script></p>", markdown("This is worded <strong>strongly</strong><script>code!</script>".html_safe))
+ end
+
+ def test_markdown_with_hard_breaks
+ assert_equal("<p>This is one scary world.</p>\n\n<p>True.</p>", markdown("This is one scary world.\n\nTrue."))
+ end
end
end
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index 43cf67d2b7..a5e7f300d9 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,4 +1,4 @@
-*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
+*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* JSON supports a custom root option: to_json(:root => 'custom') #4515 [Jatinder Singh]
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index ffdfbfcaaf..90305978c4 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -1,5 +1,5 @@
require 'active_support/json'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/class/attribute'
module ActiveModel
module Serializers
@@ -10,7 +10,8 @@ module ActiveModel
included do
extend ActiveModel::Naming
- cattr_accessor :include_root_in_json, :instance_writer => true
+ class_attribute :include_root_in_json
+ self.include_root_in_json = true
end
# Returns a JSON string representing the model. Some configuration is
@@ -92,7 +93,9 @@ module ActiveModel
end
def from_json(json)
- self.attributes = ActiveSupport::JSON.decode(json)
+ hash = ActiveSupport::JSON.decode(json)
+ hash = hash.values.first if include_root_in_json
+ self.attributes = hash
self
end
end
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index aac47d5f81..c36fc8b1f7 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -3,7 +3,7 @@ module ActiveModel
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta3"
+ BUILD = "beta4"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activemodel/test/cases/serializeration/json_serialization_test.rb b/activemodel/test/cases/serializeration/json_serialization_test.rb
index 7e89815c96..04b50e5bb8 100644
--- a/activemodel/test/cases/serializeration/json_serialization_test.rb
+++ b/activemodel/test/cases/serializeration/json_serialization_test.rb
@@ -22,35 +22,41 @@ class JsonSerializationTest < ActiveModel::TestCase
end
test "should include root in json" do
+ json = @contact.to_json
+
+ assert_match %r{^\{"contact":\{}, json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_match %r{"age":16}, json
+ assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_match %r{"awesome":true}, json
+ assert_match %r{"preferences":\{"shows":"anime"\}}, json
+ end
+
+ test "should not include root in json" do
begin
- Contact.include_root_in_json = true
+ Contact.include_root_in_json = false
json = @contact.to_json
- assert_match %r{^\{"contact":\{}, json
+ assert_no_match %r{^\{"contact":\{}, json
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
assert_match %r{"awesome":true}, json
assert_match %r{"preferences":\{"shows":"anime"\}}, json
ensure
- Contact.include_root_in_json = false
+ Contact.include_root_in_json = true
end
end
test "should include custom root in json" do
- begin
- Contact.include_root_in_json = true
- json = @contact.to_json(:root => 'json_contact')
+ json = @contact.to_json(:root => 'json_contact')
- assert_match %r{^\{"json_contact":\{}, json
- assert_match %r{"name":"Konata Izumi"}, json
- assert_match %r{"age":16}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
- assert_match %r{"awesome":true}, json
- assert_match %r{"preferences":\{"shows":"anime"\}}, json
- ensure
- Contact.include_root_in_json = false
- end
+ assert_match %r{^\{"json_contact":\{}, json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_match %r{"age":16}, json
+ assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_match %r{"awesome":true}, json
+ assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
test "should encode all encodable attributes" do
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index d8f68d7c28..348248e849 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,4 +1,4 @@
-*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
+*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* Add index length support for MySQL. #1852 [Emili Parreno, Pratik Naik]
@@ -12,8 +12,6 @@
* find_or_create_by_attr(value, ...) works when attr is protected. #4457 [Santiago Pastorino, Marc-André Lafortune]
-* New callbacks: after_commit and after_rollback. Do expensive operations like image thumbnailing after_commit instead of after_save. #2991 [Brian Durand]
-
* Serialized attributes are not converted to YAML if they are any of the formats that can be serialized to XML (like Hash, Array and Strings). [José Valim]
* Destroy uses optimistic locking. If lock_version on the record you're destroying doesn't match lock_version in the database, a StaleObjectError is raised. #1966 [Curtis Hawthorne]
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 44c8fb83e1..5aea992801 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -23,6 +23,6 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
- s.add_dependency('arel', '~> 0.3.3')
+ s.add_dependency('arel', '~> 0.4.0')
s.add_dependency('tzinfo', '~> 0.3.16')
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 284ae6695b..c1e16d08cb 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1756,7 +1756,8 @@ module ActiveRecord
end
def count_aliases_from_table_joins(name)
- quoted_name = join_base.active_record.connection.quote_table_name(name.downcase)
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
+ quoted_name = join_base.active_record.connection.quote_table_name(name.downcase).downcase
join_sql = join_base.table_joins.to_s.downcase
join_sql.blank? ? 0 :
# Table names
@@ -1902,7 +1903,7 @@ module ActiveRecord
end
def ==(other)
- other.is_a?(JoinBase) &&
+ other.class == self.class &&
other.active_record == active_record &&
other.table_joins == table_joins
end
@@ -1973,7 +1974,7 @@ module ActiveRecord
end
def ==(other)
- other.is_a?(JoinAssociation) &&
+ other.class == self.class &&
other.reflection == reflection &&
other.parent == parent
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index b9fb452eee..0c87e052c4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -122,8 +122,6 @@ module ActiveRecord
requires_new = options[:requires_new] || !last_transaction_joinable
transaction_open = false
- @_current_transaction_records ||= []
-
begin
if block_given?
if requires_new || open_transactions == 0
@@ -134,7 +132,6 @@ module ActiveRecord
end
increment_open_transactions
transaction_open = true
- @_current_transaction_records.push([])
end
yield
end
@@ -144,10 +141,8 @@ module ActiveRecord
decrement_open_transactions
if open_transactions == 0
rollback_db_transaction
- rollback_transaction_records(true)
else
rollback_to_savepoint
- rollback_transaction_records(false)
end
end
raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
@@ -162,35 +157,20 @@ module ActiveRecord
begin
if open_transactions == 0
commit_db_transaction
- commit_transaction_records
else
release_savepoint
- save_point_records = @_current_transaction_records.pop
- unless save_point_records.blank?
- @_current_transaction_records.push([]) if @_current_transaction_records.empty?
- @_current_transaction_records.last.concat(save_point_records)
- end
end
rescue Exception => database_transaction_rollback
if open_transactions == 0
rollback_db_transaction
- rollback_transaction_records(true)
else
rollback_to_savepoint
- rollback_transaction_records(false)
end
raise
end
end
end
- # Register a record with the current transaction so that its after_commit and after_rollback callbacks
- # can be called.
- def add_transaction_record(record)
- last_batch = @_current_transaction_records.last
- last_batch << record if last_batch
- end
-
# Begins the transaction (and turns off auto-committing).
def begin_db_transaction() end
@@ -288,42 +268,6 @@ module ActiveRecord
limit.to_i
end
end
-
- # Send a rollback message to all records after they have been rolled back. If rollback
- # is false, only rollback records since the last save point.
- def rollback_transaction_records(rollback) #:nodoc
- if rollback
- records = @_current_transaction_records.flatten
- @_current_transaction_records.clear
- else
- records = @_current_transaction_records.pop
- end
-
- unless records.blank?
- records.uniq.each do |record|
- begin
- record.rolledback!(rollback)
- rescue Exception => e
- record.logger.error(e) if record.respond_to?(:logger)
- end
- end
- end
- end
-
- # Send a commit message to all records after they have been committed.
- def commit_transaction_records #:nodoc
- records = @_current_transaction_records.flatten
- @_current_transaction_records.clear
- unless records.blank?
- records.uniq.each do |record|
- begin
- record.committed!
- rescue Exception => e
- record.logger.error(e) if record.respond_to?(:logger)
- end
- end
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index e8a45bb3c6..deb62e3802 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -34,6 +34,10 @@ module ActiveRecord
end
def binary_to_string(value)
+ if value.respond_to?(:force_encoding) && value.encoding != Encoding::ASCII_8BIT
+ value = value.force_encoding(Encoding::ASCII_8BIT)
+ end
+
value.gsub(/%00|%25/n) do |b|
case b
when "%00" then "\0"
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 4e62187449..99c914d7fc 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -356,13 +356,16 @@ module ActiveRecord
end
def references_eager_loaded_tables?
- joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.uniq
+ # always convert table names to downcase as in Oracle quoted table names are in uppercase
+ joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.map(&:downcase).uniq
(tables_in_string(to_sql) - joined_tables).any?
end
def tables_in_string(string)
return [] if string.blank?
- string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.uniq
+ # always convert table names to downcase as in Oracle quoted table names are in uppercase
+ # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
+ string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.map(&:downcase).uniq - ['raw_sql_']
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 5a8e2ce880..3f2c1911e7 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -8,11 +8,6 @@ module ActiveRecord
class TransactionError < ActiveRecordError # :nodoc:
end
- included do
- define_model_callbacks :commit, :commit_on_update, :commit_on_create, :commit_on_destroy, :only => :after
- define_model_callbacks :rollback, :rollback_on_update, :rollback_on_create, :rollback_on_destroy
- end
-
# Transactions are protective blocks where SQL statements are only permanent
# if they can all succeed as one atomic action. The classic example is a
# transfer between two accounts where you can only have a deposit if the
@@ -163,21 +158,6 @@ module ActiveRecord
# http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
# for more information about savepoints.
#
- # === Callbacks
- #
- # There are two types of callbacks associated with committing and rolling back transactions:
- # +after_commit+ and +after_rollback+.
- #
- # +after_commit+ callbacks are called on every record saved or destroyed within a
- # transaction immediately after the transaction is committed. +after_rollback+ callbacks
- # are called on every record saved or destroyed within a transaction immediately after the
- # transaction or savepoint is rolled back.
- #
- # These callbacks are useful for interacting with other systems since you will be guaranteed
- # that the callback is only executed when the database is in a permanent state. For example,
- # +after_commit+ is a good spot to put in a hook to clearing a cache since clearing it from
- # within a transaction could trigger the cache to be regenerated before the database is updated.
- #
# === Caveats
#
# If you're on MySQL, then do not use DDL operations in nested transactions
@@ -225,50 +205,19 @@ module ActiveRecord
# Reset id and @new_record if the transaction rolls back.
def rollback_active_record_state!
- remember_transaction_record_state
+ id_present = has_attribute?(self.class.primary_key)
+ previous_id = id
+ previous_new_record = new_record?
yield
rescue Exception
- restore_transaction_record_state
- raise
- ensure
- clear_transaction_record_state
- end
-
- # Call the after_commit callbacks
- def committed! #:nodoc:
- if transaction_record_state(:new_record)
- _run_commit_on_create_callbacks
- elsif transaction_record_state(:destroyed)
- _run_commit_on_destroy_callbacks
+ @new_record = previous_new_record
+ if id_present
+ self.id = previous_id
else
- _run_commit_on_update_callbacks
- end
- _run_commit_callbacks
- ensure
- clear_transaction_record_state
- end
-
- # Call the after rollback callbacks. The restore_state argument indicates if the record
- # state should be rolled back to the beginning or just to the last savepoint.
- def rolledback!(force_restore_state = false) #:nodoc:
- if transaction_record_state(:new_record)
- _run_rollback_on_create_callbacks
- elsif transaction_record_state(:destroyed)
- _run_rollback_on_destroy_callbacks
- else
- _run_rollback_on_update_callbacks
- end
- _run_rollback_callbacks
- ensure
- restore_transaction_record_state(force_restore_state)
- end
-
- # Add the record to the current transaction so that the :after_rollback and :after_commit callbacks
- # can be called.
- def add_to_transaction
- if self.class.connection.add_transaction_record(self)
- remember_transaction_record_state
+ @attributes.delete(self.class.primary_key)
+ @attributes_cache.delete(self.class.primary_key)
end
+ raise
end
# Executes +method+ within a transaction and captures its return value as a
@@ -280,59 +229,10 @@ module ActiveRecord
def with_transaction_returning_status
status = nil
self.class.transaction do
- add_to_transaction
status = yield
raise ActiveRecord::Rollback unless status
end
status
end
-
- protected
-
- # Save the new record state and id of a record so it can be restored later if a transaction fails.
- def remember_transaction_record_state #:nodoc
- @_start_transaction_state ||= {}
- unless @_start_transaction_state.include?(:new_record)
- @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
- @_start_transaction_state[:new_record] = @new_record
- end
- unless @_start_transaction_state.include?(:destroyed)
- @_start_transaction_state[:destroyed] = @new_record
- end
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
- end
-
- # Clear the new record state and id of a record.
- def clear_transaction_record_state #:nodoc
- if defined?(@_start_transaction_state)
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
- remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1
- end
- end
-
- # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
- def restore_transaction_record_state(force = false) #:nodoc
- if defined?(@_start_transaction_state)
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
- if @_start_transaction_state[:level] < 1
- restore_state = remove_instance_variable(:@_start_transaction_state)
- if restore_state
- @new_record = restore_state[:new_record]
- @destroyed = restore_state[:destroyed]
- if restore_state[:id]
- self.id = restore_state[:id]
- else
- @attributes.delete(self.class.primary_key)
- @attributes_cache.delete(self.class.primary_key)
- end
- end
- end
- end
- end
-
- # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
- def transaction_record_state(state) #:nodoc
- @_start_transaction_state[state] if defined?(@_start_transaction_state)
- end
end
end
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index bf6b995a37..d18fed0131 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -3,7 +3,7 @@ module ActiveRecord
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta3"
+ BUILD = "beta4"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 9b28766405..fc08c2178a 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -145,22 +145,13 @@ class AdapterTest < ActiveRecord::TestCase
def test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas
sql_inject = "1 select * from schema"
- assert_equal " LIMIT 1", @connection.add_limit_offset!("", :limit => sql_inject)
- if current_adapter?(:MysqlAdapter)
- assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit => sql_inject, :offset => 7)
- else
- assert_equal " LIMIT 1 OFFSET 7", @connection.add_limit_offset!("", :limit => sql_inject, :offset => 7)
- end
+ assert_no_match(/schema/, @connection.add_limit_offset!("", :limit=>sql_inject))
+ assert_no_match(/schema/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7))
end
def test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
sql_inject = "1, 7 procedure help()"
- if current_adapter?(:MysqlAdapter)
- assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit => sql_inject)
- assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit => '1 ; DROP TABLE USERS', :offset => 7)
- else
- assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit => sql_inject)
- assert_equal " LIMIT 1,7 OFFSET 7", @connection.add_limit_offset!("", :limit => sql_inject, :offset => 7)
- end
+ assert_no_match(/procedure/, @connection.add_limit_offset!("", :limit=>sql_inject))
+ assert_no_match(/procedure/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7))
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 9258c987ef..fb1e6e7e70 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -37,6 +37,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
if current_adapter?(:MysqlAdapter)
assert_no_match(/`firm_with_primary_keys_companies`\.`id`/, sql)
assert_match(/`firm_with_primary_keys_companies`\.`name`/, sql)
+ elsif current_adapter?(:OracleAdapter)
+ # on Oracle aliases are truncated to 30 characters and are quoted in uppercase
+ assert_no_match(/"firm_with_primary_keys_compani"\."id"/i, sql)
+ assert_match(/"firm_with_primary_keys_compani"\."name"/i, sql)
else
assert_no_match(/"firm_with_primary_keys_companies"\."id"/, sql)
assert_match(/"firm_with_primary_keys_companies"\."name"/, sql)
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index fe558f9d3b..7a4a33d177 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -7,9 +7,10 @@ require 'models/categorization'
require 'models/company'
require 'models/topic'
require 'models/reply'
+require 'models/person'
class CascadedEagerLoadingTest < ActiveRecord::TestCase
- fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments, :categorizations
+ fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments, :categorizations, :people
def test_eager_association_loading_with_cascaded_two_levels
authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
@@ -38,6 +39,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
assert_equal 9, assert_no_queries { authors[0].comments.size }
end
+ def test_eager_association_loading_grafts_stashed_associations_to_correct_parent
+ assert_nothing_raised do
+ Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').all
+ end
+ assert_equal people(:michael), Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').first
+ end
+
def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id")
assert_equal 2, authors.size
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 8e5bc56008..45c7498013 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -511,7 +511,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_deleting_updates_counter_cache
- topic = Topic.first
+ topic = Topic.first(:order => "id ASC")
assert_equal topic.replies.to_a.size, topic.replies_count
topic.replies.delete(topic.replies.first)
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 1d883f7ea8..36c572b5e7 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -430,14 +430,14 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_preserving_date_objects
- if current_adapter?(:SybaseAdapter, :OracleAdapter)
+ if current_adapter?(:SybaseAdapter)
# Sybase ctlib does not (yet?) support the date type; use datetime instead.
- # Oracle treats all dates/times as Time.
assert_kind_of(
Time, Topic.find(1).last_read,
"The last_read attribute should be of the Time class"
)
else
+ # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
assert_kind_of(
Date, Topic.find(1).last_read,
"The last_read attribute should be of the Date class"
@@ -2125,10 +2125,11 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "integer", xml.elements["//parent-id"].attributes['type']
assert_equal "true", xml.elements["//parent-id"].attributes['nil']
- if current_adapter?(:SybaseAdapter, :OracleAdapter)
+ if current_adapter?(:SybaseAdapter)
assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text
assert_equal "datetime" , xml.elements["//last-read"].attributes['type']
else
+ # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
assert_equal "2004-04-15", xml.elements["//last-read"].text
assert_equal "date" , xml.elements["//last-read"].attributes['type']
end
diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb
index 36e1caa0b6..a8b4b7a096 100644
--- a/activerecord/test/cases/date_time_test.rb
+++ b/activerecord/test/cases/date_time_test.rb
@@ -5,7 +5,9 @@ require 'models/task'
class DateTimeTest < ActiveRecord::TestCase
def test_saves_both_date_and_time
time_values = [1807, 2, 10, 15, 30, 45]
- now = DateTime.civil(*time_values)
+ # create DateTime value with local time zone offset
+ local_offset = Rational(Time.local_time(*time_values).utc_offset, 86400)
+ now = DateTime.civil(*(time_values + [local_offset]))
task = Task.new
task.starting = now
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index a3145d2c04..c275557da8 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -8,7 +8,7 @@ require 'models/comment'
class JsonSerializationTest < ActiveRecord::TestCase
class NamespacedContact < Contact
- column :name, :string
+ column :name, :string
end
def setup
@@ -23,16 +23,12 @@ class JsonSerializationTest < ActiveRecord::TestCase
end
def test_should_demodulize_root_in_json
- NamespacedContact.include_root_in_json = true
@contact = NamespacedContact.new :name => 'whatever'
json = @contact.to_json
assert_match %r{^\{"namespaced_contact":\{}, json
- ensure
- NamespacedContact.include_root_in_json = false
end
def test_should_include_root_in_json
- Contact.include_root_in_json = true
json = @contact.to_json
assert_match %r{^\{"contact":\{}, json
@@ -41,8 +37,6 @@ class JsonSerializationTest < ActiveRecord::TestCase
assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
assert_match %r{"awesome":true}, json
assert_match %r{"preferences":\{"shows":"anime"\}}, json
- ensure
- Contact.include_root_in_json = false
end
def test_should_encode_all_encodable_attributes
@@ -170,15 +164,19 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
end
def test_should_allow_only_option_for_list_of_authors
+ ActiveRecord::Base.include_root_in_json = false
authors = [@david, @mary]
-
assert_equal %([{"name":"David"},{"name":"Mary"}]), ActiveSupport::JSON.encode(authors, :only => :name)
+ ensure
+ ActiveRecord::Base.include_root_in_json = true
end
def test_should_allow_except_option_for_list_of_authors
+ ActiveRecord::Base.include_root_in_json = false
authors = [@david, @mary]
-
assert_equal %([{"id":1},{"id":2}]), ActiveSupport::JSON.encode(authors, :except => [:name, :author_address_id, :author_address_extra_id])
+ ensure
+ ActiveRecord::Base.include_root_in_json = true
end
def test_should_allow_includes_for_list_of_authors
@@ -201,7 +199,6 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
1 => @david,
2 => @mary
}
-
- assert_equal %({"1":{"name":"David"}}), ActiveSupport::JSON.encode(authors_hash, :only => [1, :name])
+ assert_equal %({"1":{"author":{"name":"David"}}}), ActiveSupport::JSON.encode(authors_hash, :only => [1, :name])
end
end
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 6ba84fa57b..1f544b4211 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -27,7 +27,7 @@ class LogSubscriberTest < ActiveSupport::TestCase
wait
assert_equal 1, @logger.logged(:debug).size
assert_match(/Developer Load/, @logger.logged(:debug).last)
- assert_match(/SELECT .*?FROM .?developers.?/, @logger.logged(:debug).last)
+ assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last)
end
def test_cached_queries
@@ -38,6 +38,6 @@ class LogSubscriberTest < ActiveSupport::TestCase
wait
assert_equal 2, @logger.logged(:debug).size
assert_match(/CACHE/, @logger.logged(:debug).last)
- assert_match(/SELECT .*?FROM .?developers.?/, @logger.logged(:debug).last)
+ assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last)
end
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index b5fa258f7b..1edec66c25 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -30,13 +30,14 @@ if ActiveRecord::Base.connection.supports_migrations?
conn = ActiveRecord::Base.connection
conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
- ActiveRecord::Base.table_name_prefix = 'foo_'
- ActiveRecord::Base.table_name_suffix = '_bar'
+ # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters
+ ActiveRecord::Base.table_name_prefix = 'p_'
+ ActiveRecord::Base.table_name_suffix = '_s'
conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
conn.initialize_schema_migrations_table
- assert_equal "foo_unique_schema_migrations_bar", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name]
+ assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name]
ensure
ActiveRecord::Base.table_name_prefix = ""
ActiveRecord::Base.table_name_suffix = ""
@@ -83,13 +84,17 @@ if ActiveRecord::Base.connection.supports_migrations?
# Orcl nds shrt indx nms. Sybs 2.
# OpenBase does not have named indexes. You must specify a single column name
- unless current_adapter?(:OracleAdapter, :SybaseAdapter, :OpenBaseAdapter)
+ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
assert_nothing_raised { Person.connection.remove_index("people", :column => ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.remove_index("people", :name => "index_people_on_last_name_and_first_name") }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") }
+ # Oracle adapter cannot have specified index name larger than 30 characters
+ # Oracle adapter is shortening index name when just column list is given
+ unless current_adapter?(:OracleAdapter)
+ assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
+ assert_nothing_raised { Person.connection.remove_index("people", :name => "index_people_on_last_name_and_first_name") }
+ assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
+ assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") }
+ end
assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => 10) }
@@ -736,13 +741,7 @@ if ActiveRecord::Base.connection.supports_migrations?
table.column :hat_size, :integer
table.column :hat_style, :string, :limit => 100
end
- # Oracle index names should be 30 or less characters
- if current_adapter?(:OracleAdapter)
- ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true,
- :name => 'index_hats_on_hat_style_size'
- else
- ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true
- end
+ ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true
assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") }
ensure
@@ -1375,8 +1374,8 @@ if ActiveRecord::Base.connection.supports_migrations?
return unless current_adapter? :OracleAdapter
# table name is 29 chars, the standard sequence name will
- # be 33 chars and fail
- assert_raise(ActiveRecord::StatementInvalid) do
+ # be 33 chars and should be shortened
+ assert_nothing_raised do
begin
Person.connection.create_table :table_with_name_thats_just_ok do |t|
t.column :foo, :string, :null => false
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 4097c5119e..43519db976 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -114,7 +114,7 @@ class RelationTest < ActiveRecord::TestCase
def test_select_with_block
even_ids = Developer.scoped.select {|d| d.id % 2 == 0 }.map(&:id)
- assert_equal [2, 4, 6, 8, 10], even_ids
+ assert_equal [2, 4, 6, 8, 10], even_ids.sort
end
def test_finding_with_hash_conditions_on_joined_table
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
deleted file mode 100644
index ebc16653cb..0000000000
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ /dev/null
@@ -1,240 +0,0 @@
-require "cases/helper"
-require 'models/topic'
-require 'models/reply'
-
-class TransactionCallbacksTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
- fixtures :topics
-
- class TopicWithCallbacks < ActiveRecord::Base
- set_table_name :topics
-
- after_commit{|record| record.send(:do_after_commit, nil)}
- after_commit(:on => :create){|record| record.send(:do_after_commit, :create)}
- after_commit(:on => :update){|record| record.send(:do_after_commit, :update)}
- after_commit(:on => :destroy){|record| record.send(:do_after_commit, :destroy)}
- after_rollback{|record| record.send(:do_after_rollback, nil)}
- after_rollback(:on => :create){|record| record.send(:do_after_rollback, :create)}
- after_rollback(:on => :update){|record| record.send(:do_after_rollback, :update)}
- after_rollback(:on => :destroy){|record| record.send(:do_after_rollback, :destroy)}
-
- def history
- @history ||= []
- end
-
- def after_commit_block(on = nil, &block)
- @after_commit ||= {}
- @after_commit[on] ||= []
- @after_commit[on] << block
- end
-
- def after_rollback_block(on = nil, &block)
- @after_rollback ||= {}
- @after_rollback[on] ||= []
- @after_rollback[on] << block
- end
-
- def do_after_commit(on)
- blocks = @after_commit[on] if defined?(@after_commit)
- blocks.each{|b| b.call(self)} if blocks
- end
-
- def do_after_rollback(on)
- blocks = @after_rollback[on] if defined?(@after_rollback)
- blocks.each{|b| b.call(self)} if blocks
- end
- end
-
- def setup
- @first, @second = TopicWithCallbacks.find(1, 3).sort_by { |t| t.id }
- end
-
- def test_call_after_commit_after_transaction_commits
- @first.after_commit_block{|r| r.history << :after_commit}
- @first.after_rollback_block{|r| r.history << :after_rollback}
-
- @first.save!
- assert_equal [:after_commit], @first.history
- end
-
- def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- @first.save!
- assert_equal [:commit_on_update], @first.history
- end
-
- def test_only_call_after_commit_on_destroy_after_transaction_commits_for_destroyed_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- @first.destroy
- assert_equal [:commit_on_destroy], @first.history
- end
-
- def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record
- @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
- @new_record.after_commit_block(:create){|r| r.history << :commit_on_create}
- @new_record.after_commit_block(:update){|r| r.history << :commit_on_update}
- @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- @new_record.save!
- assert_equal [:commit_on_create], @new_record.history
- end
-
- def test_call_after_rollback_after_transaction_rollsback
- @first.after_commit_block{|r| r.history << :after_commit}
- @first.after_rollback_block{|r| r.history << :after_rollback}
-
- Topic.transaction do
- @first.save!
- raise ActiveRecord::Rollback
- end
-
- assert_equal [:after_rollback], @first.history
- end
-
- def test_only_call_after_rollback_on_update_after_transaction_rollsback_for_existing_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- Topic.transaction do
- @first.save!
- raise ActiveRecord::Rollback
- end
-
- assert_equal [:rollback_on_update], @first.history
- end
-
- def test_only_call_after_rollback_on_destroy_after_transaction_rollsback_for_destroyed_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_update}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- Topic.transaction do
- @first.destroy
- raise ActiveRecord::Rollback
- end
-
- assert_equal [:rollback_on_destroy], @first.history
- end
-
- def test_only_call_after_rollback_on_create_after_transaction_rollsback_for_new_record
- @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
- @new_record.after_commit_block(:create){|r| r.history << :commit_on_create}
- @new_record.after_commit_block(:update){|r| r.history << :commit_on_update}
- @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- Topic.transaction do
- @new_record.save!
- raise ActiveRecord::Rollback
- end
-
- assert_equal [:rollback_on_create], @new_record.history
- end
-
- def test_call_after_rollback_when_commit_fails
- @first.connection.class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction)
- begin
- @first.connection.class.class_eval do
- def commit_db_transaction; raise "boom!"; end
- end
-
- @first.after_commit_block{|r| r.history << :after_commit}
- @first.after_rollback_block{|r| r.history << :after_rollback}
-
- assert !@first.save rescue nil
- assert_equal [:after_rollback], @first.history
- ensure
- @first.connection.class.send(:remove_method, :commit_db_transaction)
- @first.connection.class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction)
- end
- end
-
- def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint
- def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
- def @first.commits(i=0); @commits ||= 0; @commits += i if i; end
- @first.after_rollback_block{|r| r.rollbacks(1)}
- @first.after_commit_block{|r| r.commits(1)}
-
- def @second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
- def @second.commits(i=0); @commits ||= 0; @commits += i if i; end
- @second.after_rollback_block{|r| r.rollbacks(1)}
- @second.after_commit_block{|r| r.commits(1)}
-
- Topic.transaction do
- @first.save!
- Topic.transaction(:requires_new => true) do
- @second.save!
- raise ActiveRecord::Rollback
- end
- end
-
- assert_equal 1, @first.commits
- assert_equal 0, @first.rollbacks
- assert_equal 0, @second.commits
- assert_equal 1, @second.rollbacks
- end
-
- def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint_when_release_savepoint_fails
- def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
- def @first.commits(i=0); @commits ||= 0; @commits += i if i; end
-
- @first.after_rollback_block{|r| r.rollbacks(1)}
- @first.after_commit_block{|r| r.commits(1)}
-
- Topic.transaction do
- @first.save
- Topic.transaction(:requires_new => true) do
- @first.save!
- raise ActiveRecord::Rollback
- end
- Topic.transaction(:requires_new => true) do
- @first.save!
- raise ActiveRecord::Rollback
- end
- end
-
- assert_equal 1, @first.commits
- assert_equal 2, @first.rollbacks
- end
-
- def test_after_transaction_callbacks_should_not_raise_errors
- def @first.last_after_transaction_error=(e); @last_transaction_error = e; end
- def @first.last_after_transaction_error; @last_transaction_error; end
- @first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";}
- @first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";}
-
- @first.save!
- assert_equal :commit, @first.last_after_transaction_error
-
- Topic.transaction do
- @first.save!
- raise ActiveRecord::Rollback
- end
-
- assert_equal :rollback, @first.last_after_transaction_error
- end
-end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 958a4e4f94..00f3b527d7 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -320,33 +320,6 @@ class TransactionTest < ActiveRecord::TestCase
end
end
- def test_restore_active_record_state_for_all_records_in_a_transaction
- topic_1 = Topic.new(:title => 'test_1')
- topic_2 = Topic.new(:title => 'test_2')
- Topic.transaction do
- assert topic_1.save
- assert topic_2.save
- @first.save
- @second.destroy
- assert_equal false, topic_1.new_record?
- assert_not_nil topic_1.id
- assert_equal false, topic_2.new_record?
- assert_not_nil topic_2.id
- assert_equal false, @first.new_record?
- assert_not_nil @first.id
- assert_equal true, @second.destroyed?
- raise ActiveRecord::Rollback
- end
-
- assert_equal true, topic_1.new_record?
- assert_nil topic_1.id
- assert_equal true, topic_2.new_record?
- assert_nil topic_2.id
- assert_equal false, @first.new_record?
- assert_not_nil @first.id
- assert_equal false, @second.destroyed?
- end
-
if current_adapter?(:PostgreSQLAdapter) && defined?(PGconn::PQTRANS_IDLE)
def test_outside_transaction_works
assert Topic.connection.outside_transaction?
@@ -409,12 +382,6 @@ class TransactionTest < ActiveRecord::TestCase
end
private
- def define_callback_method(callback_method)
- define_method(callback_method) do
- self.history << [callback_method, :method]
- end
- end
-
def add_exception_raising_after_save_callback_to_topic
Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1
remove_method(:after_save_for_transaction)
diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb
index c8183dc0fb..bb4040058f 100644
--- a/activerecord/test/connections/native_oracle/connection.rb
+++ b/activerecord/test/connections/native_oracle/connection.rb
@@ -53,7 +53,7 @@ Course.establish_connection 'arunit2'
# for assert_queries test helper
ActiveRecord::Base.connection.class.class_eval do
- IGNORED_SELECT_SQL = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^\s*select .* from all_tab_columns/im]
+ IGNORED_SELECT_SQL = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from ((all|user)_tab_columns|(all|user)_triggers|(all|user)_constraints)/im]
def select_with_query_record(sql, name = nil, return_column_names = false)
$queries_executed ||= []
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 91fc7c9416..617f01b47d 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -43,6 +43,12 @@ class Topic < ActiveRecord::Base
before_create :default_written_on
before_destroy :destroy_children
+ # Explicitly define as :date column so that returned Oracle DATE values would be typecasted to Date and not Time.
+ # Some tests depend on assumption that this attribute will have Date values.
+ if current_adapter?(:OracleEnhancedAdapter)
+ set_date_columns :last_read
+ end
+
def parent
Topic.find(parent_id)
end
diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG
index bd97b2d549..2b2092b9fe 100644
--- a/activeresource/CHANGELOG
+++ b/activeresource/CHANGELOG
@@ -1,4 +1,4 @@
-*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
+*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* JSON: set Base.include_root_in_json = true to include a root value in the JSON: {"post": {"title": ...}}. Mirrors the Active Record option. [Santiago Pastorino]
diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb
index 6b9b8d3a9d..198e77a3d1 100644
--- a/activeresource/lib/active_resource/version.rb
+++ b/activeresource/lib/active_resource/version.rb
@@ -3,7 +3,7 @@ module ActiveResource
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta3"
+ BUILD = "beta4"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 0e24cc138a..6566d3fa06 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,4 +1,4 @@
-*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
+*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* Extracted String#truncate from TextHelper#truncate [DHH]
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index 24168c7825..c3f0acce25 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -60,7 +60,7 @@ class DateTime
# Converts self to a Ruby Date object; time portion is discarded
def to_date
::Date.new(year, month, day)
- end unless method_defined?(:to_date)
+ end unless instance_methods(false).include?(:to_date)
# Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class
# If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time
@@ -71,7 +71,7 @@ class DateTime
# To be able to keep Times, Dates and DateTimes interchangeable on conversions
def to_datetime
self
- end unless method_defined?(:to_datetime)
+ end unless instance_methods(false).include?(:to_datetime)
def self.civil_from_format(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0)
offset = utc_or_local.to_sym == :local ? local_offset : 0
@@ -81,7 +81,7 @@ class DateTime
# Converts datetime to an appropriate format for use in XML
def xmlschema
strftime("%Y-%m-%dT%H:%M:%S%Z")
- end unless method_defined?(:xmlschema)
+ end unless instance_methods(false).include?(:xmlschema)
# Converts self to a floating-point number of seconds since the Unix epoch
def to_f
diff --git a/activesupport/lib/active_support/core_ext/file.rb b/activesupport/lib/active_support/core_ext/file.rb
index dc24afbe7f..a763447566 100644
--- a/activesupport/lib/active_support/core_ext/file.rb
+++ b/activesupport/lib/active_support/core_ext/file.rb
@@ -1 +1,2 @@
require 'active_support/core_ext/file/atomic'
+require 'active_support/core_ext/file/path'
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index 615ebe9588..8bdfa0c5bc 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -3,6 +3,7 @@ class LoadError
/^no such file to load -- (.+)$/i,
/^Missing \w+ (?:file\s*)?([^\s]+.rb)$/i,
/^Missing API definition file in (.+)$/i,
+ /^cannot load such file -- (.+)$/i,
]
def path
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index 411ea0f016..d8d1f9436e 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -9,3 +9,4 @@ require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/string/interpolation'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/exclude'
+require 'active_support/core_ext/string/encoding' \ No newline at end of file
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 429b65bf15..1eb6e8748b 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -45,6 +45,8 @@ module ActiveSupport #:nodoc:
@wrapped_string = string
@wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
end
+
+ undef <=>
else
def initialize(string) #:nodoc:
@wrapped_string = string
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index 538a8b87c6..52612c27cb 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -3,7 +3,7 @@ module ActiveSupport
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta3"
+ BUILD = "beta4"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index ea6ebca3ce..dd84860a91 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -18,7 +18,7 @@ ENV['NO_RELOAD'] = '1'
require 'active_support'
# Include shims until we get off 1.8.6
-require 'active_support/ruby/shim'
+require 'active_support/ruby/shim' if RUBY_VERSION < '1.8.7'
def uses_memcached(test_name)
require 'memcache'
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index eba8170cda..19d7935211 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -26,11 +26,11 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
end
def test_to_date
- assert_equal Date.new(2005, 2, 21), DateTime.new(2005, 2, 21).to_date
+ assert_equal Date.new(2005, 2, 21), DateTime.new(2005, 2, 21, 14, 30, 0).to_date
end
def test_to_datetime
- assert_equal DateTime.new(2005, 2, 21), DateTime.new(2005, 2, 21).to_datetime
+ assert_equal DateTime.new(2005, 2, 21, 14, 30, 0), DateTime.new(2005, 2, 21, 14, 30, 0).to_datetime
end
def test_to_time
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index fe3ab3cdcf..61924745c8 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,4 +1,4 @@
-*Rails 3.0.0 [beta 4/release candidate] (unreleased)*
+*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* Version bump
* Removed Rails Metal [YK & JV].
diff --git a/railties/guides/source/3_0_release_notes.textile b/railties/guides/source/3_0_release_notes.textile
index 44ada7f5a4..41ea5d5822 100644
--- a/railties/guides/source/3_0_release_notes.textile
+++ b/railties/guides/source/3_0_release_notes.textile
@@ -565,6 +565,7 @@ Action Mailer has been given a new API with TMail being replaced out with the ne
* All mailers are now in <tt>app/mailers</tt> by default.
* Can now send email using new API with three methods: +attachments+, +headers+ and +mail+.
+* ActionMailer now has native support for inline attachments using the <tt>attachments.inline</tt> method.
* Action Mailer emailing methods now return <tt>Mail::Message</tt> objects, which can then be sent the +deliver+ message to send itself.
* All delivery methods are now abstracted out to the Mail gem.
* The mail delivery method can accept a hash of all valid mail header fields with their value pair.
diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile
index a2a748c749..8eb48e2751 100644
--- a/railties/guides/source/action_mailer_basics.textile
+++ b/railties/guides/source/action_mailer_basics.textile
@@ -211,6 +211,37 @@ attachments['filename.jpg'] = {:mime_type => 'application/x-gzip',
NOTE: If you specify an encoding, Mail will assume that your content is already encoded and not try to Base64 encode it.
+h5. Making Inline Attachments
+
+Inline attachments are now possible in ActionMailer. While previously in the pre 3.0 version of Rails, you could do inline attachments, it involved a lot of hacking and determination to pull it off.
+
+ActionMailer now makes inline attachments as trivial as they should be.
+
+* Firstly, to tell Mail to turn an attachment into an inline attachment, you just call <tt>#inline</tt> on the attachments method within your Mailer:
+
+<ruby>
+def welcome
+ attachments.inline['image.jpg'] = File.read('/path/to/image.jpg')
+end
+</ruby>
+
+* Then in your view, you can just reference <tt>attachments[]</tt> as a hash and specify which attachment you want to show, calling +url+ on it and then passing the result into the <tt>image_tag</tt> method:
+
+<erb>
+<p>Hello there, this is our image</p>
+
+<%= image_tag attachments['image.jpg'].url %>
+</erb>
+
+* As this is a standard call to +image_tag+ you can pass in an options hash after the attachment url as you could for any other image:
+
+<erb>
+<p>Hello there, this is our image</p>
+
+<%= image_tag attachments['image.jpg'].url, :alt => 'My Photo',
+ :class => 'photos' %>
+</erb>
+
h4. Mailer Views
Mailer views are located in the +app/views/name_of_mailer_class+ directory. The specific mailer view is known to the class because it's name is the same as the mailer method. So for example, in our example from above, our mailer view for the +welcome_email+ method will be in +app/views/user_mailer/welcome_email.html.erb+ for the HTML version and +welcome_email.text.erb+ for the plain text version.
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index ee0fca6592..0becb780de 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -28,6 +28,10 @@ module Rails
@operations << [:use, args, block]
end
+ def delete(*args, &block)
+ @operations << [:delete, args, block]
+ end
+
def merge_into(other)
@operations.each do |operation, args, block|
other.send(operation, *args, &block)
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index af92757053..f4990bfb4c 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -178,6 +178,7 @@ module Rails
"#{orm}:migration",
"#{orm}:model",
"#{orm}:observer",
+ "#{orm}:session_migration",
"#{test}:controller",
"#{test}:helper",
"#{test}:integration",
@@ -186,8 +187,11 @@ module Rails
"#{test}:observer",
"#{test}:scaffold",
"#{test}:view",
+ "#{test}:performance",
+ "#{test}:plugin",
"#{template}:controller",
- "#{template}:scaffold"
+ "#{template}:scaffold",
+ "#{template}:mailer"
]
end
end
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index c10876134a..34d96fd0f3 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta3"
+ BUILD = "beta4"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index bab17d8af5..aa75fed793 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -57,6 +57,12 @@ module ApplicationTests
assert !middleware.include?("ActionDispatch::Static")
end
+ test "can delete a middleware from the stack" do
+ add_to_config "config.middleware.delete ActionDispatch::Static"
+ boot!
+ assert !middleware.include?("ActionDispatch::Static")
+ end
+
test "removes show exceptions if action_dispatch.show_exceptions is disabled" do
add_to_config "config.action_dispatch.show_exceptions = false"
boot!
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 3975a39ab1..8bd9dc9f39 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -70,9 +70,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_name_collision_raises_an_error
- reserved_words = %w[generate g console c server s dbconsole db
- application destroy benchmarker profiler
- plugin runner test]
+ reserved_words = %w[application destroy plugin runner test]
reserved_words.each do |reserved|
content = capture(:stderr){ run_generator [File.join(destination_root, reserved)] }
assert_equal "Invalid application name #{reserved}. Please give a name which does not match one of the reserved rails words.\n", content
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 6f4c5d77f3..b46ac0efaf 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -232,7 +232,7 @@ Module.new do
require_environment = "-r #{environment}"
end
- `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/bin/rails #{tmp_path('app_template')}`
+ `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/bin/rails new #{tmp_path('app_template')}`
File.open("#{tmp_path}/app_template/config/boot.rb", 'w') do |f|
if require_environment
f.puts "Dir.chdir('#{File.dirname(environment)}') do"