aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--actionmailer/README.rdoc6
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionpack/CHANGELOG.md8
-rw-r--r--actionpack/README.rdoc7
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb33
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb12
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb246
-rw-r--r--actionpack/test/abstract/callbacks_test.rb8
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb2
-rw-r--r--actionpack/test/controller/routing_test.rb27
-rw-r--r--actionpack/test/dispatch/mapper_test.rb2
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb5
-rw-r--r--actionpack/test/dispatch/routing_test.rb98
-rw-r--r--actionview/README.rdoc7
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb33
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb5
-rw-r--r--actionview/lib/action_view/helpers/debug_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb3
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb5
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb4
-rw-r--r--actionview/test/template/number_helper_test.rb1
-rw-r--r--activemodel/README.rdoc7
-rw-r--r--activerecord/CHANGELOG.md46
-rw-r--r--activerecord/README.rdoc13
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb4
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb5
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb62
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb30
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb59
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb67
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb49
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb34
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/utils.rb55
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb57
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb7
-rw-r--r--activerecord/lib/active_record/core.rb53
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/model_schema.rb16
-rw-r--r--activerecord/lib/active_record/persistence.rb16
-rw-r--r--activerecord/lib/active_record/properties.rb28
-rw-r--r--activerecord/lib/active_record/reflection.rb16
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb4
-rw-r--r--activerecord/lib/active_record/result.rb2
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb8
-rw-r--r--activerecord/lib/active_record/transactions.rb8
-rw-r--r--activerecord/lib/active_record/type/binary.rb18
-rw-r--r--activerecord/lib/active_record/type/date.rb4
-rw-r--r--activerecord/lib/active_record/type/decimal.rb4
-rw-r--r--activerecord/lib/active_record/type/numeric.rb23
-rw-r--r--activerecord/lib/active_record/type/serialized.rb37
-rw-r--r--activerecord/lib/active_record/type/time_value.rb4
-rw-r--r--activerecord/lib/active_record/type/value.rb18
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb80
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb35
-rw-r--r--activerecord/test/cases/adapters/postgresql/domain_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb11
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/full_text_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb64
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb28
-rw-r--r--activerecord/test/cases/adapters/postgresql/network_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb21
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb13
-rw-r--r--activerecord/test/cases/adapters/postgresql/utils_test.rb45
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb3
-rw-r--r--activerecord/test/cases/associations/eager_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb2
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb6
-rw-r--r--activerecord/test/cases/core_test.rb68
-rw-r--r--activerecord/test/cases/custom_properties_test.rb29
-rw-r--r--activerecord/test/cases/defaults_test.rb5
-rw-r--r--activerecord/test/cases/dirty_test.rb28
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb2
-rw-r--r--activerecord/test/cases/reflection_test.rb20
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb2
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb81
-rw-r--r--activerecord/test/cases/store_test.rb14
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb5
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb36
-rw-r--r--activerecord/test/models/owner.rb12
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb17
-rw-r--r--activerecord/test/schema/schema.rb1
-rw-r--r--activerecord/test/support/schema_dumping_helper.rb11
-rw-r--r--activesupport/CHANGELOG.md26
-rw-r--r--activesupport/README.rdoc7
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb32
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb9
-rw-r--r--activesupport/lib/active_support/notifications.rb9
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb17
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb6
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb8
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb44
-rw-r--r--activesupport/test/number_helper_test.rb2
-rw-r--r--guides/source/3_2_release_notes.md2
-rw-r--r--guides/source/action_controller_overview.md4
-rw-r--r--guides/source/active_record_querying.md17
-rw-r--r--guides/source/asset_pipeline.md4
-rw-r--r--guides/source/documents.yaml5
-rw-r--r--guides/source/getting_started.md6
-rw-r--r--guides/source/i18n.md18
-rw-r--r--guides/source/maintenance_policy.md6
-rw-r--r--guides/source/migrations.md23
-rw-r--r--guides/source/testing.md20
-rw-r--r--guides/source/upgrading_ruby_on_rails.md9
-rw-r--r--railties/CHANGELOG.md8
-rw-r--r--railties/README.rdoc6
-rw-r--r--railties/lib/rails/generators.rb65
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup28
-rw-r--r--railties/test/generators/app_generator_test.rb1
-rw-r--r--railties/test/generators/shared_generator_tests.rb13
-rw-r--r--railties/test/generators_test.rb8
150 files changed, 1686 insertions, 973 deletions
diff --git a/Gemfile b/Gemfile
index 2a695df618..86ac145b73 100644
--- a/Gemfile
+++ b/Gemfile
@@ -14,6 +14,7 @@ gem 'coffee-rails', '~> 4.0.0'
gem 'arel', github: 'rails/arel', branch: 'master'
gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: 'master'
gem 'i18n', github: 'svenfuchs/i18n', branch: 'master'
+gem 'mail', '~> 2.5.4'
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 67b64fe469..ceca912ada 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -156,7 +156,11 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 9b25feaf75..01d97b7213 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -22,5 +22,5 @@ Gem::Specification.new do |s|
s.add_dependency 'actionpack', version
s.add_dependency 'actionview', version
- s.add_dependency 'mail', '~> 2.5.4'
+ s.add_dependency 'mail', ['~> 2.5', '>= 2.5.4']
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 7da58ca4e7..3b20aec20d 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,6 +1,6 @@
-* Deprecate all *_filter callbacks in favor of *_action callbacks.
-
- *Rafael Mendonça França*
+* Routes specifying 'to:' must be a string that contains a "#" or a rack
+ application. Use of a symbol should be replaced with `action: symbol`.
+ Use of a string without a "#" should be replaced with `controller: string`.
* Fix URL generation with `:trailing_slash` such that it does not add
a trailing slash after `.:format`
@@ -115,7 +115,7 @@
*Boris Kuznetsov*
* Swapped the parameters of assert_equal in `assert_select` so that the
- proper values were printed correctly
+ proper values were printed correctly.
Fixes #14422.
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 2f6575c3b5..02a24a7412 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -48,6 +48,11 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 252e297c69..ca5c80cd71 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -1,5 +1,3 @@
-require 'active_support/deprecation'
-
module AbstractController
module Callbacks
extend ActiveSupport::Concern
@@ -56,11 +54,7 @@ module AbstractController
skip_after_action(*names)
skip_around_action(*names)
end
-
- def skip_filter(*names)
- ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will removed in Rails 5. Use #{callback}_action instead.")
- skip_action_callback(*names)
- end
+ alias_method :skip_filter, :skip_action_callback
# Take callback names and an optional callback proc, normalize them,
# then call the block with each callback. This allows us to abstract
@@ -175,22 +169,14 @@ module AbstractController
set_callback(:process_action, callback, name, options)
end
end
-
- define_method "#{callback}_filter" do |*names, &blk|
- ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will removed in Rails 5. Use #{callback}_action instead.")
- send("#{callback}_action", *names, &blk)
- end
+ alias_method :"#{callback}_filter", :"#{callback}_action"
define_method "prepend_#{callback}_action" do |*names, &blk|
_insert_callbacks(names, blk) do |name, options|
set_callback(:process_action, callback, name, options.merge(:prepend => true))
end
end
-
- define_method "prepend_#{callback}_filter" do |*names, &blk|
- ActiveSupport::Deprecation.warn("prepend_#{callback}_filter is deprecated and will removed in Rails 5. Use prepend_#{callback}_action instead.")
- send("prepend_#{callback}_action", *names, &blk)
- end
+ alias_method :"prepend_#{callback}_filter", :"prepend_#{callback}_action"
# Skip a before, after or around callback. See _insert_callbacks
# for details on the allowed parameters.
@@ -199,18 +185,11 @@ module AbstractController
skip_callback(:process_action, callback, name, options)
end
end
-
- define_method "skip_#{callback}_filter" do |*names, &blk|
- ActiveSupport::Deprecation.warn("skip_#{callback}_filter is deprecated and will removed in Rails 5. Use skip_#{callback}_action instead.")
- send("skip_#{callback}_action", *names, &blk)
- end
+ alias_method :"skip_#{callback}_filter", :"skip_#{callback}_action"
# *_action is the same as append_*_action
- alias_method :"append_#{callback}_action", :"#{callback}_action" # alias_method :append_before_action, :before_action
- define_method "append_#{callback}_filter" do |*names, &blk|
- ActiveSupport::Deprecation.warn("append_#{callback}_filter is deprecated and will removed in Rails 5. Use append_#{callback}_action instead.")
- send("append_#{callback}_action", *names, &blk)
- end
+ alias_method :"append_#{callback}_action", :"#{callback}_action"
+ alias_method :"append_#{callback}_filter", :"#{callback}_action"
end
end
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index d86d49c9dc..265048a308 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -130,7 +130,7 @@ module ActionController
# looping in the common use case permit + mass-assignment. Defined in a
# method to instantiate it only if needed.
def converted_arrays
- @converted_arrays ||= Set.new
+ @converted_arrays ||= {}
end
# Returns +true+ if the parameter is permitted, +false+ otherwise.
@@ -333,15 +333,15 @@ module ActionController
private
def convert_hashes_to_parameters(key, value, assign_if_converted=true)
- converted = convert_value_to_parameters(value)
+ converted = convert_value_to_parameters(key, value)
self[key] = converted if assign_if_converted && !converted.equal?(value)
converted
end
- def convert_value_to_parameters(value)
- if value.is_a?(Array) && !converted_arrays.member?(value)
- converted = value.map { |_| convert_value_to_parameters(_) }
- converted_arrays << converted
+ def convert_value_to_parameters(key, value)
+ if value.is_a?(Array) && !converted_arrays.member?(key)
+ converted = value.map { |v| convert_value_to_parameters(nil, v) }
+ converted_arrays[key] = converted if key
converted
elsif value.is_a?(Parameters) || !value.is_a?(Hash)
value
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 1ab11392ce..5f7627cf96 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -15,7 +15,6 @@ module ActionDispatch
query_parameters.dup
end
params.merge!(path_parameters)
- params.with_indifferent_access
end
end
alias :params :parameters
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index dfe258e463..4d4b443fb4 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -291,7 +291,7 @@ module ActionDispatch
# Override Rack's GET method to support indifferent access
def GET
- @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
+ @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
rescue TypeError => e
raise ActionController::BadRequest.new(:query, e)
end
@@ -299,7 +299,7 @@ module ActionDispatch
# Override Rack's POST method to support indifferent access
def POST
- @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
+ @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
rescue TypeError => e
raise ActionController::BadRequest.new(:request, e)
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 3bc578b379..aac5546aa1 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -7,6 +7,7 @@ require 'active_support/core_ext/module/remove_method'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
require 'action_dispatch/routing/endpoint'
+require 'active_support/deprecation'
module ActionDispatch
module Routing
@@ -60,32 +61,73 @@ module ActionDispatch
end
class Mapping #:nodoc:
- IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format]
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- attr_reader :scope, :options, :requirements, :conditions, :defaults
- attr_reader :to, :default_controller, :default_action
-
- def initialize(scope, path, options)
- @scope = scope
- @requirements, @conditions, @defaults = {}, {}, {}
+ attr_reader :requirements, :conditions, :defaults
+ attr_reader :to, :default_controller, :default_action, :as, :anchor
+ def self.build(scope, path, options)
options = scope[:options].merge(options) if scope[:options]
- @to = options[:to]
- @default_controller = options[:controller] || scope[:controller]
- @default_action = options[:action] || scope[:action]
- path = normalize_path! path, options[:format]
+ options.delete :only
+ options.delete :except
+ options.delete :shallow_path
+ options.delete :shallow_prefix
+ options.delete :shallow
+
+ defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
+
+ new scope, path, defaults, options
+ end
+
+ def initialize(scope, path, defaults, options)
+ @requirements, @conditions = {}, {}
+ @defaults = defaults
+
+ @to = options.delete :to
+ @default_controller = options.delete(:controller) || scope[:controller]
+ @default_action = options.delete(:action) || scope[:action]
+ @as = options.delete :as
+ @anchor = options.delete :anchor
+
+ formatted = options.delete :format
+ via = Array(options.delete(:via) { [] })
+ options_constraints = options.delete :constraints
+
+ path = normalize_path! path, formatted
ast = path_ast path
path_params = path_params ast
- @options = normalize_options!(options, path_params, ast)
- normalize_requirements!(path_params)
- normalize_conditions!(path_params, path, ast)
- normalize_defaults!
+
+ options = normalize_options!(options, formatted, path_params, ast, scope[:module])
+
+
+ split_constraints(path_params, scope[:constraints]) if scope[:constraints]
+ constraints = constraints(options, path_params)
+
+ split_constraints path_params, constraints
+
+ @blocks = blocks(options_constraints, scope[:blocks])
+
+ if options_constraints.is_a?(Hash)
+ split_constraints path_params, options_constraints
+ options_constraints.each do |key, default|
+ if URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
+ @defaults[key] ||= default
+ end
+ end
+ end
+
+ normalize_format!(formatted)
+
+ @conditions[:path_info] = path
+ @conditions[:parsed_path_info] = ast
+
+ add_request_method(via, @conditions)
+ normalize_defaults!(options)
end
def to_route
- [ app, conditions, requirements, defaults, options[:as], options[:anchor] ]
+ [ app(@blocks), conditions, requirements, defaults, as, anchor ]
end
private
@@ -106,17 +148,17 @@ module ActionDispatch
format != false && !path.include?(':format') && !path.end_with?('/')
end
- def normalize_options!(options, path_params, path_ast)
+ def normalize_options!(options, formatted, path_params, path_ast, modyoule)
# Add a constraint for wildcard route to make it non-greedy and match the
# optional format part of the route by default
- if options[:format] != false
+ if formatted != false
path_ast.grep(Journey::Nodes::Star) do |node|
options[node.name.to_sym] ||= /.+?/
end
end
if path_params.include?(:controller)
- raise ArgumentError, ":controller segment is not allowed within a namespace block" if scope[:module]
+ raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
# Add a default constraint for :controller path segments that matches namespaced
# controllers with default routes like :controller/:action/:id(.:format), e.g:
@@ -128,23 +170,36 @@ module ActionDispatch
if to.respond_to? :call
options
else
- options.merge!(default_controller_and_action(path_params))
+ to_endpoint = split_to to
+ controller = to_endpoint[0] || default_controller
+ action = to_endpoint[1] || default_action
+
+ controller = add_controller_module(controller, modyoule)
+
+ options.merge! check_controller_and_action(path_params, controller, action)
end
end
- def normalize_requirements!(path_params)
- constraints.each do |key, requirement|
- next unless path_params.include?(key) || key == :controller
- verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
- @requirements[key] = requirement
+ def split_constraints(path_params, constraints)
+ constraints.each_pair do |key, requirement|
+ if path_params.include?(key) || key == :controller
+ verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
+ @requirements[key] = requirement
+ else
+ @conditions[key] = requirement
+ end
end
+ end
- if options[:format] == true
+ def normalize_format!(formatted)
+ if formatted == true
@requirements[:format] ||= /.+/
- elsif Regexp === options[:format]
- @requirements[:format] = options[:format]
- elsif String === options[:format]
- @requirements[:format] = Regexp.compile(options[:format])
+ elsif Regexp === formatted
+ @requirements[:format] = formatted
+ @defaults[:format] = nil
+ elsif String === formatted
+ @requirements[:format] = Regexp.compile(formatted)
+ @defaults[:format] = formatted
end
end
@@ -158,31 +213,12 @@ module ActionDispatch
end
end
- def normalize_defaults!
- @defaults.merge!(scope[:defaults]) if scope[:defaults]
- @defaults.merge!(options[:defaults]) if options[:defaults]
-
- options.each do |key, default|
- unless Regexp === default || IGNORE_OPTIONS.include?(key)
+ def normalize_defaults!(options)
+ options.each_pair do |key, default|
+ unless Regexp === default
@defaults[key] = default
end
end
-
- if options[:constraints].is_a?(Hash)
- options[:constraints].each do |key, default|
- if URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
- @defaults[key] ||= default
- end
- end
- elsif options[:constraints]
- verify_callable_constraint(options[:constraints])
- end
-
- if Regexp === options[:format]
- @defaults[:format] = nil
- elsif String === options[:format]
- @defaults[:format] = options[:format]
- end
end
def verify_callable_constraint(callable_constraint)
@@ -191,41 +227,22 @@ module ActionDispatch
end
end
- def normalize_conditions!(path_params, path, ast)
- @conditions[:path_info] = path
- @conditions[:parsed_path_info] = ast
+ def add_request_method(via, conditions)
+ return if via == [:all]
- constraints.each do |key, condition|
- unless path_params.include?(key) || key == :controller
- @conditions[key] = condition
- end
- end
-
- required_defaults = []
- options.each do |key, required_default|
- unless path_params.include?(key) || IGNORE_OPTIONS.include?(key) || Regexp === required_default
- required_defaults << key
- end
- end
- @conditions[:required_defaults] = required_defaults
-
- via_all = options.delete(:via) if options[:via] == :all
-
- if !via_all && options[:via].blank?
+ if via.empty?
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
"If you want to expose your action to GET, use `get` in the router:\n" \
" Instead of: match \"controller#action\"\n" \
" Do: get \"controller#action\""
- raise msg
+ raise ArgumentError, msg
end
- if via = options[:via]
- @conditions[:request_method] = Array(via).map { |m| m.to_s.dasherize.upcase }
- end
+ conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
end
- def app
+ def app(blocks)
return to if Redirect === to
if to.respond_to?(:call)
@@ -239,13 +256,7 @@ module ActionDispatch
end
end
- def default_controller_and_action(path_params)
- controller, action = get_controller_and_action(default_controller,
- default_action,
- to,
- @scope[:module]
- )
-
+ def check_controller_and_action(path_params, controller, action)
hash = check_part(:controller, controller, path_params, {}) do |part|
translate_controller(part) {
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
@@ -272,21 +283,30 @@ module ActionDispatch
hash
end
- def get_controller_and_action(controller, action, to, modyoule)
+ def split_to(to)
case to
- when Symbol then action = to.to_s
- when /#/ then controller, action = to.split('#')
- when String then controller = to
+ when Symbol
+ ActiveSupport::Deprecation.warn "defining a route where `to` is a symbol is deprecated. Please change \"to: :#{to}\" to \"action: :#{to}\""
+ [nil, to.to_s]
+ when /#/ then to.split('#')
+ when String
+ ActiveSupport::Deprecation.warn "defining a route where `to` is a controller without an action is deprecated. Please change \"to: :#{to}\" to \"controller: :#{to}\""
+ [to, nil]
+ else
+ []
end
+ end
+ def add_controller_module(controller, modyoule)
if modyoule && !controller.is_a?(Regexp)
if controller =~ %r{\A/}
- controller = controller[1..-1]
+ controller[1..-1]
else
- controller = [modyoule, controller].compact.join("/")
+ [modyoule, controller].compact.join("/")
end
+ else
+ controller
end
- [controller, action]
end
def translate_controller(controller)
@@ -296,24 +316,27 @@ module ActionDispatch
yield
end
- def blocks
- if options[:constraints].present? && !options[:constraints].is_a?(Hash)
- [options[:constraints]]
+ def blocks(options_constraints, scope_blocks)
+ if options_constraints && !options_constraints.is_a?(Hash)
+ verify_callable_constraint(options_constraints)
+ [options_constraints]
else
- scope[:blocks] || []
+ scope_blocks || []
end
end
- def constraints
- @constraints ||= {}.tap do |constraints|
- constraints.merge!(scope[:constraints]) if scope[:constraints]
-
- options.except(*IGNORE_OPTIONS).each do |key, option|
- constraints[key] = option if Regexp === option
+ def constraints(options, path_params)
+ constraints = {}
+ required_defaults = []
+ options.each_pair do |key, option|
+ if Regexp === option
+ constraints[key] = option
+ else
+ required_defaults << key unless path_params.include?(key)
end
-
- constraints.merge!(options[:constraints]) if options[:constraints].is_a?(Hash)
end
+ @conditions[:required_defaults] = required_defaults
+ constraints
end
def path_params(ast)
@@ -1445,7 +1468,20 @@ module ActionDispatch
if rest.empty? && Hash === path
options = path
path, to = options.find { |name, _value| name.is_a?(String) }
- options[:to] = to
+
+ case to
+ when Symbol
+ options[:action] = to
+ when String
+ if to =~ /#/
+ options[:to] = to
+ else
+ options[:controller] = to
+ end
+ else
+ options[:to] = to
+ end
+
options.delete(path)
paths = [path]
else
@@ -1515,7 +1551,7 @@ module ActionDispatch
options[:as] = name_for_action(options[:as], action)
end
- mapping = Mapping.new(@scope, URI.parser.escape(path), options)
+ mapping = Mapping.build(@scope, URI.parser.escape(path), options)
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@set.add_route(app, conditions, requirements, defaults, as, anchor)
end
diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb
index 07571602e4..8cba049485 100644
--- a/actionpack/test/abstract/callbacks_test.rb
+++ b/actionpack/test/abstract/callbacks_test.rb
@@ -267,11 +267,9 @@ module AbstractController
end
class AliasedCallbacks < ControllerWithCallbacks
- ActiveSupport::Deprecation.silence do
- before_filter :first
- after_filter :second
- around_filter :aroundz
- end
+ before_filter :first
+ after_filter :second
+ around_filter :aroundz
def first
@text = "Hello world"
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 33a91d72d9..1856ecd42b 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -169,7 +169,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
test 'arrays are converted at most once' do
params = ActionController::Parameters.new(foo: [{}])
- assert params[:foo].equal?(params[:foo])
+ assert_same params[:foo], params[:foo]
end
test "fetch doesnt raise ParameterMissing exception if there is a default" do
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 9dc6d77012..660589a86e 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -243,6 +243,33 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
assert_equal 'clients', get(URI('http://clients.example.org/'))
end
+ def test_scoped_lambda
+ scope_called = false
+ rs.draw do
+ scope '/foo', :constraints => lambda { |req| scope_called = true } do
+ get '/', :to => lambda { |env| [200, {}, %w{default}] }
+ end
+ end
+
+ assert_equal 'default', get(URI('http://www.example.org/foo/'))
+ assert scope_called, "scope constraint should be called"
+ end
+
+ def test_scoped_lambda_with_get_lambda
+ scope_called = false
+ inner_called = false
+
+ rs.draw do
+ scope '/foo', :constraints => lambda { |req| flunk "should not be called" } do
+ get '/', :constraints => lambda { |req| inner_called = true },
+ :to => lambda { |env| [200, {}, %w{default}] }
+ end
+ end
+
+ assert_equal 'default', get(URI('http://www.example.org/foo/'))
+ assert inner_called, "inner constraint should be called"
+ end
+
def test_empty_string_match
rs.draw do
get '/:username', :constraints => { :username => /[^\/]+/ },
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index bf82e09f39..d8d3209dac 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -38,7 +38,7 @@ module ActionDispatch
def test_mapping_requirements
options = { :controller => 'foo', :action => 'bar', :via => :get }
- m = Mapper::Mapping.new({}, '/store/:name(*rest)', options)
+ m = Mapper::Mapping.build({}, '/store/:name(*rest)', options)
_, _, requirements, _ = m.to_route
assert_equal(/.+?/, requirements[:rest])
end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 2a2f92b5b3..2db3fee6bb 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -145,7 +145,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
test "does not raise EOFError on GET request with multipart content-type" do
with_routing do |set|
set.draw do
- get ':action', to: 'multipart_params_parsing_test/test'
+ get ':action', controller: 'multipart_params_parsing_test/test'
end
headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" }
get "/parse", {}, headers
@@ -174,7 +174,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- post ':action', :to => 'multipart_params_parsing_test/test'
+ post ':action', :controller => 'multipart_params_parsing_test/test'
end
yield
end
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 9a77454f30..1de05cbf09 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -130,10 +130,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
end
test "ambiguous params returns a bad request" do
- with_routing do |set|
- set.draw do
- post ':action', to: ::UrlEncodedParamsParsingTest::TestController
- end
+ with_test_routing do
post "/parse", "foo[]=bar&foo[4]=bar"
assert_response :bad_request
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index d6477e19bb..778dbfc74d 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -361,8 +361,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
draw do
controller(:global) do
get 'global/hide_notice'
- get 'global/export', :to => :export, :as => :export_request
- get '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ }
+ get 'global/export', :action => :export, :as => :export_request
+ get '/export/:id/:file', :action => :export, :as => :export_download, :constraints => { :file => /.*/ }
get 'global/:action'
end
end
@@ -730,8 +730,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
draw do
resources :replies do
member do
- put :answer, :to => :mark_as_answer
- delete :answer, :to => :unmark_as_answer
+ put :answer, :action => :mark_as_answer
+ delete :answer, :action => :unmark_as_answer
end
end
end
@@ -1188,7 +1188,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
controller :articles do
scope '/articles', :as => 'article' do
scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do
- get '/:id', :to => :with_id, :as => ""
+ get '/:id', :action => :with_id, :as => ""
end
end
end
@@ -1435,7 +1435,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_scoped_controller_with_namespace_and_action
draw do
namespace :account do
- get ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback
+ get ':action/callback', :action => /twitter|github/, :controller => "callbacks", :as => :callback
end
end
@@ -1492,7 +1492,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_normalize_namespaced_matches
draw do
namespace :account do
- get 'description', :to => :description, :as => "description"
+ get 'description', :action => :description, :as => "description"
end
end
@@ -2154,7 +2154,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
resources :invoices do
get "outstanding" => "invoices#outstanding", :on => :collection
- get "overdue", :to => :overdue, :on => :collection
+ get "overdue", :action => :overdue, :on => :collection
get "print" => "invoices#print", :as => :print, :on => :member
post "preview" => "invoices#preview", :as => :preview, :on => :new
end
@@ -2242,6 +2242,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/api/1.0/users/first.last.xml', api_user_path(:version => '1.0', :id => 'first.last', :format => :xml)
end
+ def test_match_without_via
+ assert_raises(ArgumentError) do
+ draw do
+ match '/foo/bar', :to => 'files#show'
+ end
+ end
+ end
+
+ def test_match_with_empty_via
+ assert_raises(ArgumentError) do
+ draw do
+ match '/foo/bar', :to => 'files#show', :via => []
+ end
+ end
+ end
+
def test_glob_parameter_accepts_regexp
draw do
get '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
@@ -2980,7 +2996,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
assert_raise(ArgumentError) do
- draw { controller("/feeds") { get '/feeds/:service', :to => :show } }
+ assert_deprecated do
+ draw { controller("/feeds") { get '/feeds/:service', :to => :show } }
+ end
end
assert_raise(ArgumentError) do
@@ -3239,6 +3257,58 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/admin/posts/1/comments', admin_post_comments_path('1')
end
+ def test_mix_string_to_controller_action
+ draw do
+ get '/projects', controller: 'project_files',
+ action: 'index',
+ to: 'comments#index'
+ end
+ get '/projects'
+ assert_equal 'comments#index', @response.body
+ end
+
+ def test_mix_string_to_controller
+ draw do
+ get '/projects', controller: 'project_files',
+ to: 'comments#index'
+ end
+ get '/projects'
+ assert_equal 'comments#index', @response.body
+ end
+
+ def test_mix_string_to_action
+ draw do
+ get '/projects', action: 'index',
+ to: 'comments#index'
+ end
+ get '/projects'
+ assert_equal 'comments#index', @response.body
+ end
+
+ def test_mix_symbol_to_controller_action
+ assert_deprecated do
+ draw do
+ get '/projects', controller: 'project_files',
+ action: 'index',
+ to: :show
+ end
+ end
+ get '/projects'
+ assert_equal 'project_files#show', @response.body
+ end
+
+ def test_mix_string_to_controller_action_no_hash
+ assert_deprecated do
+ draw do
+ get '/projects', controller: 'project_files',
+ action: 'index',
+ to: 'show'
+ end
+ end
+ get '/projects'
+ assert_equal 'show#index', @response.body
+ end
+
def test_shallow_path_and_prefix_are_not_added_to_non_shallow_routes
draw do
scope shallow_path: 'projects', shallow_prefix: 'project' do
@@ -3503,7 +3573,7 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
def test_missing_controller
ex = assert_raises(ArgumentError) {
draw do
- get '/foo/bar', :to => :index
+ get '/foo/bar', :action => :index
end
}
assert_match(/Missing :controller/, ex.message)
@@ -3511,8 +3581,10 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
def test_missing_action
ex = assert_raises(ArgumentError) {
- draw do
- get '/foo/bar', :to => 'foo'
+ assert_deprecated do
+ draw do
+ get '/foo/bar', :to => 'foo'
+ end
end
}
assert_match(/Missing :action/, ex.message)
@@ -4019,7 +4091,7 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest
set.draw do
get "/bar/:id", :to => redirect("/foo/show/%{id}")
get "/foo/show(/:id)", :to => "test_invalid_urls/foo#show"
- get "/foo(/:action(/:id))", :to => "test_invalid_urls/foo"
+ get "/foo(/:action(/:id))", :controller => "test_invalid_urls/foo"
get "/:controller(/:action(/:id))"
end
diff --git a/actionview/README.rdoc b/actionview/README.rdoc
index 35f805346c..5bb62c7562 100644
--- a/actionview/README.rdoc
+++ b/actionview/README.rdoc
@@ -29,6 +29,11 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index f53cce32b0..7333ea999a 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -11,7 +11,7 @@ module ActionView
# the assets exist before linking to them:
#
# image_tag("rails.png")
- # # => <img alt="Rails" src="/images/rails.png" />
+ # # => <img alt="Rails" src="/assets/rails.png" />
# stylesheet_link_tag("application")
# # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
module AssetTagHelper
@@ -20,8 +20,7 @@ module ActionView
include AssetUrlHelper
include TagHelper
- # Returns an HTML script tag for each of the +sources+ provided. If
- # you don't specify an extension, <tt>.js</tt> will be appended automatically.
+ # Returns an HTML script tag for each of the +sources+ provided.
#
# Sources may be paths to JavaScript files. Relative paths are assumed to be relative
# to <tt>assets/javascripts</tt>, full paths are assumed to be relative to the document
@@ -34,19 +33,19 @@ module ActionView
# last argument.
#
# When the Asset Pipeline is enabled, you can pass the name of your manifest as
- # source and include other JavaScript or CoffeeScript files inside the manifest.
+ # source, and include other JavaScript or CoffeeScript files inside the manifest.
#
# javascript_include_tag "xmlhr"
- # # => <script src="/javascripts/xmlhr.js?1284139606"></script>
+ # # => <script src="/assets/xmlhr.js?1284139606"></script>
#
# javascript_include_tag "template.jst", extname: false
- # # => <script src="/javascripts/template.jst?1284139606"></script>
+ # # => <script src="/assets/template.jst?1284139606"></script>
#
# javascript_include_tag "xmlhr.js"
- # # => <script src="/javascripts/xmlhr.js?1284139606"></script>
+ # # => <script src="/assets/xmlhr.js?1284139606"></script>
#
# javascript_include_tag "common.javascript", "/elsewhere/cools"
- # # => <script src="/javascripts/common.javascript?1284139606"></script>
+ # # => <script src="/assets/common.javascript?1284139606"></script>
# # <script src="/elsewhere/cools.js?1423139606"></script>
#
# javascript_include_tag "http://www.example.com/xmlhr"
@@ -73,22 +72,22 @@ module ActionView
# apply to all media types.
#
# stylesheet_link_tag "style"
- # # => <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
+ # # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "style.css"
- # # => <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
+ # # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "http://www.example.com/style.css"
# # => <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "style", media: "all"
- # # => <link href="/stylesheets/style.css" media="all" rel="stylesheet" />
+ # # => <link href="/assets/style.css" media="all" rel="stylesheet" />
#
# stylesheet_link_tag "style", media: "print"
- # # => <link href="/stylesheets/style.css" media="print" rel="stylesheet" />
+ # # => <link href="/assets/style.css" media="print" rel="stylesheet" />
#
# stylesheet_link_tag "random.styles", "/css/stylish"
- # # => <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" />
+ # # => <link href="/assets/random.styles" media="screen" rel="stylesheet" />
# # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
@@ -159,17 +158,17 @@ module ActionView
# respectively:
#
# favicon_link_tag
- # # => <link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
+ # # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon" />
#
# favicon_link_tag 'myicon.ico'
- # # => <link href="/images/myicon.ico" rel="shortcut icon" type="image/x-icon" />
+ # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
#
# Mobile Safari looks for a different link tag, pointing to an image that
# will be used if you add the page to the home screen of an iOS device.
# The following call would generate such a tag:
#
# favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
- # # => <link href="/images/mb-icon.png" rel="apple-touch-icon" type="image/png" />
+ # # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" />
def favicon_link_tag(source='favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
@@ -265,7 +264,7 @@ module ActionView
# video_tag("trailer.ogg", controls: true, autobuffer: true)
# # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" ></video>
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
- # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/images/screenshot.png"></video>
+ # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video>
# video_tag("/trailers/hd.avi", size: "16x16")
# # => <video src="/trailers/hd.avi" width="16" height="16"></video>
# video_tag("/trailers/hd.avi", size: "16")
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index d86e7e490c..006b15be91 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -88,9 +88,12 @@ module ActionView
# still sending assets for plain HTTP requests from asset hosts. If you don't
# have SSL certificates for each of the asset hosts this technique allows you
# to avoid warnings in the client about mixed media.
+ # Note that the request parameter might not be supplied, e.g. when the assets
+ # are precompiled via a Rake task. Make sure to use a Proc instead of a lambda,
+ # since a Proc allows missing parameters and sets them to nil.
#
# config.action_controller.asset_host = Proc.new { |source, request|
- # if request.ssl?
+ # if request && request.ssl?
# "#{request.protocol}#{request.host_with_port}"
# else
# "#{request.protocol}assets.example.com"
diff --git a/actionview/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb
index c29c1b1eea..16cddec339 100644
--- a/actionview/lib/action_view/helpers/debug_helper.rb
+++ b/actionview/lib/action_view/helpers/debug_helper.rb
@@ -11,20 +11,16 @@ module ActionView
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
# Useful for inspecting an object at the time of rendering.
#
- # @user = User.new({ username: 'testing', password: 'xyz', age: 42}) %>
+ # @user = User.new({ username: 'testing', password: 'xyz', age: 42})
# debug(@user)
# # =>
# <pre class='debug_dump'>--- !ruby/object:User
# attributes:
# &nbsp; updated_at:
# &nbsp; username: testing
- #
# &nbsp; age: 42
# &nbsp; password: xyz
# &nbsp; created_at:
- # attributes_cache: {}
- #
- # new_record: true
# </pre>
def debug(object)
Marshal::dump(object)
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 180c4a62bf..789a413c8d 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -434,7 +434,8 @@ module ActionView
output = capture(builder, &block)
html_options[:multipart] ||= builder.multipart?
- form_tag(options[:url] || {}, html_options) { output }
+ html_options = html_options_for_form(options[:url] || {}, html_options)
+ form_tag_with_body(html_options, output)
end
def apply_form_for_options!(record, object, options) #:nodoc:
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 1e818083cc..f12d436f8e 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -67,7 +67,7 @@ module ActionView
def form_tag(url_for_options = {}, options = {}, &block)
html_options = html_options_for_form(url_for_options, options)
if block_given?
- form_tag_in_block(html_options, &block)
+ form_tag_with_body(html_options, capture(&block))
else
form_tag_html(html_options)
end
@@ -848,8 +848,7 @@ module ActionView
tag(:form, html_options, true) + extra_tags
end
- def form_tag_in_block(html_options, &block)
- content = capture(&block)
+ def form_tag_with_body(html_options, content)
output = form_tag_html(html_options)
output << content
output.safe_concat("</form>")
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index a9f3b0ffbc..9b9ca7d60d 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -139,7 +139,7 @@ module ActionView
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
- content = ERB::Util.h(content) if escape
+ content = ERB::Util.unwrapped_html_escape(content) if escape
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe
end
@@ -174,7 +174,7 @@ module ActionView
def tag_option(key, value, escape)
value = value.join(" ") if value.is_a?(Array)
- value = ERB::Util.h(value) if escape
+ value = ERB::Util.unwrapped_html_escape(value) if escape
%(#{key}="#{value}")
end
end
diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb
index adb888319d..0495224d04 100644
--- a/actionview/test/template/number_helper_test.rb
+++ b/actionview/test/template/number_helper_test.rb
@@ -48,6 +48,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "-111.235", number_with_precision(-111.2346)
assert_equal "111.00", number_with_precision(111, precision: 2)
assert_equal "0.00100", number_with_precision(0.001, precision: 5)
+ assert_equal "3.33", number_with_precision(Rational(10, 3), precision: 2)
end
def test_number_to_human_size
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index c6500f47e2..f6beff14e1 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -262,6 +262,11 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index c3c61cf50e..4f0b1a76df 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,44 @@
+* Fix regression on eager loading association based on SQL query rather than
+ existing column.
+
+ Fixes #15480.
+
+ *Lauro Caetano*, *Carlos Antonio da Silva*
+
+* Return a null column from `column_for_attribute` when no column exists.
+
+ *Sean Griffin*
+
+* Implemented ActiveRecord::Base#pretty_print to work with PP.
+
+ *Ethan*
+
+* Preserve type when dumping PostgreSQL point, bit, bit varying and money
+ columns.
+
+ *Yves Senn*
+
+* New records remain new after YAML serialization.
+
+ *Sean Griffin*
+
+* PostgreSQL support default values for enum types. Fixes #7814.
+
+ *Yves Senn*
+
+* PostgreSQL `default_sequence_name` respects schema. Fixes #7516.
+
+ *Yves Senn*
+
+* Fixed `columns_for_distinct` of postgresql adapter to work correctly
+ with orders without sort direction modifiers.
+
+ *Nikolay Kondratyev*
+
+* PostgreSQL `reset_pk_sequence!` respects schemas. Fixes #14719.
+
+ *Yves Senn*
+
* Keep PostgreSQL `hstore` and `json` attributes as `Hash` in `@attributes`.
Fixes duplication in combination with `store_accessor`.
@@ -551,12 +592,11 @@
*arthurnn*
-* Passing an Active Record object to `find` is now deprecated. Call `.id`
- on the object first.
-
* Passing an Active Record object to `find` or `exists?` is now deprecated.
Call `.id` on the object first.
+ *Aaron Patterson*
+
* Only use BINARY for MySQL case sensitive uniqueness check when column has a case insensitive collation.
*Ryuta Kamizono*
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index c813b22f3b..f4777919d3 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -19,9 +19,9 @@ A short rundown of some of the major features:
class Product < ActiveRecord::Base
end
-
+
{Learn more}[link:classes/ActiveRecord/Base.html]
-
+
The Product class is automatically mapped to the table named "products",
which might look like this:
@@ -33,7 +33,7 @@ which might look like this:
This would also define the following accessors: `Product#name` and
`Product#name=(new_name)`.
-
+
* Associations between objects defined by simple class methods.
@@ -208,6 +208,11 @@ API documentation is at:
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 572f556999..31108cc1aa 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -106,7 +106,7 @@ module ActiveRecord
table, foreign_table = tables.shift, tables.first
if reflection.source_macro == :belongs_to
- if reflection.options[:polymorphic]
+ if reflection.polymorphic?
key = reflection.association_primary_key(assoc_klass)
else
key = reflection.association_primary_key
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 35ad512537..954128064d 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -116,7 +116,7 @@ module ActiveRecord
end
def target_reflection_has_associated_record?
- !(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?)
+ !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
end
def update_through_counter?(method)
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 01173b68f3..35659766d3 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -217,7 +217,7 @@ module ActiveRecord
reflection.check_validity!
reflection.check_eager_loadable!
- if reflection.options[:polymorphic]
+ if reflection.polymorphic?
raise EagerLoadPolymorphicError.new(reflection)
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 63773bd5e1..33c8619359 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -104,11 +104,11 @@ module ActiveRecord
end
def association_key_type
- @klass.column_types[association_key_name.to_s].type
+ @klass.column_for_attribute(association_key_name).type
end
def owner_key_type
- @model.column_types[owner_key_name.to_s].type
+ @model.column_for_attribute(owner_key_name).type
end
def load_slices(slices)
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index f8a85b8a6f..fcf3b219d4 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -63,14 +63,13 @@ module ActiveRecord
# Note: this does not capture all cases, for example it would be crazy to try to
# properly support stale-checking for nested associations.
def stale_state
- if through_reflection.macro == :belongs_to
+ if through_reflection.belongs_to?
owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
end
end
def foreign_key_present?
- through_reflection.macro == :belongs_to &&
- !owner[through_reflection.foreign_key].nil?
+ through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
end
def ensure_mutable
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 816fb51942..c4cf084a04 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -13,9 +13,9 @@ module ActiveRecord
# exception is raised.
#
# cat = Cat.new(name: "Gorby", status: "yawning")
- # cat.attributes # => { "name" => "Gorby", "status" => "yawning" }
+ # cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
# cat.assign_attributes(status: "sleeping")
- # cat.attributes # => { "name" => "Gorby", "status" => "sleeping" }
+ # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
#
# New attributes will be persisted in the database when the object is saved.
#
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index a0a0214eae..e626227e7e 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -18,6 +18,8 @@ module ActiveRecord
include TimeZoneConversion
include Dirty
include Serialization
+
+ delegate :column_for_attribute, to: :class
end
AttrNames = Module.new {
@@ -192,6 +194,26 @@ module ActiveRecord
[]
end
end
+
+ # Returns the column object for the named attribute.
+ # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the
+ # named attribute does not exist.
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # person = Person.new
+ # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
+ # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
+ #
+ # person.column_for_attribute(:nothing)
+ # # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
+ def column_for_attribute(name)
+ name = name.to_s
+ columns_hash.fetch(name) do
+ ConnectionAdapters::NullColumn.new(name)
+ end
+ end
end
# If we haven't generated any methods yet, generate them, then
@@ -239,9 +261,9 @@ module ActiveRecord
# If the result is true then check for the select case.
# For queries selecting a subset of columns, return false for unselected columns.
- # We check defined?(@attributes) not to issue warnings if called on objects that
+ # We check defined?(@raw_attributes) not to issue warnings if called on objects that
# have been allocated but not yet initialized.
- if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
+ if defined?(@raw_attributes) && @raw_attributes.any? && self.class.column_names.include?(name)
return has_attribute?(name)
end
@@ -258,7 +280,7 @@ module ActiveRecord
# person.has_attribute?('age') # => true
# person.has_attribute?(:nothing) # => false
def has_attribute?(attr_name)
- @attributes.has_key?(attr_name.to_s)
+ @raw_attributes.has_key?(attr_name.to_s)
end
# Returns an array of names for the attributes available on this object.
@@ -270,7 +292,7 @@ module ActiveRecord
# person.attribute_names
# # => ["id", "created_at", "updated_at", "name", "age"]
def attribute_names
- @attributes.keys
+ @raw_attributes.keys
end
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
@@ -287,11 +309,6 @@ module ActiveRecord
}
end
- # Placeholder so it can be overriden when needed by serialization
- def attributes_for_coder # :nodoc:
- attributes
- end
-
# Returns an <tt>#inspect</tt>-like string for the value of the
# attribute +attr_name+. String attributes are truncated upto 50
# characters, Date and Time attributes are returned in the
@@ -344,23 +361,6 @@ module ActiveRecord
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
end
- # Returns the column object for the named attribute. Returns +nil+ if the
- # named attribute not exists.
- #
- # class Person < ActiveRecord::Base
- # end
- #
- # person = Person.new
- # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
- # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
- #
- # person.column_for_attribute(:nothing)
- # # => nil
- def column_for_attribute(name)
- # FIXME: should this return a null object for columns that don't exist?
- self.class.columns_hash[name.to_s]
- end
-
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
# "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
# <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
@@ -424,7 +424,7 @@ module ActiveRecord
def attribute_method?(attr_name) # :nodoc:
# We check defined? because Syck calls respond_to? before actually calling initialize.
- defined?(@attributes) && @attributes.include?(attr_name)
+ defined?(@raw_attributes) && @raw_attributes.include?(attr_name)
end
private
@@ -443,16 +443,16 @@ module ActiveRecord
# Filters the primary keys and readonly attributes from the attribute names.
def attributes_for_update(attribute_names)
- attribute_names.select do |name|
- column_for_attribute(name) && !readonly_attribute?(name)
+ attribute_names.reject do |name|
+ readonly_attribute?(name)
end
end
# Filters out the primary keys, from the attribute names, when the primary
# key is to be generated (e.g. the id attribute has no value).
def attributes_for_create(attribute_names)
- attribute_names.select do |name|
- column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
+ attribute_names.reject do |name|
+ pk_attribute?(name) && id.nil?
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index f596a8b02e..4365f5a1a1 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -43,7 +43,7 @@ module ActiveRecord
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
def read_attribute_before_type_cast(attr_name)
- @attributes[attr_name.to_s]
+ @raw_attributes[attr_name.to_s]
end
# Returns a hash of attributes before typecasting and deserialization.
@@ -57,7 +57,7 @@ module ActiveRecord
# task.attributes_before_type_cast
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
def attributes_before_type_cast
- @attributes
+ @raw_attributes
end
private
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 99070f127b..4e32b78e34 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -55,7 +55,7 @@ module ActiveRecord
# optimistic locking) won't get written unless they get marked as changed
self.class.columns.each do |c|
attr, orig_value = c.name, c.default
- changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
+ changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @raw_attributes[attr])
end
end
@@ -94,33 +94,7 @@ module ActiveRecord
end
def _field_changed?(attr, old, value)
- if column = column_for_attribute(attr)
- if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
- changes_from_zero_to_string?(old, value))
- value = nil
- else
- value = column.type_cast(value)
- end
- end
-
- old != value
- end
-
- def changes_from_nil_to_empty_string?(column, old, value)
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
- # Hence we don't record it as a change if the value changes from nil to ''.
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
- # be typecast back to 0 (''.to_i => 0)
- column.null && (old.nil? || old == 0) && value.blank?
- end
-
- def changes_from_zero_to_string?(old, value)
- # For columns with old 0 and value non-empty string
- old == 0 && value.is_a?(String) && value.present? && non_zero?(value)
- end
-
- def non_zero?(value)
- value !~ /\A0+(\.0+)?\z/
+ column_for_attribute(attr).changed?(old, value)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 979dfb207e..ae3785638a 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -22,7 +22,7 @@ module ActiveRecord
# the attribute name. Using a constant means that we do not have
# to allocate an object on each call to the attribute method.
# Making it frozen means that it doesn't get duped when used to
- # key the @attributes_cache in read_attribute.
+ # key the @attributes in read_attribute.
def method_body(method_name, const_name)
<<-EOMETHOD
def #{method_name}
@@ -94,7 +94,7 @@ module ActiveRecord
def cacheable_column?(column)
if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
- ! serialized_attributes.include? column.name
+ true
else
attribute_types_cached_by_default.include?(column.type)
end
@@ -108,22 +108,22 @@ module ActiveRecord
# If it's cached, just return it
# We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
name = attr_name.to_s
- @attributes_cache[name] || @attributes_cache.fetch(name) {
+ @attributes[name] || @attributes.fetch(name) {
column = @column_types_override[name] if @column_types_override
column ||= @column_types[name]
- return @attributes.fetch(name) {
+ return @raw_attributes.fetch(name) {
if name == 'id' && self.class.primary_key != name
read_attribute(self.class.primary_key)
end
} unless column
- value = @attributes.fetch(name) {
+ value = @raw_attributes.fetch(name) {
return block_given? ? yield(name) : nil
}
if self.class.cache_attribute?(name)
- @attributes_cache[name] = column.type_cast(value)
+ @attributes[name] = column.type_cast(value)
else
column.type_cast value
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 65d910fd46..148fc9eae5 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -76,21 +76,6 @@ module ActiveRecord
module Behavior # :nodoc:
extend ActiveSupport::Concern
- module ClassMethods # :nodoc:
- def initialize_attributes(attributes, options = {})
- serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
- super(attributes, options)
-
- serialized_attributes.each do |key, coder|
- if attributes.key?(key)
- attributes[key] = Type::Serialized::Attribute.new(coder, attributes[key], serialized)
- end
- end
-
- attributes
- end
- end
-
def should_record_timestamps?
super || (self.record_timestamps && (attributes.keys & self.class.serialized_attributes.keys).present?)
end
@@ -98,50 +83,6 @@ module ActiveRecord
def keys_for_partial_write
super | (attributes.keys & self.class.serialized_attributes.keys)
end
-
- def _field_changed?(attr, old, value)
- if self.class.serialized_attributes.include?(attr)
- old != value
- else
- super
- end
- end
-
- def read_attribute_before_type_cast(attr_name)
- if self.class.serialized_attributes.include?(attr_name)
- super.unserialized_value
- else
- super
- end
- end
-
- def attributes_before_type_cast
- super.dup.tap do |attributes|
- self.class.serialized_attributes.each_key do |key|
- if attributes.key?(key)
- attributes[key] = attributes[key].unserialized_value
- end
- end
- end
- end
-
- def typecasted_attribute_value(name)
- if self.class.serialized_attributes.include?(name)
- @attributes[name].serialized_value
- else
- super
- end
- end
-
- def attributes_for_coder
- attribute_names.each_with_object({}) do |name, attrs|
- attrs[name] = if self.class.serialized_attributes.include?(name)
- @attributes[name].serialized_value
- else
- read_attribute(name)
- end
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 6149ac4906..18778698e8 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -36,7 +36,7 @@ module ActiveRecord
previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
write_attribute(:#{attr_name}, time)
#{attr_name}_will_change! if previous_time != time_with_zone
- @attributes_cache["#{attr_name}"] = time_with_zone
+ @attributes["#{attr_name}"] = time_with_zone
end
EOV
generated_attribute_methods.module_eval(method_body, __FILE__, line)
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 629bd3bc63..5203b30462 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -53,11 +53,11 @@ module ActiveRecord
# specified +value+. Empty strings for fixnum and float columns are
# turned into +nil+.
def write_attribute(attr_name, value)
- write_attribute_with_type_cast(attr_name, value, :type_cast_for_write)
+ write_attribute_with_type_cast(attr_name, value, true)
end
def raw_write_attribute(attr_name, value)
- write_attribute_with_type_cast(attr_name, value, :raw_type_cast_for_write)
+ write_attribute_with_type_cast(attr_name, value, false)
end
private
@@ -66,24 +66,26 @@ module ActiveRecord
write_attribute(attribute_name, value)
end
- def write_attribute_with_type_cast(attr_name, value, type_cast_method)
+ def write_attribute_with_type_cast(attr_name, value, should_type_cast)
attr_name = attr_name.to_s
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
- @attributes_cache.delete(attr_name)
+ @attributes.delete(attr_name)
column = column_for_attribute(attr_name)
+ unless has_attribute?(attr_name) || self.class.columns_hash.key?(attr_name)
+ raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
+ end
+
# If we're dealing with a binary column, write the data to the cache
# so we don't attempt to typecast multiple times.
- if column && column.binary?
- @attributes_cache[attr_name] = value
+ if column.binary?
+ @attributes[attr_name] = value
end
- if column
- @attributes[attr_name] = column.public_send(type_cast_method, value)
- elsif @attributes.has_key?(attr_name)
- @attributes[attr_name] = value
+ if should_type_cast
+ @raw_attributes[attr_name] = column.type_cast_for_write(value)
else
- raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
+ @raw_attributes[attr_name] = value
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index f836e60988..04ae67234f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -18,21 +18,7 @@ module ActiveRecord
value = column.type_cast_for_database(value)
end
- case value
- when String, ActiveSupport::Multibyte::Chars
- "'#{quote_string(value.to_s)}'"
- when true then quoted_true
- when false then quoted_false
- when nil then "NULL"
- # BigDecimals need to be put in a non-normalized form and quoted.
- when BigDecimal then value.to_s('F')
- when Numeric, ActiveSupport::Duration then value.to_s
- when Date, Time then "'#{quoted_date(value)}'"
- when Symbol then "'#{quote_string(value.to_s)}'"
- when Class then "'#{value.to_s}'"
- else
- "'#{quote_string(YAML.dump(value))}'"
- end
+ _quote(value)
end
# Cast a +value+ to a type that the database understands. For example,
@@ -52,20 +38,10 @@ module ActiveRecord
value = column.type_cast_for_database(value)
end
- case value
- when Symbol, ActiveSupport::Multibyte::Chars
- value.to_s
- when true then unquoted_true
- when false then unquoted_false
- # BigDecimals need to be put in a non-normalized form and quoted.
- when BigDecimal then value.to_s('F')
- when Date, Time then quoted_date(value)
- when *types_which_need_no_typecasting
- value
- else
- to_type = column ? " to #{column.type}" : ""
- raise TypeError, "can't cast #{value.class}#{to_type}"
- end
+ _type_cast(value)
+ rescue TypeError
+ to_type = column ? " to #{column.type}" : ""
+ raise TypeError, "can't cast #{value.class}#{to_type}"
end
# Quotes a string, escaping any ' (single quote) and \ (backslash)
@@ -129,6 +105,39 @@ module ActiveRecord
def types_which_need_no_typecasting
[nil, Numeric, String]
end
+
+ def _quote(value)
+ case value
+ when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data
+ "'#{quote_string(value.to_s)}'"
+ when true then quoted_true
+ when false then quoted_false
+ when nil then "NULL"
+ # BigDecimals need to be put in a non-normalized form and quoted.
+ when BigDecimal then value.to_s('F')
+ when Numeric, ActiveSupport::Duration then value.to_s
+ when Date, Time then "'#{quoted_date(value)}'"
+ when Symbol then "'#{quote_string(value.to_s)}'"
+ when Class then "'#{value.to_s}'"
+ else
+ "'#{quote_string(YAML.dump(value))}'"
+ end
+ end
+
+ def _type_cast(value)
+ case value
+ when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data
+ value.to_s
+ when true then unquoted_true
+ when false then unquoted_false
+ # BigDecimals need to be put in a non-normalized form and quoted.
+ when BigDecimal then value.to_s('F')
+ when Date, Time then quoted_date(value)
+ when *types_which_need_no_typecasting
+ value
+ else raise TypeError
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 117c0f0969..a9b3e9cfb9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -102,8 +102,8 @@ module ActiveRecord
# * <tt>:index</tt> -
# Create an index for the column. Can be either <tt>true</tt> or an options hash.
#
- # For clarity's sake: the precision is the number of significant digits,
- # while the scale is the number of digits that can be stored following
+ # Note: The precision is the total number of significant digits
+ # and the scale is the number of digits that can be stored following
# the decimal point. For example, the number 123.45 has a precision of 5
# and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
# range from -999.99 to 999.99.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index ac14740cfe..d3e172927d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -25,7 +25,7 @@ module ActiveRecord
spec[:precision] = column.precision.inspect if column.precision
spec[:scale] = column.scale.inspect if column.scale
spec[:null] = 'false' unless column.null
- spec[:default] = default_string(column.default) if column.has_default?
+ spec[:default] = column.type_cast_for_schema(column.default) if column.has_default?
spec
end
@@ -33,31 +33,6 @@ module ActiveRecord
def migration_keys
[:name, :limit, :precision, :scale, :default, :null]
end
-
- private
-
- def default_string(value)
- case value
- when BigDecimal
- value.to_s
- when Date, DateTime, Time
- "'#{value.to_s(:db)}'"
- when Range
- # infinity dumps as Infinity, which causes uninitialized constant error
- value.inspect.gsub('Infinity', '::Float::INFINITY')
- when IPAddr
- subnet_mask = value.instance_variable_get(:@mask_addr)
-
- # If the subnet mask is equal to /32, don't output it
- if subnet_mask == (2**32 - 1)
- "\"#{value.to_s}\""
- else
- "\"#{value.to_s}/#{subnet_mask.to_s(2).count('1')}\""
- end
- else
- value.inspect
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index b589cfee09..cc494a7f40 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -14,7 +14,10 @@ module ActiveRecord
module ConnectionAdapters # :nodoc:
extend ActiveSupport::Autoload
- autoload :Column
+ autoload_at 'active_record/connection_adapters/column' do
+ autoload :Column
+ autoload :NullColumn
+ end
autoload :ConnectionSpecification
autoload_at 'active_record/connection_adapters/abstract/schema_definitions' do
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 2677b6ee83..759ac9943f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -218,10 +218,9 @@ module ActiveRecord
# QUOTING ==================================================
- def quote(value, column = nil)
- if value.kind_of?(String) && column && column.type == :binary
- s = value.unpack("H*")[0]
- "x'#{s}'"
+ def _quote(value) # :nodoc:
+ if value.is_a?(Type::Binary::Data)
+ "x'#{value.hex}'"
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index f66e99c9d1..5e4e00bc64 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -16,8 +16,9 @@ module ActiveRecord
attr_reader :name, :default, :cast_type, :null, :sql_type, :default_function
delegate :type, :precision, :scale, :limit, :klass, :accessor,
- :text?, :number?, :binary?, :serialized?,
- :type_cast, :type_cast_for_write, :raw_type_cast_for_write, :type_cast_for_database,
+ :text?, :number?, :binary?, :serialized?, :changed?,
+ :type_cast, :type_cast_for_write, :type_cast_for_database,
+ :type_cast_for_schema,
to: :cast_type
# Instantiates a new column in the table.
@@ -54,6 +55,12 @@ module ActiveRecord
type_cast(default)
end
end
+
+ class NullColumn < Column
+ def initialize(name)
+ super name, nil, Type::Value.new
+ end
+ end
end
# :startdoc:
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index f7bad20f00..971f5eed7e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -6,15 +6,6 @@ module ActiveRecord
"(#{point[0]},#{point[1]})"
end
- def string_to_bit(value) # :nodoc:
- case value
- when /^0x/i
- value[2..-1].hex.to_s(2) # Hexadecimal notation
- else
- value # Bit-string notation
- end
- end
-
def hstore_to_string(object, array_member = false) # :nodoc:
if Hash === object
string = object.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(',')
@@ -76,28 +67,6 @@ module ActiveRecord
end
end
- def string_to_cidr(string) # :nodoc:
- if string.nil?
- nil
- elsif String === string
- begin
- IPAddr.new(string)
- rescue ArgumentError
- nil
- end
- else
- string
- end
- end
-
- def cidr_to_string(object) # :nodoc:
- if IPAddr === object
- "#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
- else
- object
- end
- end
-
def string_to_array(string, oid) # :nodoc:
parse_pg_array(string).map {|val| type_cast_array(oid, val)}
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 2494e19f84..33a98b4fcb 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -2,6 +2,7 @@ require 'active_record/connection_adapters/postgresql/oid/infinity'
require 'active_record/connection_adapters/postgresql/oid/array'
require 'active_record/connection_adapters/postgresql/oid/bit'
+require 'active_record/connection_adapters/postgresql/oid/bit_varying'
require 'active_record/connection_adapters/postgresql/oid/bytea'
require 'active_record/connection_adapters/postgresql/oid/cidr'
require 'active_record/connection_adapters/postgresql/oid/date'
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
index 9b2d887d07..3073f8ff30 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
@@ -2,10 +2,19 @@ module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
- class Bit < Type::String
+ class Bit < Type::Value
+ def type
+ :bit
+ end
+
def type_cast(value)
if ::String === value
- ConnectionAdapters::PostgreSQLColumn.string_to_bit value
+ case value
+ when /^0x/i
+ value[2..-1].hex.to_s(2) # Hexadecimal notation
+ else
+ value # Bit-string notation
+ end
else
value
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb
new file mode 100644
index 0000000000..054af285bb
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb
@@ -0,0 +1,13 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class BitVarying < OID::Bit
+ def type
+ :bit_varying
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
index 507c3a62b0..534961a414 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
@@ -7,8 +7,37 @@ module ActiveRecord
:cidr
end
+ def type_cast_for_schema(value)
+ subnet_mask = value.instance_variable_get(:@mask_addr)
+
+ # If the subnet mask is equal to /32, don't output it
+ if subnet_mask == (2**32 - 1)
+ "\"#{value.to_s}\""
+ else
+ "\"#{value.to_s}/#{subnet_mask.to_s(2).count('1')}\""
+ end
+ end
+
+ def type_cast_for_database(value)
+ if IPAddr === value
+ "#{value.to_s}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
+ else
+ value
+ end
+ end
+
def cast_value(value)
- ConnectionAdapters::PostgreSQLColumn.string_to_cidr value
+ if value.nil?
+ nil
+ elsif String === value
+ begin
+ IPAddr.new(value)
+ rescue ArgumentError
+ nil
+ end
+ else
+ value
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
index 697dceb7c2..d25eb256c2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
@@ -7,6 +7,10 @@ module ActiveRecord
class_attribute :precision
+ def type
+ :money
+ end
+
def scale
2
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
index f9531ddee3..9007bfb178 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
@@ -2,7 +2,11 @@ module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
- class Point < Type::String
+ class Point < Type::Value
+ def type
+ :point
+ end
+
def type_cast(value)
if ::String === value
if value[0] == '(' && value[-1] == ')'
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
index c2262c1599..a0d8a94c74 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -24,6 +24,10 @@ module ActiveRecord
value.respond_to?(:infinite?) && value.infinite?
end
+ def type_cast_for_schema(value)
+ value.inspect.gsub('Infinity', '::Float::INFINITY')
+ end
+
def type_cast_single(value)
infinity?(value) ? value : @subtype.type_cast(value)
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index ad12298013..4c719b834f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -44,11 +44,6 @@ module ActiveRecord
when 'json' then super(PostgreSQLColumn.json_to_string(value), column)
else super
end
- when IPAddr
- case sql_type
- when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column)
- else super
- end
when Float
if value.infinite? && column.type == :datetime
"'#{value.to_s.downcase}'"
@@ -66,7 +61,6 @@ module ActiveRecord
end
when String
case sql_type
- when 'bytea' then "'#{escape_bytea(value)}'"
when 'xml' then "xml '#{quote_string(value)}'"
when /^bit/
case value
@@ -110,27 +104,12 @@ module ActiveRecord
super(value, column)
end
end
- when String
- if 'bytea' == column.sql_type
- # Return a bind param hash with format as binary.
- # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
- # for more information
- { value: value, format: 1 }
- else
- super(value, column)
- end
when Hash
case column.sql_type
when 'hstore' then PostgreSQLColumn.hstore_to_string(value, array_member)
when 'json' then PostgreSQLColumn.json_to_string(value)
else super(value, column)
end
- when IPAddr
- if %w(inet cidr).include? column.sql_type
- PostgreSQLColumn.cidr_to_string(value)
- else
- super(value, column)
- end
else
super(value, column)
end
@@ -150,12 +129,7 @@ module ActiveRecord
# - "schema.name".table_name
# - "schema.name"."table.name"
def quote_table_name(name)
- schema, table = Utils.extract_schema_and_table(name.to_s)
- if schema
- "#{quote_column_name(schema)}.#{quote_column_name(table)}"
- else
- quote_column_name(table)
- end
+ Utils.extract_schema_qualified_name(name.to_s).quoted
end
def quote_table_name_for_assignment(table, attr)
@@ -189,6 +163,27 @@ module ActiveRecord
quote(value, column)
end
end
+
+ private
+
+ def _quote(value)
+ if value.is_a?(Type::Binary::Data)
+ "'#{escape_bytea(value.to_s)}'"
+ else
+ super
+ end
+ end
+
+ def _type_cast(value)
+ if value.is_a?(Type::Binary::Data)
+ # Return a bind param hash with format as binary.
+ # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
+ # for more information
+ { value: value.to_s, format: 1 }
+ else
+ super
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index bcfd605165..0867e5ef54 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -4,68 +4,84 @@ module ActiveRecord
module ColumnMethods
def xml(*args)
options = args.extract_options!
- column(args[0], 'xml', options)
+ column(args[0], :xml, options)
end
def tsvector(*args)
options = args.extract_options!
- column(args[0], 'tsvector', options)
+ column(args[0], :tsvector, options)
end
def int4range(name, options = {})
- column(name, 'int4range', options)
+ column(name, :int4range, options)
end
def int8range(name, options = {})
- column(name, 'int8range', options)
+ column(name, :int8range, options)
end
def tsrange(name, options = {})
- column(name, 'tsrange', options)
+ column(name, :tsrange, options)
end
def tstzrange(name, options = {})
- column(name, 'tstzrange', options)
+ column(name, :tstzrange, options)
end
def numrange(name, options = {})
- column(name, 'numrange', options)
+ column(name, :numrange, options)
end
def daterange(name, options = {})
- column(name, 'daterange', options)
+ column(name, :daterange, options)
end
def hstore(name, options = {})
- column(name, 'hstore', options)
+ column(name, :hstore, options)
end
def ltree(name, options = {})
- column(name, 'ltree', options)
+ column(name, :ltree, options)
end
def inet(name, options = {})
- column(name, 'inet', options)
+ column(name, :inet, options)
end
def cidr(name, options = {})
- column(name, 'cidr', options)
+ column(name, :cidr, options)
end
def macaddr(name, options = {})
- column(name, 'macaddr', options)
+ column(name, :macaddr, options)
end
def uuid(name, options = {})
- column(name, 'uuid', options)
+ column(name, :uuid, options)
end
def json(name, options = {})
- column(name, 'json', options)
+ column(name, :json, options)
end
def citext(name, options = {})
- column(name, 'citext', options)
+ column(name, :citext, options)
+ end
+
+ def point(name, options = {})
+ column(name, :point, options)
+ end
+
+ def bit(name, options)
+ column(name, :bit, options)
+ end
+
+ def bit_varying(name, options)
+ column(name, :bit_varying, options)
+ end
+
+ def money(name, options)
+ column(name, :money, options)
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 b4d53eba26..b2aeb3a058 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -97,16 +97,16 @@ module ActiveRecord
# 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)
def table_exists?(name)
- schema, table = Utils.extract_schema_and_table(name.to_s)
- return false unless table
+ name = Utils.extract_schema_qualified_name(name.to_s)
+ return false unless name.identifier
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
SELECT COUNT(*)
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 c.relname = '#{table.gsub(/(^"|"$)/,'')}'
- AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
+ AND c.relname = '#{name.identifier}'
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
SQL
end
@@ -179,7 +179,7 @@ module ActiveRecord
# Limit, precision, and scale are all handled by the superclass.
column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
- default_value = extract_value_from_default(default)
+ default_value = extract_value_from_default(oid, default)
default_function = extract_default_function(default_value, default)
new_column(column_name, default_value, oid, type, notnull == 'f', default_function)
end
@@ -273,9 +273,9 @@ module ActiveRecord
def default_sequence_name(table_name, pk = nil) #:nodoc:
result = serial_sequence(table_name, pk || 'id')
return nil unless result
- result.split('.').last
+ Utils.extract_schema_qualified_name(result)
rescue ActiveRecord::StatementInvalid
- "#{table_name}_#{pk || 'id'}_seq"
+ PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq")
end
def serial_sequence(table, column)
@@ -312,17 +312,19 @@ module ActiveRecord
# First try looking for a sequence with a dependency on the
# given table's primary key.
result = query(<<-end_sql, 'SCHEMA')[0]
- SELECT attr.attname, seq.relname
+ SELECT attr.attname, nsp.nspname, seq.relname
FROM pg_class seq,
pg_attribute attr,
pg_depend dep,
- pg_constraint cons
+ pg_constraint cons,
+ pg_namespace nsp
WHERE seq.oid = dep.objid
AND seq.relkind = 'S'
AND attr.attrelid = dep.refobjid
AND attr.attnum = dep.refobjsubid
AND attr.attrelid = cons.conrelid
AND attr.attnum = cons.conkey[1]
+ AND seq.relnamespace = nsp.oid
AND cons.contype = 'p'
AND dep.classid = 'pg_class'::regclass
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
@@ -330,7 +332,7 @@ module ActiveRecord
if result.nil? or result.empty?
result = query(<<-end_sql, 'SCHEMA')[0]
- SELECT attr.attname,
+ SELECT attr.attname, nsp.nspname,
CASE
WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
@@ -342,13 +344,19 @@ module ActiveRecord
JOIN pg_attribute attr ON (t.oid = attrelid)
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
+ JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
WHERE t.oid = '#{quote_table_name(table)}'::regclass
AND cons.contype = 'p'
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
end_sql
end
- [result.first, result.last]
+ pk = result.shift
+ if result.last
+ [pk, PostgreSQL::Name.new(*result)]
+ else
+ [pk, nil]
+ end
rescue
nil
end
@@ -376,7 +384,7 @@ module ActiveRecord
clear_cache!
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
pk, seq = pk_and_sequence_for(new_name)
- if seq == "#{table_name}_#{pk}_seq"
+ if seq.identifier == "#{table_name}_#{pk}_seq"
new_seq = "#{new_name}_#{pk}_seq"
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
end
@@ -489,7 +497,7 @@ module ActiveRecord
# Convert Arel node to string
s = s.to_sql unless s.is_a?(String)
# Remove any ASC/DESC modifiers
- s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
+ s.gsub(/\s+(?:ASC|DESC)?\s*(?:NULLS\s+(?:FIRST|LAST)\s*)?/i, '')
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
[super, *order_columns].join(', ')
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
index 60ffd3a114..0290bcb48c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
@@ -1,13 +1,54 @@
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
+ # Value Object to hold a schema qualified name.
+ # This is usually the name of a PostgreSQL relation but it can also represent
+ # schema qualified type names. +schema+ and +identifier+ are unquoted to prevent
+ # double quoting.
+ class Name # :nodoc:
+ SEPARATOR = "."
+ attr_reader :schema, :identifier
+
+ def initialize(schema, identifier)
+ @schema, @identifier = unquote(schema), unquote(identifier)
+ end
+
+ def to_s
+ parts.join SEPARATOR
+ end
+
+ def quoted
+ parts.map { |p| PGconn.quote_ident(p) }.join SEPARATOR
+ end
+
+ def ==(o)
+ o.class == self.class && o.parts == parts
+ end
+ alias_method :eql?, :==
+
+ def hash
+ parts.hash
+ end
+
+ protected
+ def unquote(part)
+ return unless part
+ part.gsub(/(^"|"$)/,'')
+ end
+
+ def parts
+ @parts ||= [@schema, @identifier].compact
+ end
+ end
+
module Utils # :nodoc:
extend self
- # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
- # +schema_name+ is nil if not specified in +name+.
- # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
- # +name+ supports the range of schema/table references understood by PostgreSQL, for example:
+ # Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
+ # extracted from +string+.
+ # +schema+ is nil if not specified in +string+.
+ # +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
+ # +string+ supports the range of schema/table references understood by PostgreSQL, for example:
#
# * <tt>table_name</tt>
# * <tt>"table.name"</tt>
@@ -15,9 +56,9 @@ module ActiveRecord
# * <tt>schema_name."table.name"</tt>
# * <tt>"schema_name".table_name</tt>
# * <tt>"schema.name"."table name"</tt>
- def extract_schema_and_table(name)
- table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
- [schema, table]
+ def extract_schema_qualified_name(string)
+ table, schema = string.scan(/[^".\s]+|"[^"]*"/)[0..1].reverse
+ PostgreSQL::Name.new(schema, table)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 027169ae3c..0f20d52879 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -103,7 +103,11 @@ module ActiveRecord
uuid: { name: "uuid" },
json: { name: "json" },
ltree: { name: "ltree" },
- citext: { name: "citext" }
+ citext: { name: "citext" },
+ point: { name: "point" },
+ bit: { name: "bit" },
+ bit_varying: { name: "bit varying" },
+ money: { name: "money" },
}
OID = PostgreSQL::OID #:nodoc:
@@ -432,8 +436,8 @@ module ActiveRecord
m.alias_type 'name', 'varchar'
m.alias_type 'bpchar', 'varchar'
m.register_type 'bool', Type::Boolean.new
- m.register_type 'bit', OID::Bit.new
- m.alias_type 'varbit', 'bit'
+ register_class_with_limit m, 'bit', OID::Bit
+ register_class_with_limit m, 'varbit', OID::BitVarying
m.alias_type 'timestamptz', 'timestamp'
m.register_type 'date', OID::Date.new
m.register_type 'time', OID::Time.new
@@ -498,7 +502,7 @@ module ActiveRecord
end
# Extracts the value from a PostgreSQL column default definition.
- def extract_value_from_default(default) # :nodoc:
+ def extract_value_from_default(oid, default) # :nodoc:
# This is a performance optimization for Ruby 1.9.2 in development.
# If the value is nil, we return nil straight away without checking
# the regular expressions. If we check each regular expression,
@@ -508,48 +512,15 @@ module ActiveRecord
return default unless default
case default
- when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
- $1
+ # Quoted types
+ when /\A[\(B]?'(.*)'::/m
+ $1.gsub(/''/, "'")
+ # Boolean types
+ when 'true', 'false'
+ default
# Numeric types
when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
$1
- # Character types
- when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
- $1.gsub(/''/, "'")
- # Binary data types
- when /\A'(.*)'::bytea\z/m
- $1
- # Date/time types
- when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
- $1
- when /\A'(.*)'::interval\z/
- $1
- # Boolean type
- when 'true'
- true
- when 'false'
- false
- # Geometric types
- when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
- $1
- # Network address types
- when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
- $1
- # Bit string types
- when /\AB'(.*)'::"?bit(?: varying)?"?\z/
- $1
- # XML type
- when /\A'(.*)'::xml\z/m
- $1
- # Arrays
- when /\A'(.*)'::"?\D+"?\[\]\z/
- $1
- # Hstore
- when /\A'(.*)'::hstore\z/
- $1
- # JSON
- when /\A'(.*)'::json\z/
- $1
# Object identifier types
when /\A-?\d+\z/
$1
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index adf893d7e7..e6163771e8 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -219,10 +219,9 @@ module ActiveRecord
# QUOTING ==================================================
- def quote(value, column = nil)
- if value.kind_of?(String) && column && column.type == :binary
- s = value.unpack("H*")[0]
- "x'#{s}'"
+ def _quote(value) # :nodoc:
+ if value.is_a?(Type::Binary::Data)
+ "x'#{value.hex}'"
else
super
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 07eafef788..d6849fef2e 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -249,10 +249,10 @@ module ActiveRecord
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
def initialize(attributes = nil, options = {})
- defaults = self.class.column_defaults.dup
+ defaults = self.class.raw_column_defaults.dup
defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
- @attributes = self.class.initialize_attributes(defaults)
+ @raw_attributes = defaults
@column_types_override = nil
@column_types = self.class.column_types
@@ -278,13 +278,13 @@ module ActiveRecord
# post.init_with('attributes' => { 'title' => 'hello world' })
# post.title # => 'hello world'
def init_with(coder)
- @attributes = self.class.initialize_attributes(coder['attributes'])
+ @raw_attributes = coder['attributes']
@column_types_override = coder['column_types']
@column_types = self.class.column_types
init_internals
- @new_record = false
+ @new_record = coder['new_record']
self.class.define_attribute_methods
@@ -323,16 +323,15 @@ module ActiveRecord
##
def initialize_dup(other) # :nodoc:
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
- self.class.initialize_attributes(cloned_attributes, :serialized => false)
- @attributes = cloned_attributes
- @attributes[self.class.primary_key] = nil
+ @raw_attributes = cloned_attributes
+ @raw_attributes[self.class.primary_key] = nil
run_callbacks(:initialize) unless _initialize_callbacks.empty?
@aggregation_cache = {}
@association_cache = {}
- @attributes_cache = {}
+ @attributes = {}
@new_record = true
@destroyed = false
@@ -353,7 +352,8 @@ module ActiveRecord
# Post.new.encode_with(coder)
# coder # => {"attributes" => {"id" => nil, ... }}
def encode_with(coder)
- coder['attributes'] = attributes_for_coder
+ coder['attributes'] = @raw_attributes
+ coder['new_record'] = new_record?
end
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
@@ -383,13 +383,13 @@ module ActiveRecord
# accessible, even on destroyed records, but cloned models will not be
# frozen.
def freeze
- @attributes = @attributes.clone.freeze
+ @raw_attributes = @raw_attributes.clone.freeze
self
end
# Returns +true+ if the attributes hash has been frozen.
def frozen?
- @attributes.frozen?
+ @raw_attributes.frozen?
end
# Allows sort on objects
@@ -418,9 +418,9 @@ module ActiveRecord
# Returns the contents of the record as a nicely formatted string.
def inspect
- # We check defined?(@attributes) not to issue warnings if the object is
+ # We check defined?(@raw_attributes) not to issue warnings if the object is
# allocated but not initialized.
- inspection = if defined?(@attributes) && @attributes
+ inspection = if defined?(@raw_attributes) && @raw_attributes
self.class.column_names.collect { |name|
if has_attribute?(name)
"#{name}: #{attribute_for_inspect(name)}"
@@ -432,6 +432,29 @@ module ActiveRecord
"#<#{self.class} #{inspection}>"
end
+ # Takes a PP and prettily prints this record to it, allowing you to get a nice result from `pp record`
+ # when pp is required.
+ def pretty_print(pp)
+ pp.object_address_group(self) do
+ if defined?(@attributes) && @attributes
+ column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
+ pp.seplist(column_names, proc { pp.text ',' }) do |column_name|
+ column_value = read_attribute(column_name)
+ pp.breakable ' '
+ pp.group(1) do
+ pp.text column_name
+ pp.text ':'
+ pp.breakable
+ pp.pp column_value
+ end
+ end
+ else
+ pp.breakable ' '
+ pp.text 'not initialized'
+ end
+ end
+ end
+
# Returns a hash of the given methods with their names as keys and returned values as values.
def slice(*methods)
Hash[methods.map! { |method| [method, public_send(method)] }].with_indifferent_access
@@ -496,11 +519,11 @@ module ActiveRecord
def init_internals
pk = self.class.primary_key
- @attributes[pk] = nil unless @attributes.key?(pk)
+ @raw_attributes[pk] = nil unless @raw_attributes.key?(pk)
@aggregation_cache = {}
@association_cache = {}
- @attributes_cache = {}
+ @attributes = {}
@readonly = false
@destroyed = false
@marked_for_destruction = false
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index d40bea5ea7..f3d3cdc9e3 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -656,7 +656,7 @@ module ActiveRecord
fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
- if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
+ if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
# support polymorphic belongs_to as "label (Type)"
row[association.foreign_type] = $1
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 4d63b04d9f..7fb27ef6e9 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -66,7 +66,7 @@ module ActiveRecord
send(lock_col + '=', previous_lock_value + 1)
end
- def _update_record(attribute_names = @attributes.keys) #:nodoc:
+ def _update_record(attribute_names = @raw_attributes.keys) #:nodoc:
return super unless locking_enabled?
return 0 if attribute_names.empty?
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index ad6428d8a8..baf2b5fbf8 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -241,6 +241,14 @@ module ActiveRecord
@column_defaults ||= Hash[columns.map { |c| [c.name, c.default] }]
end
+ # Returns a hash where the keys are the column names and the values
+ # are the default values suitable for use in `@raw_attriubtes`
+ def raw_column_defaults # :nodoc:
+ @raw_column_defauts ||= Hash[column_defaults.map { |name, default|
+ [name, columns_hash[name].type_cast_for_write(default)]
+ }]
+ end
+
# Returns an array of column names as strings.
def column_names
@column_names ||= columns.map { |column| column.name }
@@ -285,6 +293,7 @@ module ActiveRecord
@arel_engine = nil
@column_defaults = nil
+ @raw_column_defauts = nil
@column_names = nil
@column_types = nil
@content_columns = nil
@@ -295,13 +304,6 @@ module ActiveRecord
@cached_time_zone = nil
end
- # This is a hook for use by modules that need to do extra stuff to
- # attributes when they are initialized. (e.g. attribute
- # serialization)
- def initialize_attributes(attributes, options = {}) #:nodoc:
- attributes
- end
-
private
# Guesses the table name, but does not decorate it with prefix and suffix information.
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index b74e340b3e..2e3bcc0956 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -49,7 +49,11 @@ module ActiveRecord
def instantiate(attributes, column_types = {})
klass = discriminate_class_for_record(attributes)
column_types = klass.decorate_columns(column_types.dup)
- klass.allocate.init_with('attributes' => attributes, 'column_types' => column_types)
+ klass.allocate.init_with(
+ 'attributes' => attributes,
+ 'column_types' => column_types,
+ 'new_record' => false,
+ )
end
private
@@ -179,8 +183,8 @@ module ActiveRecord
# So any change to the attributes in either instance will affect the other.
def becomes(klass)
became = klass.new
+ became.instance_variable_set("@raw_attributes", @raw_attributes)
became.instance_variable_set("@attributes", @attributes)
- became.instance_variable_set("@attributes_cache", @attributes_cache)
became.instance_variable_set("@changed_attributes", @changed_attributes) if defined?(@changed_attributes)
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
@@ -396,11 +400,11 @@ module ActiveRecord
self.class.unscoped { self.class.find(id) }
end
- @attributes.update(fresh_object.instance_variable_get('@attributes'))
+ @raw_attributes.update(fresh_object.instance_variable_get('@raw_attributes'))
@column_types = self.class.column_types
@column_types_override = fresh_object.instance_variable_get('@column_types_override')
- @attributes_cache = {}
+ @attributes = {}
self
end
@@ -490,7 +494,7 @@ module ActiveRecord
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
- def _update_record(attribute_names = @attributes.keys)
+ def _update_record(attribute_names = @raw_attributes.keys)
attributes_values = arel_attributes_with_values_for_update(attribute_names)
if attributes_values.empty?
0
@@ -501,7 +505,7 @@ module ActiveRecord
# Creates a record with values matching those of the instance attributes
# and returns its id.
- def _create_record(attribute_names = @attributes.keys)
+ def _create_record(attribute_names = @raw_attributes.keys)
attributes_values = arel_attributes_with_values_for_create(attribute_names)
new_id = self.class.unscoped.insert attributes_values
diff --git a/activerecord/lib/active_record/properties.rb b/activerecord/lib/active_record/properties.rb
index 7fe59ccce4..48ee42aaca 100644
--- a/activerecord/lib/active_record/properties.rb
+++ b/activerecord/lib/active_record/properties.rb
@@ -5,7 +5,7 @@ module ActiveRecord
Type = ActiveRecord::Type
included do
- class_attribute :user_provided_columns, instance_accessor: false # :internal
+ class_attribute :user_provided_columns, instance_accessor: false # :internal:
self.user_provided_columns = {}
end
@@ -14,6 +14,17 @@ module ActiveRecord
# Active Record's type casting behavior, as well as adding support for user defined
# types.
#
+ # +name+ The name of the methods to define attribute methods for, and the column which
+ # this will persist to.
+ #
+ # +cast_type+ A type object that contains information about how to type cast the value.
+ # See the examples section for more information.
+ #
+ # ==== Options
+ # The options hash accepts the following options:
+ #
+ # +default+ is the default value that the column should use on a new record.
+ #
# ==== Examples
#
# The type detected by Active Record can be overriden.
@@ -62,11 +73,11 @@ module ActiveRecord
#
# store_listing = StoreListing.new(price_in_cents: '$10.00')
# store_listing.price_in_cents # => 1000
- def property(name, cast_type)
+ def property(name, cast_type, options = {})
name = name.to_s
- clear_properties_cache
+ clear_caches_calculated_from_columns
# Assign a new hash to ensure that subclasses do not share a hash
- self.user_provided_columns = user_provided_columns.merge(name => connection.new_column(name, nil, cast_type))
+ self.user_provided_columns = user_provided_columns.merge(name => connection.new_column(name, options[:default], cast_type))
end
# Returns an array of column objects for the table associated with this class.
@@ -81,7 +92,7 @@ module ActiveRecord
def reset_column_information # :nodoc:
super
- clear_properties_cache
+ clear_caches_calculated_from_columns
end
private
@@ -97,9 +108,14 @@ module ActiveRecord
existing_columns + new_columns
end
- def clear_properties_cache
+ def clear_caches_calculated_from_columns
@columns = nil
@columns_hash = nil
+ @column_types = nil
+ @column_defaults = nil
+ @raw_column_defaults = nil
+ @column_names = nil
+ @content_columns = nil
end
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index dd80ec6274..4d5203612c 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -239,7 +239,7 @@ module ActiveRecord
def association_scope_cache(conn, owner)
key = conn.prepared_statements
- if options[:polymorphic]
+ if polymorphic?
key = [key, owner.read_attribute(@foreign_type)]
end
@association_scope_cache[key] ||= @scope_lock.synchronize {
@@ -303,7 +303,7 @@ module ActiveRecord
end
def check_validity_of_inverse!
- unless options[:polymorphic]
+ unless polymorphic?
if has_inverse? && inverse_of.nil?
raise InverseOfAssociationNotFoundError.new(self)
end
@@ -403,7 +403,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
def association_class
case macro
when :belongs_to
- if options[:polymorphic]
+ if polymorphic?
Associations::BelongsToPolymorphicAssociation
else
Associations::BelongsToAssociation
@@ -424,7 +424,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
end
def polymorphic?
- options.key? :polymorphic
+ options[:polymorphic]
end
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
@@ -441,7 +441,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
def calculate_constructable(macro, options)
case macro
when :belongs_to
- !options[:polymorphic]
+ !polymorphic?
when :has_one
!options[:through]
else
@@ -723,7 +723,7 @@ directive on your declaration like:
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
end
- if through_reflection.options[:polymorphic]
+ if through_reflection.polymorphic?
raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
end
@@ -731,11 +731,11 @@ directive on your declaration like:
raise HasManyThroughSourceAssociationNotFoundError.new(self)
end
- if options[:source_type] && source_reflection.options[:polymorphic].nil?
+ if options[:source_type] && !source_reflection.polymorphic?
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
end
- if source_reflection.options[:polymorphic] && options[:source_type].nil?
+ if source_reflection.polymorphic? && options[:source_type].nil?
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index d155517b18..11ab1b4595 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -177,7 +177,7 @@ module ActiveRecord
end
result = result.map do |attributes|
- values = klass.initialize_attributes(attributes).values
+ values = attributes.values
columns.zip(values).map { |column, value| column.type_cast value }
end
@@ -278,7 +278,7 @@ module ActiveRecord
if group_attrs.first.respond_to?(:to_sym)
association = @klass._reflect_on_association(group_attrs.first.to_sym)
- associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
+ associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
group_fields = Array(associated ? association.foreign_key : group_attrs)
else
group_fields = group_attrs
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 228b2aa60f..293189fa69 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -95,7 +95,7 @@ module ActiveRecord
@hash_rows ||=
begin
# We freeze the strings to prevent them getting duped when
- # used as keys in ActiveRecord::Base's @attributes hash
+ # used as keys in ActiveRecord::Base's @raw_attributes hash
columns = @columns.map { |c| c.dup.freeze }
@rows.map { |row|
# In the past we used Hash[columns.zip(row)]
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index 1a766093d0..019fe2218e 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -180,12 +180,12 @@ module ActiveRecord #:nodoc:
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
def compute_type
klass = @serializable.class
- type = if klass.serialized_attributes.key?(name)
+ column = klass.columns_hash[name] || Type::Value.new
+
+ type = if column.serialized?
super
- elsif klass.columns_hash.key?(name)
- klass.columns_hash[name].type
else
- NilClass
+ column.type
end
{ :text => :string,
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 17d1ae1ba0..d733063f5a 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -347,7 +347,7 @@ module ActiveRecord
@_start_transaction_state[:destroyed] = @destroyed
end
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
- @_start_transaction_state[:frozen?] = @attributes.frozen?
+ @_start_transaction_state[:frozen?] = @raw_attributes.frozen?
end
# Clear the new record state and id of a record.
@@ -368,16 +368,16 @@ module ActiveRecord
if transaction_level < 1 || force
restore_state = @_start_transaction_state
was_frozen = restore_state[:frozen?]
- @attributes = @attributes.dup if @attributes.frozen?
+ @raw_attributes = @raw_attributes.dup if @raw_attributes.frozen?
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
if restore_state.has_key?(:id)
write_attribute(self.class.primary_key, restore_state[:id])
else
+ @raw_attributes.delete(self.class.primary_key)
@attributes.delete(self.class.primary_key)
- @attributes_cache.delete(self.class.primary_key)
end
- @attributes.freeze if was_frozen
+ @raw_attributes.freeze if was_frozen
end
end
end
diff --git a/activerecord/lib/active_record/type/binary.rb b/activerecord/lib/active_record/type/binary.rb
index e34b7bb268..9d10c91fc1 100644
--- a/activerecord/lib/active_record/type/binary.rb
+++ b/activerecord/lib/active_record/type/binary.rb
@@ -12,6 +12,24 @@ module ActiveRecord
def klass
::String
end
+
+ def type_cast_for_database(value)
+ Data.new(super)
+ end
+
+ class Data
+ def initialize(value)
+ @value = value
+ end
+
+ def to_s
+ @value
+ end
+
+ def hex
+ @value.unpack('H*')[0]
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type/date.rb b/activerecord/lib/active_record/type/date.rb
index 45c69460ef..d90a6069b7 100644
--- a/activerecord/lib/active_record/type/date.rb
+++ b/activerecord/lib/active_record/type/date.rb
@@ -9,6 +9,10 @@ module ActiveRecord
::Date
end
+ def type_cast_for_schema(value)
+ "'#{value.to_s(:db)}'"
+ end
+
private
def cast_value(value)
diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb
index 1c0147a797..6eed005345 100644
--- a/activerecord/lib/active_record/type/decimal.rb
+++ b/activerecord/lib/active_record/type/decimal.rb
@@ -11,6 +11,10 @@ module ActiveRecord
::BigDecimal
end
+ def type_cast_for_schema(value)
+ value.to_s
+ end
+
private
def cast_value(value)
diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb
index 464d631d80..9cc6411e77 100644
--- a/activerecord/lib/active_record/type/numeric.rb
+++ b/activerecord/lib/active_record/type/numeric.rb
@@ -13,6 +13,29 @@ module ActiveRecord
else super
end
end
+
+ def changed?(old_value, new_value) # :nodoc:
+ # 0 => 'wibble' should mark as changed so numericality validations run
+ if nil_or_zero?(old_value) && non_numeric_string?(new_value)
+ # nil => '' should not mark as changed
+ old_value != new_value.presence
+ else
+ super
+ end
+ end
+
+ private
+
+ def non_numeric_string?(value)
+ # 'wibble'.to_i will give zero, we want to make sure
+ # that we aren't marking int zero to string zero as
+ # changed.
+ value !~ /\A\d+\.?\d*\z/
+ end
+
+ def nil_or_zero?(value)
+ value.nil? || value == 0
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 4052ac0fa0..78a6d31e26 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -10,20 +10,21 @@ module ActiveRecord
end
def type_cast(value)
- if value.respond_to?(:unserialized_value)
- value.unserialized_value(super(value.value))
+ if is_default_value?(value)
+ value
else
- super
+ coder.load(super)
end
end
def type_cast_for_write(value)
- Attribute.new(coder, value, :unserialized)
+ return if value.nil?
+ unless is_default_value?(value)
+ coder.dump(value)
+ end
end
- def raw_type_cast_for_write(value)
- Attribute.new(coder, value, :serialized)
- end
+ alias type_cast_for_database type_cast_for_write
def serialized?
true
@@ -33,24 +34,14 @@ module ActiveRecord
ActiveRecord::Store::IndifferentHashAccessor
end
- class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
- def unserialized_value(v = value)
- state == :serialized ? unserialize(v) : value
- end
-
- def serialized_value
- state == :unserialized ? serialize : value
- end
+ private
- def unserialize(v)
- self.state = :unserialized
- self.value = coder.load(v)
- end
+ def changed?(old_value, new_value) # :nodoc:
+ old_value != new_value
+ end
- def serialize
- self.state = :serialized
- self.value = coder.dump(value)
- end
+ def is_default_value?(value)
+ value == coder.load(nil)
end
end
end
diff --git a/activerecord/lib/active_record/type/time_value.rb b/activerecord/lib/active_record/type/time_value.rb
index 6cc19b6379..d611d72dd4 100644
--- a/activerecord/lib/active_record/type/time_value.rb
+++ b/activerecord/lib/active_record/type/time_value.rb
@@ -5,6 +5,10 @@ module ActiveRecord
::Time
end
+ def type_cast_for_schema(value)
+ "'#{value.to_s(:db)}'"
+ end
+
private
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb
index 9a4adc60cc..c072c1e2b6 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activerecord/lib/active_record/type/value.rb
@@ -12,8 +12,8 @@ module ActiveRecord
@limit = options[:limit]
end
- # The simplified that this object represents. Subclasses
- # should override this method.
+ # The simplified type that this object represents. Subclasses
+ # must override this method.
def type; end
# Takes an input from the database, or from attribute setters,
@@ -27,6 +27,10 @@ module ActiveRecord
type_cast_for_write(value)
end
+ def type_cast_for_schema(value)
+ value.inspect
+ end
+
def text?
false
end
@@ -50,7 +54,15 @@ module ActiveRecord
def type_cast_for_write(value) # :nodoc:
value
end
- alias_method :raw_type_cast_for_write, :type_cast_for_write # :internal:
+
+ # +old_value+ will always be type-cast.
+ # +new_value+ will come straight from the database
+ # or from assignment, so it could be anything. Types
+ # which cannot typecast arbitrary values should override
+ # this method.
+ def changed?(old_value, new_value) # :nodoc:
+ old_value != type_cast(new_value)
+ end
private
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index b6fccc9b94..2e7b1d7206 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -14,7 +14,6 @@ module ActiveRecord
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
value = map_enum_attribute(finder_class, attribute, value)
- value = deserialize_attribute(record, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
@@ -86,12 +85,6 @@ module ActiveRecord
relation
end
- def deserialize_attribute(record, attribute, value)
- coder = record.class.serialized_attributes[attribute.to_s]
- value = coder.dump value if value && coder
- value
- end
-
def map_enum_attribute(klass, attribute, value)
mapping = klass.defined_enums[attribute.to_s]
value = mapping[value] if value && mapping
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 34c2008ab4..e03d83df59 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -1,7 +1,5 @@
# encoding: utf-8
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlArrayTest < ActiveRecord::TestCase
class PgArray < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
new file mode 100644
index 0000000000..3a9397bc26
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'support/connection_helper'
+require 'support/schema_dumping_helper'
+
+class PostgresqlBitStringTest < ActiveRecord::TestCase
+ include ConnectionHelper
+ include SchemaDumpingHelper
+
+ class PostgresqlBitString < ActiveRecord::Base; end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table('postgresql_bit_strings', :force => true) do |t|
+ t.bit :a_bit, default: "00000011", limit: 8
+ t.bit_varying :a_bit_varying, default: "0011", limit: 4
+ end
+ end
+
+ def teardown
+ return unless @connection
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_bit_strings'
+ end
+
+ def test_bit_string_column
+ column = PostgresqlBitString.columns_hash["a_bit"]
+ assert_equal :bit, column.type
+ assert_equal "bit(8)", column.sql_type
+ assert_not column.text?
+ assert_not column.number?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_bit_string_varying_column
+ column = PostgresqlBitString.columns_hash["a_bit_varying"]
+ assert_equal :bit_varying, column.type
+ assert_equal "bit varying(4)", column.sql_type
+ assert_not column.text?
+ assert_not column.number?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_default
+ column = PostgresqlBitString.columns_hash["a_bit"]
+ assert_equal "00000011", column.default
+ assert_equal "00000011", PostgresqlBitString.new.a_bit
+
+ column = PostgresqlBitString.columns_hash["a_bit_varying"]
+ assert_equal "0011", column.default
+ assert_equal "0011", PostgresqlBitString.new.a_bit_varying
+ end
+
+ def test_schema_dumping
+ output = dump_table_schema("postgresql_bit_strings")
+ assert_match %r{t\.bit\s+"a_bit",\s+limit: 8,\s+default: "00000011"$}, output
+ assert_match %r{t\.bit_varying\s+"a_bit_varying",\s+limit: 4,\s+default: "0011"$}, output
+ end
+
+ def test_assigning_invalid_hex_string_raises_exception
+ assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit: "FF" }
+ assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit_varying: "FF" }
+ end
+
+ def test_roundtrip
+ PostgresqlBitString.create! a_bit: "00001010", a_bit_varying: "0101"
+ record = PostgresqlBitString.first
+ assert_equal "00001010", record.a_bit
+ assert_equal "0101", record.a_bit_varying
+
+ record.a_bit = "11111111"
+ record.a_bit_varying = "0xF"
+ record.save!
+
+ assert record.reload
+ assert_equal "11111111", record.a_bit
+ assert_equal "1111", record.a_bit_varying
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index fadadfa57c..3f8a5d1062 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -1,8 +1,5 @@
# encoding: utf-8
-
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlByteaTest < ActiveRecord::TestCase
class ByteaDataType < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
index 8493050726..90e837d426 100644
--- a/activerecord/test/cases/adapters/postgresql/citext_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -1,8 +1,5 @@
# encoding: utf-8
-
require 'cases/helper'
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
if ActiveRecord::Base.connection.supports_extensions?
class PostgresqlCitextTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
index ecccbf10e6..a925263098 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
module PostgresqlCompositeBehavior
include ConnectionHelper
@@ -93,6 +91,7 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
end
def type_cast_for_write(value)
+ return if value.nil?
"(#{value.city},#{value.street})"
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 0dad89c67a..a0a34e4b87 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -8,9 +8,6 @@ end
class PostgresqlTime < ActiveRecord::Base
end
-class PostgresqlBitString < ActiveRecord::Base
-end
-
class PostgresqlOid < ActiveRecord::Base
end
@@ -33,15 +30,12 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
@connection.execute("INSERT INTO postgresql_times (id, time_interval, scaled_time_interval) VALUES (1, '1 year 2 days ago', '3 weeks ago')")
@first_time = PostgresqlTime.find(1)
- @connection.execute("INSERT INTO postgresql_bit_strings (id, bit_string, bit_string_varying) VALUES (1, B'00010101', X'15')")
- @first_bit_string = PostgresqlBitString.find(1)
-
@connection.execute("INSERT INTO postgresql_oids (id, obj_id) VALUES (1, 1234)")
@first_oid = PostgresqlOid.find(1)
end
teardown do
- [PostgresqlNumber, PostgresqlTime, PostgresqlBitString, PostgresqlOid].each(&:delete_all)
+ [PostgresqlNumber, PostgresqlTime, PostgresqlOid].each(&:delete_all)
end
def test_data_type_of_number_types
@@ -54,11 +48,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal :string, @first_time.column_for_attribute(:scaled_time_interval).type
end
- def test_data_type_of_bit_string_types
- assert_equal :string, @first_bit_string.column_for_attribute(:bit_string).type
- assert_equal :string, @first_bit_string.column_for_attribute(:bit_string_varying).type
- end
-
def test_data_type_of_oid_types
assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type
end
@@ -76,11 +65,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal '-21 days', @first_time.scaled_time_interval
end
- def test_bit_string_values
- assert_equal '00010101', @first_bit_string.bit_string
- assert_equal '00010101', @first_bit_string.bit_string_varying
- end
-
def test_oid_values
assert_equal 1234, @first_oid.obj_id
end
@@ -103,23 +87,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal '2 years 00:03:00', @first_time.time_interval
end
- def test_update_bit_string
- new_bit_string = '11111111'
- new_bit_string_varying = '0xFF'
- @first_bit_string.bit_string = new_bit_string
- @first_bit_string.bit_string_varying = new_bit_string_varying
- assert @first_bit_string.save
- assert @first_bit_string.reload
- assert_equal new_bit_string, @first_bit_string.bit_string
- assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying
- end
-
- def test_invalid_hex_string
- new_bit_string = 'FF'
- @first_bit_string.bit_string = new_bit_string
- assert_raise(ActiveRecord::StatementInvalid) { assert @first_bit_string.save }
- end
-
def test_update_oid
new_value = 567890
@first_oid.obj_id = new_value
diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb
index 5286a847a4..fd7fdecff1 100644
--- a/activerecord/test/cases/adapters/postgresql/domain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlDomainTest < ActiveRecord::TestCase
include ConnectionHelper
diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
index 4146b117f6..b809f1a79c 100644
--- a/activerecord/test/cases/adapters/postgresql/enum_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -39,6 +39,17 @@ class PostgresqlEnumTest < ActiveRecord::TestCase
assert_not column.array
end
+ def test_enum_defaults
+ @connection.add_column 'postgresql_enums', 'good_mood', :mood, default: 'happy'
+ PostgresqlEnum.reset_column_information
+ column = PostgresqlEnum.columns_hash["good_mood"]
+
+ assert_equal "happy", column.default
+ assert_equal "happy", PostgresqlEnum.new.good_mood
+ ensure
+ PostgresqlEnum.reset_column_information
+ end
+
def test_enum_mapping
@connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');"
enum = PostgresqlEnum.first
diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
index 91058f8681..7b99fcdda0 100644
--- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
@@ -1,6 +1,4 @@
require "cases/helper"
-require "active_record/base"
-require "active_record/connection_adapters/postgresql_adapter"
class PostgresqlExtensionMigrationTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
index 4442abcbc4..ec646de5e9 100644
--- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
@@ -1,8 +1,5 @@
# encoding: utf-8
-
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlFullTextTest < ActiveRecord::TestCase
class PostgresqlTsvector < ActiveRecord::Base; end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
new file mode 100644
index 0000000000..2f106ee664
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'support/connection_helper'
+require 'support/schema_dumping_helper'
+
+class PostgresqlPointTest < ActiveRecord::TestCase
+ include ConnectionHelper
+ include SchemaDumpingHelper
+
+ class PostgresqlPoint < ActiveRecord::Base; end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.transaction do
+ @connection.create_table('postgresql_points') do |t|
+ t.point :x
+ t.point :y, default: [12.2, 13.3]
+ t.point :z, default: "(14.4,15.5)"
+ end
+ end
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_points'
+ end
+
+ def test_column
+ column = PostgresqlPoint.columns_hash["x"]
+ assert_equal :point, column.type
+ assert_equal "point", column.sql_type
+ assert_not column.text?
+ assert_not column.number?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_default
+ column = PostgresqlPoint.columns_hash["y"]
+ assert_equal [12.2, 13.3], column.default
+ assert_equal [12.2, 13.3], PostgresqlPoint.new.y
+
+ column = PostgresqlPoint.columns_hash["z"]
+ assert_equal [14.4, 15.5], column.default
+ assert_equal [14.4, 15.5], PostgresqlPoint.new.z
+ end
+
+ def test_schema_dumping
+ output = dump_table_schema("postgresql_points")
+ assert_match %r{t\.point\s+"x"$}, output
+ assert_match %r{t\.point\s+"y",\s+default: \[12\.2, 13\.3\]$}, output
+ assert_match %r{t\.point\s+"z",\s+default: \[14\.4, 15\.5\]$}, output
+ end
+
+ def test_roundtrip
+ PostgresqlPoint.create! x: [10, 25.2]
+ record = PostgresqlPoint.first
+ assert_equal [10, 25.2], record.x
+
+ record.x = [1.1, 2.2]
+ record.save!
+ assert record.reload
+ assert_equal [1.1, 2.2], record.x
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index 718f37a380..ddb7cd658c 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -1,7 +1,5 @@
# encoding: utf-8
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlLtreeTest < ActiveRecord::TestCase
class Ltree < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index e109f1682b..3e33477bff 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -1,20 +1,28 @@
# encoding: utf-8
-
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
+require 'support/schema_dumping_helper'
class PostgresqlMoneyTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
class PostgresqlMoney < ActiveRecord::Base; end
setup do
@connection = ActiveRecord::Base.connection
@connection.execute("set lc_monetary = 'C'")
+ @connection.create_table('postgresql_moneys') do |t|
+ t.column "wealth", "money"
+ t.column "depth", "money", default: "150.55"
+ end
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_moneys'
end
def test_column
column = PostgresqlMoney.columns_hash["wealth"]
- assert_equal :decimal, column.type
+ assert_equal :money, column.type
assert_equal "money", column.sql_type
assert_equal 2, column.scale
assert column.number?
@@ -23,6 +31,12 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase
assert_not column.array
end
+ def test_default
+ column = PostgresqlMoney.columns_hash["depth"]
+ assert_equal BigDecimal.new("150.55"), column.default
+ assert_equal BigDecimal.new("150.55"), PostgresqlMoney.new.depth
+ end
+
def test_money_values
@connection.execute("INSERT INTO postgresql_moneys (id, wealth) VALUES (1, '567.89'::money)")
@connection.execute("INSERT INTO postgresql_moneys (id, wealth) VALUES (2, '-567.89'::money)")
@@ -41,6 +55,12 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase
assert_equal(-2.25, column.type_cast("($2.25)"))
end
+ def test_schema_dumping
+ output = dump_table_schema("postgresql_moneys")
+ assert_match %r{t\.money\s+"wealth",\s+scale: 2$}, output
+ assert_match %r{t\.money\s+"depth",\s+scale: 2,\s+default: 150.55$}, output
+ end
+
def test_create_and_update_money
money = PostgresqlMoney.create(wealth: "987.65")
assert_equal 987.65, money.wealth
diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb
index e99af07970..32085cbb17 100644
--- a/activerecord/test/cases/adapters/postgresql/network_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/network_test.rb
@@ -1,8 +1,5 @@
# encoding: utf-8
-
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlNetworkTest < ActiveRecord::TestCase
class PostgresqlNetworkAddress < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 49f5ec250f..cfff1f980b 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -134,18 +134,18 @@ module ActiveRecord
end
def test_default_sequence_name
- assert_equal 'accounts_id_seq',
+ assert_equal PostgreSQL::Name.new('public', 'accounts_id_seq'),
@connection.default_sequence_name('accounts', 'id')
- assert_equal 'accounts_id_seq',
+ assert_equal PostgreSQL::Name.new('public', 'accounts_id_seq'),
@connection.default_sequence_name('accounts')
end
def test_default_sequence_name_bad_table
- assert_equal 'zomg_id_seq',
+ assert_equal PostgreSQL::Name.new(nil, 'zomg_id_seq'),
@connection.default_sequence_name('zomg', 'id')
- assert_equal 'zomg_id_seq',
+ assert_equal PostgreSQL::Name.new(nil, 'zomg_id_seq'),
@connection.default_sequence_name('zomg')
end
@@ -216,7 +216,7 @@ module ActiveRecord
)
seq = @connection.pk_and_sequence_for('ex').last
- assert_equal 'ex_id_seq', seq
+ assert_equal PostgreSQL::Name.new("public", "ex_id_seq"), seq
@connection.exec_query(
"DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
@@ -353,6 +353,17 @@ module ActiveRecord
assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls last"])
end
+ def test_columns_for_distinct_without_order_specifiers
+ assert_equal "posts.title, posts.updater_id AS alias_0",
+ @connection.columns_for_distinct("posts.title", ["posts.updater_id"])
+
+ assert_equal "posts.title, posts.updater_id AS alias_0",
+ @connection.columns_for_distinct("posts.title", ["posts.updater_id nulls last"])
+
+ assert_equal "posts.title, posts.updater_id AS alias_0",
+ @connection.columns_for_distinct("posts.title", ["posts.updater_id nulls first"])
+ end
+
def test_raise_error_when_cannot_translate_exception
assert_raise TypeError do
@connection.send(:log, nil) { @connection.execute(nil) }
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index 060b17d071..4d9cfe55f5 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -1,7 +1,5 @@
require "cases/helper"
require 'support/connection_helper'
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
if ActiveRecord::Base.connection.supports_ranges?
class PostgresqlRange < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index b6c6e38f62..9e5fd17dc4 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -331,14 +331,15 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_pk_and_sequence_for_with_schema_specified
+ pg_name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
[
%("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"),
%("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
].each do |given|
pk, seq = @connection.pk_and_sequence_for(given)
assert_equal 'id', pk, "primary key should be found when table referenced as #{given}"
- assert_equal "#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}")
- assert_equal "#{UNMATCHED_SEQUENCE_NAME}", seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
+ assert_equal pg_name.new(SCHEMA_NAME, "#{PK_TABLE_NAME}_id_seq"), seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}")
+ assert_equal pg_name.new(SCHEMA_NAME, UNMATCHED_SEQUENCE_NAME), seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
end
end
@@ -378,6 +379,14 @@ class SchemaTest < ActiveRecord::TestCase
end
end
+ def test_reset_pk_sequence
+ sequence_name = "#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
+ @connection.execute "SELECT setval('#{sequence_name}', 123)"
+ assert_equal "124", @connection.select_value("SELECT nextval('#{sequence_name}')")
+ @connection.reset_pk_sequence!("#{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME}")
+ assert_equal "1", @connection.select_value("SELECT nextval('#{sequence_name}')")
+ end
+
private
def columns(table_name)
@connection.send(:column_definitions, table_name).map do |name, type, default|
diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb
index e6d7868e9a..3fdb6888d9 100644
--- a/activerecord/test/cases/adapters/postgresql/utils_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb
@@ -1,9 +1,10 @@
require 'cases/helper'
class PostgreSQLUtilsTest < ActiveSupport::TestCase
+ Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
include ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
- def test_extract_schema_and_table
+ def test_extract_schema_qualified_name
{
%(table_name) => [nil,'table_name'],
%("table.name") => [nil,'table.name'],
@@ -14,7 +15,47 @@ class PostgreSQLUtilsTest < ActiveSupport::TestCase
%("even spaces".table) => ['even spaces','table'],
%(schema."table.name") => ['schema', 'table.name']
}.each do |given, expect|
- assert_equal expect, extract_schema_and_table(given)
+ assert_equal Name.new(*expect), extract_schema_qualified_name(given)
end
end
end
+
+class PostgreSQLNameTest < ActiveSupport::TestCase
+ Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
+
+ test "represents itself as schema.name" do
+ obj = Name.new("public", "articles")
+ assert_equal "public.articles", obj.to_s
+ end
+
+ test "without schema, represents itself as name only" do
+ obj = Name.new(nil, "articles")
+ assert_equal "articles", obj.to_s
+ end
+
+ test "quoted returns a string representation usable in a query" do
+ assert_equal %("articles"), Name.new(nil, "articles").quoted
+ assert_equal %("public"."articles"), Name.new("public", "articles").quoted
+ end
+
+ test "prevents double quoting" do
+ name = Name.new('"quoted_schema"', '"quoted_table"')
+ assert_equal "quoted_schema.quoted_table", name.to_s
+ assert_equal %("quoted_schema"."quoted_table"), name.quoted
+ end
+
+ test "equality based on state" do
+ assert_equal Name.new("access", "users"), Name.new("access", "users")
+ assert_equal Name.new(nil, "users"), Name.new(nil, "users")
+ assert_not_equal Name.new(nil, "users"), Name.new("access", "users")
+ assert_not_equal Name.new("access", "users"), Name.new("public", "users")
+ assert_not_equal Name.new("public", "users"), Name.new("public", "articles")
+ end
+
+ test "can be used as hash key" do
+ hash = {Name.new("schema", "article_seq") => "success"}
+ assert_equal "success", hash[Name.new("schema", "article_seq")]
+ assert_equal nil, hash[Name.new("schema", "articles")]
+ assert_equal nil, hash[Name.new("public", "article_seq")]
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb
index c1c85f8c92..48c6eeb62c 100644
--- a/activerecord/test/cases/adapters/postgresql/xml_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb
@@ -1,8 +1,5 @@
# encoding: utf-8
-
require 'cases/helper'
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlXMLTest < ActiveRecord::TestCase
class XmlDataType < ActiveRecord::Base
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 4bd4486b41..910067666a 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1239,6 +1239,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
}
end
+ test "including association based on sql condition and no database column" do
+ assert_equal pets(:parrot), Owner.including_last_pet.first.last_pet
+ end
+
test "include instance dependent associations is deprecated" do
message = "association scope 'posts_with_signature' is"
assert_deprecated message do
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 8d8201ddae..080c499444 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -872,6 +872,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_redefine_habtm
child = SubDeveloper.new("name" => "Aredridel")
child.special_projects << SpecialProject.new("name" => "Special Project")
- assert_equal true, child.save
+ assert child.save, 'child object should be saved'
end
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 893030345f..a674a39d65 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -333,7 +333,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
def test_parent_instance_should_be_shared_within_create_block_of_new_child
man = Man.first
- interest = man.interests.build do |i|
+ interest = man.interests.create do |i|
assert i.man.equal?(man), "Man of child should be the same instance as a parent"
end
assert interest.man.equal?(man), "Man of the child should still be the same instance as a parent"
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 4c96c2f4fd..139fe9c04b 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -299,6 +299,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase
computer = Computer.select('id').first
assert_raises(ActiveModel::MissingAttributeError) { computer[:developer] }
assert_raises(ActiveModel::MissingAttributeError) { computer[:extendedWarranty] }
+ assert_raises(ActiveModel::MissingAttributeError) { computer[:no_column_exists] = 'Hello!' }
+ assert_nothing_raised { computer[:developer] = 'Hello!' }
end
def test_read_attribute_when_false
@@ -534,7 +536,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_accessing_cached_attributes_caches_the_converted_values_and_nothing_else
t = topics(:first)
- cache = t.instance_variable_get "@attributes_cache"
+ cache = t.instance_variable_get "@attributes"
assert_not_nil cache
assert cache.empty?
@@ -868,7 +870,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def cached_columns
- Topic.columns.map(&:name) - Topic.serialized_attributes.keys
+ Topic.columns.map(&:name)
end
def time_related_columns_on_topic
diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb
index 2a52bf574c..715d92af99 100644
--- a/activerecord/test/cases/core_test.rb
+++ b/activerecord/test/cases/core_test.rb
@@ -1,6 +1,8 @@
require 'cases/helper'
require 'models/person'
require 'models/topic'
+require 'pp'
+require 'active_support/core_ext/string/strip'
class NonExistentTable < ActiveRecord::Base; end
@@ -30,4 +32,70 @@ class CoreTest < ActiveRecord::TestCase
def test_inspect_class_without_table
assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect
end
+
+ def test_pretty_print_new
+ topic = Topic.new
+ actual = ''
+ PP.pp(topic, StringIO.new(actual))
+ expected = <<-PRETTY.strip_heredoc
+ #<Topic:0xXXXXXX
+ id: nil,
+ title: nil,
+ author_name: nil,
+ author_email_address: "test@test.com",
+ written_on: nil,
+ bonus_time: nil,
+ last_read: nil,
+ content: nil,
+ important: nil,
+ approved: true,
+ replies_count: 0,
+ unique_replies_count: 0,
+ parent_id: nil,
+ parent_title: nil,
+ type: nil,
+ group: nil,
+ created_at: nil,
+ updated_at: nil>
+ PRETTY
+ assert actual.start_with?(expected.split('XXXXXX').first)
+ assert actual.end_with?(expected.split('XXXXXX').last)
+ end
+
+ def test_pretty_print_persisted
+ topic = topics(:first)
+ actual = ''
+ PP.pp(topic, StringIO.new(actual))
+ expected = <<-PRETTY.strip_heredoc
+ #<Topic:0x\\w+
+ id: 1,
+ title: "The First Topic",
+ author_name: "David",
+ author_email_address: "david@loudthinking.com",
+ written_on: 2003-07-16 14:28:11 UTC,
+ bonus_time: 2000-01-01 14:28:00 UTC,
+ last_read: Thu, 15 Apr 2004,
+ content: "Have a nice day",
+ important: nil,
+ approved: false,
+ replies_count: 1,
+ unique_replies_count: 0,
+ parent_id: nil,
+ parent_title: nil,
+ type: nil,
+ group: nil,
+ created_at: [^,]+,
+ updated_at: [^,>]+>
+ PRETTY
+ assert_match(/\A#{expected}\z/, actual)
+ end
+
+ def test_pretty_print_uninitialized
+ topic = Topic.allocate
+ actual = ''
+ PP.pp(topic, StringIO.new(actual))
+ expected = "#<Topic:XXXXXX not initialized>\n"
+ assert actual.start_with?(expected.split('XXXXXX').first)
+ assert actual.end_with?(expected.split('XXXXXX').last)
+ end
end
diff --git a/activerecord/test/cases/custom_properties_test.rb b/activerecord/test/cases/custom_properties_test.rb
index 047c1b9b74..9ba1e83df6 100644
--- a/activerecord/test/cases/custom_properties_test.rb
+++ b/activerecord/test/cases/custom_properties_test.rb
@@ -4,6 +4,7 @@ class OverloadedType < ActiveRecord::Base
property :overloaded_float, Type::Integer.new
property :overloaded_string_with_limit, Type::String.new(limit: 50)
property :non_existent_decimal, Type::Decimal.new
+ property :string_with_default, Type::String.new, default: 'the overloaded default'
end
class ChildOfOverloadedType < OverloadedType
@@ -62,12 +63,12 @@ module ActiveRecord
end
end
- def test_overloaded_properties_have_no_default
+ def test_changing_defaults
data = OverloadedType.new
unoverloaded_data = UnoverloadedType.new
- assert_nil data.overloaded_float
- assert unoverloaded_data.overloaded_float
+ assert_equal 'the overloaded default', data.string_with_default
+ assert_equal 'the original default', unoverloaded_data.string_with_default
end
def test_children_inherit_custom_properties
@@ -84,7 +85,27 @@ module ActiveRecord
def test_overloading_properties_does_not_change_column_order
column_names = OverloadedType.column_names
- assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit non_existent_decimal), column_names
+ assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit string_with_default non_existent_decimal), column_names
+ end
+
+ def test_caches_are_cleared
+ klass = Class.new(OverloadedType)
+
+ assert_equal 6, klass.columns.length
+ assert_not klass.columns_hash.key?('wibble')
+ assert_equal 6, klass.column_types.length
+ assert_equal 6, klass.column_defaults.length
+ assert_not klass.column_names.include?('wibble')
+ assert_equal 5, klass.content_columns.length
+
+ klass.property :wibble, Type::Value.new
+
+ assert_equal 7, klass.columns.length
+ assert klass.columns_hash.key?('wibble')
+ assert_equal 7, klass.column_types.length
+ assert_equal 7, klass.column_defaults.length
+ assert klass.column_names.include?('wibble')
+ assert_equal 6, klass.content_columns.length
end
end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index f885a8cbc0..92144bc802 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -206,6 +206,11 @@ if current_adapter?(:PostgreSQLAdapter)
assert_equal "some text", Default.new.text_col, "Default of text column was not correctly parse after updating default using '::text' since postgreSQL will add parens to the default in db"
end
+ def test_default_containing_quote_and_colons
+ @connection.execute "ALTER TABLE defaults ALTER COLUMN string_col SET DEFAULT 'foo''::bar'"
+ assert_equal "foo'::bar", Default.new.string_col
+ end
+
teardown do
@connection.schema_search_path = @old_search_path
Default.reset_column_information
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index df4183c065..987c55ebc2 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -616,6 +616,34 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ test "defaults with type that implements `type_cast_for_write`" do
+ type = Class.new(ActiveRecord::Type::Value) do
+ def type_cast(value)
+ value.to_i
+ end
+
+ def type_cast_for_write(value)
+ value.to_s
+ end
+
+ alias type_cast_for_database type_cast_for_write
+ end
+
+ model_class = Class.new(ActiveRecord::Base) do
+ self.table_name = 'numeric_data'
+ property :foo, type.new, default: 1
+ end
+
+ model = model_class.new
+ assert_not model.foo_changed?
+
+ model = model_class.new(foo: 1)
+ assert_not model.foo_changed?
+
+ model = model_class.new(foo: '1')
+ assert_not model.foo_changed?
+ end
+
private
def with_partial_writes(klass, on = true)
old = klass.partial_writes?
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index e0b03f4735..a52b58c4ac 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -74,7 +74,7 @@ module ActiveRecord
pk, seq = connection.pk_and_sequence_for('octopi')
- assert_equal "octopi_#{pk}_seq", seq
+ assert_equal ConnectionAdapters::PostgreSQL::Name.new("public", "octopi_#{pk}_seq"), seq
end
end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index e6603f28be..793e193329 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -80,6 +80,26 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal :integer, @first.column_for_attribute("id").type
end
+ def test_non_existent_columns_return_null_object
+ column = @first.column_for_attribute("attribute_that_doesnt_exist")
+ assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column
+ assert_equal "attribute_that_doesnt_exist", column.name
+ assert_equal nil, column.sql_type
+ assert_equal nil, column.type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ end
+
+ def test_non_existent_columns_are_identity_types
+ column = @first.column_for_attribute("attribute_that_doesnt_exist")
+ object = Object.new
+
+ assert_equal object, column.type_cast(object)
+ assert_equal object, column.type_cast_for_write(object)
+ assert_equal object, column.type_cast_for_database(object)
+ end
+
def test_reflection_klass_for_nested_class_name
reflection = MacroReflection.new(:company, nil, nil, { :class_name => 'MyApplication::Business::Company' }, ActiveRecord::Base)
assert_nothing_raised do
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 9602252b2e..ce2b06430b 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -62,7 +62,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
next if column_set.empty?
lengths = column_set.map do |column|
- if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|uuid)\s+"/)
+ if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|uuid|point)\s+"/)
match[0].length
end
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index c65a86a6ef..5ea62c9f59 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -46,34 +46,22 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal(hash, important_topic.content)
end
- # This test was added to fix GH #4004. Obviously the value returned
- # is not really the value 'before type cast' so we should maybe think
- # about changing that in the future.
- def test_serialized_attribute_before_type_cast_returns_unserialized_value
+ def test_serialized_attributes_from_database_on_subclass
Topic.serialize :content, Hash
- t = Topic.new(content: { foo: :bar })
- assert_equal({ foo: :bar }, t.content_before_type_cast)
+ t = Reply.new(content: { foo: :bar })
+ assert_equal({ foo: :bar }, t.content)
t.save!
- t.reload
- assert_equal({ foo: :bar }, t.content_before_type_cast)
- end
-
- def test_serialized_attributes_before_type_cast_returns_unserialized_value
- Topic.serialize :content, Hash
-
- t = Topic.new(content: { foo: :bar })
- assert_equal({ foo: :bar }, t.attributes_before_type_cast["content"])
- t.save!
- t.reload
- assert_equal({ foo: :bar }, t.attributes_before_type_cast["content"])
+ t = Reply.last
+ assert_equal({ foo: :bar }, t.content)
end
def test_serialized_attribute_calling_dup_method
Topic.serialize :content, JSON
- t = Topic.new(:content => { :foo => :bar }).dup
- assert_equal({ :foo => :bar }, t.content_before_type_cast)
+ orig = Topic.new(content: { foo: :bar })
+ clone = orig.dup
+ assert_equal(orig.content, clone.content)
end
def test_serialized_attribute_declared_in_subclass
@@ -116,8 +104,10 @@ class SerializedAttributeTest < ActiveRecord::TestCase
def test_serialized_attribute_should_raise_exception_on_save_with_wrong_type
Topic.serialize(:content, Hash)
- topic = Topic.new(:content => "string")
- assert_raise(ActiveRecord::SerializationTypeMismatch) { topic.save }
+ assert_raise(ActiveRecord::SerializationTypeMismatch) do
+ topic = Topic.new(content: 'string')
+ topic.save
+ end
end
def test_should_raise_exception_on_serialized_attribute_with_type_mismatch
@@ -168,45 +158,22 @@ class SerializedAttributeTest < ActiveRecord::TestCase
end
def test_serialize_with_coder
- coder = Class.new {
- # Identity
- def load(thing)
- thing
- end
-
- # base 64
- def dump(thing)
- [thing].pack('m')
- end
- }.new
-
- Topic.serialize(:content, coder)
- s = 'hello world'
- topic = Topic.new(:content => s)
- assert topic.save
- topic = topic.reload
- assert_equal [s].pack('m'), topic.content
- end
-
- def test_serialize_with_bcrypt_coder
- crypt_coder = Class.new {
- def load(thing)
- return unless thing
- BCrypt::Password.new thing
+ some_class = Struct.new(:foo) do
+ def self.dump(value)
+ value.foo
end
- def dump(thing)
- BCrypt::Password.create(thing).to_s
+ def self.load(value)
+ new(value)
end
- }.new
+ end
- Topic.serialize(:content, crypt_coder)
- password = 'password'
- topic = Topic.new(:content => password)
- assert topic.save
- topic = topic.reload
- assert_kind_of BCrypt::Password, topic.content
- assert_equal(true, topic.content == password, 'password should equal')
+ Topic.serialize(:content, some_class)
+ topic = Topic.new(:content => some_class.new('my value'))
+ topic.save!
+ topic.reload
+ assert_kind_of some_class, topic.content
+ assert_equal topic.content, some_class.new('my value')
end
def test_serialize_attribute_via_select_method_when_time_zone_available
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 6a34c55011..f841b1c983 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -183,20 +183,6 @@ class StoreTest < ActiveRecord::TestCase
assert_equal({}, @john.params)
end
- test "attributes_for_coder should return stored fields already serialized" do
- attributes = {
- "id" => @john.id,
- "name"=> @john.name,
- "settings" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ncolor: black\n",
- "preferences" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nremember_login: true\n",
- "json_data" => "{\"height\":\"tall\"}", "json_data_empty"=>"{\"is_a_good_guy\":true}",
- "params" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess {}\n",
- "account_id"=> @john.account_id
- }
-
- assert_equal attributes, @john.attributes_for_coder
- end
-
test "dump, load and dump again a model" do
dumped = YAML.dump(@john)
loaded = YAML.load(dumped)
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index 1a690c01a6..c34e7d5a30 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -416,8 +416,9 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
def test_should_support_aliased_attributes
xml = Author.select("name as firstname").to_xml
- array = Hash.from_xml(xml)['authors']
- assert_equal array.size, array.select { |author| author.has_key? 'firstname' }.size
+ Author.all.each do |author|
+ assert xml.include?(%(<firstname>#{author.name}</firstname>)), xml
+ end
end
def test_array_to_xml_including_has_many_association
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 15815d56e4..d4f8ef5b4d 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -23,13 +23,6 @@ class YamlSerializationTest < ActiveRecord::TestCase
assert_equal({:omg=>:lol}, YAML.load(YAML.dump(topic)).content)
end
- def test_encode_with_coder
- topic = Topic.first
- coder = {}
- topic.encode_with coder
- assert_equal({'attributes' => topic.attributes}, coder)
- end
-
def test_psych_roundtrip
topic = Topic.first
assert topic
@@ -47,4 +40,33 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_active_record_relation_serialization
[Topic.all].to_yaml
end
+
+ def test_raw_types_are_not_changed_on_round_trip
+ topic = Topic.new(parent_id: "123")
+ assert_equal "123", topic.parent_id_before_type_cast
+ assert_equal "123", YAML.load(YAML.dump(topic)).parent_id_before_type_cast
+ end
+
+ def test_cast_types_are_not_changed_on_round_trip
+ topic = Topic.new(parent_id: "123")
+ assert_equal 123, topic.parent_id
+ assert_equal 123, YAML.load(YAML.dump(topic)).parent_id
+ end
+
+ def test_new_records_remain_new_after_round_trip
+ topic = Topic.new
+
+ assert topic.new_record?, "Sanity check that new records are new"
+ assert YAML.load(YAML.dump(topic)).new_record?, "Record should be new after deserialization"
+
+ topic.save!
+
+ assert_not topic.new_record?, "Saved records are not new"
+ assert_not YAML.load(YAML.dump(topic)).new_record?, "Saved record should not be new after deserialization"
+
+ topic = Topic.select('title').last
+
+ assert_not topic.new_record?, "Loaded records without ID are not new"
+ assert_not YAML.load(YAML.dump(topic)).new_record?, "Record should not be new after deserialization"
+ end
end
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index cf24502d3a..2e3a9a3681 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -3,6 +3,18 @@ class Owner < ActiveRecord::Base
has_many :pets, -> { order 'pets.name desc' }
has_many :toys, :through => :pets
+ belongs_to :last_pet, class_name: 'Pet'
+ scope :including_last_pet, -> {
+ select(%q[
+ owners.*, (
+ select p.pet_id from pets p
+ where p.owner_id = owners.owner_id
+ order by p.name desc
+ limit 1
+ ) as last_pet_id
+ ]).includes(:last_pet)
+ }
+
after_commit :execute_blocks
def blocks
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 4fcbf4dbd2..e9294a11b9 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,6 +1,6 @@
ActiveRecord::Schema.define do
- %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees
+ %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_uuids postgresql_ltrees
postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type postgresql_citext).each do |table_name|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -118,13 +118,6 @@ _SQL
end
execute <<_SQL
- CREATE TABLE postgresql_moneys (
- id SERIAL PRIMARY KEY,
- wealth MONEY
- );
-_SQL
-
- execute <<_SQL
CREATE TABLE postgresql_numbers (
id SERIAL PRIMARY KEY,
single REAL,
@@ -150,14 +143,6 @@ _SQL
_SQL
execute <<_SQL
- CREATE TABLE postgresql_bit_strings (
- id SERIAL PRIMARY KEY,
- bit_string BIT(8),
- bit_string_varying BIT VARYING(8)
- );
-_SQL
-
- execute <<_SQL
CREATE TABLE postgresql_oids (
id SERIAL PRIMARY KEY,
obj_id OID
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 4cce58f4f4..5f459cf682 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -860,6 +860,7 @@ ActiveRecord::Schema.define do
t.float :overloaded_float, default: 500
t.float :unoverloaded_float
t.string :overloaded_string_with_limit, limit: 255
+ t.string :string_with_default, default: 'the original default'
end
end
diff --git a/activerecord/test/support/schema_dumping_helper.rb b/activerecord/test/support/schema_dumping_helper.rb
new file mode 100644
index 0000000000..2ae8d299e5
--- /dev/null
+++ b/activerecord/test/support/schema_dumping_helper.rb
@@ -0,0 +1,11 @@
+module SchemaDumpingHelper
+ def dump_table_schema(table, connection = ActiveRecord::Base.connection)
+ old_ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
+ ActiveRecord::SchemaDumper.ignore_tables = connection.tables - [table]
+ stream = StringIO.new
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
+ stream.string
+ ensure
+ ActiveRecord::SchemaDumper.ignore_tables = old_ignore_tables
+ end
+end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 611932874b..871e1705f7 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -18,6 +18,32 @@
*Gordon Chan*
+* Fixed precision error in NumberHelper when using Rationals.
+
+ before:
+ ActiveSupport::NumberHelper.number_to_rounded Rational(1000, 3), precision: 2
+ #=> "330.00"
+ after:
+ ActiveSupport::NumberHelper.number_to_rounded Rational(1000, 3), precision: 2
+ #=> "333.33"
+
+ See #15379.
+
+ *Juanjo Bazán*
+
+* Removed deprecated `Numeric#ago` and friends
+
+ Replacements:
+
+ 5.ago => 5.seconds.ago
+ 5.until => 5.seconds.until
+ 5.since => 5.seconds.since
+ 5.from_now => 5.seconds.from_now
+
+ See #12389 for the history and rationale behind this.
+
+ *Godfrey Chan*
+
* DateTime `advance` now supports partial days.
Before:
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
index f3582767c0..a6424a353a 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -30,6 +30,11 @@ API documentation is at:
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index 970d6faa1d..28cb3e2a3b 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -18,6 +18,6 @@ class Hash
#
# b = { b: 1 }
# { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access
- # # => {"b"=>32}
+ # # => {"b"=>1}
alias nested_under_indifferent_access with_indifferent_access
end
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index 28536e32a4..5934c578ea 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -27,7 +27,7 @@ class Hash
# hash = { name: 'Rob', age: '28' }
#
# hash.stringify_keys
- # # => { "name" => "Rob", "age" => "28" }
+ # # => {"name"=>"Rob", "age"=>"28"}
def stringify_keys
transform_keys{ |key| key.to_s }
end
@@ -44,7 +44,7 @@ class Hash
# hash = { 'name' => 'Rob', 'age' => '28' }
#
# hash.symbolize_keys
- # # => { name: "Rob", age: "28" }
+ # # => {:name=>"Rob", :age=>"28"}
def symbolize_keys
transform_keys{ |key| key.to_sym rescue key }
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 704c4248d9..689fae4830 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -61,25 +61,7 @@ class Numeric
end
alias :fortnight :fortnights
- # Reads best without arguments: 10.minutes.ago
- def ago(time = ::Time.current)
- ActiveSupport::Deprecation.warn "Calling #ago or #until on a number (e.g. 5.ago) is deprecated and will be removed in the future, use 5.seconds.ago instead"
- time - self
- end
-
- # Reads best with argument: 10.minutes.until(time)
- alias :until :ago
-
- # Reads best with argument: 10.minutes.since(time)
- def since(time = ::Time.current)
- ActiveSupport::Deprecation.warn "Calling #since or #from_now on a number (e.g. 5.since) is deprecated and will be removed in the future, use 5.seconds.since instead"
- time + self
- end
-
- # Reads best without arguments: 10.minutes.from_now
- alias :from_now :since
-
- # Used with the standard time durations, like 1.hour.in_milliseconds --
+ # Used with the standard time durations, like 1.hour.in_milliseconds --
# so we can feed them to JavaScript functions like getTime().
def in_milliseconds
self * 1000
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 2c8995be9a..46cd170c1d 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -19,12 +19,7 @@ class ERB
# puts html_escape('is a > 0 & a < 10?')
# # => is a &gt; 0 &amp; a &lt; 10?
def html_escape(s)
- s = s.to_s
- if s.html_safe?
- s
- else
- s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE).html_safe
- end
+ unwrapped_html_escape(s).html_safe
end
# Aliasing twice issues a warning "discarding old...". Remove first to avoid it.
@@ -36,6 +31,18 @@ class ERB
singleton_class.send(:remove_method, :html_escape)
module_function :html_escape
+ # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
+ # This method is not for public consumption! Seriously!
+ def unwrapped_html_escape(s) # :nodoc:
+ s = s.to_s
+ if s.html_safe?
+ s
+ else
+ s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
+ end
+ end
+ module_function :unwrapped_html_escape
+
# A utility method for escaping HTML without affecting existing escaped entities.
#
# html_escape_once('1 < 2 &amp; 3')
@@ -170,13 +177,15 @@ module ActiveSupport #:nodoc:
self[0, 0]
end
- %w[concat prepend].each do |method_name|
- define_method method_name do |value|
- super(html_escape_interpolated_argument(value))
- end
+ def concat(value)
+ super(html_escape_interpolated_argument(value))
end
alias << concat
+ def prepend(value)
+ super(html_escape_interpolated_argument(value))
+ end
+
def prepend!(value)
ActiveSupport::Deprecation.deprecation_warning "ActiveSupport::SafeBuffer#prepend!", :prepend
prepend value
@@ -231,7 +240,8 @@ module ActiveSupport #:nodoc:
private
def html_escape_interpolated_argument(arg)
- (!html_safe? || arg.html_safe?) ? arg : ERB::Util.h(arg)
+ (!html_safe? || arg.html_safe?) ? arg :
+ arg.to_s.gsub(ERB::Util::HTML_ESCAPE_REGEXP, ERB::Util::HTML_ESCAPE)
end
end
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index a4ebdea598..e782cfa2f5 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -176,7 +176,14 @@ module ActiveSupport
indices.collect { |key| self[convert_key(key)] }
end
- # Returns an exact copy of the hash.
+ # Returns a shallow copy of the hash.
+ #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new({ a: { b: 'b' } })
+ # dup = hash.dup
+ # dup[:a][:c] = 'c'
+ #
+ # hash[:a][:c] # => nil
+ # dup[:a][:c] # => "c"
def dup
self.class.new(self).tap do |new_hash|
new_hash.default = default
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 7a96c66626..325a3d75dc 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -141,6 +141,11 @@ module ActiveSupport
#
# ActiveSupport::Notifications.unsubscribe(subscriber)
#
+ # You can also unsubscribe by passing the name of the subscriber object. Note
+ # that this will unsubscribe all subscriptions with the given name:
+ #
+ # ActiveSupport::Notifications.unsubscribe("render")
+ #
# == Default Queue
#
# Notifications ships with a queue implementation that consumes and publishes events
@@ -173,8 +178,8 @@ module ActiveSupport
unsubscribe(subscriber)
end
- def unsubscribe(args)
- notifier.unsubscribe(args)
+ def unsubscribe(subscriber_or_name)
+ notifier.unsubscribe(subscriber_or_name)
end
def instrumenter
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 8f5fa646e8..6bf8c7d5de 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -25,9 +25,15 @@ module ActiveSupport
subscriber
end
- def unsubscribe(subscriber)
+ def unsubscribe(subscriber_or_name)
synchronize do
- @subscribers.reject! { |s| s.matches?(subscriber) }
+ case subscriber_or_name
+ when String
+ @subscribers.reject! { |s| s.matches?(subscriber_or_name) }
+ else
+ @subscribers.delete(subscriber_or_name)
+ end
+
@listeners_for.clear
end
end
@@ -97,12 +103,11 @@ module ActiveSupport
end
def subscribed_to?(name)
- @pattern === name.to_s
+ @pattern === name
end
- def matches?(subscriber_or_name)
- self === subscriber_or_name ||
- @pattern && @pattern === subscriber_or_name
+ def matches?(name)
+ @pattern && @pattern === name
end
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
index c45f6cdcfa..01597b288a 100644
--- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -12,11 +12,7 @@ module ActiveSupport
when Float, String
@number = BigDecimal(number.to_s)
when Rational
- if significant
- @number = BigDecimal(number, digit_count(number.to_i) + precision)
- else
- @number = BigDecimal(number, precision)
- end
+ @number = BigDecimal(number, digit_count(number.to_i) + precision)
else
@number = number.to_d
end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index d6ffe37833..4a0ed356b1 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -353,6 +353,14 @@ module ActiveSupport
initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc)
end
+ # respond_to_missing? is not called in some cases, such as when type conversion is
+ # performed with Kernel#String
+ def respond_to?(sym, include_priv = false)
+ # ensure that we're not going to throw and rescue from NoMethodError in method_missing which is slow
+ return false if sym.to_sym == :to_str
+ super
+ end
+
# Ensure proxy class responds to all methods that underlying time instance
# responds to.
def respond_to_missing?(sym, include_priv)
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index cb706d77c2..cd0cb1a144 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -70,6 +70,8 @@ class HashExtTest < ActiveSupport::TestCase
assert_respond_to h, :to_options!
assert_respond_to h, :compact
assert_respond_to h, :compact!
+ assert_respond_to h, :except
+ assert_respond_to h, :except!
end
def test_transform_keys
@@ -919,13 +921,19 @@ class HashExtTest < ActiveSupport::TestCase
def test_except_with_more_than_one_argument
original = { :a => 'x', :b => 'y', :c => 10 }
expected = { :a => 'x' }
+
assert_equal expected, original.except(:b, :c)
+
+ assert_equal expected, original.except!(:b, :c)
+ assert_equal expected, original
end
def test_except_with_original_frozen
original = { :a => 'x', :b => 'y' }
original.freeze
assert_nothing_raised { original.except(:a) }
+
+ assert_raise(RuntimeError) { original.except!(:a) }
end
def test_except_with_mocha_expectation_on_original
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 3b1dabea8d..dbc3ffd319 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -22,18 +22,6 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
end
end
- def test_deprecated_since_and_ago
- assert_equal @now + 1, assert_deprecated { 1.since(@now) }
- assert_equal @now - 1, assert_deprecated { 1.ago(@now) }
- end
-
- def test_deprecated_since_and_ago_without_argument
- now = Time.now
- assert assert_deprecated { 1.since } >= now + 1
- now = Time.now
- assert assert_deprecated { 1.ago } >= now - 1
- end
-
def test_irregular_durations
assert_equal @now.advance(:days => 3000), 3000.days.since(@now)
assert_equal @now.advance(:months => 1), 1.month.since(@now)
@@ -84,36 +72,6 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10) + 1.year
end
- def test_since_and_ago_anchored_to_time_now_when_time_zone_is_not_set
- Time.zone = nil
- with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- # since
- assert_not_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.since }
- assert_equal Time.local(2000,1,1,0,0,5), assert_deprecated { 5.since }
- # ago
- assert_not_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.ago }
- assert_equal Time.local(1999,12,31,23,59,55), assert_deprecated { 5.ago }
- end
- end
-
- def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_is_set
- Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
- with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- # since
- assert_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.since }
- assert_equal Time.utc(2000,1,1,0,0,5), assert_deprecated { 5.since.time }
- assert_equal 'Eastern Time (US & Canada)', assert_deprecated { 5.since.time_zone.name }
- # ago
- assert_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.ago }
- assert_equal Time.utc(1999,12,31,23,59,55), assert_deprecated { 5.ago.time }
- assert_equal 'Eastern Time (US & Canada)', assert_deprecated { 5.ago.time_zone.name }
- end
- ensure
- Time.zone = nil
- end
-
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
@@ -435,7 +393,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal BigDecimal, BigDecimal("1000010").class
assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
end
-
+
def test_in_milliseconds
assert_equal 10_000, 10.seconds.in_milliseconds
end
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index a7a0ae02e7..bb51cc68f2 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -134,6 +134,7 @@ module ActiveSupport
assert_equal("111.23460000000000000000", number_helper.number_to_rounded('111.2346', :precision => 20))
assert_equal("111.23460000000000000000", number_helper.number_to_rounded(BigDecimal(111.2346, Float::DIG), :precision => 20))
assert_equal("111.2346" + "0"*96, number_helper.number_to_rounded('111.2346', :precision => 100))
+ assert_equal("111.2346", number_helper.number_to_rounded(Rational(1112346, 10000), :precision => 4))
end
end
@@ -174,6 +175,7 @@ module ActiveSupport
assert_equal "9775.0000000000000000", number_helper.number_to_rounded(BigDecimal(9775), :precision => 20, :significant => true )
assert_equal "9775.0000000000000000", number_helper.number_to_rounded("9775", :precision => 20, :significant => true )
assert_equal "9775." + "0"*96, number_helper.number_to_rounded("9775", :precision => 100, :significant => true )
+ assert_equal("97.7", number_helper.number_to_rounded(Rational(9772, 100), :precision => 3, :significant => true))
end
end
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index cdcde67869..2416e1a228 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -562,4 +562,4 @@ Credits
See the [full list of contributors to Rails](http://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them.
-Rails 3.2 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev.)
+Rails 3.2 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev).
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 1735188f27..3d15319ca4 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -1078,7 +1078,7 @@ Rails keeps a log file for each environment in the `log` folder. These are extre
### Parameters Filtering
-You can filter certain request parameters from your log files by appending them to `config.filter_parameters` in the application configuration. These parameters will be marked [FILTERED] in the log.
+You can filter out sensitive request parameters from your log files by appending them to `config.filter_parameters` in the application configuration. These parameters will be marked [FILTERED] in the log.
```ruby
config.filter_parameters << :password
@@ -1086,7 +1086,7 @@ config.filter_parameters << :password
### Redirects Filtering
-Sometimes it's desirable to filter out from log files some sensible locations your application is redirecting to.
+Sometimes it's desirable to filter out from log files some sensitive locations your application is redirecting to.
You can do that by using the `config.filter_redirect` configuration option:
```ruby
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index ee8cf4ade6..673dcfc1d3 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -659,6 +659,23 @@ FROM orders
GROUP BY date(created_at)
```
+### Total of grouped items
+
+To get the total of grouped items on a single query call `count` after the `group`.
+
+```ruby
+Order.group(:status).count
+# => { 'awaiting_approval' => 7, 'paid' => 12 }
+```
+
+The SQL that would be executed would be something like this:
+
+```sql
+SELECT COUNT (*) AS count_all, status AS status
+FROM "orders"
+GROUP BY status
+```
+
Having
------
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 984480c70f..559d3f5e7d 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -580,7 +580,7 @@ config.assets.raise_runtime_errors = false
When this option is true, the asset pipeline will check if all the assets loaded
in your application are included in the `config.assets.precompile` list.
-If `config.assets.digests` is also true, the asset pipeline will require that
+If `config.assets.digest` is also true, the asset pipeline will require that
all requests for assets include digests.
### Turning Digests Off
@@ -589,7 +589,7 @@ You can turn off digests by updating `config/environments/development.rb` to
include:
```ruby
-config.assets.digests = false
+config.assets.digest = false
```
When this option is true, digests will be generated for asset URLs.
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index e365435b50..5138412312 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -96,11 +96,6 @@
url: command_line.html
description: This guide covers the command line tools and rake tasks provided by Rails.
-
- name: Caching with Rails
- work_in_progress: true
- url: caching_with_rails.html
- description: Various caching techniques provided by Rails.
- -
name: Asset Pipeline
url: asset_pipeline.html
description: This guide documents the asset pipeline.
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 530232f3f3..e088abc150 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -163,7 +163,7 @@ of the files and folders that Rails created by default:
| File/Folder | Purpose |
| ----------- | ------- |
|app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.|
-|bin/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.|
+|bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, deploy or run your application.|
|config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).|
|config.ru|Rack configuration for Rack based servers used to start the application.|
|db/|Contains your current database schema, as well as the database migrations.|
@@ -623,6 +623,8 @@ method returns an `ActiveSupport::HashWithIndifferentAccess` object, which
allows you to access the keys of the hash using either strings or symbols. In
this situation, the only parameters that matter are the ones from the form.
+TIP: Ensure you have a firm grasp of the `params` method, as you'll use it fairly regularly. Let's consider an example URL: **http://www.example.com/?username=dhh&email=dhh@email.com**. In this URL, `params[:username]` would equal "dhh" and `params[:email]` would equal "dhh@email.com".
+
If you re-submit the form one more time you'll now no longer get the missing
template error. Instead, you'll see something that looks like the following:
@@ -739,6 +741,8 @@ database columns. In the first line we do just that (remember that
`@article.save` is responsible for saving the model in the database. Finally,
we redirect the user to the `show` action, which we'll define later.
+TIP: You might be wondering why the `A` in `Article.new` is capitalized above, whereas most other references to articles in this guide have used lowercase. In this context, we are referring to the class named `Article` that is defined in `\models\article.rb`. Class names in Ruby must begin with a capital letter.
+
TIP: As we'll see later, `@article.save` returns a boolean indicating whether
the article was saved or not.
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 0eba3af6e8..8340d6807f 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -860,6 +860,24 @@ en:
subject: "Welcome to Rails Guides!"
```
+To send parameters to interpolation use the `default_i18n_subject` method on the mailer.
+
+```ruby
+# user_mailer.rb
+class UserMailer < ActionMailer::Base
+ def welcome(user)
+ mail(to: user.email, subject: default_i18n_subject(user: user.name))
+ end
+end
+```
+
+```yaml
+en:
+ user_mailer:
+ welcome:
+ subject: "%{user}, welcome to Rails Guides!"
+```
+
### Overview of Other Built-In Methods that Provide I18n Support
Rails uses fixed strings and other localizations, such as format strings and other format information in a couple of helpers. Here's a brief overview.
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 05c831ac0d..6f8584b3b7 100644
--- a/guides/source/maintenance_policy.md
+++ b/guides/source/maintenance_policy.md
@@ -12,13 +12,13 @@ Rails follows a shifted version of [semver](http://semver.org/):
**Patch `Z`**
Only bug fixes, no API changes, no new features.
-Except as necessary for security fixes or deprecations.
+Except as necessary for security fixes.
**Minor `Y`**
New features, may contain API changes (Serve as major versions of Semver).
-Breaking changes are paired with deprecation notices in patch level release of
-the last minor version level.
+Breaking changes are paired with deprecation notices in the previous minor
+or major release.
**Major `X`**
diff --git a/guides/source/migrations.md b/guides/source/migrations.md
index 6742c05946..31e314c69b 100644
--- a/guides/source/migrations.md
+++ b/guides/source/migrations.md
@@ -415,6 +415,29 @@ end
removes the `description` and `name` columns, creates a `part_number` string
column and adds an index on it. Finally it renames the `upccode` column.
+### Changing Columns
+
+Like the `remove_column` and `add_column` Rails provides the `change_column`
+migration method.
+
+```ruby
+change_column :products, :part_number, :text
+```
+
+This changes the column `part_number` on products table to be a `:text` field.
+
+Besides `change_column`, the `change_column_null` and `change_column_default`
+methods are used specifically to change the null and default values of a
+column.
+
+```ruby
+change_column_null :products, :name, false
+change_column_default :products, :approved, false
+```
+
+This sets `:name` field on products to a `NOT NULL` column and the default
+value of the `:approved` field to false.
+
### When Helpers aren't Enough
If the helpers provided by Active Record aren't enough you can use the `execute`
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 4149146c4c..bac4b63c75 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -175,10 +175,10 @@ class ArticleTest < ActiveSupport::TestCase
The `ArticleTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `ArticleTest` thus has all the methods available from `ActiveSupport::TestCase`. You'll see those methods a little later in this guide.
-Any method defined within a class inherited from `MiniTest::Unit::TestCase`
-(which is the superclass of `ActiveSupport::TestCase`) that begins with `test` (case sensitive) is simply called a test. So, `test_password`, `test_valid_password` and `testValidPassword` all are legal test names and are run automatically when the test case is run.
+Any method defined within a class inherited from `Minitest::Test`
+(which is the superclass of `ActiveSupport::TestCase`) that begins with `test_` (case sensitive) is simply called a test. So, `test_password` and `test_valid_password` are legal test names and are run automatically when the test case is run.
-Rails adds a `test` method that takes a test name and a block. It generates a normal `MiniTest::Unit` test with method names prefixed with `test_`. So,
+Rails adds a `test` method that takes a test name and a block. It generates a normal `Minitest::Unit` test with method names prefixed with `test_`. So,
```ruby
test "the truth" do
@@ -393,7 +393,7 @@ NOTE: Creating your own assertions is an advanced topic that we won't cover in t
### Rails Specific Assertions
-Rails adds some custom assertions of its own to the `test/unit` framework:
+Rails adds some custom assertions of its own to the `minitest` framework:
| Assertion | Purpose |
| --------------------------------------------------------------------------------- | ------- |
@@ -788,16 +788,12 @@ when you initiate a Rails project.
| `rake test:all:db` | Runs all tests quickly by merging all types and resetting db |
-Brief Note About `MiniTest`
+Brief Note About `Minitest`
-----------------------------
-Ruby ships with a vast Standard Library for all common use-cases including testing. Ruby 1.8 provided `Test::Unit`, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in `Test::Unit::Assertions`. The class `ActiveSupport::TestCase` which we have been using in our unit and functional tests extends `Test::Unit::TestCase`, allowing
-us to use all of the basic assertions in our tests.
+Ruby ships with a vast Standard Library for all common use-cases including testing. Since version 1.9, Ruby provides `Minitest`, a framework for testing. All the basic assertions such as `assert_equal` discussed above are actually defined in `Minitest::Assertions`. The classes `ActiveSupport::TestCase`, `ActionController::TestCase`, `ActionMailer::TestCase`, `ActionView::TestCase` and `ActionDispatch::IntegrationTest` - which we have been inheriting in our test classes - include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests.
-Ruby 1.9 introduced `MiniTest`, an updated version of `Test::Unit` which provides a backwards compatible API for `Test::Unit`. You could also use `MiniTest` in Ruby 1.8 by installing the `minitest` gem.
-
-NOTE: For more information on `Test::Unit`, refer to [test/unit Documentation](http://ruby-doc.org/stdlib/libdoc/test/unit/rdoc/)
-For more information on `MiniTest`, refer to [Minitest](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/minitest/unit/rdoc/)
+NOTE: For more information on `Minitest`, refer to [Minitest](http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest.html)
Setup and Teardown
------------------
@@ -1041,7 +1037,7 @@ access to Rails' helper methods such as `link_to` or `pluralize`.
Other Testing Approaches
------------------------
-The built-in `test/unit` based testing is not the only way to test Rails applications. Rails developers have come up with a wide variety of other approaches and aids for testing, including:
+The built-in `minitest` based testing is not the only way to test Rails applications. Rails developers have come up with a wide variety of other approaches and aids for testing, including:
* [NullDB](http://avdi.org/projects/nulldb/), a way to speed up testing by avoiding database use.
* [Factory Girl](https://github.com/thoughtbot/factory_girl/tree/master), a replacement for fixtures.
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index eab5779533..03d1f2a3a0 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -720,17 +720,18 @@ config.assets.js_compressor = :uglifier
Upgrading from Rails 3.1 to Rails 3.2
-------------------------------------
-If your application is currently on any version of Rails older than 3.1.x, you should upgrade to Rails 3.1 before attempting an update to Rails 3.2.
+If your application is currently on any version of Rails older than 3.1.x, you
+should upgrade to Rails 3.1 before attempting an update to Rails 3.2.
-The following changes are meant for upgrading your application to Rails 3.2.17,
-the last 3.2.x version of Rails.
+The following changes are meant for upgrading your application to the latest
+3.2.x version of Rails.
### Gemfile
Make the following changes to your `Gemfile`.
```ruby
-gem 'rails', '3.2.17'
+gem 'rails', '3.2.18'
group :assets do
gem 'sass-rails', '~> 3.2.6'
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 8a41f160fa..3350b1a4b2 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,11 @@
+* Invalid `bin/rails generate` commands will now show spelling suggestions.
+
+ *Richard Schneeman*
+
+* Add `bin/setup` script to bootstrap an application.
+
+ *Yves Senn*
+
* Replace double quotes with single quotes while adding an entry into Gemfile.
*Alexander Belaev*
diff --git a/railties/README.rdoc b/railties/README.rdoc
index 6248b5feed..a25658668c 100644
--- a/railties/README.rdoc
+++ b/railties/README.rdoc
@@ -31,7 +31,11 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 04ce38f841..bf2390cb7e 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -156,11 +156,20 @@ module Rails
args << "--help" if args.empty? && klass.arguments.any? { |a| a.required? }
klass.start(args, config)
else
- puts "Could not find generator '#{namespace}'. Please choose a generator below."
- print_generators
+ options = sorted_groups.map(&:last).flatten
+ suggestions = options.sort_by {|suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3)
+ msg = "Could not find generator '#{namespace}'. "
+ msg << "Maybe you meant #{ suggestions.map {|s| "'#{s}'"}.join(" or ") }\n"
+ msg << "Run `rails generate --help` for more options."
+ puts msg
end
end
+ # Returns an array of generator namespaces that are hidden.
+ # Generator namespaces may be hidden for a variety of reasons.
+ # Some are aliased such as "rails:migration" and can be
+ # invoked with the shorter "migration", others are private to other generators
+ # such as "css:scaffold".
def self.hidden_namespaces
@hidden_namespaces ||= begin
orm = options[:rails][:orm]
@@ -215,31 +224,71 @@ module Rails
print_generators
end
- def self.print_generators
+ def self.public_namespaces
lookup!
+ subclasses.map { |k| k.namespace }
+ end
- namespaces = subclasses.map{ |k| k.namespace }
- namespaces.sort!
+ def self.print_generators
+ sorted_groups.each { |b, n| print_list(b, n) }
+ end
+ def self.sorted_groups
+ namespaces = public_namespaces
+ namespaces.sort!
groups = Hash.new { |h,k| h[k] = [] }
namespaces.each do |namespace|
base = namespace.split(':').first
groups[base] << namespace
end
- # Print Rails defaults first.
rails = groups.delete("rails")
rails.map! { |n| n.sub(/^rails:/, '') }
rails.delete("app")
rails.delete("plugin")
- print_list("rails", rails)
hidden_namespaces.each { |n| groups.delete(n.to_s) }
- groups.sort.each { |b, n| print_list(b, n) }
+ [["rails", rails]] + groups.sort.to_a
end
protected
+ # This code is based directly on the Text gem implementation
+ # Returns a value representing the "cost" of transforming str1 into str2
+ def self.levenshtein_distance str1, str2
+ s = str1
+ t = str2
+ n = s.length
+ m = t.length
+ max = n/2
+
+ return m if (0 == n)
+ return n if (0 == m)
+ return n if (n - m).abs > max
+
+ d = (0..m).to_a
+ x = nil
+
+ str1.each_char.each_with_index do |char1,i|
+ e = i+1
+
+ str2.each_char.each_with_index do |char2,j|
+ cost = (char1 == char2) ? 0 : 1
+ x = [
+ d[j+1] + 1, # insertion
+ e + 1, # deletion
+ d[j] + cost # substitution
+ ].min
+ d[j] = e
+ e = x
+ end
+
+ d[m] = x
+ end
+
+ return x
+ end
+
# Prints a list of generators.
def self.print_list(base, namespaces) #:nodoc:
namespaces = namespaces.reject do |n|
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup
new file mode 100644
index 0000000000..0e22b3fa5c
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup
@@ -0,0 +1,28 @@
+require 'pathname'
+
+# path to your application root.
+APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+
+Dir.chdir APP_ROOT do
+ # This script is a starting point to setup your application.
+ # Add necessary setup steps to this file:
+
+ puts "== Installing dependencies =="
+ system "gem install bundler --conservative"
+ system "bundle check || bundle install"
+
+ # puts "\n== Copying sample files =="
+ # unless File.exist?("config/database.yml")
+ # system "cp config/database.yml.sample config/database.yml"
+ # end
+
+ puts "\n== Preparing database =="
+ system "bin/rake db:setup"
+
+ puts "\n== Removing old logs and tempfiles =="
+ system "rm -f log/*"
+ system "rm -rf tmp/cache"
+
+ puts "\n== Restarting application server =="
+ system "touch tmp/restart.txt"
+end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 1cbbf62459..74cff08676 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -21,6 +21,7 @@ DEFAULT_APP_FILES = %w(
bin/bundle
bin/rails
bin/rake
+ bin/setup
config/environments
config/initializers
config/locales
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 8e198d5fe1..b998fef42e 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -78,9 +78,12 @@ module SharedGeneratorTests
end
def test_template_raises_an_error_with_invalid_path
- content = capture(:stderr){ run_generator([destination_root, "-m", "non/existent/path"]) }
- assert_match(/The template \[.*\] could not be loaded/, content)
- assert_match(/non\/existent\/path/, content)
+ quietly do
+ content = capture(:stderr){ run_generator([destination_root, "-m", "non/existent/path"]) }
+
+ assert_match(/The template \[.*\] could not be loaded/, content)
+ assert_match(/non\/existent\/path/, content)
+ end
end
def test_template_is_executed_when_supplied
@@ -89,7 +92,7 @@ module SharedGeneratorTests
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)
- assert_match(/It works!/, capture(:stdout) { generator.invoke_all })
+ quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }
end
def test_template_is_executed_when_supplied_an_https_path
@@ -98,7 +101,7 @@ module SharedGeneratorTests
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)
- assert_match(/It works!/, capture(:stdout) { generator.invoke_all })
+ quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }
end
def test_dev_option
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 8d6dbf80c2..0b4edafaca 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -24,7 +24,13 @@ class GeneratorsTest < Rails::Generators::TestCase
name = :unknown
output = capture(:stdout){ Rails::Generators.invoke name }
assert_match "Could not find generator '#{name}'", output
- assert_match "scaffold", output
+ assert_match "`rails generate --help`", output
+ end
+
+ def test_generator_suggestions
+ name = :migrationz
+ output = capture(:stdout){ Rails::Generators.invoke name }
+ assert_match "Maybe you meant 'migration'", output
end
def test_help_when_a_generator_with_required_arguments_is_invoked_without_arguments