Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge remote branch 'rails/master'

  • Loading branch information...
commit 751f79a03351f1f0d21436b2b947352b97ded093 2 parents e7e6ee3 + ab2877c
@fxn fxn authored
Showing with 721 additions and 646 deletions.
  1. +3 −2 Gemfile
  2. +1 −1  RAILS_VERSION
  3. +1 −1  actionmailer/CHANGELOG
  4. +1 −1  actionmailer/actionmailer.gemspec
  5. +37 −2 actionmailer/lib/action_mailer/base.rb
  6. +5 −0 actionmailer/lib/action_mailer/mail_helper.rb
  7. +1 −1  actionmailer/lib/action_mailer/version.rb
  8. +17 −0 actionmailer/test/base_test.rb
  9. +5 −0 actionmailer/test/fixtures/base_mailer/inline_attachment.html.erb
  10. +4 −0 actionmailer/test/fixtures/base_mailer/inline_attachment.text.erb
  11. +3 −3 actionmailer/test/old_base/mail_service_test.rb
  12. +11 −1 actionpack/CHANGELOG
  13. +8 −0 actionpack/lib/action_controller/metal/url_for.rb
  14. +1 −0  actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
  15. +2 −2 actionpack/lib/action_dispatch/middleware/show_exceptions.rb
  16. +98 −30 actionpack/lib/action_dispatch/routing/mapper.rb
  17. +4 −1 actionpack/lib/action_dispatch/routing/route_set.rb
  18. +0 −2  actionpack/lib/action_dispatch/testing/assertions/selector.rb
  19. +1 −1  actionpack/lib/action_pack/version.rb
  20. +5 −2 actionpack/lib/action_view/helpers/asset_tag_helper.rb
  21. +14 −14 actionpack/lib/action_view/helpers/cache_helper.rb
  22. +1 −1  actionpack/lib/action_view/helpers/tag_helper.rb
  23. +22 −16 actionpack/lib/action_view/helpers/text_helper.rb
  24. +1 −1  actionpack/lib/action_view/lookup_context.rb
  25. +0 −15 actionpack/test/controller/caching_test.rb
  26. +3 −0  actionpack/test/controller/new_base/render_rjs_test.rb
  27. +117 −6 actionpack/test/dispatch/routing_test.rb
  28. +1 −1  actionpack/test/dispatch/show_exceptions_test.rb
  29. +9 −0 actionpack/test/template/asset_tag_helper_test.rb
  30. +2 −0  actionpack/test/template/tag_helper_test.rb
  31. +164 −6 actionpack/test/template/text_helper_test.rb
  32. +1 −1  activemodel/CHANGELOG
  33. +6 −3 activemodel/lib/active_model/serializers/json.rb
  34. +1 −1  activemodel/lib/active_model/version.rb
  35. +21 −15 activemodel/test/cases/serializeration/json_serialization_test.rb
  36. +1 −3 activerecord/CHANGELOG
  37. +1 −1  activerecord/activerecord.gemspec
  38. +4 −3 activerecord/lib/active_record/associations.rb
  39. +0 −56 activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
  40. +4 −0 activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
  41. +5 −2 activerecord/lib/active_record/relation.rb
  42. +9 −109 activerecord/lib/active_record/transactions.rb
  43. +1 −1  activerecord/lib/active_record/version.rb
  44. +4 −13 activerecord/test/cases/adapter_test.rb
  45. +4 −0 activerecord/test/cases/associations/belongs_to_associations_test.rb
  46. +9 −1 activerecord/test/cases/associations/cascaded_eager_loading_test.rb
  47. +1 −1  activerecord/test/cases/associations/has_many_associations_test.rb
  48. +4 −3 activerecord/test/cases/base_test.rb
  49. +3 −1 activerecord/test/cases/date_time_test.rb
  50. +8 −11 activerecord/test/cases/json_serialization_test.rb
  51. +2 −2 activerecord/test/cases/log_subscriber_test.rb
  52. +16 −17 activerecord/test/cases/migration_test.rb
  53. +1 −1  activerecord/test/cases/relations_test.rb
  54. +0 −240 activerecord/test/cases/transaction_callbacks_test.rb
  55. +0 −33 activerecord/test/cases/transactions_test.rb
  56. +1 −1  activerecord/test/connections/native_oracle/connection.rb
  57. +6 −0 activerecord/test/models/topic.rb
  58. +1 −1  activeresource/CHANGELOG
  59. +1 −1  activeresource/lib/active_resource/version.rb
  60. +1 −1  activesupport/CHANGELOG
  61. +3 −3 activesupport/lib/active_support/core_ext/date_time/conversions.rb
  62. +1 −0  activesupport/lib/active_support/core_ext/file.rb
  63. +1 −0  activesupport/lib/active_support/core_ext/load_error.rb
  64. +1 −0  activesupport/lib/active_support/core_ext/string.rb
  65. +2 −0  activesupport/lib/active_support/multibyte/chars.rb
  66. +1 −1  activesupport/lib/active_support/version.rb
  67. +1 −1  activesupport/test/abstract_unit.rb
  68. +2 −2 activesupport/test/core_ext/date_time_ext_test.rb
  69. +1 −1  railties/CHANGELOG
  70. +1 −0  railties/guides/source/3_0_release_notes.textile
  71. +31 −0 railties/guides/source/action_mailer_basics.textile
  72. +4 −0 railties/lib/rails/configuration.rb
  73. +5 −1 railties/lib/rails/generators.rb
  74. +1 −1  railties/lib/rails/version.rb
  75. +6 −0 railties/test/application/middleware_test.rb
  76. +1 −3 railties/test/generators/app_generator_test.rb
  77. +1 −1  railties/test/isolation/abstract_unit.rb
View
5 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'
View
2  RAILS_VERSION
@@ -1 +1 @@
-3.0.0.beta3
+3.0.0.beta4
View
2  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]
View
2  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
View
39 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 @@ def set_content_type(m, user_content_type, class_default)
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
View
5 actionmailer/lib/action_mailer/mail_helper.rb
@@ -32,5 +32,10 @@ def mailer
def message
@_message
end
+
+ # Access the message attachments list.
+ def attachments
+ @_message.attachments
+ end
end
end
View
2  actionmailer/lib/action_mailer/version.rb
@@ -3,7 +3,7 @@ module VERSION #:nodoc:
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta3"
+ BUILD = "beta4"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
View
17 actionmailer/test/base_test.rb
@@ -33,6 +33,11 @@ def plain_text_only(hash = {})
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 @@ def give_a_greeting
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
View
5 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>
View
4 actionmailer/test/fixtures/base_mailer/inline_attachment.text.erb
@@ -0,0 +1,4 @@
+Inline Image
+
+No image for you
+
View
6 actionmailer/test/old_base/mail_service_test.rb
@@ -674,7 +674,7 @@ def test_unquote_quoted_printable_subject
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 @@ def test_multipart_with_mime_version
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 @@ def test_multipart_with_utf8_subject
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)
View
12 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]
View
8 actionpack/lib/action_controller/metal/url_for.rb
@@ -16,5 +16,13 @@ def _router
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
View
1  actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
@@ -23,6 +23,7 @@ class Tokenizer #: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
View
4 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 @@ def rescue_action_in_public(exception)
# 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)
View
128 actionpack/lib/action_dispatch/routing/mapper.rb
@@ -350,6 +350,10 @@ def constraints(constraints = {})
scope(:constraints => constraints) { yield }
end
+ def shallow
+ scope(:shallow => true) { yield }
+ end
+
def defaults(defaults = {})
scope(:defaults => defaults) { yield }
end
@@ -374,12 +378,21 @@ def scope_options
@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 @@ def singular
name.to_s.singularize
end
+ def member_prefix
+ ':id'
+ end
+
def member_name
singular
end
@@ -509,11 +526,19 @@ def collection_options
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 @@ def action_type(action)
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 @@ def initialize(*args) #:nodoc:
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 @@ def resource(*resources, &block)
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 @@ def resource(*resources, &block)
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 @@ def resources(*resources, &block)
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 @@ def resources(*resources, &block)
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 @@ def collection
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 @@ def match(*args)
@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 @@ def match(*args)
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 @@ def action_path(name, path_names = nil)
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 @@ def apply_common_behavior_for(method, resources, options, &block)
return true
end
- if @scope[:scope_level] == :resources
+ if resource_scope?
nested do
send(method, resources.pop, options, &block)
end
@@ -739,6 +799,14 @@ def apply_common_behavior_for(method, resources, options, &block)
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]
View
5 actionpack/lib/action_dispatch/routing/route_set.rb
@@ -68,6 +68,10 @@ def initialize
clear!
end
+ def helper_names
+ self.module.instance_methods.map(&:to_s)
+ end
+
def clear!
@routes = {}
@helpers = []
@@ -176,7 +180,6 @@ def #{selector}(*args)
url_for(options)
end
- protected :#{selector}
END_EVAL
helpers << selector
end
View
2  actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -267,14 +267,12 @@ def assert_select(*args, &block)
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
View
2  actionpack/lib/action_pack/version.rb
@@ -3,7 +3,7 @@ module VERSION #:nodoc:
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta3"
+ BUILD = "beta4"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
View
7 actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -620,7 +620,10 @@ def image_tag(source, options = {})
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 @@ def compute_public_path(source, dir, ext = nil, include_host = true)
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,
View
28 actionpack/lib/action_view/helpers/cache_helper.rb
@@ -32,27 +32,27 @@ module CacheHelper
# <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
View
2  actionpack/lib/action_view/helpers/tag_helper.rb
@@ -110,7 +110,7 @@ def escape_once(html)
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)
View
38 actionpack/lib/action_view/helpers/text_helper.rb
@@ -74,6 +74,7 @@ def truncate(text, *args)
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 @@ def highlight(text, phrases, *args)
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 @@ def word_wrap(text, *args)
#
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 @@ def textilize(text, *options)
#
# 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 @@ def textilize_without_paragraph(text)
#
# 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 @@ def markdown(text)
#
# 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 @@ def simple_format(text, html_options={})
# # => "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 @@ def auto_link(text, *args, &block)#link = :all, html = {}, &block)
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 @@ def set_cycle(name, cycle_object)
# 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 @@ def auto_link_urls(text, html_options = {})
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
View
2  actionpack/lib/action_view/lookup_context.rb
@@ -188,7 +188,7 @@ def with_layout_format
begin
yield
ensure
- _set_detail(:formats, formats)
+ _set_detail(:formats, old_formats)
end
end
end
View
15 actionpack/test/controller/caching_test.rb
@@ -644,21 +644,6 @@ def test_expire_fragment_with_regexp
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
View
3  actionpack/test/controller/new_base/render_rjs_test.rb
@@ -2,7 +2,10 @@
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') %>",
View
123 actionpack/test/dispatch/routing_test.rb
@@ -34,6 +34,33 @@ def self.matches?(request)
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 @@ def self.matches?(request)
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 @@ def self.matches?(request)
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 @@ def self.matches?(request)
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 @@ def self.matches?(request)
end
match "whatever/:controller(/:action(/:id))"
+
+ resource :profile do
+ get :settings
+
+ new do
+ post :preview
+ end
+ end
end
end
@@ -728,6 +779,18 @@ def test_update_person_route
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 @@ def test_assert_recognizes_account_overview
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
View
2  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}
View
9 actionpack/test/template/asset_tag_helper_test.rb
@@ -404,6 +404,15 @@ def test_proc_asset_id
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
View
2  actionpack/test/template/tag_helper_test.rb
@@ -39,6 +39,8 @@ def test_content_tag
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
View
170 actionpack/test/template/text_helper_test.rb
@@ -7,6 +7,12 @@
$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 @@ def test_simple_format_should_be_html_safe
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 @@ def test_truncate_multibyte
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 @@ def test_highlighter
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 @@ def test_highlight_with_html
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 @@ def generate_result(link_text, href = nil)
%{<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 @@ def test_auto_linking
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 @@ def test_cycle_no_instance_variable_clashes
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 @@ def test_textilize_with_options
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
View
2  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]
View
9 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 JSON
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 @@ def as_json(options = nil)
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
View
2  activemodel/lib/active_model/version.rb
@@ -3,7 +3,7 @@ module VERSION #:nodoc:
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta3"
+ BUILD = "beta4"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
View
36 activemodel/test/cases/serializeration/json_serialization_test.rb
@@ -22,35 +22,41 @@ def setup
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
View
4 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]
View
2  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
View
7 activerecord/lib/active_record/associations.rb
@@ -1756,7 +1756,8 @@ def join_base
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 @@ def initialize(active_record, joins = nil)
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 @@ def initialize(reflection, join_dependency, parent = nil)
end
def ==(other)
- other.is_a?(JoinAssociation) &&
+ other.class == self.class &&
other.reflection == reflection &&
other.parent == parent
end
View
56 activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -122,8 +122,6 @@ def transaction(options = {})
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 @@ def transaction(options = {})
end
increment_open_transactions
transaction_open = true
- @_current_transaction_records.push([])
end
yield
end
@@ -144,10 +141,8 @@ def transaction(options = {})
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 @@ def transaction(options = {})
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 @@ def sanitize_limit(limit)
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
View
4 activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -34,6 +34,10 @@ def string_to_binary(value)
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"
View
7 activerecord/lib/active_record/relation.rb
@@ -356,13 +356,16 @@ def with_create_scope
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
View
118 activerecord/lib/active_record/transactions.rb
@@ -8,11 +8,6 @@ module Transactions
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 @@ class TransactionError < ActiveRecordError # :nodoc:
# 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 @@ def save!(*) #:nodoc:
# 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 @@ def add_to_transaction
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
View
2  activerecord/lib/active_record/version.rb
@@ -3,7 +3,7 @@ module VERSION #:nodoc:
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta3"
+ BUILD = "beta4"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
View
17 activerecord/test/cases/adapter_test.rb
@@ -145,22 +145,13 @@ def test_foreign_key_violations_are_translated_to_specific_exception
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
View
4 activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -37,6 +37,10 @@ def test_belongs_to_with_primary_key_joins_on_correct_column
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)
View
10 activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -7,9 +7,10 @@
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 @@ def test_eager_association_loading_with_hmt_does_not_table_name_collide_when_joi
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
View
2  activerecord/test/cases/associations/has_many_associations_test.rb
@@ -511,7 +511,7 @@ def test_deleting_before_save
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)
View
7 activerecord/test/cases/base_test.rb
@@ -430,14 +430,14 @@ def test_non_attribute_access_and_assignment
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 @@ def test_to_xml
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
View
4 activerecord/test/cases/date_time_test.rb
@@ -5,7 +5,9 @@
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
View
19 activerecord/test/cases/json_serialization_test.rb
@@ -8,7 +8,7 @@
class JsonSerializationTest < ActiveRecord::TestCase
class NamespacedContact < Contact
- column :name, :string
+ column :name, :string
end
def setup
@@ -23,16 +23,12 @@ def setup
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