diff options
17 files changed, 255 insertions, 28 deletions
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 4290707a64..ee0e69d87c 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -65,6 +65,7 @@ module ActionController      def redirect_to(options = {}, response_status = {}) #:doc:        raise ActionControllerError.new("Cannot redirect to nil!") unless options        raise AbstractController::DoubleRenderError if response_body +      logger.debug { "Redirected by #{caller(1).first rescue "unknown"}" } if logger        self.status        = _extract_redirect_to_status(options, response_status)        self.location      = _compute_redirect_to_location(options) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 7e33ca2fac..52eb1aa447 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -460,8 +460,11 @@ module ActionView        # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,        #   which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options        #   as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>. -      # * +prompt+ - set to true or a prompt string. When the select element doesn't have a value yet, this +      # +      # Options: +      # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this        #   prepends an option with a generic prompt - "Please select" - or the given prompt string. +      # * <tt>:divider</tt> - the divider for the options groups.        #        # Sample usage (Array):        #   grouped_options = [ @@ -490,15 +493,51 @@ module ActionView        #     <option value="Canada">Canada</option>        #   </optgroup>        # +      # Sample usage (divider): +      #   grouped_options = [ +      #    [['United States','US'], 'Canada'], +      #    ['Denmark','Germany','France'] +      #   ] +      #   grouped_options_for_select(grouped_options, divider: '---------') +      # +      # Possible output: +      #   <optgroup label="---------"> +      #     <option value="Denmark">Denmark</option> +      #     <option value="Germany">Germany</option> +      #     <option value="France">France</option> +      #   </optgroup> +      #   <optgroup label="---------"> +      #     <option value="US">United States</option> +      #     <option value="Canada">Canada</option> +      #   </optgroup> +      #        # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to        # wrap the output in an appropriate <tt><select></tt> tag. -      def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil) +      def grouped_options_for_select(*args) +        grouped_options = args.shift +        options = args.extract_options! +        selected_key = args.shift +        if prompt = args.shift +          ActiveSupport::Deprecation.warn 'Passing the prompt to grouped_options_for_select as an argument is deprecated. Please pass it in an options hash.' +        else +          prompt = options[:prompt] +          divider = options[:divider] +        end +          body = "".html_safe -        body.safe_concat content_tag(:option, prompt, :value => "") if prompt + +        if prompt +          body.safe_concat content_tag(:option, prompt_text(prompt), :value => "") +        end          grouped_options = grouped_options.sort if grouped_options.is_a?(Hash) -        grouped_options.each do |label, container| +        grouped_options.each do |container| +          if divider +            label, container = divider, container +          else +            label, container = container +          end            body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label)          end @@ -714,6 +753,10 @@ module ActionView          def value_for_collection(item, value)            value.respond_to?(:call) ? value.call(item) : item.send(value)          end + +        def prompt_text(prompt) +          prompt = prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select') +        end      end      class FormBuilder diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb index 380ebe4b65..e077cd5b3c 100644 --- a/actionpack/lib/action_view/helpers/tags/base.rb +++ b/actionpack/lib/action_view/helpers/tags/base.rb @@ -140,8 +140,7 @@ module ActionView              option_tags = content_tag('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags            end            if value.blank? && options[:prompt] -            prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select') -            option_tags = content_tag('option', prompt, :value => '') + "\n" + option_tags +            option_tags = content_tag('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags            end            option_tags          end diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 2cff91adda..9b64bc9d81 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -296,10 +296,34 @@ class FormOptionsHelperTest < ActionView::TestCase      )    end -  def test_grouped_options_for_select_with_selected_and_prompt +  def test_grouped_options_for_select_with_optional_divider      assert_dom_equal( +      "<optgroup label=\"----------\"><option value=\"US\">US</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"----------\"><option value=\"GB\">GB</option>\n<option value=\"Germany\">Germany</option></optgroup>", + +      grouped_options_for_select([['US',"Canada"] , ["GB", "Germany"]], divider: "----------") +    ) +  end + +  def test_grouped_options_for_select_with_selected_and_prompt_deprecated +    assert_deprecated 'Passing the prompt to grouped_options_for_select as an argument is deprecated. Please pass it in an options hash.' do +      assert_dom_equal(          "<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",          grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", "Choose a product...") +      ) +    end +  end + +  def test_grouped_options_for_select_with_selected_and_prompt +    assert_dom_equal( +        "<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", +        grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", prompt: "Choose a product...") +    ) +  end + +  def test_grouped_options_for_select_with_selected_and_prompt_true +    assert_dom_equal( +        "<option value=\"\">Please select</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", +        grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", prompt: true)      )    end @@ -307,10 +331,18 @@ class FormOptionsHelperTest < ActionView::TestCase      assert grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]]).html_safe?    end +  def test_grouped_options_for_select_with_prompt_returns_html_escaped_string_deprecated +    ActiveSupport::Deprecation.silence do +      assert_dom_equal( +        "<option value=\"\"><Choose One></option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", +        grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, '<Choose One>')) +    end +  end +    def test_grouped_options_for_select_with_prompt_returns_html_escaped_string      assert_dom_equal(        "<option value=\"\"><Choose One></option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", -      grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, '<Choose One>')) +      grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, prompt: '<Choose One>'))    end    def test_optgroups_with_with_options_with_hash diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 00321ec860..56ec4c5c23 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -468,6 +468,8 @@ module ActiveRecord              raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \                                    "new records could not be saved."            end + +          new_target          end          def concat_records(records) diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 1933ce2b46..01bd3ae26c 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -124,6 +124,7 @@ module ActiveRecord          when :binary               then "#{klass}.binary_to_string(#{var_name})"          when :boolean              then "#{klass}.value_to_boolean(#{var_name})"          when :hstore               then "#{klass}.string_to_hstore(#{var_name})" +        when :inet, :cidr          then "#{klass}.string_to_cidr(#{var_name})"          else var_name          end        end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index c82afc232c..df3d5e4657 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -137,6 +137,14 @@ module ActiveRecord            end          end +        class Cidr < Type +          def type_cast(value) +            return if value.nil? + +            ConnectionAdapters::PostgreSQLColumn.string_to_cidr value +          end +        end +          class TypeMap            def initialize              @mapping = {} @@ -212,11 +220,9 @@ module ActiveRecord          # FIXME: why are we keeping these types as strings?          alias_type 'tsvector', 'text'          alias_type 'interval', 'text' -        alias_type 'cidr',     'text' -        alias_type 'inet',     'text' -        alias_type 'macaddr',  'text'          alias_type 'bit',      'text'          alias_type 'varbit',   'text' +        alias_type 'macaddr',  'text'          # FIXME: I don't think this is correct. We should probably be returning a parsed date,          # but the tests pass with a string returned. @@ -237,6 +243,9 @@ module ActiveRecord          register_type 'polygon', OID::Identity.new          register_type 'circle', OID::Identity.new          register_type 'hstore', OID::Hstore.new + +        register_type 'cidr', OID::Cidr.new +        alias_type 'inet', 'cidr'        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 4d5459939b..6714970103 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -8,6 +8,8 @@ require 'arel/visitors/bind_visitor'  gem 'pg', '~> 0.11'  require 'pg' +require 'ipaddr' +  module ActiveRecord    module ConnectionHandling      # Establishes a connection to the database that's used by all Active Record objects @@ -79,6 +81,25 @@ module ActiveRecord            end          end +        def string_to_cidr(string) +          if string.nil? +            nil +          elsif String === string +            IPAddr.new(string) +          else +            string +          end + +        end + +        def cidr_to_string(object) +          if IPAddr === object +            "#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}" +          else +            object +          end +        end +          private          HstorePair = begin            quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/ @@ -197,6 +218,13 @@ module ActiveRecord            :decimal          when 'hstore'            :hstore +        # Network address types +        when 'inet' +          :inet +        when 'cidr' +          :cidr +        when 'macaddr' +          :macaddr          # Character types          when /^(?:character varying|bpchar)(?:\(\d+\))?$/            :string @@ -211,9 +239,6 @@ module ActiveRecord          # Geometric types          when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/            :string -        # Network address types -        when /^(?:cidr|inet|macaddr)$/ -          :string          # Bit strings          when /^bit(?: varying)?(?:\(\d+\))?$/            :string @@ -282,6 +307,18 @@ module ActiveRecord          def hstore(name, options = {})            column(name, 'hstore', options)          end + +        def inet(name, options = {}) +          column(name, 'inet', options) +        end + +        def cidr(name, options = {}) +          column(name, 'cidr', options) +        end + +        def macaddr(name, options = {}) +          column(name, 'macaddr', options) +        end        end        ADAPTER_NAME = 'PostgreSQL' @@ -301,7 +338,10 @@ module ActiveRecord          :boolean     => { :name => "boolean" },          :xml         => { :name => "xml" },          :tsvector    => { :name => "tsvector" }, -        :hstore      => { :name => "hstore" } +        :hstore      => { :name => "hstore" }, +        :inet        => { :name => "inet" }, +        :cidr        => { :name => "cidr" }, +        :macaddr     => { :name => "macaddr" }        }        # Returns 'PostgreSQL' as adapter name for identification purposes. @@ -510,6 +550,11 @@ module ActiveRecord            when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column)            else super            end +        when IPAddr +          case column.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}'" @@ -549,6 +594,9 @@ module ActiveRecord          when Hash            return super unless 'hstore' == column.sql_type            PostgreSQLColumn.hstore_to_string(value) +        when IPAddr +          return super unless ['inet','cidr'].includes? column.sql_type +          PostgreSQLColumn.cidr_to_string(value)          else            super          end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 64e5640791..30e1035300 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -290,7 +290,15 @@ module ActiveRecord        status = nil        self.class.transaction do          add_to_transaction -        status = yield +        begin +          status = yield +        rescue ActiveRecord::Rollback +          if defined?(@_start_transaction_state)  +            @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 +          end +          status = nil +        end +                  raise ActiveRecord::Rollback unless status        end        status diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index ce08e4c6a7..34660577da 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -86,9 +86,9 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase    end    def test_data_type_of_network_address_types -    assert_equal :string, @first_network_address.column_for_attribute(:cidr_address).type -    assert_equal :string, @first_network_address.column_for_attribute(:inet_address).type -    assert_equal :string, @first_network_address.column_for_attribute(:mac_address).type +    assert_equal :cidr, @first_network_address.column_for_attribute(:cidr_address).type +    assert_equal :inet, @first_network_address.column_for_attribute(:inet_address).type +    assert_equal :macaddr, @first_network_address.column_for_attribute(:mac_address).type    end    def test_data_type_of_bit_string_types @@ -134,9 +134,12 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase      assert_equal '-1 years -2 days', @first_time.time_interval    end -  def test_network_address_values -    assert_equal '192.168.0.0/24', @first_network_address.cidr_address -    assert_equal '172.16.1.254', @first_network_address.inet_address +  def test_network_address_values_ipaddr +    cidr_address = IPAddr.new '192.168.0.0/24' +    inet_address = IPAddr.new '172.16.1.254' + +    assert_equal cidr_address, @first_network_address.cidr_address +    assert_equal inet_address, @first_network_address.inet_address      assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address    end @@ -200,8 +203,8 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase    end    def test_update_network_address -    new_cidr_address = '10.1.2.3/32' -    new_inet_address = '10.0.0.0/8' +    new_inet_address = '10.1.2.3/32' +    new_cidr_address = '10.0.0.0/8'      new_mac_address = 'bc:de:f0:12:34:56'      assert @first_network_address.cidr_address = new_cidr_address      assert @first_network_address.inet_address = new_inet_address diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 13d8c68b33..2e24f8ebe1 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1685,6 +1685,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      assert_equal [bulb2], car.reload.bulbs    end +  def test_replace_returns_new_target +    car = Car.create(:name => 'honda') +    bulb1 = car.bulbs.create +    bulb2 = car.bulbs.create +    bulb3 = Bulb.create + +    assert_equal [bulb1, bulb2], car.bulbs +    result = car.bulbs.replace([bulb1, bulb3]) +    assert_equal [bulb1, bulb3], car.bulbs +    assert_equal [bulb1, bulb3], result +  end +    def test_building_has_many_association_with_restrict_dependency      option_before = ActiveRecord::Base.dependent_restrict_raises      ActiveRecord::Base.dependent_restrict_raises = true diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 15ceaa1fcc..ab80dd1d6d 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -236,6 +236,27 @@ class SchemaDumperTest < ActiveRecord::TestCase        end      end +    def test_schema_dump_includes_inet_shorthand_definition +      output = standard_dump +      if %r{create_table "postgresql_network_address"} =~ output +        assert_match %r{t.inet "inet_address"}, output +      end +    end + +    def test_schema_dump_includes_cidr_shorthand_definition +      output = standard_dump +      if %r{create_table "postgresql_network_address"} =~ output +        assert_match %r{t.cidr "cidr_address"}, output +      end +    end + +    def test_schema_dump_includes_macaddr_shorthand_definition +      output = standard_dump +      if %r{create_table "postgresql_network_address"} =~ output +        assert_match %r{t.macaddr "macaddr_address"}, output +      end +    end +      def test_schema_dump_includes_hstores_shorthand_definition        output = standard_dump        if %r{create_table "postgresql_hstores"} =~ output diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 9846f5b12d..961ba8d9ba 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -287,8 +287,45 @@ class TransactionObserverCallbacksTest < ActiveRecord::TestCase        raise ActiveRecord::Rollback      end +    assert topic.id.nil? +    assert !topic.persisted?      assert_equal %w{ after_rollback }, topic.history    end + +  class TopicWithManualRollbackObserverAttached < ActiveRecord::Base +    self.table_name = :topics +    def history +      @history ||= [] +    end +  end + +  class TopicWithManualRollbackObserverAttachedObserver < ActiveRecord::Observer +    def after_save(record) +      record.history.push "after_save" +      raise ActiveRecord::Rollback +    end +  end + +  def test_after_save_called_with_manual_rollback +    assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer' + +    topic = TopicWithManualRollbackObserverAttached.new +     +    assert !topic.save +    assert_equal nil, topic.id +    assert !topic.persisted? +    assert_equal %w{ after_save }, topic.history +  end +  def test_after_save_called_with_manual_rollback_bang +    assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer' + +    topic = TopicWithManualRollbackObserverAttached.new +     +    topic.save! +    assert_equal nil, topic.id +    assert !topic.persisted? +    assert_equal %w{ after_save }, topic.history +  end  end  class SaveFromAfterCommitBlockTest < ActiveRecord::TestCase diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 01df2c5b64..1f88843ee9 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,7 @@  ## Rails 4.0.0 (unreleased) ## +*   Load all environments available in `config.paths["config/environments"]`. *Piotr Sarnacki* +  *   The application generator generates `public/humans.txt` with some basic data. *Paul Campbell*  *   Add `config.queue_consumer` to allow the default consumer to be configurable. *Carlos Antonio da Silva* diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index ef48bc4f94..47856c87c6 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -567,8 +567,9 @@ module Rails      end      initializer :load_environment_config, :before => :load_environment_hook, :group => :all do -      environment = paths["config/environments"].existent.first -      require environment if environment +      paths["config/environments"].existent.each do |environment| +        require environment +      end      end      initializer :append_assets_path, :group => :all do |app| diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css index 3b5cc6648e..3192ec897b 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css @@ -10,4 +10,4 @@   *   *= require_self   *= require_tree . -*/ + */ diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 9a6b2b66ca..55f72f532f 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -537,10 +537,11 @@ YAML        assert_equal "foo", last_response.body      end -    test "it loads its environment file" do +    test "it loads its environments file" do        @plugin.write "lib/bukkits.rb", <<-RUBY          module Bukkits            class Engine < ::Rails::Engine +            config.paths["config/environments"].push "config/environments/additional.rb"            end          end        RUBY @@ -551,9 +552,16 @@ YAML          end        RUBY +      @plugin.write "config/environments/additional.rb", <<-RUBY +        Bukkits::Engine.configure do +          config.additional_environment_loaded = true +        end +      RUBY +        boot_rails        assert Bukkits::Engine.config.environment_loaded +      assert Bukkits::Engine.config.additional_environment_loaded      end      test "it passes router in env" do  | 
