diff options
40 files changed, 388 insertions, 80 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 394b1473d3..911c916028 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,8 @@ +* Do not discard query parameters that form a hash with the same root key as + the `wrapper_key` for a request using `wrap_parameters`. + + *Josh Jordan* + * Add `:serializer` option for `config.session_store :cookie_store`. This changes default serializer when using `:cookie_store`. diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index c9f1d8dcb4..2ca8955741 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -231,7 +231,12 @@ module ActionController # by the metal call stack. def process_action(*args) if _wrapper_enabled? - wrapped_hash = _wrap_parameters request.request_parameters + if request.parameters[_wrapper_key].present? + wrapped_hash = _extract_parameters(request.parameters) + else + wrapped_hash = _wrap_parameters request.request_parameters + end + wrapped_keys = request.request_parameters.keys wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys) @@ -259,14 +264,16 @@ module ActionController # Returns the list of parameters which will be selected for wrapped. def _wrap_parameters(parameters) - value = if include_only = _wrapper_options.include + { _wrapper_key => _extract_parameters(parameters) } + end + + def _extract_parameters(parameters) + if include_only = _wrapper_options.include parameters.slice(*include_only) else exclude = _wrapper_options.exclude || [] parameters.except(*(exclude + EXCLUDE_PARAMETERS)) end - - { _wrapper_key => value } end # Checks if we should perform parameters wrapping. diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 36dcca2905..9b26845190 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -75,7 +75,6 @@ module ActionDispatch autoload :Parameters autoload :ParameterFilter autoload :FilterParameters - autoload :FilterRedirect autoload :Upload autoload :UploadedFile, 'action_dispatch/http/upload' autoload :URL diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb index 900ce1c646..cd603649c3 100644 --- a/actionpack/lib/action_dispatch/http/filter_redirect.rb +++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb @@ -5,7 +5,8 @@ module ActionDispatch FILTERED = '[FILTERED]'.freeze # :nodoc: def filtered_location - if !location_filter.empty? && location_filter_match? + filters = location_filter + if !filters.empty? && location_filter_match?(filters) FILTERED else location @@ -15,15 +16,15 @@ module ActionDispatch private def location_filter - if request.present? + if request request.env['action_dispatch.redirect_filter'] || [] else [] end end - def location_filter_match? - location_filter.any? do |filter| + def location_filter_match?(filters) + filters.any? do |filter| if String === filter location.include?(filter) elsif Regexp === filter diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 7b2655b2d8..bc13ee00f1 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/module/attribute_accessors' +require 'action_dispatch/http/filter_redirect' require 'monitor' module ActionDispatch # :nodoc: diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index d87e2b85b0..11ccb6cf3b 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -188,6 +188,26 @@ class ParamsWrapperTest < ActionController::TestCase assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }}) end end + + def test_preserves_query_string_params + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + get :parse, { 'user' => { 'username' => 'nixon' } } + assert_parameters( + {'user' => { 'username' => 'nixon' } } + ) + end + end + + def test_empty_parameter_set + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, {} + assert_parameters( + {'user' => { } } + ) + end + end end class NamespacedParamsWrapperTest < ActionController::TestCase diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index 5c729345dc..50712e0830 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -28,6 +28,8 @@ require 'action_view/version' module ActionView extend ActiveSupport::Autoload + ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' + eager_autoload do autoload :Base autoload :Context @@ -54,7 +56,6 @@ module ActionView autoload_at "action_view/template/resolver" do autoload :Resolver autoload :PathResolver - autoload :FileSystemResolver autoload :OptimizedFileSystemResolver autoload :FallbackFileSystemResolver end @@ -81,8 +82,6 @@ module ActionView autoload :TestCase - ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' - def self.eager_load! super ActionView::Helpers.eager_load! diff --git a/actionview/lib/action_view/helpers/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb index eeb0ed94b9..5af92c4ff2 100644 --- a/actionview/lib/action_view/helpers/csrf_helper.rb +++ b/actionview/lib/action_view/helpers/csrf_helper.rb @@ -12,8 +12,11 @@ module ActionView # These are used to generate the dynamic forms that implement non-remote links with # <tt>:method</tt>. # - # Note that regular forms generate hidden fields, and that Ajax calls are whitelisted, - # so they do not use these tags. + # You don't need to use these tags for regular forms as they generate their own hidden fields. + # + # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the + # "X-CSRF-Token" HTTP header. If you are using jQuery with jquery-rails this happens automatically. + # def csrf_meta_tags if protect_against_forgery? [ diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 56dd7a4390..3ccace1274 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -232,6 +232,11 @@ module ActionView # # <div><input value="New" type="submit" /></div> # # </form>" # + # <%= button_to "New", new_articles_path %> + # # => "<form method="post" action="/articles/new" class="button_to"> + # # <div><input value="New" type="submit" /></div> + # # </form>" + # # <%= button_to [:make_happy, @user] do %> # Make happy <strong><%= @user.name %></strong> # <% end %> diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index e07d9b6314..76c9890776 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -1,6 +1,7 @@ require 'thread_safe' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/module/attribute_accessors' +require 'action_view/template/resolver' module ActionView # = Action View Lookup Context diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index deba33510a..7e978e15d2 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -56,6 +56,13 @@ class UrlHelperTest < ActiveSupport::TestCase assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com") end + def test_button_to_with_path + assert_dom_equal( + %{<form method="post" action="/article/Hello" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + button_to("Hello", article_path("Hello".html_safe)) + ) + end + def test_button_to_with_straight_url_and_request_forgery self.request_forgery = true diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 6585808fa2..500d8dc42f 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,7 @@ +* `#to_param` returns `nil` if `#to_key` returns `nil`. Fixes #11399. + + *Yves Senn* + * Ability to specify multiple contexts when defining a validation. Example: diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index 6b0a752566..0a19ef686d 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -62,7 +62,7 @@ module ActiveModel # person = Person.create # person.to_param # => "1" def to_param - persisted? ? to_key.join('-') : nil + (persisted? && key = to_key) ? key.join('-') : nil end # Returns a +string+ identifying the path associated with the object. diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb index 3bb177591d..c5cfbf909d 100644 --- a/activemodel/test/cases/conversion_test.rb +++ b/activemodel/test/cases/conversion_test.rb @@ -24,6 +24,16 @@ class ConversionTest < ActiveModel::TestCase assert_equal "1", Contact.new(id: 1).to_param end + test "to_param returns nil if to_key is nil" do + klass = Class.new(Contact) do + def persisted? + true + end + end + + assert_nil klass.new.to_param + end + test "to_partial_path default implementation returns a string giving a relative path" do assert_equal "contacts/contact", Contact.new.to_partial_path assert_equal "helicopters/helicopter", Helicopter.new.to_partial_path, diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 8c09284e53..272f05eb79 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,31 @@ +* Pass `has_and_belongs_to_many` `:autosave` option to + the underlying `has_many :through` association. + + Fixes #13923. + + *Yves Senn* + +* PostgreSQL implementation of `SchemaStatements#index_name_exists?`. + + The database agnostic implementation does not detect with indexes that are + not supported by the ActiveRecord schema dumper. For example, expressions + indexes would not be detected. + + Fixes #11018. + + *Jonathan Baudanza* + +* Parsing PostgreSQL arrays with empty strings now works correctly. + + Previously, if you tried to parse `{"1","","2","","3"}` the result + would be `["1","2","3"]`, removing the empty strings from the array, + which would be incorrect. Now it will correctly produce `["1","","2","","3"]` + as the result of parsing the above PostgreSQL array. + + Fixes #13907. + + *MaurĂcio Linhares* + * Associations now raise `ArgumentError` on name conflicts. Dangerous association names conflicts include instance or class methods already @@ -856,7 +884,7 @@ *Severin Schoepke* -* `ActiveRecord::Store` works together with PG `hstore` columns. +* `ActiveRecord::Store` works together with PostgreSQL `hstore` columns. Fixes #12452. @@ -1142,7 +1170,7 @@ *Yves Senn* , *Severin Schoepke* -* Fix multidimensional PG arrays containing non-string items. +* Fix multidimensional PostgreSQL arrays containing non-string items. *Yves Senn* @@ -1160,7 +1188,7 @@ *Richard Schneeman* -* Removed redundant override of `xml` column definition for PG, +* Removed redundant override of `xml` column definition for PostgreSQL, in order to use `xml` column type instead of `text`. *Paul Nikitochkin*, *Michael Nikitochkin* diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index f3f77e21c0..b5e21cbede 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -669,11 +669,14 @@ module ActiveRecord # and member posts that use the posts table for STI. In this case, there must be a +type+ # column in the posts table. # + # Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+. + # The +class_name+ of the +attachable+ is passed as a String. + # # class Asset < ActiveRecord::Base # belongs_to :attachable, polymorphic: true # - # def attachable_type=(sType) - # super(sType.to_s.classify.constantize.base_class.to_s) + # def attachable_type=(class_name) + # super(class_name.constantize.base_class.to_s) # end # end # @@ -1582,7 +1585,7 @@ module ActiveRecord hm_options[:through] = middle_reflection.name hm_options[:source] = join_model.right_reflection.name - [:before_add, :after_add, :before_remove, :after_remove].each do |k| + [:before_add, :after_add, :before_remove, :after_remove, :autosave].each do |k| hm_options[k] = options[k] if options.key? k end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 17f056e764..5a0ba9e6b1 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -1,3 +1,5 @@ +require 'active_record/associations/join_helper' + module ActiveRecord module Associations class AssociationScope #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb index 20de8d1982..0b218f2bfd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb @@ -91,8 +91,9 @@ module ActiveRecord end def add_item_to_array(array, current_item, quoted) - if current_item.length == 0 - elsif !quoted && current_item == 'NULL' + return if !quoted && current_item.length == 0 + + if !quoted && current_item == 'NULL' array.push nil else array.push current_item 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 571257f6dd..ae8ede4b42 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -126,6 +126,19 @@ module ActiveRecord SQL end + def index_name_exists?(table_name, index_name, default) + exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 + SELECT COUNT(*) + FROM pg_class t + INNER JOIN pg_index d ON t.oid = d.indrelid + INNER JOIN pg_class i ON d.indexrelid = i.oid + WHERE i.relkind = 'i' + AND i.relname = '#{index_name}' + AND t.relname = '#{table_name}' + AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) ) + SQL + end + # Returns an array of indexes for the given table. def indexes(table_name, name = nil) result = query(<<-SQL, 'SCHEMA') diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 6303fe5ee4..d691192cfd 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -138,12 +138,12 @@ module ActiveRecord # class Post < ActiveRecord::Base # scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) } # end - def arel_table + def arel_table # :nodoc: @arel_table ||= Arel::Table.new(table_name, arel_engine) end # Returns the Arel engine. - def arel_engine + def arel_engine # :nodoc: @arel_engine ||= if Base == self || connection_handler.retrieve_connection_pool(self) self diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 860063426a..077f09b67d 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -824,11 +824,12 @@ module ActiveRecord end # Returns the Arel object associated with the relation. - def arel + def arel # :nodoc: @arel ||= build_arel end - # Like #arel, but ignores the default scope of the model. + private + def build_arel arel = Arel::SelectManager.new(table.engine, table) @@ -854,8 +855,6 @@ module ActiveRecord arel end - private - def symbol_unscoping(scope) if !VALID_UNSCOPING_VALUES.include?(scope) raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index d71e2aa2bb..3090f4478f 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -93,6 +93,18 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert_cycle(:tags, [[['1'], ['2']], [['2'], ['3']]]) end + def test_with_empty_strings + assert_cycle(:tags, [ '1', '2', '', '4', '', '5' ]) + end + + def test_with_multi_dimensional_empty_strings + assert_cycle(:tags, [[['1', '2'], ['', '4'], ['', '5']]]) + end + + def test_with_arbitrary_whitespace + assert_cycle(:tags, [[['1', '2'], [' ', '4'], [' ', '5']]]) + end + def test_multi_dimensional_with_integers assert_cycle(:ratings, [[[1], [7]], [[8], [10]]]) end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 1e59bca6a7..3f7009c1d1 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -246,6 +246,18 @@ class SchemaTest < ActiveRecord::TestCase assert_nothing_raised { with_schema_search_path nil } end + def test_index_name_exists + with_schema_search_path(SCHEMA_NAME) do + assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true) + assert_not @connection.index_name_exists?(TABLE_NAME, 'missing_index', true) + end + end + def test_dump_indexes_for_schema_one do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN) end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 703f805188..7a0c335627 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -1183,15 +1183,15 @@ module AutosaveAssociationOnACollectionAssociationTests end def test_should_default_invalid_error_from_i18n - I18n.backend.store_translations(:en, :activerecord => {:errors => { :models => - { @association_name.to_s.singularize.to_sym => { :blank => "cannot be blank" } } + I18n.backend.store_translations(:en, activerecord: {errors: { models: + { @associated_model_name.to_s.to_sym => { blank: "cannot be blank" } } }}) - @pirate.send(@association_name).build(:name => '') + @pirate.send(@association_name).build(name: '') assert !@pirate.valid? assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"] - assert_equal ["#{@association_name.to_s.titleize} name cannot be blank"], @pirate.errors.full_messages + assert_equal ["#{@association_name.to_s.humanize} name cannot be blank"], @pirate.errors.full_messages assert @pirate.errors[@association_name].empty? ensure I18n.backend = I18n::Backend::Simple.new @@ -1307,6 +1307,7 @@ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase def setup super @association_name = :birds + @associated_model_name = :bird @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @child_1 = @pirate.birds.create(:name => 'Posideons Killer') @@ -1321,12 +1322,30 @@ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::T def setup super + @association_name = :autosaved_parrots + @associated_model_name = :parrot + @habtm = true + + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") + @child_1 = @pirate.parrots.create(name: 'Posideons Killer') + @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne') + end + + include AutosaveAssociationOnACollectionAssociationTests +end + +class TestAutosaveAssociationOnAHasAndBelongsToManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase + self.use_transactional_fixtures = false unless supports_savepoints? + + def setup + super @association_name = :parrots + @associated_model_name = :parrot @habtm = true - @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") - @child_1 = @pirate.parrots.create(:name => 'Posideons Killer') - @child_2 = @pirate.parrots.create(:name => 'Killer bandita Dionne') + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") + @child_1 = @pirate.parrots.create(name: 'Posideons Killer') + @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne') end include AutosaveAssociationOnACollectionAssociationTests diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 9dc26cfd4d..f0ad9ebb8a 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -314,7 +314,9 @@ class NamedScopingTest < ActiveRecord::TestCase non_conflicts.each do |name| assert_nothing_raised do - klass.class_eval { scope name, ->{ where(approved: true) } } + silence_warnings do + klass.class_eval { scope name, ->{ where(approved: true) } } + end end assert_nothing_raised do diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 170fc2ffe3..7bb0caf44b 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -13,6 +13,7 @@ class Pirate < ActiveRecord::Base :after_add => proc {|p,pa| p.ship_log << "after_adding_proc_parrot_#{pa.id || '<new>'}"}, :before_remove => proc {|p,pa| p.ship_log << "before_removing_proc_parrot_#{pa.id}"}, :after_remove => proc {|p,pa| p.ship_log << "after_removing_proc_parrot_#{pa.id}"} + has_and_belongs_to_many :autosaved_parrots, class_name: "Parrot", autosave: true has_many :treasures, :as => :looter has_many :treasure_estimates, :through => :treasures, :source => :price_estimates diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 17890b2668..fa2f2384f9 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,27 @@ +* Deprecate custom `BigDecimal` serialization + + Deprecate the custom `BigDecimal` serialization that is included when requiring + `active_support/all` as a fix for #12467. Let Ruby handle YAML serialization + for `BigDecimal` instead. + + *David Celis* + +* Fix parsing bugs in `XmlMini` + + Symbols or boolean parsing would raise an error for non string values (e.g. + integers). Decimal parsing would fail due to a missing requirement. + + *Birkir A. Barkarson* + +* Maintain the current timezone when calling `wrap_with_time_zone` + + Extend the solution from the fix for #12163 to the general case where `Time` + methods are wrapped with a time zone. + + Fixes #12596. + + *Andrew White* + * Remove behavior that automatically remove the Date/Time stubs, added by `travel` and `travel_to` methods, after each test case. diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 53154aef27..2b7f5943b5 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -452,7 +452,7 @@ module ActiveSupport # Clear the entire cache. Be careful with this method since it could # affect other processes if shared cache is being used. # - # Options are passed to the underlying cache implementation. + # The options hash is passed to the underlying cache implementation. # # All implementations may not support this method. def clear(options = nil) diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb index 39b8cea807..843c592669 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb @@ -1,22 +1,7 @@ require 'bigdecimal' require 'bigdecimal/util' -require 'yaml' class BigDecimal - YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' } - - def encode_with(coder) - string = to_s - coder.represent_scalar(nil, YAML_MAPPING[string] || string) - end - - # Backport this method if it doesn't exist - unless method_defined?(:to_d) - def to_d - self - end - end - DEFAULT_STRING_FORMAT = 'F' def to_formatted_s(*args) if args[0].is_a?(Symbol) diff --git a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb new file mode 100644 index 0000000000..46ba93ead4 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb @@ -0,0 +1,14 @@ +ActiveSupport::Deprecation.warn 'core_ext/big_decimal/yaml_conversions is deprecated and will be removed in the future.' + +require 'bigdecimal' +require 'yaml' +require 'active_support/core_ext/big_decimal/conversions' + +class BigDecimal + YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' } + + def encode_with(coder) + string = to_s + coder.represent_scalar(nil, YAML_MAPPING[string] || string) + end +end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index d459af1778..c25c97cfa8 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -292,12 +292,6 @@ module ActiveSupport end end - def change(options) - new_time = time.change(options) - periods = time_zone.periods_for_local(new_time) - self.class.new(nil, time_zone, new_time, periods.include?(period) ? period : nil) - end - %w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method_name} # def month @@ -396,7 +390,8 @@ module ActiveSupport def wrap_with_time_zone(time) if time.acts_like?(:time) - self.class.new(nil, time_zone, time) + periods = time_zone.periods_for_local(time) + self.class.new(nil, time_zone, time, periods.include?(period) ? period : nil) elsif time.is_a?(Range) wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end) else diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index d082a0a499..009ee4db90 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -1,7 +1,9 @@ require 'time' require 'base64' +require 'bigdecimal' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/date_time/calculations' module ActiveSupport # = XmlMini @@ -56,13 +58,13 @@ module ActiveSupport # TODO use regexp instead of Date.parse unless defined?(PARSING) PARSING = { - "symbol" => Proc.new { |symbol| symbol.to_sym }, + "symbol" => Proc.new { |symbol| symbol.to_s.to_sym }, "date" => Proc.new { |date| ::Date.parse(date) }, "datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc }, "integer" => Proc.new { |integer| integer.to_i }, "float" => Proc.new { |float| float.to_f }, "decimal" => Proc.new { |number| BigDecimal(number) }, - "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) }, + "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) }, "string" => Proc.new { |string| string.to_s }, "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml }, "base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) }, diff --git a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb new file mode 100644 index 0000000000..e634679d20 --- /dev/null +++ b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb @@ -0,0 +1,11 @@ +require 'abstract_unit' + +class BigDecimalYamlConversionsTest < ActiveSupport::TestCase + def test_to_yaml + assert_deprecated { require 'active_support/core_ext/big_decimal/yaml_conversions' } + assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) + assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) + assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml) + assert_match("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) + end +end diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb index b386e55d6c..423a3f2e9d 100644 --- a/activesupport/test/core_ext/bigdecimal_test.rb +++ b/activesupport/test/core_ext/bigdecimal_test.rb @@ -2,18 +2,6 @@ require 'abstract_unit' require 'active_support/core_ext/big_decimal' class BigDecimalTest < ActiveSupport::TestCase - def test_to_yaml - assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) - assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) - assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml) - assert_match("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) - end - - def test_to_d - bd = BigDecimal.new '10' - assert_equal bd, bd.to_d - end - def test_to_s bd = BigDecimal.new '0.01' assert_equal '0.01', bd.to_s diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 8e25f1e2f2..7fe4d4a6b2 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -500,6 +500,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal twz, twz.change(:min => 0) end + def test_round_at_dst_boundary + twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone['Madrid']) + assert_equal twz, twz.round + end + def test_advance assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.advance(:years => 2).inspect diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index d992028323..753effb54e 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -169,4 +169,128 @@ module XmlMiniTest end end end + + class ParsingTest < ActiveSupport::TestCase + def setup + @parsing = ActiveSupport::XmlMini::PARSING + end + + def test_symbol + parser = @parsing['symbol'] + assert_equal :symbol, parser.call('symbol') + assert_equal :symbol, parser.call(:symbol) + assert_equal :'123', parser.call(123) + assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } + end + + def test_date + parser = @parsing['date'] + assert_equal Date.new(2013,11,12), parser.call("2013-11-12T0211Z") + assert_raises(TypeError) { parser.call(1384190018) } + assert_raises(ArgumentError) { parser.call("not really a date") } + end + + def test_datetime + parser = @parsing['datetime'] + assert_equal Time.new(2013,11,12,02,11,00,0), parser.call("2013-11-12T02:11:00Z") + assert_equal DateTime.new(2013,11,12), parser.call("2013-11-12T0211Z") + assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T02:11Z") + assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T11:11+9") + assert_raises(ArgumentError) { parser.call("1384190018") } + end + + def test_integer + parser = @parsing['integer'] + assert_equal 123, parser.call(123) + assert_equal 123, parser.call(123.003) + assert_equal 123, parser.call("123") + assert_equal 0, parser.call("") + assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } + end + + def test_float + parser = @parsing['float'] + assert_equal 123, parser.call("123") + assert_equal 123.003, parser.call("123.003") + assert_equal 123.0, parser.call("123,003") + assert_equal 0.0, parser.call("") + assert_equal 123, parser.call(123) + assert_equal 123.05, parser.call(123.05) + assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } + end + + def test_decimal + parser = @parsing['decimal'] + assert_equal 123, parser.call("123") + assert_equal 123.003, parser.call("123.003") + assert_equal 123.0, parser.call("123,003") + assert_equal 0.0, parser.call("") + assert_equal 123, parser.call(123) + assert_raises(ArgumentError) { parser.call(123.04) } + assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } + end + + def test_boolean + parser = @parsing['boolean'] + [1, true, "1"].each do |value| + assert parser.call(value) + end + + [0, false, "0"].each do |value| + assert_not parser.call(value) + end + end + + def test_string + parser = @parsing['string'] + assert_equal "123", parser.call(123) + assert_equal "123", parser.call("123") + assert_equal "[]", parser.call("[]") + assert_equal "[]", parser.call([]) + assert_equal "{}", parser.call({}) + assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } + end + + def test_yaml + yaml = <<YAML +product: + - sku : BL394D + quantity : 4 + description : Basketball +YAML + expected = { + "product"=> [ + {"sku"=>"BL394D", "quantity"=>4, "description"=>"Basketball"} + ] + } + parser = @parsing['yaml'] + assert_equal(expected, parser.call(yaml)) + assert_equal({1 => 'test'}, parser.call({1 => 'test'})) + assert_equal({"1 => 'test'"=>nil}, parser.call("{1 => 'test'}")) + end + + def test_base64Binary_and_binary + base64 = <<BASE64 +TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz +IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg +dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu +dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo +ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4= +BASE64 + expected_base64 = <<EXPECTED +Man is distinguished, not only by his reason, but by this singular passion from +other animals, which is a lust of the mind, that by a perseverance of delight +in the continued and indefatigable generation of knowledge, exceeds the short +vehemence of any carnal pleasure. +EXPECTED + + parser = @parsing['base64Binary'] + assert_equal expected_base64.gsub(/\n/," ").strip, parser.call(base64) + parser.call("NON BASE64 INPUT") + + parser = @parsing['binary'] + assert_equal expected_base64.gsub(/\n/," ").strip, parser.call(base64, 'encoding' => 'base64') + assert_equal "IGNORED INPUT", parser.call("IGNORED INPUT", {}) + end + end end diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb index 760b196abd..169453400f 100644 --- a/guides/rails_guides/helpers.rb +++ b/guides/rails_guides/helpers.rb @@ -1,3 +1,5 @@ +require 'yaml' + module RailsGuides module Helpers def guide(name, url, options = {}, &block) diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 0aa0b49b15..bdb1a61bfb 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -84,21 +84,25 @@ Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose dollar sign `$` should be run in the command line. Verify that you have a current version of Ruby installed: +TIP. A number of tools exist to help you quickly install Ruby and Ruby +on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org), +while Mac OS X users can use [Rails One Click](http://railsoneclick.com). + ```bash $ ruby -v ruby 2.0.0p353 ``` +If you don't have Ruby installed have a look at +[ruby-lang.org](https://www.ruby-lang.org/en/downloads/) for possible ways to +install Ruby on your platform. + To install Rails, use the `gem install` command provided by RubyGems: ```bash $ gem install rails ``` -TIP. A number of tools exist to help you quickly install Ruby and Ruby -on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org), -while Mac OS X users can use [Rails One Click](http://railsoneclick.com). - To verify that you have everything installed correctly, you should be able to run the following: diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 8dfb17a681..d72717fa3b 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -214,7 +214,7 @@ This approach has almost the same set of advantages as setting the locale from t Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL (e.g. `link_to( books_url(locale: I18n.locale))`) would be tedious and probably impossible, of course. -Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000515, which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000503) and helper methods dependent on it (by implementing/overriding this method). +Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000515), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000503) and helper methods dependent on it (by implementing/overriding this method). We can include something like this in our `ApplicationController` then: diff --git a/guides/source/testing.md b/guides/source/testing.md index 33cd3e868b..169fd75cfa 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -339,7 +339,7 @@ NOTE: The execution of each test method stops as soon as any error or an asserti When a test fails you are presented with the corresponding backtrace. By default Rails filters that backtrace and will only print lines relevant to your -application. This eliminates the framwork noise and helps to focus on your +application. This eliminates the framework noise and helps to focus on your code. However there are situations when you want to see the full backtrace. simply set the `BACKTRACE` environment variable to enable this behavior: |