aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile12
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb2
-rw-r--r--actionmailer/test/delivery_methods_test.rb3
-rw-r--r--actionpack/lib/action_controller/base.rb2
-rw-r--r--actionpack/lib/action_controller/metal/live.rb74
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb7
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb29
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb24
-rw-r--r--actionpack/test/controller/live_stream_test.rb88
-rw-r--r--actionpack/test/controller/mime/accept_format_test.rb94
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb493
-rw-r--r--actionpack/test/controller/mime/respond_with_test.rb (renamed from actionpack/test/controller/mime_responds_test.rb)619
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb2
-rw-r--r--actionpack/test/controller/render_test.rb4
-rw-r--r--actionview/actionview.gemspec2
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb6
-rw-r--r--activerecord/CHANGELOG.md48
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb6
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb10
-rw-r--r--activerecord/lib/active_record/reflection.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb14
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb70
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb16
-rw-r--r--activerecord/test/cases/finder_test.rb56
-rw-r--r--activerecord/test/cases/nested_attributes_with_callbacks_test.rb144
-rw-r--r--activerecord/test/cases/relations_test.rb4
-rw-r--r--activerecord/test/cases/transactions_test.rb5
-rw-r--r--activerecord/test/models/author.rb3
-rw-r--r--activerecord/test/models/auto_id.rb4
-rw-r--r--activerecord/test/models/car.rb3
-rw-r--r--activerecord/test/models/citation.rb3
-rw-r--r--activerecord/test/models/club.rb1
-rw-r--r--activerecord/test/models/company.rb2
-rw-r--r--activerecord/test/models/man.rb1
-rw-r--r--activerecord/test/models/member.rb1
-rw-r--r--activerecord/test/models/mixed_case_monkey.rb2
-rw-r--r--activerecord/test/models/movie.rb4
-rw-r--r--activerecord/test/models/post.rb1
-rw-r--r--activerecord/test/models/project.rb1
-rw-r--r--activerecord/test/models/topic.rb1
-rw-r--r--activesupport/CHANGELOG.md16
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/thread.rb21
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb23
-rw-r--r--activesupport/lib/active_support/duration.rb14
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb3
-rw-r--r--activesupport/lib/active_support/ordered_options.rb2
-rw-r--r--activesupport/test/abstract_unit.rb4
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb5
-rw-r--r--activesupport/test/core_ext/thread_test.rb11
-rw-r--r--activesupport/test/deprecation_test.rb16
-rw-r--r--activesupport/test/transliterate_test.rb6
-rw-r--r--activesupport/test/xml_mini_test.rb12
-rw-r--r--guides/source/4_0_release_notes.md13
-rw-r--r--guides/source/_welcome.html.erb2
-rw-r--r--guides/source/action_view_overview.md66
-rw-r--r--guides/source/active_record_basics.md4
-rw-r--r--guides/source/active_record_callbacks.md4
-rw-r--r--guides/source/active_record_querying.md8
-rw-r--r--guides/source/active_support_core_extensions.md77
-rw-r--r--guides/source/association_basics.md6
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md6
-rw-r--r--guides/source/development_dependencies_install.md32
-rw-r--r--guides/source/engines.md4
-rw-r--r--guides/source/form_helpers.md2
-rw-r--r--guides/source/getting_started.md9
-rw-r--r--guides/source/i18n.md13
-rw-r--r--guides/source/initialization.md2
-rw-r--r--guides/source/layout.html.erb10
-rw-r--r--guides/source/testing.md90
-rw-r--r--guides/source/upgrading_ruby_on_rails.md2
-rw-r--r--guides/source/working_with_javascript_in_rails.md2
-rw-r--r--railties/CHANGELOG.md22
-rw-r--r--railties/lib/rails/api/task.rb12
-rw-r--r--railties/lib/rails/app_rails_loader.rb2
-rw-r--r--railties/lib/rails/engine.rb9
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb6
-rw-r--r--railties/lib/rails/generators/rails/controller/controller_generator.rb36
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb2
-rw-r--r--railties/test/application/rake/notes_test.rb4
-rw-r--r--railties/test/generators/controller_generator_test.rb5
-rw-r--r--railties/test/generators/scaffold_generator_test.rb4
99 files changed, 1654 insertions, 867 deletions
diff --git a/Gemfile b/Gemfile
index cff6ae4dec..d703a2e404 100644
--- a/Gemfile
+++ b/Gemfile
@@ -61,13 +61,13 @@ platforms :ruby do
end
platforms :jruby do
- git 'git://github.com/jruby/activerecord-jdbc-adapter.git' do
- gem 'activerecord-jdbcsqlite3-adapter'
- group :db do
- gem 'activerecord-jdbcmysql-adapter'
- gem 'activerecord-jdbcpostgresql-adapter'
- end
+ git 'git://github.com/jruby/activerecord-jdbc-adapter.git' do
+ gem 'activerecord-jdbcsqlite3-adapter'
+ group :db do
+ gem 'activerecord-jdbcmysql-adapter'
+ gem 'activerecord-jdbcpostgresql-adapter'
end
+ end
end
# gems that are necessary for ActiveRecord tests with Oracle database
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index aedcd81e52..9a1a27c8ed 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -64,7 +64,7 @@ module ActionMailer
raise "Delivery method cannot be nil"
when Symbol
if klass = delivery_methods[method]
- mail.delivery_method(klass, (send(:"#{method}_settings") || {}).merge(options || {}))
+ mail.delivery_method(klass,(send(:"#{method}_settings") || {}).merge!(options || {}))
else
raise "Invalid delivery method #{method.inspect}"
end
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index 20412c7bb2..61a037ea18 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -152,9 +152,6 @@ class MailDeliveryTest < ActiveSupport::TestCase
assert_equal "overridden", delivery_method_instance.settings[:user_name]
assert_equal "somethingobtuse", delivery_method_instance.settings[:password]
assert_equal delivery_method_instance.settings.merge(overridden_options), delivery_method_instance.settings
-
- # make sure that overriding delivery method options per mail instance doesn't affect the Base setting
- assert_equal settings, ActionMailer::Base.smtp_settings
end
test "non registered delivery methods raises errors" do
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 1faecc850b..67d261db77 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -59,7 +59,7 @@ module ActionController
# <input type="text" name="post[address]" value="hyacintvej">
#
# A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
- # If the address input had been named <tt>post[address][street]</tt>, the params would have included
+ # If the address input had been named "post[address][street]", the params would have included
# <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
#
# == Sessions
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 8092fd639f..0dd788645b 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -1,5 +1,6 @@
require 'action_dispatch/http/response'
require 'delegate'
+require 'active_support/json'
module ActionController
# Mix this module in to your controller, and all actions in that controller
@@ -32,6 +33,79 @@ module ActionController
# the main thread. Make sure your actions are thread safe, and this shouldn't
# be a problem (don't share state across threads, etc).
module Live
+ # This class provides the ability to write an SSE (Server Sent Event)
+ # to an IO stream. The class is initialized with a stream and can be used
+ # to either write a JSON string or an object which can be converted to JSON.
+ #
+ # Writing an object will convert it into standard SSE format with whatever
+ # options you have configured. You may choose to set the following options:
+ #
+ # 1) Event. If specified, an event with this name will be dispatched on
+ # the browser.
+ # 2) Retry. The reconnection time in milliseconds used when attempting
+ # to send the event.
+ # 3) Id. If the connection dies while sending an SSE to the browser, then
+ # the server will receive a +Last-Event-ID+ header with value equal to +id+.
+ #
+ # After setting an option in the constructor of the SSE object, all future
+ # SSEs sent accross the stream will use those options unless overridden.
+ #
+ # Example Usage:
+ #
+ # class MyController < ActionController::Base
+ # include ActionController::Live
+ #
+ # def index
+ # response.headers['Content-Type'] = 'text/event-stream'
+ # sse = SSE.new(response.stream, retry: 300, event: "event-name")
+ # sse.write({ name: 'John'})
+ # sse.write({ name: 'John'}, id: 10)
+ # sse.write({ name: 'John'}, id: 10, event: "other-event")
+ # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500)
+ # ensure
+ # sse.close
+ # end
+ # end
+ #
+ # Note: SSEs are not currently supported by IE. However, they are supported
+ # by Chrome, Firefox, Opera, and Safari.
+ class SSE
+
+ WHITELISTED_OPTIONS = %w( retry event id )
+
+ def initialize(stream, options = {})
+ @stream = stream
+ @options = options
+ end
+
+ def close
+ @stream.close
+ end
+
+ def write(object, options = {})
+ case object
+ when String
+ perform_write(object, options)
+ else
+ perform_write(ActiveSupport::JSON.encode(object), options)
+ end
+ end
+
+ private
+
+ def perform_write(json, options)
+ current_options = @options.merge(options).stringify_keys
+
+ WHITELISTED_OPTIONS.each do |option_name|
+ if (option_value = current_options[option_name])
+ @stream.write "#{option_name}: #{option_value}\n"
+ end
+ end
+
+ @stream.write "data: #{json}\n\n"
+ end
+ end
+
class Buffer < ActionDispatch::Response::Buffer #:nodoc:
def initialize(response)
@error_callback = nil
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 5272dc6cdb..abed6e53cc 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -6,6 +6,13 @@ module ActionController
Renderers.add(key, &block)
end
+ class MissingRenderer < LoadError
+ def initialize(format)
+ @format = format
+ super("No renderer defined for format: #{@format}")
+ end
+ end
+
module Renderers
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index fd5b661209..66ff34a794 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -202,6 +202,7 @@ module ActionController #:nodoc:
# This is the common behavior for formats associated with APIs, such as :xml and :json.
def api_behavior(error)
raise error unless resourceful?
+ raise MissingRenderer.new(format) unless has_renderer?
if get?
display resource
@@ -269,6 +270,11 @@ module ActionController #:nodoc:
resource.respond_to?(:errors) && !resource.errors.empty?
end
+ # Check whether the neceessary Renderer is available
+ def has_renderer?
+ Renderers::RENDERERS.include?(format)
+ end
+
# By default, render the <code>:edit</code> action for HTML requests with errors, unless
# the verb was POST.
#
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 8e992070f1..dcb299ed03 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -57,22 +57,25 @@ module ActionDispatch
# you'll get a weird error down the road, but our form handling
# should really prevent that from happening
def normalize_encode_params(params)
- if params.is_a?(String)
- return params.force_encoding(Encoding::UTF_8).encode!
- elsif !params.is_a?(Hash)
- return params
- end
-
- new_hash = {}
- params.each do |key, val|
- new_key = key.is_a?(String) ? key.dup.force_encoding(Encoding::UTF_8).encode! : key
- new_hash[new_key] = if val.is_a?(Array)
- val.map! { |el| normalize_encode_params(el) }
+ case params
+ when String
+ params.force_encoding(Encoding::UTF_8).encode!
+ when Hash
+ if params.has_key?(:tempfile)
+ UploadedFile.new(params)
else
- normalize_encode_params(val)
+ params.each_with_object({}) do |(key, val), new_hash|
+ new_key = key.is_a?(String) ? key.dup.force_encoding(Encoding::UTF_8).encode! : key
+ new_hash[new_key] = if val.is_a?(Array)
+ val.map! { |el| normalize_encode_params(el) }
+ else
+ normalize_encode_params(val)
+ end
+ end.with_indifferent_access
end
+ else
+ params
end
- new_hash.with_indifferent_access
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index b4cf8ad2f7..aba8f66118 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -18,7 +18,6 @@ module ActionDispatch
include ActionDispatch::Http::MimeNegotiation
include ActionDispatch::Http::Parameters
include ActionDispatch::Http::FilterParameters
- include ActionDispatch::Http::Upload
include ActionDispatch::Http::URL
autoload :Session, 'action_dispatch/request/session'
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index b57c84dec8..a8d2dc3950 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -73,18 +73,5 @@ module ActionDispatch
filename.force_encoding(Encoding::UTF_8).encode! if filename
end
end
-
- module Upload # :nodoc:
- # Replace file upload hash with UploadedFile objects
- # when normalize and encode parameters.
- def normalize_encode_params(value)
- if Hash === value && value.has_key?(:tempfile)
- UploadedFile.new(value)
- else
- super
- end
- end
- private :normalize_encode_params
- end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
index 57a2940802..f154021ae6 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
@@ -8,7 +8,7 @@
</header>
<div id="container">
- <h2><%= @exception.message %></h2>
+ <h2><%= h @exception.message %></h2>
<%= render template: "rescues/_source" %>
<%= render template: "rescues/_trace" %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb
index ca14215946..5c016e544e 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb
@@ -3,5 +3,5 @@
</header>
<div id="container">
- <h2><%= @exception.message %></h2>
+ <h2><%= h @exception.message %></h2>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
index cd3daff065..7e9cedb95e 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
@@ -2,7 +2,7 @@
<h1>Routing Error</h1>
</header>
<div id="container">
- <h2><%= @exception.message %></h2>
+ <h2><%= h @exception.message %></h2>
<% unless @exception.failures.empty? %>
<p>
<h2>Failure reasons:</h2>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
index 31f46ee340..027a0f5b3e 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
@@ -10,7 +10,7 @@
<p>
Showing <i><%= @exception.file_name %></i> where line <b>#<%= @exception.line_number %></b> raised:
</p>
- <pre><code><%= @exception.message %></code></pre>
+ <pre><code><%= h @exception.message %></code></pre>
<div class="source">
<div class="info">
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb
index c1fbf67eed..259fb2bb3b 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb
@@ -2,5 +2,5 @@
<h1>Unknown action</h1>
</header>
<div id="container">
- <h2><%= @exception.message %></h2>
+ <h2><%= h @exception.message %></h2>
</div>
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 943fc15026..0e5dc1fc6c 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -514,12 +514,11 @@ module ActionDispatch
@recall = recall.dup
@set = set
- normalize_recall!
normalize_options!
normalize_controller_action_id!
use_relative_controller!
normalize_controller!
- normalize_action!
+ handle_nil_action!
end
def controller
@@ -538,11 +537,6 @@ module ActionDispatch
end
end
- # Set 'index' as default action for recall
- def normalize_recall!
- @recall[:action] ||= 'index'
- end
-
def normalize_options!
# If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like
@@ -558,8 +552,8 @@ module ActionDispatch
options[:controller] = options[:controller].to_s
end
- if options.key?(:action)
- options[:action] = (options[:action] || 'index').to_s
+ if options[:action]
+ options[:action] = options[:action].to_s
end
end
@@ -569,6 +563,8 @@ module ActionDispatch
# :controller, :action or :id is not found, don't pull any
# more keys from the recall.
def normalize_controller_action_id!
+ @recall[:action] ||= 'index' if current_controller
+
use_recall_for(:controller) or return
use_recall_for(:action) or return
use_recall_for(:id)
@@ -590,11 +586,13 @@ module ActionDispatch
@options[:controller] = controller.sub(%r{^/}, '') if controller
end
- # Move 'index' action from options to recall
- def normalize_action!
- if @options[:action] == 'index'
- @recall[:action] = @options.delete(:action)
+ # This handles the case of action: nil being explicitly passed.
+ # It is identical to action: "index"
+ def handle_nil_action!
+ if options.has_key?(:action) && options[:action].nil?
+ options[:action] = 'index'
end
+ recall[:action] = options.delete(:action) if options[:action] == 'index'
end
# Generates a path from routes, returns [path, params].
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index e727b27db0..0a431270b5 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -2,6 +2,94 @@ require 'abstract_unit'
require 'active_support/concurrency/latch'
module ActionController
+ class SSETest < ActionController::TestCase
+ class SSETestController < ActionController::Base
+ include ActionController::Live
+
+ def basic_sse
+ response.headers['Content-Type'] = 'text/event-stream'
+ sse = SSE.new(response.stream)
+ sse.write("{\"name\":\"John\"}")
+ sse.write({ name: "Ryan" })
+ ensure
+ sse.close
+ end
+
+ def sse_with_event
+ sse = SSE.new(response.stream, event: "send-name")
+ sse.write("{\"name\":\"John\"}")
+ sse.write({ name: "Ryan" })
+ ensure
+ sse.close
+ end
+
+ def sse_with_retry
+ sse = SSE.new(response.stream, retry: 1000)
+ sse.write("{\"name\":\"John\"}")
+ sse.write({ name: "Ryan" }, retry: 1500)
+ ensure
+ sse.close
+ end
+
+ def sse_with_id
+ sse = SSE.new(response.stream)
+ sse.write("{\"name\":\"John\"}", id: 1)
+ sse.write({ name: "Ryan" }, id: 2)
+ ensure
+ sse.close
+ end
+ end
+
+ tests SSETestController
+
+ def wait_for_response_stream_close
+ while !response.stream.closed?
+ sleep 0.01
+ end
+ end
+
+ def test_basic_sse
+ get :basic_sse
+
+ wait_for_response_stream_close
+ assert_match(/data: {\"name\":\"John\"}/, response.body)
+ assert_match(/data: {\"name\":\"Ryan\"}/, response.body)
+ end
+
+ def test_sse_with_event_name
+ get :sse_with_event
+
+ wait_for_response_stream_close
+ assert_match(/data: {\"name\":\"John\"}/, response.body)
+ assert_match(/data: {\"name\":\"Ryan\"}/, response.body)
+ assert_match(/event: send-name/, response.body)
+ end
+
+ def test_sse_with_retry
+ get :sse_with_retry
+
+ wait_for_response_stream_close
+ first_response, second_response = response.body.split("\n\n")
+ assert_match(/data: {\"name\":\"John\"}/, first_response)
+ assert_match(/retry: 1000/, first_response)
+
+ assert_match(/data: {\"name\":\"Ryan\"}/, second_response)
+ assert_match(/retry: 1500/, second_response)
+ end
+
+ def test_sse_with_id
+ get :sse_with_id
+
+ wait_for_response_stream_close
+ first_response, second_response = response.body.split("\n\n")
+ assert_match(/data: {\"name\":\"John\"}/, first_response)
+ assert_match(/id: 1/, first_response)
+
+ assert_match(/data: {\"name\":\"Ryan\"}/, second_response)
+ assert_match(/id: 2/, second_response)
+ end
+ end
+
class LiveStreamTest < ActionController::TestCase
class TestController < ActionController::Base
include ActionController::Live
diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb
new file mode 100644
index 0000000000..c03c7edeb8
--- /dev/null
+++ b/actionpack/test/controller/mime/accept_format_test.rb
@@ -0,0 +1,94 @@
+require 'abstract_unit'
+
+class StarStarMimeController < ActionController::Base
+ layout nil
+
+ def index
+ render
+ end
+end
+
+class StarStarMimeControllerTest < ActionController::TestCase
+ tests StarStarMimeController
+
+ def test_javascript_with_format
+ @request.accept = "text/javascript"
+ get :index, :format => 'js'
+ assert_match "function addition(a,b){ return a+b; }", @response.body
+ end
+
+ def test_javascript_with_no_format
+ @request.accept = "text/javascript"
+ get :index
+ assert_match "function addition(a,b){ return a+b; }", @response.body
+ end
+
+ def test_javascript_with_no_format_only_star_star
+ @request.accept = "*/*"
+ get :index
+ assert_match "function addition(a,b){ return a+b; }", @response.body
+ end
+end
+
+class AbstractPostController < ActionController::Base
+ self.view_paths = File.dirname(__FILE__) + "/../../fixtures/post_test/"
+end
+
+# For testing layouts which are set automatically
+class PostController < AbstractPostController
+ around_action :with_iphone
+
+ def index
+ respond_to(:html, :iphone, :js)
+ end
+
+protected
+
+ def with_iphone
+ request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone"
+ yield
+ end
+end
+
+class SuperPostController < PostController
+end
+
+class MimeControllerLayoutsTest < ActionController::TestCase
+ tests PostController
+
+ def setup
+ super
+ @request.host = "www.example.com"
+ Mime::Type.register_alias("text/html", :iphone)
+ end
+
+ def teardown
+ super
+ Mime::Type.unregister(:iphone)
+ end
+
+ def test_missing_layout_renders_properly
+ get :index
+ assert_equal '<html><div id="html">Hello Firefox</div></html>', @response.body
+
+ @request.accept = "text/iphone"
+ get :index
+ assert_equal 'Hello iPhone', @response.body
+ end
+
+ def test_format_with_inherited_layouts
+ @controller = SuperPostController.new
+
+ get :index
+ assert_equal '<html><div id="html">Super Firefox</div></html>', @response.body
+
+ @request.accept = "text/iphone"
+ get :index
+ assert_equal '<html><div id="super_iphone">Super iPhone</div></html>', @response.body
+ end
+
+ def test_non_navigational_format_with_no_template_fallbacks_to_html_template_with_no_layout
+ get :index, :format => :js
+ assert_equal "Hello Firefox", @response.body
+ end
+end
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
new file mode 100644
index 0000000000..774dabe105
--- /dev/null
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -0,0 +1,493 @@
+require 'abstract_unit'
+
+class RespondToController < ActionController::Base
+ layout :set_layout
+
+ def html_xml_or_rss
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.xml { render :text => "XML" }
+ type.rss { render :text => "RSS" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def js_or_html
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.js { render :text => "JS" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def json_or_yaml
+ respond_to do |type|
+ type.json { render :text => "JSON" }
+ type.yaml { render :text => "YAML" }
+ end
+ end
+
+ def html_or_xml
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.xml { render :text => "XML" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def json_xml_or_html
+ respond_to do |type|
+ type.json { render :text => 'JSON' }
+ type.xml { render :xml => 'XML' }
+ type.html { render :text => 'HTML' }
+ end
+ end
+
+
+ def forced_xml
+ request.format = :xml
+
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.xml { render :text => "XML" }
+ end
+ end
+
+ def just_xml
+ respond_to do |type|
+ type.xml { render :text => "XML" }
+ end
+ end
+
+ def using_defaults
+ respond_to do |type|
+ type.html
+ type.xml
+ end
+ end
+
+ def using_defaults_with_type_list
+ respond_to(:html, :xml)
+ end
+
+ def using_defaults_with_all
+ respond_to do |type|
+ type.html
+ type.all{ render text: "ALL" }
+ end
+ end
+
+ def made_for_content_type
+ respond_to do |type|
+ type.rss { render :text => "RSS" }
+ type.atom { render :text => "ATOM" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def custom_type_handling
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.custom("application/crazy-xml") { render :text => "Crazy XML" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+
+ def custom_constant_handling
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.mobile { render :text => "Mobile" }
+ end
+ end
+
+ def custom_constant_handling_without_block
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.mobile
+ end
+ end
+
+ def handle_any
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.any(:js, :xml) { render :text => "Either JS or XML" }
+ end
+ end
+
+ def handle_any_any
+ respond_to do |type|
+ type.html { render :text => 'HTML' }
+ type.any { render :text => 'Whatever you ask for, I got it' }
+ end
+ end
+
+ def all_types_with_layout
+ respond_to do |type|
+ type.html
+ end
+ end
+
+ def iphone_with_html_response_type
+ request.format = :iphone if request.env["HTTP_ACCEPT"] == "text/iphone"
+
+ respond_to do |type|
+ type.html { @type = "Firefox" }
+ type.iphone { @type = "iPhone" }
+ end
+ end
+
+ def iphone_with_html_response_type_without_layout
+ request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone"
+
+ respond_to do |type|
+ type.html { @type = "Firefox"; render :action => "iphone_with_html_response_type" }
+ type.iphone { @type = "iPhone" ; render :action => "iphone_with_html_response_type" }
+ end
+ end
+
+ protected
+ def set_layout
+ case action_name
+ when "all_types_with_layout", "iphone_with_html_response_type"
+ "respond_to/layouts/standard"
+ when "iphone_with_html_response_type_without_layout"
+ "respond_to/layouts/missing"
+ end
+ end
+end
+
+class RespondToControllerTest < ActionController::TestCase
+ tests RespondToController
+
+ def setup
+ super
+ @request.host = "www.example.com"
+ Mime::Type.register_alias("text/html", :iphone)
+ Mime::Type.register("text/x-mobile", :mobile)
+ end
+
+ def teardown
+ super
+ Mime::Type.unregister(:iphone)
+ Mime::Type.unregister(:mobile)
+ end
+
+ def test_html
+ @request.accept = "text/html"
+ get :js_or_html
+ assert_equal 'HTML', @response.body
+
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+
+ assert_raises(ActionController::UnknownFormat) do
+ get :just_xml
+ end
+ end
+
+ def test_all
+ @request.accept = "*/*"
+ get :js_or_html
+ assert_equal 'HTML', @response.body # js is not part of all
+
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+
+ get :just_xml
+ assert_equal 'XML', @response.body
+ end
+
+ def test_xml
+ @request.accept = "application/xml"
+ get :html_xml_or_rss
+ assert_equal 'XML', @response.body
+ end
+
+ def test_js_or_html
+ @request.accept = "text/javascript, text/html"
+ xhr :get, :js_or_html
+ assert_equal 'JS', @response.body
+
+ @request.accept = "text/javascript, text/html"
+ xhr :get, :html_or_xml
+ assert_equal 'HTML', @response.body
+
+ @request.accept = "text/javascript, text/html"
+
+ assert_raises(ActionController::UnknownFormat) do
+ xhr :get, :just_xml
+ end
+ end
+
+ def test_json_or_yaml_with_leading_star_star
+ @request.accept = "*/*, application/json"
+ get :json_xml_or_html
+ assert_equal 'HTML', @response.body
+
+ @request.accept = "*/* , application/json"
+ get :json_xml_or_html
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_json_or_yaml
+ xhr :get, :json_or_yaml
+ assert_equal 'JSON', @response.body
+
+ get :json_or_yaml, :format => 'json'
+ assert_equal 'JSON', @response.body
+
+ get :json_or_yaml, :format => 'yaml'
+ assert_equal 'YAML', @response.body
+
+ { 'YAML' => %w(text/yaml),
+ 'JSON' => %w(application/json text/x-json)
+ }.each do |body, content_types|
+ content_types.each do |content_type|
+ @request.accept = content_type
+ get :json_or_yaml
+ assert_equal body, @response.body
+ end
+ end
+ end
+
+ def test_js_or_anything
+ @request.accept = "text/javascript, */*"
+ xhr :get, :js_or_html
+ assert_equal 'JS', @response.body
+
+ xhr :get, :html_or_xml
+ assert_equal 'HTML', @response.body
+
+ xhr :get, :just_xml
+ assert_equal 'XML', @response.body
+ end
+
+ def test_using_defaults
+ @request.accept = "*/*"
+ get :using_defaults
+ assert_equal "text/html", @response.content_type
+ assert_equal 'Hello world!', @response.body
+
+ @request.accept = "application/xml"
+ get :using_defaults
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<p>Hello world!</p>\n", @response.body
+ end
+
+ def test_using_defaults_with_all
+ @request.accept = "*/*"
+ get :using_defaults_with_all
+ assert_equal "HTML!", @response.body.strip
+
+ @request.accept = "text/html"
+ get :using_defaults_with_all
+ assert_equal "HTML!", @response.body.strip
+
+ @request.accept = "application/json"
+ get :using_defaults_with_all
+ assert_equal "ALL", @response.body
+ end
+
+ def test_using_defaults_with_type_list
+ @request.accept = "*/*"
+ get :using_defaults_with_type_list
+ assert_equal "text/html", @response.content_type
+ assert_equal 'Hello world!', @response.body
+
+ @request.accept = "application/xml"
+ get :using_defaults_with_type_list
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<p>Hello world!</p>\n", @response.body
+ end
+
+ def test_with_atom_content_type
+ @request.accept = ""
+ @request.env["CONTENT_TYPE"] = "application/atom+xml"
+ xhr :get, :made_for_content_type
+ assert_equal "ATOM", @response.body
+ end
+
+ def test_with_rss_content_type
+ @request.accept = ""
+ @request.env["CONTENT_TYPE"] = "application/rss+xml"
+ xhr :get, :made_for_content_type
+ assert_equal "RSS", @response.body
+ end
+
+ def test_synonyms
+ @request.accept = "application/javascript"
+ get :js_or_html
+ assert_equal 'JS', @response.body
+
+ @request.accept = "application/x-xml"
+ get :html_xml_or_rss
+ assert_equal "XML", @response.body
+ end
+
+ def test_custom_types
+ @request.accept = "application/crazy-xml"
+ get :custom_type_handling
+ assert_equal "application/crazy-xml", @response.content_type
+ assert_equal 'Crazy XML', @response.body
+
+ @request.accept = "text/html"
+ get :custom_type_handling
+ assert_equal "text/html", @response.content_type
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_xhtml_alias
+ @request.accept = "application/xhtml+xml,application/xml"
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_firefox_simulation
+ @request.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_handle_any
+ @request.accept = "*/*"
+ get :handle_any
+ assert_equal 'HTML', @response.body
+
+ @request.accept = "text/javascript"
+ get :handle_any
+ assert_equal 'Either JS or XML', @response.body
+
+ @request.accept = "text/xml"
+ get :handle_any
+ assert_equal 'Either JS or XML', @response.body
+ end
+
+ def test_handle_any_any
+ @request.accept = "*/*"
+ get :handle_any_any
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_handle_any_any_parameter_format
+ get :handle_any_any, {:format=>'html'}
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_handle_any_any_explicit_html
+ @request.accept = "text/html"
+ get :handle_any_any
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_handle_any_any_javascript
+ @request.accept = "text/javascript"
+ get :handle_any_any
+ assert_equal 'Whatever you ask for, I got it', @response.body
+ end
+
+ def test_handle_any_any_xml
+ @request.accept = "text/xml"
+ get :handle_any_any
+ assert_equal 'Whatever you ask for, I got it', @response.body
+ end
+
+ def test_browser_check_with_any_any
+ @request.accept = "application/json, application/xml"
+ get :json_xml_or_html
+ assert_equal 'JSON', @response.body
+
+ @request.accept = "application/json, application/xml, */*"
+ get :json_xml_or_html
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_html_type_with_layout
+ @request.accept = "text/html"
+ get :all_types_with_layout
+ assert_equal '<html><div id="html">HTML for all_types_with_layout</div></html>', @response.body
+ end
+
+ def test_xhr
+ xhr :get, :js_or_html
+ assert_equal 'JS', @response.body
+ end
+
+ def test_custom_constant
+ get :custom_constant_handling, :format => "mobile"
+ assert_equal "text/x-mobile", @response.content_type
+ assert_equal "Mobile", @response.body
+ end
+
+ def test_custom_constant_handling_without_block
+ get :custom_constant_handling_without_block, :format => "mobile"
+ assert_equal "text/x-mobile", @response.content_type
+ assert_equal "Mobile", @response.body
+ end
+
+ def test_forced_format
+ get :html_xml_or_rss
+ assert_equal "HTML", @response.body
+
+ get :html_xml_or_rss, :format => "html"
+ assert_equal "HTML", @response.body
+
+ get :html_xml_or_rss, :format => "xml"
+ assert_equal "XML", @response.body
+
+ get :html_xml_or_rss, :format => "rss"
+ assert_equal "RSS", @response.body
+ end
+
+ def test_internally_forced_format
+ get :forced_xml
+ assert_equal "XML", @response.body
+
+ get :forced_xml, :format => "html"
+ assert_equal "XML", @response.body
+ end
+
+ def test_extension_synonyms
+ get :html_xml_or_rss, :format => "xhtml"
+ assert_equal "HTML", @response.body
+ end
+
+ def test_render_action_for_html
+ @controller.instance_eval do
+ def render(*args)
+ @action = args.first[:action] unless args.empty?
+ @action ||= action_name
+
+ response.body = "#{@action} - #{formats}"
+ end
+ end
+
+ get :using_defaults
+ assert_equal "using_defaults - #{[:html].to_s}", @response.body
+
+ get :using_defaults, :format => "xml"
+ assert_equal "using_defaults - #{[:xml].to_s}", @response.body
+ end
+
+ def test_format_with_custom_response_type
+ get :iphone_with_html_response_type
+ assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body
+
+ get :iphone_with_html_response_type, :format => "iphone"
+ assert_equal "text/html", @response.content_type
+ assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
+ end
+
+ def test_format_with_custom_response_type_and_request_headers
+ @request.accept = "text/iphone"
+ get :iphone_with_html_response_type
+ assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
+ assert_equal "text/html", @response.content_type
+ end
+
+ def test_invalid_format
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_defaults, :format => "invalidformat"
+ end
+ end
+end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime/respond_with_test.rb
index 2d89969fd3..29ddbff8d4 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime/respond_with_test.rb
@@ -1,529 +1,5 @@
require 'abstract_unit'
require 'controller/fake_models'
-require 'active_support/core_ext/hash/conversions'
-
-class StarStarMimeController < ActionController::Base
- layout nil
-
- def index
- render
- end
-end
-
-class RespondToController < ActionController::Base
- layout :set_layout
-
- def html_xml_or_rss
- respond_to do |type|
- type.html { render :text => "HTML" }
- type.xml { render :text => "XML" }
- type.rss { render :text => "RSS" }
- type.all { render :text => "Nothing" }
- end
- end
-
- def js_or_html
- respond_to do |type|
- type.html { render :text => "HTML" }
- type.js { render :text => "JS" }
- type.all { render :text => "Nothing" }
- end
- end
-
- def json_or_yaml
- respond_to do |type|
- type.json { render :text => "JSON" }
- type.yaml { render :text => "YAML" }
- end
- end
-
- def html_or_xml
- respond_to do |type|
- type.html { render :text => "HTML" }
- type.xml { render :text => "XML" }
- type.all { render :text => "Nothing" }
- end
- end
-
- def json_xml_or_html
- respond_to do |type|
- type.json { render :text => 'JSON' }
- type.xml { render :xml => 'XML' }
- type.html { render :text => 'HTML' }
- end
- end
-
-
- def forced_xml
- request.format = :xml
-
- respond_to do |type|
- type.html { render :text => "HTML" }
- type.xml { render :text => "XML" }
- end
- end
-
- def just_xml
- respond_to do |type|
- type.xml { render :text => "XML" }
- end
- end
-
- def using_defaults
- respond_to do |type|
- type.html
- type.xml
- end
- end
-
- def using_defaults_with_type_list
- respond_to(:html, :xml)
- end
-
- def using_defaults_with_all
- respond_to do |type|
- type.html
- type.all{ render text: "ALL" }
- end
- end
-
- def made_for_content_type
- respond_to do |type|
- type.rss { render :text => "RSS" }
- type.atom { render :text => "ATOM" }
- type.all { render :text => "Nothing" }
- end
- end
-
- def custom_type_handling
- respond_to do |type|
- type.html { render :text => "HTML" }
- type.custom("application/crazy-xml") { render :text => "Crazy XML" }
- type.all { render :text => "Nothing" }
- end
- end
-
-
- def custom_constant_handling
- respond_to do |type|
- type.html { render :text => "HTML" }
- type.mobile { render :text => "Mobile" }
- end
- end
-
- def custom_constant_handling_without_block
- respond_to do |type|
- type.html { render :text => "HTML" }
- type.mobile
- end
- end
-
- def handle_any
- respond_to do |type|
- type.html { render :text => "HTML" }
- type.any(:js, :xml) { render :text => "Either JS or XML" }
- end
- end
-
- def handle_any_any
- respond_to do |type|
- type.html { render :text => 'HTML' }
- type.any { render :text => 'Whatever you ask for, I got it' }
- end
- end
-
- def all_types_with_layout
- respond_to do |type|
- type.html
- end
- end
-
- def iphone_with_html_response_type
- request.format = :iphone if request.env["HTTP_ACCEPT"] == "text/iphone"
-
- respond_to do |type|
- type.html { @type = "Firefox" }
- type.iphone { @type = "iPhone" }
- end
- end
-
- def iphone_with_html_response_type_without_layout
- request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone"
-
- respond_to do |type|
- type.html { @type = "Firefox"; render :action => "iphone_with_html_response_type" }
- type.iphone { @type = "iPhone" ; render :action => "iphone_with_html_response_type" }
- end
- end
-
- protected
- def set_layout
- case action_name
- when "all_types_with_layout", "iphone_with_html_response_type"
- "respond_to/layouts/standard"
- when "iphone_with_html_response_type_without_layout"
- "respond_to/layouts/missing"
- end
- end
-end
-
-class StarStarMimeControllerTest < ActionController::TestCase
- tests StarStarMimeController
-
- def test_javascript_with_format
- @request.accept = "text/javascript"
- get :index, :format => 'js'
- assert_match "function addition(a,b){ return a+b; }", @response.body
- end
-
- def test_javascript_with_no_format
- @request.accept = "text/javascript"
- get :index
- assert_match "function addition(a,b){ return a+b; }", @response.body
- end
-
- def test_javascript_with_no_format_only_star_star
- @request.accept = "*/*"
- get :index
- assert_match "function addition(a,b){ return a+b; }", @response.body
- end
-
-end
-
-class RespondToControllerTest < ActionController::TestCase
- tests RespondToController
-
- def setup
- super
- @request.host = "www.example.com"
- Mime::Type.register_alias("text/html", :iphone)
- Mime::Type.register("text/x-mobile", :mobile)
- end
-
- def teardown
- super
- Mime::Type.unregister(:iphone)
- Mime::Type.unregister(:mobile)
- end
-
- def test_html
- @request.accept = "text/html"
- get :js_or_html
- assert_equal 'HTML', @response.body
-
- get :html_or_xml
- assert_equal 'HTML', @response.body
-
- assert_raises(ActionController::UnknownFormat) do
- get :just_xml
- end
- end
-
- def test_all
- @request.accept = "*/*"
- get :js_or_html
- assert_equal 'HTML', @response.body # js is not part of all
-
- get :html_or_xml
- assert_equal 'HTML', @response.body
-
- get :just_xml
- assert_equal 'XML', @response.body
- end
-
- def test_xml
- @request.accept = "application/xml"
- get :html_xml_or_rss
- assert_equal 'XML', @response.body
- end
-
- def test_js_or_html
- @request.accept = "text/javascript, text/html"
- xhr :get, :js_or_html
- assert_equal 'JS', @response.body
-
- @request.accept = "text/javascript, text/html"
- xhr :get, :html_or_xml
- assert_equal 'HTML', @response.body
-
- @request.accept = "text/javascript, text/html"
-
- assert_raises(ActionController::UnknownFormat) do
- xhr :get, :just_xml
- end
- end
-
- def test_json_or_yaml_with_leading_star_star
- @request.accept = "*/*, application/json"
- get :json_xml_or_html
- assert_equal 'HTML', @response.body
-
- @request.accept = "*/* , application/json"
- get :json_xml_or_html
- assert_equal 'HTML', @response.body
- end
-
- def test_json_or_yaml
- xhr :get, :json_or_yaml
- assert_equal 'JSON', @response.body
-
- get :json_or_yaml, :format => 'json'
- assert_equal 'JSON', @response.body
-
- get :json_or_yaml, :format => 'yaml'
- assert_equal 'YAML', @response.body
-
- { 'YAML' => %w(text/yaml),
- 'JSON' => %w(application/json text/x-json)
- }.each do |body, content_types|
- content_types.each do |content_type|
- @request.accept = content_type
- get :json_or_yaml
- assert_equal body, @response.body
- end
- end
- end
-
- def test_js_or_anything
- @request.accept = "text/javascript, */*"
- xhr :get, :js_or_html
- assert_equal 'JS', @response.body
-
- xhr :get, :html_or_xml
- assert_equal 'HTML', @response.body
-
- xhr :get, :just_xml
- assert_equal 'XML', @response.body
- end
-
- def test_using_defaults
- @request.accept = "*/*"
- get :using_defaults
- assert_equal "text/html", @response.content_type
- assert_equal 'Hello world!', @response.body
-
- @request.accept = "application/xml"
- get :using_defaults
- assert_equal "application/xml", @response.content_type
- assert_equal "<p>Hello world!</p>\n", @response.body
- end
-
- def test_using_defaults_with_all
- @request.accept = "*/*"
- get :using_defaults_with_all
- assert_equal "HTML!", @response.body.strip
-
- @request.accept = "text/html"
- get :using_defaults_with_all
- assert_equal "HTML!", @response.body.strip
-
- @request.accept = "application/json"
- get :using_defaults_with_all
- assert_equal "ALL", @response.body
- end
-
- def test_using_defaults_with_type_list
- @request.accept = "*/*"
- get :using_defaults_with_type_list
- assert_equal "text/html", @response.content_type
- assert_equal 'Hello world!', @response.body
-
- @request.accept = "application/xml"
- get :using_defaults_with_type_list
- assert_equal "application/xml", @response.content_type
- assert_equal "<p>Hello world!</p>\n", @response.body
- end
-
- def test_with_atom_content_type
- @request.accept = ""
- @request.env["CONTENT_TYPE"] = "application/atom+xml"
- xhr :get, :made_for_content_type
- assert_equal "ATOM", @response.body
- end
-
- def test_with_rss_content_type
- @request.accept = ""
- @request.env["CONTENT_TYPE"] = "application/rss+xml"
- xhr :get, :made_for_content_type
- assert_equal "RSS", @response.body
- end
-
- def test_synonyms
- @request.accept = "application/javascript"
- get :js_or_html
- assert_equal 'JS', @response.body
-
- @request.accept = "application/x-xml"
- get :html_xml_or_rss
- assert_equal "XML", @response.body
- end
-
- def test_custom_types
- @request.accept = "application/crazy-xml"
- get :custom_type_handling
- assert_equal "application/crazy-xml", @response.content_type
- assert_equal 'Crazy XML', @response.body
-
- @request.accept = "text/html"
- get :custom_type_handling
- assert_equal "text/html", @response.content_type
- assert_equal 'HTML', @response.body
- end
-
- def test_xhtml_alias
- @request.accept = "application/xhtml+xml,application/xml"
- get :html_or_xml
- assert_equal 'HTML', @response.body
- end
-
- def test_firefox_simulation
- @request.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
- get :html_or_xml
- assert_equal 'HTML', @response.body
- end
-
- def test_handle_any
- @request.accept = "*/*"
- get :handle_any
- assert_equal 'HTML', @response.body
-
- @request.accept = "text/javascript"
- get :handle_any
- assert_equal 'Either JS or XML', @response.body
-
- @request.accept = "text/xml"
- get :handle_any
- assert_equal 'Either JS or XML', @response.body
- end
-
- def test_handle_any_any
- @request.accept = "*/*"
- get :handle_any_any
- assert_equal 'HTML', @response.body
- end
-
- def test_handle_any_any_parameter_format
- get :handle_any_any, {:format=>'html'}
- assert_equal 'HTML', @response.body
- end
-
- def test_handle_any_any_explicit_html
- @request.accept = "text/html"
- get :handle_any_any
- assert_equal 'HTML', @response.body
- end
-
- def test_handle_any_any_javascript
- @request.accept = "text/javascript"
- get :handle_any_any
- assert_equal 'Whatever you ask for, I got it', @response.body
- end
-
- def test_handle_any_any_xml
- @request.accept = "text/xml"
- get :handle_any_any
- assert_equal 'Whatever you ask for, I got it', @response.body
- end
-
- def test_browser_check_with_any_any
- @request.accept = "application/json, application/xml"
- get :json_xml_or_html
- assert_equal 'JSON', @response.body
-
- @request.accept = "application/json, application/xml, */*"
- get :json_xml_or_html
- assert_equal 'HTML', @response.body
- end
-
- def test_html_type_with_layout
- @request.accept = "text/html"
- get :all_types_with_layout
- assert_equal '<html><div id="html">HTML for all_types_with_layout</div></html>', @response.body
- end
-
- def test_xhr
- xhr :get, :js_or_html
- assert_equal 'JS', @response.body
- end
-
- def test_custom_constant
- get :custom_constant_handling, :format => "mobile"
- assert_equal "text/x-mobile", @response.content_type
- assert_equal "Mobile", @response.body
- end
-
- def test_custom_constant_handling_without_block
- get :custom_constant_handling_without_block, :format => "mobile"
- assert_equal "text/x-mobile", @response.content_type
- assert_equal "Mobile", @response.body
- end
-
- def test_forced_format
- get :html_xml_or_rss
- assert_equal "HTML", @response.body
-
- get :html_xml_or_rss, :format => "html"
- assert_equal "HTML", @response.body
-
- get :html_xml_or_rss, :format => "xml"
- assert_equal "XML", @response.body
-
- get :html_xml_or_rss, :format => "rss"
- assert_equal "RSS", @response.body
- end
-
- def test_internally_forced_format
- get :forced_xml
- assert_equal "XML", @response.body
-
- get :forced_xml, :format => "html"
- assert_equal "XML", @response.body
- end
-
- def test_extension_synonyms
- get :html_xml_or_rss, :format => "xhtml"
- assert_equal "HTML", @response.body
- end
-
- def test_render_action_for_html
- @controller.instance_eval do
- def render(*args)
- @action = args.first[:action] unless args.empty?
- @action ||= action_name
-
- response.body = "#{@action} - #{formats}"
- end
- end
-
- get :using_defaults
- assert_equal "using_defaults - #{[:html].to_s}", @response.body
-
- get :using_defaults, :format => "xml"
- assert_equal "using_defaults - #{[:xml].to_s}", @response.body
- end
-
- def test_format_with_custom_response_type
- get :iphone_with_html_response_type
- assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body
-
- get :iphone_with_html_response_type, :format => "iphone"
- assert_equal "text/html", @response.content_type
- assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
- end
-
- def test_format_with_custom_response_type_and_request_headers
- @request.accept = "text/iphone"
- get :iphone_with_html_response_type
- assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
- assert_equal "text/html", @response.content_type
- end
-
- def test_invalid_format
- assert_raises(ActionController::UnknownFormat) do
- get :using_defaults, :format => "invalidformat"
- end
- end
-end
class RespondWithController < ActionController::Base
respond_to :html, :json, :touch
@@ -631,6 +107,20 @@ class RenderJsonRespondWithController < RespondWithController
end
end
+class CsvRespondWithController < ActionController::Base
+ respond_to :csv
+
+ class RespondWithCsv
+ def to_csv
+ "c,s,v"
+ end
+ end
+
+ def index
+ respond_with(RespondWithCsv.new)
+ end
+end
+
class EmptyRespondWithController < ActionController::Base
def index
respond_with(Customer.new("david", 13))
@@ -646,7 +136,6 @@ class RespondWithControllerTest < ActionController::TestCase
Mime::Type.register_alias('text/html', :iphone)
Mime::Type.register_alias('text/html', :touch)
Mime::Type.register('text/x-mobile', :mobile)
- Customer.send(:undef_method, :to_json) if Customer.method_defined?(:to_json)
end
def teardown
@@ -1132,6 +621,23 @@ class RespondWithControllerTest < ActionController::TestCase
RespondWithController.responder = ActionController::Responder
end
+ def test_uses_renderer_if_an_api_behavior
+ ActionController::Renderers.add :csv do |obj, options|
+ send_data obj.to_csv, type: Mime::CSV
+ end
+ @controller = CsvRespondWithController.new
+ get :index, format: 'csv'
+ assert_equal Mime::CSV, @response.content_type
+ assert_equal "c,s,v", @response.body
+ end
+
+ def test_raises_missing_renderer_if_an_api_behavior_with_no_renderer
+ @controller = CsvRespondWithController.new
+ assert_raise ActionController::MissingRenderer do
+ get :index, format: 'csv'
+ end
+ end
+
def test_error_is_raised_if_no_respond_to_is_declared_and_respond_with_is_called
@controller = EmptyRespondWithController.new
@request.accept = "*/*"
@@ -1155,69 +661,6 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
-class AbstractPostController < ActionController::Base
- self.view_paths = File.dirname(__FILE__) + "/../fixtures/post_test/"
-end
-
-# For testing layouts which are set automatically
-class PostController < AbstractPostController
- around_action :with_iphone
-
- def index
- respond_to(:html, :iphone, :js)
- end
-
-protected
-
- def with_iphone
- request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone"
- yield
- end
-end
-
-class SuperPostController < PostController
-end
-
-class MimeControllerLayoutsTest < ActionController::TestCase
- tests PostController
-
- def setup
- super
- @request.host = "www.example.com"
- Mime::Type.register_alias("text/html", :iphone)
- end
-
- def teardown
- super
- Mime::Type.unregister(:iphone)
- end
-
- def test_missing_layout_renders_properly
- get :index
- assert_equal '<html><div id="html">Hello Firefox</div></html>', @response.body
-
- @request.accept = "text/iphone"
- get :index
- assert_equal 'Hello iPhone', @response.body
- end
-
- def test_format_with_inherited_layouts
- @controller = SuperPostController.new
-
- get :index
- assert_equal '<html><div id="html">Super Firefox</div></html>', @response.body
-
- @request.accept = "text/iphone"
- get :index
- assert_equal '<html><div id="super_iphone">Super iPhone</div></html>', @response.body
- end
-
- def test_non_navigational_format_with_no_template_fallbacks_to_html_template_with_no_layout
- get :index, :format => :js
- assert_equal "Hello Firefox", @response.body
- end
-end
-
class FlashResponder < ActionController::Responder
def initialize(controller, resources, options={})
super
diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb
index 2b36a399bb..f4bdd3e1d4 100644
--- a/actionpack/test/controller/new_base/render_streaming_test.rb
+++ b/actionpack/test/controller/new_base/render_streaming_test.rb
@@ -92,7 +92,7 @@ module RenderStreaming
io.rewind
assert_match "(undefined method `invalid!' for nil:NilClass)", io.read
ensure
- ActionView::Base.logger = _old
+ ActionController::Base.logger = _old
end
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 98b34a872b..fd835795c0 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -775,10 +775,6 @@ class RenderTest < ActionController::TestCase
@request.host = "www.nextangle.com"
end
- def teardown
- ActionView::Base.logger = nil
- end
-
# :ported:
def test_simple_show
get :hello_world
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index cdac074973..87cb568300 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -5,7 +5,7 @@ Gem::Specification.new do |s|
s.name = 'actionview'
s.version = version
s.summary = 'Rendering framework putting the V in MVC (part of Rails).'
- s.description = 'Simple, battle-tested conventions and helpers for building web pages.'
+ s.description = ''
s.required_ruby_version = '>= 1.9.3'
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index f0347081ee..e4f5e61e91 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -155,12 +155,6 @@ class JsonSerializationTest < ActiveModel::TestCase
end
end
- test "as_json should keep the default order in the hash" do
- json = @contact.as_json
-
- assert_equal %w(name age created_at awesome preferences), json.keys
- end
-
test "from_json should work without a root (class attribute)" do
json = @contact.to_json
result = Contact.new.from_json(json)
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 3010f42cd6..98d2fa62d7 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,51 @@
+* Stop interpreting SQL 'string' columns as :string type because there is no
+ common STRING datatype in SQL.
+
+ *Ben Woosley*
+
+* `ActiveRecord::FinderMethods#exists?` returns `true`/`false` in all cases.
+
+ *Xavier Noria*
+
+* Assign inet/cidr attribute with `nil` value for invalid address.
+
+ Example:
+
+ record = User.new
+
+ record.logged_in_from_ip # is type of an inet or a cidr
+
+ # Before:
+ record.logged_in_from_ip = 'bad ip address' # raise exception
+
+ # After:
+ record.logged_in_from_ip = 'bad ip address' # do not raise exception
+ record.logged_in_from_ip # => nil
+ record.logged_in_from_ip_before_type_cast # => 'bad ip address'
+
+ *Paul Nikitochkin*
+
+* `add_to_target` now accepts a second optional `skip_callbacks` argument
+
+ If truthy, it will skip the :before_add and :after_add callbacks.
+
+ *Ben Woosley*
+
+* Fix interactions between `:before_add` callbacks and nested attributes
+ assignment of `has_many` associations, when the association was not
+ yet loaded:
+
+ - A `:before_add` callback was being called when a nested attributes
+ assignment assigned to an existing record.
+
+ - Nested Attributes assignment did not affect the record in the
+ association target when a `:before_add` callback triggered the
+ loading of the association
+
+ *Jörg Schray*
+
* Allow enable_extension migration method to be revertible.
-
+
*Eric Tipton*
* Type cast hstore values on write, so that the value is consistent
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 8ce02afef8..228c500f0a 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -290,7 +290,7 @@ module ActiveRecord
# Returns true if the collection is empty.
#
- # If the collection has been loaded
+ # If the collection has been loaded
# it is equivalent to <tt>collection.size.zero?</tt>. If the
# collection has not been loaded, it is equivalent to
# <tt>collection.exists?</tt>. If the collection has not already been
@@ -366,8 +366,8 @@ module ActiveRecord
target
end
- def add_to_target(record)
- callback(:before_add, record)
+ def add_to_target(record, skip_callbacks = false)
+ callback(:before_add, record) unless skip_callbacks
yield(record) if block_given?
if association_scope.distinct_value && index = @target.index(record)
@@ -376,7 +376,7 @@ module ActiveRecord
@target << record
end
- callback(:after_add, record)
+ callback(:after_add, record) unless skip_callbacks
set_inverse_instance(record)
record
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 97e1bc5379..32244b1755 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -39,8 +39,8 @@ module ActiveRecord
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
def select_values(arel, name = nil)
- result = select_rows(to_sql(arel, []), name)
- result.map { |v| v[0] }
+ select_rows(to_sql(arel, []), name)
+ .map { |v| v[0] }
end
# Returns an array of arrays containing the field values.
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index bccfa41ad1..fb53090edc 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -272,7 +272,7 @@ module ActiveRecord
:text
when /blob/i, /binary/i
:binary
- when /char/i, /string/i
+ when /char/i
:string
when /boolean/i
:boolean
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index 56dc9ea813..ef7b976d4f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -100,7 +100,11 @@ module ActiveRecord
if string.nil?
nil
elsif String === string
- IPAddr.new(string)
+ begin
+ IPAddr.new(string)
+ rescue ArgumentError
+ nil
+ end
else
string
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index e53e8553ad..df28451bb7 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -465,19 +465,17 @@ module ActiveRecord
association.build(attributes.except(*UNASSIGNABLE_KEYS))
end
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
- unless association.loaded? || call_reject_if(association_name, attributes)
+ unless call_reject_if(association_name, attributes)
# Make sure we are operating on the actual object which is in the association's
# proxy_target array (either by finding it, or adding it if not found)
- target_record = association.target.detect { |record| record == existing_record }
-
+ # Take into account that the proxy_target may have changed due to callbacks
+ target_record = association.target.detect { |record| record.id.to_s == attributes['id'].to_s }
if target_record
existing_record = target_record
else
- association.add_to_target(existing_record)
+ association.add_to_target(existing_record, :skip_callbacks)
end
- end
- if !call_reject_if(association_name, attributes)
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
end
else
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 73d154e03e..f428f160cf 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -394,7 +394,7 @@ module ActiveRecord
# returns either nil or the inverse association name that it finds.
def automatic_inverse_of
if can_find_inverse_of_automatically?(self)
- inverse_name = active_record.name.downcase.to_sym
+ inverse_name = ActiveSupport::Inflector.underscore(active_record.name).to_sym
begin
reflection = klass.reflect_on_association(inverse_name)
@@ -413,7 +413,7 @@ module ActiveRecord
end
# Checks if the inverse reflection that is returned from the
- # +set_automatic_inverse_of+ method is a valid reflection. We must
+ # +automatic_inverse_of+ method is a valid reflection. We must
# make sure that the reflection's active_record name matches up
# with the current reflection's klass name.
#
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 2d3bd563ac..0132a02f83 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -171,21 +171,21 @@ module ActiveRecord
last or raise RecordNotFound
end
- # Returns truthy if a record exists in the table that matches the +id+ or
- # conditions given, or falsy otherwise. The argument can take six forms:
+ # Returns +true+ if a record exists in the table that matches the +id+ or
+ # conditions given, or +false+ otherwise. The argument can take six forms:
#
# * Integer - Finds the record with this primary key.
# * String - Finds the record with a primary key corresponding to this
# string (such as <tt>'5'</tt>).
# * Array - Finds the record that matches these +find+-style conditions
- # (such as <tt>['color = ?', 'red']</tt>).
+ # (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
# * Hash - Finds the record that matches these +find+-style conditions
- # (such as <tt>{color: 'red'}</tt>).
+ # (such as <tt>{name: 'David'}</tt>).
# * +false+ - Returns always +false+.
# * No args - Returns +false+ if the table is empty, +true+ otherwise.
#
- # For more information about specifying conditions as a Hash or Array,
- # see the Conditions section in the introduction to ActiveRecord::Base.
+ # For more information about specifying conditions as a hash or array,
+ # see the Conditions section in the introduction to <tt>ActiveRecord::Base</tt>.
#
# Note: You can't pass in a condition as a string (like <tt>name =
# 'Jamie'</tt>), since it would be sanitized and then queried against
@@ -213,7 +213,7 @@ module ActiveRecord
relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
end
- connection.select_value(relation.arel, "#{name} Exists", relation.bind_values)
+ connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
end
# This method is called whenever no records are found with either a single
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 489efac932..b8dd35c4c5 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -66,7 +66,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
def test_write_value
data = "\u001F"
record = ByteaDataType.create(payload: data)
- refute record.new_record?
+ assert_not record.new_record?
assert_equal(data, record.payload)
end
@@ -74,14 +74,14 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log'))
assert(data.size > 1)
record = ByteaDataType.create(payload: data)
- refute record.new_record?
+ assert_not record.new_record?
assert_equal(data, record.payload)
assert_equal(data, ByteaDataType.where(id: record.id).first.payload)
end
def test_write_nil
record = ByteaDataType.create(payload: nil)
- refute record.new_record?
+ assert_not record.new_record?
assert_equal(nil, record.payload)
assert_equal(nil, ByteaDataType.where(id: record.id).first.payload)
end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index e946b8bb22..75b6f4f8ce 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -311,11 +311,11 @@ _SQL
def test_update_tstzrange
skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges?
new_tstzrange = Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET')
- assert @first_range.tstz_range = new_tstzrange
+ @first_range.tstz_range = new_tstzrange
assert @first_range.save
assert @first_range.reload
assert_equal new_tstzrange, @first_range.tstz_range
- assert @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000')
+ @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000')
assert @first_range.save
assert @first_range.reload
assert_nil @first_range.tstz_range
@@ -335,11 +335,11 @@ _SQL
skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges?
tz = ::ActiveRecord::Base.default_timezone
new_tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0)
- assert @first_range.ts_range = new_tsrange
+ @first_range.ts_range = new_tsrange
assert @first_range.save
assert @first_range.reload
assert_equal new_tsrange, @first_range.ts_range
- assert @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0)
+ @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0)
assert @first_range.save
assert @first_range.reload
assert_nil @first_range.ts_range
@@ -357,11 +357,11 @@ _SQL
def test_update_numrange
skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges?
new_numrange = BigDecimal.new('0.5')...BigDecimal.new('1')
- assert @first_range.num_range = new_numrange
+ @first_range.num_range = new_numrange
assert @first_range.save
assert @first_range.reload
assert_equal new_numrange, @first_range.num_range
- assert @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5')
+ @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5')
assert @first_range.save
assert @first_range.reload
assert_nil @first_range.num_range
@@ -379,11 +379,11 @@ _SQL
def test_update_daterange
skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges?
new_daterange = Date.new(2012, 2, 3)...Date.new(2012, 2, 10)
- assert @first_range.date_range = new_daterange
+ @first_range.date_range = new_daterange
assert @first_range.save
assert @first_range.reload
assert_equal new_daterange, @first_range.date_range
- assert @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3)
+ @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3)
assert @first_range.save
assert @first_range.reload
assert_nil @first_range.date_range
@@ -401,11 +401,11 @@ _SQL
def test_update_int4range
skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges?
new_int4range = 6...10
- assert @first_range.int4_range = new_int4range
+ @first_range.int4_range = new_int4range
assert @first_range.save
assert @first_range.reload
assert_equal new_int4range, @first_range.int4_range
- assert @first_range.int4_range = 3...3
+ @first_range.int4_range = 3...3
assert @first_range.save
assert @first_range.reload
assert_nil @first_range.int4_range
@@ -423,11 +423,11 @@ _SQL
def test_update_int8range
skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges?
new_int8range = 60000...10000000
- assert @first_range.int8_range = new_int8range
+ @first_range.int8_range = new_int8range
assert @first_range.save
assert @first_range.reload
assert_equal new_int8range, @first_range.int8_range
- assert @first_range.int8_range = 39999...39999
+ @first_range.int8_range = 39999...39999
assert @first_range.save
assert @first_range.reload
assert_nil @first_range.int8_range
@@ -435,10 +435,10 @@ _SQL
def test_update_tsvector
new_text_vector = "'new' 'text' 'vector'"
- assert @first_tsvector.text_vector = new_text_vector
+ @first_tsvector.text_vector = new_text_vector
assert @first_tsvector.save
assert @first_tsvector.reload
- assert @first_tsvector.text_vector = new_text_vector
+ @first_tsvector.text_vector = new_text_vector
assert @first_tsvector.save
assert @first_tsvector.reload
assert_equal new_text_vector, @first_tsvector.text_vector
@@ -479,11 +479,11 @@ _SQL
def test_update_integer_array
new_value = [32800,95000,29350,17000]
- assert @first_array.commission_by_quarter = new_value
+ @first_array.commission_by_quarter = new_value
assert @first_array.save
assert @first_array.reload
assert_equal new_value, @first_array.commission_by_quarter
- assert @first_array.commission_by_quarter = new_value
+ @first_array.commission_by_quarter = new_value
assert @first_array.save
assert @first_array.reload
assert_equal new_value, @first_array.commission_by_quarter
@@ -491,11 +491,11 @@ _SQL
def test_update_text_array
new_value = ['robby','robert','rob','robbie']
- assert @first_array.nicknames = new_value
+ @first_array.nicknames = new_value
assert @first_array.save
assert @first_array.reload
assert_equal new_value, @first_array.nicknames
- assert @first_array.nicknames = new_value
+ @first_array.nicknames = new_value
assert @first_array.save
assert @first_array.reload
assert_equal new_value, @first_array.nicknames
@@ -503,7 +503,7 @@ _SQL
def test_update_money
new_value = BigDecimal.new('123.45')
- assert @first_money.wealth = new_value
+ @first_money.wealth = new_value
assert @first_money.save
assert @first_money.reload
assert_equal new_value, @first_money.wealth
@@ -512,8 +512,8 @@ _SQL
def test_update_number
new_single = 789.012
new_double = 789012.345
- assert @first_number.single = new_single
- assert @first_number.double = new_double
+ @first_number.single = new_single
+ @first_number.double = new_double
assert @first_number.save
assert @first_number.reload
assert_equal new_single, @first_number.single
@@ -521,7 +521,7 @@ _SQL
end
def test_update_time
- assert @first_time.time_interval = '2 years 3 minutes'
+ @first_time.time_interval = '2 years 3 minutes'
assert @first_time.save
assert @first_time.reload
assert_equal '2 years 00:03:00', @first_time.time_interval
@@ -531,9 +531,9 @@ _SQL
new_inet_address = '10.1.2.3/32'
new_cidr_address = '10.0.0.0/8'
new_mac_address = 'bc:de:f0:12:34:56'
- assert @first_network_address.cidr_address = new_cidr_address
- assert @first_network_address.inet_address = new_inet_address
- assert @first_network_address.mac_address = new_mac_address
+ @first_network_address.cidr_address = new_cidr_address
+ @first_network_address.inet_address = new_inet_address
+ @first_network_address.mac_address = new_mac_address
assert @first_network_address.save
assert @first_network_address.reload
assert_equal @first_network_address.cidr_address, new_cidr_address
@@ -544,8 +544,8 @@ _SQL
def test_update_bit_string
new_bit_string = '11111111'
new_bit_string_varying = '0xFF'
- assert @first_bit_string.bit_string = new_bit_string
- assert @first_bit_string.bit_string_varying = new_bit_string_varying
+ @first_bit_string.bit_string = new_bit_string
+ @first_bit_string.bit_string_varying = new_bit_string_varying
assert @first_bit_string.save
assert @first_bit_string.reload
assert_equal new_bit_string, @first_bit_string.bit_string
@@ -558,9 +558,23 @@ _SQL
assert_raise(ActiveRecord::StatementInvalid) { assert @first_bit_string.save }
end
+ def test_invalid_network_address
+ @first_network_address.cidr_address = 'invalid addr'
+ assert_nil @first_network_address.cidr_address
+ assert_equal 'invalid addr', @first_network_address.cidr_address_before_type_cast
+ assert @first_network_address.save
+
+ @first_network_address.reload
+
+ @first_network_address.inet_address = 'invalid addr'
+ assert_nil @first_network_address.inet_address
+ assert_equal 'invalid addr', @first_network_address.inet_address_before_type_cast
+ assert @first_network_address.save
+ end
+
def test_update_oid
new_value = 567890
- assert @first_oid.obj_id = new_value
+ @first_oid.obj_id = new_value
assert @first_oid.save
assert @first_oid.reload
assert_equal new_value, @first_oid.obj_id
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index b3429648ee..a534f0e56a 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -47,7 +47,7 @@ module ActiveRecord
def test_quote_cast_numeric
fixnum = 666
- c = Column.new(nil, nil, 'string')
+ c = Column.new(nil, nil, 'varchar')
assert_equal "'666'", @conn.quote(fixnum, c)
c = Column.new(nil, nil, 'text')
assert_equal "'666'", @conn.quote(fixnum, c)
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 724d1cbbf8..941e851aae 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -654,7 +654,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
sarah = Person.create!(:first_name => 'Sarah', :primary_contact_id => people(:susan).id, :gender => 'F', :number1_fan_id => 1)
john = Person.create!(:first_name => 'John', :primary_contact_id => sarah.id, :gender => 'M', :number1_fan_id => 1)
assert_equal sarah.agents, [john]
- assert_equal people(:susan).agents.flat_map(&:agents), people(:susan).agents_of_agents
+ assert_equal people(:susan).agents.map(&:agents).flatten, people(:susan).agents_of_agents
end
def test_associate_existing_with_nonstandard_primary_key_on_belongs_to
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 71cf1237e8..2477e60e51 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -9,10 +9,24 @@ require 'models/rating'
require 'models/comment'
require 'models/car'
require 'models/bulb'
+require 'models/mixed_case_monkey'
class AutomaticInverseFindingTests < ActiveRecord::TestCase
fixtures :ratings, :comments, :cars
+ def test_has_one_and_belongs_to_should_find_inverse_automatically_on_multiple_word_name
+ monkey_reflection = MixedCaseMonkey.reflect_on_association(:man)
+ man_reflection = Man.reflect_on_association(:mixed_case_monkey)
+
+ assert_respond_to monkey_reflection, :has_inverse?
+ assert monkey_reflection.has_inverse?, "The monkey reflection should have an inverse"
+ assert_equal man_reflection, monkey_reflection.inverse_of, "The monkey reflection's inverse should be the man reflection"
+
+ assert_respond_to man_reflection, :has_inverse?
+ assert man_reflection.has_inverse?, "The man reflection should have an inverse"
+ assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection"
+ end
+
def test_has_one_and_belongs_to_should_find_inverse_automatically
car_reflection = Car.reflect_on_association(:bulb)
bulb_reflection = Bulb.reflect_on_association(:car)
@@ -406,7 +420,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
interest = Interest.create!(man: man)
man.interests.find(interest.id)
- refute man.interests.loaded?
+ assert_not man.interests.loaded?
end
def test_raise_record_not_found_error_when_invalid_ids_are_passed
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 51a8a13d04..949e994f8d 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -45,17 +45,18 @@ class FinderTest < ActiveRecord::TestCase
end
def test_exists
- assert Topic.exists?(1)
- assert Topic.exists?("1")
- assert Topic.exists?(title: "The First Topic")
- assert Topic.exists?(heading: "The First Topic")
- assert Topic.exists?(:author_name => "Mary", :approved => true)
- assert Topic.exists?(["parent_id = ?", 1])
- assert !Topic.exists?(45)
- assert !Topic.exists?(Topic.new)
+ assert_equal true, Topic.exists?(1)
+ assert_equal true, Topic.exists?("1")
+ assert_equal true, Topic.exists?(title: "The First Topic")
+ assert_equal true, Topic.exists?(heading: "The First Topic")
+ assert_equal true, Topic.exists?(:author_name => "Mary", :approved => true)
+ assert_equal true, Topic.exists?(["parent_id = ?", 1])
+
+ assert_equal false, Topic.exists?(45)
+ assert_equal false, Topic.exists?(Topic.new)
begin
- assert !Topic.exists?("foo")
+ assert_equal false, Topic.exists?("foo")
rescue ActiveRecord::StatementInvalid
# PostgreSQL complains about string comparison with integer field
rescue Exception
@@ -72,61 +73,62 @@ class FinderTest < ActiveRecord::TestCase
end
def test_exists_returns_true_with_one_record_and_no_args
- assert Topic.exists?
+ assert_equal true, Topic.exists?
end
def test_exists_returns_false_with_false_arg
- assert !Topic.exists?(false)
+ assert_equal false, Topic.exists?(false)
end
# exists? should handle nil for id's that come from URLs and always return false
# (example: Topic.exists?(params[:id])) where params[:id] is nil
def test_exists_with_nil_arg
- assert !Topic.exists?(nil)
- assert Topic.exists?
- assert !Topic.first.replies.exists?(nil)
- assert Topic.first.replies.exists?
+ assert_equal false, Topic.exists?(nil)
+ assert_equal true, Topic.exists?
+
+ assert_equal false, Topic.first.replies.exists?(nil)
+ assert_equal true, Topic.first.replies.exists?
end
# ensures +exists?+ runs valid SQL by excluding order value
def test_exists_with_order
- assert Topic.order(:id).distinct.exists?
+ assert_equal true, Topic.order(:id).distinct.exists?
end
def test_exists_with_includes_limit_and_empty_result
- assert !Topic.includes(:replies).limit(0).exists?
- assert !Topic.includes(:replies).limit(1).where('0 = 1').exists?
+ assert_equal false, Topic.includes(:replies).limit(0).exists?
+ assert_equal false, Topic.includes(:replies).limit(1).where('0 = 1').exists?
end
def test_exists_with_distinct_association_includes_and_limit
author = Author.first
- assert !author.unique_categorized_posts.includes(:special_comments).limit(0).exists?
- assert author.unique_categorized_posts.includes(:special_comments).limit(1).exists?
+ assert_equal false, author.unique_categorized_posts.includes(:special_comments).limit(0).exists?
+ assert_equal true, author.unique_categorized_posts.includes(:special_comments).limit(1).exists?
end
def test_exists_with_distinct_association_includes_limit_and_order
author = Author.first
- assert !author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(0).exists?
- assert author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(1).exists?
+ assert_equal false, author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(0).exists?
+ assert_equal true, author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(1).exists?
end
def test_exists_with_empty_table_and_no_args_given
Topic.delete_all
- assert !Topic.exists?
+ assert_equal false, Topic.exists?
end
def test_exists_with_aggregate_having_three_mappings
existing_address = customers(:david).address
- assert Customer.exists?(:address => existing_address)
+ assert_equal true, Customer.exists?(:address => existing_address)
end
def test_exists_with_aggregate_having_three_mappings_with_one_difference
existing_address = customers(:david).address
- assert !Customer.exists?(:address =>
+ assert_equal false, Customer.exists?(:address =>
Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
- assert !Customer.exists?(:address =>
+ assert_equal false, Customer.exists?(:address =>
Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
- assert !Customer.exists?(:address =>
+ assert_equal false, Customer.exists?(:address =>
Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
end
diff --git a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
new file mode 100644
index 0000000000..43a69928b6
--- /dev/null
+++ b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
@@ -0,0 +1,144 @@
+require "cases/helper"
+require "models/pirate"
+require "models/bird"
+
+class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
+ Pirate.has_many(:birds_with_add_load,
+ :class_name => "Bird",
+ :before_add => proc { |p,b|
+ @@add_callback_called << b
+ p.birds_with_add_load.to_a
+ })
+ Pirate.has_many(:birds_with_add,
+ :class_name => "Bird",
+ :before_add => proc { |p,b| @@add_callback_called << b })
+
+ Pirate.accepts_nested_attributes_for(:birds_with_add_load,
+ :birds_with_add,
+ :allow_destroy => true)
+
+ def setup
+ @@add_callback_called = []
+ @pirate = Pirate.new.tap do |pirate|
+ pirate.catchphrase = "Don't call me!"
+ pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}]
+ pirate.save!
+ end
+ @birds = @pirate.birds.to_a
+ end
+
+ def bird_to_update
+ @birds[0]
+ end
+
+ def bird_to_destroy
+ @birds[1]
+ end
+
+ def existing_birds_attributes
+ @birds.map do |bird|
+ bird.attributes.slice("id","name")
+ end
+ end
+
+ def new_birds
+ @pirate.birds_with_add.to_a - @birds
+ end
+
+ def new_bird_attributes
+ [{'name' => "New Bird"}]
+ end
+
+ def destroy_bird_attributes
+ [{'id' => bird_to_destroy.id.to_s, "_destroy" => true}]
+ end
+
+ def update_new_and_destroy_bird_attributes
+ [{'id' => @birds[0].id.to_s, 'name' => 'New Name'},
+ {'name' => "New Bird"},
+ {'id' => bird_to_destroy.id.to_s, "_destroy" => true}]
+ end
+
+ # Characterizing when :before_add callback is called
+ test ":before_add called for new bird when not loaded" do
+ assert_not @pirate.birds_with_add.loaded?
+ @pirate.birds_with_add_attributes = new_bird_attributes
+ assert_new_bird_with_callback_called
+ end
+
+ test ":before_add called for new bird when loaded" do
+ @pirate.birds_with_add.load_target
+ @pirate.birds_with_add_attributes = new_bird_attributes
+ assert_new_bird_with_callback_called
+ end
+
+ def assert_new_bird_with_callback_called
+ assert_equal(1, new_birds.size)
+ assert_equal(new_birds, @@add_callback_called)
+ end
+
+ test ":before_add not called for identical assignment when not loaded" do
+ assert_not @pirate.birds_with_add.loaded?
+ @pirate.birds_with_add_attributes = existing_birds_attributes
+ assert_callbacks_not_called
+ end
+
+ test ":before_add not called for identical assignment when loaded" do
+ @pirate.birds_with_add.load_target
+ @pirate.birds_with_add_attributes = existing_birds_attributes
+ assert_callbacks_not_called
+ end
+
+ test ":before_add not called for destroy assignment when not loaded" do
+ assert_not @pirate.birds_with_add.loaded?
+ @pirate.birds_with_add_attributes = destroy_bird_attributes
+ assert_callbacks_not_called
+ end
+
+ test ":before_add not called for deletion assignment when loaded" do
+ @pirate.birds_with_add.load_target
+ @pirate.birds_with_add_attributes = destroy_bird_attributes
+ assert_callbacks_not_called
+ end
+
+ def assert_callbacks_not_called
+ assert_empty new_birds
+ assert_empty @@add_callback_called
+ end
+
+ # Ensuring that the records in the association target are updated,
+ # whether the association is loaded before or not
+ test "Assignment updates records in target when not loaded" do
+ assert_not @pirate.birds_with_add.loaded?
+ @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes
+ assert_assignment_affects_records_in_target(:birds_with_add)
+ end
+
+ test "Assignment updates records in target when loaded" do
+ @pirate.birds_with_add.load_target
+ @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes
+ assert_assignment_affects_records_in_target(:birds_with_add)
+ end
+
+ test("Assignment updates records in target when not loaded" +
+ " and callback loads target") do
+ assert_not @pirate.birds_with_add_load.loaded?
+ @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes
+ assert_assignment_affects_records_in_target(:birds_with_add_load)
+ end
+
+ test("Assignment updates records in target when loaded" +
+ " and callback loads target") do
+ @pirate.birds_with_add_load.load_target
+ @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes
+ assert_assignment_affects_records_in_target(:birds_with_add_load)
+ end
+
+ def assert_assignment_affects_records_in_target(association_name)
+ association = @pirate.send(association_name)
+ assert association.detect {|b| b == bird_to_update }.name_changed?,
+ 'Update record not updated'
+ assert association.detect {|b| b == bird_to_destroy }.marked_for_destruction?,
+ 'Destroy record not marked for destruction'
+ end
+end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index bf9d395b2d..e1a760d240 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -170,6 +170,10 @@ class RelationTest < ActiveRecord::TestCase
assert_equal topics(:fourth).title, topics.first.title
end
+ def test_order_with_hash_and_symbol_generates_the_same_sql
+ assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql
+ end
+
def test_raising_exception_on_invalid_hash_params
assert_raise(ArgumentError) { Topic.order(:name, "id DESC", :id => :DeSc) }
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index abfc90474c..a5cb22aaf6 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -453,11 +453,6 @@ class TransactionTest < ActiveRecord::TestCase
raise ActiveRecord::Rollback
end
end
-
- ensure
- Topic.reset_column_information # reset the column information to get correct reading
- Topic.connection.remove_column('topics', 'stuff') if Topic.column_names.include?('stuff')
- Topic.reset_column_information # reset the column information again for other tests
end
def test_transactions_state_from_rollback
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 7dad8041f3..feb828de31 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -8,7 +8,9 @@ class Author < ActiveRecord::Base
has_many :posts_sorted_by_id_limited, -> { order('posts.id').limit(1) }, :class_name => "Post"
has_many :posts_with_categories, -> { includes(:categories) }, :class_name => "Post"
has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, :class_name => "Post"
+ has_many :posts_containing_the_letter_a, :class_name => "Post"
has_many :posts_with_special_categorizations, :class_name => 'PostWithSpecialCategorization'
+ has_many :posts_with_extension, :class_name => "Post"
has_one :post_about_thinking, -> { where("posts.title like '%thinking%'") }, :class_name => 'Post'
has_one :post_about_thinking_with_last_comment, -> { where("posts.title like '%thinking%'").includes(:last_comment) }, :class_name => 'Post'
has_many :comments, through: :posts do
@@ -30,6 +32,7 @@ class Author < ActiveRecord::Base
has_many :welcome_posts, -> { where(:title => 'Welcome to the weblog') }, :class_name => 'Post'
has_many :comments_desc, -> { order('comments.id DESC') }, :through => :posts, :source => :comments
+ has_many :limited_comments, -> { limit(1) }, :through => :posts, :source => :comments
has_many :funky_comments, :through => :posts, :source => :comments
has_many :ordered_uniq_comments, -> { distinct.order('comments.id') }, :through => :posts, :source => :comments
has_many :ordered_uniq_comments_desc, -> { distinct.order('comments.id DESC') }, :through => :posts, :source => :comments
diff --git a/activerecord/test/models/auto_id.rb b/activerecord/test/models/auto_id.rb
index 82c6544bd5..d720e2be5e 100644
--- a/activerecord/test/models/auto_id.rb
+++ b/activerecord/test/models/auto_id.rb
@@ -1,4 +1,4 @@
class AutoId < ActiveRecord::Base
- self.table_name = "auto_id_tests"
- self.primary_key = "auto_id"
+ def self.table_name () "auto_id_tests" end
+ def self.primary_key () "auto_id" end
end
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index 6d257dbe7e..a14a9febba 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -1,9 +1,12 @@
class Car < ActiveRecord::Base
+
has_many :bulbs
has_many :funky_bulbs, class_name: 'FunkyBulb', dependent: :destroy
has_many :foo_bulbs, -> { where(:name => 'foo') }, :class_name => "Bulb"
+ has_many :frickinawesome_bulbs, -> { where :frickinawesome => true }, :class_name => "Bulb"
has_one :bulb
+ has_one :frickinawesome_bulb, -> { where :frickinawesome => true }, :class_name => "Bulb"
has_many :tyres
has_many :engines, :dependent => :destroy
diff --git a/activerecord/test/models/citation.rb b/activerecord/test/models/citation.rb
index 3d87eb795c..545aa8110d 100644
--- a/activerecord/test/models/citation.rb
+++ b/activerecord/test/models/citation.rb
@@ -1,3 +1,6 @@
class Citation < ActiveRecord::Base
belongs_to :reference_of, :class_name => "Book", :foreign_key => :book2_id
+
+ belongs_to :book1, :class_name => "Book", :foreign_key => :book1_id
+ belongs_to :book2, :class_name => "Book", :foreign_key => :book2_id
end
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index 566e0873f1..816c5e6937 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -2,6 +2,7 @@ class Club < ActiveRecord::Base
has_one :membership
has_many :memberships, :inverse_of => false
has_many :members, :through => :memberships
+ has_many :current_memberships
has_one :sponsor
has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member"
belongs_to :category
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 8104c607b5..c5d4ec0833 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -49,6 +49,7 @@ class Firm < Company
has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, :class_name => "Client"
has_many :clients_like_ms_with_hash_conditions, -> { where(:name => 'Microsoft').order("id") }, :class_name => "Client"
has_many :plain_clients, :class_name => 'Client'
+ has_many :readonly_clients, -> { readonly }, :class_name => 'Client'
has_many :clients_using_primary_key, :class_name => 'Client',
:primary_key => 'name', :foreign_key => 'firm_name'
has_many :clients_using_primary_key_with_delete_all, :class_name => 'Client',
@@ -166,6 +167,7 @@ class ExclusivelyDependentFirm < Company
has_one :account, :foreign_key => "firm_id", :dependent => :delete
has_many :dependent_sanitized_conditional_clients_of_firm, -> { order("id").where("name = 'BigShot Inc.'") }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all
has_many :dependent_conditional_clients_of_firm, -> { order("id").where("name = ?", 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all
+ has_many :dependent_hash_conditional_clients_of_firm, -> { order("id").where(:name => 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all
end
class SpecialClient < Client
diff --git a/activerecord/test/models/man.rb b/activerecord/test/models/man.rb
index 4bff92dc98..f4d127730c 100644
--- a/activerecord/test/models/man.rb
+++ b/activerecord/test/models/man.rb
@@ -6,4 +6,5 @@ class Man < ActiveRecord::Base
# These are "broken" inverse_of associations for the purposes of testing
has_one :dirty_face, :class_name => 'Face', :inverse_of => :dirty_man
has_many :secret_interests, :class_name => 'Interest', :inverse_of => :secret_man
+ has_one :mixed_case_monkey
end
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index 72095f9236..cc47c7bc18 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -2,6 +2,7 @@ class Member < ActiveRecord::Base
has_one :current_membership
has_one :selected_membership
has_one :membership
+ has_many :fellow_members, :through => :club, :source => :members
has_one :club, :through => :current_membership
has_one :selected_club, :through => :selected_membership, :source => :club
has_one :favourite_club, -> { where "memberships.favourite = ?", true }, :through => :membership, :source => :club
diff --git a/activerecord/test/models/mixed_case_monkey.rb b/activerecord/test/models/mixed_case_monkey.rb
index 763baefd91..4d37371777 100644
--- a/activerecord/test/models/mixed_case_monkey.rb
+++ b/activerecord/test/models/mixed_case_monkey.rb
@@ -1,3 +1,5 @@
class MixedCaseMonkey < ActiveRecord::Base
self.primary_key = 'monkeyID'
+
+ belongs_to :man
end
diff --git a/activerecord/test/models/movie.rb b/activerecord/test/models/movie.rb
index c441be2bef..6384b4c801 100644
--- a/activerecord/test/models/movie.rb
+++ b/activerecord/test/models/movie.rb
@@ -1,3 +1,5 @@
class Movie < ActiveRecord::Base
- self.primary_key = "movieid"
+ def self.primary_key
+ "movieid"
+ end
end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 77564ffad4..93a7a2073c 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -122,6 +122,7 @@ class Post < ActiveRecord::Base
has_many :secure_readers
has_many :readers_with_person, -> { includes(:person) }, :class_name => "Reader"
has_many :people, :through => :readers
+ has_many :secure_people, :through => :secure_readers
has_many :single_people, :through => :readers
has_many :people_with_callbacks, :source=>:person, :through => :readers,
:before_add => lambda {|owner, reader| log(:added, :before, reader.first_name) },
diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb
index 7f42a4b1f8..c094b726b4 100644
--- a/activerecord/test/models/project.rb
+++ b/activerecord/test/models/project.rb
@@ -1,6 +1,7 @@
class Project < ActiveRecord::Base
has_and_belongs_to_many :developers, -> { distinct.order 'developers.name desc, developers.id desc' }
has_and_belongs_to_many :readonly_developers, -> { readonly }, :class_name => "Developer"
+ has_and_belongs_to_many :selected_developers, -> { distinct.select "developers.*" }, :class_name => "Developer"
has_and_belongs_to_many :non_unique_developers, -> { order 'developers.name desc, developers.id desc' }, :class_name => 'Developer'
has_and_belongs_to_many :limited_developers, -> { limit 1 }, :class_name => "Developer"
has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, :class_name => "Developer"
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 40c8e97fc2..17035bf338 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -34,6 +34,7 @@ class Topic < ActiveRecord::Base
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count'
+ has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title"
has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index be97b744c8..81c6246c5c 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,19 @@
+* Add String#remove(pattern) as a short-hand for the common pattern of String#gsub(pattern, '')
+
+ *DHH*
+
+* Adds a new deprecation behaviour that raises an exception. Throwing this
+ line into +config/environments/development.rb+
+
+ ActiveSupport::Deprecation.behavior = :raise
+
+ will cause the application to raise an +ActiveSupport::DeprecationException+
+ on deprecations.
+
+ Use this for aggressive deprecation cleanups.
+
+ *Xavier Noria*
+
* Remove 'cow' => 'kine' irregular inflection from default inflections.
*Andrew White*
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index af048d0c85..c60e833441 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -129,7 +129,7 @@ class Date
options.fetch(:day, day)
)
end
-
+
# Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there.
def compare_with_coercion(other)
if other.is_a?(Time)
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index fbf2877117..8930376ac8 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -235,6 +235,7 @@ module ActiveSupport
value.map! { |i| deep_to_h(i) }
value.length > 1 ? value : value.first
end
+
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 48190e1e66..534bbe3c42 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -47,7 +47,7 @@ class Object
end
# Same as #try, but will raise a NoMethodError exception if the receiving is not nil and
- # does not implement the tried method.
+ # does not implemented the tried method.
def try!(*a, &b)
if a.empty? && block_given?
yield self
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index a4cacaf789..49c0df6026 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -20,6 +20,16 @@ class String
self
end
+ # Returns a new string with all occurrences of the pattern removed. Short-hand for String#gsub(pattern, '').
+ def remove(pattern)
+ gsub pattern, ''
+ end
+
+ # Alters the string by removing all occurrences of the pattern. Short-hand for String#gsub!(pattern, '').
+ def remove!(pattern)
+ gsub! pattern, ''
+ end
+
# Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
#
# 'Once upon a time in a world far far away'.truncate(27)
diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb
index 5481766f10..e80f442973 100644
--- a/activesupport/lib/active_support/core_ext/thread.rb
+++ b/activesupport/lib/active_support/core_ext/thread.rb
@@ -23,14 +23,14 @@ class Thread
# for the fiber local. The fiber is executed in the same thread, so the
# thread local values are available.
def thread_variable_get(key)
- locals[key.to_sym]
+ _locals[key.to_sym]
end
# Sets a thread local with +key+ to +value+. Note that these are local to
# threads, and not to fibers. Please see Thread#thread_variable_get for
# more information.
def thread_variable_set(key, value)
- locals[key.to_sym] = value
+ _locals[key.to_sym] = value
end
# Returns an an array of the names of the thread-local variables (as Symbols).
@@ -45,7 +45,7 @@ class Thread
# Note that these are not fiber local variables. Please see Thread#thread_variable_get
# for more details.
def thread_variables
- locals.keys
+ _locals.keys
end
# Returns <tt>true</tt> if the given string (or symbol) exists as a
@@ -59,16 +59,21 @@ class Thread
# Note that these are not fiber local variables. Please see Thread#thread_variable_get
# for more details.
def thread_variable?(key)
- locals.has_key?(key.to_sym)
+ _locals.has_key?(key.to_sym)
+ end
+
+ def freeze
+ _locals.freeze
+ super
end
private
- def locals
- if defined?(@locals)
- @locals
+ def _locals
+ if defined?(@_locals)
+ @_locals
else
- LOCK.synchronize { @locals ||= {} }
+ LOCK.synchronize { @_locals ||= {} }
end
end
end unless Thread.instance_methods.include?(:thread_variable_set)
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 90db180124..328b8c320a 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -1,14 +1,24 @@
require "active_support/notifications"
module ActiveSupport
+ class DeprecationException < StandardError
+ end
+
class Deprecation
# Default warning behaviors per Rails.env.
DEFAULT_BEHAVIORS = {
- :stderr => Proc.new { |message, callstack|
+ raise: ->(message, callstack) {
+ e = DeprecationException.new(message)
+ e.set_backtrace(callstack)
+ raise e
+ },
+
+ stderr: ->(message, callstack) {
$stderr.puts(message)
$stderr.puts callstack.join("\n ") if debug
},
- :log => Proc.new { |message, callstack|
+
+ log: ->(message, callstack) {
logger =
if defined?(Rails) && Rails.logger
Rails.logger
@@ -19,11 +29,13 @@ module ActiveSupport
logger.warn message
logger.debug callstack.join("\n ") if debug
},
- :notify => Proc.new { |message, callstack|
+
+ notify: ->(message, callstack) {
ActiveSupport::Notifications.instrument("deprecation.rails",
:message => message, :callstack => callstack)
},
- :silence => Proc.new { |message, callstack| }
+
+ silence: ->(message, callstack) {},
}
module Behavior
@@ -40,6 +52,7 @@ module ActiveSupport
#
# Available behaviors:
#
+ # [+raise+] Raise <tt>ActiveSupport::DeprecationException</tt>.
# [+stderr+] Log all deprecation warnings to +$stderr+.
# [+log+] Log all deprecation warnings to +Rails.logger+.
# [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
@@ -52,7 +65,7 @@ module ActiveSupport
# ActiveSupport::Deprecation.behavior = :stderr
# ActiveSupport::Deprecation.behavior = [:stderr, :log]
# ActiveSupport::Deprecation.behavior = MyCustomHandler
- # ActiveSupport::Deprecation.behavior = proc { |message, callstack|
+ # ActiveSupport::Deprecation.behavior = ->(message, callstack) {
# # custom stuff
# }
def behavior=(behavior)
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 2cb1f408b6..00727ef73c 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -70,13 +70,13 @@ module ActiveSupport
alias :until :ago
def inspect #:nodoc:
- consolidated = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }
- parts = [:years, :months, :days, :minutes, :seconds].map do |length|
- n = consolidated[length]
- "#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero?
- end.compact
- parts = ["0 seconds"] if parts.empty?
- parts.to_sentence(:locale => :en)
+ val_for = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }
+ [:years, :months, :days, :minutes, :seconds].
+ select {|unit| val_for[unit].nonzero?}.
+ tap {|units| units << :seconds if units.empty?}.
+ map {|unit| [unit, val_for[unit]]}.
+ map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
+ to_sentence(:locale => :en)
end
def as_json(options = nil) #:nodoc:
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 0c9a729ce5..3a244b34b5 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -54,10 +54,11 @@ module ActiveSupport
@transaction_id = transaction_id
@end = ending
@children = []
+ @duration = nil
end
def duration
- 1000.0 * (self.end - time)
+ @duration ||= 1000.0 * (self.end - time)
end
def <<(event)
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index e03bb4ca0f..a33e2c58a9 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -41,7 +41,7 @@ module ActiveSupport
end
# +InheritableOptions+ provides a constructor to build an +OrderedOptions+
- # hash inherited from the another hash.
+ # hash inherited from another hash.
#
# Use this if you already have some hash and you want to create a new one based on it.
#
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 939bd7bfa8..62105da49c 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -24,3 +24,7 @@ Thread.abort_on_exception = true
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
+
+def rubinius_skip(message = '')
+ skip message if RUBY_ENGINE == 'rbx'
+end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 12126fb9ef..2537af0394 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -277,6 +277,11 @@ class StringInflectionsTest < ActiveSupport::TestCase
def test_truncate_should_not_be_html_safe
assert !"Hello World!".truncate(12).html_safe?
end
+
+ def test_remove
+ assert_equal "Summer", "Fast Summer".remove(/Fast /)
+ assert_equal "Summer", "Fast Summer".remove!(/Fast /)
+ end
def test_constantize
run_constantize_tests_on do |string|
diff --git a/activesupport/test/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb
index 230c1203ad..54d2dcd8dd 100644
--- a/activesupport/test/core_ext/thread_test.rb
+++ b/activesupport/test/core_ext/thread_test.rb
@@ -63,7 +63,18 @@ class ThreadExt < ActiveSupport::TestCase
end
end
+ def test_thread_variable_frozen_after_set
+ t = Thread.new { }.join
+ t.thread_variable_set :foo, "bar"
+ t.freeze
+ assert_raises(RuntimeError) do
+ t.thread_variable_set(:baz, "qux")
+ end
+ end
+
def test_thread_variable_security
+ rubinius_skip "$SAFE is not supported on Rubinius."
+
t = Thread.new { sleep }
assert_raises(SecurityError) do
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 9616e42f44..9674851b9d 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -98,6 +98,22 @@ class DeprecationTest < ActiveSupport::TestCase
assert_match(/foo=nil/, @b)
end
+ def test_raise_behaviour
+ ActiveSupport::Deprecation.behavior = :raise
+
+ message = 'Revise this deprecated stuff now!'
+ callstack = %w(foo bar baz)
+
+ begin
+ ActiveSupport::Deprecation.behavior.first.call(message, callstack)
+ rescue ActiveSupport::DeprecationException => e
+ assert_equal message, e.message
+ assert_equal callstack, e.backtrace
+ else
+ flunk 'the :raise deprecation behaviour should raise the expected exception'
+ end
+ end
+
def test_default_stderr_behavior
ActiveSupport::Deprecation.behavior = :stderr
behavior = ActiveSupport::Deprecation.behavior.first
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index e0f85f4e7c..ce91c443e1 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -3,6 +3,7 @@ require 'abstract_unit'
require 'active_support/inflector/transliterate'
class TransliterateTest < ActiveSupport::TestCase
+
def test_transliterate_should_not_change_ascii_chars
(0..127).each do |byte|
char = [byte].pack("U")
@@ -23,13 +24,12 @@ class TransliterateTest < ActiveSupport::TestCase
def test_transliterate_should_work_with_custom_i18n_rules_and_uncomposed_utf8
char = [117, 776].pack("U*") # "ü" as ASCII "u" plus COMBINING DIAERESIS
I18n.backend.store_translations(:de, :i18n => {:transliterate => {:rule => {"ü" => "ue"}}})
- default_locale, I18n.locale = I18n.locale, :de
+ I18n.locale = :de
assert_equal "ue", ActiveSupport::Inflector.transliterate(char)
- ensure
- I18n.locale = default_locale
end
def test_transliterate_should_allow_a_custom_replacement_char
assert_equal "a*b", ActiveSupport::Inflector.transliterate("a索b", "*")
end
+
end
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index d992028323..a025279e16 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -106,11 +106,7 @@ module XmlMiniTest
module Nokogiri end
setup do
- @xml, @default_backend = ActiveSupport::XmlMini, ActiveSupport::XmlMini.backend
- end
-
- teardown do
- ActiveSupport::XmlMini.backend = @default_backend
+ @xml = ActiveSupport::XmlMini
end
test "#with_backend should switch backend and then switch back" do
@@ -139,11 +135,7 @@ module XmlMiniTest
module LibXML end
setup do
- @xml, @default_backend = ActiveSupport::XmlMini, ActiveSupport::XmlMini.backend
- end
-
- teardown do
- ActiveSupport::XmlMini.backend = @default_backend
+ @xml = ActiveSupport::XmlMini
end
test "#with_backend should be thread-safe" do
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index 8be7a86d20..ff4d30b4c0 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -173,6 +173,19 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ
* `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!`.
+* `String#to_date` now raises `Argument Error: invalid date` instead of `NoMethodError: undefined method 'div' for nil:NilClass`
+ when given an invalid date. It is now the same as `Date.parse`, and it accepts more invalid dates than 3.x, such as:
+
+ ```
+ # ActiveSupport 3.x
+ "asdf".to_date # => NoMethodError: undefined method `div' for nil:NilClass
+ "333".to_date # => NoMethodError: undefined method `div' for nil:NilClass
+
+ # ActiveSupport 4
+ "asdf".to_date # => ArgumentError: invalid date
+ "333".to_date # => Fri, 29 Nov 2013
+ ```
+
### Deprecations
* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead.
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 0a0a958e30..9210c40c17 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -15,7 +15,7 @@
</p>
<% end %>
<p>
- The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.14/">http://guides.rubyonrails.org/v3.2.14/</a>.
+ The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.13/">http://guides.rubyonrails.org/v3.2.13/</a>.
</p>
<p>
The guides for Rails 2.3.x are available at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>.
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 6fce5a1dc2..f7d2016784 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -1520,6 +1520,72 @@ number_with_precision(111.2345) # => 111.235
number_with_precision(111.2345, 2) # => 111.23
```
+### SanitizeHelper
+
+The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
+
+#### sanitize
+
+This sanitize helper will html encode all tags and strip all attributes that aren’t specifically allowed.
+
+```ruby
+sanitize @article.body
+```
+
+If either the :attributes or :tags options are passed, only the mentioned tags and attributes are allowed and nothing else.
+
+```ruby
+sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style)
+```
+
+To change defaults for multiple uses, for example adding table tags to the default:
+
+```ruby
+class Application < Rails::Application
+ config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
+end
+```
+
+#### sanitize_css(style)
+
+Sanitizes a block of CSS code.
+
+#### strip_links(html)
+Strips all link tags from text leaving just the link text.
+
+```ruby
+strip_links("<a href="http://rubyonrails.org">Ruby on Rails</a>")
+# => Ruby on Rails
+```
+
+```ruby
+strip_links("emails to <a href="mailto:me@email.com">me@email.com</a>.")
+# => emails to me@email.com.
+```
+
+```ruby
+strip_links('Blog: <a href="http://myblog.com/">Visit</a>.')
+# => Blog: Visit.
+```
+
+#### strip_tags(html)
+
+Strips all HTML tags from the html, including comments.
+This uses the html-scanner tokenizer and so its HTML parsing ability is limited by that of html-scanner.
+
+```ruby
+strip_tags("Strip <i>these</i> tags!")
+# => Strip these tags!
+```
+
+```ruby
+strip_tags("<b>Bold</b> no more! <a href='more.html'>See more</a>")
+# => Bold no more! See more
+```
+
+NB: The output may still contain unescaped ‘<’, ‘>’, ‘&’ characters and confuse browsers.
+
+
Localized Views
---------------
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index 556c2544ff..bff60efc33 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -188,11 +188,11 @@ end
```
It's also possible to override the column that should be used as the table's
-primary key using the `ActiveRecord::Base.set_primary_key` method:
+primary key using the `ActiveRecord::Base.primary_key=` method:
```ruby
class Product < ActiveRecord::Base
- set_primary_key "product_id"
+ self.primary_key = "product_id"
end
```
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index df1dd22971..95eb84dd1f 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -180,7 +180,7 @@ NOTE: The `find_by_*` and `find_by_*!` methods are dynamic finders generated aut
Skipping Callbacks
------------------
-Just as with validations, it is also possible to skip callbacks. These methods should be used with caution, however, because important business rules and application logic may be kept in callbacks. Bypassing them without understanding the potential implications may lead to invalid data.
+Just as with validations, it is also possible to skip callbacks by using the following methods:
* `decrement`
* `decrement_counter`
@@ -195,6 +195,8 @@ Just as with validations, it is also possible to skip callbacks. These methods s
* `update_all`
* `update_counters`
+These methods should be used with caution, however, because important business rules and application logic may be kept in callbacks. Bypassing them without understanding the potential implications may lead to invalid data.
+
Halting Execution
-----------------
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 7fe9b8b4af..ce571c6f96 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -514,13 +514,7 @@ SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
Post.where.not(author: author)
```
-In other words, this query can be generated by calling `where` with no argument,
-then immediately chain with `not` passing `where` conditions. This will generate
-SQL code like this:
-
-```sql
-SELECT * FROM posts WHERE (author_id != 1)
-```
+In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions.
Ordering
--------
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 1915252122..ca023f7f66 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -96,13 +96,12 @@ INFO: The predicate for strings uses the Unicode-aware character class `[:space:
WARNING: Note that numbers are not mentioned. In particular, 0 and 0.0 are **not** blank.
-For example, this method from `ActionController::HttpAuthentication::Token::ControllerMethods` uses `blank?` for checking whether a token is present:
+For example, this method from `ActionDispatch::Session::AbstractStore` uses `blank?` for checking whether a session key is present:
```ruby
-def authenticate(controller, &login_procedure)
- token, options = token_and_options(controller.request)
- unless token.blank?
- login_procedure.call(token, options)
+def ensure_session_key!
+ if @key.blank?
+ raise ArgumentError, 'A key is required...'
end
end
```
@@ -421,9 +420,9 @@ NOTE: Defined in `active_support/core_ext/object/with_options.rb`.
### JSON support
-Active Support provides a better implemention of `to_json` than the `json` gem ordinarily provides for Ruby objects. This is because some classes, like `Hash` and `OrderedHash` needs special handling in order to provide a proper JSON representation.
+Active Support provides a better implemention of `to_json` than the +json+ gem ordinarily provides for Ruby objects. This is because some classes, like +Hash+ and +OrderedHash+ needs special handling in order to provide a proper JSON representation.
-Active Support also provides an implementation of `as_json` for the `Process::Status` class.
+Active Support also provides an implementation of `as_json` for the <tt>Process::Status</tt> class.
NOTE: Defined in `active_support/core_ext/object/to_json.rb`.
@@ -1249,6 +1248,18 @@ Calling `to_s` on a safe string returns a safe string, but coercion with `to_str
Calling `dup` or `clone` on safe strings yields safe strings.
+### `remove`
+
+The method `remove` will remove all occurrences of the pattern:
+
+```ruby
+"Hello World".remove(/Hello /) => "World"
+```
+
+There's also the destructive version `String#remove!`.
+
+NOTE: Defined in `active_support/core_ext/string/filters.rb`.
+
### `squish`
The method `squish` strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each:
@@ -1988,7 +1999,7 @@ Produce a string representation of a number in human-readable words:
1234567890123456.to_s(:human) # => "1.23 Quadrillion"
```
-NOTE: Defined in `active_support/core_ext/numeric/conversions.rb`.
+NOTE: Defined in `active_support/core_ext/numeric/formatting.rb`.
Extensions to `Integer`
-----------------------
@@ -2046,7 +2057,7 @@ BigDecimal.new(5.00, 6).to_s # => "5.0"
### `to_formatted_s`
-The method `to_formatted_s` provides a default specifier of "F". This means that a simple call to `to_formatted_s` or `to_s` will result in floating point representation instead of engineering notation:
+Te method `to_formatted_s` provides a default specifier of "F". This means that a simple call to `to_formatted_s` or `to_s` will result in floating point representation instead of engineering notation:
```ruby
BigDecimal.new(5.00, 6).to_formatted_s # => "5.0"
@@ -2433,7 +2444,7 @@ dup[1][2] = 4
array[1][2] == nil # => true
```
-NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`.
+NOTE: Defined in `active_support/core_ext/array/deep_dup.rb`.
### Grouping
@@ -2659,7 +2670,45 @@ hash[:b][:e] == nil # => true
hash[:b][:d] == [3, 4] # => true
```
-NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`.
+NOTE: Defined in `active_support/core_ext/hash/deep_dup.rb`.
+
+### Diffing
+
+The method `diff` returns a hash that represents a diff of the receiver and the argument with the following logic:
+
+* Pairs `key`, `value` that exist in both hashes do not belong to the diff hash.
+
+* If both hashes have `key`, but with different values, the pair in the receiver wins.
+
+* The rest is just merged.
+
+```ruby
+{a: 1}.diff(a: 1)
+# => {}, first rule
+
+{a: 1}.diff(a: 2)
+# => {:a=>1}, second rule
+
+{a: 1}.diff(b: 2)
+# => {:a=>1, :b=>2}, third rule
+
+{a: 1, b: 2, c: 3}.diff(b: 1, c: 3, d: 4)
+# => {:a=>1, :b=>2, :d=>4}, all rules
+
+{}.diff({}) # => {}
+{a: 1}.diff({}) # => {:a=>1}
+{}.diff(a: 1) # => {:a=>1}
+```
+
+An important property of this diff hash is that you can retrieve the original hash by applying `diff` twice:
+
+```ruby
+hash.diff(hash2).diff(hash2) == hash
+```
+
+Diffing hashes may be useful for error messages related to expected option hashes for example.
+
+NOTE: Defined in `active_support/core_ext/hash/diff.rb`.
### Working with Keys
@@ -3794,13 +3843,13 @@ def default_helper_module!
module_path = module_name.underscore
helper module_path
rescue MissingSourceFile => e
- raise e unless e.is_missing? "helpers/#{module_path}_helper"
+ raise e unless e.is_missing? "#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
end
```
-NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`.
+NOTE: Defined in `active_support/core_ext/name_error.rb`.
Extensions to `LoadError`
-------------------------
@@ -3823,4 +3872,4 @@ rescue NameError => e
end
```
-NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`.
+NOTE: Defined in `active_support/core_ext/load_error.rb`.
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 9b80a65a44..e133e71d42 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -1137,6 +1137,12 @@ Controls what happens to the associated object when its owner is destroyed:
* `:restrict_with_exception` causes an exception to be raised if there is an associated record
* `:restrict_with_error` causes an error to be added to the owner if there is an associated object
+It's necessary not to set or leave `:nullify` option for those associations
+that have `NOT NULL` database constraints. If you don't set `dependent` to
+destroy such associations you won't be able to change the associated object
+because initial associated object foreign key will be set to unallowed `NULL`
+value.
+
##### `:foreign_key`
By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 2f8a178376..a0352790f4 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -184,7 +184,11 @@ Ruby on Rails has two main sets of documentation: the guides help you in learnin
You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/rails/docrails/translating-rails-guides).
-You can either ask for commit bit if you'd like to contribute to [Docrails](http://github.com/rails/docrails) regularly (Please contact anyone from [the core team](http://rubyonrails.org/core)), or else open a pull request to [Rails](http://github.com/rails/rails) itself.
+You can either open a pull request to [Rails](http://github.com/rails/rails) or
+ask the [Rails core team](http://rubyonrails.org/core) for commit access on
+[docrails](http://github.com/rails/docrails) if you contribute regularly.
+Please do not open pull requests in docrails, if you'd like to get feedback on your
+change, ask for it in [Rails](http://github.com/rails/rails) instead.
Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation.
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index ec25e09222..7e61c78ed9 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -74,7 +74,7 @@ ports.
If you have any problems with these libraries, you can install them manually by compiling the source code. Just follow the instructions at the [Red Hat/CentOS section of the Nokogiri tutorials](http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos) .
-Also, SQLite3 and its development files for the `sqlite3` gem — in Ubuntu you're done with just
+Also, SQLite3 and its development files for the `sqlite3-ruby` gem — in Ubuntu you're done with just
```bash
$ sudo apt-get install sqlite3 libsqlite3-dev
@@ -86,6 +86,7 @@ And if you are on Fedora or CentOS, you're done with
$ sudo yum install sqlite3 sqlite3-devel
```
+<<<<<<< HEAD
If you are on Arch Linux, you will need to run:
```bash
@@ -100,6 +101,8 @@ For FreeBSD users, you're done with:
Or compile the `databases/sqlite3` port.
+=======
+>>>>>>> ec8ef1e1055c4e1598da13f49d30261f07f4a9b4
Get a recent version of [Bundler](http://gembundler.com/)
```bash
@@ -113,7 +116,29 @@ and run:
$ bundle install --without db
```
-This command will install all dependencies except the MySQL and PostgreSQL Ruby drivers. We will come back to these soon. With dependencies installed, you can run the test suite with:
+This command will install all dependencies except the MySQL and PostgreSQL Ruby drivers. We will come back to these soon.
+
+NOTE: If you would like to run the tests that use memcached, you need to ensure that you have it installed and running.
+
+You can use homebrew to install memcached on OSX:
+
+```bash
+$ brew install memcached
+```
+
+On Ubuntu you can install it with apt-get:
+
+```bash
+$ sudo apt-get install memcached
+```
+
+Or use yum on Fedora or CentOS:
+
+```bash
+$ sudo yum install memcached
+```
+
+With the dependencies now installed, you can run the test suite with:
```bash
$ bundle exec rake test
@@ -166,6 +191,7 @@ $ sudo yum install mysql-server mysql-devel
$ sudo yum install postgresql-server postgresql-devel
```
+<<<<<<< HEAD
If you are running Arch Linux, MySQL isn't supported anymore so you will need to
use MariaDB instead (see [this announcement](https://www.archlinux.org/news/mariadb-replaces-mysql-in-repositories/)):
@@ -185,6 +211,8 @@ Or install them through ports (they are located under the `databases` folder).
If you run into troubles during the installation of MySQL, please see
[the MySQL documentation](http://dev.mysql.com/doc/refman/5.1/en/freebsd-installation.html).
+=======
+>>>>>>> ec8ef1e1055c4e1598da13f49d30261f07f4a9b4
After that, run:
```bash
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 9106b6382d..7a125fd341 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -104,7 +104,7 @@ At the root of this brand new engine's directory lives a `blorgh.gemspec` file.
gem 'blorgh', path: "vendor/engines/blorgh"
```
-By specifying it as a gem within the `Gemfile`, Bundler will load it as such, parsing this `blorgh.gemspec` file and requiring a file within the `lib` directory called `lib/blorgh.rb`. This file requires the `blorgh/engine.rb` file (located at `lib/blorgh/engine.rb`) and defines a base module called `Blorgh`.
+Don't forget to run `bundle install` as usual. By specifying it as a gem within the `Gemfile`, Bundler will load it as such, parsing this `blorgh.gemspec` file and requiring a file within the `lib` directory called `lib/blorgh.rb`. This file requires the `blorgh/engine.rb` file (located at `lib/blorgh/engine.rb`) and defines a base module called `Blorgh`.
```ruby
require "blorgh/engine"
@@ -466,7 +466,7 @@ NOTE: Other engines, such as Devise, handle this a little differently by making
The engine contains migrations for the `blorgh_posts` and `blorgh_comments` table which need to be created in the application's database so that the engine's models can query them correctly. To copy these migrations into the application use this command:
```bash
-$ rake blorgh_engine:install:migrations
+$ rake blorgh:install:migrations
```
If you have multiple engines that need migrations copied over, use `railties:install:migrations` instead:
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 11e8db9e88..3a18fb81d8 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -605,7 +605,7 @@ The object in the `params` hash is an instance of a subclass of IO. Depending on
```ruby
def upload
uploaded_io = params[:person][:picture]
- File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'w') do |file|
+ File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'wb') do |file|
file.write(uploaded_io.read)
end
end
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 784a61adc1..021aa070fb 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -310,6 +310,10 @@ end
A controller is simply a class that is defined to inherit from `ApplicationController`. It's inside this class that you'll define methods that will become the actions for this controller. These actions will perform CRUD operations on the posts within our system.
+NOTE: There are `public`, `private` and `protected` methods in `Ruby`
+(for more details you can check on [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/)).
+But only `public` methods can be actions for controllers.
+
If you refresh <http://localhost:3000/posts/new> now, you'll get a new error:
![Unknown action new for PostsController!](images/getting_started/unknown_action_new_for_posts.png)
@@ -594,8 +598,9 @@ end
```
A couple of things to note. We use `Post.find` to find the post we're
-interested in. We also use an instance variable (prefixed by `@`) to
-hold a reference to the post object. We do this because Rails will pass all instance
+interested in, passing in `params[:id]` to get the `:id` parameter from the
+request. We also use an instance variable (prefixed by `@`) to hold a
+reference to the post object. We do this because Rails will pass all instance
variables to the view.
Now, create a new file `app/views/posts/show.html.erb` with the following
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index facfb96d98..e4214fd74e 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -731,6 +731,19 @@ en:
Then `User.model_name.human` will return "Dude" and `User.human_attribute_name("login")` will return "Handle".
+You can also set a plural form for model names, adding as following:
+
+```ruby
+en:
+ activerecord:
+ models:
+ user:
+ one: Dude
+ other: Dudes
+```
+
+Then `User.model_name.human(:count => 2)` will return "Dudes". With `:count => 1` or without params will return "Dude".
+
#### Error Message Scopes
Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes, and/or validations. It also transparently takes single table inheritance into account.
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 26259408b4..c78eef5bf0 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -40,7 +40,7 @@ This file is as follows:
```ruby
#!/usr/bin/env ruby
APP_PATH = File.expand_path('../../config/application', __FILE__)
-require File.expand_path('../../config/boot', __FILE__)
+require_relative '../config/boot'
require 'rails/commands'
```
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 71d3c5638b..24e1e23497 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -101,14 +101,8 @@
You're encouraged to help improve the quality of this guide.
</p>
<p>
- If you see any typos or factual errors that you are confident to fix,
- you can push the fix to <%= link_to 'docrails', 'https://github.com/rails/docrails' %> (Ask
- the <%= link_to 'Rails core team', 'http://rubyonrails.org/core' %> for push access).
- If you choose to open a pull request, please do it in <%= link_to 'Rails', 'https://github.com/rails/rails' %>
- and not in the <%= link_to 'docrails', 'https://github.com/rails/docrails' %> repository.
- Commits made to docrails are still reviewed, but that happens after you've submitted your
- contribution. <%= link_to 'docrails', 'https://github.com/rails/docrails' %> is
- cross-merged with master periodically.
+ Please contribute if you see any typos or factual errors.
+ To get started, you can read our <%= link_to 'documentation contributions', 'http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation' %> section.
</p>
<p>
You may also find incomplete content, or stuff that is not up to date.
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 62c9835fa4..0258202c18 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -66,18 +66,34 @@ Here's a sample YAML fixture file:
```yaml
# lo & behold! I am a YAML comment!
david:
- name: David Heinemeier Hansson
- birthday: 1979-10-15
- profession: Systems development
+ name: David Heinemeier Hansson
+ birthday: 1979-10-15
+ profession: Systems development
steve:
- name: Steve Ross Kellock
- birthday: 1974-09-27
- profession: guy with keyboard
+ name: Steve Ross Kellock
+ birthday: 1974-09-27
+ profession: guy with keyboard
```
Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column. Keys which resemble YAML keywords such as 'yes' and 'no' are quoted so that the YAML Parser correctly interprets them.
+If you are working with [associations](/association_basics.html), you can simply
+define a reference node between two different fixtures. Here's an example with
+a belongs_to/has_many association:
+
+```yaml
+# In fixtures/categories.yml
+about:
+ name: About
+
+# In fixtures/articles.yml
+one:
+ title: Welcome to Rails!
+ body: Hello world!
+ category: about
+```
+
#### ERB'in It Up
ERB allows you to embed Ruby code within templates. The YAML fixture format is pre-processed with ERB when Rails loads fixtures. This allows you to use Ruby to help you generate some sample data. For example, the following code generates a thousand users:
@@ -357,28 +373,28 @@ Here's an extract of the assertions you can use with `minitest`, the default tes
| Assertion | Purpose |
| ---------------------------------------------------------------- | ------- |
| `assert( test, [msg] )` | Ensures that `test` is true.|
-| `refute( test, [msg] )` | Ensures that `test` is false.|
+| `assert_not( test, [msg] )` | Ensures that `test` is false.|
| `assert_equal( expected, actual, [msg] )` | Ensures that `expected == actual` is true.|
-| `refute_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.|
+| `assert_not_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.|
| `assert_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is true.|
-| `refute_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.|
+| `assert_not_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.|
| `assert_nil( obj, [msg] )` | Ensures that `obj.nil?` is true.|
-| `refute_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.|
+| `assert_not_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.|
| `assert_match( regexp, string, [msg] )` | Ensures that a string matches the regular expression.|
-| `refute_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.|
+| `assert_no_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.|
| `assert_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.|
-| `refute_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.|
+| `assert_not_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.|
| `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.|
| `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.|
| `assert_nothing_raised( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given exceptions.|
| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.|
-| `refute_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.|
+| `assert_not_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.|
| `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is or descends from `class`.|
-| `refute_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.|
+| `assert_not_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.|
| `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` responds to `symbol`.|
-| `refute_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.|
+| `assert_not_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.|
| `assert_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is true.|
-| `refute_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.|
+| `assert_not_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.|
| `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?|
| `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.|
@@ -990,6 +1006,47 @@ class UserControllerTest < ActionController::TestCase
end
```
+Testing helpers
+---------------
+
+In order to test helpers, all you need to do is check that the output of the
+helper method matches what you'd expect. Tests related to the helpers are
+located under the `test/helpers` directory. Rails provides a generator which
+generates both the helper and the test file:
+
+```bash
+$ rails generate helper User
+ create app/helpers/user_helper.rb
+ invoke test_unit
+ create test/helpers/user_helper_test.rb
+```
+
+The generated test file contains the following code:
+
+```ruby
+require 'test_helper'
+
+class UserHelperTest < ActionView::TestCase
+end
+```
+
+A helper is just a simple module where you can define methods which are
+available into your views. To test the output of the helper's methods, you just
+have to use a mixin like this:
+
+```ruby
+class UserHelperTest < ActionView::TestCase
+ include UserHelper
+
+ test "should return the user name" do
+ # ...
+ end
+end
+```
+
+Moreover, since the test class extends from `ActionView::TestCase`, you have
+access to Rails' helper methods such as `link_to` or `pluralize`.
+
Other Testing Approaches
------------------------
@@ -998,6 +1055,7 @@ The built-in `test/unit` based testing is not the only way to test Rails applica
* [NullDB](http://avdi.org/projects/nulldb/), a way to speed up testing by avoiding database use.
* [Factory Girl](https://github.com/thoughtbot/factory_girl/tree/master), a replacement for fixtures.
* [Machinist](https://github.com/notahat/machinist/tree/master), another replacement for fixtures.
+* [Fixture Builder](https://github.com/rdy/fixture_builder), a tool that compiles Ruby factories into fixtures before a test run.
* [MiniTest::Spec Rails](https://github.com/metaskills/minitest-spec-rails), use the MiniTest::Spec DSL within your rails tests.
* [Shoulda](http://www.thoughtbot.com/projects/shoulda), an extension to `test/unit` with additional helpers, macros, and assertions.
* [RSpec](http://relishapp.com/rspec), a behavior-driven development framework
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 73c783085e..391551938a 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -320,7 +320,7 @@ config.assets.js_compressor = :uglifier
### sass-rails
-* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")`
+* `asset_url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")`
Upgrading from Rails 3.1 to Rails 3.2
-------------------------------------
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index bd0c796673..301e0e7e6c 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -185,7 +185,7 @@ $(document).ready ->
```
Obviously, you'll want to be a bit more sophisticated than that, but it's a
-start.
+start. You can see more about the events [in the jquery-ujs wiki](https://github.com/rails/jquery-ujs/wiki/ajax).
### form_tag
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 7a89e5c219..cc9722e59c 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,25 @@
+* rake notes now searches *.less files
+
+ *Josh Crowder*
+
+* Generate nested route for namespaced controller generated using
+ `rails g controller`.
+ Fixes #11532.
+
+ Example:
+
+ rails g controller admin/dashboard index
+
+ # Before:
+ get "dashboard/index"
+
+ # After:
+ namespace :admin do
+ get "dashboard/index"
+ end
+
+ *Prathamesh Sonpatki*
+
* Fix the event name of action_dispatch requests.
*Rafael Mendonça França*
diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb
index 3e32576040..1f9568fb5f 100644
--- a/railties/lib/rails/api/task.rb
+++ b/railties/lib/rails/api/task.rb
@@ -16,7 +16,8 @@ module Rails
:include => %w(
README.rdoc
lib/active_record/**/*.rb
- )
+ ),
+ :exclude => 'lib/active_record/vendor/*'
},
'activemodel' => {
@@ -32,22 +33,23 @@ module Rails
lib/abstract_controller/**/*.rb
lib/action_controller/**/*.rb
lib/action_dispatch/**/*.rb
- )
+ ),
+ :exclude => 'lib/action_controller/vendor/*'
},
'actionview' => {
:include => %w(
README.rdoc
lib/action_view/**/*.rb
- ),
- :exclude => 'lib/action_view/vendor/*'
+ )
},
'actionmailer' => {
:include => %w(
README.rdoc
lib/action_mailer/**/*.rb
- )
+ ),
+ :exclude => 'lib/action_mailer/vendor/*'
},
'railties' => {
diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb
index fbb83fa10e..4a17803f1c 100644
--- a/railties/lib/rails/app_rails_loader.rb
+++ b/railties/lib/rails/app_rails_loader.rb
@@ -2,7 +2,7 @@ require 'pathname'
module Rails
module AppRailsLoader
- RUBY = Gem.ruby
+ RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
EXECUTABLES = ['bin/rails', 'script/rails']
BUNDLER_WARNING = <<EOS
Looks like your app's ./bin/rails is a stub that was generated by Bundler.
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index be8af5c46c..8000fc3b1e 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -351,13 +351,8 @@ module Rails
Rails::Railtie::Configuration.eager_load_namespaces << base
base.called_from = begin
- call_stack = if Kernel.respond_to?(:caller_locations)
- caller_locations.map(&:path)
- else
- # Remove the line number from backtraces making sure we don't leave anything behind
- caller.map { |p| p.sub(/:\d+.*/, '') }
- end
-
+ # Remove the line number from backtraces making sure we don't leave anything behind
+ call_stack = caller.map { |p| p.sub(/:\d+.*/, '') }
File.dirname(call_stack.detect { |p| p !~ %r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack] })
end
end
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index 4a40ba654d..69c10efa47 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -21,13 +21,13 @@
<%%= f.label :password_confirmation %><br>
<%%= f.password_field :password_confirmation %>
<% else -%>
- <% if attribute.reference? -%>
+ <%- if attribute.reference? -%>
<%%= f.label :<%= attribute.column_name %> %><br>
<%%= f.<%= attribute.field_type %> :<%= attribute.column_name %> %>
- <% else -%>
+ <%- else -%>
<%%= f.label :<%= attribute.name %> %><br>
<%%= f.<%= attribute.field_type %> :<%= attribute.name %> %>
- <% end -%>
+ <%- end -%>
<% end -%>
</div>
<% end -%>
diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb
index bae54623c6..822f35fb42 100644
--- a/railties/lib/rails/generators/rails/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb
@@ -10,11 +10,45 @@ module Rails
def add_routes
actions.reverse.each do |action|
- route %{get "#{file_name}/#{action}"}
+ route generate_routing_code(action)
end
end
hook_for :template_engine, :test_framework, :helper, :assets
+
+ private
+
+ # This method creates nested route entry for namespaced resources.
+ # For eg. rails g controller foo/bar/baz index
+ # Will generate -
+ # namespace :foo do
+ # namespace :bar do
+ # get "baz/index"
+ # end
+ # end
+ def generate_routing_code(action)
+ depth = class_path.length
+ # Create 'namespace' ladder
+ # namespace :foo do
+ # namespace :bar do
+ namespace_ladder = class_path.each_with_index.map do |ns, i|
+ %{#{" " * i * 2}namespace :#{ns} do\n }
+ end.join
+
+ # Create route
+ # get "baz/index"
+ route = %{#{" " * depth * 2}get "#{file_name}/#{action}"\n}
+
+ # Create `end` ladder
+ # end
+ # end
+ end_ladder = (1..depth).reverse_each.map do |i|
+ "#{" " * i * 2}end\n"
+ end.join
+
+ # Combine the 3 parts to generate complete route entry
+ namespace_ladder + route + end_ladder
+ end
end
end
end
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 290634290f..b806b922b7 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -82,7 +82,7 @@ class SourceAnnotationExtractor
case item
when /\.(builder|rb|coffee|rake)$/
/#\s*(#{tag}):?\s*(.*)$/
- when /\.(css|scss|sass|js)$/
+ when /\.(css|scss|sass|less|js)$/
/\/\/\s*(#{tag}):?\s*(.*)$/
when /\.erb$/
/<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index 9a92c5f6ff..01d751e822 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -25,6 +25,7 @@ module ApplicationTests
app_file "app/assets/stylesheets/application.css", "// TODO: note in css"
app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss"
app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass"
+ app_file "app/assets/stylesheets/application.css.less", "// TODO: note in less"
app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby"
app_file "lib/tasks/task.rake", "# TODO: note in rake"
@@ -48,9 +49,10 @@ module ApplicationTests
assert_match(/note in css/, output)
assert_match(/note in scss/, output)
assert_match(/note in sass/, output)
+ assert_match(/note in less/, output)
assert_match(/note in rake/, output)
- assert_equal 10, lines.size
+ assert_equal 11, lines.size
lines.each do |line|
assert_equal 4, line[0].size
diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb
index 5205deafd9..9c664a903a 100644
--- a/railties/test/generators/controller_generator_test.rb
+++ b/railties/test/generators/controller_generator_test.rb
@@ -82,4 +82,9 @@ class ControllerGeneratorTest < Rails::Generators::TestCase
assert_instance_method :bar, controller
end
end
+
+ def test_namespaced_routes_are_created_in_routes
+ run_generator ["admin/dashboard", "index"]
+ assert_file "config/routes.rb", /namespace :admin do\n\s+get "dashboard\/index"\n/
+ end
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 4b837da483..524bbde2b7 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -305,8 +305,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
assert_file "app/views/accounts/_form.html.erb" do |content|
- assert_match(/<%= f\.text_field :name %>/, content)
- assert_match(/<%= f\.text_field :currency_id %>/, content)
+ assert_match(/^\W{4}<%= f\.text_field :name %>/, content)
+ assert_match(/^\W{4}<%= f\.text_field :currency_id %>/, content)
end
end