aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile4
-rw-r--r--actionmailer/CHANGELOG.md33
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/action_mailer/base.rb22
-rw-r--r--actionmailer/test/base_test.rb6
-rw-r--r--actionmailer/test/mailers/base_mailer.rb5
-rw-r--r--actionpack/CHANGELOG.md97
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/best_standards_support.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb29
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb15
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb4
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb2
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb16
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb10
-rw-r--r--actionpack/test/controller/routing_test.rb20
-rw-r--r--actionpack/test/controller/send_file_test.rb14
-rw-r--r--actionpack/test/dispatch/best_standards_support_test.rb34
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb5
-rw-r--r--actionpack/test/dispatch/request_test.rb7
-rw-r--r--actionpack/test/dispatch/routing/route_set_test.rb86
-rw-r--r--actionpack/test/dispatch/routing_test.rb9
-rw-r--r--actionpack/test/template/date_helper_i18n_test.rb15
-rw-r--r--actionpack/test/template/date_helper_test.rb31
-rw-r--r--actionpack/test/template/form_helper_test.rb13
-rw-r--r--actionpack/test/template/record_tag_helper_test.rb8
-rw-r--r--actionpack/test/template/render_test.rb7
-rw-r--r--actionpack/test/template/sprockets_helper_test.rb2
-rw-r--r--actionpack/test/template/sprockets_helper_with_routes_test.rb4
-rw-r--r--actionpack/test/template/template_test.rb16
-rw-r--r--activemodel/CHANGELOG.md15
-rw-r--r--activemodel/lib/active_model/errors.rb2
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb7
-rw-r--r--activemodel/lib/active_model/validations.rb2
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb12
-rw-r--r--activerecord/CHANGELOG.md183
-rw-r--r--activerecord/lib/active_record/associations/association.rb3
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb16
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb18
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb1
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb2
-rw-r--r--activerecord/lib/active_record/integration.rb13
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb4
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb7
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/query_cache.rb12
-rw-r--r--activerecord/lib/active_record/railties/databases.rake24
-rw-r--r--activerecord/lib/active_record/relation.rb7
-rw-r--r--activerecord/lib/active_record/relation/batches.rb4
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb16
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb12
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/sql_types_test.rb18
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb6
-rw-r--r--activerecord/test/cases/ar_schema_test.rb36
-rw-r--r--activerecord/test/cases/associations/eager_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb15
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb6
-rw-r--r--activerecord/test/cases/base_test.rb58
-rw-r--r--activerecord/test/cases/batches_test.rb23
-rw-r--r--activerecord/test/cases/calculations_test.rb16
-rw-r--r--activerecord/test/cases/dirty_test.rb14
-rw-r--r--activerecord/test/cases/finder_test.rb5
-rw-r--r--activerecord/test/cases/helper.rb2
-rw-r--r--activerecord/test/cases/locking_test.rb12
-rw-r--r--activerecord/test/cases/migration_test.rb18
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb11
-rw-r--r--activerecord/test/cases/persistence_test.rb8
-rw-r--r--activerecord/test/cases/query_cache_test.rb11
-rw-r--r--activerecord/test/cases/relations_test.rb14
-rw-r--r--activerecord/test/fixtures/peoples_treasures.yml3
-rw-r--r--activerecord/test/migrations/10_urban/9_add_expressions.rb11
-rw-r--r--activerecord/test/models/bulb.rb2
-rw-r--r--activerecord/test/models/developer.rb14
-rw-r--r--activerecord/test/models/person.rb27
-rw-r--r--activerecord/test/models/topic.rb6
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb5
-rw-r--r--activerecord/test/schema/schema.rb7
-rw-r--r--activeresource/CHANGELOG.md11
-rw-r--r--activeresource/test/cases/base_test.rb2
-rw-r--r--activesupport/CHANGELOG.md31
-rw-r--r--activesupport/lib/active_support/concern.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/marshal.rb4
-rw-r--r--activesupport/lib/active_support/json/encoding.rb10
-rw-r--r--activesupport/lib/active_support/testing/mochaing.rb4
-rw-r--r--activesupport/lib/active_support/testing/setup_and_teardown.rb11
-rw-r--r--activesupport/test/abstract_unit.rb2
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb18
-rw-r--r--activesupport/test/json/encoding_test.rb38
-rw-r--r--railties/CHANGELOG.md25
-rw-r--r--railties/guides/source/active_record_validations_callbacks.textile8
-rw-r--r--railties/guides/source/active_support_core_extensions.textile4
-rw-r--r--railties/guides/source/ajax_on_rails.textile267
-rw-r--r--railties/guides/source/configuring.textile2
-rw-r--r--railties/guides/source/performance_testing.textile2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb8
-rwxr-xr-xrailties/lib/rails/generators/rails/plugin_new/templates/Rakefile2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/model/model_generator.rb12
-rw-r--r--railties/lib/rails/generators/test_unit/model/templates/fixtures.yml4
-rw-r--r--railties/test/application/rack/logger_test.rb1
-rw-r--r--railties/test/application/route_inspect_test.rb2
-rw-r--r--railties/test/application/routing_test.rb71
-rw-r--r--railties/test/generators/model_generator_test.rb21
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb6
-rw-r--r--railties/test/generators_test.rb4
121 files changed, 1479 insertions, 452 deletions
diff --git a/Gemfile b/Gemfile
index 8c381e9c9e..c45be188f3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -22,7 +22,7 @@ end
gem 'uglifier', '>= 1.0.3', :require => false
gem 'rake', '>= 0.8.7'
-gem 'mocha', '>= 0.12.1'
+gem 'mocha', '>= 0.13.0', :require => false
group :doc do
# The current sdoc cannot generate GitHub links due
@@ -47,7 +47,7 @@ instance_eval File.read '.Gemfile' if File.exists? '.Gemfile'
platforms :mri do
group :test do
- gem 'ruby-prof', '~> 0.11.2'
+ gem 'ruby-prof', '~> 0.11.2' if RUBY_VERSION < '2.0'
end
end
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index fffadd18a0..b35c1da69f 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,11 +1,36 @@
-## Rails 3.2.10 ##
+## Rails 3.2.11 (unreleased) ##
+
+
+## Rails 3.2.10 (Jan 2, 2013) ##
+
+* No changes.
+
+
+## Rails 3.2.9 (Nov 12, 2012) ##
+
+* The return value from mailer methods is no longer relevant. This fixes a bug,
+ which was introduced with 3.2.9.
+ Backport #8450
+ Fix #8448
+
+ class ExampleMailer < ActionMailer::Base
+ # in 3.2.9, returning a falsy value from a mailer action, prevented the email from beeing sent.
+ # With 3.2.10 the return value is no longer relevant. If you call mail() the email will be sent.
+ def nil_returning_mailer_action
+ mail()
+ nil
+ end
+ end
+
+ *Yves Senn*
+
## Rails 3.2.9 (Nov 12, 2012) ##
-* Do not render views when mail() isn't called.
- Fix #7761
+* Do not render views when mail() isn't called.
+ Fix #7761
- *Yves Senn*
+ *Yves Senn*
## Rails 3.2.8 (Aug 9, 2012) ##
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 3603017814..c2c8e9b8e2 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -17,5 +17,5 @@ Gem::Specification.new do |s|
s.requirements << 'none'
s.add_dependency('actionpack', version)
- s.add_dependency('mail', '~> 2.4.4')
+ s.add_dependency('mail', '~> 2.5.3')
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index a9fb49a303..9e2f640915 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -123,8 +123,8 @@ module ActionMailer #:nodoc:
#
# <%= users_url(:host => "example.com") %>
#
- # You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the
- # <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will
+ # You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the
+ # <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will
# have no concept of a current URL from which to determine a relative path.
#
# It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt>
@@ -133,7 +133,7 @@ module ActionMailer #:nodoc:
# config.action_mailer.default_url_options = { :host => "example.com" }
#
# When you decide to set a default <tt>:host</tt> for your mailers, then you need to make sure to use the
- # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper
+ # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper
# will generate relative URLs by default when a <tt>:host</tt> option isn't explicitly provided, passing
# <tt>:only_path => false</tt> will ensure that absolute URLs are generated.
#
@@ -150,8 +150,8 @@ module ActionMailer #:nodoc:
#
# = Multipart Emails
#
- # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use
- # multipart templates, where each template is named after the name of the action, followed by the content
+ # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use
+ # multipart templates, where each template is named after the name of the action, followed by the content
# type. Each such detected template will be added as a separate part to the message.
#
# For example, if the following templates exist:
@@ -448,6 +448,7 @@ module ActionMailer #:nodoc:
# method, for instance).
def initialize(method_name=nil, *args)
super()
+ @mail_was_called = false
@_message = Mail.new
process(method_name, *args) if method_name
end
@@ -455,10 +456,8 @@ module ActionMailer #:nodoc:
def process(*args) #:nodoc:
lookup_context.skip_default_locale!
- generated_mail = super
- unless generated_mail
- @_message = NullMail.new
- end
+ super
+ @_message = NullMail.new unless @mail_was_called
end
class NullMail #:nodoc:
@@ -616,8 +615,9 @@ module ActionMailer #:nodoc:
# end
#
def mail(headers={}, &block)
- # Guard flag to prevent both the old and the new API from firing
- # Should be removed when old API is removed
+ # Guard flag to prevent both the old and the new API from firing.
+ # On master this flag was renamed to `@_mail_was_called`.
+ # On master there is only one API and this flag is no longer used as a guard.
@mail_was_called = true
m = @_message
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index b69b26faf0..f648cb1546 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -477,6 +477,12 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver
end
+ test 'the return value of mailer methods is not relevant' do
+ mail = BaseMailer.with_nil_as_return_value
+ assert_equal('Welcome', mail.body.to_s.strip)
+ mail.deliver
+ end
+
# Before and After hooks
class MyObserver
diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb
index 8c4430b046..50438ead2a 100644
--- a/actionmailer/test/mailers/base_mailer.rb
+++ b/actionmailer/test/mailers/base_mailer.rb
@@ -118,4 +118,9 @@ class BaseMailer < ActionMailer::Base
def without_mail_call
end
+
+ def with_nil_as_return_value(hash = {})
+ mail(:template_name => "welcome")
+ nil
+ end
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 4d7035e63f..2de084cfd6 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,4 +1,99 @@
-## Rails 3.2.10 ##
+## Rails 3.2.11 (unreleased) ##
+
+* Do not append second slash to root_url when using `trailing_slash: true`
+ Fix #8700.
+ Backport #8701.
+
+ Example:
+ # before
+ root_url # => http://test.host//
+
+ # after
+ root_url # => http://test.host/
+
+ *Yves Senn*
+
+* Fix a bug in `content_tag_for` that prevents it for work without a block.
+
+ *Jasl*
+
+* Clear url helper methods when routes are reloaded by removing the methods
+ explicitly rather than just clearing the module because it didn't work
+ properly and could be the source of a memory leak.
+
+ *Andrew White*
+
+* Fix a bug in ActionDispatch::Request#raw_post that caused env['rack.input']
+ to be read but not rewound.
+
+ *Matt Venables*
+
+* More descriptive error messages when calling `render :partial` with
+ an invalid `:layout` argument.
+ #8376
+
+ render :partial => 'partial', :layout => true
+
+ # results in ActionView::MissingTemplate: Missing partial /true
+
+ *Yves Senn*
+
+* Accept symbols as #send_data :disposition value. [Backport #8329] *Elia Schito*
+
+* Add i18n scope to distance_of_time_in_words. [Backport #7997] *Steve Klabnik*
+
+* Fix side effect of `url_for` changing the `:controller` string option. [Backport #6003]
+ Before:
+
+ controller = '/projects'
+ url_for :controller => controller, :action => 'status'
+
+ puts controller #=> 'projects'
+
+ After
+
+ puts controller #=> '/projects'
+
+ [Nikita Beloglazov + Andrew White]
+
+* Introduce `ActionView::Template::Handlers::ERB.escape_whitelist`. This is a list
+ of mime types where template text is not html escaped by default. It prevents `Jack & Joe`
+ from rendering as `Jack &amp; Joe` for the whitelisted mime types. The default whitelist
+ contains text/plain. Fix #7976 [Backport #8235]
+
+ *Joost Baaij*
+
+* `BestStandardsSupport` middleware now appends it's `X-UA-Compatible` value to app's
+ returned value if any. Fix #8086 [Backport #8093]
+
+ *Nikita Afanasenko*
+
+* prevent double slashes in engine urls when `Rails.application.default_url_options[:trailing_slash] = true` is set
+ Fix #7842
+
+ *Yves Senn*
+
+* Fix input name when `:multiple => true` and `:index` are set.
+
+ Before:
+
+ check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1)
+ #=> <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" />
+
+ After:
+
+ check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1)
+ #=> <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" />
+
+ Fix #8108
+
+ *Daniel Fox, Grant Hutchins & Trace Wax*
+
+
+## Rails 3.2.10 (Jan 2, 2013) ##
+
+* No changes.
+
## Rails 3.2.9 (Nov 12, 2012) ##
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 62e16ea047..95c058fc86 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -141,7 +141,7 @@ module ActionController #:nodoc:
raise ArgumentError, ":#{arg} option required" if options[arg].nil?
end
- disposition = options[:disposition]
+ disposition = options[:disposition].to_s
disposition += %(; filename="#{options[:filename]}") if options[:filename]
content_type = options[:type]
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index afc0496ef9..0413346d94 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -179,8 +179,9 @@ module ActionDispatch
# work with raw requests directly.
def raw_post
unless @env.include? 'RAW_POST_DATA'
- @env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
- body.rewind if body.respond_to?(:rewind)
+ raw_post_body = body
+ @env['RAW_POST_DATA'] = raw_post_body.read(@env['CONTENT_LENGTH'].to_i)
+ raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
end
@env['RAW_POST_DATA']
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 64459836b5..f07d5adc9b 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -43,7 +43,11 @@ module ActionDispatch
params = options[:params] || {}
params.reject! {|k,v| v.to_param.nil? }
- rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
+ if options[:trailing_slash] && !path.ends_with?('/')
+ rewritten_url << path.sub(/(\?|\z)/) { "/" + $& }
+ else
+ rewritten_url << path
+ end
rewritten_url << "?#{params.to_query}" unless params.empty?
rewritten_url << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
rewritten_url
diff --git a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb
index 69adcc419f..d338996240 100644
--- a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb
+++ b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb
@@ -15,7 +15,13 @@ module ActionDispatch
def call(env)
status, headers, body = @app.call(env)
- headers["X-UA-Compatible"] = @header
+
+ if headers["X-UA-Compatible"] && @header
+ headers["X-UA-Compatible"] << "," << @header.to_s
+ else
+ headers["X-UA-Compatible"] = @header
+ end
+
[status, headers, body]
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 952219631a..9a474d2e3a 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -451,7 +451,7 @@ module ActionDispatch
# we must actually delete prefix segment keys to avoid passing them to next url_for
_route.segment_keys.each { |k| options.delete(k) }
prefix = _routes.url_helpers.send("#{name}_path", prefix_options)
- prefix = '' if prefix == '/'
+ prefix = prefix.gsub(%r{/\z}, '')
prefix
end
end
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index dc28389360..50b20a2a25 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -78,10 +78,10 @@ module ActionDispatch
# params, depending of how many arguments your block accepts. A string is required as a
# return value.
#
- # match 'jokes/:number', :to => redirect do |params, request|
- # path = (params[:number].to_i.even? ? "/wheres-the-beef" : "/i-love-lamp")
+ # match 'jokes/:number', :to => redirect { |params, request|
+ # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
# "http://#{request.host_with_port}/#{path}"
- # end
+ # }
#
# The options version of redirect allows you to supply only the parts of the url which need
# to change, it also supports interpolation of the path similar to the first example.
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 667094c469..a993699e05 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -94,7 +94,12 @@ module ActionDispatch
attr_reader :routes, :helpers, :module
def initialize
- clear!
+ @routes = {}
+ @helpers = []
+
+ @module = Module.new do
+ instance_methods.each { |selector| remove_method(selector) }
+ end
end
def helper_names
@@ -102,12 +107,14 @@ module ActionDispatch
end
def clear!
+ @helpers.each do |helper|
+ @module.module_eval do
+ remove_possible_method helper
+ end
+ end
+
@routes = {}
@helpers = []
-
- @module ||= Module.new do
- instance_methods.each { |selector| remove_method(selector) }
- end
end
def add(name, route)
@@ -291,7 +298,6 @@ module ActionDispatch
def clear!
@finalized = false
- @url_helpers = nil
named_routes.clear
set.clear
formatter.clear
@@ -442,12 +448,12 @@ module ActionDispatch
normalize_options!
normalize_controller_action_id!
use_relative_controller!
- controller.sub!(%r{^/}, '') if controller
+ normalize_controller!
handle_nil_action!
end
def controller
- @controller ||= @options[:controller]
+ @options[:controller]
end
def current_controller
@@ -504,10 +510,15 @@ module ActionDispatch
old_parts = current_controller.split('/')
size = controller.count("/") + 1
parts = old_parts[0...-size] << controller
- @controller = @options[:controller] = parts.join("/")
+ @options[:controller] = parts.join("/")
end
end
+ # Remove leading slashes from controllers
+ def normalize_controller!
+ @options[:controller] = controller.sub(%r{^/}, '') if controller
+ end
+
# This handles the case of :action => nil being explicitly passed.
# It is identical to :action => "index"
def handle_nil_action!
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 87e2332c42..99aa144d3a 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -65,12 +65,17 @@ module ActionView
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
#
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
+ options = {
+ :scope => :'datetime.distance_in_words',
+ }.merge!(options)
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
- distance_in_minutes = (((to_time - from_time).abs)/60).round
- distance_in_seconds = ((to_time - from_time).abs).round
+ distance = (to_time.to_f - from_time.to_f).abs
+ distance_in_minutes = (distance / 60.0).round
+ distance_in_seconds = distance.round
- I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
+ I18n.with_options :locale => options[:locale], :scope => options[:scope] do |locale|
case distance_in_minutes
when 0..1
return distance_in_minutes == 0 ?
@@ -129,8 +134,8 @@ module ActionView
# from_time = Time.now - 3.days - 14.minutes - 25.seconds
# time_ago_in_words(from_time) # => 3 days
#
- def time_ago_in_words(from_time, include_seconds = false)
- distance_of_time_in_words(from_time, Time.now, include_seconds)
+ def time_ago_in_words(from_time, include_seconds = false, options = {})
+ distance_of_time_in_words(from_time, Time.now, include_seconds, options)
end
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index a3409ee3c7..d00bad7608 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -1205,9 +1205,11 @@ module ActionView
options["name"] ||= tag_name_with_index(@auto_index)
options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
else
- options["name"] ||= tag_name + (options['multiple'] ? '[]' : '')
+ options["name"] ||= tag_name
options["id"] = options.fetch("id"){ tag_id }
end
+
+ options["name"] += "[]" if options["multiple"]
options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 670ff18a66..f0573437ca 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -45,7 +45,7 @@ module ActionView
# # => <form action="/posts" method="post">
#
# form_tag('/posts/1', :method => :put)
- # # => <form action="/posts/1" method="put">
+ # # => <form action="/posts/1" method="post"> ... <input name="_method" type="hidden" value="put" /> ...
#
# form_tag('/upload', :multipart => true)
# # => <form action="/upload" method="post" enctype="multipart/form-data">
@@ -53,7 +53,7 @@ module ActionView
# <%= form_tag('/posts') do -%>
# <div><%= submit_tag 'Save' %></div>
# <% end -%>
- # # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
+ # # => <form action="/posts" method="post"><div><input type="submit" name="commit" value="Save" /></div></form>
#
# <%= form_tag('/posts', :remote => true) %>
# # => <form action="/posts" method="post" data-remote="true">
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index aae6389445..709b5d2c17 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -98,7 +98,9 @@ module ActionView
options, prefix = prefix, nil if prefix.is_a?(Hash)
options = options ? options.dup : {}
options.merge!(:class => "#{dom_class(record, prefix)} #{options[:class]}".strip, :id => dom_id(record, prefix))
- if block.arity == 0
+ if !block_given?
+ content_tag(tag_name, "", options)
+ elsif block.arity == 0
content_tag(tag_name, capture(&block), options)
else
content_tag(tag_name, capture(record, &block), options)
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index 3093aca91e..71fa05ab3e 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -256,7 +256,7 @@ module ActionView
object, as = @object, @variable
if !block && (layout = @options[:layout])
- layout = find_template(layout)
+ layout = find_template(layout.to_s)
end
object ||= locals[as]
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index ea495ea9ca..118b18a81c 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -15,6 +15,17 @@ module ActionView
src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
end
+ # Erubis toggles <%= and <%== behavior when escaping is enabled.
+ # We override to always treat <%== as escaped.
+ def add_expr(src, code, indicator)
+ case indicator
+ when '=='
+ add_expr_escaped(src, code)
+ else
+ super
+ end
+ end
+
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expr_literal(src, code)
@@ -48,6 +59,10 @@ module ActionView
class_attribute :erb_implementation
self.erb_implementation = Erubis
+ # Do not escape templates of these mime types.
+ class_attribute :escape_whitelist
+ self.escape_whitelist = ["text/plain"]
+
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
def self.call(template)
@@ -83,6 +98,7 @@ module ActionView
self.class.erb_implementation.new(
erb,
+ :escape => (self.class.escape_whitelist.include? template.mime_type),
:trim => (self.class.erb_trim_mode == "-")
).src
end
diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb
index 29c8885d9f..c727f03346 100644
--- a/actionpack/test/controller/new_base/render_template_test.rb
+++ b/actionpack/test/controller/new_base/render_template_test.rb
@@ -9,7 +9,8 @@ module RenderTemplate
"locals.html.erb" => "The secret is <%= secret %>",
"xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend",
"with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>",
- "with_implicit_raw.html.erb" => "Hello <%== '<strong>this is also raw</strong>' %>",
+ "with_implicit_raw.html.erb" => "Hello <%== '<strong>this is also raw</strong>' %> in a html template",
+ "with_implicit_raw.text.erb" => "Hello <%== '<strong>this is also raw</strong>' %> in a text template",
"test/with_json.html.erb" => "<%= render :template => 'test/with_json', :formats => [:json] %>",
"test/with_json.json.erb" => "<%= render :template => 'test/final', :formats => [:json] %>",
"test/final.json.erb" => "{ final: json }",
@@ -113,7 +114,12 @@ module RenderTemplate
get :with_implicit_raw
- assert_body "Hello <strong>this is also raw</strong>"
+ assert_body "Hello <strong>this is also raw</strong> in a html template"
+ assert_status 200
+
+ get :with_implicit_raw, :format => 'text'
+
+ assert_body "Hello <strong>this is also raw</strong> in a text template"
assert_status 200
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 7079a6ff90..1a1610eac7 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -58,13 +58,13 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase
end
class MockController
- def self.build(helpers)
+ def self.build(helpers, additional_options = {})
Class.new do
- def url_for(options)
+ define_method :url_for do |options|
options[:protocol] ||= "http"
options[:host] ||= "test.host"
- super(options)
+ super(options.merge(additional_options))
end
include helpers
@@ -468,8 +468,8 @@ class LegacyRouteSetTests < Test::Unit::TestCase
routes.send(:pages_url)
end
- def setup_for_named_route
- MockController.build(rs.url_helpers).new
+ def setup_for_named_route(options = {})
+ MockController.build(rs.url_helpers, options).new
end
def test_named_route_without_hash
@@ -487,6 +487,16 @@ class LegacyRouteSetTests < Test::Unit::TestCase
assert_equal("/", routes.send(:root_path))
end
+ def test_named_route_root_with_trailing_slash
+ rs.draw do
+ root :to => "hello#index"
+ end
+
+ routes = setup_for_named_route(:trailing_slash => true)
+ assert_equal("http://test.host/", routes.send(:root_url))
+ assert_equal("http://test.host/?foo=bar", routes.send(:root_url, :foo => :bar))
+ end
+
def test_named_route_with_regexps
rs.draw do
match 'page/:year/:month/:day/:title' => 'page#show', :as => 'article',
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 8f885ff28e..4e4dd8d005 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -118,6 +118,18 @@ class SendFileTest < ActionController::TestCase
assert_equal 'private', h['Cache-Control']
end
+ def test_send_file_headers_with_disposition_as_a_symbol
+ options = {
+ :type => Mime::PNG,
+ :disposition => :disposition,
+ :filename => 'filename'
+ }
+
+ @controller.headers = {}
+ @controller.send(:send_file_headers!, options)
+ assert_equal 'disposition; filename="filename"', @controller.headers['Content-Disposition']
+ end
+
def test_send_file_headers_with_mime_lookup_with_symbol
options = {
:type => :png
@@ -138,7 +150,7 @@ class SendFileTest < ActionController::TestCase
@controller.headers = {}
assert_raise(ArgumentError){ @controller.send(:send_file_headers!, options) }
end
-
+
def test_send_file_headers_guess_type_from_extension
{
'image.png' => 'image/png',
diff --git a/actionpack/test/dispatch/best_standards_support_test.rb b/actionpack/test/dispatch/best_standards_support_test.rb
new file mode 100644
index 0000000000..0737c40a39
--- /dev/null
+++ b/actionpack/test/dispatch/best_standards_support_test.rb
@@ -0,0 +1,34 @@
+require 'abstract_unit'
+
+class BestStandardsSupportTest < ActiveSupport::TestCase
+ def test_with_best_standards_support
+ _, headers, _ = app(true, {}).call({})
+ assert_equal "IE=Edge,chrome=1", headers["X-UA-Compatible"]
+ end
+
+ def test_with_builtin_best_standards_support
+ _, headers, _ = app(:builtin, {}).call({})
+ assert_equal "IE=Edge", headers["X-UA-Compatible"]
+ end
+
+ def test_without_best_standards_support
+ _, headers, _ = app(false, {}).call({})
+ assert_equal nil, headers["X-UA-Compatible"]
+ end
+
+ def test_appends_to_app_headers
+ app_headers = { "X-UA-Compatible" => "requiresActiveX=true" }
+ _, headers, _ = app(true, app_headers).call({})
+
+ expects = "requiresActiveX=true,IE=Edge,chrome=1"
+ assert_equal expects, headers["X-UA-Compatible"]
+ end
+
+ private
+
+ def app(type, headers)
+ app = proc { [200, headers, "response"] }
+ ActionDispatch::BestStandardsSupport.new(app, type)
+ end
+
+end
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index bd5b5edab0..88dc2c093b 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -248,6 +248,11 @@ module TestGenerationPrefix
assert_equal "/something/", app_object.root_path
end
+ test "[OBJECT] generating application's route includes default_url_options[:trailing_slash]" do
+ RailsApplication.routes.default_url_options[:trailing_slash] = true
+ assert_equal "/awesome/blog/posts", engine_object.posts_path
+ end
+
test "[OBJECT] generating engine's route with url_for" do
path = engine_object.url_for(:controller => "inside_engine_generating",
:action => "show",
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 5b3d38c48c..56431b4daf 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -530,6 +530,13 @@ class RequestTest < ActiveSupport::TestCase
assert_equal Mime::XML, request.negotiate_mime([Mime::XML, Mime::CSV])
end
+ test "raw_post rewinds rack.input if RAW_POST_DATA is nil" do
+ request = stub_request('rack.input' => StringIO.new("foo"),
+ 'CONTENT_LENGTH' => 3)
+ assert_equal "foo", request.raw_post
+ assert_equal "foo", request.env['rack.input'].read
+ end
+
test "process parameter filter" do
test_hashes = [
[{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb
new file mode 100644
index 0000000000..629b8ed8bd
--- /dev/null
+++ b/actionpack/test/dispatch/routing/route_set_test.rb
@@ -0,0 +1,86 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Routing
+ class RouteSetTest < ActiveSupport::TestCase
+ class SimpleApp
+ def initialize(response)
+ @response = response
+ end
+
+ def call(env)
+ [ 200, { 'Content-Type' => 'text/plain' }, [response] ]
+ end
+ end
+
+ setup do
+ @set = RouteSet.new
+ end
+
+ test "url helpers are added when route is added" do
+ draw do
+ get 'foo', :to => SimpleApp.new('foo#index')
+ end
+
+ assert_equal '/foo', url_helpers.foo_path
+ assert_raises NoMethodError do
+ assert_equal '/bar', url_helpers.bar_path
+ end
+
+ draw do
+ get 'foo', :to => SimpleApp.new('foo#index')
+ get 'bar', :to => SimpleApp.new('bar#index')
+ end
+
+ assert_equal '/foo', url_helpers.foo_path
+ assert_equal '/bar', url_helpers.bar_path
+ end
+
+ test "url helpers are updated when route is updated" do
+ draw do
+ get 'bar', :to => SimpleApp.new('bar#index'), :as => :bar
+ end
+
+ assert_equal '/bar', url_helpers.bar_path
+
+ draw do
+ get 'baz', :to => SimpleApp.new('baz#index'), :as => :bar
+ end
+
+ assert_equal '/baz', url_helpers.bar_path
+ end
+
+ test "url helpers are removed when route is removed" do
+ draw do
+ get 'foo', :to => SimpleApp.new('foo#index')
+ get 'bar', :to => SimpleApp.new('bar#index')
+ end
+
+ assert_equal '/foo', url_helpers.foo_path
+ assert_equal '/bar', url_helpers.bar_path
+
+ draw do
+ get 'foo', :to => SimpleApp.new('foo#index')
+ end
+
+ assert_equal '/foo', url_helpers.foo_path
+ assert_raises NoMethodError do
+ assert_equal '/bar', url_helpers.bar_path
+ end
+ end
+
+ private
+ def clear!
+ @set.clear!
+ end
+
+ def draw(&block)
+ @set.draw(&block)
+ end
+
+ def url_helpers
+ @set.url_helpers
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index e61b4e2d8f..46d16598f7 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -887,6 +887,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal original_options, options
end
+ def test_url_for_does_not_modify_controller
+ controller = '/projects'
+ options = {:controller => controller, :action => 'status', :only_path => true}
+ url = url_for(options)
+
+ assert_equal '/projects/status', url
+ assert_equal '/projects', controller
+ end
+
# tests the arguments modification free version of define_hash_access
def test_named_route_with_no_side_effects
original_options = { :host => 'test.host' }
diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb
index d45215acfd..b0b407b473 100644
--- a/actionpack/test/template/date_helper_i18n_test.rb
+++ b/actionpack/test/template/date_helper_i18n_test.rb
@@ -36,16 +36,25 @@ class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
end
end
- def assert_distance_of_time_in_words_translates_key(passed, expected)
+ def test_distance_of_time_in_words_calls_i18n_with_custom_scope
+ {
+ [30.days, false] => [:'about_x_months', 1],
+ [60.days, false] => [:'x_months', 2],
+ }.each do |passed, expected|
+ assert_distance_of_time_in_words_translates_key(passed, expected, {:scope => :'datetime.distance_in_words_ago'})
+ end
+ end
+
+ def assert_distance_of_time_in_words_translates_key(passed, expected, options = {})
diff, include_seconds = *passed
key, count = *expected
to = @from + diff
- options = {:locale => 'en', :scope => :'datetime.distance_in_words'}
+ options = {:locale => 'en', :scope => :'datetime.distance_in_words'}.merge(options)
options[:count] = count if count
I18n.expects(:t).with(key, options)
- distance_of_time_in_words(@from, to, include_seconds, :locale => 'en')
+ distance_of_time_in_words(@from, to, include_seconds, options)
end
def test_distance_of_time_pluralizations
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index cf707e1de5..e4f84f8dd7 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -128,12 +128,39 @@ class DateHelperTest < ActionView::TestCase
end
def test_distance_in_words_with_integers
- assert_equal "less than a minute", distance_of_time_in_words(59)
+ assert_equal "1 minute", distance_of_time_in_words(59)
assert_equal "about 1 hour", distance_of_time_in_words(60*60)
- assert_equal "less than a minute", distance_of_time_in_words(0, 59)
+ assert_equal "1 minute", distance_of_time_in_words(0, 59)
assert_equal "about 1 hour", distance_of_time_in_words(60*60, 0)
end
+ def test_distance_in_words_with_times
+ assert_equal "1 minute", distance_of_time_in_words(30.seconds)
+ assert_equal "1 minute", distance_of_time_in_words(59.seconds)
+ assert_equal "2 minutes", distance_of_time_in_words(119.seconds)
+ assert_equal "2 minutes", distance_of_time_in_words(1.minute + 59.seconds)
+ assert_equal "3 minutes", distance_of_time_in_words(2.minute + 30.seconds)
+ assert_equal "44 minutes", distance_of_time_in_words(44.minutes + 29.seconds)
+ assert_equal "about 1 hour", distance_of_time_in_words(44.minutes + 30.seconds)
+ assert_equal "about 1 hour", distance_of_time_in_words(60.minutes)
+
+ # include seconds
+ assert_equal "half a minute", distance_of_time_in_words(39.seconds, 0, true)
+ assert_equal "less than a minute", distance_of_time_in_words(40.seconds, 0, true)
+ assert_equal "less than a minute", distance_of_time_in_words(59.seconds, 0, true)
+ assert_equal "1 minute", distance_of_time_in_words(60.seconds, 0, true)
+ end
+
+ def test_distance_in_words_with_offset_datetimes
+ start_date = DateTime.new 1975, 1, 31, 0, 0, 0, '+6'
+ end_date = DateTime.new 1977, 1, 31, 0, 0, 0, '+6'
+ assert_equal("about 2 years", distance_of_time_in_words(start_date, end_date))
+
+ start_date = DateTime.new 1982, 12, 3, 0, 0, 0, '+6'
+ end_date = DateTime.new 2010, 11, 30, 0, 0, 0, '+6'
+ assert_equal("almost 28 years", distance_of_time_in_words(start_date, end_date))
+ end
+
def test_time_ago_in_words
assert_equal "about 1 year", time_ago_in_words(1.year.ago - 1.day)
end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 19af01e2c8..49a325af79 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -393,6 +393,19 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_check_box_with_multiple_behavior_and_index
+ @post.comment_ids = [2,3]
+ assert_dom_equal(
+ '<input name="post[foo][comment_ids][]" type="hidden" value="0" /><input id="post_foo_comment_ids_1" name="post[foo][comment_ids][]" type="checkbox" value="1" />',
+ check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1)
+ )
+ assert_dom_equal(
+ '<input name="post[bar][comment_ids][]" type="hidden" value="0" /><input checked="checked" id="post_bar_comment_ids_3" name="post[bar][comment_ids][]" type="checkbox" value="3" />',
+ check_box("post", "comment_ids", { :multiple => true, :index => "bar" }, 3)
+ )
+
+ end
+
def test_checkbox_disabled_disables_hidden_field
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" disabled="disabled"/><input checked="checked" disabled="disabled" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb
index f33471ab28..3cb5997a83 100644
--- a/actionpack/test/template/record_tag_helper_test.rb
+++ b/actionpack/test/template/record_tag_helper_test.rb
@@ -81,6 +81,14 @@ class RecordTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
+ def test_content_tag_for_collection_without_given_block
+ post_1 = RecordTagPost.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
+ post_2 = RecordTagPost.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
+ expected = %(<li class="record_tag_post" id="record_tag_post_101"></li>\n<li class="record_tag_post" id="record_tag_post_102"></li>)
+ actual = content_tag_for(:li, [post_1, post_2])
+ assert_dom_equal expected, actual
+ end
+
def test_div_for_collection
post_1 = RecordTagPost.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
post_2 = RecordTagPost.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index bfeb6aee1a..b907e3297b 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -336,7 +336,7 @@ module RenderTestCases
ActionView::Template.register_template_handler :foo, CustomHandler
assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
end
-
+
def test_render_knows_about_types_registered_when_extensions_are_checked_earlier_in_initialization
ActionView::Template::Handlers.extensions
ActionView::Template.register_template_handler :foo, CustomHandler
@@ -406,6 +406,11 @@ module RenderTestCases
@view.render(:partial => 'test/partial_with_layout_block_content', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'})
end
+ def test_render_partial_with_layout_raises_descriptive_error
+ e = assert_raises(ActionView::MissingTemplate) { @view.render(:partial => 'test/partial', :layout => true) }
+ assert_match "Missing partial /true with", e.message
+ end
+
def test_render_with_nested_layout
assert_equal %(<title>title</title>\n\n<div id="column">column</div>\n<div id="content">content</div>\n),
@view.render(:file => "test/nested_layout", :layout => "layouts/yield")
diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb
index 72d03e43e9..e944cfaee3 100644
--- a/actionpack/test/template/sprockets_helper_test.rb
+++ b/actionpack/test/template/sprockets_helper_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'sprockets'
require 'sprockets/helpers/rails_helper'
-require 'mocha'
+require 'mocha/setup'
class SprocketsHelperTest < ActionView::TestCase
include Sprockets::Helpers::RailsHelper
diff --git a/actionpack/test/template/sprockets_helper_with_routes_test.rb b/actionpack/test/template/sprockets_helper_with_routes_test.rb
index bcbd81a7dd..bc253ea0fd 100644
--- a/actionpack/test/template/sprockets_helper_with_routes_test.rb
+++ b/actionpack/test/template/sprockets_helper_with_routes_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'sprockets'
require 'sprockets/helpers/rails_helper'
-require 'mocha'
+require 'mocha/setup'
class SprocketsHelperWithRoutesTest < ActionView::TestCase
include Sprockets::Helpers::RailsHelper
@@ -54,4 +54,4 @@ class SprocketsHelperWithRoutesTest < ActionView::TestCase
stylesheet_link_tag(:application)
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb
index 5880eb2bd4..56943381d8 100644
--- a/actionpack/test/template/template_test.rb
+++ b/actionpack/test/template/template_test.rb
@@ -25,6 +25,10 @@ class TestERBTemplate < ActiveSupport::TestCase
"Hello"
end
+ def apostrophe
+ "l'apostrophe"
+ end
+
def partial
ActionView::Template.new(
"<%= @virtual_path %>",
@@ -47,7 +51,7 @@ class TestERBTemplate < ActiveSupport::TestCase
end
end
- def new_template(body = "<%= hello %>", details = {})
+ def new_template(body = "<%= hello %>", details = { :format => :html })
ActionView::Template.new(body, "hello template", ERBHandler, {:virtual_path => "hello"}.merge!(details))
end
@@ -64,6 +68,16 @@ class TestERBTemplate < ActiveSupport::TestCase
assert_equal "Hello", render
end
+ def test_basic_template_does_html_escape
+ @template = new_template("<%= apostrophe %>")
+ assert_equal "l&#x27;apostrophe", render
+ end
+
+ def test_text_template_does_not_html_escape
+ @template = new_template("<%= apostrophe %> <%== apostrophe %>", :format => :text)
+ assert_equal "l'apostrophe l'apostrophe", render
+ end
+
def test_template_loses_its_source_after_rendering
@template = new_template
render
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 6f09c1cb43..b56016b020 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,4 +1,12 @@
-## Rails 3.2.10 ##
+## Rails 3.2.11 (unreleased) ##
+
+* Specify type of singular association during serialization *Steve Klabnik*
+
+
+## Rails 3.2.10 (Jan 2, 2013) ##
+
+* No changes.
+
## Rails 3.2.9 (Nov 12, 2012) ##
@@ -13,24 +21,29 @@
*Carlos Antonio da Silva*
+
## Rails 3.2.8 (Aug 9, 2012) ##
* No changes.
+
## Rails 3.2.7 (Jul 26, 2012) ##
* `validates_inclusion_of` and `validates_exclusion_of` now accept `:within` option as alias of `:in` as documented.
* Fix the the backport of the object dup with the ruby 1.9.3p194.
+
## Rails 3.2.6 (Jun 12, 2012) ##
* No changes.
+
## Rails 3.2.4 (May 31, 2012) ##
* No changes.
+
## Rails 3.2.3 (March 30, 2012) ##
* No changes.
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 00bd68512b..7eaee050cd 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -84,7 +84,7 @@ module ActiveModel
end
# Backport dup from 1.9 so that #initialize_dup gets called
- unless Object.respond_to?(:initialize_dup)
+ unless Object.respond_to?(:initialize_dup, true)
def dup # :nodoc:
copy = super
copy.initialize_dup(self)
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 2dc7d00f52..a63856045f 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -144,7 +144,12 @@ module ActiveModel
end
else
merged_options[:root] = association.to_s
- records.to_xml(merged_options)
+
+ unless records.class.to_s.underscore == association.to_s
+ merged_options[:type] = records.class.name
+ end
+
+ records.to_xml merged_options
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 45d8677fa0..c7e508958b 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -173,7 +173,7 @@ module ActiveModel
end
# Backport dup from 1.9 so that #initialize_dup gets called
- unless Object.respond_to?(:initialize_dup)
+ unless Object.respond_to?(:initialize_dup, true)
def dup # :nodoc:
copy = super
copy.initialize_dup(self)
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index 83db8d21f4..62c2b2035f 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -6,12 +6,12 @@ require 'ostruct'
class Contact
include ActiveModel::Serializers::Xml
- attr_accessor :address, :friends
+ attr_accessor :address, :friends, :contact
remove_method :attributes if method_defined?(:attributes)
def attributes
- instance_values.except("address", "friends")
+ instance_values.except("address", "friends", "contact")
end
end
@@ -55,6 +55,9 @@ class XmlSerializationTest < ActiveModel::TestCase
@contact.address.state = "CA"
@contact.address.zip = 11111
@contact.friends = [Contact.new, Contact.new]
+ @related_contact = SerializableContact.new
+ @related_contact.name = "related"
+ @contact.contact = @related_contact
end
test "should serialize default root" do
@@ -203,4 +206,9 @@ class XmlSerializationTest < ActiveModel::TestCase
assert_match %r{<friends>}, xml
assert_match %r{<friend>}, xml
end
+
+ test "association with sti" do
+ xml = @contact.to_xml(:include => :contact)
+ assert xml.include?(%(<contact type="SerializableContact">))
+ end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index bd8a0bc039..bd99ba7c09 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,10 +1,191 @@
-## Rails 3.2.10 ##
+## Rails 3.2.11 (unreleased)
+
+* Serialized attributes can be serialized in integer columns.
+ Fix #8575.
+
+ *Rafael Mendonça França*
+
+* Keep index names when using `alter_table` with sqlite3.
+ Fix #3489.
+ Backport #8522.
+
+ *Yves Senn*
+
+* Recognize migrations placed in directories containing numbers and 'rb'.
+ Fix #8492.
+ Backport of #8500.
+
+ *Yves Senn*
+
+* Add `ActiveRecord::Base.cache_timestamp_format` class attribute to control
+ the format of the timestamp value in the cache key.
+ This allows users to improve the precision of the cache key.
+ Fixes #8195.
+
+ *Rafael Mendonça França*
+
+* Add `:nsec` date format. This can be used to improve the precision of cache key.
+ Please note that this format only works with Ruby 1.9, Ruby 1.8 will ignore it completely.
+
+ *Jamie Gaskins*
+
+* Unscope `update_column(s)` query to ignore default scope.
+
+ When applying `default_scope` to a class with a where clause, using
+ `update_column(s)` could generate a query that would not properly update
+ the record due to the where clause from the `default_scope` being applied
+ to the update query.
+
+ class User < ActiveRecord::Base
+ default_scope where(active: true)
+ end
+
+ user = User.first
+ user.active = false
+ user.save!
+
+ user.update_column(:active, true) # => false
+
+ In this situation we want to skip the default_scope clause and just
+ update the record based on the primary key. With this change:
+
+ user.update_column(:active, true) # => true
+
+ Backport of #8436 fix.
+
+ *Carlos Antonio da Silva*
+
+* Fix performance problem with primary_key method in PostgreSQL adapter when having many schemas.
+ Uses pg_constraint table instead of pg_depend table which has many records in general.
+ Fix #8414
+
+ *kennyj*
+
+* Do not instantiate intermediate Active Record objects when eager loading.
+ These records caused `after_find` to run more than expected.
+ Fix #3313
+ Backport of #8403
+
+ *Yves Senn*
+
+* Fix `pluck` to work with joins. Backport of #4942.
+
+ *Carlos Antonio da Silva*
+
+* Fix a problem with `translate_exception` method in a non English environment.
+ Backport of #6397.
+
+ *kennyj*
+
+* Fix dirty attribute checks for TimeZoneConversion with nil and blank
+ datetime attributes. Setting a nil datetime to a blank string should not
+ result in a change being flagged.
+ Fixes #8310.
+ Backport of #8311.
+
+ *Alisdair McDiarmid*
+
+* Prevent mass assignment to the type column of polymorphic associations when using `build`.
+ Fixes #8265.
+ Backport of #8291.
+
+ *Yves Senn*
+
+* When running migrations on Postgresql, the `:limit` option for `binary` and `text` columns is
+ silently dropped.
+ Previously, these migrations caused sql exceptions, because Postgresql doesn't support limits
+ on these types.
+
+ *Victor Costan*
+
+* Calling `include?` on `has_many` associations on unsaved records no longer
+ returns `true` when passed a record with a `nil` foreign key.
+ Fixes #7950.
+
+ *George Brocklehurst*
+
+* `#pluck` can be used on a relation with `select` clause.
+ Fixes #7551.
+ Backport of #8176.
+
+ Example:
+
+ Topic.select([:approved, :id]).order(:id).pluck(:id)
+
+ *Yves Senn*
+
+* Use `nil?` instead of `blank?` to check whether dynamic finder with a bang
+ should raise RecordNotFound.
+ Fixes #7238.
+
+ *Nikita Afanasenko*
+
+* Fix deleting from a HABTM join table upon destroying an object of a model
+ with optimistic locking enabled.
+ Fixes #5332.
+
+ *Nick Rogers*
+
+* Use query cache/uncache when using ENV["DATABASE_URL"].
+ Fixes #6951.
+ Backport of #8074.
+
+ *kennyj*
+
+* Do not create useless database transaction when building `has_one` association.
+
+ Example:
+
+ User.has_one :profile
+ User.new.build_profile
+
+ Backport of #8154.
+
+ *Bogdan Gusiev*
+
+* `AR::Base#attributes_before_type_cast` now returns unserialized values for serialized attributes.
+
+ *Nikita Afanasenko*
+
+* Fix issue that raises `NameError` when overriding the `accepts_nested_attributes` in child classes.
+
+ Before:
+
+ class Shared::Person < ActiveRecord::Base
+ has_one :address
+
+ accepts_nested_attributes :address, :reject_if => :all_blank
+ end
+
+ class Person < Shared::Person
+ accepts_nested_attributes :address
+ end
+
+ Person
+ #=> NameError: method `address_attributes=' not defined in Person
+
+ After:
+
+ Person
+ #=> Person(id: integer, ...)
+
+ Fixes #8131.
+
+ *Gabriel Sobrinho, Ricardo Henrique*
+
+
+## Rails 3.2.10 (Jan 2, 2013) ##
* CVE-2012-5664 options hashes should only be extracted if there are extra
parameters
+
## Rails 3.2.9 (Nov 12, 2012) ##
+* Fix `find_in_batches` crashing when IDs are strings and start option is not specified.
+
+ *Alexis Bernard*
+
* Fix issue with collection associations calling first(n)/last(n) and attempting
to set the inverse association when `:inverse_of` was used. Fixes #8087.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 59c1bad559..ab0d888b16 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -231,7 +231,8 @@ module ActiveRecord
def build_record(attributes, options)
reflection.build_association(attributes, options) do |record|
- attributes = create_scope.except(*(record.changed - [reflection.foreign_key]))
+ skip_assign = [reflection.foreign_key, reflection.type].compact
+ attributes = create_scope.except(*(record.changed - skip_assign))
record.assign_attributes(attributes, :without_protection => true)
end
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index f9cffa40c8..226639ad37 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -33,6 +33,18 @@ module ActiveRecord
private
+ def column_for(table_name, column_name)
+ columns = alias_tracker.connection.schema_cache.columns_hash[table_name]
+ columns[column_name]
+ end
+
+ def bind(scope, column, value)
+ substitute = alias_tracker.connection.substitute_at(
+ column, scope.bind_values.length)
+ scope.bind_values += [[column, value]]
+ substitute
+ end
+
def add_constraints(scope)
tables = construct_tables
@@ -67,7 +79,9 @@ module ActiveRecord
conditions = self.conditions[i]
if reflection == chain.last
- scope = scope.where(table[key].eq(owner[foreign_key]))
+ column = column_for(table.table_name, key.to_s)
+ bind_val = bind(scope, column, owner[foreign_key])
+ scope = scope.where(table[key].eq(bind_val))
if reflection.type
scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 501ebe7c5b..56f9013d61 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -11,7 +11,7 @@ module ActiveRecord
# If target and record are nil, or target is equal to record,
# we don't need to have transaction.
if (target || record) && target != record
- reflection.klass.transaction do
+ transaction_if(save) do
remove_target!(options[:dependent]) if target && !target.destroyed?
if record
@@ -70,6 +70,14 @@ module ActiveRecord
def nullify_owner_attributes(record)
record[reflection.foreign_key] = nil
end
+
+ def transaction_if(value)
+ if value
+ reflection.klass.transaction { yield }
+ else
+ yield
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 00023b0b6c..deb89f3ac1 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -90,6 +90,14 @@ module ActiveRecord
end
end
+ def _field_changed?(attr, old, value)
+ if self.class.serialized_attributes.include?(attr)
+ old != value
+ else
+ super
+ end
+ end
+
def read_attribute_before_type_cast(attr_name)
if serialized_attributes.include?(attr_name)
super.unserialized_value
@@ -97,6 +105,16 @@ module ActiveRecord
super
end
end
+
+ def attributes_before_type_cast
+ super.dup.tap do |attributes|
+ self.class.serialized_attributes.each_key do |key|
+ if attributes.key?(key)
+ attributes[key] = attributes[key].unserialized_value
+ end
+ end
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 4c3d5eed4c..39d81cf6ef 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -37,6 +37,7 @@ module ActiveRecord
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
method_body, line = <<-EOV, __LINE__ + 1
def #{attr_name}=(original_time)
+ original_time = nil if original_time.blank?
time = original_time
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 62c8110274..612114bbce 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -557,7 +557,7 @@ module ActiveRecord #:nodoc:
end
# Backport dup from 1.9 so that initialize_dup() gets called
- unless Object.respond_to?(:initialize_dup)
+ unless Object.respond_to?(:initialize_dup, true)
def dup # :nodoc:
copy = super
copy.initialize_dup(self)
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 db99c3fbef..c49aed7069 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -4,6 +4,7 @@ module ActiveRecord
# Converts an arel AST to SQL
def to_sql(arel, binds = [])
if arel.respond_to?(:ast)
+ binds = binds.dup
visitor.accept(arel.ast) do
quote(*binds.shift.reverse)
end
@@ -20,14 +21,14 @@ module ActiveRecord
# Returns a record hash with the column names as keys and column values
# as values.
- def select_one(arel, name = nil)
- result = select_all(arel, name)
+ def select_one(arel, name = nil, binds = [])
+ result = select_all(arel, name, binds)
result.first if result
end
# Returns a single value from a record
- def select_value(arel, name = nil)
- if result = select_one(arel, name)
+ def select_value(arel, name = nil, binds = [])
+ if result = select_one(arel, name, binds)
result.values.first
end
end
@@ -266,7 +267,7 @@ module ActiveRecord
# Inserts the given fixture into the table. Overridden in adapters that require
# something beyond a simple insert (eg. Oracle).
def insert_fixture(fixture, table_name)
- columns = Hash[columns(table_name).map { |c| [c.name, c] }]
+ columns = schema_cache.columns_hash(table_name)
key_list = []
value_list = fixture.map do |name, value|
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index c132692249..4850b68051 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -2,7 +2,7 @@ require 'active_record/connection_adapters/abstract_mysql_adapter'
require 'active_record/connection_adapters/statement_pool'
require 'active_support/core_ext/hash/keys'
-gem 'mysql', '~> 2.8.1'
+gem 'mysql', '~> 2.8'
require 'mysql'
class Mysql
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 80a0c6d13c..5a53135901 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -988,12 +988,11 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
row = exec_query(<<-end_sql, 'SCHEMA').rows.first
- SELECT DISTINCT(attr.attname)
+ SELECT attr.attname
FROM pg_attribute attr
- INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
WHERE cons.contype = 'p'
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
+ AND cons.conrelid = '#{quote_table_name(table)}'::regclass
end_sql
row && row.first
@@ -1078,6 +1077,13 @@ module ActiveRecord
when nil, 0..0x3fffffff; super(type)
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
end
+ when 'text'
+ # PostgreSQL doesn't support limits on text columns.
+ # The hard limit is 1Gb, according to section 8.3 in the manual.
+ case limit
+ when nil, 0..0x3fffffff; super(type)
+ else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
+ end
when 'integer'
return 'integer' unless limit
@@ -1135,11 +1141,15 @@ module ActiveRecord
@connection.server_version
end
+ # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
+ FOREIGN_KEY_VIOLATION = "23503"
+ UNIQUE_VIOLATION = "23505"
+
def translate_exception(exception, message)
- case exception.message
- when /duplicate key value violates unique constraint/
+ case exception.result.error_field(PGresult::PG_DIAG_SQLSTATE)
+ when UNIQUE_VIOLATION
RecordNotUnique.new(message, exception)
- when /violates foreign key constraint/
+ when FOREIGN_KEY_VIOLATION
InvalidForeignKey.new(message, exception)
else
super
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 4e8932a695..bc8d24a03f 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
class SchemaCache
- attr_reader :columns, :columns_hash, :primary_keys, :tables
+ attr_reader :primary_keys, :tables
attr_reader :connection
def initialize(conn)
@@ -30,6 +30,25 @@ module ActiveRecord
@tables[name] = connection.table_exists?(name)
end
+ # Get the columns for a table
+ def columns(table = nil)
+ if table
+ @columns[table]
+ else
+ @columns
+ end
+ end
+
+ # Get the columns for a table as a hash, key is the column name
+ # value is the column object.
+ def columns_hash(table = nil)
+ if table
+ @columns_hash[table]
+ else
+ @columns_hash
+ end
+ end
+
# Clears out internal caches
def clear!
@columns.clear
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index e80b465bab..ca84c95bdc 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -530,7 +530,7 @@ module ActiveRecord
unless columns.empty?
# index name can't be the same
- opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
+ opts = { :name => name.gsub(/(^|_)(#{from})_/, "\\1#{to}_") }
opts[:unique] = true if index.unique
add_index(to, columns, opts)
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 2c42f4cca5..f2ace18d44 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -1,5 +1,16 @@
module ActiveRecord
module Integration
+ extend ActiveSupport::Concern
+
+ included do
+ ##
+ # :singleton-method:
+ # Indicates the format used to generate the timestamp format in the cache key.
+ # This is +:number+, by default.
+ class_attribute :cache_timestamp_format, :instance_writer => false
+ self.cache_timestamp_format = :number
+ end
+
# Returns a String, which Action Pack uses for constructing an URL to this
# object. The default implementation returns this record's id as a String,
# or nil if this record's unsaved.
@@ -39,7 +50,7 @@ module ActiveRecord
when new_record?
"#{self.class.model_name.cache_key}/new"
when timestamp = self[:updated_at]
- timestamp = timestamp.utc.to_s(:number)
+ timestamp = timestamp.utc.to_s(cache_timestamp_format)
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
else
"#{self.class.model_name.cache_key}/#{id}"
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 7deac2588a..b2881991d5 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -102,6 +102,8 @@ module ActiveRecord
def destroy #:nodoc:
return super unless locking_enabled?
+ destroy_associations
+
if persisted?
table = self.class.arel_table
lock_col = self.class.locking_column
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index e6686597b7..e8ec93470d 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -458,7 +458,7 @@ module ActiveRecord
say_with_time "#{method}(#{arg_list})" do
unless reverting?
unless arguments.empty? || method == :execute
- arguments[0] = Migrator.proper_table_name(arguments.first)
+ arguments[0] = Migrator.proper_table_name(arguments.first) unless method == :assume_migrated_upto_version
arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table
end
end
@@ -627,7 +627,7 @@ module ActiveRecord
seen = Hash.new false
migrations = files.map do |file|
- version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first
+ version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/).first
raise IllegalMigrationNameError.new(file) unless version
version = version.to_i
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index d2065d701f..05091654c0 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -277,13 +277,14 @@ module ActiveRecord
type = (reflection.collection? ? :collection : :one_to_one)
+ # remove_possible_method :pirate_attributes=
+ #
# def pirate_attributes=(attributes)
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
# end
class_eval <<-eoruby, __FILE__, __LINE__ + 1
- if method_defined?(:#{association_name}_attributes=)
- remove_method(:#{association_name}_attributes=)
- end
+ remove_possible_method(:#{association_name}_attributes=)
+
def #{association_name}_attributes=(attributes)
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index fd3380a53c..36b6b5fce2 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -194,7 +194,7 @@ module ActiveRecord
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
raise ActiveRecordError, "can not update on a new record object" unless persisted?
- updated_count = self.class.update_all({ name => value }, self.class.primary_key => id)
+ updated_count = self.class.unscoped.update_all({ name => value }, self.class.primary_key => id)
raw_write_attribute(name, value)
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 466d148901..2156889a0f 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -6,19 +6,19 @@ module ActiveRecord
module ClassMethods
# Enable the query cache within the block if Active Record is configured.
def cache(&block)
- if ActiveRecord::Base.configurations.blank?
- yield
- else
+ if ActiveRecord::Base.connected?
connection.cache(&block)
+ else
+ yield
end
end
# Disable the query cache within the block if Active Record is configured.
def uncached(&block)
- if ActiveRecord::Base.configurations.blank?
- yield
- else
+ if ActiveRecord::Base.connected?
connection.uncached(&block)
+ else
+ yield
end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index ae6d9c8653..1bdb841398 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -64,10 +64,21 @@ db_namespace = namespace :db do
end
end
+ # If neither encoding nor collation is specified, use the utf-8 defaults.
def mysql_creation_options(config)
- @charset = ENV['CHARSET'] || 'utf8'
- @collation = ENV['COLLATION'] || 'utf8_unicode_ci'
- {:charset => (config['encoding'] || @charset), :collation => (config['collation'] || @collation)}
+ default_charset = ENV['CHARSET'] || 'utf8'
+ default_collation = ENV['COLLATION'] || 'utf8_unicode_ci'
+
+ Hash.new.tap do |options|
+ options[:charset] = config['encoding'] if config.include? 'encoding'
+ options[:collation] = config['collation'] if config.include? 'collation'
+
+ # Set default charset only when collation isn't set.
+ options[:charset] ||= default_charset unless options[:collation]
+
+ # Set default collation only when charset is also default.
+ options[:collation] ||= default_collation if options[:charset] == default_charset
+ end
end
def create_database(config)
@@ -101,9 +112,12 @@ db_namespace = namespace :db do
error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error
end
access_denied_error = 1045
+
+ create_options = mysql_creation_options(config)
+
begin
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
- ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
+ ActiveRecord::Base.connection.create_database(config['database'], create_options)
ActiveRecord::Base.establish_connection(config)
rescue error_class => sqlerr
if sqlerr.errno == access_denied_error
@@ -119,7 +133,7 @@ db_namespace = namespace :db do
ActiveRecord::Base.establish_connection(config)
else
$stderr.puts sqlerr.error
- $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['encoding'] || @charset}, collation: #{config['collation'] || @collation}"
+ $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{create_options[:charset]}, collation: #{create_options[:collation]}"
$stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['encoding']
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 4b3b30d6ed..a727f40194 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -464,7 +464,12 @@ module ActiveRecord
node.left.relation.name == table_name
}
- Hash[equalities.map { |where| [where.left.name, where.right] }]
+ binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
+
+ Hash[equalities.map { |where|
+ name = where.left.name
+ [name, binds.fetch(name.to_s) { where.right }]
+ }]
end
def scope_for_create
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 2fd89882ff..14701f668c 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -59,11 +59,11 @@ module ActiveRecord
relation = apply_finder_options(finder_options)
end
- start = options.delete(:start).to_i
+ start = options.delete(:start)
batch_size = options.delete(:batch_size) || 1000
relation = relation.reorder(batch_order).limit(batch_size)
- records = relation.where(table[primary_key].gteq(start)).all
+ records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
while records.any?
records_size = records.size
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 802059db21..20c5fa03a2 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -177,8 +177,15 @@ module ActiveRecord
# Person.where(:confirmed => true).limit(5).pluck(:id)
#
def pluck(column_name)
- column_name = column_name.to_s
- klass.connection.select_all(select(column_name).arel).map! do |attributes|
+ if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
+ column_name = "#{table_name}.#{column_name}"
+ else
+ column_name = column_name.to_s
+ end
+
+ relation = clone
+ relation.select_values = [column_name]
+ klass.connection.select_all(relation.arel, nil, bind_values).map! do |attributes|
klass.type_cast_attribute(attributes.keys.first, klass.initialize_attributes(attributes))
end
end
@@ -240,7 +247,8 @@ module ActiveRecord
query_builder = relation.arel
end
- type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation)
+ result = @klass.connection.select_value(query_builder, nil, relation.bind_values)
+ type_cast_calculated_value(result, column_for(column_name), operation)
end
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
@@ -286,7 +294,7 @@ module ActiveRecord
relation = except(:group).group(group)
relation.select_values = select_values
- calculated_data = @klass.connection.select_all(relation)
+ calculated_data = @klass.connection.select_all(relation, nil, bind_values)
if association
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index abc67d9c15..cbb2f676c8 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -199,7 +199,7 @@ module ActiveRecord
relation = relation.where(table[primary_key].eq(id)) if id
end
- connection.select_value(relation, "#{name} Exists") ? true : false
+ connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
rescue ThrowResult
false
end
@@ -253,9 +253,11 @@ module ActiveRecord
orders = relation.order_values.map { |val| val.presence }.compact
values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
- relation = relation.dup
+ relation = relation.dup.select(values)
+
+ id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
+ ids_array = id_rows.map {|row| row[primary_key]}
- ids_array = relation.select(values).collect {|row| row[primary_key]}
ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
end
@@ -263,7 +265,7 @@ module ActiveRecord
conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
result = where(conditions).send(match.finder)
- if match.bang? && result.blank?
+ if match.bang? && result.nil?
raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
else
yield(result) if block_given?
@@ -332,7 +334,7 @@ module ActiveRecord
substitute = connection.substitute_at(column, @bind_values.length)
relation = where(table[primary_key].eq(substitute))
- relation.bind_values = [[column, id]]
+ relation.bind_values += [[column, id]]
record = relation.first
unless record
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index c25570d758..3eead56e47 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -22,7 +22,7 @@ module ActiveRecord
end
end
- (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order]).each do |method|
+ (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order, :binds]).each do |method|
value = r.send(:"#{method}_values")
merged_relation.send(:"#{method}_values=", merged_relation.send(:"#{method}_values") + value) if value.present?
end
@@ -31,6 +31,8 @@ module ActiveRecord
merged_wheres = @where_values + r.where_values
+ merged_binds = (@bind_values + r.bind_values).uniq(&:first)
+
unless @where_values.empty?
# Remove duplicates, last one wins.
seen = Hash.new { |h,table| h[table] = {} }
@@ -47,6 +49,7 @@ module ActiveRecord
end
merged_relation.where_values = merged_wheres
+ merged_relation.bind_values = merged_binds
(Relation::SINGLE_VALUE_METHODS - [:lock, :create_with, :reordering]).each do |method|
value = r.send(:"#{method}_value")
diff --git a/activerecord/test/cases/adapters/postgresql/sql_types_test.rb b/activerecord/test/cases/adapters/postgresql/sql_types_test.rb
new file mode 100644
index 0000000000..d7d40f6385
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/sql_types_test.rb
@@ -0,0 +1,18 @@
+require "cases/helper"
+
+class SqlTypesTest < ActiveRecord::TestCase
+ def test_binary_types
+ assert_equal 'bytea', type_to_sql(:binary, 100_000)
+ assert_raise ActiveRecord::ActiveRecordError do
+ type_to_sql :binary, 4294967295
+ end
+ assert_equal 'text', type_to_sql(:text, 100_000)
+ assert_raise ActiveRecord::ActiveRecordError do
+ type_to_sql :text, 4294967295
+ end
+ end
+
+ def type_to_sql(*args)
+ ActiveRecord::Base.connection.type_to_sql(*args)
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index ec69a36174..614be8760a 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -157,7 +157,6 @@ module ActiveRecord
binary = DualEncoding.new :name => 'いただきます!', :data => str
binary.save!
assert_equal str, binary.data
-
ensure
if "<3".respond_to?(:encode)
DualEncoding.connection.drop_table('dual_encodings')
@@ -167,8 +166,9 @@ module ActiveRecord
def test_type_cast_should_not_mutate_encoding
return skip('only test encoding on 1.9') unless "<3".encoding_aware?
- name = 'hello'.force_encoding(Encoding::ASCII_8BIT)
- owner = Owner.create(:name => name)
+ name = 'hello'.force_encoding(Encoding::ASCII_8BIT)
+ Owner.create(:name => name)
+
assert_equal Encoding::ASCII_8BIT, name.encoding
end
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 588adc38e3..ee338a3b99 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -11,23 +11,30 @@ if ActiveRecord::Base.connection.supports_migrations?
def teardown
@connection.drop_table :fruits rescue nil
+ @connection.drop_table :"_pre_fruits_suf_" rescue nil
+ @connection.drop_table :"_pre_schema_migrations_suf_" rescue nil
end
def test_schema_define
- ActiveRecord::Schema.define(:version => 7) do
- create_table :fruits do |t|
- t.column :color, :string
- t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
- t.column :texture, :string
- t.column :flavor, :string
- end
- end
+ perform_schema_define!
assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" }
assert_equal 7, ActiveRecord::Migrator::current_version
end
+ def test_schema_define_with_table_prefix_and_suffix
+ ActiveRecord::Base.table_name_prefix = '_pre_'
+ ActiveRecord::Base.table_name_suffix = '_suf_'
+
+ perform_schema_define!
+
+ assert_equal 7, ActiveRecord::Migrator::current_version
+ ensure
+ ActiveRecord::Base.table_name_prefix = ''
+ ActiveRecord::Base.table_name_suffix = ''
+ end
+
def test_schema_raises_an_error_for_invalid_column_type
assert_raise NoMethodError do
ActiveRecord::Schema.define(:version => 8) do
@@ -39,4 +46,17 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ private
+
+ def perform_schema_define!
+ ActiveRecord::Schema.define(:version => 7) do
+ create_table :fruits do |t|
+ t.column :color, :string
+ t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
+ t.column :texture, :string
+ t.column :flavor, :string
+ end
+ end
+ end
+
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index bf01b46852..d3601e0dba 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -916,6 +916,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).size
end
+ def test_dont_create_temporary_active_record_instances
+ Developer.instance_count = 0
+ developers = Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).to_a
+ assert_equal developers.count, Developer.instance_count
+ end
+
def test_order_on_join_table_with_include_and_limit
assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index ed4475770b..b4788e0a3d 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1225,6 +1225,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert companies(:first_firm).clients.include?(Client.find(2))
end
+ def test_included_in_collection_for_new_records
+ client = Client.create(:name => 'Persisted')
+ assert_nil client.client_of
+ assert !Firm.new.clients_of_firm.include?(client),
+ 'includes a client that does not belong to any firm'
+ end
+
def test_adding_array_and_collection
assert_nothing_raised { Firm.find(:first).clients + Firm.find(:all).last.clients }
end
@@ -1697,6 +1704,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [tagging], post.taggings
end
+ def test_build_with_polymotphic_has_many_does_not_allow_to_override_type_and_id
+ welcome = posts(:welcome)
+ tagging = welcome.taggings.build(:taggable_id => 99, :taggable_type => 'ShouldNotChange')
+
+ assert_equal welcome.id, tagging.taggable_id
+ assert_equal 'Post', tagging.taggable_type
+ end
+
def test_dont_call_save_callbacks_twice_on_has_many
firm = companies(:first_firm)
contract = firm.contracts.create!
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 08831a42ba..31aa3788c7 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -173,6 +173,12 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.account
end
+ def test_build_association_dont_create_transaction
+ assert_no_queries {
+ Firm.new.build_account
+ }
+ end
+
def test_build_and_create_should_not_happen_within_scope
pirate = pirates(:blackbeard)
scoped_count = pirate.association(:foo_bulb).scoped.where_values.count
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 073e856e5e..b849eead26 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -23,6 +23,8 @@ require 'models/edge'
require 'models/joke'
require 'models/bulb'
require 'models/bird'
+require 'models/car'
+require 'models/bulb'
require 'rexml/document'
require 'active_support/core_ext/exception'
require 'bcrypt'
@@ -1293,6 +1295,16 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal({ :foo => :bar }, t.content_before_type_cast)
end
+ def test_serialized_attributes_before_type_cast_returns_unserialized_value
+ Topic.serialize :content, Hash
+
+ t = Topic.new(:content => { :foo => :bar })
+ assert_equal({ :foo => :bar }, t.attributes_before_type_cast["content"])
+ t.save!
+ t.reload
+ assert_equal({ :foo => :bar }, t.attributes_before_type_cast["content"])
+ end
+
def test_serialized_attribute_calling_dup_method
klass = Class.new(ActiveRecord::Base)
klass.table_name = "topics"
@@ -1430,6 +1442,29 @@ class BasicsTest < ActiveRecord::TestCase
Topic.serialize(:content)
end
+ def test_serialize_attribute_via_select_method_when_time_zone_available
+ ActiveRecord::Base.time_zone_aware_attributes = true
+ Topic.serialize(:content, MyObject)
+
+ myobj = MyObject.new('value1', 'value2')
+ topic = Topic.create(:content => myobj)
+
+ ActiveRecord::IdentityMap.without do
+ assert_equal(myobj, Topic.select(:content).find(topic.id).content)
+ assert_raise(ActiveModel::MissingAttributeError) { Topic.select(:id).find(topic.id).content }
+ end
+ ensure
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ end
+
+ def test_serialize_attribute_can_be_serialized_in_an_integer_column
+ insures = ['life']
+ person = SerializedPerson.new(:first_name => 'David', :insures => insures)
+ assert person.save
+ person = person.reload
+ assert_equal(insures, person.insures)
+ end
+
def test_quote
author_name = "\\ \001 ' \n \\n \""
topic = Topic.create('author_name' => author_name)
@@ -2144,6 +2179,29 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key
end
+ def test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format
+ return skip "Only in Ruby 1.9" if RUBY_VERSION < '1.9'
+
+ dev = CachedDeveloper.first
+ assert_equal "cached_developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key
+ end
+
+ def test_cache_key_changes_when_child_touched
+ return skip "Only in Ruby 1.9" if RUBY_VERSION < '1.9'
+
+ old_timestamp_format = Car.cache_timestamp_format
+ Car.cache_timestamp_format = :nsec
+ car = Car.create
+ Bulb.create(:car => car)
+
+ key = car.cache_key
+ car.bulb.touch
+ car.reload
+ assert_not_equal key, car.cache_key
+ ensure
+ Car.cache_timestamp_format = old_timestamp_format
+ end
+
def test_cache_key_format_for_existing_record_with_nil_updated_at
dev = Developer.first
dev.update_attribute(:updated_at, nil)
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 660098b9ad..ad2a749ab4 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -1,8 +1,9 @@
require 'cases/helper'
require 'models/post'
+require 'models/subscriber'
class EachTest < ActiveRecord::TestCase
- fixtures :posts
+ fixtures :posts, :subscribers
def setup
@posts = Post.order("id asc")
@@ -136,4 +137,24 @@ class EachTest < ActiveRecord::TestCase
assert_equal special_posts_ids, posts.map(&:id)
end
+ def test_find_in_batches_should_use_any_column_as_primary_key
+ nick_order_subscribers = Subscriber.order('nick asc')
+ start_nick = nick_order_subscribers.second.nick
+
+ subscribers = []
+ Subscriber.find_in_batches(:batch_size => 1, :start => start_nick) do |batch|
+ subscribers.concat(batch)
+ end
+
+ assert_equal nick_order_subscribers[1..-1].map(&:id), subscribers.map(&:id)
+ end
+
+ def test_find_in_batches_should_use_any_column_as_primary_key_when_start_is_not_specified
+ Subscriber.count('nick') # preheat arel's table cache
+ assert_queries(Subscriber.count + 1) do
+ Subscriber.find_each(:batch_size => 1) do |subscriber|
+ assert_kind_of Subscriber, subscriber
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index cf1181e829..f931b39998 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -487,4 +487,20 @@ class CalculationsTest < ActiveRecord::TestCase
def test_pluck_with_qualified_column_name
assert_equal [1,2,3,4], Topic.order(:id).pluck("topics.id")
end
+
+ def test_pluck_replaces_select_clause
+ taks_relation = Topic.select([:approved, :id]).order(:id)
+ assert_equal [1,2,3,4], taks_relation.pluck(:id)
+ assert_equal [false, true, true, true], taks_relation.pluck(:approved)
+ end
+
+ def test_pluck_auto_table_name_prefix
+ c = Company.create!(:name => "test", :contracts => [Contract.new])
+ assert_equal [c.id], Company.joins(:contracts).pluck(:id)
+ end
+
+ def test_pluck_not_auto_table_name_prefix_if_column_joined
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+ assert_equal [7], Company.joins(:contracts).pluck(:developer_id).map(&:to_i)
+ end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 4fc7e77786..86a28d95ad 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -201,6 +201,20 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ def test_nullable_datetime_not_marked_as_changed_if_new_value_is_blank
+ in_time_zone 'Edinburgh' do
+ target = Class.new(ActiveRecord::Base)
+ target.table_name = 'topics'
+
+ topic = target.create
+ assert_equal nil, topic.written_on
+
+ topic.written_on = ""
+ assert_equal nil, topic.written_on
+ assert !topic.written_on_changed?
+ end
+ end
+
def test_integer_zero_to_string_zero_not_marked_as_changed
pirate = Pirate.new
pirate.parrot_id = 0
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 77ca3b574d..7d63d76c34 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -664,6 +664,11 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
end
+ def test_find_by_one_attribute_bang_with_blank_defined
+ blank_topic = BlankTopic.create(:title => "The Blank One")
+ assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One")
+ end
+
def test_find_by_one_attribute_with_order_option
assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index e352a55104..cf49b125d7 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -4,7 +4,7 @@ require 'config'
require 'test/unit'
require 'stringio'
-require 'mocha'
+require 'mocha/setup'
require 'active_record'
require 'active_support/dependencies'
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 015a3ccefd..066a60f81a 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -7,6 +7,7 @@ require 'models/ship'
require 'models/legacy_thing'
require 'models/reference'
require 'models/string_key_object'
+require 'models/treasure'
class LockWithoutDefault < ActiveRecord::Base; end
@@ -20,7 +21,7 @@ class ReadonlyNameShip < Ship
end
class OptimisticLockingTest < ActiveRecord::TestCase
- fixtures :people, :legacy_things, :references, :string_key_objects
+ fixtures :people, :legacy_things, :references, :string_key_objects, :peoples_treasures
def test_non_integer_lock_existing
s1 = StringKeyObject.find("record1")
@@ -267,6 +268,15 @@ class SetLockingColumnTest < ActiveRecord::TestCase
assert_equal "omg", k.original_locking_column
end
end
+
+ def test_removing_has_and_belongs_to_many_associations_upon_destroy
+ p = RichPerson.create! :first_name => 'Jon'
+ p.treasures.create!
+ assert !p.treasures.empty?
+ p.destroy
+ assert p.treasures.empty?
+ assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty?
+ end
end
class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index f0c55b1535..7bb71b7b6e 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1051,6 +1051,18 @@ if ActiveRecord::Base.connection.supports_migrations?
Person.connection.remove_column("people", "administrator") rescue nil
end
+ def test_change_column_with_custom_index_name
+ Person.connection.add_column "people", "category", :string, :default => 'human'
+ Person.connection.add_index :people, :category, :name => 'people_categories_idx'
+
+ assert_equal ['people_categories_idx'], Person.connection.indexes('people').map(&:name)
+ Person.connection.change_column "people", "category", :string, :null => false, :default => 'article'
+
+ assert_equal ['people_categories_idx'], Person.connection.indexes('people').map(&:name)
+ ensure
+ Person.connection.remove_column("people", "category") rescue nil
+ end
+
def test_change_column_default
Person.connection.change_column_default "people", "first_name", "Tester"
Person.reset_column_information
@@ -1430,6 +1442,12 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ def test_finds_migrations_in_numbered_directory
+ migrations = ActiveRecord::Migrator.migrations [MIGRATIONS_ROOT + '/10_urban']
+ assert_equal 9, migrations[0].version
+ assert_equal 'AddExpressions', migrations[0].name
+ end
+
def test_dump_schema_information_outputs_lexically_ordered_versions
migration_path = MIGRATIONS_ROOT + '/valid_with_timestamps'
ActiveRecord::Migrator.run(:up, migration_path, 20100301010101)
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 16b1eb040e..85b9d3c1a1 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -172,6 +172,17 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}]
assert_equal man.interests.first.topic, man.interests[0].topic
end
+
+ def test_accepts_nested_attributes_for_can_be_overridden_in_subclasses
+ Pirate.accepts_nested_attributes_for(:parrot)
+
+ mean_pirate_class = Class.new(Pirate) do
+ accepts_nested_attributes_for :parrot
+ end
+ mean_pirate = mean_pirate_class.new
+ mean_pirate.parrot_attributes = { :name => "James" }
+ assert_equal "James", mean_pirate.parrot.name
+ end
end
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index a35cb3c4a5..00dc1f6d72 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -519,6 +519,14 @@ class PersistencesTest < ActiveRecord::TestCase
assert return_value
end
+ def test_update_column_with_default_scope
+ developer = DeveloperCalledDavid.first
+ developer.name = 'John'
+ developer.save!
+
+ assert developer.update_column(:name, 'Will'), 'did not update record due to default scope'
+ end
+
def test_update_attributes
topic = Topic.find(1)
assert !topic.approved?
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index dd881f8230..dfc6ea2457 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -173,6 +173,17 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_queries(2) { task.lock!; task.lock! }
end
end
+
+ def test_cache_is_available_when_connection_is_connected
+ conf = ActiveRecord::Base.configurations
+
+ ActiveRecord::Base.configurations = {}
+ Task.cache do
+ assert_queries(1) { Task.find(1); Task.find(1) }
+ end
+ ensure
+ ActiveRecord::Base.configurations = conf
+ end
end
class QueryCacheExpiryTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index b24df47efd..ada4294401 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1185,4 +1185,18 @@ class RelationTest < ActiveRecord::TestCase
end
assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name)
end
+
+ test 'group with select and includes' do
+ authors_count = Post.select('author_id, COUNT(author_id) AS num_posts').
+ group('author_id').order('author_id').includes(:author).to_a
+
+ assert_no_queries do
+ result = authors_count.map do |post|
+ [post.num_posts.to_i, post.author.try(:name)]
+ end
+
+ expected = [[1, nil], [5, "David"], [3, "Mary"], [2, "Bob"]]
+ assert_equal expected, result
+ end
+ end
end
diff --git a/activerecord/test/fixtures/peoples_treasures.yml b/activerecord/test/fixtures/peoples_treasures.yml
new file mode 100644
index 0000000000..a72b190d0c
--- /dev/null
+++ b/activerecord/test/fixtures/peoples_treasures.yml
@@ -0,0 +1,3 @@
+michael_diamond:
+ rich_person_id: <%= ActiveRecord::Fixtures.identify(:michael) %>
+ treasure_id: <%= ActiveRecord::Fixtures.identify(:diamond) %>
diff --git a/activerecord/test/migrations/10_urban/9_add_expressions.rb b/activerecord/test/migrations/10_urban/9_add_expressions.rb
new file mode 100644
index 0000000000..79a342e574
--- /dev/null
+++ b/activerecord/test/migrations/10_urban/9_add_expressions.rb
@@ -0,0 +1,11 @@
+class AddExpressions < ActiveRecord::Migration
+ def self.up
+ create_table("expressions") do |t|
+ t.column :expression, :string
+ end
+ end
+
+ def self.down
+ drop_table "expressions"
+ end
+end
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index 888afc7604..ab3caa3f79 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -1,6 +1,6 @@
class Bulb < ActiveRecord::Base
default_scope where(:name => 'defaulty')
- belongs_to :car
+ belongs_to :car, :touch => true
attr_protected :car_id, :frickinawesome
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 4dc9fff9fd..f8b758b3c2 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -63,6 +63,15 @@ class Developer < ActiveRecord::Base
self.all
end
end
+
+ after_find :track_instance_count
+ cattr_accessor :instance_count
+
+ def track_instance_count
+ self.class.instance_count ||= 0
+ self.class.instance_count += 1
+ end
+ private :track_instance_count
end
class AuditLog < ActiveRecord::Base
@@ -236,3 +245,8 @@ class ThreadsafeDeveloper < ActiveRecord::Base
limit(1)
end
end
+
+class CachedDeveloper < ActiveRecord::Base
+ self.table_name = "developers"
+ self.cache_timestamp_format = :nsec
+end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 5b92227f4a..07c529d685 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -86,3 +86,30 @@ class TightPerson < ActiveRecord::Base
end
class TightDescendant < TightPerson; end
+
+class RichPerson < ActiveRecord::Base
+ self.table_name = 'people'
+
+ has_and_belongs_to_many :treasures, :join_table => 'peoples_treasures'
+end
+
+class Insure
+ INSURES = %W{life annuality}
+
+ def self.load mask
+ INSURES.select do |insure|
+ (1 << INSURES.index(insure)) & mask.to_i > 0
+ end
+ end
+
+ def self.dump insures
+ numbers = insures.map { |insure| INSURES.index(insure) }
+ numbers.inject(0) { |sum, n| sum + (1 << n) }
+ end
+end
+
+class SerializedPerson < ActiveRecord::Base
+ self.table_name = 'people'
+
+ serialize :insures, Insure
+end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 1a1a18166a..5166fefe81 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -112,6 +112,12 @@ class ImportantTopic < Topic
serialize :important, Hash
end
+class BlankTopic < Topic
+ def blank?
+ true
+ end
+end
+
module Web
class Topic < ActiveRecord::Base
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply'
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 34e24b5a2e..b2c655ddcd 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -132,5 +132,10 @@ _SQL
_SQL
rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table
end
+
+ create_table :limitless_fields, :force => true do |t|
+ t.binary :binary, :limit => 100_000
+ t.text :text, :limit => 100_000
+ end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 8a3dfbb35a..1a993fef11 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -112,6 +112,7 @@ ActiveRecord::Schema.define do
t.string :name
t.integer :engines_count
t.integer :wheels_count
+ t.timestamps
end
create_table :categories, :force => true do |t|
@@ -478,9 +479,15 @@ ActiveRecord::Schema.define do
t.integer :followers_count, :default => 0
t.references :best_friend
t.references :best_friend_of
+ t.integer :insures, :null => false, :default => 0
t.timestamps
end
+ create_table :peoples_treasures, :id => false, :force => true do |t|
+ t.column :rich_person_id, :integer
+ t.column :treasure_id, :integer
+ end
+
create_table :pets, :primary_key => :pet_id ,:force => true do |t|
t.string :name
t.integer :owner_id, :integer
diff --git a/activeresource/CHANGELOG.md b/activeresource/CHANGELOG.md
index 8d251bdce2..176ffb4723 100644
--- a/activeresource/CHANGELOG.md
+++ b/activeresource/CHANGELOG.md
@@ -1,21 +1,30 @@
-## Rails 3.2.10 ##
+## Rails 3.2.11 ##
+
+## Rails 3.2.10 (Jan 2, 2013) ##
+
+* No changes.
+
## Rails 3.2.9 (Nov 12, 2012) ##
* No changes.
+
## Rails 3.2.8 (Aug 9, 2012) ##
* No changes.
+
## Rails 3.2.7 (Jul 26, 2012) ##
* No changes.
+
## Rails 3.2.6 (Jun 12, 2012) ##
* No changes.
+
## Rails 3.2.5 (Jun 1, 2012) ##
* No changes.
diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb
index 7b42f64a35..5ef8a51ef7 100644
--- a/activeresource/test/cases/base_test.rb
+++ b/activeresource/test/cases/base_test.rb
@@ -10,7 +10,7 @@ require "fixtures/subscription_plan"
require 'active_support/json'
require 'active_support/ordered_hash'
require 'active_support/core_ext/hash/conversions'
-require 'mocha'
+require 'mocha/setup'
class BaseTest < Test::Unit::TestCase
def setup
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 7faf55bf52..2278c04f0d 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,30 @@
+## Rails 3.2.11 (unreleased)
+
+* Remove surrogate unicode character encoding from ActiveSupport::JSON.encode
+ The encoding scheme was broken for unicode characters outside the basic
+ multilingual plane; since json is assumed to be UTF-8, and we already force the
+ encoding to UTF-8 simply pass through the un-encoded characters.
+
+ *Brett Carter*
+
+* Fix mocha v0.13.0 compatibility. *James Mead*
+
+* `#as_json` isolates options when encoding a hash. [Backport #8185]
+ Fix #8182
+
+ *Yves Senn*
+
+* Handle the possible Permission Denied errors atomic.rb might trigger due to
+ its chown and chmod calls. [Backport #8027]
+
+ *Daniele Sluijters*
+
+
+## Rails 3.2.10 (Jan 2, 2013) ##
+
+* No changes.
+
+
## Rails 3.2.9 (Nov 12, 2012) ##
* Add logger.push_tags and .pop_tags to complement logger.tagged:
@@ -16,6 +43,7 @@
* Add %:z and %::z format string support to ActiveSupport::TimeWithZone#strftime. [fixes #6962] *kennyj*
+
## Rails 3.2.8 (Aug 9, 2012) ##
* Fix ActiveSupport integration with Mocha > 0.12.1. *Mike Gunderloy*
@@ -24,6 +52,7 @@
* ERB::Util.html_escape now escapes single quotes. *Santiago Pastorino*
+
## Rails 3.2.7 (Jul 26, 2012) ##
* Hash#fetch(fetch) is not the same as doing hash[key]
@@ -36,10 +65,12 @@
* bump AS deprecation_horizon to 4.0
+
## Rails 3.2.6 (Jun 12, 2012) ##
* No changes.
+
## Rails 3.2.5 (Jun 1, 2012) ##
* ActiveSupport::JSON::Variable is deprecated. Define your own #as_json and #encode_json methods
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index ae1d0633ce..677f7c0bb0 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -6,7 +6,9 @@ module ActiveSupport
# module M
# def self.included(base)
# base.extend ClassMethods
- # scope :disabled, where(:disabled => true)
+ # base.class_eval do
+ # scope :disabled, where(:disabled => true)
+ # end
# end
#
# module ClassMethods
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index fc3277f4d2..b0daf6db9e 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -36,7 +36,12 @@ class File
FileUtils.mv(temp_file.path, file_name)
# Set correct permissions on new file
- chown(old_stat.uid, old_stat.gid, file_name)
- chmod(old_stat.mode, file_name)
+ begin
+ chown(old_stat.uid, old_stat.gid, file_name)
+ # This operation will affect filesystem ACL's
+ chmod(old_stat.mode, file_name)
+ rescue Errno::EPERM
+ # Changing file ownership failed, moving on.
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index 49ac18d245..d180e1e588 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -13,6 +13,8 @@ class Time
:rfc822 => lambda { |time| time.strftime("%a, %d %b %Y %H:%M:%S #{time.formatted_offset(false)}") }
}
+ DATE_FORMATS[:nsec] = '%Y%m%d%H%M%S%9N' if RUBY_VERSION >= '1.9'
+
# Converts to a formatted string. See DATE_FORMATS for builtin formats.
#
# This method is aliased to <tt>to_s</tt>.
diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb
index 457d3f5b62..ce5948d126 100644
--- a/activesupport/lib/active_support/core_ext/time/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/time/marshal.rb
@@ -20,7 +20,7 @@ if !Marshal.load(Marshal.dump(Time.now.utc)).utc?
def _dump(*args)
obj = dup
obj.instance_variable_set('@marshal_with_utc_coercion', utc?)
- obj._dump_without_utc_flag(*args)
+ obj.send :_dump_without_utc_flag, *args
end
end
end
@@ -51,7 +51,7 @@ if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone
def _dump(*args)
obj = dup
obj.instance_variable_set('@_zone', zone)
- obj._dump_without_zone(*args)
+ obj.send :_dump_without_zone, *args
end
end
end
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 91e4f07c6c..a50e6524c6 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -61,7 +61,7 @@ module ActiveSupport
# hashes and arrays need to get encoder in the options, so that they can detect circular references
options.merge(:encoder => self)
else
- options
+ options.dup
end
end
@@ -122,13 +122,7 @@ module ActiveSupport
if string.respond_to?(:force_encoding)
string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY)
end
- json = string.
- gsub(escape_regex) { |s| ESCAPED_CHARS[s] }.
- gsub(/([\xC0-\xDF][\x80-\xBF]|
- [\xE0-\xEF][\x80-\xBF]{2}|
- [\xF0-\xF7][\x80-\xBF]{3})+/nx) { |s|
- s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/n, '\\\\u\&')
- }
+ json = string.gsub(escape_regex) { |s| ESCAPED_CHARS[s] }
json = %("#{json}")
json.force_encoding(::Encoding::UTF_8) if json.respond_to?(:force_encoding)
json
diff --git a/activesupport/lib/active_support/testing/mochaing.rb b/activesupport/lib/active_support/testing/mochaing.rb
index 4ad75a6681..ae4e7e9a1f 100644
--- a/activesupport/lib/active_support/testing/mochaing.rb
+++ b/activesupport/lib/active_support/testing/mochaing.rb
@@ -1,7 +1,7 @@
begin
- silence_warnings { require 'mocha' }
+ silence_warnings { require 'mocha/setup' }
rescue LoadError
# Fake Mocha::ExpectationError so we can rescue it in #run. Bleh.
Object.const_set :Mocha, Module.new
Mocha.const_set :ExpectationError, Class.new(StandardError)
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb
index f2268d1767..e5353f65cf 100644
--- a/activesupport/lib/active_support/testing/setup_and_teardown.rb
+++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb
@@ -61,7 +61,7 @@ module ActiveSupport
def run(result)
return if @method_name.to_s == "default_test"
- mocha_counter = retrieve_mocha_counter(result)
+ mocha_counter = retrieve_mocha_counter(self, result)
yield(Test::Unit::TestCase::STARTED, name)
@_result = result
@@ -83,6 +83,8 @@ module ActiveSupport
begin
teardown
run_callbacks :teardown
+ rescue Mocha::ExpectationError => e
+ add_failure(e.message, e.backtrace)
rescue Test::Unit::AssertionFailedError => e
add_failure(e.message, e.backtrace)
rescue Exception => e
@@ -100,19 +102,20 @@ module ActiveSupport
protected
- def retrieve_mocha_counter(result) #:nodoc:
+ def retrieve_mocha_counter(test_case, result) #:nodoc:
if respond_to?(:mocha_verify) # using mocha
if defined?(Mocha::TestCaseAdapter::AssertionCounter)
Mocha::TestCaseAdapter::AssertionCounter.new(result)
elsif defined?(Mocha::Integration::TestUnit::AssertionCounter)
Mocha::Integration::TestUnit::AssertionCounter.new(result)
- else
+ elsif defined?(Mocha::MonkeyPatching::TestUnit::AssertionCounter)
Mocha::MonkeyPatching::TestUnit::AssertionCounter.new(result)
+ else
+ Mocha::Integration::AssertionCounter.new(test_case)
end
end
end
end
-
end
end
end
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 0382739871..c26a48a490 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -25,7 +25,7 @@ end
require 'test/unit'
require 'empty_bool'
-silence_warnings { require 'mocha' }
+silence_warnings { require 'mocha/setup' }
ENV['NO_RELOAD'] = '1'
require 'active_support'
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index c030611de3..7a818411d0 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -538,14 +538,16 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_to_s
- time = Time.utc(2005, 2, 21, 17, 44, 30)
- assert_equal time.to_default_s, time.to_s
- assert_equal time.to_default_s, time.to_s(:doesnt_exist)
- assert_equal "2005-02-21 17:44:30", time.to_s(:db)
- assert_equal "21 Feb 17:44", time.to_s(:short)
- assert_equal "17:44", time.to_s(:time)
- assert_equal "February 21, 2005 17:44", time.to_s(:long)
- assert_equal "February 21st, 2005 17:44", time.to_s(:long_ordinal)
+ time = Time.utc(2005, 2, 21, 17, 44, 30.12345678901)
+ assert_equal time.to_default_s, time.to_s
+ assert_equal time.to_default_s, time.to_s(:doesnt_exist)
+ assert_equal "2005-02-21 17:44:30", time.to_s(:db)
+ assert_equal "21 Feb 17:44", time.to_s(:short)
+ assert_equal "17:44", time.to_s(:time)
+ assert_equal "20050221174430", time.to_s(:number)
+ assert_equal "20050221174430123456789", time.to_s(:nsec) if RUBY_VERSION >= '1.9'
+ assert_equal "February 21, 2005 17:44", time.to_s(:long)
+ assert_equal "February 21st, 2005 17:44", time.to_s(:long_ordinal)
with_env_tz "UTC" do
assert_equal "Mon, 21 Feb 2005 17:44:30 +0000", time.to_s(:rfc822)
end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index a9590f164f..e4e13c3f25 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -22,6 +22,15 @@ class TestJSONEncoding < Test::Unit::TestCase
end
end
+ class CustomWithOptions
+ attr_accessor :foo, :bar
+
+ def as_json(options={})
+ options[:only] = %w(foo bar)
+ super(options)
+ end
+ end
+
TrueTests = [[ true, %(true) ]]
FalseTests = [[ false, %(false) ]]
NilTests = [[ nil, %(null) ]]
@@ -91,11 +100,11 @@ class TestJSONEncoding < Test::Unit::TestCase
def test_utf8_string_encoded_properly_when_kcode_is_utf8
with_kcode 'UTF8' do
result = ActiveSupport::JSON.encode('€2.99')
- assert_equal '"\\u20ac2.99"', result
+ assert_equal '"€2.99"', result
assert_equal(Encoding::UTF_8, result.encoding) if result.respond_to?(:encoding)
result = ActiveSupport::JSON.encode('✎☺')
- assert_equal '"\\u270e\\u263a"', result
+ assert_equal '"✎☺"', result
assert_equal(Encoding::UTF_8, result.encoding) if result.respond_to?(:encoding)
end
end
@@ -104,11 +113,24 @@ class TestJSONEncoding < Test::Unit::TestCase
def test_non_utf8_string_transcodes
s = '二'.encode('Shift_JIS')
result = ActiveSupport::JSON.encode(s)
- assert_equal '"\\u4e8c"', result
+ assert_equal '"二"', result
assert_equal Encoding::UTF_8, result.encoding
end
end
+ def test_wide_utf8_chars
+ w = '𠜎'
+ result = ActiveSupport::JSON.encode(w)
+ assert_equal '"𠜎"', result
+ end
+
+ def test_wide_utf8_roundtrip
+ hash = { :string => "𐒑" }
+ json = ActiveSupport::JSON.encode(hash)
+ decoded_hash = ActiveSupport::JSON.decode(json)
+ assert_equal "𐒑", decoded_hash['string']
+ end
+
def test_exception_raised_when_encoding_circular_reference_in_array
a = [1]
a << a
@@ -239,6 +261,16 @@ class TestJSONEncoding < Test::Unit::TestCase
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
+ def test_to_json_should_not_keep_options_around
+ f = CustomWithOptions.new
+ f.foo = "hello"
+ f.bar = "world"
+
+ hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}}
+ assert_equal({ "foo" => { "foo" => "hello", "bar" => "world" }, "other_hash" => { "foo" => "other_foo", "test" => "other_test"} },
+ JSON.parse(hash.to_json))
+ end
+
def test_struct_encoding
Struct.new('UserNameAndEmail', :name, :email)
Struct.new('UserNameAndDate', :name, :date)
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index adca0839d9..0ddc7ccca7 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,7 +1,30 @@
-## Rails 3.2.10 ##
+## Rails 3.2.11 (unreleased) ##
+
+
+## Rails 3.2.10 (Jan 2, 2013) ##
+
+* No changes.
+
## Rails 3.2.9 (Nov 12, 2012) ##
+* Quote column names in generates fixture files. This prevents
+ conflicts with reserved YAML keywords such as 'yes' and 'no'
+ Fix #8612.
+ Backport #8616.
+
+ *Yves Senn*
+
+* Engines with a dummy app include the rake tasks of dependencies in the app namespace. [Backport: #8262]
+ Fix #8229
+
+ *Yves Senn*
+
+* Add dummy app Rake tasks when --skip-test-unit and --dummy-path is passed to the plugin generator. [Backport #8139]
+ Fix #8121
+
+ *Yves Senn*
+
* Update supported ruby versions error message in ruby_version_check.rb *Lihan Li*
## Rails 3.2.8 (Aug 9, 2012) ##
diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index 15d24f9ac1..d2a445b508 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -901,8 +901,12 @@ Below is a simple example where we change the Rails behavior to always display t
<ruby>
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
- errors = Array(instance.error_message).join(',')
- %(#{html_tag}<span class="validation-error">&nbsp;#{errors}</span>).html_safe
+ if html_tag =~ /\<label/
+ html_tag
+ else
+ errors = Array(instance.error_message).join(',')
+ %(#{html_tag}<span class="validation-error">&nbsp;#{errors}</span>).html_safe
+ end
end
</ruby>
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index fbccff5005..83e35214a5 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -3615,7 +3615,9 @@ File.atomic_write(joined_asset_path) do |cache|
end
</ruby>
-To accomplish this +atomic_write+ creates a temporary file. That's the file the code in the block actually writes to. On completion, the temporary file is renamed, which is an atomic operation on POSIX systems. If the target file exists +atomic_write+ overwrites it and keeps owners and permissions.
+To accomplish this `atomic_write` creates a temporary file. That's the file the code in the block actually writes to. On completion, the temporary file is renamed, which is an atomic operation on POSIX systems. If the target file exists `atomic_write` overwrites it and keeps owners and permissions. However there are a few cases where `atomic_write` cannot change the file ownership or permissions, this error is caught and skipped over trusting in the user/filesystem to ensure the file is accessible to the processes that need it.
+
+NOTE. Due to the chmod operation `atomic_write` performs, if the target file has an ACL set on it this ACL will be recalculated/modified.
WARNING. Note you can't append with +atomic_write+.
diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile
deleted file mode 100644
index 3a0ccfe9b2..0000000000
--- a/railties/guides/source/ajax_on_rails.textile
+++ /dev/null
@@ -1,267 +0,0 @@
-h2. AJAX on Rails
-
-This guide covers the built-in Ajax/JavaScript functionality of Rails (and more); it will enable you to create rich and dynamic AJAX applications with ease! We will cover the following topics:
-
-* Quick introduction to AJAX and related technologies
-* Unobtrusive JavaScript helpers with drivers for Prototype, jQuery etc
-* Testing JavaScript functionality
-
-endprologue.
-
-h3. Hello AJAX - a Quick Intro
-
-You'll need the basics of DOM, HTTP requests and other topics discussed here to really understand Ajax on Rails.
-
-h4. Asynchronous JavaScript + XML
-
-Basic terminology, new style of creating web apps
-
-h4. The DOM
-
-basics of the DOM, how is it built, properties, features, why is it central to AJAX
-
-h4. Standard HTML communication vs AJAX
-
-How do 'standard' and AJAX requests differ, why does this matter for understanding AJAX on Rails (tie in for *_remote helpers, the next section)
-
-h3. Built-in Rails Helpers
-
-Rails 3.1 ships with "jQuery":http://jquery.com as the default JavaScript library. The Gemfile contains <tt>gem 'jquery-rails'</tt> which makes the jQuery files available to the application automatically. This can be accessed as:
-
-<ruby>
-javascript_include_tag :defaults
-</ruby>
-
-h4. Examples
-
-All the remote_method helpers has been removed. To make them working with AJAX, simply pass the <tt>:remote => true</tt> option to the original non-remote method.
-
-<ruby>
-button_to "New", :action => "new", :form_class => "new-thing"
-</ruby>
-
-will produce
-
-<html>
-<form method="post" action="/controller/new" class="new-thing">
- <div><input value="New" type="submit" /></div>
-</form>
-</html>
-
-<ruby>
-button_to "Create", :action => "create", :remote => true, :form => { "data-type" => "json" }
-</ruby>
-
-will produce
-
-<html>
-<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
- <div><input value="Create" type="submit" /></div>
-</form>
-</html>
-
-<ruby>
-button_to "Delete Image", { :action => "delete", :id => @image.id },
- :confirm => "Are you sure?", :method => :delete
-</ruby>
-
-will produce
-
-<html>
-<form method="post" action="/images/delete/1" class="button_to">
- <div>
- <input type="hidden" name="_method" value="delete" />
- <input data-confirm='Are you sure?' value="Delete" type="submit" />
- </div>
-</form>
-</html>
-
-<ruby>
-button_to('Destroy', 'http://www.example.com', :confirm => 'Are you sure?',
- :method => "delete", :remote => true, :disable_with => 'loading...')
-</ruby>
-
-will produce
-
-<html>
-<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
- <div>
- <input name='_method' value='delete' type='hidden' />
- <input value='Destroy' type='submit' disable_with='loading...' data-confirm='Are you sure?' />
- </div>
-</form>
-</html>
-
-You can also choose to use Prototype instead of jQuery and specify the option using +-j+ switch while generating the application.
-
-<shell>
-rails new app_name -j prototype
-</shell>
-
-You are ready to add some AJAX love to your Rails app!
-
-h4. The Quintessential AJAX Rails Helper: link_to_remote
-
-Let's start with what is probably the most often used helper: +link_to_remote+. It has an interesting feature from the documentation point of view: the options supplied to +link_to_remote+ are shared by all other AJAX helpers, so learning the mechanics and options of +link_to_remote+ is a great help when using other helpers.
-
-The signature of +link_to_remote+ function is the same as that of the standard +link_to+ helper:
-
-<ruby>
-def link_to_remote(name, options = {}, html_options = nil)
-</ruby>
-
-And here is a simple example of link_to_remote in action:
-
-<ruby>
-link_to_remote "Add to cart",
- :url => add_to_cart_url(product.id),
- :update => "cart"
-</ruby>
-
-* The very first parameter, a string, is the text of the link which appears on the page.
-* The second parameter, the +options+ hash is the most interesting part as it has the AJAX specific stuff:
-** *:url* This is the only parameter that is always required to generate the simplest remote link (technically speaking, it is not required, you can pass an empty +options+ hash to +link_to_remote+ - but in this case the URL used for the POST request will be equal to your current URL which is probably not your intention). This URL points to your AJAX action handler. The URL is typically specified by Rails REST view helpers, but you can use the +url_for+ format too.
-** *:update* Specifying a DOM id of the element we would like to update. The above example demonstrates the simplest way of accomplishing this - however, we are in trouble if the server responds with an error message because that will be injected into the page too! However, Rails has a solution for this situation:
-
-<ruby>
-link_to_remote "Add to cart",
- :url => add_to_cart_url(product),
- :update => { :success => "cart", :failure => "error" }
-</ruby>
-
-If the server returns 200, the output of the above example is equivalent to our first, simple one. However, in case of error, the element with the DOM id +error+ is updated rather than the +cart+ element.
-
-** *position* By default (i.e. when not specifying this option, like in the examples before) the response is injected into the element with the specified DOM id, replacing the original content of the element (if there was any). You might want to alter this behavior by keeping the original content - the only question is where to place the new content? This can specified by the +position+ parameter, with four possibilities:
-*** +:before+ Inserts the response text just before the target element. More precisely, it creates a text node from the response and inserts it as the left sibling of the target element.
-*** +:after+ Similar behavior to +:before+, but in this case the response is inserted after the target element.
-*** +:top+ Inserts the text into the target element, before it's original content. If the target element was empty, this is equivalent with not specifying +:position+ at all.
-*** +:bottom+ The counterpart of +:top+: the response is inserted after the target element's original content.
-
-A typical example of using +:bottom+ is inserting a new &lt;li&gt; element into an existing list:
-
-<ruby>
-link_to_remote "Add new item",
- :url => items_url,
- :update => 'item_list',
- :position => :bottom
-</ruby>
-
-** *:method* Most typically you want to use a POST request when adding a remote link to your view so this is the default behavior. However, sometimes you'll want to update (PUT) or delete/destroy (DELETE) something and you can specify this with the +:method+ option. Let's see an example for a typical AJAX link for deleting an item from a list:
-
-<ruby>
-link_to_remote "Delete the item",
- :url => item_url(item),
- :method => :delete
-</ruby>
-
-Note that if we wouldn't override the default behavior (POST), the above snippet would route to the create action rather than destroy.
-
-** *JavaScript filters* You can customize the remote call further by wrapping it with some JavaScript code. Let's say in the previous example, when deleting a link, you'd like to ask for a confirmation by showing a simple modal text box to the user. This is a typical example what you can accomplish with these options - let's see them one by one:
-*** +:confirm+ =&gt; +msg+ Pops up a JavaScript confirmation dialog, displaying +msg+. If the user chooses 'OK', the request is launched, otherwise canceled.
-*** +:condition+ =&gt; +code+ Evaluates +code+ (which should evaluate to a boolean) and proceeds if it's true, cancels the request otherwise.
-*** +:before+ =&gt; +code+ Evaluates the +code+ just before launching the request. The output of the code has no influence on the execution. Typically used show a progress indicator (see this in action in the next example).
-*** +:after+ =&gt; +code+ Evaluates the +code+ after launching the request. Note that this is different from the +:success+ or +:complete+ callback (covered in the next section) since those are triggered after the request is completed, while the code snippet passed to +:after+ is evaluated after the remote call is made. A common example is to disable elements on the page or otherwise prevent further action while the request is completed.
-*** +:submit+ =&gt; +dom_id+ This option does not make sense for +link_to_remote+, but we'll cover it for the sake of completeness. By default, the parent element of the form elements the user is going to submit is the current form - use this option if you want to change the default behavior. By specifying this option you can change the parent element to the element specified by the DOM id +dom_id+.
-*** +:with+ &gt; +code+ The JavaScript code snippet in +code+ is evaluated and added to the request URL as a parameter (or set of parameters). Therefore, +code+ should return a valid URL query string (like "item_type=8" or "item_type=8&sort=true"). Usually you want to obtain some value(s) from the page - let's see an example:
-
-<ruby>
-link_to_remote "Update record",
- :url => record_url(record),
- :method => :put,
- :with => "'status=' <plus> 'encodeURIComponent($('status').value) <plus> '&completed=' <plus> $('completed')"
-</ruby>
-
-This generates a remote link which adds 2 parameters to the standard URL generated by Rails, taken from the page (contained in the elements matched by the 'status' and 'completed' DOM id).
-
-** *Callbacks* Since an AJAX call is typically asynchronous, as it's name suggests (this is not a rule, and you can fire a synchronous request - see the last option, +:type+) your only way of communicating with a request once it is fired is via specifying callbacks. There are six options at your disposal (in fact 508, counting all possible response types, but these six are the most frequent and therefore specified by a constant):
-*** +:loading:+ =&gt; +code+ The request is in the process of receiving the data, but the transfer is not completed yet.
-*** +:loaded:+ =&gt; +code+ The transfer is completed, but the data is not processed and returned yet
-*** +:interactive:+ =&gt; +code+ One step after +:loaded+: The data is fully received and being processed
-*** +:success:+ =&gt; +code+ The data is fully received, parsed and the server responded with "200 OK"
-*** +:failure:+ =&gt; +code+ The data is fully received, parsed and the server responded with *anything* but "200 OK" (typically 404 or 500, but in general with any status code ranging from 100 to 509)
-*** +:complete:+ =&gt; +code+ The combination of the previous two: The request has finished receiving and parsing the data, and returned a status code (which can be anything).
-*** Any other status code ranging from 100 to 509: Additionally you might want to check for other HTTP status codes, such as 404. In this case simply use the status code as a number:
-<ruby>
-link_to_remote "Add new item",
- :url => items_url,
- :update => "item_list",
- 404 => "alert('Item not found!')"
-</ruby>
-Let's see a typical example for the most frequent callbacks, +:success+, +:failure+ and +:complete+ in action:
-
-<ruby>
-link_to_remote "Add new item",
- :url => items_url,
- :update => "item_list",
- :before => "$('progress').show()",
- :complete => "$('progress').hide()",
- :success => "display_item_added(request)",
- :failure => "display_error(request)"
-</ruby>
-
-** *:type* If you want to fire a synchronous request for some obscure reason (blocking the browser while the request is processed and doesn't return a status code), you can use the +:type+ option with the value of +:synchronous+.
-* Finally, using the +html_options+ parameter you can add HTML attributes to the generated tag. It works like the same parameter of the +link_to+ helper. There are interesting side effects for the +href+ and +onclick+ parameters though:
-** If you specify the +href+ parameter, the AJAX link will degrade gracefully, i.e. the link will point to the URL even if JavaScript is disabled in the client browser
-** +link_to_remote+ gains it's AJAX behavior by specifying the remote call in the onclick handler of the link. If you supply +html_options[:onclick]+ you override the default behavior, so use this with care!
-
-We are finished with +link_to_remote+. I know this is quite a lot to digest for one helper function, but remember, these options are common for all the rest of the Rails view helpers, so we will take a look at the differences / additional parameters in the next sections.
-
-h4. AJAX Forms
-
-There are three different ways of adding AJAX forms to your view using Rails Prototype helpers. They are slightly different, but striving for the same goal: instead of submitting the form using the standard HTTP request/response cycle, it is submitted asynchronously, thus not reloading the page. These methods are the following:
-
-* +remote_form_for+ (and it's alias +form_remote_for+) is tied to Rails most tightly of the three since it takes a resource, model or array of resources (in case of a nested resource) as a parameter.
-* +form_remote_tag+ AJAXifies the form by serializing and sending it's data in the background
-* +submit_to_remote+ and +button_to_remote+ is more rarely used than the previous two. Rather than creating an AJAX form, you add a button/input
-
-Let's see them in action one by one!
-
-h5. +remote_form_for+
-
-h5. +form_remote_tag+
-
-h5. +submit_to_remote+
-
-h4. Observing Elements
-
-h5. +observe_field+
-
-h5. +observe_form+
-
-h4. Calling a Function Periodically
-
-h5. +periodically_call_remote+
-
-
-h4. Miscellaneous Functionality
-
-h5. +remote_function+
-
-h5. +update_page+
-
-h4. Serving JavaScript
-
-First we'll check out how to send JavaScript to the server manually. You are practically never going to need this, but it's interesting to understand what's going on under the hood.
-
-<ruby>
-def javascript_test
- render :text => "alert('Hello, world!')",
- :content_type => "text/javascript"
-end
-</ruby>
-
-(Note: if you want to test the above method, create a +link_to_remote+ with a single parameter - +:url+, pointing to the +javascript_test+ action)
-
-What happens here is that by specifying the Content-Type header variable, we instruct the browser to evaluate the text we are sending over (rather than displaying it as plain text, which is the default behavior).
-
-h3. Testing JavaScript
-
-JavaScript testing reminds me the definition of the world 'classic' by Mark Twain: "A classic is something that everybody wants to have read and nobody wants to read." It's similar with JavaScript testing: everyone would like to have it, yet it's not done by too much developers as it is tedious, complicated, there is a proliferation of tools and no consensus/accepted best practices, but we will nevertheless take a stab at it:
-
-* (Fire)Watir
-* Selenium
-* Celerity/Culerity
-* Cucumber+Webrat
-* Mention stuff like screw.unit/jsSpec
-
-Note to self: check out the RailsConf JS testing video
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index 3466e1c3f4..f5b39dd4ac 100644
--- a/railties/guides/source/configuring.textile
+++ b/railties/guides/source/configuring.textile
@@ -277,6 +277,8 @@ h4. Configuring Active Record
* +config.active_record.auto_explain_threshold_in_seconds+ configures the threshold for automatic EXPLAINs (+nil+ disables this feature). Queries exceeding the threshold get their query plan logged. Default is 0.5 in development mode.
+* +config.active_record.cache_timestamp_format+ controls the format of the timestamp value in the cache key. Default is +:number+.
+
The MySQL adapter adds one additional configuration option:
* +ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans+ controls whether Active Record will consider all +tinyint(1)+ columns in a MySQL database to be booleans and is true by default.
diff --git a/railties/guides/source/performance_testing.textile b/railties/guides/source/performance_testing.textile
index 958b13cd9e..0cf7d4a4fb 100644
--- a/railties/guides/source/performance_testing.textile
+++ b/railties/guides/source/performance_testing.textile
@@ -447,7 +447,7 @@ h4. Using Ruby-Prof on MRI and REE
Add Ruby-Prof to your applications' Gemfile if you want to benchmark/profile under MRI or REE:
<ruby>
-gem 'ruby-prof', :git => 'git://github.com/wycats/ruby-prof.git'
+gem 'ruby-prof'
</ruby>
Now run +bundle install+ and you're ready to go.
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index cd7d51e628..6c53d8bebb 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -204,7 +204,7 @@ task :default => :test
end
def create_test_dummy_files
- return if options[:skip_test_unit] && options[:dummy_path] == 'test/dummy'
+ return unless with_dummy_app?
create_dummy_app
end
@@ -242,6 +242,10 @@ task :default => :test
options[:mountable]
end
+ def with_dummy_app?
+ options[:skip_test_unit].blank? || options[:dummy_path] != 'test/dummy'
+ end
+
def self.banner
"rails plugin new #{self.arguments.map(&:usage).join(' ')} [options]"
end
@@ -282,7 +286,7 @@ task :default => :test
dummy_application_path = File.expand_path("#{dummy_path}/config/application.rb", destination_root)
unless options[:pretend] || !File.exists?(dummy_application_path)
contents = File.read(dummy_application_path)
- contents[(contents.index("module Dummy"))..-1]
+ contents[(contents.index(/module ([\w]+)\n(.*)class Application/m))..-1]
end
end
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
index 564fda3c49..9b3b8cc03f 100755
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
@@ -20,7 +20,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_files.include('lib/**/*.rb')
end
-<% if full? && !options[:skip_active_record] && !options[:skip_test_unit] -%>
+<% if full? && !options[:skip_active_record] && with_dummy_app? -%>
APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__)
load 'rails/tasks/engine.rake'
<% end %>
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb
index 996ea79e67..6cb7e38b56 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb
@@ -12,7 +12,7 @@ require "active_resource/railtie"
<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
<% end -%>
-Bundler.require
+Bundler.require(*Rails.groups)
require "<%= name %>"
<%= application_definition %>
diff --git a/railties/lib/rails/generators/test_unit/model/model_generator.rb b/railties/lib/rails/generators/test_unit/model/model_generator.rb
index c1dd535dd3..9749a6b133 100644
--- a/railties/lib/rails/generators/test_unit/model/model_generator.rb
+++ b/railties/lib/rails/generators/test_unit/model/model_generator.rb
@@ -3,6 +3,9 @@ require 'rails/generators/test_unit'
module TestUnit
module Generators
class ModelGenerator < Base
+
+ RESERVED_YAML_KEYWORDS = %w(y yes n no true false on off null)
+
argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
class_option :fixture, :type => :boolean
@@ -19,6 +22,15 @@ module TestUnit
template 'fixtures.yml', File.join('test/fixtures', class_path, "#{plural_file_name}.yml")
end
end
+
+ private
+ def yaml_key_value(key, value)
+ if RESERVED_YAML_KEYWORDS.include?(key.downcase)
+ "'#{key}': #{value}"
+ else
+ "#{key}: #{value}"
+ end
+ end
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
index 5c8780aa64..2c33766418 100644
--- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
+++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
@@ -3,12 +3,12 @@
<% unless attributes.empty? -%>
one:
<% attributes.each do |attribute| -%>
- <%= attribute.name %>: <%= attribute.default %>
+ <%= yaml_key_value(attribute.name, attribute.default) %>
<% end -%>
two:
<% attributes.each do |attribute| -%>
- <%= attribute.name %>: <%= attribute.default %>
+ <%= yaml_key_value(attribute.name, attribute.default) %>
<% end -%>
<% else -%>
# This model initially had no columns defined. If you add columns to the
diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb
index c13f9a49e7..984d8374fb 100644
--- a/railties/test/application/rack/logger_test.rb
+++ b/railties/test/application/rack/logger_test.rb
@@ -1,6 +1,7 @@
require "isolation/abstract_unit"
require "active_support/log_subscriber/test_helper"
require "rack/test"
+require "mocha/setup"
module ApplicationTests
module RackTests
diff --git a/railties/test/application/route_inspect_test.rb b/railties/test/application/route_inspect_test.rb
index b897cf15b8..5c920cb33a 100644
--- a/railties/test/application/route_inspect_test.rb
+++ b/railties/test/application/route_inspect_test.rb
@@ -1,5 +1,5 @@
require 'test/unit'
-require 'mocha'
+require 'mocha/setup'
require 'rails/application/route_inspector'
require 'action_controller'
require 'rails/engine'
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index a05e39658d..e50d744e26 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -229,6 +229,77 @@ module ApplicationTests
end
end
+ test 'routes are added and removed when reloading' do
+ app('development')
+
+ controller :foo, <<-RUBY
+ class FooController < ApplicationController
+ def index
+ render :text => "foo"
+ end
+ end
+ RUBY
+
+ controller :bar, <<-RUBY
+ class BarController < ApplicationController
+ def index
+ render :text => "bar"
+ end
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get 'foo', :to => 'foo#index'
+ end
+ RUBY
+
+ get '/foo'
+ assert_equal 'foo', last_response.body
+ assert_equal '/foo', Rails.application.routes.url_helpers.foo_path
+
+ get '/bar'
+ assert_equal 404, last_response.status
+ assert_raises NoMethodError do
+ assert_equal '/bar', Rails.application.routes.url_helpers.bar_path
+ end
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get 'foo', :to => 'foo#index'
+ get 'bar', :to => 'bar#index'
+ end
+ RUBY
+
+ Rails.application.reload_routes!
+
+ get '/foo'
+ assert_equal 'foo', last_response.body
+ assert_equal '/foo', Rails.application.routes.url_helpers.foo_path
+
+ get '/bar'
+ assert_equal 'bar', last_response.body
+ assert_equal '/bar', Rails.application.routes.url_helpers.bar_path
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get 'foo', :to => 'foo#index'
+ end
+ RUBY
+
+ Rails.application.reload_routes!
+
+ get '/foo'
+ assert_equal 'foo', last_response.body
+ assert_equal '/foo', Rails.application.routes.url_helpers.foo_path
+
+ get '/bar'
+ assert_equal 404, last_response.status
+ assert_raises NoMethodError do
+ assert_equal '/bar', Rails.application.routes.url_helpers.bar_path
+ end
+ end
+
test 'resource routing with irregular inflection' do
app_file 'config/initializers/inflection.rb', <<-RUBY
ActiveSupport::Inflector.inflections do |inflect|
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index f64abc1016..b96c591450 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -157,7 +157,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_match(/create_table :products/, up)
assert_match(/t\.string :name/, up)
assert_match(/t\.integer :supplier_id/, up)
-
+
assert_match(/add_index :products, :name/, up)
assert_match(/add_index :products, :supplier_id/, up)
assert_no_match(/add_index :products, :year/, up)
@@ -182,7 +182,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_match(/add_index :products, :discount, :unique => true/, content)
end
end
-
+
def test_migration_without_timestamps
ActiveRecord::Base.timestamped_migrations = false
run_generator ["account"]
@@ -261,7 +261,17 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_invokes_default_test_framework
run_generator
assert_file "test/unit/account_test.rb", /class AccountTest < ActiveSupport::TestCase/
+
assert_file "test/fixtures/accounts.yml", /name: MyString/, /age: 1/
+ assert_generated_fixture("test/fixtures/accounts.yml",
+ {"one"=>{"name"=>"MyString", "age"=>1}, "two"=>{"name"=>"MyString", "age"=>1}})
+ end
+
+ def test_fixtures_respect_reserved_yml_keywords
+ run_generator ["LineItem", "no:integer", "Off:boolean", "ON:boolean"]
+
+ assert_generated_fixture("test/fixtures/line_items.yml",
+ {"one"=>{"no"=>1, "Off"=>false, "ON"=>false}, "two"=>{"no"=>1, "Off"=>false, "ON"=>false}})
end
def test_fixture_is_skipped
@@ -329,4 +339,11 @@ class ModelGeneratorTest < Rails::Generators::TestCase
run_generator ["Account"]
assert_file 'app/models/account.rb', /# attr_accessible :title, :body/
end
+
+ private
+ def assert_generated_fixture(path, parsed_contents)
+ fixture_file = File.new File.expand_path(path, destination_root)
+ assert_equal(parsed_contents, YAML.load(fixture_file))
+ end
+
end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index dcf4f95194..a33c6b096c 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -65,6 +65,12 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_no_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile")))
end
+ def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files
+ run_generator [destination_root, "-T", "--mountable", '--dummy-path', 'my_dummy_app']
+
+ assert_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile")))
+ end
+
def test_ensure_that_plugin_options_are_not_passed_to_app_generator
FileUtils.cd(Rails.root)
assert_no_match(/It works from file!.*It works_from_file/, run_generator([destination_root, "-m", "lib/template.rb"]))
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 5f9ee220dc..01bd11e1ab 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -1,7 +1,7 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/model/model_generator'
require 'rails/generators/test_unit/model/model_generator'
-require 'mocha'
+require 'mocha/setup'
class GeneratorsTest < Rails::Generators::TestCase
include GeneratorsTestHelper
@@ -201,7 +201,7 @@ class GeneratorsTest < Rails::Generators::TestCase
mspec = Rails::Generators.find_by_namespace :fixjour
assert mspec.source_paths.include?(File.join(Rails.root, "lib", "templates", "fixjour"))
end
-
+
def test_usage_with_embedded_ruby
require File.expand_path("fixtures/lib/generators/usage_template/usage_template_generator", File.dirname(__FILE__))
output = capture(:stdout) { Rails::Generators.invoke :usage_template, ['--help'] }