aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock12
-rw-r--r--actionmailer/test/delivery_methods_test.rb6
-rw-r--r--actionpack/CHANGELOG.md18
-rw-r--r--actionpack/lib/abstract_controller/collector.rb6
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/metal.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb16
-rw-r--r--actionpack/lib/action_controller/metal/live.rb8
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb6
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb4
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb14
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb6
-rw-r--r--actionpack/lib/action_controller/test_case.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb20
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb120
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb18
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb2
-rw-r--r--actionpack/test/abstract/collector_test.rb6
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb2
-rw-r--r--actionpack/test/controller/content_type_test.rb44
-rw-r--r--actionpack/test/controller/live_stream_test.rb92
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb4
-rw-r--r--actionpack/test/controller/redirect_test.rb6
-rw-r--r--actionpack/test/controller/render_other_test.rb2
-rw-r--r--actionpack/test/controller/render_xml_test.rb2
-rw-r--r--actionpack/test/controller/send_file_test.rb109
-rw-r--r--actionpack/test/controller/test_case_test.rb11
-rw-r--r--actionpack/test/controller/webservice_test.rb4
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb87
-rw-r--r--actionpack/test/dispatch/request_test.rb48
-rw-r--r--actionpack/test/dispatch/response_test.rb23
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb2
-rw-r--r--actionview/lib/action_view/lookup_context.rb4
-rw-r--r--actionview/test/template/lookup_context_test.rb6
-rw-r--r--activejob/test/helper.rb1
-rw-r--r--activejob/test/support/integration/adapters/sidekiq.rb2
-rw-r--r--activejob/test/support/integration/dummy_app_template.rb3
-rw-r--r--activemodel/lib/active_model/callbacks.rb1
-rw-r--r--activemodel/lib/active_model/type.rb57
-rw-r--r--activemodel/lib/active_model/type/big_integer.rb (renamed from activerecord/lib/active_record/type/big_integer.rb)4
-rw-r--r--activemodel/lib/active_model/type/binary.rb (renamed from activerecord/lib/active_record/type/binary.rb)2
-rw-r--r--activemodel/lib/active_model/type/boolean.rb (renamed from activerecord/lib/active_record/type/boolean.rb)6
-rw-r--r--activemodel/lib/active_model/type/date.rb50
-rw-r--r--activemodel/lib/active_model/type/date_time.rb44
-rw-r--r--activemodel/lib/active_model/type/decimal.rb (renamed from activerecord/lib/active_record/type/decimal.rb)4
-rw-r--r--activemodel/lib/active_model/type/decimal_without_scale.rb (renamed from activerecord/lib/active_record/type/decimal_without_scale.rb)4
-rw-r--r--activemodel/lib/active_model/type/float.rb (renamed from activerecord/lib/active_record/type/float.rb)2
-rw-r--r--activemodel/lib/active_model/type/helpers.rb4
-rw-r--r--activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb (renamed from activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb)7
-rw-r--r--activemodel/lib/active_model/type/helpers/mutable.rb (renamed from activerecord/lib/active_record/type/helpers/mutable.rb)2
-rw-r--r--activemodel/lib/active_model/type/helpers/numeric.rb (renamed from activerecord/lib/active_record/type/helpers/numeric.rb)2
-rw-r--r--activemodel/lib/active_model/type/helpers/time_value.rb (renamed from activerecord/lib/active_record/type/helpers/time_value.rb)39
-rw-r--r--activemodel/lib/active_model/type/integer.rb (renamed from activerecord/lib/active_record/type/integer.rb)2
-rw-r--r--activemodel/lib/active_model/type/registry.rb64
-rw-r--r--activemodel/lib/active_model/type/string.rb (renamed from activerecord/lib/active_record/type/string.rb)2
-rw-r--r--activemodel/lib/active_model/type/text.rb (renamed from activerecord/lib/active_record/type/text.rb)4
-rw-r--r--activemodel/lib/active_model/type/time.rb42
-rw-r--r--activemodel/lib/active_model/type/unsigned_integer.rb (renamed from activerecord/lib/active_record/type/unsigned_integer.rb)2
-rw-r--r--activemodel/lib/active_model/type/value.rb (renamed from activerecord/lib/active_record/type/value.rb)2
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb1
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb4
-rw-r--r--activemodel/test/cases/attribute_assignment_test.rb2
-rw-r--r--activemodel/test/cases/type/decimal_test.rb (renamed from activerecord/test/cases/type/decimal_test.rb)5
-rw-r--r--activemodel/test/cases/type/integer_test.rb108
-rw-r--r--activemodel/test/cases/type/registry_test.rb39
-rw-r--r--activemodel/test/cases/type/string_test.rb20
-rw-r--r--activemodel/test/cases/type/unsigned_integer_test.rb (renamed from activerecord/test/cases/type/unsigned_integer_test.rb)5
-rw-r--r--activemodel/test/cases/types_test.rb122
-rw-r--r--activerecord/CHANGELOG.md41
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/query.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb4
-rw-r--r--activerecord/lib/active_record/model_schema.rb4
-rw-r--r--activerecord/lib/active_record/railties/databases.rake4
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb27
-rw-r--r--activerecord/lib/active_record/transactions.rb1
-rw-r--r--activerecord/lib/active_record/type.rb34
-rw-r--r--activerecord/lib/active_record/type/adapter_specific_registry.rb36
-rw-r--r--activerecord/lib/active_record/type/date.rb46
-rw-r--r--activerecord/lib/active_record/type/date_time.rb41
-rw-r--r--activerecord/lib/active_record/type/helpers.rb4
-rw-r--r--activerecord/lib/active_record/type/internal/abstract_json.rb4
-rw-r--r--activerecord/lib/active_record/type/internal/timezone.rb15
-rw-r--r--activerecord/lib/active_record/type/serialized.rb4
-rw-r--r--activerecord/lib/active_record/type/time.rb40
-rw-r--r--activerecord/lib/active_record/type/type_map.rb2
-rw-r--r--activerecord/lib/active_record/type_caster/connection.rb2
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb6
-rw-r--r--activerecord/test/cases/adapter_test.rb15
-rw-r--r--activerecord/test/cases/adapters/mysql/explain_test.rb32
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb4
-rw-r--r--activerecord/test/cases/attribute_test.rb1
-rw-r--r--activerecord/test/cases/base_test.rb2
-rw-r--r--activerecord/test/cases/connection_adapters/schema_cache_test.rb11
-rw-r--r--activerecord/test/cases/dirty_test.rb18
-rw-r--r--activerecord/test/cases/finder_test.rb12
-rw-r--r--activerecord/test/cases/helper.rb8
-rw-r--r--activerecord/test/cases/integration_test.rb1
-rw-r--r--activerecord/test/cases/migration/helper.rb2
-rw-r--r--activerecord/test/cases/relation_test.rb29
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb14
-rw-r--r--activerecord/test/cases/timestamp_test.rb20
-rw-r--r--activerecord/test/cases/touch_later_test.rb2
-rw-r--r--activerecord/test/cases/type/date_time_test.rb14
-rw-r--r--activerecord/test/cases/type/integer_test.rb100
-rw-r--r--activerecord/test/cases/type/string_test.rb14
-rw-r--r--activerecord/test/cases/types_test.rb105
-rw-r--r--activerecord/test/cases/view_test.rb6
-rw-r--r--activerecord/test/models/contact.rb2
-rw-r--r--activerecord/test/schema/schema.rb75
-rw-r--r--activesupport/CHANGELOG.md21
-rw-r--r--activesupport/lib/active_support/callbacks.rb42
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/blank.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/blank.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb10
-rw-r--r--activesupport/lib/active_support/number_helper.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb5
-rw-r--r--activesupport/test/callbacks_test.rb20
-rw-r--r--guides/source/action_view_overview.md33
-rw-r--r--guides/source/engines.md4
-rw-r--r--guides/source/i18n.md4
-rw-r--r--guides/source/routing.md4
-rw-r--r--railties/lib/rails/mailers_controller.rb2
-rw-r--r--railties/lib/rails/templates/rails/mailers/email.html.erb4
-rw-r--r--railties/test/generators/app_generator_test.rb193
-rw-r--r--railties/test/generators/plugin_generator_test.rb1
-rw-r--r--railties/test/generators/shared_generator_tests.rb48
156 files changed, 1723 insertions, 1056 deletions
diff --git a/Gemfile b/Gemfile
index a9a326c0ff..8bb4c7d7a9 100644
--- a/Gemfile
+++ b/Gemfile
@@ -17,7 +17,7 @@ gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
gem 'jquery-rails', github: 'rails/jquery-rails', branch: 'master'
gem 'coffee-rails', '~> 4.1.0'
-gem 'turbolinks'
+gem 'turbolinks', github: 'rails/turbolinks', branch: 'master'
gem 'arel', github: 'rails/arel', branch: 'master'
gem 'mail', github: 'mikel/mail', branch: 'master'
diff --git a/Gemfile.lock b/Gemfile.lock
index 16225777b6..563a9f56be 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -89,6 +89,14 @@ GIT
sprockets (4.0.0)
rack (> 1, < 3)
+GIT
+ remote: git://github.com/rails/turbolinks.git
+ revision: 4bb563cd777875d3ad73cf007c26334f2aa8dc37
+ branch: master
+ specs:
+ turbolinks (3.0.0)
+ coffee-rails
+
PATH
remote: .
specs:
@@ -273,8 +281,6 @@ GEM
thread_safe (0.3.5)
timers (4.0.4)
hitimes
- turbolinks (2.5.3)
- coffee-rails
tzinfo (1.2.2)
thread_safe (~> 0.1)
tzinfo-data (1.2015.6)
@@ -341,7 +347,7 @@ DEPENDENCIES
sqlite3 (~> 1.3.6)
stackprof
sucker_punch
- turbolinks
+ turbolinks!
tzinfo-data
uglifier (>= 1.3.0)
w3c_validators
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index ceaa4ca83e..d17e774092 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -165,16 +165,18 @@ class MailDeliveryTest < ActiveSupport::TestCase
test "non registered delivery methods raises errors" do
DeliveryMailer.delivery_method = :unknown
- assert_raise RuntimeError do
+ error = assert_raise RuntimeError do
DeliveryMailer.welcome.deliver_now
end
+ assert_equal "Invalid delivery method :unknown", error.message
end
test "undefined delivery methods raises errors" do
DeliveryMailer.delivery_method = nil
- assert_raise RuntimeError do
+ error = assert_raise RuntimeError do
DeliveryMailer.welcome.deliver_now
end
+ assert_equal "Delivery method cannot be nil", error.message
end
test "does not perform deliveries if requested" do
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 73a833a23b..bb15edee63 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,21 @@
+* ActionDispatch::Response#new no longer applies default headers. If you want
+ default headers applied to the response object, then call
+ `ActionDispatch::Response.create`. This change only impacts people who are
+ directly constructing an `ActionDispatch::Response` object.
+
+* Accessing mime types via constants like `Mime::HTML` is deprecated. Please
+ change code like this:
+
+ Mime::HTML
+
+ To this:
+
+ Mime::Type[:HTML]
+
+ This change is so that Rails will not manage a list of constants, and fixes
+ an issue where if a type isn't registered you could possibly get the wrong
+ object.
+
* `url_for` does not modify its arguments when generating polymorphic URLs.
*Bernerd Schaefer*
diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb
index ddd56b354a..3b5128cda5 100644
--- a/actionpack/lib/abstract_controller/collector.rb
+++ b/actionpack/lib/abstract_controller/collector.rb
@@ -7,7 +7,7 @@ module AbstractController
const = sym.upcase
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{sym}(*args, &block) # def html(*args, &block)
- custom(Mime::#{const}, *args, &block) # custom(Mime::HTML, *args, &block)
+ custom(Mime::Type[:#{const}], *args, &block) # custom(Mime::Type[:HTML], *args, &block)
end # end
RUBY
end
@@ -25,7 +25,7 @@ module AbstractController
def method_missing(symbol, &block)
const_name = symbol.upcase
- unless Mime.const_defined?(const_name)
+ unless Mime::Type.registered?(const_name)
raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \
"http://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \
"If you meant to respond to a variant like :tablet or :phone, not a custom format, " \
@@ -33,7 +33,7 @@ module AbstractController
"format.html { |html| html.tablet { ... } }"
end
- mime_constant = Mime.const_get(const_name)
+ mime_constant = Mime::Type[const_name]
if Mime::SET.include?(mime_constant)
AbstractController::Collector.generate_method_for_mime(mime_constant)
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 6c0a072b73..78b43f2fbe 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -55,7 +55,7 @@ module AbstractController
# Returns Content-Type of rendered content
# :api: public
def rendered_format
- Mime::TEXT
+ Mime::Type[:TEXT]
end
DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %i(
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 0384740fef..3d72755f1d 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -135,7 +135,7 @@ module ActionController
end
def self.make_response!(request)
- ActionDispatch::Response.new.tap do |res|
+ ActionDispatch::Response.create.tap do |res|
res.request = request
end
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 15d4562abb..fe470552b0 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -34,7 +34,7 @@ module ActionController
#
# def authenticate
# case request.format
- # when Mime::XML, Mime::ATOM
+ # when Mime::Type[:XML], Mime::Type[:ATOM]
# if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
# @current_user = user
# else
@@ -361,7 +361,7 @@ module ActionController
#
# def authenticate
# case request.format
- # when Mime::XML, Mime::ATOM
+ # when Mime::Type[:XML], Mime::Type[:ATOM]
# if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
# @current_user = user
# else
@@ -436,15 +436,17 @@ module ActionController
end
end
- # Parses the token and options out of the token authorization header. If
- # the header looks like this:
+ # Parses the token and options out of the token authorization header.
+ # The value for the Authorization header is expected to have the prefix
+ # <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
# Authorization: Token token="abc", nonce="def"
- # Then the returned token is "abc", and the options is {nonce: "def"}
+ # Then the returned token is <tt>"abc"</tt>, and the options are
+ # <tt>{nonce: "def"}</tt>
#
# request - ActionDispatch::Request instance with the current headers.
#
- # Returns an Array of [String, Hash] if a token is present.
- # Returns nil if no token is found.
+ # Returns an +Array+ of <tt>[String, Hash]</tt> if a token is present.
+ # Returns +nil+ if no token is found.
def token_and_options(request)
authorization_request = request.authorization.to_s
if authorization_request[TOKEN_REGEX]
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 667c7f87ca..c874165816 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -236,6 +236,10 @@ module ActionController
end
end
+ def initialize(status = 200, header = {}, body = [])
+ super(status, Header.new(self, header), body)
+ end
+
private
def before_committed
@@ -257,10 +261,6 @@ module ActionController
buf
end
- def merge_default_headers(original, default)
- Header.new self, super
- end
-
def handle_conditional_get!
super unless committed?
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 88a4818c16..fc42fe5c07 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -229,14 +229,14 @@ module ActionController #:nodoc:
@responses = {}
@variant = variant
- mimes.each { |mime| @responses["Mime::#{mime.upcase}".constantize] = nil }
+ mimes.each { |mime| @responses[Mime::Type[mime.upcase.to_sym]] = nil }
end
def any(*args, &block)
if args.any?
args.each { |type| send(type, &block) }
else
- custom(Mime::ALL, &block)
+ custom(Mime::Type[:ALL], &block)
end
end
alias :all :any
@@ -251,7 +251,7 @@ module ActionController #:nodoc:
end
def response
- response = @responses.fetch(format, @responses[Mime::ALL])
+ response = @responses.fetch(format, @responses[Mime::Type[:ALL]])
if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax
response.variant
elsif response.nil? || response.arity == 0 # `format.html` - just a format, call its block
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index e680432127..c38fc40b81 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -276,7 +276,9 @@ module ActionController
# Checks if we should perform parameters wrapping.
def _wrapper_enabled?
- ref = request.content_mime_type.try(:ref)
+ return false unless request.has_content_type?
+
+ ref = request.content_mime_type.ref
_wrapper_formats.include?(ref) && _wrapper_key && !request.request_parameters[_wrapper_key]
end
end
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index cb74c4f0d4..d867c97b46 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -68,11 +68,11 @@ module ActionController
# ActionController::Renderers.add :csv do |obj, options|
# filename = options[:filename] || 'data'
# str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
- # send_data str, type: Mime::CSV,
+ # send_data str, type: Mime::Type[:CSV],
# disposition: "attachment; filename=#{filename}.csv"
# end
#
- # Note that we used Mime::CSV for the csv mime type as it comes with Rails.
+ # Note that we used Mime::Type[:CSV] for the csv mime type as it comes with Rails.
# For a custom renderer, you'll need to register a mime type with
# <tt>Mime::Type.register</tt>.
#
@@ -116,24 +116,24 @@ module ActionController
json = json.to_json(options) unless json.kind_of?(String)
if options[:callback].present?
- if content_type.nil? || content_type == Mime::JSON
- self.content_type = Mime::JS
+ if content_type.nil? || content_type == Mime::Type[:JSON]
+ self.content_type = Mime::Type[:JS]
end
"/**/#{options[:callback]}(#{json})"
else
- self.content_type ||= Mime::JSON
+ self.content_type ||= Mime::Type[:JSON]
json
end
end
add :js do |js, options|
- self.content_type ||= Mime::JS
+ self.content_type ||= Mime::Type[:JS]
js.respond_to?(:to_js) ? js.to_js(options) : js
end
add :xml do |xml, options|
- self.content_type ||= Mime::XML
+ self.content_type ||= Mime::Type[:XML]
xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
end
end
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 00b551af94..1ecccf9864 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -64,7 +64,7 @@ module ActionController
end
def _set_html_content_type
- self.content_type = Mime::HTML.to_s
+ self.content_type = Mime::Type[:HTML].to_s
end
def _set_rendered_content_type(format)
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index 47d940f692..b2b3b4283f 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -2,12 +2,6 @@ module ActionController
module Testing
extend ActiveSupport::Concern
- # TODO : Rewrite tests using controller.headers= to use Rack env
- def headers=(new_headers)
- @_response ||= ActionDispatch::Response.new
- @_response.headers.replace(new_headers)
- end
-
# Behavior specific to functional tests
module Functional # :nodoc:
def set_response!(request)
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index f16c851456..cf78688126 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -34,7 +34,7 @@ module ActionController
self.session = session
self.session_options = TestSession::DEFAULT_OPTIONS
@custom_param_parsers = {
- Mime::XML => lambda { |raw_post| Hash.from_xml(raw_post)['hash'] }
+ Mime::Type[:XML] => lambda { |raw_post| Hash.from_xml(raw_post)['hash'] }
}
end
@@ -402,7 +402,7 @@ module ActionController
MSG
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ @request.env['HTTP_ACCEPT'] ||= [Mime::Type[:JS], Mime::Type[:HTML], Mime::Type[:XML], 'text/xml', Mime::Type[:ALL]].join(', ')
__send__(*args).tap do
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
@@ -505,7 +505,7 @@ module ActionController
if xhr
@request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'
@request.fetch_header('HTTP_ACCEPT') do |k|
- @request.set_header k, [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ @request.set_header k, [Mime::Type[:JS], Mime::Type[:HTML], Mime::Type[:XML], 'text/xml', Mime::Type[:ALL]].join(', ')
end
end
@@ -585,7 +585,7 @@ module ActionController
end
def build_response(klass)
- klass.new
+ klass.create
end
included do
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index cab60a508a..a966c5e452 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -10,7 +10,7 @@ module ActionDispatch
self.ignore_accept_header = false
end
- # The MIME type of the HTTP request, such as Mime::XML.
+ # The MIME type of the HTTP request, such as Mime::Type[:XML].
#
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
@@ -29,6 +29,10 @@ module ActionDispatch
content_mime_type && content_mime_type.to_s
end
+ def has_content_type?
+ has_header? 'CONTENT_TYPE'
+ end
+
# Returns the accepted MIME type for the request.
def accepts
fetch_header("action_dispatch.request.accepts") do |k|
@@ -45,9 +49,9 @@ module ActionDispatch
# Returns the MIME type for the \format used in the request.
#
- # GET /posts/5.xml | request.format => Mime::XML
- # GET /posts/5.xhtml | request.format => Mime::HTML
- # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first
+ # GET /posts/5.xml | request.format => Mime::Type[:XML]
+ # GET /posts/5.xhtml | request.format => Mime::Type[:HTML]
+ # GET /posts/5 | request.format => Mime::Type[:HTML] or Mime::Type[:JS], or request.accepts.first
#
def format(view_path = [])
formats.first || Mime::NullType.instance
@@ -66,9 +70,9 @@ module ActionDispatch
elsif use_accept_header && valid_accept_header
accepts
elsif xhr?
- [Mime::JS]
+ [Mime::Type[:JS]]
else
- [Mime::HTML]
+ [Mime::Type[:HTML]]
end
set_header k, v
end
@@ -134,14 +138,14 @@ module ActionDispatch
#
def negotiate_mime(order)
formats.each do |priority|
- if priority == Mime::ALL
+ if priority == Mime::Type[:ALL]
return order.first
elsif order.include?(priority)
return priority
end
end
- order.include?(Mime::ALL) ? format : nil
+ order.include?(Mime::Type[:ALL]) ? format : nil
end
protected
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index a639f8a8f8..a4dfe72c63 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -1,23 +1,32 @@
-require 'set'
require 'singleton'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/string/starts_ends_with'
+require 'active_support/deprecation'
module Mime
- class Mimes < Array
- def symbols
- @symbols ||= map(&:to_sym)
+ class Mimes
+ include Enumerable
+
+ def initialize
+ @mimes = []
+ @symbols = nil
end
- %w(<< concat shift unshift push pop []= clear compact! collect!
- delete delete_at delete_if flatten! map! insert reject! reverse!
- replace slice! sort! uniq!).each do |method|
- module_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{method}(*)
- @symbols = nil
- super
- end
- CODE
+ def each
+ @mimes.each { |x| yield x }
+ end
+
+ def <<(type)
+ @mimes << type
+ @symbols = nil
+ end
+
+ def delete_if
+ @mimes.delete_if { |x| yield x }.tap { @symbols = nil }
+ end
+
+ def symbols
+ @symbols ||= map(&:to_sym)
end
end
@@ -35,6 +44,40 @@ module Mime
return type if type.is_a?(Type)
EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k }
end
+
+ def const_missing(sym)
+ if Mime::Type.registered?(sym)
+ ActiveSupport::Deprecation.warn <<-eow
+Accessing mime types via constants is deprecated. Please change:
+
+ `Mime::#{sym}`
+
+to:
+
+ `Mime::Type[:#{sym}]`
+ eow
+ Mime::Type[sym]
+ else
+ super
+ end
+ end
+
+ def const_defined?(sym, inherit = true)
+ if Mime::Type.registered?(sym)
+ ActiveSupport::Deprecation.warn <<-eow
+Accessing mime types via constants is deprecated. Please change:
+
+ `Mime.const_defined?(#{sym})`
+
+to:
+
+ `Mime::Type.registered?(:#{sym})`
+ eow
+ true
+ else
+ super
+ end
+ end
end
# Encapsulates the notion of a mime type. Can be used at render time, for example, with:
@@ -51,9 +94,6 @@ module Mime
# end
# end
class Type
- @@html_types = Set.new [:html, :all]
- cattr_reader :html_types
-
attr_reader :symbol
@register_callbacks = []
@@ -66,7 +106,7 @@ module Mime
def initialize(index, name, q = nil)
@index = index
@name = name
- q ||= 0.0 if @name == Mime::ALL.to_s # default wildcard match to end of list
+ q ||= 0.0 if @name == Mime::Type[:ALL].to_s # default wildcard match to end of list
@q = ((q || 1.0).to_f * 100).to_i
end
@@ -120,7 +160,7 @@ module Mime
end
def app_xml_idx
- @app_xml_idx ||= index(Mime::XML.to_s)
+ @app_xml_idx ||= index(Mime::Type[:XML].to_s)
end
def text_xml
@@ -137,6 +177,8 @@ module Mime
end
end
+ TYPES = {}
+
class << self
TRAILING_STAR_REGEXP = /(text|application)\/\*/
PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
@@ -145,6 +187,18 @@ module Mime
@register_callbacks << block
end
+ def registered?(symbol)
+ TYPES.key? symbol
+ end
+
+ def [](symbol)
+ TYPES[symbol]
+ end
+
+ def add_type(symbol, type)
+ TYPES[symbol] = type
+ end
+
def lookup(string)
LOOKUP[string]
end
@@ -160,17 +214,18 @@ module Mime
end
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
- Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms))
+ new_mime = Type.new(string, symbol, mime_type_synonyms)
+ add_type symbol.upcase, new_mime
- new_mime = Mime.const_get(symbol.upcase)
SET << new_mime
- ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
- ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }
+ ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = new_mime } unless skip_lookup
+ ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = new_mime }
@register_callbacks.each do |callback|
callback.call(new_mime)
end
+ new_mime
end
def parse(accept_header)
@@ -216,8 +271,7 @@ module Mime
# Mime::Type.unregister(:mobile)
def unregister(symbol)
symbol = symbol.upcase
- mime = Mime.const_get(symbol)
- Mime.instance_eval { remove_const(symbol) }
+ mime = TYPES.delete symbol
SET.delete_if { |v| v.eql?(mime) }
LOOKUP.delete_if { |_,v| v.eql?(mime) }
@@ -243,7 +297,7 @@ module Mime
end
def ref
- to_sym || to_s
+ symbol || to_s
end
def ===(list)
@@ -255,24 +309,23 @@ module Mime
end
def ==(mime_type)
- return false if mime_type.blank?
+ return false unless mime_type
(@synonyms + [ self ]).any? do |synonym|
synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym
end
end
def =~(mime_type)
- return false if mime_type.blank?
+ return false unless mime_type
regexp = Regexp.new(Regexp.quote(mime_type.to_s))
- (@synonyms + [ self ]).any? do |synonym|
- synonym.to_s =~ regexp
- end
+ @synonyms.any? { |synonym| synonym.to_s =~ regexp } || @string =~ regexp
end
def html?
- @@html_types.include?(to_sym) || @string =~ /html/
+ symbol == :html || @string =~ /html/
end
+ def all?; false; end
private
@@ -290,6 +343,11 @@ module Mime
def respond_to_missing?(method, include_private = false) #:nodoc:
method.to_s.ends_with? '?'
end
+
+ class All < Type
+ def all?; true; end
+ def html?; true; end
+ end
end
class NullType
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index 01a10c693b..0af3e5c0df 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -33,4 +33,4 @@ Mime::Type.register "application/pdf", :pdf, [], %w(pdf)
Mime::Type.register "application/zip", :zip, [], %w(zip)
# Create Mime::ALL but do not add it to the SET.
-Mime::ALL = Mime::Type.new("*/*", :all, [])
+Mime::Type.add_type :ALL, Mime::Type::All.new("*/*", :all, [])
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 27fadb708e..e3c4392760 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -4,7 +4,7 @@ module ActionDispatch
PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
DEFAULT_PARSERS = {
- Mime::JSON => lambda { |raw_post|
+ Mime::Type[:JSON] => lambda { |raw_post|
data = ActiveSupport::JSON.decode(raw_post)
data.is_a?(Hash) ? data : {:_json => data}
}
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 45ffacd6f5..cbeea9e267 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -103,13 +103,21 @@ module ActionDispatch # :nodoc:
end
end
+ def self.create(status = 200, header = {}, body = [], default_headers: self.default_headers)
+ header = merge_default_headers(header, default_headers)
+ new status, header, body
+ end
+
+ def self.merge_default_headers(original, default)
+ default.respond_to?(:merge) ? default.merge(original) : original
+ end
+
# The underlying body, as a streamable object.
attr_reader :stream
- def initialize(status = 200, header = {}, body = [], default_headers: self.class.default_headers)
+ def initialize(status = 200, header = {}, body = [])
super()
- header = merge_default_headers(header, default_headers)
@header = header
self.body, self.status = body, status
@@ -345,10 +353,6 @@ module ActionDispatch # :nodoc:
def before_sending
end
- def merge_default_headers(original, default)
- default.respond_to?(:merge) ? default.merge(original) : original
- end
-
def build_buffer(response, body)
Buffer.new response, body
end
@@ -361,7 +365,7 @@ module ActionDispatch # :nodoc:
return if content_type
ct = parse_content_type
- set_content_type(ct.mime_type || Mime::HTML.to_s,
+ set_content_type(ct.mime_type || Mime::Type[:HTML].to_s,
ct.charset || self.class.default_charset)
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index c482b1c5e7..014b7bee61 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -276,7 +276,7 @@ module ActionDispatch
req = ActionDispatch::Request.new env
@app.call(env)
ensure
- session = Request::Session.find(req) || {}
+ session = req.session || {}
flash_hash = req.flash_hash
if flash_hash && (flash_hash.present? || session.key?('flash'))
diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb
index 21b3b89d22..81fa10a613 100644
--- a/actionpack/lib/action_dispatch/testing/assertions.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions.rb
@@ -12,7 +12,7 @@ module ActionDispatch
include Rails::Dom::Testing::Assertions
def html_document
- @html_document ||= if @response.content_type === Mime::XML
+ @html_document ||= if @response.content_type === Mime::Type[:XML]
Nokogiri::XML::Document.parse(@response.body)
else
Nokogiri::HTML::Document.parse(@response.body)
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 4dfd4f3f71..753cd2073b 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -354,7 +354,7 @@ module ActionDispatch
if xhr
headers ||= {}
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ headers['HTTP_ACCEPT'] ||= [Mime::Type[:JS], Mime::Type[:HTML], Mime::Type[:XML], 'text/xml', Mime::Type[:ALL]].join(', ')
end
# this modifies the passed request_env directly
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index 6a31d6243f..4b79a90242 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -7,7 +7,7 @@ module ActionDispatch
# See Response for more information on controller response objects.
class TestResponse < Response
def self.from_response(response)
- new response.status, response.headers, response.body, default_headers: nil
+ new response.status, response.headers, response.body
end
# Was the response successful?
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
index fc59bf19c4..3b36e43c0b 100644
--- a/actionpack/test/abstract/collector_test.rb
+++ b/actionpack/test/abstract/collector_test.rb
@@ -53,9 +53,9 @@ module AbstractController
collector.html
collector.text(:foo)
collector.js(:bar) { :baz }
- assert_equal [Mime::HTML, [], nil], collector.responses[0]
- assert_equal [Mime::TEXT, [:foo], nil], collector.responses[1]
- assert_equal [Mime::JS, [:bar]], collector.responses[2][0,2]
+ assert_equal [Mime::Type[:HTML], [], nil], collector.responses[0]
+ assert_equal [Mime::Type[:TEXT], [:foo], nil], collector.responses[1]
+ assert_equal [Mime::Type[:JS], [:bar]], collector.responses[2][0,2]
assert_equal :baz, collector.responses[2][2].call
end
end
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index beeafc2e53..7dfeadceb0 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -65,7 +65,7 @@ class ActionPackAssertionsController < ActionController::Base
end
def render_text_with_custom_content_type
- render body: "Hello!", content_type: Mime::RSS
+ render body: "Hello!", content_type: Mime::Type[:RSS]
end
def session_stuffing
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index c5bbc479c9..4a86f1bad3 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
class OldContentTypeController < ActionController::Base
# :ported:
def render_content_type_from_body
- response.content_type = Mime::RSS
+ response.content_type = Mime::Type[:RSS]
render body: "hello world!"
end
@@ -14,7 +14,7 @@ class OldContentTypeController < ActionController::Base
# :ported:
def render_content_type_from_render
- render body: "hello world!", :content_type => Mime::RSS
+ render body: "hello world!", :content_type => Mime::Type[:RSS]
end
# :ported:
@@ -36,7 +36,7 @@ class OldContentTypeController < ActionController::Base
end
def render_change_for_builder
- response.content_type = Mime::HTML
+ response.content_type = Mime::Type[:HTML]
render :action => "render_default_for_builder"
end
@@ -45,7 +45,7 @@ class OldContentTypeController < ActionController::Base
format.html { render body: "hello world!" }
format.xml { render action: "render_default_content_types_for_respond_to" }
format.js { render body: "hello world!" }
- format.rss { render body: "hello world!", content_type: Mime::XML }
+ format.rss { render body: "hello world!", content_type: Mime::Type[:XML] }
end
end
end
@@ -64,68 +64,68 @@ class ContentTypeTest < ActionController::TestCase
def test_render_defaults
get :render_defaults
assert_equal "utf-8", @response.charset
- assert_equal Mime::TEXT, @response.content_type
+ assert_equal Mime::Type[:TEXT], @response.content_type
end
def test_render_changed_charset_default
with_default_charset "utf-16" do
get :render_defaults
assert_equal "utf-16", @response.charset
- assert_equal Mime::TEXT, @response.content_type
+ assert_equal Mime::Type[:TEXT], @response.content_type
end
end
# :ported:
def test_content_type_from_body
get :render_content_type_from_body
- assert_equal Mime::RSS, @response.content_type
+ assert_equal Mime::Type[:RSS], @response.content_type
assert_equal "utf-8", @response.charset
end
# :ported:
def test_content_type_from_render
get :render_content_type_from_render
- assert_equal Mime::RSS, @response.content_type
+ assert_equal Mime::Type[:RSS], @response.content_type
assert_equal "utf-8", @response.charset
end
# :ported:
def test_charset_from_body
get :render_charset_from_body
- assert_equal Mime::TEXT, @response.content_type
+ assert_equal Mime::Type[:TEXT], @response.content_type
assert_equal "utf-16", @response.charset
end
# :ported:
def test_nil_charset_from_body
get :render_nil_charset_from_body
- assert_equal Mime::TEXT, @response.content_type
+ assert_equal Mime::Type[:TEXT], @response.content_type
assert_equal "utf-8", @response.charset, @response.headers.inspect
end
def test_nil_default_for_erb
with_default_charset nil do
get :render_default_for_erb
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::Type[:HTML], @response.content_type
assert_nil @response.charset, @response.headers.inspect
end
end
def test_default_for_erb
get :render_default_for_erb
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::Type[:HTML], @response.content_type
assert_equal "utf-8", @response.charset
end
def test_default_for_builder
get :render_default_for_builder
- assert_equal Mime::XML, @response.content_type
+ assert_equal Mime::Type[:XML], @response.content_type
assert_equal "utf-8", @response.charset
end
def test_change_for_builder
get :render_change_for_builder
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::Type[:HTML], @response.content_type
assert_equal "utf-8", @response.charset
end
@@ -144,24 +144,24 @@ class AcceptBasedContentTypeTest < ActionController::TestCase
tests OldContentTypeController
def test_render_default_content_types_for_respond_to
- @request.accept = Mime::HTML.to_s
+ @request.accept = Mime::Type[:HTML].to_s
get :render_default_content_types_for_respond_to
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::Type[:HTML], @response.content_type
- @request.accept = Mime::JS.to_s
+ @request.accept = Mime::Type[:JS].to_s
get :render_default_content_types_for_respond_to
- assert_equal Mime::JS, @response.content_type
+ assert_equal Mime::Type[:JS], @response.content_type
end
def test_render_default_content_types_for_respond_to_with_template
- @request.accept = Mime::XML.to_s
+ @request.accept = Mime::Type[:XML].to_s
get :render_default_content_types_for_respond_to
- assert_equal Mime::XML, @response.content_type
+ assert_equal Mime::Type[:XML], @response.content_type
end
def test_render_default_content_types_for_respond_to_with_overwrite
- @request.accept = Mime::RSS.to_s
+ @request.accept = Mime::Type[:RSS].to_s
get :render_default_content_types_for_respond_to
- assert_equal Mime::XML, @response.content_type
+ assert_equal Mime::Type[:XML], @response.content_type
end
end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index e9c19b7acf..4d1c23cbee 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -112,7 +112,7 @@ module ActionController
class TestController < ActionController::Base
include ActionController::Live
- attr_accessor :latch, :tc
+ attr_accessor :latch, :tc, :error_latch
def self.controller_path
'test'
@@ -204,6 +204,12 @@ module ActionController
end
def overfill_buffer_and_die
+ logger = ActionController::Base.logger || Logger.new($stdout)
+ response.stream.on_error do
+ logger.warn 'Error while streaming'
+ error_latch.count_down
+ end
+
# Write until the buffer is full. It doesn't expose that
# information directly, so we must hard-code its size:
10.times do
@@ -256,20 +262,12 @@ module ActionController
end
def test_set_cookie
- @controller = TestController.new
get :set_cookie
assert_equal({'hello' => 'world'}, @response.cookies)
assert_equal "hello world", @response.body
end
- def test_set_response!
- @controller.set_response!(@request)
- assert_kind_of(Live::Response, @controller.response)
- assert_equal @request, @controller.response.request
- end
-
def test_write_to_stream
- @controller = TestController.new
get :basic_stream
assert_equal "helloworld", @response.body
assert_equal 'text/event-stream', @response.headers['Content-Type']
@@ -281,10 +279,9 @@ module ActionController
@controller.latch = Concurrent::CountDownLatch.new
parts = ['hello', 'world']
- @controller.request = @request
- @controller.response = @response
+ get :blocking_stream
- t = Thread.new(@response) { |resp|
+ t = Thread.new(response) { |resp|
resp.await_commit
resp.stream.each do |part|
assert_equal parts.shift, part
@@ -294,38 +291,28 @@ module ActionController
end
}
- @controller.process :blocking_stream
-
assert t.join(3), 'timeout expired before the thread terminated'
end
def test_abort_with_full_buffer
@controller.latch = Concurrent::CountDownLatch.new
-
- @request.parameters[:format] = 'plain'
- @controller.request = @request
- @controller.response = @response
-
- got_error = Concurrent::CountDownLatch.new
- @response.stream.on_error do
- ActionController::Base.logger.warn 'Error while streaming'
- got_error.count_down
- end
-
- t = Thread.new(@response) { |resp|
- resp.await_commit
- _, _, body = resp.to_a
- body.each do
- @controller.latch.wait
- body.close
- break
- end
- }
+ @controller.error_latch = Concurrent::CountDownLatch.new
capture_log_output do |output|
- @controller.process :overfill_buffer_and_die
+ get :overfill_buffer_and_die, :format => 'plain'
+
+ t = Thread.new(response) { |resp|
+ resp.await_commit
+ _, _, body = resp.to_a
+ body.each do
+ @controller.latch.wait
+ body.close
+ break
+ end
+ }
+
t.join
- got_error.wait
+ @controller.error_latch.wait
assert_match 'Error while streaming', output.rewind && output.read
end
end
@@ -333,20 +320,18 @@ module ActionController
def test_ignore_client_disconnect
@controller.latch = Concurrent::CountDownLatch.new
- @controller.request = @request
- @controller.response = @response
+ capture_log_output do |output|
+ get :ignore_client_disconnect
- t = Thread.new(@response) { |resp|
- resp.await_commit
- _, _, body = resp.to_a
- body.each do
- body.close
- break
- end
- }
+ t = Thread.new(response) { |resp|
+ resp.await_commit
+ _, _, body = resp.to_a
+ body.each do
+ body.close
+ break
+ end
+ }
- capture_log_output do |output|
- @controller.process :ignore_client_disconnect
t.join
Timeout.timeout(3) do
@controller.latch.wait
@@ -364,11 +349,8 @@ module ActionController
end
def test_live_stream_default_header
- @controller.request = @request
- @controller.response = @response
- @controller.process :default_header
- _, headers, _ = @response.prepare!
- assert headers['Content-Type']
+ get :default_header
+ assert response.headers['Content-Type']
end
def test_render_text
@@ -437,13 +419,13 @@ module ActionController
def test_stale_without_etag
get :with_stale
- assert_equal 200, @response.status.to_i
+ assert_equal 200, response.status.to_i
end
def test_stale_with_etag
@request.if_none_match = Digest::MD5.hexdigest("123")
get :with_stale
- assert_equal 304, @response.status.to_i
+ assert_equal 304, response.status.to_i
end
end
diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb
index 0445a837ca..d9899fe01f 100644
--- a/actionpack/test/controller/new_base/content_type_test.rb
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -7,12 +7,12 @@ module ContentType
end
def set_on_response_obj
- response.content_type = Mime::RSS
+ response.content_type = Mime::Type[:RSS]
render body: "Hello world!"
end
def set_on_render
- render body: "Hello world!", content_type: Mime::RSS
+ render body: "Hello world!", content_type: Mime::Type[:RSS]
end
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 91b30ede6a..631ff7d02a 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -266,15 +266,17 @@ class RedirectTest < ActionController::TestCase
end
def test_redirect_to_nil
- assert_raise(ActionController::ActionControllerError) do
+ error = assert_raise(ActionController::ActionControllerError) do
get :redirect_to_nil
end
+ assert_equal "Cannot redirect to nil!", error.message
end
def test_redirect_to_params
- assert_raise(ActionController::ActionControllerError) do
+ error = assert_raise(ActionController::ActionControllerError) do
get :redirect_to_params
end
+ assert_equal "Cannot redirect to a parameter hash!", error.message
end
def test_redirect_to_with_block
diff --git a/actionpack/test/controller/render_other_test.rb b/actionpack/test/controller/render_other_test.rb
index af50e11261..8891f6177f 100644
--- a/actionpack/test/controller/render_other_test.rb
+++ b/actionpack/test/controller/render_other_test.rb
@@ -12,7 +12,7 @@ class RenderOtherTest < ActionController::TestCase
def test_using_custom_render_option
ActionController.add_renderer :simon do |says, options|
- self.content_type = Mime::TEXT
+ self.content_type = Mime::Type[:TEXT]
self.response_body = "Simon says: #{says}"
end
diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb
index 7a91577b17..094d3ea1d2 100644
--- a/actionpack/test/controller/render_xml_test.rb
+++ b/actionpack/test/controller/render_xml_test.rb
@@ -92,6 +92,6 @@ class RenderXmlTest < ActionController::TestCase
def test_should_use_implicit_content_type
get :implicit_content_type, format: 'atom'
- assert_equal Mime::ATOM, @response.content_type
+ assert_equal Mime::Type[:ATOM], @response.content_type
end
end
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index fb9883fb18..c712c75c88 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -20,6 +20,47 @@ class SendFileController < ActionController::Base
send_file(file_path, options)
end
+ def test_send_file_headers_bang
+ options = {
+ :type => Mime::Type[:PNG],
+ :disposition => 'disposition',
+ :filename => 'filename'
+ }
+
+ send_data "foo", options
+ end
+
+ def test_send_file_headers_with_disposition_as_a_symbol
+ options = {
+ :type => Mime::Type[:PNG],
+ :disposition => :disposition,
+ :filename => 'filename'
+ }
+
+ send_data "foo", options
+ end
+
+ def test_send_file_headers_with_mime_lookup_with_symbol
+ options = { :type => :png }
+
+ send_data "foo", options
+ end
+
+ def test_send_file_headers_with_bad_symbol
+ options = { :type => :this_type_is_not_registered }
+ send_data "foo", options
+ end
+
+ def test_send_file_headers_with_nil_content_type
+ options = { :type => nil }
+ send_data "foo", options
+ end
+
+ def test_send_file_headers_guess_type_from_extension
+ options = { :filename => params[:filename] }
+ send_data "foo", options
+ end
+
def data
send_data(file_data, options)
end
@@ -88,72 +129,38 @@ class SendFileTest < ActionController::TestCase
# Test that send_file_headers! is setting the correct HTTP headers.
def test_send_file_headers_bang
- options = {
- :type => Mime::PNG,
- :disposition => 'disposition',
- :filename => 'filename'
- }
-
# Do it a few times: the resulting headers should be identical
# no matter how many times you send with the same options.
# Test resolving Ticket #458.
- @controller.headers = {}
- @controller.send(:send_file_headers!, options)
- @controller.send(:send_file_headers!, options)
- @controller.send(:send_file_headers!, options)
-
- h = @controller.headers
- assert_equal 'image/png', @controller.content_type
- assert_equal 'disposition; filename="filename"', h['Content-Disposition']
- assert_equal 'binary', h['Content-Transfer-Encoding']
+ 5.times do
+ get :test_send_file_headers_bang
- # test overriding Cache-Control: no-cache header to fix IE open/save dialog
- @controller.send(:send_file_headers!, options)
- @controller.response.prepare!
- assert_equal 'private', h['Cache-Control']
+ assert_equal 'image/png', response.content_type
+ assert_equal 'disposition; filename="filename"', response.get_header('Content-Disposition')
+ assert_equal 'binary', response.get_header('Content-Transfer-Encoding')
+ assert_equal 'private', response.get_header('Cache-Control')
+ end
end
def test_send_file_headers_with_disposition_as_a_symbol
- options = {
- :type => Mime::PNG,
- :disposition => :disposition,
- :filename => 'filename'
- }
+ get :test_send_file_headers_with_disposition_as_a_symbol
- @controller.headers = {}
- @controller.send(:send_file_headers!, options)
- assert_equal 'disposition; filename="filename"', @controller.headers['Content-Disposition']
+ assert_equal 'disposition; filename="filename"', response.get_header('Content-Disposition')
end
def test_send_file_headers_with_mime_lookup_with_symbol
- options = {
- :type => :png
- }
-
- @controller.headers = {}
- @controller.send(:send_file_headers!, options)
-
- assert_equal 'image/png', @controller.content_type
+ get __method__
+ assert_equal 'image/png', response.content_type
end
def test_send_file_headers_with_bad_symbol
- options = {
- :type => :this_type_is_not_registered
- }
-
- @controller.headers = {}
- error = assert_raise(ArgumentError) { @controller.send(:send_file_headers!, options) }
- assert_equal "Unknown MIME type #{options[:type]}", error.message
+ error = assert_raise(ArgumentError) { get __method__ }
+ assert_equal "Unknown MIME type this_type_is_not_registered", error.message
end
def test_send_file_headers_with_nil_content_type
- options = {
- :type => nil
- }
-
- @controller.headers = {}
- error = assert_raise(ArgumentError) { @controller.send(:send_file_headers!, options) }
+ error = assert_raise(ArgumentError) { get __method__ }
assert_equal ":type option required", error.message
end
@@ -169,10 +176,8 @@ class SendFileTest < ActionController::TestCase
'file.unk' => 'application/octet-stream',
'zip' => 'application/octet-stream'
}.each do |filename,expected_type|
- options = { :filename => filename }
- @controller.headers = {}
- @controller.send(:send_file_headers!, options)
- assert_equal expected_type, @controller.content_type
+ get __method__, params: { filename: filename }
+ assert_equal expected_type, response.content_type
end
end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 06bf9dec74..40c97abd35 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -974,6 +974,11 @@ class ResponseDefaultHeadersTest < ActionController::TestCase
headers.delete params[:header]
head :ok, 'C' => '3'
end
+
+ # Render a head response, but don't touch default headers
+ def leave_alone
+ head :ok
+ end
end
def before_setup
@@ -999,9 +1004,13 @@ class ResponseDefaultHeadersTest < ActionController::TestCase
end
test "response contains default headers" do
+ get :leave_alone
+
# Response headers start out with the defaults
- assert_equal @defaults, response.headers
+ assert_equal @defaults.merge('Content-Type' => 'text/html'), response.headers
+ end
+ test "response deletes a default header" do
get :remove_header, params: { header: 'A' }
assert_response :ok
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index 8b37c9599e..2aee914a24 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -65,7 +65,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_register_and_use_json_simple
with_test_route_set do
- with_params_parsers Mime::JSON => Proc.new { |data| ActiveSupport::JSON.decode(data)['request'].with_indifferent_access } do
+ with_params_parsers Mime::Type[:JSON] => Proc.new { |data| ActiveSupport::JSON.decode(data)['request'].with_indifferent_access } do
post "/",
params: '{"request":{"summary":"content...","title":"JSON"}}',
headers: { 'CONTENT_TYPE' => 'application/json' }
@@ -99,7 +99,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_parsing_json_doesnot_rescue_exception
req = Class.new(ActionDispatch::Request) do
def params_parsers
- { Mime::JSON => Proc.new { |data| raise Interrupt } }
+ { Mime::Type[:JSON] => Proc.new { |data| raise Interrupt } }
end
def content_length; get_header('rack.input').length; end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 3017a9c2d6..d9fb44f06d 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -13,76 +13,75 @@ class MimeTypeTest < ActiveSupport::TestCase
test "unregister" do
begin
Mime::Type.register("text/x-mobile", :mobile)
- assert defined?(Mime::MOBILE)
- assert_equal Mime::MOBILE, Mime::LOOKUP['text/x-mobile']
- assert_equal Mime::MOBILE, Mime::EXTENSION_LOOKUP['mobile']
+ assert Mime::Type.registered?(:MOBILE)
+ assert_equal Mime::Type[:MOBILE], Mime::LOOKUP['text/x-mobile']
+ assert_equal Mime::Type[:MOBILE], Mime::EXTENSION_LOOKUP['mobile']
Mime::Type.unregister(:mobile)
- assert !defined?(Mime::MOBILE), "Mime::MOBILE should not be defined"
+ assert !Mime.const_defined?(:MOBILE), "Mime::MOBILE should not be defined"
assert !Mime::LOOKUP.has_key?('text/x-mobile'), "Mime::LOOKUP should not have key ['text/x-mobile]"
assert !Mime::EXTENSION_LOOKUP.has_key?('mobile'), "Mime::EXTENSION_LOOKUP should not have key ['mobile]"
ensure
- Mime.module_eval { remove_const :MOBILE if const_defined?(:MOBILE) }
Mime::LOOKUP.reject!{|key,_| key == 'text/x-mobile'}
end
end
test "parse text with trailing star at the beginning" do
accept = "text/*, text/html, application/json, multipart/form-data"
- expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML, Mime::JSON, Mime::MULTIPART_FORM]
+ expect = [Mime::Type[:HTML], Mime::Type[:TEXT], Mime::Type[:JS], Mime::Type[:CSS], Mime::Type[:ICS], Mime::Type[:CSV], Mime::Type[:VCF], Mime::Type[:XML], Mime::Type[:YAML], Mime::Type[:JSON], Mime::Type[:MULTIPART_FORM]]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
test "parse text with trailing star in the end" do
accept = "text/html, application/json, multipart/form-data, text/*"
- expect = [Mime::HTML, Mime::JSON, Mime::MULTIPART_FORM, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML]
+ expect = [Mime::Type[:HTML], Mime::Type[:JSON], Mime::Type[:MULTIPART_FORM], Mime::Type[:TEXT], Mime::Type[:JS], Mime::Type[:CSS], Mime::Type[:ICS], Mime::Type[:CSV], Mime::Type[:VCF], Mime::Type[:XML], Mime::Type[:YAML]]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
test "parse text with trailing star" do
accept = "text/*"
- expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML, Mime::JSON]
+ expect = [Mime::Type[:HTML], Mime::Type[:TEXT], Mime::Type[:JS], Mime::Type[:CSS], Mime::Type[:ICS], Mime::Type[:CSV], Mime::Type[:VCF], Mime::Type[:XML], Mime::Type[:YAML], Mime::Type[:JSON]]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
test "parse application with trailing star" do
accept = "application/*"
- expect = [Mime::HTML, Mime::JS, Mime::XML, Mime::RSS, Mime::ATOM, Mime::YAML, Mime::URL_ENCODED_FORM, Mime::JSON, Mime::PDF, Mime::ZIP]
+ expect = [Mime::Type[:HTML], Mime::Type[:JS], Mime::Type[:XML], Mime::Type[:RSS], Mime::Type[:ATOM], Mime::Type[:YAML], Mime::Type[:URL_ENCODED_FORM], Mime::Type[:JSON], Mime::Type[:PDF], Mime::Type[:ZIP]]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
test "parse without q" do
accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,application/pdf,*/*"
- expect = [Mime::HTML, Mime::XML, Mime::YAML, Mime::PNG, Mime::TEXT, Mime::PDF, Mime::ALL]
+ expect = [Mime::Type[:HTML], Mime::Type[:XML], Mime::Type[:YAML], Mime::Type[:PNG], Mime::Type[:TEXT], Mime::Type[:PDF], Mime::Type[:ALL]]
assert_equal expect, Mime::Type.parse(accept)
end
test "parse with q" do
accept = "text/xml,application/xhtml+xml,text/yaml; q=0.3,application/xml,text/html; q=0.8,image/png,text/plain; q=0.5,application/pdf,*/*; q=0.2"
- expect = [Mime::HTML, Mime::XML, Mime::PNG, Mime::PDF, Mime::TEXT, Mime::YAML, Mime::ALL]
+ expect = [Mime::Type[:HTML], Mime::Type[:XML], Mime::Type[:PNG], Mime::Type[:PDF], Mime::Type[:TEXT], Mime::Type[:YAML], Mime::Type[:ALL]]
assert_equal expect, Mime::Type.parse(accept)
end
test "parse single media range with q" do
accept = "text/html;q=0.9"
- expect = [Mime::HTML]
+ expect = [Mime::Type[:HTML]]
assert_equal expect, Mime::Type.parse(accept)
end
test "parse arbitrary media type parameters" do
accept = 'multipart/form-data; boundary="simple boundary"'
- expect = [Mime::MULTIPART_FORM]
+ expect = [Mime::Type[:MULTIPART_FORM]]
assert_equal expect, Mime::Type.parse(accept)
end
# Accept header send with user HTTP_USER_AGENT: Sunrise/0.42j (Windows XP)
test "parse broken acceptlines" do
accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/*,,*/*;q=0.5"
- expect = [Mime::HTML, Mime::XML, "image/*", Mime::TEXT, Mime::ALL]
+ expect = [Mime::Type[:HTML], Mime::Type[:XML], "image/*", Mime::Type[:TEXT], Mime::Type[:ALL]]
assert_equal expect, Mime::Type.parse(accept).collect(&:to_s)
end
@@ -90,16 +89,15 @@ class MimeTypeTest < ActiveSupport::TestCase
# (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1)
test "parse other broken acceptlines" do
accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, , pronto/1.00.00, sslvpn/1.00.00.00, */*"
- expect = ['image/gif', 'image/x-xbitmap', 'image/jpeg','image/pjpeg', 'application/x-shockwave-flash', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/msword', 'pronto/1.00.00', 'sslvpn/1.00.00.00', Mime::ALL]
+ expect = ['image/gif', 'image/x-xbitmap', 'image/jpeg','image/pjpeg', 'application/x-shockwave-flash', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/msword', 'pronto/1.00.00', 'sslvpn/1.00.00.00', Mime::Type[:ALL]]
assert_equal expect, Mime::Type.parse(accept).collect(&:to_s)
end
test "custom type" do
begin
- Mime::Type.register("image/foo", :foo)
- assert_nothing_raised do
- assert_equal Mime::FOO, Mime::SET.last
- end
+ type = Mime::Type.register("image/foo", :foo)
+ assert_equal Mime::Type[:FOO], type
+ assert Mime::Type.registered?(:FOO)
ensure
Mime::Type.unregister(:FOO)
end
@@ -109,7 +107,7 @@ class MimeTypeTest < ActiveSupport::TestCase
begin
Mime::Type.register "text/foobar", :foobar, ["text/foo", "text/bar"]
%w[text/foobar text/foo text/bar].each do |type|
- assert_equal Mime::FOOBAR, type
+ assert_equal Mime::Type[:FOOBAR], type
end
ensure
Mime::Type.unregister(:FOOBAR)
@@ -124,7 +122,7 @@ class MimeTypeTest < ActiveSupport::TestCase
end
Mime::Type.register("text/foo", :foo)
- assert_equal [Mime::FOO], registered_mimes
+ assert_equal [Mime::Type[:FOO]], registered_mimes
ensure
Mime::Type.unregister(:FOO)
end
@@ -134,7 +132,7 @@ class MimeTypeTest < ActiveSupport::TestCase
begin
Mime::Type.register "text/foobar", :foobar, [], [:foo, "bar"]
%w[foobar foo bar].each do |extension|
- assert_equal Mime::FOOBAR, Mime::EXTENSION_LOOKUP[extension]
+ assert_equal Mime::Type[:FOOBAR], Mime::EXTENSION_LOOKUP[extension]
end
ensure
Mime::Type.unregister(:FOOBAR)
@@ -144,15 +142,15 @@ class MimeTypeTest < ActiveSupport::TestCase
test "register alias" do
begin
Mime::Type.register_alias "application/xhtml+xml", :foobar
- assert_equal Mime::HTML, Mime::EXTENSION_LOOKUP['foobar']
+ assert_equal Mime::Type[:HTML], Mime::EXTENSION_LOOKUP['foobar']
ensure
Mime::Type.unregister(:FOOBAR)
end
end
test "type should be equal to symbol" do
- assert_equal Mime::HTML, 'application/xhtml+xml'
- assert_equal Mime::HTML, :html
+ assert_equal Mime::Type[:HTML], 'application/xhtml+xml'
+ assert_equal Mime::Type[:HTML], :html
end
test "type convenience methods" do
@@ -160,44 +158,51 @@ class MimeTypeTest < ActiveSupport::TestCase
types = Mime::SET.symbols.uniq - [:all, :iphone]
# Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
- types.delete_if { |type| !Mime.const_defined?(type.upcase) }
-
+ types.delete_if { |type| !Mime::Type.registered?(type.upcase) }
types.each do |type|
- mime = Mime.const_get(type.upcase)
+ mime = Mime::Type[type.upcase]
assert mime.respond_to?("#{type}?"), "#{mime.inspect} does not respond to #{type}?"
- assert mime.send("#{type}?"), "#{mime.inspect} is not #{type}?"
+ assert_equal type, mime.symbol, "#{mime.inspect} is not #{type}?"
invalid_types = types - [type]
- invalid_types.delete(:html) if Mime::Type.html_types.include?(type)
- invalid_types.each { |other_type| assert !mime.send("#{other_type}?"), "#{mime.inspect} is #{other_type}?" }
+ invalid_types.delete(:html)
+ invalid_types.each { |other_type|
+ assert_not_equal mime.symbol, other_type, "#{mime.inspect} is #{other_type}?"
+ }
end
end
test "mime all is html" do
- assert Mime::ALL.all?, "Mime::ALL is not all?"
- assert Mime::ALL.html?, "Mime::ALL is not html?"
+ assert Mime::Type[:ALL].all?, "Mime::ALL is not all?"
+ assert Mime::Type[:ALL].html?, "Mime::ALL is not html?"
+ end
+
+ test "deprecated lookup" do
+ assert_deprecated do
+ assert Mime::ALL.all?, "Mime::ALL is not all?"
+ end
end
test "verifiable mime types" do
all_types = Mime::SET.symbols
all_types.uniq!
# Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
- all_types.delete_if { |type| !Mime.const_defined?(type.upcase) }
+ all_types.delete_if { |type| !Mime::Type.registered?(type.upcase) }
end
test "references gives preference to symbols before strings" do
- assert_equal :html, Mime::HTML.ref
+ assert_equal :html, Mime::Type[:HTML].ref
another = Mime::Type.lookup("foo/bar")
assert_nil another.to_sym
assert_equal "foo/bar", another.ref
end
test "regexp matcher" do
- assert Mime::JS =~ "text/javascript"
- assert Mime::JS =~ "application/javascript"
- assert Mime::JS !~ "text/html"
- assert !(Mime::JS !~ "text/javascript")
- assert !(Mime::JS !~ "application/javascript")
- assert Mime::HTML =~ 'application/xhtml+xml'
+ assert Mime::Type[:JS] =~ "text/javascript"
+ assert Mime::Type[:JS] =~ "application/javascript"
+ assert Mime::Type[:JS] !~ "text/html"
+ assert !(Mime::Type[:JS] !~ "text/javascript")
+ assert !(Mime::Type[:JS] !~ "application/javascript")
+ assert Mime::Type[:HTML] =~ 'application/xhtml+xml'
end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 258d097b7c..40866595ed 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -750,33 +750,33 @@ class RequestFormat < BaseRequestTest
test "xml format" do
request = stub_request
assert_called(request, :parameters, times: 2, returns: {format: :xml}) do
- assert_equal Mime::XML, request.format
+ assert_equal Mime::Type[:XML], request.format
end
end
test "xhtml format" do
request = stub_request
assert_called(request, :parameters, times: 2, returns: {format: :xhtml}) do
- assert_equal Mime::HTML, request.format
+ assert_equal Mime::Type[:HTML], request.format
end
end
test "txt format" do
request = stub_request
assert_called(request, :parameters, times: 2, returns: {format: :txt}) do
- assert_equal Mime::TEXT, request.format
+ assert_equal Mime::Type[:TEXT], request.format
end
end
test "XMLHttpRequest" do
request = stub_request(
'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest',
- 'HTTP_ACCEPT' => [Mime::JS, Mime::HTML, Mime::XML, "text/xml", Mime::ALL].join(",")
+ 'HTTP_ACCEPT' => [Mime::Type[:JS], Mime::Type[:HTML], Mime::Type[:XML], "text/xml", Mime::Type[:ALL]].join(",")
)
assert_called(request, :parameters, times: 1, returns: {}) do
assert request.xhr?
- assert_equal Mime::JS, request.format
+ assert_equal Mime::Type[:JS], request.format
end
end
@@ -796,29 +796,29 @@ class RequestFormat < BaseRequestTest
test "formats text/html with accept header" do
request = stub_request 'HTTP_ACCEPT' => 'text/html'
- assert_equal [Mime::HTML], request.formats
+ assert_equal [Mime::Type[:HTML]], request.formats
end
test "formats blank with accept header" do
request = stub_request 'HTTP_ACCEPT' => ''
- assert_equal [Mime::HTML], request.formats
+ assert_equal [Mime::Type[:HTML]], request.formats
end
test "formats XMLHttpRequest with accept header" do
request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
- assert_equal [Mime::JS], request.formats
+ assert_equal [Mime::Type[:JS]], request.formats
end
test "formats application/xml with accept header" do
request = stub_request('CONTENT_TYPE' => 'application/xml; charset=UTF-8',
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest")
- assert_equal [Mime::XML], request.formats
+ assert_equal [Mime::Type[:XML]], request.formats
end
test "formats format:text with accept header" do
request = stub_request
assert_called(request, :parameters, times: 2, returns: {format: :txt}) do
- assert_equal [Mime::TEXT], request.formats
+ assert_equal [Mime::Type[:TEXT]], request.formats
end
end
@@ -848,7 +848,7 @@ class RequestFormat < BaseRequestTest
test "formats with xhr request" do
request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [Mime::JS], request.formats
+ assert_equal [Mime::Type[:JS]], request.formats
end
end
@@ -859,35 +859,35 @@ class RequestFormat < BaseRequestTest
begin
request = stub_request 'HTTP_ACCEPT' => 'application/xml'
assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime::HTML ], request.formats
+ assert_equal [ Mime::Type[:HTML] ], request.formats
end
request = stub_request 'HTTP_ACCEPT' => 'koz-asked/something-crazy'
assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime::HTML ], request.formats
+ assert_equal [ Mime::Type[:HTML] ], request.formats
end
request = stub_request 'HTTP_ACCEPT' => '*/*;q=0.1'
assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime::HTML ], request.formats
+ assert_equal [ Mime::Type[:HTML] ], request.formats
end
request = stub_request 'HTTP_ACCEPT' => 'application/jxw'
assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime::HTML ], request.formats
+ assert_equal [ Mime::Type[:HTML] ], request.formats
end
request = stub_request 'HTTP_ACCEPT' => 'application/xml',
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime::JS ], request.formats
+ assert_equal [ Mime::Type[:JS] ], request.formats
end
request = stub_request 'HTTP_ACCEPT' => 'application/xml',
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
assert_called(request, :parameters, times: 2, returns: {format: :json}) do
- assert_equal [ Mime::JSON ], request.formats
+ assert_equal [ Mime::Type[:JSON] ], request.formats
end
ensure
ActionDispatch::Request.ignore_accept_header = old_ignore_accept_header
@@ -897,7 +897,7 @@ end
class RequestMimeType < BaseRequestTest
test "content type" do
- assert_equal Mime::HTML, stub_request('CONTENT_TYPE' => 'text/html').content_mime_type
+ assert_equal Mime::Type[:HTML], stub_request('CONTENT_TYPE' => 'text/html').content_mime_type
end
test "no content type" do
@@ -905,11 +905,11 @@ class RequestMimeType < BaseRequestTest
end
test "content type is XML" do
- assert_equal Mime::XML, stub_request('CONTENT_TYPE' => 'application/xml').content_mime_type
+ assert_equal Mime::Type[:XML], stub_request('CONTENT_TYPE' => 'application/xml').content_mime_type
end
test "content type with charset" do
- assert_equal Mime::XML, stub_request('CONTENT_TYPE' => 'application/xml; charset=UTF-8').content_mime_type
+ assert_equal Mime::Type[:XML], stub_request('CONTENT_TYPE' => 'application/xml; charset=UTF-8').content_mime_type
end
test "user agent" do
@@ -922,9 +922,9 @@ class RequestMimeType < BaseRequestTest
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
)
- assert_equal nil, request.negotiate_mime([Mime::XML, Mime::JSON])
- assert_equal Mime::HTML, request.negotiate_mime([Mime::XML, Mime::HTML])
- assert_equal Mime::HTML, request.negotiate_mime([Mime::XML, Mime::ALL])
+ assert_equal nil, request.negotiate_mime([Mime::Type[:XML], Mime::Type[:JSON]])
+ assert_equal Mime::Type[:HTML], request.negotiate_mime([Mime::Type[:XML], Mime::Type[:HTML]])
+ assert_equal Mime::Type[:HTML], request.negotiate_mime([Mime::Type[:XML], Mime::Type[:ALL]])
end
test "negotiate_mime with content_type" do
@@ -933,7 +933,7 @@ class RequestMimeType < BaseRequestTest
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
)
- assert_equal Mime::XML, request.negotiate_mime([Mime::XML, Mime::CSV])
+ assert_equal Mime::Type[:XML], request.negotiate_mime([Mime::Type[:XML], Mime::Type[:CSV]])
end
end
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 5d74424de7..85cdcda655 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -4,7 +4,7 @@ require 'rack/content_length'
class ResponseTest < ActiveSupport::TestCase
def setup
- @response = ActionDispatch::Response.new
+ @response = ActionDispatch::Response.create
end
def test_can_wait_until_commit
@@ -149,12 +149,19 @@ class ResponseTest < ActiveSupport::TestCase
status, headers, body = @response.to_a
assert_equal "user_name=david; path=/", headers["Set-Cookie"]
assert_equal({"user_name" => "david"}, @response.cookies)
+ end
+ test "multiple cookies" do
+ @response.set_cookie("user_name", :value => "david", :path => "/")
@response.set_cookie("login", :value => "foo&bar", :path => "/", :expires => Time.utc(2005, 10, 10,5))
status, headers, body = @response.to_a
assert_equal "user_name=david; path=/\nlogin=foo%26bar; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000", headers["Set-Cookie"]
assert_equal({"login" => "foo&bar", "user_name" => "david"}, @response.cookies)
+ end
+ test "delete cookies" do
+ @response.set_cookie("user_name", :value => "david", :path => "/")
+ @response.set_cookie("login", :value => "foo&bar", :path => "/", :expires => Time.utc(2005, 10, 10,5))
@response.delete_cookie("login")
status, headers, body = @response.to_a
assert_equal({"user_name" => "david", "login" => nil}, @response.cookies)
@@ -178,13 +185,13 @@ class ResponseTest < ActiveSupport::TestCase
test "read charset and content type" do
resp = ActionDispatch::Response.new.tap { |response|
response.charset = 'utf-16'
- response.content_type = Mime::XML
+ response.content_type = Mime::Type[:XML]
response.body = 'Hello'
}
resp.to_a
assert_equal('utf-16', resp.charset)
- assert_equal(Mime::XML, resp.content_type)
+ assert_equal(Mime::Type[:XML], resp.content_type)
assert_equal('application/xml; charset=utf-16', resp.headers['Content-Type'])
end
@@ -210,7 +217,7 @@ class ResponseTest < ActiveSupport::TestCase
'X-Content-Type-Options' => 'nosniff',
'X-XSS-Protection' => '1;'
}
- resp = ActionDispatch::Response.new.tap { |response|
+ resp = ActionDispatch::Response.create.tap { |response|
response.body = 'Hello'
}
resp.to_a
@@ -229,7 +236,7 @@ class ResponseTest < ActiveSupport::TestCase
ActionDispatch::Response.default_headers = {
'X-XX-XXXX' => 'Here is my phone number'
}
- resp = ActionDispatch::Response.new.tap { |response|
+ resp = ActionDispatch::Response.create.tap { |response|
response.body = 'Hello'
}
resp.to_a
@@ -317,7 +324,7 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
@app = lambda { |env|
ActionDispatch::Response.new.tap { |resp|
resp.charset = 'utf-16'
- resp.content_type = Mime::XML
+ resp.content_type = Mime::Type[:XML]
resp.body = 'Hello'
}.to_a
}
@@ -326,7 +333,7 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal('utf-16', @response.charset)
- assert_equal(Mime::XML, @response.content_type)
+ assert_equal(Mime::Type[:XML], @response.content_type)
assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type'])
end
@@ -342,7 +349,7 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal('utf-16', @response.charset)
- assert_equal(Mime::XML, @response.content_type)
+ assert_equal(Mime::Type[:XML], @response.content_type)
assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type'])
end
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index ebf5ea5b77..5a9a5d6d18 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -849,8 +849,8 @@ module ActionView
# file_field(:user, :avatar)
# # => <input type="file" id="user_avatar" name="user[avatar]" />
#
- # file_field(:post, :image, :multiple => true)
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
+ # file_field(:post, :image, multiple: true)
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
#
# file_field(:post, :attached, accept: 'text/html')
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index 470dc86d6c..d7182d1fac 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -139,7 +139,7 @@ module ActionView
# number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
# number_to_percentage(1000, locale: :fr) # => 1 000,000%
# number_to_percentage("98a") # => 98a%
- # number_to_percentage(100, format: "%n %") # => 100 %
+ # number_to_percentage(100, format: "%n %") # => 100.000 %
#
# number_to_percentage("98a", raise: true) # => InvalidNumberError
def number_to_percentage(number, options = {})
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 8b7ede250a..ec6edfaaa3 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -20,7 +20,7 @@ module ActionView
mattr_accessor :registered_details
self.registered_details = []
- def self.register_detail(name, options = {}, &block)
+ def self.register_detail(name, &block)
self.registered_details << name
initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
@@ -55,7 +55,7 @@ module ActionView
end
register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
register_detail(:variants) { [] }
- register_detail(:handlers){ Template::Handlers.extensions }
+ register_detail(:handlers) { Template::Handlers.extensions }
class DetailsKey #:nodoc:
alias :eql? :equal?
diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb
index 14dafd7d63..1184cf7da8 100644
--- a/actionview/test/template/lookup_context_test.rb
+++ b/actionview/test/template/lookup_context_test.rb
@@ -27,7 +27,7 @@ class LookupContextTest < ActiveSupport::TestCase
end
test "normalizes details on initialization" do
- assert_equal Mime::SET, @lookup_context.formats
+ assert_equal Mime::SET.to_a, @lookup_context.formats
assert_equal :en, @lookup_context.locale
end
@@ -48,11 +48,11 @@ class LookupContextTest < ActiveSupport::TestCase
test "handles */* formats" do
@lookup_context.formats = ["*/*"]
- assert_equal Mime::SET, @lookup_context.formats
+ assert_equal Mime::SET.to_a, @lookup_context.formats
end
test "handles explicitly defined */* formats fallback to :js" do
- @lookup_context.formats = [:js, Mime::ALL]
+ @lookup_context.formats = [:js, Mime::Type[:ALL]]
assert_equal [:js, *Mime::SET.symbols], @lookup_context.formats
end
diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb
index 55f690bda8..7e86415f48 100644
--- a/activejob/test/helper.rb
+++ b/activejob/test/helper.rb
@@ -11,6 +11,7 @@ GlobalID.app = 'aj'
if ENV['AJ_INTEGRATION_TESTS']
require 'support/integration/helper'
else
+ ActiveJob::Base.logger = Logger.new(nil)
require "adapters/#{@adapter}"
end
diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb
index d9938600f2..9aa07bcb52 100644
--- a/activejob/test/support/integration/adapters/sidekiq.rb
+++ b/activejob/test/support/integration/adapters/sidekiq.rb
@@ -58,7 +58,7 @@ module SidekiqJobsManager
timeout: 1,
})
Sidekiq.average_scheduled_poll_interval = 0.5
- Sidekiq::Scheduled.const_set :INITIAL_WAIT, 1
+ Sidekiq.options[:poll_interval_average] = 1
begin
sidekiq.run
continue_write.puts "started"
diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb
index 4ffdb8cffa..0c062a025e 100644
--- a/activejob/test/support/integration/dummy_app_template.rb
+++ b/activejob/test/support/integration/dummy_app_template.rb
@@ -1,8 +1,9 @@
if ENV['AJ_ADAPTER'] == 'delayed_job'
generate "delayed_job:active_record", "--quiet"
- rake("db:migrate")
end
+rake("db:migrate")
+
initializer 'activejob.rb', <<-CODE
require "#{File.expand_path("../jobs_manager.rb", __FILE__)}"
JobsManager.current_manager.setup
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 2cf39b68fb..0d6a3dc52d 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -103,6 +103,7 @@ module ActiveModel
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = {
+ terminator: deprecated_false_terminator,
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name],
only: [:before, :around, :after]
diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb
new file mode 100644
index 0000000000..f8ca7d0512
--- /dev/null
+++ b/activemodel/lib/active_model/type.rb
@@ -0,0 +1,57 @@
+require 'active_model/type/helpers'
+require 'active_model/type/value'
+
+require 'active_model/type/big_integer'
+require 'active_model/type/binary'
+require 'active_model/type/boolean'
+require 'active_model/type/date'
+require 'active_model/type/date_time'
+require 'active_model/type/decimal'
+require 'active_model/type/decimal_without_scale'
+require 'active_model/type/float'
+require 'active_model/type/integer'
+require 'active_model/type/string'
+require 'active_model/type/text'
+require 'active_model/type/time'
+require 'active_model/type/unsigned_integer'
+
+require 'active_model/type/registry'
+
+module ActiveModel
+ module Type
+ @registry = Registry.new
+
+ class << self
+ attr_accessor :registry # :nodoc:
+ delegate :add_modifier, to: :registry
+
+ # Add a new type to the registry, allowing it to be referenced as a
+ # symbol by ActiveModel::Attributes::ClassMethods#attribute. If your
+ # type is only meant to be used with a specific database adapter, you can
+ # do so by passing +adapter: :postgresql+. If your type has the same
+ # name as a native type for the current adapter, an exception will be
+ # raised unless you specify an +:override+ option. +override: true+ will
+ # cause your type to be used instead of the native type. +override:
+ # false+ will cause the native type to be used over yours if one exists.
+ def register(type_name, klass = nil, **options, &block)
+ registry.register(type_name, klass, **options, &block)
+ end
+
+ def lookup(*args, **kwargs) # :nodoc:
+ registry.lookup(*args, **kwargs)
+ end
+ end
+
+ register(:big_integer, Type::BigInteger)
+ register(:binary, Type::Binary)
+ register(:boolean, Type::Boolean)
+ register(:date, Type::Date)
+ register(:date_time, Type::DateTime)
+ register(:decimal, Type::Decimal)
+ register(:float, Type::Float)
+ register(:integer, Type::Integer)
+ register(:string, Type::String)
+ register(:text, Type::Text)
+ register(:time, Type::Time)
+ end
+end
diff --git a/activerecord/lib/active_record/type/big_integer.rb b/activemodel/lib/active_model/type/big_integer.rb
index 0c72d8914f..4168cbfce7 100644
--- a/activerecord/lib/active_record/type/big_integer.rb
+++ b/activemodel/lib/active_model/type/big_integer.rb
@@ -1,6 +1,6 @@
-require 'active_record/type/integer'
+require 'active_model/type/integer'
-module ActiveRecord
+module ActiveModel
module Type
class BigInteger < Integer # :nodoc:
private
diff --git a/activerecord/lib/active_record/type/binary.rb b/activemodel/lib/active_model/type/binary.rb
index 0baf8c63ad..a0cc45b4c3 100644
--- a/activerecord/lib/active_record/type/binary.rb
+++ b/activemodel/lib/active_model/type/binary.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
class Binary < Value # :nodoc:
def type
diff --git a/activerecord/lib/active_record/type/boolean.rb b/activemodel/lib/active_model/type/boolean.rb
index f6a75512fd..c1bce98c87 100644
--- a/activerecord/lib/active_record/type/boolean.rb
+++ b/activemodel/lib/active_model/type/boolean.rb
@@ -1,6 +1,8 @@
-module ActiveRecord
+module ActiveModel
module Type
class Boolean < Value # :nodoc:
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
+
def type
:boolean
end
@@ -11,7 +13,7 @@ module ActiveRecord
if value == ''
nil
else
- !ConnectionAdapters::Column::FALSE_VALUES.include?(value)
+ !FALSE_VALUES.include?(value)
end
end
end
diff --git a/activemodel/lib/active_model/type/date.rb b/activemodel/lib/active_model/type/date.rb
new file mode 100644
index 0000000000..f74243a22c
--- /dev/null
+++ b/activemodel/lib/active_model/type/date.rb
@@ -0,0 +1,50 @@
+module ActiveModel
+ module Type
+ class Date < Value # :nodoc:
+ include Helpers::AcceptsMultiparameterTime.new
+
+ def type
+ :date
+ end
+
+ def type_cast_for_schema(value)
+ "'#{value.to_s(:db)}'"
+ end
+
+ private
+
+ def cast_value(value)
+ if value.is_a?(::String)
+ return if value.empty?
+ fast_string_to_date(value) || fallback_string_to_date(value)
+ elsif value.respond_to?(:to_date)
+ value.to_date
+ else
+ value
+ end
+ end
+
+ ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
+ def fast_string_to_date(string)
+ if string =~ ISO_DATE
+ new_date $1.to_i, $2.to_i, $3.to_i
+ end
+ end
+
+ def fallback_string_to_date(string)
+ new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
+ end
+
+ def new_date(year, mon, mday)
+ if year && year != 0
+ ::Date.new(year, mon, mday) rescue nil
+ end
+ end
+
+ def value_from_multiparameter_assignment(*)
+ time = super
+ time && time.to_date
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/type/date_time.rb b/activemodel/lib/active_model/type/date_time.rb
new file mode 100644
index 0000000000..2f2df4320f
--- /dev/null
+++ b/activemodel/lib/active_model/type/date_time.rb
@@ -0,0 +1,44 @@
+module ActiveModel
+ module Type
+ class DateTime < Value # :nodoc:
+ include Helpers::TimeValue
+ include Helpers::AcceptsMultiparameterTime.new(
+ defaults: { 4 => 0, 5 => 0 }
+ )
+
+ def type
+ :datetime
+ end
+
+ private
+
+ def cast_value(value)
+ return apply_seconds_precision(value) unless value.is_a?(::String)
+ return if value.empty?
+
+ fast_string_to_time(value) || fallback_string_to_time(value)
+ end
+
+ # '0.123456' -> 123456
+ # '1.123456' -> 123456
+ def microseconds(time)
+ time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
+ end
+
+ def fallback_string_to_time(string)
+ time_hash = ::Date._parse(string)
+ time_hash[:sec_fraction] = microseconds(time_hash)
+
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
+ end
+
+ def value_from_multiparameter_assignment(values_hash)
+ missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
+ if missing_parameter
+ raise ArgumentError, missing_parameter
+ end
+ super
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/decimal.rb b/activemodel/lib/active_model/type/decimal.rb
index f5b145230d..d19d8baada 100644
--- a/activerecord/lib/active_record/type/decimal.rb
+++ b/activemodel/lib/active_model/type/decimal.rb
@@ -1,4 +1,6 @@
-module ActiveRecord
+require "bigdecimal/util"
+
+module ActiveModel
module Type
class Decimal < Value # :nodoc:
include Helpers::Numeric
diff --git a/activerecord/lib/active_record/type/decimal_without_scale.rb b/activemodel/lib/active_model/type/decimal_without_scale.rb
index ff5559e300..129baa0c10 100644
--- a/activerecord/lib/active_record/type/decimal_without_scale.rb
+++ b/activemodel/lib/active_model/type/decimal_without_scale.rb
@@ -1,6 +1,6 @@
-require 'active_record/type/big_integer'
+require 'active_model/type/big_integer'
-module ActiveRecord
+module ActiveModel
module Type
class DecimalWithoutScale < BigInteger # :nodoc:
def type
diff --git a/activerecord/lib/active_record/type/float.rb b/activemodel/lib/active_model/type/float.rb
index d88482b85d..0f925bc7e1 100644
--- a/activerecord/lib/active_record/type/float.rb
+++ b/activemodel/lib/active_model/type/float.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
class Float < Value # :nodoc:
include Helpers::Numeric
diff --git a/activemodel/lib/active_model/type/helpers.rb b/activemodel/lib/active_model/type/helpers.rb
new file mode 100644
index 0000000000..a805a359ab
--- /dev/null
+++ b/activemodel/lib/active_model/type/helpers.rb
@@ -0,0 +1,4 @@
+require 'active_model/type/helpers/accepts_multiparameter_time'
+require 'active_model/type/helpers/numeric'
+require 'active_model/type/helpers/mutable'
+require 'active_model/type/helpers/time_value'
diff --git a/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb b/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb
index be571fc1c7..fa1ccd057e 100644
--- a/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb
+++ b/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
module Helpers
class AcceptsMultiparameterTime < Module # :nodoc:
@@ -17,10 +17,7 @@ module ActiveRecord
end
return unless values_hash[1] && values_hash[2] && values_hash[3]
values = values_hash.sort.map(&:last)
- ::Time.send(
- ActiveRecord::Base.default_timezone,
- *values
- )
+ ::Time.send(default_timezone, *values)
end
private :value_from_multiparameter_assignment
end
diff --git a/activerecord/lib/active_record/type/helpers/mutable.rb b/activemodel/lib/active_model/type/helpers/mutable.rb
index 88a9099277..4dddbe4e5e 100644
--- a/activerecord/lib/active_record/type/helpers/mutable.rb
+++ b/activemodel/lib/active_model/type/helpers/mutable.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
module Helpers
module Mutable # :nodoc:
diff --git a/activerecord/lib/active_record/type/helpers/numeric.rb b/activemodel/lib/active_model/type/helpers/numeric.rb
index a755a02a59..c883010506 100644
--- a/activerecord/lib/active_record/type/helpers/numeric.rb
+++ b/activemodel/lib/active_model/type/helpers/numeric.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
module Helpers
module Numeric # :nodoc:
diff --git a/activerecord/lib/active_record/type/helpers/time_value.rb b/activemodel/lib/active_model/type/helpers/time_value.rb
index 7eb41557cb..63993c0d93 100644
--- a/activerecord/lib/active_record/type/helpers/time_value.rb
+++ b/activemodel/lib/active_model/type/helpers/time_value.rb
@@ -1,16 +1,14 @@
-module ActiveRecord
+require "active_support/core_ext/time/zones"
+
+module ActiveModel
module Type
module Helpers
module TimeValue # :nodoc:
def serialize(value)
- if precision && value.respond_to?(:usec)
- number_of_insignificant_digits = 6 - precision
- round_power = 10 ** number_of_insignificant_digits
- value = value.change(usec: value.usec / round_power * round_power)
- end
+ value = apply_seconds_precision(value)
if value.acts_like?(:time)
- zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
+ zone_conversion_method = is_utc? ? :getutc : :getlocal
if value.respond_to?(zone_conversion_method)
value = value.send(zone_conversion_method)
@@ -20,6 +18,25 @@ module ActiveRecord
value
end
+ def is_utc?
+ ::Time.zone_default.nil? || ::Time.zone_default =~ 'UTC'
+ end
+
+ def default_timezone
+ if is_utc?
+ :utc
+ else
+ :local
+ end
+ end
+
+ def apply_seconds_precision(value)
+ return value unless precision && value.respond_to?(:usec)
+ number_of_insignificant_digits = 6 - precision
+ round_power = 10 ** number_of_insignificant_digits
+ value.change(usec: value.usec / round_power * round_power)
+ end
+
def type_cast_for_schema(value)
"'#{value.to_s(:db)}'"
end
@@ -39,15 +56,17 @@ module ActiveRecord
return unless time
time -= offset
- Base.default_timezone == :utc ? time : time.getlocal
+ is_utc? ? time : time.getlocal
else
- ::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
+ ::Time.public_send(default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
end
end
+ ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
+
# Doesn't handle time zones.
def fast_string_to_time(string)
- if string =~ ConnectionAdapters::Column::Format::ISO_DATETIME
+ if string =~ ISO_DATETIME
microsec = ($7.to_r * 1_000_000).to_i
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
end
diff --git a/activerecord/lib/active_record/type/integer.rb b/activemodel/lib/active_model/type/integer.rb
index c5040c6d3b..2f73ede009 100644
--- a/activerecord/lib/active_record/type/integer.rb
+++ b/activemodel/lib/active_model/type/integer.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
class Integer < Value # :nodoc:
include Helpers::Numeric
diff --git a/activemodel/lib/active_model/type/registry.rb b/activemodel/lib/active_model/type/registry.rb
new file mode 100644
index 0000000000..adc88eb624
--- /dev/null
+++ b/activemodel/lib/active_model/type/registry.rb
@@ -0,0 +1,64 @@
+module ActiveModel
+ # :stopdoc:
+ module Type
+ class Registry
+ def initialize
+ @registrations = []
+ end
+
+ def register(type_name, klass = nil, **options, &block)
+ block ||= proc { |_, *args| klass.new(*args) }
+ registrations << registration_klass.new(type_name, block, **options)
+ end
+
+ def lookup(symbol, *args)
+ registration = find_registration(symbol, *args)
+
+ if registration
+ registration.call(self, symbol, *args)
+ else
+ raise ArgumentError, "Unknown type #{symbol.inspect}"
+ end
+ end
+
+ protected
+
+ attr_reader :registrations
+
+ private
+
+ def registration_klass
+ Registration
+ end
+
+ def find_registration(symbol, *args)
+ registrations.find { |r| r.matches?(symbol, *args) }
+ end
+ end
+
+ class Registration
+ # Options must be taken because of https://bugs.ruby-lang.org/issues/10856
+ def initialize(name, block, **)
+ @name = name
+ @block = block
+ end
+
+ def call(_registry, *args, **kwargs)
+ if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
+ block.call(*args, **kwargs)
+ else
+ block.call(*args)
+ end
+ end
+
+ def matches?(type_name, *args, **kwargs)
+ type_name == name
+ end
+
+ protected
+
+ attr_reader :name, :block
+ end
+ end
+ # :startdoc:
+end
diff --git a/activerecord/lib/active_record/type/string.rb b/activemodel/lib/active_model/type/string.rb
index 2662b7e874..fd1630c751 100644
--- a/activerecord/lib/active_record/type/string.rb
+++ b/activemodel/lib/active_model/type/string.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
class String < Value # :nodoc:
def type
diff --git a/activerecord/lib/active_record/type/text.rb b/activemodel/lib/active_model/type/text.rb
index 26f980f060..1ad04daba4 100644
--- a/activerecord/lib/active_record/type/text.rb
+++ b/activemodel/lib/active_model/type/text.rb
@@ -1,6 +1,6 @@
-require 'active_record/type/string'
+require 'active_model/type/string'
-module ActiveRecord
+module ActiveModel
module Type
class Text < String # :nodoc:
def type
diff --git a/activemodel/lib/active_model/type/time.rb b/activemodel/lib/active_model/type/time.rb
new file mode 100644
index 0000000000..7101bad566
--- /dev/null
+++ b/activemodel/lib/active_model/type/time.rb
@@ -0,0 +1,42 @@
+module ActiveModel
+ module Type
+ class Time < Value # :nodoc:
+ include Helpers::TimeValue
+ include Helpers::AcceptsMultiparameterTime.new(
+ defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
+ )
+
+ def type
+ :time
+ end
+
+ def user_input_in_time_zone(value)
+ return unless value.present?
+
+ case value
+ when ::String
+ value = "2000-01-01 #{value}"
+ when ::Time
+ value = value.change(year: 2000, day: 1, month: 1)
+ end
+
+ super(value)
+ end
+
+ private
+
+ def cast_value(value)
+ return value unless value.is_a?(::String)
+ return if value.empty?
+
+ dummy_time_value = "2000-01-01 #{value}"
+
+ fast_string_to_time(dummy_time_value) || begin
+ time_hash = ::Date._parse(dummy_time_value)
+ return if time_hash[:hour].nil?
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/unsigned_integer.rb b/activemodel/lib/active_model/type/unsigned_integer.rb
index ed3e527483..3f49f9f5f7 100644
--- a/activerecord/lib/active_record/type/unsigned_integer.rb
+++ b/activemodel/lib/active_model/type/unsigned_integer.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
class UnsignedInteger < Integer # :nodoc:
private
diff --git a/activerecord/lib/active_record/type/value.rb b/activemodel/lib/active_model/type/value.rb
index 6b9d147ecc..c7d1197d69 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activemodel/lib/active_model/type/value.rb
@@ -1,4 +1,4 @@
-module ActiveRecord
+module ActiveModel
module Type
class Value
attr_reader :precision, :scale, :limit
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index 4b58ef66e3..52111e5442 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -23,6 +23,7 @@ module ActiveModel
included do
include ActiveSupport::Callbacks
define_callbacks :validation,
+ terminator: deprecated_false_terminator,
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name]
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index bb2f0bd064..8f8ade90bb 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -9,7 +9,7 @@ module ActiveModel
def validate_each(record, attribute, value)
if (confirmed = record.send("#{attribute}_confirmation"))
- unless confimation_value_equal?(record, attribute, value, confirmed)
+ unless confirmation_value_equal?(record, attribute, value, confirmed)
human_attribute_name = record.class.human_attribute_name(attribute)
record.errors.add(:"#{attribute}_confirmation", :confirmation, options.except(:case_sensitive).merge!(attribute: human_attribute_name))
end
@@ -27,7 +27,7 @@ module ActiveModel
end.compact)
end
- def confimation_value_equal?(record, attribute, value, confirmed)
+ def confirmation_value_equal?(record, attribute, value, confirmed)
if !options[:case_sensitive] && value.is_a?(String)
value.casecmp(confirmed) == 0
else
diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb
index 64a85e01eb..3336691841 100644
--- a/activemodel/test/cases/attribute_assignment_test.rb
+++ b/activemodel/test/cases/attribute_assignment_test.rb
@@ -70,7 +70,7 @@ class AttributeAssignmentTest < ActiveModel::TestCase
end
end
- test "an ArgumentError is raised if a non-hash-like obejct is passed" do
+ test "an ArgumentError is raised if a non-hash-like object is passed" do
assert_raises(ArgumentError) do
Model.new(1)
end
diff --git a/activerecord/test/cases/type/decimal_test.rb b/activemodel/test/cases/type/decimal_test.rb
index 01ee36b892..353dbf84ad 100644
--- a/activerecord/test/cases/type/decimal_test.rb
+++ b/activemodel/test/cases/type/decimal_test.rb
@@ -1,8 +1,9 @@
require "cases/helper"
+require "active_model/type"
-module ActiveRecord
+module ActiveModel
module Type
- class DecimalTest < ActiveRecord::TestCase
+ class DecimalTest < ActiveModel::TestCase
def test_type_cast_decimal
type = Decimal.new
assert_equal BigDecimal.new("0"), type.cast(BigDecimal.new("0"))
diff --git a/activemodel/test/cases/type/integer_test.rb b/activemodel/test/cases/type/integer_test.rb
new file mode 100644
index 0000000000..dac922db42
--- /dev/null
+++ b/activemodel/test/cases/type/integer_test.rb
@@ -0,0 +1,108 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ module Type
+ class IntegerTest < ActiveModel::TestCase
+ test "simple values" do
+ type = Type::Integer.new
+ assert_equal 1, type.cast(1)
+ assert_equal 1, type.cast('1')
+ assert_equal 1, type.cast('1ignore')
+ assert_equal 0, type.cast('bad1')
+ assert_equal 0, type.cast('bad')
+ assert_equal 1, type.cast(1.7)
+ assert_equal 0, type.cast(false)
+ assert_equal 1, type.cast(true)
+ assert_nil type.cast(nil)
+ end
+
+ test "random objects cast to nil" do
+ type = Type::Integer.new
+ assert_nil type.cast([1,2])
+ assert_nil type.cast({1 => 2})
+ assert_nil type.cast(1..2)
+ end
+
+ test "casting objects without to_i" do
+ type = Type::Integer.new
+ assert_nil type.cast(::Object.new)
+ end
+
+ test "casting nan and infinity" do
+ type = Type::Integer.new
+ assert_nil type.cast(::Float::NAN)
+ assert_nil type.cast(1.0/0.0)
+ end
+
+ test "casting booleans for database" do
+ type = Type::Integer.new
+ assert_equal 1, type.serialize(true)
+ assert_equal 0, type.serialize(false)
+ end
+
+ test "changed?" do
+ type = Type::Integer.new
+
+ assert type.changed?(5, 5, '5wibble')
+ assert_not type.changed?(5, 5, '5')
+ assert_not type.changed?(5, 5, '5.0')
+ assert_not type.changed?(-5, -5, '-5')
+ assert_not type.changed?(-5, -5, '-5.0')
+ assert_not type.changed?(nil, nil, nil)
+ end
+
+ test "values below int min value are out of range" do
+ assert_raises(::RangeError) do
+ Integer.new.serialize(-2147483649)
+ end
+ end
+
+ test "values above int max value are out of range" do
+ assert_raises(::RangeError) do
+ Integer.new.serialize(2147483648)
+ end
+ end
+
+ test "very small numbers are out of range" do
+ assert_raises(::RangeError) do
+ Integer.new.serialize(-9999999999999999999999999999999)
+ end
+ end
+
+ test "very large numbers are out of range" do
+ assert_raises(::RangeError) do
+ Integer.new.serialize(9999999999999999999999999999999)
+ end
+ end
+
+ test "normal numbers are in range" do
+ type = Integer.new
+ assert_equal(0, type.serialize(0))
+ assert_equal(-1, type.serialize(-1))
+ assert_equal(1, type.serialize(1))
+ end
+
+ test "int max value is in range" do
+ assert_equal(2147483647, Integer.new.serialize(2147483647))
+ end
+
+ test "int min value is in range" do
+ assert_equal(-2147483648, Integer.new.serialize(-2147483648))
+ end
+
+ test "columns with a larger limit have larger ranges" do
+ type = Integer.new(limit: 8)
+
+ assert_equal(9223372036854775807, type.serialize(9223372036854775807))
+ assert_equal(-9223372036854775808, type.serialize(-9223372036854775808))
+ assert_raises(::RangeError) do
+ type.serialize(-9999999999999999999999999999999)
+ end
+ assert_raises(::RangeError) do
+ type.serialize(9999999999999999999999999999999)
+ end
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/type/registry_test.rb b/activemodel/test/cases/type/registry_test.rb
new file mode 100644
index 0000000000..2a48998a62
--- /dev/null
+++ b/activemodel/test/cases/type/registry_test.rb
@@ -0,0 +1,39 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ class RegistryTest < ActiveModel::TestCase
+ test "a class can be registered for a symbol" do
+ registry = Type::Registry.new
+ registry.register(:foo, ::String)
+ registry.register(:bar, ::Array)
+
+ assert_equal "", registry.lookup(:foo)
+ assert_equal [], registry.lookup(:bar)
+ end
+
+ test "a block can be registered" do
+ registry = Type::Registry.new
+ registry.register(:foo) do |*args|
+ [*args, "block for foo"]
+ end
+ registry.register(:bar) do |*args|
+ [*args, "block for bar"]
+ end
+
+ assert_equal [:foo, 1, "block for foo"], registry.lookup(:foo, 1)
+ assert_equal [:foo, 2, "block for foo"], registry.lookup(:foo, 2)
+ assert_equal [:bar, 1, 2, 3, "block for bar"], registry.lookup(:bar, 1, 2, 3)
+ end
+
+ test "a reasonable error is given when no type is found" do
+ registry = Type::Registry.new
+
+ e = assert_raises(ArgumentError) do
+ registry.lookup(:foo)
+ end
+
+ assert_equal "Unknown type :foo", e.message
+ end
+ end
+end
diff --git a/activemodel/test/cases/type/string_test.rb b/activemodel/test/cases/type/string_test.rb
new file mode 100644
index 0000000000..8ec771ea42
--- /dev/null
+++ b/activemodel/test/cases/type/string_test.rb
@@ -0,0 +1,20 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ class StringTypeTest < ActiveModel::TestCase
+ test "type casting" do
+ type = Type::String.new
+ assert_equal "t", type.cast(true)
+ assert_equal "f", type.cast(false)
+ assert_equal "123", type.cast(123)
+ end
+
+ test "values are duped coming out" do
+ s = "foo"
+ type = Type::String.new
+ assert_not_same s, type.cast(s)
+ assert_not_same s, type.deserialize(s)
+ end
+ end
+end
diff --git a/activerecord/test/cases/type/unsigned_integer_test.rb b/activemodel/test/cases/type/unsigned_integer_test.rb
index f2c910eade..16301b3ac0 100644
--- a/activerecord/test/cases/type/unsigned_integer_test.rb
+++ b/activemodel/test/cases/type/unsigned_integer_test.rb
@@ -1,8 +1,9 @@
require "cases/helper"
+require "active_model/type"
-module ActiveRecord
+module ActiveModel
module Type
- class UnsignedIntegerTest < ActiveRecord::TestCase
+ class UnsignedIntegerTest < ActiveModel::TestCase
test "unsigned int max value is in range" do
assert_equal(4294967295, UnsignedInteger.new.serialize(4294967295))
end
diff --git a/activemodel/test/cases/types_test.rb b/activemodel/test/cases/types_test.rb
new file mode 100644
index 0000000000..f937208580
--- /dev/null
+++ b/activemodel/test/cases/types_test.rb
@@ -0,0 +1,122 @@
+require "cases/helper"
+require "active_model/type"
+require "active_support/core_ext/numeric/time"
+
+module ActiveModel
+ class TypesTest < ActiveModel::TestCase
+ def test_type_cast_boolean
+ type = Type::Boolean.new
+ assert type.cast('').nil?
+ assert type.cast(nil).nil?
+
+ assert type.cast(true)
+ assert type.cast(1)
+ assert type.cast('1')
+ assert type.cast('t')
+ assert type.cast('T')
+ assert type.cast('true')
+ assert type.cast('TRUE')
+ assert type.cast('on')
+ assert type.cast('ON')
+ assert type.cast(' ')
+ assert type.cast("\u3000\r\n")
+ assert type.cast("\u0000")
+ assert type.cast('SOMETHING RANDOM')
+
+ # explicitly check for false vs nil
+ assert_equal false, type.cast(false)
+ assert_equal false, type.cast(0)
+ assert_equal false, type.cast('0')
+ assert_equal false, type.cast('f')
+ assert_equal false, type.cast('F')
+ assert_equal false, type.cast('false')
+ assert_equal false, type.cast('FALSE')
+ assert_equal false, type.cast('off')
+ assert_equal false, type.cast('OFF')
+ end
+
+ def test_type_cast_float
+ type = Type::Float.new
+ assert_equal 1.0, type.cast("1")
+ end
+
+ def test_changing_float
+ type = Type::Float.new
+
+ assert type.changed?(5.0, 5.0, '5wibble')
+ assert_not type.changed?(5.0, 5.0, '5')
+ assert_not type.changed?(5.0, 5.0, '5.0')
+ assert_not type.changed?(nil, nil, nil)
+ end
+
+ def test_type_cast_binary
+ type = Type::Binary.new
+ assert_equal nil, type.cast(nil)
+ assert_equal "1", type.cast("1")
+ assert_equal 1, type.cast(1)
+ end
+
+ def test_type_cast_time
+ type = Type::Time.new
+ assert_equal nil, type.cast(nil)
+ assert_equal nil, type.cast('')
+ assert_equal nil, type.cast('ABC')
+
+ time_string = Time.now.utc.strftime("%T")
+ assert_equal time_string, type.cast(time_string).strftime("%T")
+ end
+
+ def test_type_cast_datetime_and_timestamp
+ type = Type::DateTime.new
+ assert_equal nil, type.cast(nil)
+ assert_equal nil, type.cast('')
+ assert_equal nil, type.cast(' ')
+ assert_equal nil, type.cast('ABC')
+
+ datetime_string = Time.now.utc.strftime("%FT%T")
+ assert_equal datetime_string, type.cast(datetime_string).strftime("%FT%T")
+ end
+
+ def test_type_cast_date
+ type = Type::Date.new
+ assert_equal nil, type.cast(nil)
+ assert_equal nil, type.cast('')
+ assert_equal nil, type.cast(' ')
+ assert_equal nil, type.cast('ABC')
+
+ date_string = Time.now.utc.strftime("%F")
+ assert_equal date_string, type.cast(date_string).strftime("%F")
+ end
+
+ def test_type_cast_duration_to_integer
+ type = Type::Integer.new
+ assert_equal 1800, type.cast(30.minutes)
+ assert_equal 7200, type.cast(2.hours)
+ end
+
+ def test_string_to_time_with_timezone
+ ["UTC", "US/Eastern"].each do |zone|
+ with_timezone_config default: zone do
+ type = Type::DateTime.new
+ assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.cast("Wed, 04 Sep 2013 03:00:00 EAT")
+ end
+ end
+ end
+
+ def test_type_equality
+ assert_equal Type::Value.new, Type::Value.new
+ assert_not_equal Type::Value.new, Type::Integer.new
+ assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2)
+ end
+
+ private
+
+ def with_timezone_config(default:)
+ old_zone_default = ::Time.zone_default
+ ::Time.zone_default = ::Time.find_zone(default)
+ yield
+ ensure
+ ::Time.zone_default = old_zone_default
+ end
+ end
+end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 1b016b6e8b..98a5b27854 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,44 @@
+* Fixed taking precision into count when assigning a value to timestamp attribute
+
+ Timestamp column can have less precision than ruby timestamp
+ In result in how big a fraction of a second can be stored in the
+ database.
+
+
+ m = Model.create!
+ m.created_at.usec == m.reload.created_at.usec
+ # => false
+ # due to different precision in Time.now and database column
+
+ If the precision is low enough, (mysql default is 0, so it is always low
+ enough by default) the value changes when model is reloaded from the
+ database. This patch fixes that issue ensuring that any timestamp
+ assigned as an attribute is converted to column precision under the
+ attribute.
+
+ *Bogdan Gusiev*
+
+* Introduce `connection.data_sources` and `connection.data_source_exists?`.
+ These methods determine what relations can be used to back Active Record
+ models (usually tables and views).
+
+ Also deprecate `SchemaCache#tables`, `SchemaCache#table_exists?` and
+ `SchemaCache#clear_table_cache!` in favor of their new data source
+ counterparts.
+
+ *Yves Senn*, *Matthew Draper*
+
+* `ActiveRecord::Tasks::MySQLDatabaseTasks` fails if shellout to
+ mysql commands (like `mysqldump`) is not successful.
+
+ *Steve Mitchell*
+
+* Ensure `select` quotes aliased attributes, even when using `from`.
+
+ Fixes #21488
+
+ *Sean Griffin & @johanlunds*
+
* MySQL: support `unsigned` numeric data types.
Example:
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 82b07de482..99b500526d 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -96,7 +96,7 @@ module ActiveRecord
end
end
- # Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
+ # Raises an <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
# \Active \Record method is defined in the model, otherwise +false+.
#
# class Person < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb
index 553122a5fc..10498f4322 100644
--- a/activerecord/lib/active_record/attribute_methods/query.rb
+++ b/activerecord/lib/active_record/attribute_methods/query.rb
@@ -19,7 +19,7 @@ module ActiveRecord
if Numeric === value || value !~ /[^0-9]/
!value.to_i.zero?
else
- return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
+ return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
!value.blank?
end
elsif value.respond_to?(:zero?)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 40175c8c8c..285b4065b1 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -23,6 +23,20 @@ module ActiveRecord
table_name[0...table_alias_length].tr('.', '_')
end
+ # Returns the relation names useable to back Active Record models.
+ # For most adapters this means all #tables and #views.
+ def data_sources
+ tables | views
+ end
+
+ # Checks to see if the data source +name+ exists on the database.
+ #
+ # data_source_exists?(:ebooks)
+ #
+ def data_source_exists?(name)
+ data_sources.include?(name.to_s)
+ end
+
# Returns an array of table names defined in the database.
def tables(name = nil)
raise NotImplementedError, "#tables is not implemented"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index feb41e96f0..79a24d1996 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -628,6 +628,7 @@ module ActiveRecord
def tables(name = nil) # :nodoc:
select_values("SHOW FULL TABLES", 'SCHEMA')
end
+ alias data_sources tables
def truncate(table_name, name = nil)
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
@@ -644,6 +645,7 @@ module ActiveRecord
select_values(sql, 'SCHEMA').any?
end
+ alias data_source_exists? table_exists?
def views # :nodoc:
select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 8222811e0c..5e31efec4a 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -5,13 +5,6 @@ module ActiveRecord
module ConnectionAdapters
# An abstract definition of a column in a table.
class Column
- FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
-
- module Format
- ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
- ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
- end
-
attr_reader :name, :null, :sql_type_metadata, :default, :default_function, :collation
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb
index 2c04c46131..424769f765 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb
@@ -4,18 +4,14 @@ module ActiveRecord
module OID # :nodoc:
class DateTime < Type::DateTime # :nodoc:
def cast_value(value)
- if value.is_a?(::String)
- case value
- when 'infinity' then ::Float::INFINITY
- when '-infinity' then -::Float::INFINITY
- when / BC$/
- astronomical_year = format("%04d", -value[/^\d+/].to_i + 1)
- super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year))
- else
- super
- end
+ case value
+ when 'infinity' then ::Float::INFINITY
+ when '-infinity' then -::Float::INFINITY
+ when / BC$/
+ astronomical_year = format("%04d", -value[/^\d+/].to_i + 1)
+ super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year))
else
- value
+ super
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 66d311dd48..aaf5b2898b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -73,6 +73,16 @@ module ActiveRecord
select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
end
+ def data_sources # :nodoc
+ select_values(<<-SQL, 'SCHEMA')
+ SELECT c.relname
+ FROM pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relkind IN ('r', 'v','m') -- (r)elation/table, (v)iew, (m)aterialized view
+ AND n.nspname = ANY (current_schemas(false))
+ SQL
+ end
+
# Returns true if table exists.
# If the schema is not specified as part of +name+ then it will only find tables within
# the current schema search path (regardless of permissions to access tables in other schemas)
@@ -89,6 +99,7 @@ module ActiveRecord
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
SQL
end
+ alias data_source_exists? table_exists?
def views # :nodoc:
select_values(<<-SQL, 'SCHEMA')
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 981d5d7a3c..eee142378c 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -10,7 +10,7 @@ module ActiveRecord
@columns = {}
@columns_hash = {}
@primary_keys = {}
- @tables = {}
+ @data_sources = {}
end
def initialize_dup(other)
@@ -18,33 +18,38 @@ module ActiveRecord
@columns = @columns.dup
@columns_hash = @columns_hash.dup
@primary_keys = @primary_keys.dup
- @tables = @tables.dup
+ @data_sources = @data_sources.dup
end
def primary_keys(table_name)
- @primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil
+ @primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
end
# A cached lookup for table existence.
- def table_exists?(name)
- prepare_tables if @tables.empty?
- return @tables[name] if @tables.key? name
+ def data_source_exists?(name)
+ prepare_data_sources if @data_sources.empty?
+ return @data_sources[name] if @data_sources.key? name
- @tables[name] = connection.table_exists?(name)
+ @data_sources[name] = connection.data_source_exists?(name)
end
+ alias table_exists? data_source_exists?
+ deprecate :table_exists? => "use #data_source_exists? instead"
+
# Add internal cache for table with +table_name+.
def add(table_name)
- if table_exists?(table_name)
+ if data_source_exists?(table_name)
primary_keys(table_name)
columns(table_name)
columns_hash(table_name)
end
end
- def tables(name)
- @tables[name]
+ def data_sources(name)
+ @data_sources[name]
end
+ alias tables data_sources
+ deprecate :tables => "use #data_sources instead"
# Get the columns for a table
def columns(table_name)
@@ -64,36 +69,38 @@ module ActiveRecord
@columns.clear
@columns_hash.clear
@primary_keys.clear
- @tables.clear
+ @data_sources.clear
@version = nil
end
def size
- [@columns, @columns_hash, @primary_keys, @tables].map(&:size).inject :+
+ [@columns, @columns_hash, @primary_keys, @data_sources].map(&:size).inject :+
end
- # Clear out internal caches for table with +table_name+.
- def clear_table_cache!(table_name)
- @columns.delete table_name
- @columns_hash.delete table_name
- @primary_keys.delete table_name
- @tables.delete table_name
+ # Clear out internal caches for the data source +name+.
+ def clear_data_source_cache!(name)
+ @columns.delete name
+ @columns_hash.delete name
+ @primary_keys.delete name
+ @data_sources.delete name
end
+ alias clear_table_cache! clear_data_source_cache!
+ deprecate :clear_table_cache! => "use #clear_data_source_cache! instead"
def marshal_dump
# if we get current version during initialization, it happens stack over flow.
@version = ActiveRecord::Migrator.current_version
- [@version, @columns, @columns_hash, @primary_keys, @tables]
+ [@version, @columns, @columns_hash, @primary_keys, @data_sources]
end
def marshal_load(array)
- @version, @columns, @columns_hash, @primary_keys, @tables = array
+ @version, @columns, @columns_hash, @primary_keys, @data_sources = array
end
private
- def prepare_tables
- connection.tables.each { |table| @tables[table] = true }
+ def prepare_data_sources
+ connection.data_sources.each { |source| @data_sources[source] = true }
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index e2f52707e8..f6a6e3eb4d 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -319,10 +319,12 @@ module ActiveRecord
row['name']
end
end
+ alias data_sources tables
def table_exists?(table_name)
table_name && tables(nil, table_name).any?
end
+ alias data_source_exists? table_exists?
def views # :nodoc:
select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA')
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index a09437b4b0..2336d23a1c 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -22,7 +22,7 @@ module ActiveRecord
# p1.save
#
# p2.first_name = "should fail"
- # p2.save # Raises a ActiveRecord::StaleObjectError
+ # p2.save # Raises an ActiveRecord::StaleObjectError
#
# Optimistic locking will also check for stale data when objects are destroyed. Example:
#
@@ -32,7 +32,7 @@ module ActiveRecord
# p1.first_name = "Michael"
# p1.save
#
- # p2.destroy # Raises a ActiveRecord::StaleObjectError
+ # p2.destroy # Raises an ActiveRecord::StaleObjectError
#
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
# or otherwise apply the business logic needed to resolve the conflict.
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 2b0c755ef4..c461c04a26 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -213,7 +213,7 @@ module ActiveRecord
# Indicates whether the table associated with this class exists
def table_exists?
- connection.schema_cache.table_exists?(table_name)
+ connection.schema_cache.data_source_exists?(table_name)
end
def attributes_builder # :nodoc:
@@ -290,7 +290,7 @@ module ActiveRecord
def reset_column_information
connection.clear_cache!
undefine_attribute_methods
- connection.schema_cache.clear_table_cache!(table_name)
+ connection.schema_cache.clear_data_source_cache!(table_name)
reload_schema_from_cache
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d543a84ff1..d940ac244a 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -255,7 +255,7 @@ db_namespace = namespace :db do
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
con.schema_cache.clear!
- con.tables.each { |table| con.schema_cache.add(table) }
+ con.data_sources.each { |table| con.schema_cache.add(table) }
open(filename, 'wb') { |f| f.write(Marshal.dump(con.schema_cache)) }
end
@@ -353,7 +353,7 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
end
- # desc 'Check for pending migrations and load the test schema'
+ # desc 'Load the test schema'
task :prepare => %w(environment load_config) do
unless ActiveRecord::Base.configurations.blank?
db_namespace['test:load'].invoke
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 0f6015fa93..c562bd9ce4 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -70,9 +70,9 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.sum(:age) # => 4562
- def sum(*args)
- return super if block_given?
- calculate(:sum, *args)
+ def sum(column_name = nil, &block)
+ return super(&block) if block_given?
+ calculate(:sum, column_name)
end
# This calculates aggregate values in the given column. Methods for count, sum, average,
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index e25b889851..038280509d 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -250,7 +250,7 @@ module ActiveRecord
def _select!(*fields) # :nodoc:
fields.flatten!
fields.map! do |field|
- klass.attribute_alias?(field) ? klass.attribute_alias(field) : field
+ klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
end
self.select_values += fields
self
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index ba75ffa5a1..32f4229977 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -15,7 +15,7 @@ module ActiveRecord
# them into a valid SQL fragment for a WHERE clause.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
- def sanitize_sql_for_conditions(condition, table_name = self.table_name)
+ def sanitize_sql_for_conditions(condition)
return nil if condition.blank?
case condition
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index f1f827f163..2362dae9fc 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -89,7 +89,7 @@ HEADER
end
def tables(stream)
- sorted_tables = @connection.tables.sort - @connection.views
+ sorted_tables = @connection.data_sources.sort - @connection.views
sorted_tables.each do |table_name|
table(table_name, stream) unless ignored?(table_name)
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index ba5dd0a0d9..8929aa85c8 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -56,22 +56,21 @@ module ActiveRecord
end
def structure_dump(filename)
- args = prepare_command_options('mysqldump')
+ args = prepare_command_options
args.concat(["--result-file", "#{filename}"])
args.concat(["--no-data"])
args.concat(["--routines"])
args.concat(["#{configuration['database']}"])
- unless Kernel.system(*args)
- $stderr.puts "Could not dump the database structure. "\
- "Make sure `mysqldump` is in your PATH and check the command output for warnings."
- end
+
+ run_cmd('mysqldump', args, 'dumping')
end
def structure_load(filename)
- args = prepare_command_options('mysql')
+ args = prepare_command_options
args.concat(['--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}])
args.concat(["--database", "#{configuration['database']}"])
- Kernel.system(*args)
+
+ run_cmd('mysql', args, 'loading')
end
private
@@ -130,7 +129,7 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
$stdin.gets.strip
end
- def prepare_command_options(command)
+ def prepare_command_options
args = {
'host' => '--host',
'port' => '--port',
@@ -145,7 +144,17 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
'sslkey' => '--ssl-key'
}.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact
- [command, *args]
+ args
+ end
+
+ def run_cmd(cmd, args, action)
+ fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
+ end
+
+ def run_cmd_error(cmd, args, action)
+ msg = "failed to execute: `#{cmd}`\n"
+ msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
+ msg
end
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 4a569fc242..1a2988ea77 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -11,6 +11,7 @@ module ActiveRecord
:before_commit_without_transaction_enrollment,
:commit_without_transaction_enrollment,
:rollback_without_transaction_enrollment,
+ terminator: deprecated_false_terminator,
scope: [:kind, :name]
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index 53f3b53bec..74dfe88349 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -1,27 +1,18 @@
-require 'active_record/type/helpers'
-require 'active_record/type/value'
+require 'active_model/type'
+
+require 'active_record/type/internal/abstract_json'
+require 'active_record/type/internal/timezone'
-require 'active_record/type/big_integer'
-require 'active_record/type/binary'
-require 'active_record/type/boolean'
require 'active_record/type/date'
require 'active_record/type/date_time'
-require 'active_record/type/decimal'
-require 'active_record/type/decimal_without_scale'
-require 'active_record/type/float'
-require 'active_record/type/integer'
-require 'active_record/type/serialized'
-require 'active_record/type/string'
-require 'active_record/type/text'
require 'active_record/type/time'
-require 'active_record/type/unsigned_integer'
+require 'active_record/type/serialized'
require 'active_record/type/adapter_specific_registry'
+
require 'active_record/type/type_map'
require 'active_record/type/hash_lookup_type_map'
-require 'active_record/type/internal/abstract_json'
-
module ActiveRecord
module Type
@registry = AdapterSpecificRegistry.new
@@ -53,6 +44,19 @@ module ActiveRecord
end
end
+ Helpers = ActiveModel::Type::Helpers
+ BigInteger = ActiveModel::Type::BigInteger
+ Binary = ActiveModel::Type::Binary
+ Boolean = ActiveModel::Type::Boolean
+ Decimal = ActiveModel::Type::Decimal
+ DecimalWithoutScale = ActiveModel::Type::DecimalWithoutScale
+ Float = ActiveModel::Type::Float
+ Integer = ActiveModel::Type::Integer
+ String = ActiveModel::Type::String
+ Text = ActiveModel::Type::Text
+ UnsignedInteger = ActiveModel::Type::UnsignedInteger
+ Value = ActiveModel::Type::Value
+
register(:big_integer, Type::BigInteger, override: false)
register(:binary, Type::Binary, override: false)
register(:boolean, Type::Boolean, override: false)
diff --git a/activerecord/lib/active_record/type/adapter_specific_registry.rb b/activerecord/lib/active_record/type/adapter_specific_registry.rb
index 5f71b3cb94..d440eac619 100644
--- a/activerecord/lib/active_record/type/adapter_specific_registry.rb
+++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb
@@ -1,35 +1,24 @@
+require 'active_model/type/registry'
+
module ActiveRecord
# :stopdoc:
module Type
- class AdapterSpecificRegistry
- def initialize
- @registrations = []
- end
-
- def register(type_name, klass = nil, **options, &block)
- block ||= proc { |_, *args| klass.new(*args) }
- registrations << Registration.new(type_name, block, **options)
+ class AdapterSpecificRegistry < ActiveModel::Type::Registry
+ def add_modifier(options, klass, **args)
+ registrations << DecorationRegistration.new(options, klass, **args)
end
- def lookup(symbol, *args)
- registration = registrations
- .select { |r| r.matches?(symbol, *args) }
- .max
+ private
- if registration
- registration.call(self, symbol, *args)
- else
- raise ArgumentError, "Unknown type #{symbol.inspect}"
- end
+ def registration_klass
+ Registration
end
- def add_modifier(options, klass, **args)
- registrations << DecorationRegistration.new(options, klass, **args)
+ def find_registration(symbol, *args)
+ registrations
+ .select { |registration| registration.matches?(symbol, *args) }
+ .max
end
-
- protected
-
- attr_reader :registrations
end
class Registration
@@ -137,6 +126,5 @@ module ActiveRecord
class TypeConflictError < StandardError
end
-
# :startdoc:
end
diff --git a/activerecord/lib/active_record/type/date.rb b/activerecord/lib/active_record/type/date.rb
index 3ceab59ebb..ccafed054e 100644
--- a/activerecord/lib/active_record/type/date.rb
+++ b/activerecord/lib/active_record/type/date.rb
@@ -1,49 +1,7 @@
module ActiveRecord
module Type
- class Date < Value # :nodoc:
- include Helpers::AcceptsMultiparameterTime.new
-
- def type
- :date
- end
-
- def type_cast_for_schema(value)
- "'#{value.to_s(:db)}'"
- end
-
- private
-
- def cast_value(value)
- if value.is_a?(::String)
- return if value.empty?
- fast_string_to_date(value) || fallback_string_to_date(value)
- elsif value.respond_to?(:to_date)
- value.to_date
- else
- value
- end
- end
-
- def fast_string_to_date(string)
- if string =~ ConnectionAdapters::Column::Format::ISO_DATE
- new_date $1.to_i, $2.to_i, $3.to_i
- end
- end
-
- def fallback_string_to_date(string)
- new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
- end
-
- def new_date(year, mon, mday)
- if year && year != 0
- ::Date.new(year, mon, mday) rescue nil
- end
- end
-
- def value_from_multiparameter_assignment(*)
- time = super
- time && time.to_date
- end
+ class Date < ActiveModel::Type::Date
+ include Internal::Timezone
end
end
end
diff --git a/activerecord/lib/active_record/type/date_time.rb b/activerecord/lib/active_record/type/date_time.rb
index a5199959b9..1fb9380ecd 100644
--- a/activerecord/lib/active_record/type/date_time.rb
+++ b/activerecord/lib/active_record/type/date_time.rb
@@ -1,44 +1,7 @@
module ActiveRecord
module Type
- class DateTime < Value # :nodoc:
- include Helpers::TimeValue
- include Helpers::AcceptsMultiparameterTime.new(
- defaults: { 4 => 0, 5 => 0 }
- )
-
- def type
- :datetime
- end
-
- private
-
- def cast_value(string)
- return string unless string.is_a?(::String)
- return if string.empty?
-
- fast_string_to_time(string) || fallback_string_to_time(string)
- end
-
- # '0.123456' -> 123456
- # '1.123456' -> 123456
- def microseconds(time)
- time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
- end
-
- def fallback_string_to_time(string)
- time_hash = ::Date._parse(string)
- time_hash[:sec_fraction] = microseconds(time_hash)
-
- new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
- end
-
- def value_from_multiparameter_assignment(values_hash)
- missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
- if missing_parameter
- raise ArgumentError, missing_parameter
- end
- super
- end
+ class DateTime < ActiveModel::Type::DateTime
+ include Internal::Timezone
end
end
end
diff --git a/activerecord/lib/active_record/type/helpers.rb b/activerecord/lib/active_record/type/helpers.rb
deleted file mode 100644
index 634d417d13..0000000000
--- a/activerecord/lib/active_record/type/helpers.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-require 'active_record/type/helpers/accepts_multiparameter_time'
-require 'active_record/type/helpers/numeric'
-require 'active_record/type/helpers/mutable'
-require 'active_record/type/helpers/time_value'
diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb
index 963a8245d0..097d1bd363 100644
--- a/activerecord/lib/active_record/type/internal/abstract_json.rb
+++ b/activerecord/lib/active_record/type/internal/abstract_json.rb
@@ -1,8 +1,8 @@
module ActiveRecord
module Type
module Internal # :nodoc:
- class AbstractJson < Type::Value # :nodoc:
- include Type::Helpers::Mutable
+ class AbstractJson < ActiveModel::Type::Value # :nodoc:
+ include ActiveModel::Type::Helpers::Mutable
def type
:json
diff --git a/activerecord/lib/active_record/type/internal/timezone.rb b/activerecord/lib/active_record/type/internal/timezone.rb
new file mode 100644
index 0000000000..947e06158a
--- /dev/null
+++ b/activerecord/lib/active_record/type/internal/timezone.rb
@@ -0,0 +1,15 @@
+module ActiveRecord
+ module Type
+ module Internal
+ module Timezone
+ def is_utc?
+ ActiveRecord::Base.default_timezone == :utc
+ end
+
+ def default_timezone
+ ActiveRecord::Base.default_timezone
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index ea3e0d6a45..203a395415 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module Type
- class Serialized < DelegateClass(Type::Value) # :nodoc:
- include Helpers::Mutable
+ class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc:
+ include ActiveModel::Type::Helpers::Mutable
attr_reader :subtype, :coder
diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb
index 19a10021bc..70988d84ff 100644
--- a/activerecord/lib/active_record/type/time.rb
+++ b/activerecord/lib/active_record/type/time.rb
@@ -1,42 +1,8 @@
module ActiveRecord
module Type
- class Time < Value # :nodoc:
- include Helpers::TimeValue
- include Helpers::AcceptsMultiparameterTime.new(
- defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
- )
-
- def type
- :time
- end
-
- def user_input_in_time_zone(value)
- return unless value.present?
-
- case value
- when ::String
- value = "2000-01-01 #{value}"
- when ::Time
- value = value.change(year: 2000, day: 1, month: 1)
- end
-
- super(value)
- end
-
- private
-
- def cast_value(value)
- return value unless value.is_a?(::String)
- return if value.empty?
-
- dummy_time_value = "2000-01-01 #{value}"
-
- fast_string_to_time(dummy_time_value) || begin
- time_hash = ::Date._parse(dummy_time_value)
- return if time_hash[:hour].nil?
- new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
- end
- end
+ class Time < ActiveModel::Type::Time
+ include Internal::Timezone
end
end
end
+
diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb
index 8ce36cc9af..81d7ed39bb 100644
--- a/activerecord/lib/active_record/type/type_map.rb
+++ b/activerecord/lib/active_record/type/type_map.rb
@@ -57,7 +57,7 @@ module ActiveRecord
end
def default_value
- @default_value ||= Value.new
+ @default_value ||= ActiveModel::Type::Value.new
end
end
end
diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb
index 3878270770..868d08ed44 100644
--- a/activerecord/lib/active_record/type_caster/connection.rb
+++ b/activerecord/lib/active_record/type_caster/connection.rb
@@ -20,7 +20,7 @@ module ActiveRecord
private
def column_for(attribute_name)
- if connection.schema_cache.table_exists?(table_name)
+ if connection.schema_cache.data_source_exists?(table_name)
connection.schema_cache.columns_hash(table_name)[attribute_name.to_s]
end
end
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index 49a68fb94c..43c817e057 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -7,7 +7,7 @@ module ActiveRecord
module ConnectionAdapters
class FakeAdapter < AbstractAdapter
- attr_accessor :tables, :primary_keys
+ attr_accessor :data_sources, :primary_keys
@columns = Hash.new { |h,k| h[k] = [] }
class << self
@@ -16,7 +16,7 @@ module ActiveRecord
def initialize(connection, logger)
super
- @tables = []
+ @data_sources = []
@primary_keys = {}
@columns = self.class.columns
end
@@ -37,7 +37,7 @@ module ActiveRecord
@columns[table_name]
end
- def table_exists?(*)
+ def data_source_exists?(*)
true
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 9a111b37cd..62579a4a7a 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -36,6 +36,21 @@ module ActiveRecord
assert !@connection.table_exists?(nil)
end
+ def test_data_sources
+ data_sources = @connection.data_sources
+ assert data_sources.include?("accounts")
+ assert data_sources.include?("authors")
+ assert data_sources.include?("tasks")
+ assert data_sources.include?("topics")
+ end
+
+ def test_data_source_exists?
+ assert @connection.data_source_exists?("accounts")
+ assert @connection.data_source_exists?(:accounts)
+ assert_not @connection.data_source_exists?("nonexistingtable")
+ assert_not @connection.data_source_exists?(nil)
+ end
+
def test_indexes
idx_name = "accounts_idx"
diff --git a/activerecord/test/cases/adapters/mysql/explain_test.rb b/activerecord/test/cases/adapters/mysql/explain_test.rb
index f75386abb9..c44c1e6648 100644
--- a/activerecord/test/cases/adapters/mysql/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql/explain_test.rb
@@ -2,26 +2,20 @@ require "cases/helper"
require 'models/developer'
require 'models/computer'
-module ActiveRecord
- module ConnectionAdapters
- class MysqlAdapter
- class ExplainTest < ActiveRecord::TestCase
- fixtures :developers
+class MysqlExplainTest < ActiveRecord::MysqlTestCase
+ fixtures :developers
- def test_explain_for_one_query
- explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- end
+ def test_explain_for_one_query
+ explain = Developer.where(id: 1).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %r(developers |.* const), explain
+ end
- def test_explain_with_eager_loading
- explain = Developer.where(:id => 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
- assert_match %r(audit_logs |.* ALL), explain
- end
- end
- end
+ def test_explain_with_eager_loading
+ explain = Developer.where(id: 1).includes(:audit_logs).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %r(developers |.* const), explain
+ assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
+ assert_match %r(audit_logs |.* ALL), explain
end
end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 8655fa26b6..29573d8e0d 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -65,14 +65,6 @@ module ActiveRecord
end
end
- def test_tables_quoting
- @conn.tables(nil, "foo-bar", nil)
- flunk
- rescue => e
- # assertion for *quoted* database properly
- assert_match(/database 'foo-bar'/, e.inspect)
- end
-
def test_pk_and_sequence_for
with_example_table do
pk, seq = @conn.pk_and_sequence_for('ex')
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 8eb57f08d0..e361521155 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -128,15 +128,14 @@ module ActiveRecord
result = connection.exec_insert("insert into postgresql_partitioned_table_parent DEFAULT VALUES", nil, [], 'id')
expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
assert_equal expect.to_i, result.rows.first.first
- end
+ end
def test_exec_insert_default_values_quoted_schema_with_returning_disabled_and_no_sequence_name_given
connection = connection_without_insert_returning
result = connection.exec_insert('insert into "public"."postgresql_partitioned_table_parent" DEFAULT VALUES', nil, [], 'id')
expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
assert_equal expect.to_i, result.rows.first.first
- end
-
+ end
def test_sql_for_insert_with_returning_disabled
connection = connection_without_insert_returning
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index bee612d8d3..f89a394f96 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -5,9 +5,11 @@ require 'support/schema_dumping_helper'
module PGSchemaHelper
def with_schema_search_path(schema_search_path)
@connection.schema_search_path = schema_search_path
+ @connection.schema_cache.clear!
yield if block_given?
ensure
@connection.schema_search_path = "'$user', public"
+ @connection.schema_cache.clear!
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 02b67f901f..938350627f 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -500,7 +500,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
line_item = LineItem.create!
invoice = Invoice.create!(line_items: [line_item])
initial = invoice.updated_at
- line_item.touch
+ travel(1.second) do
+ line_item.touch
+ end
assert_not_equal initial, invoice.reload.updated_at
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 0beaf0056a..cd19a7a5bc 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -170,7 +170,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
part = ShipPart.create(name: 'cockpit')
updated_at = part.updated_at
- ship.parts << part
+ travel(1.second) do
+ ship.parts << part
+ end
assert_equal part.ship, ship
assert_not_equal part.updated_at, updated_at
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
index 0ec368f51d..c69782d93f 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activerecord/test/cases/attribute_test.rb
@@ -4,7 +4,6 @@ module ActiveRecord
class AttributeTest < ActiveRecord::TestCase
setup do
@type = Minitest::Mock.new
- @type.expect(:==, false, [false])
end
teardown do
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 3aa0849cb5..991145bed4 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -204,7 +204,7 @@ class BasicsTest < ActiveRecord::TestCase
)
# For adapters which support microsecond resolution.
- if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) || mysql_56?
+ if subsecond_precision_supported?
assert_equal 11, Topic.find(1).written_on.sec
assert_equal 223300, Topic.find(1).written_on.usec
assert_equal 9900, Topic.find(2).written_on.usec
diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
index c7531f5418..db832fe55d 100644
--- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb
+++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
@@ -29,7 +29,7 @@ module ActiveRecord
def test_clearing
@cache.columns('posts')
@cache.columns_hash('posts')
- @cache.tables('posts')
+ @cache.data_sources('posts')
@cache.primary_keys('posts')
@cache.clear!
@@ -40,17 +40,22 @@ module ActiveRecord
def test_dump_and_load
@cache.columns('posts')
@cache.columns_hash('posts')
- @cache.tables('posts')
+ @cache.data_sources('posts')
@cache.primary_keys('posts')
@cache = Marshal.load(Marshal.dump(@cache))
assert_equal 11, @cache.columns('posts').size
assert_equal 11, @cache.columns_hash('posts').size
- assert @cache.tables('posts')
+ assert @cache.data_sources('posts')
assert_equal 'id', @cache.primary_keys('posts')
end
+ def test_table_methods_deprecation
+ assert_deprecated { assert @cache.table_exists?('posts') }
+ assert_deprecated { assert @cache.tables('posts') }
+ assert_deprecated { @cache.clear_table_cache!('posts') }
+ end
end
end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index f5aaf22e13..e9a80842d0 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -467,8 +467,10 @@ class DirtyTest < ActiveRecord::TestCase
topic.save!
updated_at = topic.updated_at
- topic.content[:hello] = 'world'
- topic.save!
+ travel(1.second) do
+ topic.content[:hello] = 'world'
+ topic.save!
+ end
assert_not_equal updated_at, topic.updated_at
assert_equal 'world', topic.content[:hello]
@@ -521,6 +523,9 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal Hash.new, pirate.previous_changes
pirate = Pirate.find_by_catchphrase("arrr")
+
+ travel(1.second)
+
pirate.catchphrase = "Me Maties!"
pirate.save!
@@ -532,6 +537,9 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.previous_changes.key?('created_on')
pirate = Pirate.find_by_catchphrase("Me Maties!")
+
+ travel(1.second)
+
pirate.catchphrase = "Thar She Blows!"
pirate.save
@@ -542,6 +550,8 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.previous_changes.key?('parrot_id')
assert !pirate.previous_changes.key?('created_on')
+ travel(1.second)
+
pirate = Pirate.find_by_catchphrase("Thar She Blows!")
pirate.update(catchphrase: "Ahoy!")
@@ -552,6 +562,8 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.previous_changes.key?('parrot_id')
assert !pirate.previous_changes.key?('created_on')
+ travel(1.second)
+
pirate = Pirate.find_by_catchphrase("Ahoy!")
pirate.update_attribute(:catchphrase, "Ninjas suck!")
@@ -561,6 +573,8 @@ class DirtyTest < ActiveRecord::TestCase
assert_not_nil pirate.previous_changes['updated_on'][1]
assert !pirate.previous_changes.key?('parrot_id')
assert !pirate.previous_changes.key?('created_on')
+ ensure
+ travel_back
end
if ActiveRecord::Base.connection.supports_migrations?
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 0dc884497c..307b68764e 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -701,12 +701,12 @@ class FinderTest < ActiveRecord::TestCase
end
def test_bind_arity
- assert_nothing_raised { bind '' }
+ assert_nothing_raised { bind '' }
assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' }
- assert_nothing_raised { bind '?', 1 }
- assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 }
+ assert_nothing_raised { bind '?', 1 }
+ assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 }
end
def test_named_bind_variables
@@ -721,6 +721,12 @@ class FinderTest < ActiveRecord::TestCase
assert_kind_of Time, Topic.where(["id = :id", { id: 1 }]).first.written_on
end
+ def test_named_bind_arity
+ assert_nothing_raised { bind "name = :name", { name: "37signals" } }
+ assert_nothing_raised { bind "name = :name", { name: "37signals", id: 1 } }
+ assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "name = :name", { id: 1 } }
+ end
+
class SimpleEnumerable
include Enumerable
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index b61a5126e0..8773986882 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -46,10 +46,10 @@ def in_memory_db?
ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:"
end
-def mysql_56?
- current_adapter?(:MysqlAdapter, :Mysql2Adapter) &&
- ActiveRecord::Base.connection.send(:version) >= '5.6.0' &&
- ActiveRecord::Base.connection.send(:version) < '5.7.0'
+def subsecond_precision_supported?
+ !current_adapter?(:MysqlAdapter, :Mysql2Adapter) ||
+ (ActiveRecord::Base.connection.send(:version) >= '5.6.0' &&
+ ActiveRecord::Base.connection.send(:version) < '5.7.0')
end
def mysql_enforcing_gtid_consistency?
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index 018b7b0d8f..80d17b8114 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -125,6 +125,7 @@ class IntegrationTest < ActiveRecord::TestCase
end
def test_cache_key_format_is_precise_enough
+ skip("Subsecond precision is not supported") unless subsecond_precision_supported?
dev = Developer.first
key = dev.cache_key
dev.touch
diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb
index 5bc0898f33..ad85684c0b 100644
--- a/activerecord/test/cases/migration/helper.rb
+++ b/activerecord/test/cases/migration/helper.rb
@@ -28,7 +28,7 @@ module ActiveRecord
super
TestModel.reset_table_name
TestModel.reset_sequence_name
- connection.drop_table :test_models rescue nil
+ connection.drop_table :test_models, if_exists: true
end
private
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 37d3965022..b0fb905760 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -243,18 +243,23 @@ module ActiveRecord
end
def test_select_quotes_when_using_from_clause
- if sqlite3_version_includes_quoting_bug?
- skip <<-ERROR.squish
- You are using an outdated version of SQLite3 which has a bug in
- quoted column names. Please update SQLite3 and rebuild the sqlite3
- ruby gem
- ERROR
- end
+ skip_if_sqlite3_version_includes_quoting_bug
quoted_join = ActiveRecord::Base.connection.quote_table_name("join")
selected = Post.select(:join).from(Post.select("id as #{quoted_join}")).map(&:join)
assert_equal Post.pluck(:id), selected
end
+ def test_selecting_aliased_attribute_quotes_column_name_when_from_is_used
+ skip_if_sqlite3_version_includes_quoting_bug
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = :test_with_keyword_column_name
+ alias_attribute :description, :desc
+ end
+ klass.create!(description: "foo")
+
+ assert_equal ["foo"], klass.select(:description).from(klass.all).map(&:desc)
+ end
+
def test_relation_merging_with_merged_joins_as_strings
join_string = "LEFT OUTER JOIN #{Rating.quoted_table_name} ON #{SpecialComment.quoted_table_name}.id = #{Rating.quoted_table_name}.comment_id"
special_comments_with_ratings = SpecialComment.joins join_string
@@ -292,6 +297,16 @@ module ActiveRecord
private
+ def skip_if_sqlite3_version_includes_quoting_bug
+ if sqlite3_version_includes_quoting_bug?
+ skip <<-ERROR.squish
+ You are using an outdated version of SQLite3 which has a bug in
+ quoted column names. Please update SQLite3 and rebuild the sqlite3
+ ruby gem
+ ERROR
+ end
+ end
+
def sqlite3_version_includes_quoting_bug?
if current_adapter?(:SQLite3Adapter)
selected_quoted_column_names = ActiveRecord::Base.connection.exec_query(
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index d0deb4c273..a93fa57257 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -270,15 +270,16 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
end
- def test_warn_when_external_structure_dump_fails
+ def test_warn_when_external_structure_dump_command_execution_fails
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db").returns(false)
+ Kernel.expects(:system)
+ .with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db")
+ .returns(false)
- warnings = capture(:stderr) do
+ e = assert_raise(RuntimeError) {
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
- end
-
- assert_match(/Could not dump the database structure/, warnings)
+ }
+ assert_match(/^failed to execute: `mysqldump`$/, e.message)
end
def test_structure_dump_with_port_number
@@ -311,6 +312,7 @@ module ActiveRecord
def test_structure_load
filename = "awesome-file.sql"
Kernel.expects(:system).with('mysql', '--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db")
+ .returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 5dab32995c..970f6bcf4a 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -84,7 +84,9 @@ class TimestampTest < ActiveRecord::TestCase
def test_touching_an_attribute_updates_timestamp
previously_created_at = @developer.created_at
- @developer.touch(:created_at)
+ travel(1.second) do
+ @developer.touch(:created_at)
+ end
assert !@developer.created_at_changed? , 'created_at should not be changed'
assert !@developer.changed?, 'record should not be changed'
@@ -199,8 +201,10 @@ class TimestampTest < ActiveRecord::TestCase
owner = pet.owner
previously_owner_updated_at = owner.updated_at
- pet.name = "Fluffy the Third"
- pet.save
+ travel(1.second) do
+ pet.name = "Fluffy the Third"
+ pet.save
+ end
assert_not_equal previously_owner_updated_at, pet.owner.updated_at
end
@@ -210,7 +214,9 @@ class TimestampTest < ActiveRecord::TestCase
owner = pet.owner
previously_owner_updated_at = owner.updated_at
- pet.destroy
+ travel(1.second) do
+ pet.destroy
+ end
assert_not_equal previously_owner_updated_at, pet.owner.updated_at
end
@@ -254,8 +260,10 @@ class TimestampTest < ActiveRecord::TestCase
owner.update_columns(happy_at: 3.days.ago)
previously_owner_updated_at = owner.updated_at
- pet.name = "I'm a parrot"
- pet.save
+ travel(1.second) do
+ pet.name = "I'm a parrot"
+ pet.save
+ end
assert_not_equal previously_owner_updated_at, pet.owner.updated_at
end
diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
index b3c42c8e42..7058f4fbe2 100644
--- a/activerecord/test/cases/touch_later_test.rb
+++ b/activerecord/test/cases/touch_later_test.rb
@@ -72,7 +72,7 @@ class TouchLaterTest < ActiveRecord::TestCase
end
def test_touch_touches_immediately_with_a_custom_time
- time = Time.now.utc - 25.days
+ time = (Time.now.utc - 25.days).change(nsec: 0)
topic = Topic.create!(updated_at: time, created_at: time)
assert_equal time, topic.updated_at
assert_equal time, topic.created_at
diff --git a/activerecord/test/cases/type/date_time_test.rb b/activerecord/test/cases/type/date_time_test.rb
new file mode 100644
index 0000000000..bc4900e1c2
--- /dev/null
+++ b/activerecord/test/cases/type/date_time_test.rb
@@ -0,0 +1,14 @@
+require "cases/helper"
+require "models/task"
+
+module ActiveRecord
+ module Type
+ class IntegerTest < ActiveRecord::TestCase
+ def test_datetime_seconds_precision_applied_to_timestamp
+ skip "This test is invalid if subsecond precision isn't supported" unless subsecond_precision_supported?
+ p = Task.create!(starting: ::Time.now)
+ assert_equal p.starting.usec, p.reload.starting.usec
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb
index 0dcdbd0667..c0932d5357 100644
--- a/activerecord/test/cases/type/integer_test.rb
+++ b/activerecord/test/cases/type/integer_test.rb
@@ -4,112 +4,12 @@ require "models/company"
module ActiveRecord
module Type
class IntegerTest < ActiveRecord::TestCase
- test "simple values" do
- type = Type::Integer.new
- assert_equal 1, type.cast(1)
- assert_equal 1, type.cast('1')
- assert_equal 1, type.cast('1ignore')
- assert_equal 0, type.cast('bad1')
- assert_equal 0, type.cast('bad')
- assert_equal 1, type.cast(1.7)
- assert_equal 0, type.cast(false)
- assert_equal 1, type.cast(true)
- assert_nil type.cast(nil)
- end
-
- test "random objects cast to nil" do
- type = Type::Integer.new
- assert_nil type.cast([1,2])
- assert_nil type.cast({1 => 2})
- assert_nil type.cast(1..2)
- end
-
test "casting ActiveRecord models" do
type = Type::Integer.new
firm = Firm.create(:name => 'Apple')
assert_nil type.cast(firm)
end
- test "casting objects without to_i" do
- type = Type::Integer.new
- assert_nil type.cast(::Object.new)
- end
-
- test "casting nan and infinity" do
- type = Type::Integer.new
- assert_nil type.cast(::Float::NAN)
- assert_nil type.cast(1.0/0.0)
- end
-
- test "casting booleans for database" do
- type = Type::Integer.new
- assert_equal 1, type.serialize(true)
- assert_equal 0, type.serialize(false)
- end
-
- test "changed?" do
- type = Type::Integer.new
-
- assert type.changed?(5, 5, '5wibble')
- assert_not type.changed?(5, 5, '5')
- assert_not type.changed?(5, 5, '5.0')
- assert_not type.changed?(-5, -5, '-5')
- assert_not type.changed?(-5, -5, '-5.0')
- assert_not type.changed?(nil, nil, nil)
- end
-
- test "values below int min value are out of range" do
- assert_raises(::RangeError) do
- Integer.new.serialize(-2147483649)
- end
- end
-
- test "values above int max value are out of range" do
- assert_raises(::RangeError) do
- Integer.new.serialize(2147483648)
- end
- end
-
- test "very small numbers are out of range" do
- assert_raises(::RangeError) do
- Integer.new.serialize(-9999999999999999999999999999999)
- end
- end
-
- test "very large numbers are out of range" do
- assert_raises(::RangeError) do
- Integer.new.serialize(9999999999999999999999999999999)
- end
- end
-
- test "normal numbers are in range" do
- type = Integer.new
- assert_equal(0, type.serialize(0))
- assert_equal(-1, type.serialize(-1))
- assert_equal(1, type.serialize(1))
- end
-
- test "int max value is in range" do
- assert_equal(2147483647, Integer.new.serialize(2147483647))
- end
-
- test "int min value is in range" do
- assert_equal(-2147483648, Integer.new.serialize(-2147483648))
- end
-
- test "columns with a larger limit have larger ranges" do
- type = Integer.new(limit: 8)
-
- assert_equal(9223372036854775807, type.serialize(9223372036854775807))
- assert_equal(-9223372036854775808, type.serialize(-9223372036854775808))
- assert_raises(::RangeError) do
- type.serialize(-9999999999999999999999999999999)
- end
- assert_raises(::RangeError) do
- type.serialize(9999999999999999999999999999999)
- end
- end
-
test "values which are out of range can be re-assigned" do
klass = Class.new(ActiveRecord::Base) do
self.table_name = 'posts'
diff --git a/activerecord/test/cases/type/string_test.rb b/activerecord/test/cases/type/string_test.rb
index 56e9bf434d..6fe6d46711 100644
--- a/activerecord/test/cases/type/string_test.rb
+++ b/activerecord/test/cases/type/string_test.rb
@@ -2,20 +2,6 @@ require 'cases/helper'
module ActiveRecord
class StringTypeTest < ActiveRecord::TestCase
- test "type casting" do
- type = Type::String.new
- assert_equal "t", type.cast(true)
- assert_equal "f", type.cast(false)
- assert_equal "123", type.cast(123)
- end
-
- test "values are duped coming out" do
- s = "foo"
- type = Type::String.new
- assert_not_same s, type.cast(s)
- assert_not_same s, type.deserialize(s)
- end
-
test "string mutations are detected" do
klass = Class.new(Base)
klass.table_name = 'authors'
diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb
index 9b1859c2ce..81fcf04a27 100644
--- a/activerecord/test/cases/types_test.rb
+++ b/activerecord/test/cases/types_test.rb
@@ -3,111 +3,6 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
class TypesTest < ActiveRecord::TestCase
- def test_type_cast_boolean
- type = Type::Boolean.new
- assert type.cast('').nil?
- assert type.cast(nil).nil?
-
- assert type.cast(true)
- assert type.cast(1)
- assert type.cast('1')
- assert type.cast('t')
- assert type.cast('T')
- assert type.cast('true')
- assert type.cast('TRUE')
- assert type.cast('on')
- assert type.cast('ON')
- assert type.cast(' ')
- assert type.cast("\u3000\r\n")
- assert type.cast("\u0000")
- assert type.cast('SOMETHING RANDOM')
-
- # explicitly check for false vs nil
- assert_equal false, type.cast(false)
- assert_equal false, type.cast(0)
- assert_equal false, type.cast('0')
- assert_equal false, type.cast('f')
- assert_equal false, type.cast('F')
- assert_equal false, type.cast('false')
- assert_equal false, type.cast('FALSE')
- assert_equal false, type.cast('off')
- assert_equal false, type.cast('OFF')
- end
-
- def test_type_cast_float
- type = Type::Float.new
- assert_equal 1.0, type.cast("1")
- end
-
- def test_changing_float
- type = Type::Float.new
-
- assert type.changed?(5.0, 5.0, '5wibble')
- assert_not type.changed?(5.0, 5.0, '5')
- assert_not type.changed?(5.0, 5.0, '5.0')
- assert_not type.changed?(nil, nil, nil)
- end
-
- def test_type_cast_binary
- type = Type::Binary.new
- assert_equal nil, type.cast(nil)
- assert_equal "1", type.cast("1")
- assert_equal 1, type.cast(1)
- end
-
- def test_type_cast_time
- type = Type::Time.new
- assert_equal nil, type.cast(nil)
- assert_equal nil, type.cast('')
- assert_equal nil, type.cast('ABC')
-
- time_string = Time.now.utc.strftime("%T")
- assert_equal time_string, type.cast(time_string).strftime("%T")
- end
-
- def test_type_cast_datetime_and_timestamp
- type = Type::DateTime.new
- assert_equal nil, type.cast(nil)
- assert_equal nil, type.cast('')
- assert_equal nil, type.cast(' ')
- assert_equal nil, type.cast('ABC')
-
- datetime_string = Time.now.utc.strftime("%FT%T")
- assert_equal datetime_string, type.cast(datetime_string).strftime("%FT%T")
- end
-
- def test_type_cast_date
- type = Type::Date.new
- assert_equal nil, type.cast(nil)
- assert_equal nil, type.cast('')
- assert_equal nil, type.cast(' ')
- assert_equal nil, type.cast('ABC')
-
- date_string = Time.now.utc.strftime("%F")
- assert_equal date_string, type.cast(date_string).strftime("%F")
- end
-
- def test_type_cast_duration_to_integer
- type = Type::Integer.new
- assert_equal 1800, type.cast(30.minutes)
- assert_equal 7200, type.cast(2.hours)
- end
-
- def test_string_to_time_with_timezone
- [:utc, :local].each do |zone|
- with_timezone_config default: zone do
- type = Type::DateTime.new
- assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.cast("Wed, 04 Sep 2013 03:00:00 EAT")
- end
- end
- end
-
- def test_type_equality
- assert_equal Type::Value.new, Type::Value.new
- assert_not_equal Type::Value.new, Type::Integer.new
- assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2)
- end
-
def test_attributes_which_are_invalid_for_database_can_still_be_reassigned
type_which_cannot_go_to_the_database = Type::Value.new
def type_which_cannot_go_to_the_database.serialize(*)
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index d7b3e223ed..e80d8bd584 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -44,9 +44,15 @@ module ViewBehavior
def test_table_exists
view_name = Ebook.table_name
+ # TODO: switch this assertion around once we changed #tables to not return views.
assert @connection.table_exists?(view_name), "'#{view_name}' table should exist"
end
+ def test_views_ara_valid_data_sources
+ view_name = Ebook.table_name
+ assert @connection.data_source_exists?(view_name), "'#{view_name}' should be a data source"
+ end
+
def test_column_definitions
assert_equal([["id", :integer],
["name", :string],
diff --git a/activerecord/test/models/contact.rb b/activerecord/test/models/contact.rb
index 3ea17c3abf..9f2f69e1ee 100644
--- a/activerecord/test/models/contact.rb
+++ b/activerecord/test/models/contact.rb
@@ -3,7 +3,7 @@ module ContactFakeColumns
base.class_eval do
establish_connection(:adapter => 'fake')
- connection.tables = [table_name]
+ connection.data_sources = [table_name]
connection.primary_keys = {
table_name => 'id'
}
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index df1834d381..4c6d1ef3ce 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -251,10 +251,17 @@ ActiveRecord::Schema.define do
create_table :developers, force: true do |t|
t.string :name
t.integer :salary, default: 70000
- t.datetime :created_at
- t.datetime :updated_at
- t.datetime :created_on
- t.datetime :updated_on
+ if subsecond_precision_supported?
+ t.datetime :created_at, precision: 6
+ t.datetime :updated_at, precision: 6
+ t.datetime :created_on, precision: 6
+ t.datetime :updated_on, precision: 6
+ else
+ t.datetime :created_at
+ t.datetime :updated_at
+ t.datetime :created_on
+ t.datetime :updated_on
+ end
end
create_table :developers_projects, force: true, id: false do |t|
@@ -353,7 +360,11 @@ ActiveRecord::Schema.define do
create_table :invoices, force: true do |t|
t.integer :balance
- t.datetime :updated_at
+ if subsecond_precision_supported?
+ t.datetime :updated_at, precision: 6
+ else
+ t.datetime :updated_at
+ end
end
create_table :iris, force: true do |t|
@@ -503,7 +514,11 @@ ActiveRecord::Schema.define do
create_table :owners, primary_key: :owner_id, force: true do |t|
t.string :name
- t.column :updated_at, :datetime
+ if subsecond_precision_supported?
+ t.column :updated_at, :datetime, precision: 6
+ else
+ t.column :updated_at, :datetime
+ end
t.column :happy_at, :datetime
t.string :essay_id
end
@@ -521,10 +536,17 @@ ActiveRecord::Schema.define do
t.column :color, :string
t.column :parrot_sti_class, :string
t.column :killer_id, :integer
- t.column :created_at, :datetime
- t.column :created_on, :datetime
- t.column :updated_at, :datetime
- t.column :updated_on, :datetime
+ if subsecond_precision_supported?
+ t.column :created_at, :datetime, precision: 0
+ t.column :created_on, :datetime, precision: 0
+ t.column :updated_at, :datetime, precision: 0
+ t.column :updated_on, :datetime, precision: 0
+ else
+ t.column :created_at, :datetime
+ t.column :created_on, :datetime
+ t.column :updated_at, :datetime
+ t.column :updated_on, :datetime
+ end
end
create_table :parrots_pirates, id: false, force: true do |t|
@@ -567,15 +589,24 @@ ActiveRecord::Schema.define do
create_table :pets, primary_key: :pet_id, force: true do |t|
t.string :name
t.integer :owner_id, :integer
- t.timestamps null: false
+ if subsecond_precision_supported?
+ t.timestamps null: false, precision: 6
+ else
+ t.timestamps null: false
+ end
end
create_table :pirates, force: true do |t|
t.column :catchphrase, :string
t.column :parrot_id, :integer
t.integer :non_validated_parrot_id
- t.column :created_on, :datetime
- t.column :updated_on, :datetime
+ if subsecond_precision_supported?
+ t.column :created_on, :datetime, precision: 6
+ t.column :updated_on, :datetime, precision: 6
+ else
+ t.column :created_on, :datetime
+ t.column :updated_on, :datetime
+ end
end
create_table :posts, force: true do |t|
@@ -685,7 +716,11 @@ ActiveRecord::Schema.define do
create_table :ship_parts, force: true do |t|
t.string :name
t.integer :ship_id
- t.datetime :updated_at
+ if subsecond_precision_supported?
+ t.datetime :updated_at, precision: 6
+ else
+ t.datetime :updated_at
+ end
end
create_table :prisoners, force: true do |t|
@@ -755,7 +790,7 @@ ActiveRecord::Schema.define do
t.string :title, limit: 250
t.string :author_name
t.string :author_email_address
- if mysql_56?
+ if subsecond_precision_supported?
t.datetime :written_on, precision: 6
else
t.datetime :written_on
@@ -778,7 +813,11 @@ ActiveRecord::Schema.define do
t.string :parent_title
t.string :type
t.string :group
- t.timestamps null: true
+ if subsecond_precision_supported?
+ t.timestamps null: true, precision: 6
+ else
+ t.timestamps null: true
+ end
end
create_table :toys, primary_key: :toy_id, force: true do |t|
@@ -943,6 +982,10 @@ ActiveRecord::Schema.define do
t.string :token
t.string :auth_token
end
+
+ create_table :test_with_keyword_column_name, force: true do |t|
+ t.string :desc
+ end
end
Course.connection.create_table :courses, force: true do |t|
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 7d090eca02..309d842da1 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,9 @@
+* Short-circuit `blank?` on date and time values since they are never blank.
+
+ Fixes #21657
+
+ *Andrew White*
+
* Replaced deprecated `ThreadSafe::Cache` with its successor `Concurrent::Map` now that
the thread_safe gem has been merged into concurrent-ruby.
@@ -271,20 +277,17 @@
The preferred method to halt a callback chain from now on is to explicitly
`throw(:abort)`.
- In the past, returning `false` in an ActiveSupport callback had the side
- effect of halting the callback chain. This is not recommended anymore and,
- depending on the value of
- `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`, will
- either not work at all or display a deprecation warning.
+ In the past, callbacks could only be halted by explicitly providing a
+ terminator and by having a callback match the conditions of the terminator.
* Add `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
Setting `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
- to `true` will let an app support the deprecated way of halting callback
- chains by returning `false`.
+ to `true` will let an app support the deprecated way of halting Active Record,
+ Active Model and Active Model validations callback chains by returning `false`.
Setting the value to `false` will tell the app to ignore any `false` value
- returned by callbacks, and only halt the chain upon `throw(:abort)`.
+ returned by those callbacks, and only halt the chain upon `throw(:abort)`.
The value can also be set with the Rails configuration option
`config.active_support.halt_callback_chains_on_return_false`.
@@ -294,7 +297,7 @@
For new Rails 5.0 apps, its value is set to `false` in an initializer, so
these apps will support the new behavior by default.
- *claudiob*
+ *claudiob*, *Roque Pinel*
* Changes arguments and default value of CallbackChain's `:terminator` option
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 80c5fdba17..3db9ea2ac0 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -536,23 +536,12 @@ module ActiveSupport
Proc.new do |target, result_lambda|
terminate = true
catch(:abort) do
- result = result_lambda.call if result_lambda.is_a?(Proc)
- if halt_and_display_warning_on_return_false && result == false
- display_deprecation_warning_for_false_terminator
- else
- terminate = false
- end
+ result_lambda.call if result_lambda.is_a?(Proc)
+ terminate = false
end
terminate
end
end
-
- def display_deprecation_warning_for_false_terminator
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Returning `false` in a callback will not implicitly halt a callback chain in the next release of Rails.
- To explicitly halt a callback chain, please use `throw :abort` instead.
- MSG
- end
end
module ClassMethods
@@ -686,7 +675,8 @@ module ActiveSupport
#
# In this example, if any before validate callbacks returns +false+,
# any successive before and around callback is not executed.
- # Defaults to +false+, meaning no value halts the chain.
+ #
+ # The default terminator halts the chain when a callback throws +:abort+.
#
# * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
# callbacks should be terminated by the <tt>:terminator</tt> option. By
@@ -764,6 +754,30 @@ module ActiveSupport
def set_callbacks(name, callbacks)
send "_#{name}_callbacks=", callbacks
end
+
+ def deprecated_false_terminator
+ Proc.new do |target, result_lambda|
+ terminate = true
+ catch(:abort) do
+ result = result_lambda.call if result_lambda.is_a?(Proc)
+ if CallbackChain.halt_and_display_warning_on_return_false && result == false
+ display_deprecation_warning_for_false_terminator
+ else
+ terminate = false
+ end
+ end
+ terminate
+ end
+ end
+
+ private
+
+ def display_deprecation_warning_for_false_terminator
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in the next release of Rails.
+ To explicitly halt the callback chain, please use `throw :abort` instead.
+ MSG
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index c5cc31abc5..8718b7e1e5 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -85,7 +85,9 @@ class Array
# Extends <tt>Array#to_s</tt> to convert a collection of elements into a
# comma separated id list if <tt>:db</tt> argument is given as the format.
#
- # Blog.all.to_formatted_s(:db) # => "1,2,3"
+ # Blog.all.to_formatted_s(:db) # => "1,2,3"
+ # Blog.none.to_formatted_s(:db) # => "null"
+ # [1,2].to_formatted_s # => "[1, 2]"
def to_formatted_s(format = :default)
case format
when :db
diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb
index 465fedda80..7f0f4639a2 100644
--- a/activesupport/lib/active_support/core_ext/date.rb
+++ b/activesupport/lib/active_support/core_ext/date.rb
@@ -1,5 +1,5 @@
require 'active_support/core_ext/date/acts_like'
+require 'active_support/core_ext/date/blank'
require 'active_support/core_ext/date/calculations'
require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/date/zones'
-
diff --git a/activesupport/lib/active_support/core_ext/date/blank.rb b/activesupport/lib/active_support/core_ext/date/blank.rb
new file mode 100644
index 0000000000..71627b6a6f
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date/blank.rb
@@ -0,0 +1,12 @@
+require 'date'
+
+class Date #:nodoc:
+ # No Date is blank:
+ #
+ # Date.today.blank? # => false
+ #
+ # @return [false]
+ def blank?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index e8a27b9f38..bcb228b09a 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/date_time/acts_like'
+require 'active_support/core_ext/date_time/blank'
require 'active_support/core_ext/date_time/calculations'
require 'active_support/core_ext/date_time/conversions'
require 'active_support/core_ext/date_time/zones'
diff --git a/activesupport/lib/active_support/core_ext/date_time/blank.rb b/activesupport/lib/active_support/core_ext/date_time/blank.rb
new file mode 100644
index 0000000000..56981b75fb
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_time/blank.rb
@@ -0,0 +1,12 @@
+require 'date'
+
+class DateTime #:nodoc:
+ # No DateTime is ever blank:
+ #
+ # DateTime.now.blank? # => false
+ #
+ # @return [false]
+ def blank?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index a294228979..039c50a4a2 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -127,3 +127,14 @@ class Numeric #:nodoc:
false
end
end
+
+class Time #:nodoc:
+ # No Time is blank:
+ #
+ # Time.now.blank? # => false
+ #
+ # @return [false]
+ def blank?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
index 55f281b213..d4c17dfb07 100644
--- a/activesupport/lib/active_support/core_ext/object/inclusion.rb
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -5,7 +5,7 @@ class Object
# characters = ["Konata", "Kagami", "Tsukasa"]
# "Konata".in?(characters) # => true
#
- # This will throw an ArgumentError if the argument doesn't respond
+ # This will throw an +ArgumentError+ if the argument doesn't respond
# to +#include?+.
def in?(another_object)
another_object.include?(self)
@@ -18,7 +18,7 @@ class Object
#
# params[:bucket_type].presence_in %w( project calendar )
#
- # This will throw an ArgumentError if the argument doesn't respond to +#include?+.
+ # This will throw an +ArgumentError+ if the argument doesn't respond to +#include?+.
#
# @return [Object]
def presence_in(another_object)
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index c67eb25b68..8c16d95b62 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -94,7 +94,7 @@ class Object
# :call-seq:
# try!(*a, &b)
#
- # Same as #try, but raises a NoMethodError exception if the receiver is
+ # Same as #try, but raises a +NoMethodError+ exception if the receiver is
# not +nil+ and does not implement the tried method.
#
# "a".try!(:upcase) # => "A"
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 7055f7f699..cc6f2158e7 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -9,12 +9,10 @@ class String
# encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
# class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
#
- # name = 'Claus Müller'
- # name.reverse # => "rell??M sualC"
- # name.length # => 13
- #
- # name.mb_chars.reverse.to_s # => "rellüM sualC"
- # name.mb_chars.length # => 12
+ # >> "lj".upcase
+ # => "lj"
+ # >> "lj".mb_chars.upcase.to_s
+ # => "LJ"
#
# == Method chaining
#
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 9762d95145..504f96961a 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -118,7 +118,7 @@ module ActiveSupport
# number_to_percentage(1000, locale: :fr) # => 1 000,000%
# number_to_percentage:(1000, precision: nil) # => 1000%
# number_to_percentage('98a') # => 98a%
- # number_to_percentage(100, format: '%n %') # => 100 %
+ # number_to_percentage(100, format: '%n %') # => 100.000 %
def number_to_percentage(number, options = {})
NumberToPercentageConverter.convert(number, options)
end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 07fc787550..eb26eb5ee5 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -388,6 +388,11 @@ module ActiveSupport
end
alias_method :kind_of?, :is_a?
+ # An instance of ActiveSupport::TimeWithZone is never blank
+ def blank?
+ false
+ end
+
def freeze
period; utc; time # preload instance variables before freezing
super
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index cda9732cae..4f47e0519f 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -766,13 +766,11 @@ module CallbacksTest
end
class CallbackFalseTerminatorWithoutConfigTest < ActiveSupport::TestCase
- def test_returning_false_halts_callback_if_config_variable_is_not_set
+ def test_returning_false_does_not_halt_callback_if_config_variable_is_not_set
obj = CallbackFalseTerminator.new
- assert_deprecated do
- obj.save
- assert_equal :second, obj.halted
- assert !obj.saved
- end
+ obj.save
+ assert_equal nil, obj.halted
+ assert obj.saved
end
end
@@ -781,13 +779,11 @@ module CallbacksTest
ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = true
end
- def test_returning_false_halts_callback_if_config_variable_is_true
+ def test_returning_false_does_not_halt_callback_if_config_variable_is_true
obj = CallbackFalseTerminator.new
- assert_deprecated do
- obj.save
- assert_equal :second, obj.halted
- assert !obj.saved
- end
+ obj.save
+ assert_equal nil, obj.halted
+ assert obj.saved
end
end
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 00c41a480e..76454e77c7 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -147,6 +147,39 @@ xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
end
```
+#### Jbuilder
+[Jbuilder](https://github.com/rails/jbuilder) is a gem that's
+maintained by the Rails team and included in the default Rails Gemfile.
+It's similar to Builder, but is used to generate JSON, instead of XML.
+
+If you don't have it, you can add the following to your Gemfile:
+
+```ruby
+gem 'jbuilder'
+```
+
+A Jbuilder object named `json` is automatically made available to templates with
+a `.jbuilder` extension.
+
+Here is a basic example:
+
+```ruby
+json.name("Alex")
+json.email("alex@example.com")
+```
+
+would produce:
+
+```json
+{
+ "name": "Alex",
+ "email: "alex@example.com"
+}
+```
+
+See the [Jbuilder documention](https://github.com/rails/jbuilder#jbuilder) for
+more examples and information.
+
#### Template Caching
By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will check the file's modification time and recompile it in development mode.
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 3b1588b75a..6bbd68ff78 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -150,7 +150,7 @@ When you include the engine into an application later on, you will do so with
this line in the Rails application's `Gemfile`:
```ruby
-gem 'blorgh', path: "vendor/engines/blorgh"
+gem 'blorgh', path: 'engines/blorgh'
```
Don't forget to run `bundle install` as usual. By specifying it as a gem within
@@ -639,7 +639,7 @@ However, because you are developing the `blorgh` engine on your local machine,
you will need to specify the `:path` option in your `Gemfile`:
```ruby
-gem 'blorgh', path: "/path/to/blorgh"
+gem 'blorgh', path: 'engines/blorgh'
```
Then run `bundle` to install the gem.
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 987320a0f8..ea79855919 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -109,7 +109,7 @@ The **translations load path** (`I18n.load_path`) is an array of paths to files
NOTE: The backend lazy-loads these translations when a translation is looked up for the first time. This backend can be swapped with something else even after translations have already been announced.
-The default `application.rb` file has instructions on how to add locales from another directory and how to set a different default locale.
+The default `config/application.rb` file has instructions on how to add locales from another directory and how to set a different default locale.
```ruby
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
@@ -117,7 +117,7 @@ The default `application.rb` file has instructions on how to add locales from an
# config.i18n.default_locale = :de
```
-The load path must be specified before any translations are looked up. To change the default locale from an initializer instead of `application.rb`:
+The load path must be specified before any translations are looked up. To change the default locale from an initializer instead of `config/application.rb`:
```ruby
# config/initializers/locale.rb
diff --git a/guides/source/routing.md b/guides/source/routing.md
index e4799d93fa..1fd38c0940 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -7,7 +7,7 @@ This guide covers the user-facing features of Rails routing.
After reading this guide, you will know:
-* How to interpret the code in `routes.rb`.
+* How to interpret the code in `config/routes.rb`.
* How to construct your own routes, using either the preferred resourceful style or the `match` method.
* What parameters to expect an action to receive.
* How to automatically create paths and URLs using route helpers.
@@ -1118,7 +1118,7 @@ Rails offers facilities for inspecting and testing your routes.
To get a complete list of the available routes in your application, visit `http://localhost:3000/rails/info/routes` in your browser while your server is running in the **development** environment. You can also execute the `rake routes` command in your terminal to produce the same output.
-Both methods will list all of your routes, in the same order that they appear in `routes.rb`. For each route, you'll see:
+Both methods will list all of your routes, in the same order that they appear in `config/routes.rb`. For each route, you'll see:
* The route name (if any)
* The HTTP verb used (if the route doesn't respond to all verbs)
diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb
index ed0ea9dd4d..38004fe99a 100644
--- a/railties/lib/rails/mailers_controller.rb
+++ b/railties/lib/rails/mailers_controller.rb
@@ -31,7 +31,7 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
raise AbstractController::ActionNotFound, "Email part '#{part_type}' not found in #{@preview.name}##{@email_action}"
end
else
- @part = find_preferred_part(request.format, Mime::HTML, Mime::TEXT)
+ @part = find_preferred_part(request.format, Mime::Type[:HTML], Mime::Type[:TEXT])
render action: 'email', layout: false, formats: %w[html]
end
else
diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb
index bb6e49966d..cf95cd8ad0 100644
--- a/railties/lib/rails/templates/rails/mailers/email.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/email.html.erb
@@ -95,8 +95,8 @@
<% if @email.multipart? %>
<dd>
<select onchange="document.getElementsByName('messageBody')[0].src=this.options[this.selectedIndex].value;">
- <option <%= request.format == Mime::HTML ? 'selected' : '' %> value="?part=text%2Fhtml">View as HTML email</option>
- <option <%= request.format == Mime::TEXT ? 'selected' : '' %> value="?part=text%2Fplain">View as plain-text email</option>
+ <option <%= request.format == Mime::Type[:HTML] ? 'selected' : '' %> value="?part=text%2Fhtml">View as HTML email</option>
+ <option <%= request.format == Mime::Type[:TEXT] ? 'selected' : '' %> value="?part=text%2Fplain">View as plain-text email</option>
</select>
</dd>
<% end %>
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index daf362357c..e5f10a89d3 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -1,7 +1,6 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/app/app_generator'
require 'generators/shared_generator_tests'
-require 'mocha/setup' # FIXME: stop using mocha
DEFAULT_APP_FILES = %w(
.gitignore
@@ -117,35 +116,33 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [app_root]
- Rails.application.config.root = app_moved_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
+ stub_rails_application(app_moved_root) do
+ Rails.application.stub(:is_a?, -> *args { Rails::Application }) do
+ FileUtils.mv(app_root, app_moved_root)
- FileUtils.mv(app_root, app_moved_root)
+ # make sure we are in correct dir
+ FileUtils.cd(app_moved_root)
- # make sure we are in correct dir
- FileUtils.cd(app_moved_root)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true },
- destination_root: app_moved_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file "myapp_moved/config/environment.rb", /Rails\.application\.initialize!/
- assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true },
+ destination_root: app_moved_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "myapp_moved/config/environment.rb", /Rails\.application\.initialize!/
+ assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/
+ end
+ end
end
def test_rails_update_generates_correct_session_key
app_root = File.join(destination_root, 'myapp')
run_generator [app_root]
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file "myapp/config/initializers/session_store.rb", /_myapp_session/
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "myapp/config/initializers/session_store.rb", /_myapp_session/
+ end
end
def test_new_application_use_json_serialzier
@@ -158,14 +155,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
app_root = File.join(destination_root, 'myapp')
run_generator [app_root]
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/)
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/)
+ end
end
def test_rails_update_does_not_create_callback_terminator_initializer
@@ -174,14 +169,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
FileUtils.rm("#{app_root}/config/initializers/callback_terminator.rb")
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_no_file "#{app_root}/config/initializers/callback_terminator.rb"
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_no_file "#{app_root}/config/initializers/callback_terminator.rb"
+ end
end
def test_rails_update_does_not_remove_callback_terminator_initializer_if_already_present
@@ -190,14 +183,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
FileUtils.touch("#{app_root}/config/initializers/callback_terminator.rb")
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file "#{app_root}/config/initializers/callback_terminator.rb"
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "#{app_root}/config/initializers/callback_terminator.rb"
+ end
end
def test_rails_update_set_the_cookie_serializer_to_marchal_if_it_is_not_already_configured
@@ -206,14 +197,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb")
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/)
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/)
+ end
end
def test_rails_update_does_not_create_active_record_belongs_to_required_by_default
@@ -222,14 +211,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
FileUtils.rm("#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb")
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_no_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb"
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_no_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb"
+ end
end
def test_rails_update_does_not_remove_active_record_belongs_to_required_by_default_if_already_present
@@ -238,14 +225,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
FileUtils.touch("#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb")
- Rails.application.config.root = app_root
- Rails.application.class.stubs(:name).returns("Myapp")
- Rails.application.stubs(:is_a?).returns(Rails::Application)
-
- generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
- generator.send(:app_const)
- quietly { generator.send(:update_config_files) }
- assert_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb"
+ stub_rails_application(app_root) do
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb"
+ end
end
def test_application_names_are_not_singularized
@@ -456,13 +441,15 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_usage_read_from_file
- File.expects(:read).returns("USAGE FROM FILE")
- assert_equal "USAGE FROM FILE", Rails::Generators::AppGenerator.desc
+ assert_called(File, :read, returns: "USAGE FROM FILE") do
+ assert_equal "USAGE FROM FILE", Rails::Generators::AppGenerator.desc
+ end
end
def test_default_usage
- Rails::Generators::AppGenerator.expects(:usage_path).returns(nil)
- assert_match(/Create rails files for app generator/, Rails::Generators::AppGenerator.desc)
+ assert_called(Rails::Generators::AppGenerator, :usage_path, returns: nil) do
+ assert_match(/Create rails files for app generator/, Rails::Generators::AppGenerator.desc)
+ end
end
def test_default_namespace
@@ -538,18 +525,31 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_spring_binstubs
jruby_skip "spring doesn't run on JRuby"
- generator.stubs(:bundle_command).with('install')
- generator.expects(:bundle_command).with('exec spring binstub --all').once
- quietly { generator.invoke_all }
+ command_check = -> command do
+ @binstub_called ||= 0
+
+ case command
+ when 'install'
+ # Called when running bundle, we just want to stub it so nothing to do here.
+ when 'exec spring binstub --all'
+ @binstub_called += 1
+ assert_equal 1, @binstub_called, "exec spring binstub --all expected to be called once, but was called #{@install_called} times."
+ end
+ end
+
+ generator.stub :bundle_command, command_check do
+ quietly { generator.invoke_all }
+ end
end
def test_spring_no_fork
jruby_skip "spring doesn't run on JRuby"
- Process.stubs(:respond_to?).with(:fork).returns(false)
- run_generator
+ assert_called_with(Process, :respond_to?, [:fork], returns: false) do
+ run_generator
- assert_file "Gemfile" do |content|
- assert_no_match(/spring/, content)
+ assert_file "Gemfile" do |content|
+ assert_no_match(/spring/, content)
+ end
end
end
@@ -651,18 +651,37 @@ class AppGeneratorTest < Rails::Generators::TestCase
template = %{ after_bundle { run 'echo ran after_bundle' } }
template.instance_eval "def read; self; end" # Make the string respond to read
- generator([destination_root], template: path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
+ check_open = -> *args do
+ assert_equal [ path, 'Accept' => 'application/x-thor-template' ], args
+ template
+ end
- bundler_first = sequence('bundle, binstubs, after_bundle')
- generator.expects(:bundle_command).with('install').once.in_sequence(bundler_first)
- generator.expects(:bundle_command).with('exec spring binstub --all').in_sequence(bundler_first)
- generator.expects(:run).with('echo ran after_bundle').in_sequence(bundler_first)
+ sequence = ['install', 'exec spring binstub --all', 'echo ran after_bundle']
+ ensure_bundler_first = -> command do
+ @sequence_step ||= 0
- quietly { generator.invoke_all }
+ assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}"
+ @sequence_step += 1
+ end
+
+ generator([destination_root], template: path).stub(:open, check_open, template) do
+ generator.stub(:bundle_command, ensure_bundler_first) do
+ generator.stub(:run, ensure_bundler_first) do
+ quietly { generator.invoke_all }
+ end
+ end
+ end
end
protected
+ def stub_rails_application(root)
+ Rails.application.config.root = root
+ Rails.application.class.stub(:name, "Myapp") do
+ yield
+ end
+ end
+
def action(*args, &block)
capture(:stdout) { generator.send(*args, &block) }
end
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index afccf9d885..0625e5fbd7 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -1,7 +1,6 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/plugin/plugin_generator'
require 'generators/shared_generator_tests'
-require 'mocha/setup' # FIXME: stop using mocha
DEFAULT_PLUGIN_FILES = %w(
.gitignore
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 77372fb514..acb78ec888 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -28,9 +28,22 @@ module SharedGeneratorTests
def assert_generates_with_bundler(options = {})
generator([destination_root], options)
- generator.expects(:bundle_command).with('install').once
- generator.stubs(:bundle_command).with('exec spring binstub --all')
- quietly { generator.invoke_all }
+
+ command_check = -> command do
+ @install_called ||= 0
+
+ case command
+ when 'install'
+ @install_called += 1
+ assert_equal 1, @install_called, "install expected to be called once, but was called #{@install_called} times"
+ when 'exec spring binstub --all'
+ # Called when running tests with spring, let through unscathed.
+ end
+ end
+
+ generator.stub :bundle_command, command_check do
+ quietly { generator.invoke_all }
+ end
end
def test_generation_runs_bundle_install
@@ -91,8 +104,14 @@ module SharedGeneratorTests
template = %{ say "It works!" }
template.instance_eval "def read; self; end" # Make the string respond to read
- generator([destination_root], template: path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }
+ check_open = -> *args do
+ assert_equal [ path, 'Accept' => 'application/x-thor-template' ], args
+ template
+ end
+
+ generator([destination_root], template: path).stub(:open, check_open, template) do
+ quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }
+ end
end
def test_dev_option
@@ -107,18 +126,19 @@ module SharedGeneratorTests
end
def test_skip_gemfile
- generator([destination_root], skip_gemfile: true).expects(:bundle_command).never
- quietly { generator.invoke_all }
- assert_no_file 'Gemfile'
+ assert_not_called(generator([destination_root], skip_gemfile: true), :bundle_command) do
+ quietly { generator.invoke_all }
+ assert_no_file 'Gemfile'
+ end
end
def test_skip_bundle
- generator([destination_root], skip_bundle: true).expects(:bundle_command).never
- quietly { generator.invoke_all }
-
- # skip_bundle is only about running bundle install, ensure the Gemfile is still
- # generated.
- assert_file 'Gemfile'
+ assert_not_called(generator([destination_root], skip_bundle: true), :bundle_command) do
+ quietly { generator.invoke_all }
+ # skip_bundle is only about running bundle install, ensure the Gemfile is still
+ # generated.
+ assert_file 'Gemfile'
+ end
end
def test_skip_git