aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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_dispatch/http/parameters.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb27
-rw-r--r--actionpack/lib/action_dispatch/journey/router/strexp.rb15
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb351
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb7
-rw-r--r--actionpack/test/abstract/callbacks_test.rb8
-rw-r--r--actionpack/test/controller/routing_test.rb27
-rw-r--r--actionpack/test/dispatch/mapper_test.rb6
-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.rb130
-rw-r--r--actionpack/test/journey/path/pattern_test.rb46
-rw-r--r--actionpack/test/journey/route_test.rb22
-rw-r--r--actionpack/test/journey/router/strexp_test.rb32
-rw-r--r--actionpack/test/journey/router_test.rb66
-rw-r--r--actionpack/test/journey/routes_test.rb10
-rw-r--r--actionview/README.rdoc7
-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--activemodel/lib/active_model/naming.rb5
-rw-r--r--activerecord/CHANGELOG.md34
-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/through_association.rb5
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb31
-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_dumper.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb5
-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.rb21
-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/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/has_and_belongs_to_many_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/dirty_test.rb28
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb2
-rw-r--r--activerecord/test/cases/reflection_test.rb19
-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/yaml_serialization_test.rb36
-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/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.md2
-rw-r--r--guides/source/maintenance_policy.md27
-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.md4
-rw-r--r--railties/README.rdoc6
-rw-r--r--railties/lib/rails/generators.rb5
-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
144 files changed, 1720 insertions, 1056 deletions
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_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/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
index 935442ef66..bb01c087bc 100644
--- a/actionpack/lib/action_dispatch/journey/nodes/node.rb
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -93,6 +93,10 @@ module ActionDispatch
class Star < Unary # :nodoc:
def type; :STAR; end
+
+ def name
+ left.name.tr '*:', ''
+ end
end
class Binary < Node # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index cb0a02c298..3af940a02f 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -1,27 +1,20 @@
+require 'action_dispatch/journey/router/strexp'
+
module ActionDispatch
module Journey # :nodoc:
module Path # :nodoc:
class Pattern # :nodoc:
attr_reader :spec, :requirements, :anchored
+ def self.from_string string
+ new Journey::Router::Strexp.build(string, {}, ["/.?"], true)
+ end
+
def initialize(strexp)
- parser = Journey::Parser.new
-
- @anchored = true
-
- case strexp
- when String
- @spec = parser.parse(strexp)
- @requirements = {}
- @separators = "/.?"
- when Router::Strexp
- @spec = parser.parse(strexp.path)
- @requirements = strexp.requirements
- @separators = strexp.separators.join
- @anchored = strexp.anchor
- else
- raise ArgumentError, "Bad expression: #{strexp}"
- end
+ @spec = strexp.ast
+ @requirements = strexp.requirements
+ @separators = strexp.separators.join
+ @anchored = strexp.anchor
@names = nil
@optional_names = nil
diff --git a/actionpack/lib/action_dispatch/journey/router/strexp.rb b/actionpack/lib/action_dispatch/journey/router/strexp.rb
index f97f1a223e..4b7738f335 100644
--- a/actionpack/lib/action_dispatch/journey/router/strexp.rb
+++ b/actionpack/lib/action_dispatch/journey/router/strexp.rb
@@ -6,18 +6,21 @@ module ActionDispatch
alias :compile :new
end
- attr_reader :path, :requirements, :separators, :anchor
+ attr_reader :path, :requirements, :separators, :anchor, :ast
- def initialize(path, requirements, separators, anchor = true)
+ def self.build(path, requirements, separators, anchor = true)
+ parser = Journey::Parser.new
+ ast = parser.parse path
+ new ast, path, requirements, separators, anchor
+ end
+
+ def initialize(ast, path, requirements, separators, anchor = true)
+ @ast = ast
@path = path
@requirements = requirements
@separators = separators
@anchor = anchor
end
-
- def names
- @path.scan(/:\w+/).map { |s| s.tr(':', '') }
- end
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 4cf7adfb3c..a32e4ee0d1 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,65 +61,104 @@ 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}
- WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
- attr_reader :scope, :path, :options, :requirements, :conditions, :defaults
- attr_reader :to, :default_controller, :default_action
-
- def initialize(set, scope, path, options)
- @set, @scope, @path = set, scope, path
- @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]
-
- @options = normalize_options!(options)
- normalize_path!
- normalize_requirements!
- normalize_conditions!
- normalize_defaults!
+
+ 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, 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
- def normalize_path!
- raise ArgumentError, "path is required" if @path.blank?
- @path = Mapper.normalize_path(@path)
+ def normalize_path!(path, format)
+ path = Mapper.normalize_path(path)
- if required_format?
- @path = "#{@path}.:format"
- elsif optional_format?
- @path = "#{@path}(.:format)"
+ if format == true
+ "#{path}.:format"
+ elsif optional_format?(path, format)
+ "#{path}(.:format)"
+ else
+ path
end
end
- def required_format?
- options[:format] == true
+ def optional_format?(path, format)
+ format != false && !path.include?(':format') && !path.end_with?('/')
end
- def optional_format?
- options[:format] != false && !path.include?(':format') && !path.end_with?('/')
- end
-
- def normalize_options!(options)
- path_without_format = path.sub(/\(\.:format\)$/, '')
-
+ 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 path_without_format.match(WILDCARD_PATH) && options[:format] != false
- options[$1.to_sym] ||= /.+?/
+ if formatted != false
+ path_ast.grep(Journey::Nodes::Star) do |node|
+ options[node.name.to_sym] ||= /.+?/
+ end
end
- if path_without_format.match(':controller')
- raise ArgumentError, ":controller segment is not allowed within a namespace block" if scope[:module]
+ if path_params.include?(:controller)
+ 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:
@@ -127,22 +167,33 @@ module ActionDispatch
options[:controller] ||= /.+?/
end
- options.merge!(default_controller_and_action)
+ if to.respond_to? :call
+ options
+ else
+ options.merge!(default_controller_and_action(path_params, modyoule))
+ end
end
- def normalize_requirements!
- constraints.each do |key, requirement|
- next unless segment_keys.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
@@ -156,31 +207,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)
@@ -189,40 +221,22 @@ module ActionDispatch
end
end
- def normalize_conditions!
- @conditions[:path_info] = path
-
- constraints.each do |key, condition|
- unless segment_keys.include?(key) || key == :controller
- @conditions[key] = condition
- end
- end
-
- required_defaults = []
- options.each do |key, required_default|
- unless segment_keys.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
+ def add_request_method(via, conditions)
+ return if 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)
@@ -236,87 +250,97 @@ module ActionDispatch
end
end
- def default_controller_and_action
- if to.respond_to?(:call)
- { }
- else
- if to.is_a?(String)
- controller, action = to.split('#')
- elsif to.is_a?(Symbol)
- action = to.to_s
- end
+ def default_controller_and_action(path_params, modyoule)
+ controller, action = get_controller_and_action(default_controller,
+ default_action,
+ to,
+ modyoule
+ )
- controller ||= default_controller
- action ||= default_action
-
- if @scope[:module] && !controller.is_a?(Regexp)
- if controller =~ %r{\A/}
- controller = controller[1..-1]
- else
- controller = [@scope[:module], controller].compact.join("/").presence
- end
- end
+ 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."
+ message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
- if controller.is_a?(String) && controller =~ %r{\A/}
- raise ArgumentError, "controller name should not start with a slash"
- end
+ raise ArgumentError, message
+ }
+ end
- controller = controller.to_s unless controller.is_a?(Regexp)
- action = action.to_s unless action.is_a?(Regexp)
+ check_part(:action, action, path_params, hash) { |part|
+ part.is_a?(Regexp) ? part : part.to_s
+ }
+ end
- if controller.blank? && segment_keys.exclude?(:controller)
- message = "Missing :controller key on routes definition, please check your routes."
+ def check_part(name, part, path_params, hash)
+ if part
+ hash[name] = yield(part)
+ else
+ unless path_params.include?(name)
+ message = "Missing :#{name} key on routes definition, please check your routes."
raise ArgumentError, message
end
+ end
+ hash
+ end
+
+ def get_controller_and_action(controller, action, to, modyoule)
+ case to
+ when Symbol
+ ActiveSupport::Deprecation.warn "defining a route where `to` is a symbol is deprecated. Please change \"to: :#{to}\" to \"action: :#{to}\""
+ action = to.to_s
+ when /#/ then controller, action = 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}\""
+ controller = to
+ end
- if action.blank? && segment_keys.exclude?(:action)
- message = "Missing :action key on routes definition, please check your routes."
- raise ArgumentError, message
+ if modyoule && !controller.is_a?(Regexp)
+ if controller =~ %r{\A/}
+ controller = controller[1..-1]
+ else
+ controller = [modyoule, controller].compact.join("/")
end
+ end
+ [controller, action]
+ end
- if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/
- message = "'#{controller}' is not a supported controller name. This can lead to potential routing problems."
- message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
- raise ArgumentError, message
- end
+ def translate_controller(controller)
+ return controller if Regexp === controller
+ return controller.to_s if controller =~ /\A[a-z_0-9][a-z_0-9\/]*\z/
- hash = {}
- hash[:controller] = controller unless controller.blank?
- hash[:action] = action unless action.blank?
- hash
- end
+ 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 segment_keys
- @segment_keys ||= path_pattern.names.map{ |s| s.to_sym }
- end
-
- def path_pattern
- Journey::Path::Pattern.new(strexp)
+ def path_params(ast)
+ ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }
end
- def strexp
- Journey::Router::Strexp.compile(path, requirements, SEPARATORS)
+ def path_ast(path)
+ parser = Journey::Parser.new
+ parser.parse path
end
def dispatcher
@@ -1439,7 +1463,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
@@ -1493,6 +1530,8 @@ module ActionDispatch
def add_route(action, options) # :nodoc:
path = path_for_action(action, options.delete(:path))
+ raise ArgumentError, "path is required" if path.blank?
+
action = action.to_s.dup
if action =~ /^[\w\-\/]+$/
@@ -1507,7 +1546,7 @@ module ActionDispatch
options[:as] = name_for_action(options[:as], action)
end
- mapping = Mapping.new(@set, @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/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 3ca5abf0fd..bdda802195 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -418,7 +418,9 @@ module ActionDispatch
"http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
end
- path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
+ path = conditions.delete :path_info
+ ast = conditions.delete :parsed_path_info
+ path = build_path(path, ast, requirements, SEPARATORS, anchor)
conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
route = @set.add_route(app, path, conditions, defaults, name)
@@ -426,8 +428,9 @@ module ActionDispatch
route
end
- def build_path(path, requirements, separators, anchor)
+ def build_path(path, ast, requirements, separators, anchor)
strexp = Journey::Router::Strexp.new(
+ ast,
path,
requirements,
SEPARATORS,
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/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 58457b0c28..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 FakeSet.new, {}, '/store/:name(*rest)', options
+ m = Mapper::Mapping.build({}, '/store/:name(*rest)', options)
_, _, requirements, _ = m.to_route
assert_equal(/.+?/, requirements[:rest])
end
@@ -72,7 +72,7 @@ module ActionDispatch
mapper = Mapper.new fakeset
mapper.get '/*path/foo/:bar', :to => 'pages#show'
assert_equal '/*path/foo/:bar(.:format)', fakeset.conditions.first[:path_info]
- assert_nil fakeset.requirements.first[:path]
+ assert_equal(/.+?/, fakeset.requirements.first[:path])
end
def test_map_wildcard_with_multiple_wildcard
@@ -80,7 +80,7 @@ module ActionDispatch
mapper = Mapper.new fakeset
mapper.get '/*foo/*bar', :to => 'pages#show'
assert_equal '/*foo/*bar(.:format)', fakeset.conditions.first[:path_info]
- assert_nil fakeset.requirements.first[:foo]
+ assert_equal(/.+?/, fakeset.requirements.first[:foo])
assert_equal(/.+?/, fakeset.requirements.first[:bar])
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 c9777ae71f..778dbfc74d 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -99,6 +99,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_namespace_without_controller_segment
+ draw do
+ namespace :admin do
+ get 'hello/:controllers/:action'
+ end
+ end
+ get '/admin/hello/foo/new'
+ assert_equal 'foo', @request.params["controllers"]
+ end
+
def test_session_singleton_resource
draw do
resource :session do
@@ -351,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
@@ -720,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
@@ -1178,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
@@ -1425,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
@@ -1482,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
@@ -2144,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
@@ -2232,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/
@@ -2970,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
@@ -3137,6 +3165,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/foo', foo_root_path
end
+ def test_namespace_as_controller
+ draw do
+ namespace :foo do
+ get '/', to: '/bar#index', as: 'root'
+ end
+ end
+
+ get '/foo'
+ assert_equal 'bar#index', @response.body
+ assert_equal '/foo', foo_root_path
+ end
+
def test_trailing_slash
draw do
resources :streams
@@ -3217,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
@@ -3481,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)
@@ -3489,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)
@@ -3546,6 +3640,16 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
assert_match "'Admin::StorageFiles' is not a supported controller name", e.message
end
+
+ def test_warn_with_ruby_constant_syntax_no_colons
+ e = assert_raise(ArgumentError) do
+ draw do
+ resources :storage_files, :controller => 'Admin'
+ end
+ end
+
+ assert_match "'Admin' is not a supported controller name", e.message
+ end
end
class TestDefaultScope < ActionDispatch::IntegrationTest
@@ -3987,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/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb
index ce02104181..9dfdfc23ed 100644
--- a/actionpack/test/journey/path/pattern_test.rb
+++ b/actionpack/test/journey/path/pattern_test.rb
@@ -18,7 +18,7 @@ module ActionDispatch
'/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar\Z},
}.each do |path, expected|
define_method(:"test_to_regexp_#{path}") do
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
path,
{ :controller => /.+/ },
["/", ".", "?"]
@@ -41,7 +41,7 @@ module ActionDispatch
'/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar},
}.each do |path, expected|
define_method(:"test_to_non_anchored_regexp_#{path}") do
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
path,
{ :controller => /.+/ },
["/", ".", "?"],
@@ -65,7 +65,7 @@ module ActionDispatch
'/:controller/*foo/bar' => %w{ controller foo },
}.each do |path, expected|
define_method(:"test_names_#{path}") do
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
path,
{ :controller => /.+/ },
["/", ".", "?"]
@@ -75,12 +75,8 @@ module ActionDispatch
end
end
- def test_to_raise_exception_with_bad_expression
- assert_raise(ArgumentError, "Bad expression: []") { Pattern.new [] }
- end
-
def test_to_regexp_with_extended_group
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/:name',
{ :name => /
#ROFL
@@ -101,13 +97,13 @@ module ActionDispatch
['/:foo(/:bar)', %w{ bar }],
['/:foo(/:bar)/:lol(/:baz)', %w{ bar baz }],
].each do |pattern, list|
- path = Pattern.new pattern
+ path = Pattern.from_string pattern
assert_equal list.sort, path.optional_names.sort
end
end
def test_to_regexp_match_non_optional
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/:name',
{ :name => /\d+/ },
["/", ".", "?"]
@@ -118,7 +114,7 @@ module ActionDispatch
end
def test_to_regexp_with_group
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/:name',
{ :name => /(tender|love)/ },
["/", ".", "?"]
@@ -131,7 +127,7 @@ module ActionDispatch
def test_ast_sets_regular_expressions
requirements = { :name => /(tender|love)/, :value => /./ }
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/:name/:value',
requirements,
["/", ".", "?"]
@@ -148,7 +144,7 @@ module ActionDispatch
end
def test_match_data_with_group
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/:name',
{ :name => /(tender|love)/ },
["/", ".", "?"]
@@ -160,7 +156,7 @@ module ActionDispatch
end
def test_match_data_with_multi_group
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/:name/:id',
{ :name => /t(((ender|love)))()/ },
["/", ".", "?"]
@@ -175,7 +171,7 @@ module ActionDispatch
def test_star_with_custom_re
z = /\d+/
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/*foo',
{ :foo => z },
["/", ".", "?"]
@@ -185,7 +181,7 @@ module ActionDispatch
end
def test_insensitive_regexp_with_group
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/:name/aaron',
{ :name => /(tender|love)/i },
["/", ".", "?"]
@@ -197,7 +193,7 @@ module ActionDispatch
end
def test_to_regexp_with_strexp
- strexp = Router::Strexp.new('/:controller', { }, ["/", ".", "?"])
+ strexp = Router::Strexp.build('/:controller', { }, ["/", ".", "?"])
path = Pattern.new strexp
x = %r{\A/([^/.?]+)\Z}
@@ -205,20 +201,20 @@ module ActionDispatch
end
def test_to_regexp_defaults
- path = Pattern.new '/:controller(/:action(/:id))'
+ path = Pattern.from_string '/:controller(/:action(/:id))'
expected = %r{\A/([^/.?]+)(?:/([^/.?]+)(?:/([^/.?]+))?)?\Z}
assert_equal expected, path.to_regexp
end
def test_failed_match
- path = Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Pattern.from_string '/:controller(/:action(/:id(.:format)))'
uri = 'content'
assert_not path =~ uri
end
def test_match_controller
- path = Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Pattern.from_string '/:controller(/:action(/:id(.:format)))'
uri = '/content'
match = path =~ uri
@@ -230,7 +226,7 @@ module ActionDispatch
end
def test_match_controller_action
- path = Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Pattern.from_string '/:controller(/:action(/:id(.:format)))'
uri = '/content/list'
match = path =~ uri
@@ -242,7 +238,7 @@ module ActionDispatch
end
def test_match_controller_action_id
- path = Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Pattern.from_string '/:controller(/:action(/:id(.:format)))'
uri = '/content/list/10'
match = path =~ uri
@@ -254,7 +250,7 @@ module ActionDispatch
end
def test_match_literal
- path = Path::Pattern.new "/books(/:action(.:format))"
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
uri = '/books'
match = path =~ uri
@@ -264,7 +260,7 @@ module ActionDispatch
end
def test_match_literal_with_action
- path = Path::Pattern.new "/books(/:action(.:format))"
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
uri = '/books/list'
match = path =~ uri
@@ -274,7 +270,7 @@ module ActionDispatch
end
def test_match_literal_with_action_and_format
- path = Path::Pattern.new "/books(/:action(.:format))"
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
uri = '/books/list.rss'
match = path =~ uri
diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb
index cbe6284714..21d867aca0 100644
--- a/actionpack/test/journey/route_test.rb
+++ b/actionpack/test/journey/route_test.rb
@@ -5,7 +5,7 @@ module ActionDispatch
class TestRoute < ActiveSupport::TestCase
def test_initialize
app = Object.new
- path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
defaults = {}
route = Route.new("name", app, path, {}, defaults)
@@ -16,7 +16,7 @@ module ActionDispatch
def test_route_adds_itself_as_memo
app = Object.new
- path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
defaults = {}
route = Route.new("name", app, path, {}, defaults)
@@ -26,21 +26,21 @@ module ActionDispatch
end
def test_ip_address
- path = Path::Pattern.new '/messages/:id(.:format)'
+ path = Path::Pattern.from_string '/messages/:id(.:format)'
route = Route.new("name", nil, path, {:ip => '192.168.1.1'},
{ :controller => 'foo', :action => 'bar' })
assert_equal '192.168.1.1', route.ip
end
def test_default_ip
- path = Path::Pattern.new '/messages/:id(.:format)'
+ path = Path::Pattern.from_string '/messages/:id(.:format)'
route = Route.new("name", nil, path, {},
{ :controller => 'foo', :action => 'bar' })
assert_equal(//, route.ip)
end
def test_format_with_star
- path = Path::Pattern.new '/:controller/*extra'
+ path = Path::Pattern.from_string '/:controller/*extra'
route = Route.new("name", nil, path, {},
{ :controller => 'foo', :action => 'bar' })
assert_equal '/foo/himom', route.format({
@@ -50,7 +50,7 @@ module ActionDispatch
end
def test_connects_all_match
- path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
route = Route.new("name", nil, path, {:action => 'bar'}, { :controller => 'foo' })
assert_equal '/foo/bar/10', route.format({
@@ -61,21 +61,21 @@ module ActionDispatch
end
def test_extras_are_not_included_if_optional
- path = Path::Pattern.new '/page/:id(/:action)'
+ path = Path::Pattern.from_string '/page/:id(/:action)'
route = Route.new("name", nil, path, { }, { :action => 'show' })
assert_equal '/page/10', route.format({ :id => 10 })
end
def test_extras_are_not_included_if_optional_with_parameter
- path = Path::Pattern.new '(/sections/:section)/pages/:id'
+ path = Path::Pattern.from_string '(/sections/:section)/pages/:id'
route = Route.new("name", nil, path, { }, { :action => 'show' })
assert_equal '/pages/10', route.format({:id => 10})
end
def test_extras_are_not_included_if_optional_parameter_is_nil
- path = Path::Pattern.new '(/sections/:section)/pages/:id'
+ path = Path::Pattern.from_string '(/sections/:section)/pages/:id'
route = Route.new("name", nil, path, { }, { :action => 'show' })
assert_equal '/pages/10', route.format({:id => 10, :section => nil})
@@ -85,10 +85,10 @@ module ActionDispatch
constraints = {:required_defaults => [:controller, :action]}
defaults = {:controller=>"pages", :action=>"show"}
- path = Path::Pattern.new "/page/:id(/:action)(.:format)"
+ path = Path::Pattern.from_string "/page/:id(/:action)(.:format)"
specific = Route.new "name", nil, path, constraints, defaults
- path = Path::Pattern.new "/:controller(/:action(/:id))(.:format)"
+ path = Path::Pattern.from_string "/:controller(/:action(/:id))(.:format)"
generic = Route.new "name", nil, path, constraints
knowledge = {:id=>20, :controller=>"pages", :action=>"show"}
diff --git a/actionpack/test/journey/router/strexp_test.rb b/actionpack/test/journey/router/strexp_test.rb
deleted file mode 100644
index 7ccdfb7b4d..0000000000
--- a/actionpack/test/journey/router/strexp_test.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'abstract_unit'
-
-module ActionDispatch
- module Journey
- class Router
- class TestStrexp < ActiveSupport::TestCase
- def test_many_names
- exp = Strexp.new(
- "/:controller(/:action(/:id(.:format)))",
- {:controller=>/.+?/},
- ["/", ".", "?"],
- true)
-
- assert_equal ["controller", "action", "id", "format"], exp.names
- end
-
- def test_names
- {
- "/bar(.:format)" => %w{ format },
- ":format" => %w{ format },
- ":format-" => %w{ format },
- ":format0" => %w{ format0 },
- ":format1,:format2" => %w{ format1 format2 },
- }.each do |string, expected|
- exp = Strexp.new(string, {}, ["/", ".", "?"])
- assert_equal expected, exp.names
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index f298808be8..e092432b01 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -32,7 +32,7 @@ module ActionDispatch
def test_dashes
router = Router.new(routes)
- exp = Router::Strexp.new '/foo-bar-baz', {}, ['/.?']
+ exp = Router::Strexp.build '/foo-bar-baz', {}, ['/.?']
path = Path::Pattern.new exp
routes.add_route nil, path, {}, {:id => nil}, {}
@@ -49,7 +49,7 @@ module ActionDispatch
router = Router.new(routes)
#match the escaped version of /ほげ
- exp = Router::Strexp.new '/%E3%81%BB%E3%81%92', {}, ['/.?']
+ exp = Router::Strexp.build '/%E3%81%BB%E3%81%92', {}, ['/.?']
path = Path::Pattern.new exp
routes.add_route nil, path, {}, {:id => nil}, {}
@@ -68,7 +68,7 @@ module ActionDispatch
requirements = { :hello => /world/ }
- exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
+ exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
path = Path::Pattern.new exp
routes.add_route nil, path, requirements, {:id => nil}, {}
@@ -88,7 +88,7 @@ module ActionDispatch
requirements = { :hello => /mom/ }
- exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
+ exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
path = Path::Pattern.new exp
router.routes.add_route nil, path, requirements, {:id => nil}, {}
@@ -115,7 +115,7 @@ module ActionDispatch
def test_request_class_overrides_path_info
router = Router.new(routes)
- exp = Router::Strexp.new '/bar', {}, ['/.?']
+ exp = Router::Strexp.build '/bar', {}, ['/.?']
path = Path::Pattern.new exp
routes.add_route nil, path, {}, {}, {}
@@ -133,8 +133,8 @@ module ActionDispatch
def test_regexp_first_precedence
add_routes @router, [
- Router::Strexp.new("/whois/:domain", {:domain => /\w+\.[\w\.]+/}, ['/', '.', '?']),
- Router::Strexp.new("/whois/:id(.:format)", {}, ['/', '.', '?'])
+ Router::Strexp.build("/whois/:domain", {:domain => /\w+\.[\w\.]+/}, ['/', '.', '?']),
+ Router::Strexp.build("/whois/:id(.:format)", {}, ['/', '.', '?'])
]
env = rails_env 'PATH_INFO' => '/whois/example.com'
@@ -152,7 +152,7 @@ module ActionDispatch
def test_required_parts_verified_are_anchored
add_routes @router, [
- Router::Strexp.new("/foo/:id", { :id => /\d/ }, ['/', '.', '?'], false)
+ Router::Strexp.build("/foo/:id", { :id => /\d/ }, ['/', '.', '?'], false)
]
assert_raises(ActionController::UrlGenerationError) do
@@ -162,7 +162,7 @@ module ActionDispatch
def test_required_parts_are_verified_when_building
add_routes @router, [
- Router::Strexp.new("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
+ Router::Strexp.build("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
]
path, _ = @formatter.generate(nil, { :id => '10' }, { })
@@ -175,7 +175,7 @@ module ActionDispatch
def test_only_required_parts_are_verified
add_routes @router, [
- Router::Strexp.new("/foo(/:id)", {:id => /\d/}, ['/', '.', '?'], false)
+ Router::Strexp.build("/foo(/:id)", {:id => /\d/}, ['/', '.', '?'], false)
]
path, _ = @formatter.generate(nil, { :id => '10' }, { })
@@ -190,7 +190,7 @@ module ActionDispatch
def test_knows_what_parts_are_missing_from_named_route
route_name = "gorby_thunderhorse"
- pattern = Router::Strexp.new("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
+ pattern = Router::Strexp.build("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
path = Path::Pattern.new pattern
@router.routes.add_route nil, path, {}, {}, route_name
@@ -213,7 +213,7 @@ module ActionDispatch
route_set = Routing::RouteSet.new
mapper = Routing::Mapper.new route_set
- strexp = Router::Strexp.new("/", {}, ['/', '.', '?'], false)
+ strexp = Router::Strexp.build("/", {}, ['/', '.', '?'], false)
path = Path::Pattern.new strexp
app = lambda { |env| [200, {}, ['success!']] }
mapper.get '/weblog', :to => app
@@ -225,7 +225,7 @@ module ActionDispatch
end
def test_defaults_merge_correctly
- path = Path::Pattern.new '/foo(/:id)'
+ path = Path::Pattern.from_string '/foo(/:id)'
@router.routes.add_route nil, path, {}, {:id => nil}, {}
env = rails_env 'PATH_INFO' => '/foo/10'
@@ -241,7 +241,7 @@ module ActionDispatch
def test_recognize_with_unbound_regexp
add_routes @router, [
- Router::Strexp.new("/foo", { }, ['/', '.', '?'], false)
+ Router::Strexp.build("/foo", { }, ['/', '.', '?'], false)
]
env = rails_env 'PATH_INFO' => '/foo/bar'
@@ -254,7 +254,7 @@ module ActionDispatch
def test_bound_regexp_keeps_path_info
add_routes @router, [
- Router::Strexp.new("/foo", { }, ['/', '.', '?'], true)
+ Router::Strexp.build("/foo", { }, ['/', '.', '?'], true)
]
env = rails_env 'PATH_INFO' => '/foo'
@@ -308,7 +308,7 @@ module ActionDispatch
end
def test_nil_path_parts_are_ignored
- path = Path::Pattern.new "/:controller(/:action(.:format))"
+ path = Path::Pattern.from_string "/:controller(/:action(.:format))"
@router.routes.add_route @app, path, {}, {}, {}
params = { :controller => "tasks", :format => nil }
@@ -321,7 +321,7 @@ module ActionDispatch
def test_generate_slash
params = [ [:controller, "tasks"],
[:action, "show"] ]
- str = Router::Strexp.new("/", Hash[params], ['/', '.', '?'], true)
+ str = Router::Strexp.build("/", Hash[params], ['/', '.', '?'], true)
path = Path::Pattern.new str
@router.routes.add_route @app, path, {}, {}, {}
@@ -331,7 +331,7 @@ module ActionDispatch
end
def test_generate_calls_param_proc
- path = Path::Pattern.new '/:controller(/:action)'
+ path = Path::Pattern.from_string '/:controller(/:action)'
@router.routes.add_route @app, path, {}, {}, {}
parameterized = []
@@ -348,7 +348,7 @@ module ActionDispatch
end
def test_generate_id
- path = Path::Pattern.new '/:controller(/:action)'
+ path = Path::Pattern.from_string '/:controller(/:action)'
@router.routes.add_route @app, path, {}, {}, {}
path, params = @formatter.generate(
@@ -358,7 +358,7 @@ module ActionDispatch
end
def test_generate_escapes
- path = Path::Pattern.new '/:controller(/:action)'
+ path = Path::Pattern.from_string '/:controller(/:action)'
@router.routes.add_route @app, path, {}, {}, {}
path, _ = @formatter.generate(nil,
@@ -369,7 +369,7 @@ module ActionDispatch
end
def test_generate_escapes_with_namespaced_controller
- path = Path::Pattern.new '/:controller(/:action)'
+ path = Path::Pattern.from_string '/:controller(/:action)'
@router.routes.add_route @app, path, {}, {}, {}
path, _ = @formatter.generate(
@@ -380,7 +380,7 @@ module ActionDispatch
end
def test_generate_extra_params
- path = Path::Pattern.new '/:controller(/:action)'
+ path = Path::Pattern.from_string '/:controller(/:action)'
@router.routes.add_route @app, path, {}, {}, {}
path, params = @formatter.generate(
@@ -394,7 +394,7 @@ module ActionDispatch
end
def test_generate_uses_recall_if_needed
- path = Path::Pattern.new '/:controller(/:action(/:id))'
+ path = Path::Pattern.from_string '/:controller(/:action(/:id))'
@router.routes.add_route @app, path, {}, {}, {}
path, params = @formatter.generate(
@@ -406,7 +406,7 @@ module ActionDispatch
end
def test_generate_with_name
- path = Path::Pattern.new '/:controller(/:action)'
+ path = Path::Pattern.from_string '/:controller(/:action)'
@router.routes.add_route @app, path, {}, {}, {}
path, params = @formatter.generate(
@@ -423,7 +423,7 @@ module ActionDispatch
'/content/show/10' => { :controller => 'content', :action => 'show', :id => "10" },
}.each do |request_path, expected|
define_method("test_recognize_#{expected.keys.map(&:to_s).join('_')}") do
- path = Path::Pattern.new "/:controller(/:action(/:id))"
+ path = Path::Pattern.from_string "/:controller(/:action(/:id))"
app = Object.new
route = @router.routes.add_route(app, path, {}, {}, {})
@@ -445,7 +445,7 @@ module ActionDispatch
:splat => ['/segment/a/b%20c+d', { :segment => 'segment', :splat => 'a/b c+d' }]
}.each do |name, (request_path, expected)|
define_method("test_recognize_#{name}") do
- path = Path::Pattern.new '/:segment/*splat'
+ path = Path::Pattern.from_string '/:segment/*splat'
app = Object.new
route = @router.routes.add_route(app, path, {}, {}, {})
@@ -463,7 +463,7 @@ module ActionDispatch
end
def test_namespaced_controller
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
"/:controller(/:action(/:id))",
{ :controller => /.+?/ },
["/", ".", "?"]
@@ -489,7 +489,7 @@ module ActionDispatch
end
def test_recognize_literal
- path = Path::Pattern.new "/books(/:action(.:format))"
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
app = Object.new
route = @router.routes.add_route(app, path, {}, {:controller => 'books'})
@@ -506,7 +506,7 @@ module ActionDispatch
end
def test_recognize_head_request_as_get_route
- path = Path::Pattern.new "/books(/:action(.:format))"
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
app = Object.new
conditions = {
:request_method => 'GET'
@@ -525,7 +525,7 @@ module ActionDispatch
end
def test_recognize_cares_about_verbs
- path = Path::Pattern.new "/books(/:action(.:format))"
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
app = Object.new
conditions = {
:request_method => 'GET'
@@ -553,7 +553,11 @@ module ActionDispatch
def add_routes router, paths
paths.each do |path|
- path = Path::Pattern.new path
+ if String === path
+ path = Path::Pattern.from_string path
+ else
+ path = Path::Pattern.new path
+ end
router.routes.add_route @app, path, {}, {}, {}
end
end
diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb
index 25e0321d31..a4efc82b8c 100644
--- a/actionpack/test/journey/routes_test.rb
+++ b/actionpack/test/journey/routes_test.rb
@@ -5,7 +5,7 @@ module ActionDispatch
class TestRoutes < ActiveSupport::TestCase
def test_clear
routes = Routes.new
- exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
+ exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
path = Path::Pattern.new exp
requirements = { :hello => /world/ }
@@ -18,7 +18,7 @@ module ActionDispatch
def test_ast
routes = Routes.new
- path = Path::Pattern.new '/hello'
+ path = Path::Pattern.from_string '/hello'
routes.add_route nil, path, {}, {}, {}
ast = routes.ast
@@ -28,7 +28,7 @@ module ActionDispatch
def test_simulator_changes
routes = Routes.new
- path = Path::Pattern.new '/hello'
+ path = Path::Pattern.from_string '/hello'
routes.add_route nil, path, {}, {}, {}
sim = routes.simulator
@@ -40,8 +40,8 @@ module ActionDispatch
#def add_route app, path, conditions, defaults, name = nil
routes = Routes.new
- one = Path::Pattern.new '/hello'
- two = Path::Pattern.new '/aaron'
+ one = Path::Pattern.from_string '/hello'
+ two = Path::Pattern.from_string '/aaron'
routes.add_route nil, one, {}, {}, 'aaron'
routes.add_route nil, two, {}, {}, 'aaron'
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_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/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 2a428687c5..5219de2606 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -218,10 +218,11 @@ module ActiveModel
# used to retrieve all kinds of naming-related information
# (See ActiveModel::Name for more information).
#
- # class Person < ActiveModel::Model
+ # class Person
+ # include ActiveModel::Model
# end
#
- # Person.model_name # => Person
+ # Person.model_name.name # => "Person"
# Person.model_name.class # => ActiveModel::Name
# Person.model_name.singular # => "person"
# Person.model_name.plural # => "people"
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index c3c61cf50e..7ea3bde2bc 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,37 @@
+* 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`.
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/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..e56a4cc805 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -239,9 +239,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 +258,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 +270,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 +287,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
@@ -355,10 +350,12 @@ module ActiveRecord
# # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
#
# person.column_for_attribute(:nothing)
- # # => nil
+ # # => #<ActiveRecord::ConnectionAdapters::Column:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
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]
+ name = name.to_s
+ self.class.columns_hash.fetch(name) do
+ ConnectionAdapters::Column.new(name, nil, Type::Value.new)
+ end
end
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
@@ -424,7 +421,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 +440,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_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_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..3b0dcbc6a7 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.
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..67570dad3c 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,
@@ -507,6 +511,13 @@ module ActiveRecord
# makes this method very very slow.
return default unless default
+ # TODO: The default extraction is related to the cast-type.
+ # we should probably make a type_map lookup and cast the default-
+ # expression accordingly
+ if oid.type == :enum && default =~ /\A'(.*)'::/
+ return $1
+ end
+
case default
when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
$1
@@ -550,6 +561,8 @@ module ActiveRecord
# JSON
when /\A'(.*)'::json\z/
$1
+ when /\A'(.*)'::money\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/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/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/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/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..b3c02d29cb 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -80,6 +80,25 @@ 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_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/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/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 6915a491b0..8a9d31a7bb 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,29 @@
+* 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/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 f2a2f3c3db..3155c0ab0f 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..d9619bbc21 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.|
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 8f119f36aa..6f8584b3b7 100644
--- a/guides/source/maintenance_policy.md
+++ b/guides/source/maintenance_policy.md
@@ -3,10 +3,29 @@ Maintenance Policy for Ruby on Rails
Support of the Rails framework is divided into four groups: New features, bug
fixes, security issues, and severe security issues. They are handled as
-follows, all versions in x.y.z format
+follows, all versions in `X.Y.Z` format.
--------------------------------------------------------------------------------
+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.
+
+**Minor `Y`**
+
+New features, may contain API changes (Serve as major versions of Semver).
+Breaking changes are paired with deprecation notices in the previous minor
+or major release.
+
+**Major `X`**
+
+New features, will likely contain API changes. The difference between Rails'
+minor and major releases is the magnitude of breaking changes, and usually
+reserved for special occasions.
+
New Features
------------
@@ -20,7 +39,7 @@ Only the latest release series will receive bug fixes. When enough bugs are
fixed and its deemed worthy to release a new gem, this is the branch it happens
from.
-**Currently included series:** 4.1.z, 4.0.z
+**Currently included series:** `4.1.Z`, `4.0.Z`.
Security Issues
---------------
@@ -35,7 +54,7 @@ be built from 1.2.2, and then added to the end of 1-2-stable. This means that
security releases are easy to upgrade to if you're running the latest version
of Rails.
-**Currently included series:** 4.1.z, 4.0.z
+**Currently included series:** `4.1.Z`, `4.0.Z`.
Severe Security Issues
----------------------
@@ -44,7 +63,7 @@ For severe security issues we will provide new versions as above, and also the
last major release series will receive patches and new versions. The
classification of the security issue is judged by the core team.
-**Currently included series:** 4.1.z, 4.0.z, 3.2.z
+**Currently included series:** `4.1.Z`, `4.0.Z`, `3.2.Z`.
Unsupported Release Series
--------------------------
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..b328619646 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,7 @@
+* 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..2a0148fe9d 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -161,6 +161,11 @@ module Rails
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]
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