diff options
286 files changed, 3794 insertions, 2573 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/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 652ce0b3d8..5852aeaec2 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -301,12 +301,13 @@ module ActionMailer    #       end    #   end    # -  # Callbacks in ActionMailer are implemented using AbstractController::Callbacks, so you -  # can define and configure callbacks in the same manner that you would use callbacks in -  # classes that inherit from ActionController::Base. +  # Callbacks in Action Mailer are implemented using +  # <tt>AbstractController::Callbacks</tt>, so you can define and configure +  # callbacks in the same manner that you would use callbacks in classes that +  # inherit from <tt>ActionController::Base</tt>.    #    # Note that unless you have a specific reason to do so, you should prefer using before_action -  # rather than after_action in your ActionMailer classes so that headers are parsed properly. +  # rather than after_action in your Action Mailer classes so that headers are parsed properly.    #    # = Previewing emails    # diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb index 6452bf616c..06da0dd27e 100644 --- a/actionmailer/lib/action_mailer/test_helper.rb +++ b/actionmailer/lib/action_mailer/test_helper.rb @@ -1,4 +1,6 @@  module ActionMailer +  # Provides helper methods for testing Action Mailer, including #assert_emails +  # and #assert_no_emails    module TestHelper      # Asserts that the number of emails sent matches the given number.      # diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index a98aec913f..06f80a8fdc 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -1,7 +1,8 @@  require_relative 'gem_version'  module ActionMailer -  # Returns the version of the currently loaded ActionMailer as a <tt>Gem::Version</tt> +  # Returns the version of the currently loaded Action Mailer as a +  # <tt>Gem::Version</tt>.    def self.version      gem_version    end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 86e98c011c..fb633f11d5 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,11 @@ +*   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`. + +*   Deprecate all *_filter callbacks in favor of *_action callbacks. + +    *Rafael Mendonça França* +  *   Fix URL generation with `:trailing_slash` such that it does not add      a trailing slash after `.:format` @@ -111,7 +119,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/RUNNING_UNIT_TESTS.rdoc b/actionpack/RUNNING_UNIT_TESTS.rdoc index 2f923136d9..f96a9d9da5 100644 --- a/actionpack/RUNNING_UNIT_TESTS.rdoc +++ b/actionpack/RUNNING_UNIT_TESTS.rdoc @@ -4,7 +4,7 @@ The easiest way to run the unit tests is through Rake. The default task runs  the entire test suite for all classes. For more information, check out the  full array of rake tasks with "rake -T". -Rake can be found at http://rake.rubyforge.org. +Rake can be found at http://docs.seattlerb.org/rake/.  == Running by hand diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index c00f0d0c6f..acdfb33efa 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -255,7 +255,7 @@ module AbstractController        # Checks if the action name is valid and returns false otherwise.        def _valid_action_name?(action_name) -        action_name.to_s !~ Regexp.new(File::SEPARATOR) +        action_name !~ Regexp.new(File::SEPARATOR)        end    end  end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 69aca308d6..252e297c69 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation' +  module AbstractController    module Callbacks      extend ActiveSupport::Concern @@ -42,21 +44,23 @@ module AbstractController          end        end -      # Skip before, after, and around action callbacks matching any of the names -      # Aliased as skip_filter. +      # Skip before, after, and around action callbacks matching any of the names.        #        # ==== Parameters        # * <tt>names</tt> - A list of valid names that could be used for        #   callbacks. Note that skipping uses Ruby equality, so it's        #   impossible to skip a callback defined using an anonymous proc -      #   using #skip_filter +      #   using #skip_action_callback        def skip_action_callback(*names)          skip_before_action(*names)          skip_after_action(*names)          skip_around_action(*names)        end -      alias_method :skip_filter, :skip_action_callback +      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        # Take callback names and an optional callback proc, normalize them,        # then call the block with each callback. This allows us to abstract @@ -85,7 +89,6 @@ module AbstractController        # :call-seq: before_action(names, block)        #        # Append a callback before actions. See _insert_callbacks for parameter details. -      # Aliased as before_filter.        ##        # :method: prepend_before_action @@ -93,7 +96,6 @@ module AbstractController        # :call-seq: prepend_before_action(names, block)        #        # Prepend a callback before actions. See _insert_callbacks for parameter details. -      # Aliased as prepend_before_filter.        ##        # :method: skip_before_action @@ -101,7 +103,6 @@ module AbstractController        # :call-seq: skip_before_action(names)        #        # Skip a callback before actions. See _insert_callbacks for parameter details. -      # Aliased as skip_before_filter.        ##        # :method: append_before_action @@ -109,7 +110,6 @@ module AbstractController        # :call-seq: append_before_action(names, block)        #        # Append a callback before actions. See _insert_callbacks for parameter details. -      # Aliased as append_before_filter.        ##        # :method: after_action @@ -117,7 +117,6 @@ module AbstractController        # :call-seq: after_action(names, block)        #        # Append a callback after actions. See _insert_callbacks for parameter details. -      # Aliased as after_filter.        ##        # :method: prepend_after_action @@ -125,7 +124,6 @@ module AbstractController        # :call-seq: prepend_after_action(names, block)        #        # Prepend a callback after actions. See _insert_callbacks for parameter details. -      # Aliased as prepend_after_filter.        ##        # :method: skip_after_action @@ -133,7 +131,6 @@ module AbstractController        # :call-seq: skip_after_action(names)        #        # Skip a callback after actions. See _insert_callbacks for parameter details. -      # Aliased as skip_after_filter.        ##        # :method: append_after_action @@ -141,7 +138,6 @@ module AbstractController        # :call-seq: append_after_action(names, block)        #        # Append a callback after actions. See _insert_callbacks for parameter details. -      # Aliased as append_after_filter.        ##        # :method: around_action @@ -149,7 +145,6 @@ module AbstractController        # :call-seq: around_action(names, block)        #        # Append a callback around actions. See _insert_callbacks for parameter details. -      # Aliased as around_filter.        ##        # :method: prepend_around_action @@ -157,7 +152,6 @@ module AbstractController        # :call-seq: prepend_around_action(names, block)        #        # Prepend a callback around actions. See _insert_callbacks for parameter details. -      # Aliased as prepend_around_filter.        ##        # :method: skip_around_action @@ -165,7 +159,6 @@ module AbstractController        # :call-seq: skip_around_action(names)        #        # Skip a callback around actions. See _insert_callbacks for parameter details. -      # Aliased as skip_around_filter.        ##        # :method: append_around_action @@ -173,7 +166,6 @@ module AbstractController        # :call-seq: append_around_action(names, block)        #        # Append a callback around actions. See _insert_callbacks for parameter details. -      # Aliased as append_around_filter.        # set up before_action, prepend_before_action, skip_before_action, etc.        # for each of before, after, and around. @@ -184,7 +176,10 @@ module AbstractController            end          end -        alias_method :"#{callback}_filter", :"#{callback}_action" +        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          define_method "prepend_#{callback}_action" do |*names, &blk|            _insert_callbacks(names, blk) do |name, options| @@ -192,7 +187,10 @@ module AbstractController            end          end -        alias_method :"prepend_#{callback}_filter", :"prepend_#{callback}_action" +        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          # Skip a before, after or around callback. See _insert_callbacks          # for details on the allowed parameters. @@ -202,11 +200,17 @@ module AbstractController            end          end -        alias_method :"skip_#{callback}_filter", :"skip_#{callback}_action" +        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          # *_action is the same as append_*_action          alias_method :"append_#{callback}_action", :"#{callback}_action"  # alias_method :append_before_action, :before_action -        alias_method :"append_#{callback}_filter", :"#{callback}_action"  # alias_method :append_before_filter, :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        end      end    end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 70ca99f01c..9a427ebfdb 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -221,13 +221,18 @@ module ActionController      # Makes the controller a Rack endpoint that runs the action in the given      # +env+'s +action_dispatch.request.path_parameters+ key.      def self.call(env) -      action(env['action_dispatch.request.path_parameters'][:action]).call(env) +      req = ActionDispatch::Request.new env +      action(req.path_parameters[:action]).call(env)      end      # Returns a Rack endpoint for the given action name.      def self.action(name, klass = ActionDispatch::Request) -      middleware_stack.build(name.to_s) do |env| -        new.dispatch(name, klass.new(env)) +      if middleware_stack.any? +        middleware_stack.build(name) do |env| +          new.dispatch(name, klass.new(env)) +        end +      else +        lambda { |env| new.dispatch(name, klass.new(env)) }        end      end diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 29ce5abd55..46405cef55 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -78,7 +78,7 @@ module ActionController      #     respond_to do |format|      #       format.html      #       format.csv { render csv: @csvable, filename: @csvable.name } -    #     } +    #     end      #   end      # To use renderers and their mime types in more concise ways, see      # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 8a8d459876..5f7627cf96 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -4,10 +4,7 @@ require 'active_support/core_ext/hash/indifferent_access'  module ActionDispatch    module Http      module Parameters -      def initialize(env) -        super -        @symbolized_path_params = nil -      end +      PARAMETERS_KEY = 'action_dispatch.request.path_parameters'        # Returns both GET and POST \parameters in a single hash.        def parameters @@ -24,7 +21,7 @@ module ActionDispatch        def path_parameters=(parameters) #:nodoc:          @env.delete('action_dispatch.request.parameters') -        @env[Routing::RouteSet::PARAMETERS_KEY] = parameters +        @env[PARAMETERS_KEY] = parameters        end        # The same as <tt>path_parameters</tt> with explicitly symbolized keys. @@ -39,11 +36,7 @@ module ActionDispatch        #        # See <tt>symbolized_path_parameters</tt> for symbolized keys.        def path_parameters -        @env[Routing::RouteSet::PARAMETERS_KEY] ||= {} -      end - -      def reset_parameters #:nodoc: -        @env.delete("action_dispatch.request.parameters") +        @env[PARAMETERS_KEY] ||= {}        end      private diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index bc948a6a64..4d4b443fb4 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -53,6 +53,17 @@ module ActionDispatch        @uuid              = nil      end +    def check_path_parameters! +      # If any of the path parameters has an invalid encoding then +      # raise since it's likely to trigger errors further on. +      path_parameters.each do |key, value| +        next unless value.respond_to?(:valid_encoding?) +        unless value.valid_encoding? +          raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}" +        end +      end +    end +      def key?(key)        @env.key?(key)      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/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 1ba91d548e..9f0a3af902 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -16,14 +16,6 @@ module ActionDispatch          @app         = app          @path        = path -        # Unwrap any constraints so we can see what's inside for route generation. -        # This allows the formatter to skip over any mounted applications or redirects -        # that shouldn't be matched when using a url_for without a route name. -        if app.is_a?(Routing::Mapper::Constraints) -          app = app.app -        end -        @dispatcher  = app.is_a?(Routing::RouteSet::Dispatcher) -          @constraints = constraints          @defaults    = defaults          @required_defaults = nil @@ -99,7 +91,7 @@ module ActionDispatch        end        def dispatcher? -        @dispatcher +        @app.dispatcher?        end        def matches?(request) diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 2ead6a4eb3..74fa9ee3a2 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -39,7 +39,7 @@ module ActionDispatch            req.path_parameters = set_params.merge parameters -          status, headers, body = route.app.call(req.env) +          status, headers, body = route.app.serve(req)            if 'pass' == headers['X-Cascade']              req.script_name     = script_name @@ -99,7 +99,10 @@ module ActionDispatch            routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|              r.path.match(req.path_info)            } -          routes.concat get_routes_as_head(routes) + +          if req.env["REQUEST_METHOD"] === "HEAD" +            routes.concat get_routes_as_head(routes) +          end            routes.sort_by!(&:precedence).select! { |r| r.matches?(req) } @@ -115,7 +118,7 @@ module ActionDispatch          def get_routes_as_head(routes)            precedence = (routes.map(&:precedence).max || 0) + 1 -          routes = routes.select { |r| +          routes.select { |r|              r.verb === "GET" && !(r.verb === "HEAD")            }.map! { |r|              Route.new(r.name, @@ -126,8 +129,6 @@ module ActionDispatch                          route.precedence = r.precedence + precedence                        end            } -          routes.flatten! -          routes          end      end    end 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/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb index cce0d75af4..6ffa242da4 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -148,8 +148,8 @@      // On key press perform a search for matching paths      searchElem.onkeyup = function(e){        var userInput         = searchElem.value, -          defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + sanitizePath(userInput) +'):</th></tr>', -          defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + userInput +'):</th></tr>', +          defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + escape(sanitizePath(userInput)) +'):</th></tr>', +          defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + escape(userInput) +'):</th></tr>',            noExactMatch      = '<tr><th colspan="4">No Exact Matches Found</th></tr>',            noFuzzyMatch      = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>'; diff --git a/actionpack/lib/action_dispatch/routing/endpoint.rb b/actionpack/lib/action_dispatch/routing/endpoint.rb new file mode 100644 index 0000000000..88aa13c3e8 --- /dev/null +++ b/actionpack/lib/action_dispatch/routing/endpoint.rb @@ -0,0 +1,10 @@ +module ActionDispatch +  module Routing +    class Endpoint # :nodoc: +      def dispatcher?;   false; end +      def redirect?;     false; end +      def matches?(req); true;  end +      def app;           self;  end +    end +  end +end diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 2135b280da..ea3b2f419d 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -5,22 +5,15 @@ module ActionDispatch    module Routing      class RouteWrapper < SimpleDelegator        def endpoint -        rack_app ? rack_app.inspect : "#{controller}##{action}" +        app.dispatcher? ? "#{controller}##{action}" : rack_app.inspect        end        def constraints          requirements.except(:controller, :action)        end -      def rack_app(app = self.app) -        @rack_app ||= begin -          class_name = app.class.name.to_s -          if class_name == "ActionDispatch::Routing::Mapper::Constraints" -            app.app -          elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/ -            app -          end -        end +      def rack_app +        app.app        end        def verb @@ -73,7 +66,7 @@ module ActionDispatch        end        def engine? -        rack_app && rack_app.respond_to?(:routes) +        rack_app.respond_to?(:routes)        end      end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index b33c5e0dfd..a32e4ee0d1 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -6,6 +6,8 @@ require 'active_support/core_ext/array/extract_options'  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 @@ -15,35 +17,41 @@ module ActionDispatch                         :controller, :action, :path_names, :constraints,                         :shallow, :blocks, :defaults, :options] -      class Constraints #:nodoc: +      class Constraints < Endpoint #:nodoc:          attr_reader :app, :constraints -        def initialize(app, constraints, request) +        def initialize(app, constraints, dispatcher_p)            # Unwrap Constraints objects.  I don't actually think it's possible            # to pass a Constraints object to this constructor, but there were            # multiple places that kept testing children of this object.  I            # *think* they were just being defensive, but I have no idea. -          while app.is_a?(self.class) +          if app.is_a?(self.class)              constraints += app.constraints              app = app.app            end -          @app, @constraints, @request = app, constraints, request +          @dispatcher = dispatcher_p + +          @app, @constraints, = app, constraints          end -        def matches?(env) -          req = @request.new(env) +        def dispatcher?; @dispatcher; end +        def matches?(req)            @constraints.all? do |constraint|              (constraint.respond_to?(:matches?) && constraint.matches?(req)) ||                (constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))            end -        ensure -          req.reset_parameters          end -        def call(env) -          matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ] +        def serve(req) +          return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req) + +          if dispatcher? +            @app.serve req +          else +            @app.call req.env +          end          end          private @@ -53,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 -          end - -          def optional_format? -            options[:format] != false && !path.include?(':format') && !path.end_with?('/') +          def optional_format?(path, format) +            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: @@ -120,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 @@ -149,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) @@ -182,132 +221,126 @@ 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 +          def add_request_method(via, conditions) +            return if via == [:all] -            via_all = options.delete(:via) if options[:via] == :all - -            if !via_all && options[:via].blank? +            if via.empty?                msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \                      "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \                      "If you want to expose your action to GET, use `get` in the router:\n" \                      "  Instead of: match \"controller#action\"\n" \                      "  Do: get \"controller#action\"" -              raise msg +              raise ArgumentError, msg              end -            if via = options[:via] -              @conditions[:request_method] = Array(via).map { |m| m.to_s.dasherize.upcase } -            end +            conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }            end -          def app -            if blocks.any? -              Constraints.new(endpoint, blocks, @set.request_class) -            else -              endpoint -            end -          end +          def app(blocks) +            return to if Redirect === to -          def default_controller_and_action              if to.respond_to?(:call) -              { } +              Constraints.new(to, blocks, false)              else -              if to.is_a?(String) -                controller, action = to.split('#') -              elsif to.is_a?(Symbol) -                action = to.to_s +              if blocks.any? +                Constraints.new(dispatcher, blocks, true) +              else +                dispatcher                end +            end +          end -              controller ||= default_controller -              action     ||= default_action +          def default_controller_and_action(path_params, modyoule) +            controller, action = get_controller_and_action(default_controller, +              default_action, +              to, +              modyoule +            ) -              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) -          end - -          def strexp -            Journey::Router::Strexp.compile(path, requirements, SEPARATORS) +          def path_params(ast) +            ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }            end -          def endpoint -            to.respond_to?(:call) ? to : dispatcher +          def path_ast(path) +            parser = Journey::Parser.new +            parser.parse path            end            def dispatcher @@ -413,6 +446,12 @@ module ActionDispatch          # [:action]          #   The route's action.          # +        # [:param] +        #   Overrides the default resource identifier `:id` (name of the +        #   dynamic segment used to generate the routes). +        #   You can access that segment from your controller using +        #   <tt>params[<:param>]</tt>. +        #          # [:path]          #   The path prefix for the routes.          # @@ -1424,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 @@ -1478,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\-\/]+$/ @@ -1492,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/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index f8ed0cbe6a..b1b39a5496 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -3,10 +3,11 @@ require 'active_support/core_ext/uri'  require 'active_support/core_ext/array/extract_options'  require 'rack/utils'  require 'action_controller/metal/exceptions' +require 'action_dispatch/routing/endpoint'  module ActionDispatch    module Routing -    class Redirect # :nodoc: +    class Redirect < Endpoint # :nodoc:        attr_reader :status, :block        def initialize(status, block) @@ -14,17 +15,14 @@ module ActionDispatch          @block  = block        end -      def call(env) -        req = Request.new(env) +      def redirect?; true; end -        # If any of the path parameters has an invalid encoding then -        # raise since it's likely to trigger errors further on. -        req.path_parameters.each do |key, value| -          unless value.valid_encoding? -            raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}" -          end -        end +      def call(env) +        serve Request.new env +      end +      def serve(req) +        req.check_path_parameters!          uri = URI.parse(path(req.path_parameters, req))          unless uri.host diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 924455bce2..bdda802195 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -8,6 +8,7 @@ require 'active_support/core_ext/module/remove_method'  require 'active_support/core_ext/array/extract_options'  require 'action_controller/metal/exceptions'  require 'action_dispatch/http/request' +require 'action_dispatch/routing/endpoint'  module ActionDispatch    module Routing @@ -18,26 +19,17 @@ module ActionDispatch        # alias inspect to to_s.        alias inspect to_s -      PARAMETERS_KEY = 'action_dispatch.request.path_parameters' - -      class Dispatcher #:nodoc: +      class Dispatcher < Routing::Endpoint #:nodoc:          def initialize(defaults)            @defaults = defaults            @controller_class_names = ThreadSafe::Cache.new          end -        def call(env) -          params = env[PARAMETERS_KEY] - -          # If any of the path parameters has an invalid encoding then -          # raise since it's likely to trigger errors further on. -          params.each do |key, value| -            next unless value.respond_to?(:valid_encoding?) +        def dispatcher?; true; end -            unless value.valid_encoding? -              raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}" -            end -          end +        def serve(req) +          req.check_path_parameters! +          params = req.path_parameters            prepare_params!(params) @@ -46,7 +38,7 @@ module ActionDispatch              return [404, {'X-Cascade' => 'pass'}, []]            end -          dispatch(controller, params[:action], env) +          dispatch(controller, params[:action], req.env)          end          def prepare_params!(params) @@ -426,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) @@ -434,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, @@ -703,12 +698,10 @@ module ActionDispatch            end            old_params = req.path_parameters            req.path_parameters = old_params.merge params -          dispatcher = route.app -          if dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) -            dispatcher = dispatcher.app -          end +          app = route.app +          if app.matches?(req) && app.dispatcher? +            dispatcher = app.app -          if dispatcher.is_a?(Dispatcher)              if dispatcher.controller(params, false)                dispatcher.prepare_params!(params)                return params diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 107e62d20f..af3bc26691 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -269,12 +269,6 @@ module ActionDispatch              path = location.query ? "#{location.path}?#{location.query}" : location.path            end -          unless ActionController::Base < ActionController::Testing -            ActionController::Base.class_eval do -              include ActionController::Testing -            end -          end -            hostname, port = host.split(':')            env = { diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index 8cba049485..07571602e4 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -267,9 +267,11 @@ module AbstractController      end      class AliasedCallbacks < ControllerWithCallbacks -      before_filter :first -      after_filter :second -      around_filter :aroundz +      ActiveSupport::Deprecation.silence do +        before_filter :first +        after_filter :second +        around_filter :aroundz +      end        def first          @text = "Hello world" diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 46de36317e..6584d20840 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -251,7 +251,6 @@ end  module ActionController    class Base -    include ActionController::Testing      # This stub emulates the Railtie including the URL helpers from a Rails application      include SharedTestRoutes.url_helpers      include SharedTestRoutes.mounted_helpers diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb index 03d5d27cf4..89667df3a4 100644 --- a/actionpack/test/controller/content_type_test.rb +++ b/actionpack/test/controller/content_type_test.rb @@ -68,12 +68,11 @@ class ContentTypeTest < ActionController::TestCase    end    def test_render_changed_charset_default -    ActionDispatch::Response.default_charset = "utf-16" -    get :render_defaults -    assert_equal "utf-16", @response.charset -    assert_equal Mime::HTML, @response.content_type -  ensure -    ActionDispatch::Response.default_charset = "utf-8" +    with_default_charset "utf-16" do +      get :render_defaults +      assert_equal "utf-16", @response.charset +      assert_equal Mime::HTML, @response.content_type +    end    end    # :ported: @@ -105,12 +104,11 @@ class ContentTypeTest < ActionController::TestCase    end    def test_nil_default_for_erb -    ActionDispatch::Response.default_charset = nil -    get :render_default_for_erb -    assert_equal Mime::HTML, @response.content_type -    assert_nil @response.charset, @response.headers.inspect -  ensure -    ActionDispatch::Response.default_charset = "utf-8" +    with_default_charset nil do +      get :render_default_for_erb +      assert_equal Mime::HTML, @response.content_type +      assert_nil @response.charset, @response.headers.inspect +    end    end    def test_default_for_erb @@ -130,6 +128,16 @@ class ContentTypeTest < ActionController::TestCase      assert_equal Mime::HTML, @response.content_type      assert_equal "utf-8", @response.charset    end + +  private + +  def with_default_charset(charset) +    old_default_charset = ActionDispatch::Response.default_charset +    ActionDispatch::Response.default_charset = charset +    yield +  ensure +    ActionDispatch::Response.default_charset = old_default_charset +  end  end  class AcceptBasedContentTypeTest < ActionController::TestCase diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index c87494aa64..b2b01b3fa9 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -2,13 +2,13 @@ require 'abstract_unit'  class ActionController::Base    class << self -    %w(append_around_filter prepend_after_filter prepend_around_filter prepend_before_filter skip_after_filter skip_before_filter skip_filter).each do |pending| +    %w(append_around_action prepend_after_action prepend_around_action prepend_before_action skip_after_action skip_before_action skip_action_callback).each do |pending|        define_method(pending) do |*args|          $stderr.puts "#{pending} unimplemented: #{args.inspect}"        end unless method_defined?(pending)      end -    def before_filters +    def before_actions        filters = _process_action_callbacks.select { |c| c.kind == :before }        filters.map! { |c| c.raw_filter }      end @@ -28,8 +28,8 @@ end  class FilterTest < ActionController::TestCase    class TestController < ActionController::Base -    before_filter :ensure_login -    after_filter  :clean_up +    before_action :ensure_login +    after_action  :clean_up      def show        render :inline => "ran action" @@ -42,13 +42,13 @@ class FilterTest < ActionController::TestCase        end        def clean_up -        @ran_after_filter ||= [] -        @ran_after_filter << "clean_up" +        @ran_after_action ||= [] +        @ran_after_action << "clean_up"        end    end    class ChangingTheRequirementsController < TestController -    before_filter :ensure_login, :except => [:go_wild] +    before_action :ensure_login, :except => [:go_wild]      def go_wild        render :text => "gobble" @@ -56,9 +56,9 @@ class FilterTest < ActionController::TestCase    end    class TestMultipleFiltersController < ActionController::Base -    before_filter :try_1 -    before_filter :try_2 -    before_filter :try_3 +    before_action :try_1 +    before_action :try_2 +    before_action :try_3      (1..3).each do |i|        define_method "fail_#{i}" do @@ -78,8 +78,8 @@ class FilterTest < ActionController::TestCase    end    class RenderingController < ActionController::Base -    before_filter :before_filter_rendering -    after_filter :unreached_after_filter +    before_action :before_action_rendering +    after_action :unreached_after_action      def show        @ran_action = true @@ -87,29 +87,29 @@ class FilterTest < ActionController::TestCase      end      private -      def before_filter_rendering +      def before_action_rendering          @ran_filter ||= [] -        @ran_filter << "before_filter_rendering" +        @ran_filter << "before_action_rendering"          render :inline => "something else"        end -      def unreached_after_filter -        @ran_filter << "unreached_after_filter_after_render" +      def unreached_after_action +        @ran_filter << "unreached_after_action_after_render"        end    end -  class RenderingForPrependAfterFilterController < RenderingController -    prepend_after_filter :unreached_prepend_after_filter +  class RenderingForPrependAfterActionController < RenderingController +    prepend_after_action :unreached_prepend_after_action      private -      def unreached_prepend_after_filter -        @ran_filter << "unreached_preprend_after_filter_after_render" +      def unreached_prepend_after_action +        @ran_filter << "unreached_preprend_after_action_after_render"        end    end -  class BeforeFilterRedirectionController < ActionController::Base -    before_filter :before_filter_redirects -    after_filter :unreached_after_filter +  class BeforeActionRedirectionController < ActionController::Base +    before_action :before_action_redirects +    after_action :unreached_after_action      def show        @ran_action = true @@ -122,23 +122,23 @@ class FilterTest < ActionController::TestCase      end      private -      def before_filter_redirects +      def before_action_redirects          @ran_filter ||= [] -        @ran_filter << "before_filter_redirects" +        @ran_filter << "before_action_redirects"          redirect_to(:action => 'target_of_redirection')        end -      def unreached_after_filter -        @ran_filter << "unreached_after_filter_after_redirection" +      def unreached_after_action +        @ran_filter << "unreached_after_action_after_redirection"        end    end -  class BeforeFilterRedirectionForPrependAfterFilterController < BeforeFilterRedirectionController -    prepend_after_filter :unreached_prepend_after_filter_after_redirection +  class BeforeActionRedirectionForPrependAfterActionController < BeforeActionRedirectionController +    prepend_after_action :unreached_prepend_after_action_after_redirection      private -      def unreached_prepend_after_filter_after_redirection -        @ran_filter << "unreached_prepend_after_filter_after_redirection" +      def unreached_prepend_after_action_after_redirection +        @ran_filter << "unreached_prepend_after_action_after_redirection"        end    end @@ -151,8 +151,8 @@ class FilterTest < ActionController::TestCase        render :inline => "ran action"      end -    def show_without_filter -      render :inline => "ran action without filter" +    def show_without_action +      render :inline => "ran action without action"      end      private @@ -168,70 +168,70 @@ class FilterTest < ActionController::TestCase    end    class ConditionalCollectionFilterController < ConditionalFilterController -    before_filter :ensure_login, :except => [ :show_without_filter, :another_action ] +    before_action :ensure_login, :except => [ :show_without_action, :another_action ]    end    class OnlyConditionSymController < ConditionalFilterController -    before_filter :ensure_login, :only => :show +    before_action :ensure_login, :only => :show    end    class ExceptConditionSymController < ConditionalFilterController -    before_filter :ensure_login, :except => :show_without_filter +    before_action :ensure_login, :except => :show_without_action    end    class BeforeAndAfterConditionController < ConditionalFilterController -    before_filter :ensure_login, :only => :show -    after_filter  :clean_up_tmp, :only => :show +    before_action :ensure_login, :only => :show +    after_action  :clean_up_tmp, :only => :show    end    class OnlyConditionProcController < ConditionalFilterController -    before_filter(:only => :show) {|c| c.instance_variable_set(:"@ran_proc_filter", true) } +    before_action(:only => :show) {|c| c.instance_variable_set(:"@ran_proc_action", true) }    end    class ExceptConditionProcController < ConditionalFilterController -    before_filter(:except => :show_without_filter) {|c| c.instance_variable_set(:"@ran_proc_filter", true) } +    before_action(:except => :show_without_action) {|c| c.instance_variable_set(:"@ran_proc_action", true) }    end    class ConditionalClassFilter -    def self.before(controller) controller.instance_variable_set(:"@ran_class_filter", true) end +    def self.before(controller) controller.instance_variable_set(:"@ran_class_action", true) end    end    class OnlyConditionClassController < ConditionalFilterController -    before_filter ConditionalClassFilter, :only => :show +    before_action ConditionalClassFilter, :only => :show    end    class ExceptConditionClassController < ConditionalFilterController -    before_filter ConditionalClassFilter, :except => :show_without_filter +    before_action ConditionalClassFilter, :except => :show_without_action    end    class AnomolousYetValidConditionController < ConditionalFilterController -    before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.instance_variable_set(:"@ran_proc_filter1", true)}, :except => :show_without_filter) { |c| c.instance_variable_set(:"@ran_proc_filter2", true)} +    before_action(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.instance_variable_set(:"@ran_proc_action1", true)}, :except => :show_without_action) { |c| c.instance_variable_set(:"@ran_proc_action2", true)}    end    class OnlyConditionalOptionsFilter < ConditionalFilterController -    before_filter :ensure_login, :only => :index, :if => Proc.new {|c| c.instance_variable_set(:"@ran_conditional_index_proc", true) } +    before_action :ensure_login, :only => :index, :if => Proc.new {|c| c.instance_variable_set(:"@ran_conditional_index_proc", true) }    end    class ConditionalOptionsFilter < ConditionalFilterController -    before_filter :ensure_login, :if => Proc.new { |c| true } -    before_filter :clean_up_tmp, :if => Proc.new { |c| false } +    before_action :ensure_login, :if => Proc.new { |c| true } +    before_action :clean_up_tmp, :if => Proc.new { |c| false }    end    class ConditionalOptionsSkipFilter < ConditionalFilterController -    before_filter :ensure_login -    before_filter :clean_up_tmp +    before_action :ensure_login +    before_action :clean_up_tmp -    skip_before_filter :ensure_login, if: -> { false } -    skip_before_filter :clean_up_tmp, if: -> { true } +    skip_before_action :ensure_login, if: -> { false } +    skip_before_action :clean_up_tmp, if: -> { true }    end    class ClassController < ConditionalFilterController -    before_filter ConditionalClassFilter +    before_action ConditionalClassFilter    end    class PrependingController < TestController -    prepend_before_filter :wonderful_life -    # skip_before_filter :fire_flash +    prepend_before_action :wonderful_life +    # skip_before_action :fire_flash      private        def wonderful_life @@ -241,8 +241,8 @@ class FilterTest < ActionController::TestCase    end    class SkippingAndLimitedController < TestController -    skip_before_filter :ensure_login -    before_filter :ensure_login, :only => :index +    skip_before_action :ensure_login +    before_action :ensure_login, :only => :index      def index        render :text => 'ok' @@ -254,9 +254,9 @@ class FilterTest < ActionController::TestCase    end    class SkippingAndReorderingController < TestController -    skip_before_filter :ensure_login -    before_filter :find_record -    before_filter :ensure_login +    skip_before_action :ensure_login +    before_action :find_record +    before_action :ensure_login      def index        render :text => 'ok' @@ -270,10 +270,10 @@ class FilterTest < ActionController::TestCase    end    class ConditionalSkippingController < TestController -    skip_before_filter :ensure_login, :only => [ :login ] -    skip_after_filter  :clean_up,     :only => [ :login ] +    skip_before_action :ensure_login, :only => [ :login ] +    skip_after_action  :clean_up,     :only => [ :login ] -    before_filter :find_user, :only => [ :change_password ] +    before_action :find_user, :only => [ :change_password ]      def login        render :inline => "ran action" @@ -291,8 +291,8 @@ class FilterTest < ActionController::TestCase    end    class ConditionalParentOfConditionalSkippingController < ConditionalFilterController -    before_filter :conditional_in_parent_before, :only => [:show, :another_action] -    after_filter  :conditional_in_parent_after, :only => [:show, :another_action] +    before_action :conditional_in_parent_before, :only => [:show, :another_action] +    after_action  :conditional_in_parent_after, :only => [:show, :another_action]      private @@ -308,20 +308,20 @@ class FilterTest < ActionController::TestCase    end    class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController -    skip_before_filter :conditional_in_parent_before, :only => :another_action -    skip_after_filter  :conditional_in_parent_after, :only => :another_action +    skip_before_action :conditional_in_parent_before, :only => :another_action +    skip_after_action  :conditional_in_parent_after, :only => :another_action    end    class AnotherChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController -    skip_before_filter :conditional_in_parent_before, :only => :show +    skip_before_action :conditional_in_parent_before, :only => :show    end    class ProcController < PrependingController -    before_filter(proc { |c| c.instance_variable_set(:"@ran_proc_filter", true) }) +    before_action(proc { |c| c.instance_variable_set(:"@ran_proc_action", true) })    end    class ImplicitProcController < PrependingController -    before_filter { |c| c.instance_variable_set(:"@ran_proc_filter", true) } +    before_action { |c| c.instance_variable_set(:"@ran_proc_action", true) }    end    class AuditFilter @@ -367,7 +367,7 @@ class FilterTest < ActionController::TestCase    end    class AuditController < ActionController::Base -    before_filter(AuditFilter) +    before_action(AuditFilter)      def show        render :text => "hello" @@ -375,14 +375,14 @@ class FilterTest < ActionController::TestCase    end    class AroundFilterController < PrependingController -    around_filter AroundFilter.new +    around_action AroundFilter.new    end    class BeforeAfterClassFilterController < PrependingController      begin        filter = AroundFilter.new -      before_filter filter -      after_filter filter +      before_action filter +      after_action filter      end    end @@ -394,18 +394,18 @@ class FilterTest < ActionController::TestCase        super()      end -    before_filter { |c| c.class.execution_log << " before procfilter "  } -    prepend_around_filter AroundFilter.new +    before_action { |c| c.class.execution_log << " before procfilter "  } +    prepend_around_action AroundFilter.new -    after_filter  { |c| c.class.execution_log << " after procfilter " } -    append_around_filter AppendedAroundFilter.new +    after_action  { |c| c.class.execution_log << " after procfilter " } +    append_around_action AppendedAroundFilter.new    end    class MixedSpecializationController < ActionController::Base      class OutOfOrder < StandardError; end -    before_filter :first -    before_filter :second, :only => :foo +    before_action :first +    before_action :second, :only => :foo      def foo        render :text => 'foo' @@ -426,7 +426,7 @@ class FilterTest < ActionController::TestCase    end    class DynamicDispatchController < ActionController::Base -    before_filter :choose +    before_action :choose      %w(foo bar baz).each do |action|        define_method(action) { render :text => action } @@ -439,9 +439,9 @@ class FilterTest < ActionController::TestCase    end    class PrependingBeforeAndAfterController < ActionController::Base -    prepend_before_filter :before_all -    prepend_after_filter :after_all -    before_filter :between_before_all_and_after_all +    prepend_before_action :before_all +    prepend_after_action :after_all +    before_action :between_before_all_and_after_all      def before_all        @ran_filter ||= [] @@ -473,7 +473,7 @@ class FilterTest < ActionController::TestCase    end    class RescuedController < ActionController::Base -    around_filter RescuingAroundFilterWithBlock.new +    around_action RescuingAroundFilterWithBlock.new      def show        raise ErrorToRescue.new("Something made the bad noise.") @@ -482,10 +482,10 @@ class FilterTest < ActionController::TestCase    class NonYieldingAroundFilterController < ActionController::Base -    before_filter :filter_one -    around_filter :non_yielding_filter -    before_filter :filter_two -    after_filter :filter_three +    before_action :filter_one +    around_action :non_yielding_action +    before_action :action_two +    after_action :action_three      def index        render :inline => "index" @@ -498,24 +498,24 @@ class FilterTest < ActionController::TestCase          @filters  << "filter_one"        end -      def filter_two -        @filters  << "filter_two" +      def action_two +        @filters  << "action_two"        end -      def non_yielding_filter +      def non_yielding_action          @filters  << "it didn't yield"          @filter_return_value        end -      def filter_three -        @filters  << "filter_three" +      def action_three +        @filters  << "action_three"        end    end    class ImplicitActionsController < ActionController::Base -    before_filter :find_only, :only => :edit -    before_filter :find_except, :except => :edit +    before_action :find_only, :only => :edit +    before_action :find_except, :except => :edit      private @@ -528,7 +528,7 @@ class FilterTest < ActionController::TestCase      end    end -  def test_non_yielding_around_filters_not_returning_false_do_not_raise +  def test_non_yielding_around_actions_not_returning_false_do_not_raise      controller = NonYieldingAroundFilterController.new      controller.instance_variable_set "@filter_return_value", true      assert_nothing_raised do @@ -536,7 +536,7 @@ class FilterTest < ActionController::TestCase      end    end -  def test_non_yielding_around_filters_returning_false_do_not_raise +  def test_non_yielding_around_actions_returning_false_do_not_raise      controller = NonYieldingAroundFilterController.new      controller.instance_variable_set "@filter_return_value", false      assert_nothing_raised do @@ -544,64 +544,64 @@ class FilterTest < ActionController::TestCase      end    end -  def test_after_filters_are_not_run_if_around_filter_returns_false +  def test_after_actions_are_not_run_if_around_action_returns_false      controller = NonYieldingAroundFilterController.new      controller.instance_variable_set "@filter_return_value", false      test_process(controller, "index")      assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters']    end -  def test_after_filters_are_not_run_if_around_filter_does_not_yield +  def test_after_actions_are_not_run_if_around_action_does_not_yield      controller = NonYieldingAroundFilterController.new      controller.instance_variable_set "@filter_return_value", true      test_process(controller, "index")      assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters']    end -  def test_added_filter_to_inheritance_graph -    assert_equal [ :ensure_login ], TestController.before_filters +  def test_added_action_to_inheritance_graph +    assert_equal [ :ensure_login ], TestController.before_actions    end    def test_base_class_in_isolation -    assert_equal [ ], ActionController::Base.before_filters +    assert_equal [ ], ActionController::Base.before_actions    end -  def test_prepending_filter -    assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_filters +  def test_prepending_action +    assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_actions    end -  def test_running_filters +  def test_running_actions      test_process(PrependingController)      assert_equal %w( wonderful_life ensure_login ), assigns["ran_filter"]    end -  def test_running_filters_with_proc +  def test_running_actions_with_proc      test_process(ProcController) -    assert assigns["ran_proc_filter"] +    assert assigns["ran_proc_action"]    end -  def test_running_filters_with_implicit_proc +  def test_running_actions_with_implicit_proc      test_process(ImplicitProcController) -    assert assigns["ran_proc_filter"] +    assert assigns["ran_proc_action"]    end -  def test_running_filters_with_class +  def test_running_actions_with_class      test_process(AuditController)      assert assigns["was_audited"]    end -  def test_running_anomolous_yet_valid_condition_filters +  def test_running_anomolous_yet_valid_condition_actions      test_process(AnomolousYetValidConditionController)      assert_equal %w( ensure_login ), assigns["ran_filter"] -    assert assigns["ran_class_filter"] -    assert assigns["ran_proc_filter1"] -    assert assigns["ran_proc_filter2"] +    assert assigns["ran_class_action"] +    assert assigns["ran_proc_action1"] +    assert assigns["ran_proc_action2"] -    test_process(AnomolousYetValidConditionController, "show_without_filter") +    test_process(AnomolousYetValidConditionController, "show_without_action")      assert_nil assigns["ran_filter"] -    assert !assigns["ran_class_filter"] -    assert !assigns["ran_proc_filter1"] -    assert !assigns["ran_proc_filter2"] +    assert !assigns["ran_class_action"] +    assert !assigns["ran_proc_action1"] +    assert !assigns["ran_proc_action2"]    end    def test_running_conditional_options @@ -614,59 +614,59 @@ class FilterTest < ActionController::TestCase      assert_equal %w( ensure_login ), assigns["ran_filter"]    end -  def test_skipping_class_filters +  def test_skipping_class_actions      test_process(ClassController) -    assert_equal true, assigns["ran_class_filter"] +    assert_equal true, assigns["ran_class_action"]      skipping_class_controller = Class.new(ClassController) do -      skip_before_filter ConditionalClassFilter +      skip_before_action ConditionalClassFilter      end      test_process(skipping_class_controller) -    assert_nil assigns['ran_class_filter'] +    assert_nil assigns['ran_class_action']    end -  def test_running_collection_condition_filters +  def test_running_collection_condition_actions      test_process(ConditionalCollectionFilterController)      assert_equal %w( ensure_login ), assigns["ran_filter"] -    test_process(ConditionalCollectionFilterController, "show_without_filter") +    test_process(ConditionalCollectionFilterController, "show_without_action")      assert_nil assigns["ran_filter"]      test_process(ConditionalCollectionFilterController, "another_action")      assert_nil assigns["ran_filter"]    end -  def test_running_only_condition_filters +  def test_running_only_condition_actions      test_process(OnlyConditionSymController)      assert_equal %w( ensure_login ), assigns["ran_filter"] -    test_process(OnlyConditionSymController, "show_without_filter") +    test_process(OnlyConditionSymController, "show_without_action")      assert_nil assigns["ran_filter"]      test_process(OnlyConditionProcController) -    assert assigns["ran_proc_filter"] -    test_process(OnlyConditionProcController, "show_without_filter") -    assert !assigns["ran_proc_filter"] +    assert assigns["ran_proc_action"] +    test_process(OnlyConditionProcController, "show_without_action") +    assert !assigns["ran_proc_action"]      test_process(OnlyConditionClassController) -    assert assigns["ran_class_filter"] -    test_process(OnlyConditionClassController, "show_without_filter") -    assert !assigns["ran_class_filter"] +    assert assigns["ran_class_action"] +    test_process(OnlyConditionClassController, "show_without_action") +    assert !assigns["ran_class_action"]    end -  def test_running_except_condition_filters +  def test_running_except_condition_actions      test_process(ExceptConditionSymController)      assert_equal %w( ensure_login ), assigns["ran_filter"] -    test_process(ExceptConditionSymController, "show_without_filter") +    test_process(ExceptConditionSymController, "show_without_action")      assert_nil assigns["ran_filter"]      test_process(ExceptConditionProcController) -    assert assigns["ran_proc_filter"] -    test_process(ExceptConditionProcController, "show_without_filter") -    assert !assigns["ran_proc_filter"] +    assert assigns["ran_proc_action"] +    test_process(ExceptConditionProcController, "show_without_action") +    assert !assigns["ran_proc_action"]      test_process(ExceptConditionClassController) -    assert assigns["ran_class_filter"] -    test_process(ExceptConditionClassController, "show_without_filter") -    assert !assigns["ran_class_filter"] +    assert assigns["ran_class_action"] +    test_process(ExceptConditionClassController, "show_without_action") +    assert !assigns["ran_class_action"]    end    def test_running_only_condition_and_conditional_options @@ -674,70 +674,70 @@ class FilterTest < ActionController::TestCase      assert_not assigns["ran_conditional_index_proc"]    end -  def test_running_before_and_after_condition_filters +  def test_running_before_and_after_condition_actions      test_process(BeforeAndAfterConditionController)      assert_equal %w( ensure_login clean_up_tmp), assigns["ran_filter"] -    test_process(BeforeAndAfterConditionController, "show_without_filter") +    test_process(BeforeAndAfterConditionController, "show_without_action")      assert_nil assigns["ran_filter"]    end -  def test_around_filter +  def test_around_action      test_process(AroundFilterController)      assert assigns["before_ran"]      assert assigns["after_ran"]    end -  def test_before_after_class_filter +  def test_before_after_class_action      test_process(BeforeAfterClassFilterController)      assert assigns["before_ran"]      assert assigns["after_ran"]    end -  def test_having_properties_in_around_filter +  def test_having_properties_in_around_action      test_process(AroundFilterController)      assert_equal "before and after", assigns["execution_log"]    end -  def test_prepending_and_appending_around_filter +  def test_prepending_and_appending_around_action      test_process(MixedFilterController)      assert_equal " before aroundfilter  before procfilter  before appended aroundfilter " +                   " after appended aroundfilter  after procfilter  after aroundfilter ",                   MixedFilterController.execution_log    end -  def test_rendering_breaks_filtering_chain +  def test_rendering_breaks_actioning_chain      response = test_process(RenderingController)      assert_equal "something else", response.body      assert !assigns["ran_action"]    end -  def test_before_filter_rendering_breaks_filtering_chain_for_after_filter +  def test_before_action_rendering_breaks_actioning_chain_for_after_action      test_process(RenderingController) -    assert_equal %w( before_filter_rendering ), assigns["ran_filter"] +    assert_equal %w( before_action_rendering ), assigns["ran_filter"]      assert !assigns["ran_action"]    end -  def test_before_filter_redirects_breaks_filtering_chain_for_after_filter -    test_process(BeforeFilterRedirectionController) +  def test_before_action_redirects_breaks_actioning_chain_for_after_action +    test_process(BeforeActionRedirectionController)      assert_response :redirect -    assert_equal "http://test.host/filter_test/before_filter_redirection/target_of_redirection", redirect_to_url -    assert_equal %w( before_filter_redirects ), assigns["ran_filter"] +    assert_equal "http://test.host/filter_test/before_action_redirection/target_of_redirection", redirect_to_url +    assert_equal %w( before_action_redirects ), assigns["ran_filter"]    end -  def test_before_filter_rendering_breaks_filtering_chain_for_preprend_after_filter -    test_process(RenderingForPrependAfterFilterController) -    assert_equal %w( before_filter_rendering ), assigns["ran_filter"] +  def test_before_action_rendering_breaks_actioning_chain_for_preprend_after_action +    test_process(RenderingForPrependAfterActionController) +    assert_equal %w( before_action_rendering ), assigns["ran_filter"]      assert !assigns["ran_action"]    end -  def test_before_filter_redirects_breaks_filtering_chain_for_preprend_after_filter -    test_process(BeforeFilterRedirectionForPrependAfterFilterController) +  def test_before_action_redirects_breaks_actioning_chain_for_preprend_after_action +    test_process(BeforeActionRedirectionForPrependAfterActionController)      assert_response :redirect -    assert_equal "http://test.host/filter_test/before_filter_redirection_for_prepend_after_filter/target_of_redirection", redirect_to_url -    assert_equal %w( before_filter_redirects ), assigns["ran_filter"] +    assert_equal "http://test.host/filter_test/before_action_redirection_for_prepend_after_action/target_of_redirection", redirect_to_url +    assert_equal %w( before_action_redirects ), assigns["ran_filter"]    end -  def test_filters_with_mixed_specialization_run_in_order +  def test_actions_with_mixed_specialization_run_in_order      assert_nothing_raised do        response = test_process(MixedSpecializationController, 'bar')        assert_equal 'bar', response.body @@ -758,7 +758,7 @@ class FilterTest < ActionController::TestCase      end    end -  def test_running_prepended_before_and_after_filter +  def test_running_prepended_before_and_after_action      test_process(PrependingBeforeAndAfterController)      assert_equal %w( before_all between_before_all_and_after_all after_all ), assigns["ran_filter"]    end @@ -775,26 +775,26 @@ class FilterTest < ActionController::TestCase      assert_equal %w( find_record ensure_login ), assigns["ran_filter"]    end -  def test_conditional_skipping_of_filters +  def test_conditional_skipping_of_actions      test_process(ConditionalSkippingController, "login")      assert_nil assigns["ran_filter"]      test_process(ConditionalSkippingController, "change_password")      assert_equal %w( ensure_login find_user ), assigns["ran_filter"]      test_process(ConditionalSkippingController, "login") -    assert !@controller.instance_variable_defined?("@ran_after_filter") +    assert !@controller.instance_variable_defined?("@ran_after_action")      test_process(ConditionalSkippingController, "change_password") -    assert_equal %w( clean_up ), @controller.instance_variable_get("@ran_after_filter") +    assert_equal %w( clean_up ), @controller.instance_variable_get("@ran_after_action")    end -  def test_conditional_skipping_of_filters_when_parent_filter_is_also_conditional +  def test_conditional_skipping_of_actions_when_parent_action_is_also_conditional      test_process(ChildOfConditionalParentController)      assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']      test_process(ChildOfConditionalParentController, 'another_action')      assert_nil assigns['ran_filter']    end -  def test_condition_skipping_of_filters_when_siblings_also_have_conditions +  def test_condition_skipping_of_actions_when_siblings_also_have_conditions      test_process(ChildOfConditionalParentController)      assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']      test_process(AnotherChildOfConditionalParentController) @@ -808,7 +808,7 @@ class FilterTest < ActionController::TestCase      assert_nil assigns['ran_filter']    end -  def test_a_rescuing_around_filter +  def test_a_rescuing_around_action      response = nil      assert_nothing_raised do        response = test_process(RescuedController) @@ -818,7 +818,7 @@ class FilterTest < ActionController::TestCase      assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body)    end -  def test_filters_obey_only_and_except_for_implicit_actions +  def test_actions_obey_only_and_except_for_implicit_actions      test_process(ImplicitActionsController, 'show')      assert_equal 'Except', assigns(:except)      assert_nil assigns(:only) @@ -852,7 +852,7 @@ class PostsController < ActionController::Base      include AroundExceptions    end -  module_eval %w(raises_before raises_after raises_both no_raise no_filter).map { |action| "def #{action}; default_action end" }.join("\n") +  module_eval %w(raises_before raises_after raises_both no_raise no_action).map { |action| "def #{action}; default_action end" }.join("\n")    private      def default_action @@ -861,9 +861,9 @@ class PostsController < ActionController::Base  end  class ControllerWithSymbolAsFilter < PostsController -  around_filter :raise_before, :only => :raises_before -  around_filter :raise_after, :only => :raises_after -  around_filter :without_exception, :only => :no_raise +  around_action :raise_before, :only => :raises_before +  around_action :raise_after, :only => :raises_after +  around_action :without_exception, :only => :no_raise    private      def raise_before @@ -895,7 +895,7 @@ class ControllerWithFilterClass < PostsController      end    end -  around_filter YieldingFilter, :only => :raises_after +  around_action YieldingFilter, :only => :raises_after  end  class ControllerWithFilterInstance < PostsController @@ -906,11 +906,11 @@ class ControllerWithFilterInstance < PostsController      end    end -  around_filter YieldingFilter.new, :only => :raises_after +  around_action YieldingFilter.new, :only => :raises_after  end  class ControllerWithProcFilter < PostsController -  around_filter(:only => :no_raise) do |c,b| +  around_action(:only => :no_raise) do |c,b|      c.instance_variable_set(:"@before", true)      b.call      c.instance_variable_set(:"@after", true) @@ -918,14 +918,14 @@ class ControllerWithProcFilter < PostsController  end  class ControllerWithNestedFilters < ControllerWithSymbolAsFilter -  around_filter :raise_before, :raise_after, :without_exception, :only => :raises_both +  around_action :raise_before, :raise_after, :without_exception, :only => :raises_both  end  class ControllerWithAllTypesOfFilters < PostsController -  before_filter :before -  around_filter :around -  after_filter :after -  around_filter :around_again +  before_action :before +  around_action :around +  after_action :after +  around_action :around_again    private    def before @@ -951,8 +951,8 @@ class ControllerWithAllTypesOfFilters < PostsController  end  class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters -  skip_filter :around_again -  skip_filter :after +  skip_action_callback :around_again +  skip_action_callback :after  end  class YieldingAroundFiltersTest < ActionController::TestCase @@ -963,7 +963,7 @@ class YieldingAroundFiltersTest < ActionController::TestCase      assert_nothing_raised { test_process(controller,'no_raise') }      assert_nothing_raised { test_process(controller,'raises_before') }      assert_nothing_raised { test_process(controller,'raises_after') } -    assert_nothing_raised { test_process(controller,'no_filter') } +    assert_nothing_raised { test_process(controller,'no_action') }    end    def test_with_symbol @@ -992,7 +992,7 @@ class YieldingAroundFiltersTest < ActionController::TestCase      assert assigns['after']    end -  def test_nested_filters +  def test_nested_actions      controller = ControllerWithNestedFilters      assert_nothing_raised do        begin @@ -1008,31 +1008,31 @@ class YieldingAroundFiltersTest < ActionController::TestCase      end    end -  def test_filter_order_with_all_filter_types +  def test_action_order_with_all_action_types      test_process(ControllerWithAllTypesOfFilters,'no_raise')      assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) after around (after yield)', assigns['ran_filter'].join(' ')    end -  def test_filter_order_with_skip_filter_method +  def test_action_order_with_skip_action_method      test_process(ControllerWithTwoLessFilters,'no_raise')      assert_equal 'before around (before yield) around (after yield)', assigns['ran_filter'].join(' ')    end -  def test_first_filter_in_multiple_before_filter_chain_halts +  def test_first_action_in_multiple_before_action_chain_halts      controller = ::FilterTest::TestMultipleFiltersController.new      response = test_process(controller, 'fail_1')      assert_equal ' ', response.body      assert_equal 1, controller.instance_variable_get(:@try)    end -  def test_second_filter_in_multiple_before_filter_chain_halts +  def test_second_action_in_multiple_before_action_chain_halts      controller = ::FilterTest::TestMultipleFiltersController.new      response = test_process(controller, 'fail_2')      assert_equal ' ', response.body      assert_equal 2, controller.instance_variable_get(:@try)    end -  def test_last_filter_in_multiple_before_filter_chain_halts +  def test_last_action_in_multiple_before_action_chain_halts      controller = ::FilterTest::TestMultipleFiltersController.new      response = test_process(controller, 'fail_3')      assert_equal ' ', response.body diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb index c95ef8a0c7..27871ef351 100644 --- a/actionpack/test/controller/localized_templates_test.rb +++ b/actionpack/test/controller/localized_templates_test.rb @@ -8,22 +8,24 @@ end  class LocalizedTemplatesTest < ActionController::TestCase    tests LocalizedController +  setup do +    @old_locale = I18n.locale +  end + +  teardown do +    I18n.locale = @old_locale +  end +    def test_localized_template_is_used -    old_locale = I18n.locale      I18n.locale = :de      get :hello_world      assert_equal "Gutten Tag", @response.body -  ensure -    I18n.locale = old_locale    end    def test_default_locale_template_is_used_when_locale_is_missing -    old_locale = I18n.locale      I18n.locale = :dk      get :hello_world      assert_equal "Hello World", @response.body -  ensure -    I18n.locale = old_locale    end    def test_use_fallback_locales @@ -36,13 +38,9 @@ class LocalizedTemplatesTest < ActionController::TestCase    end    def test_localized_template_has_correct_header_with_no_format_in_template_name -    old_locale = I18n.locale      I18n.locale = :it -      get :hello_world      assert_equal "Ciao Mondo", @response.body      assert_equal "text/html",  @response.content_type -  ensure -    I18n.locale = old_locale    end  end diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index 11ccb6cf3b..645ecae220 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -337,14 +337,26 @@ class IrregularInflectionParamsWrapperTest < ActionController::TestCase    tests ParamswrappernewsController    def test_uses_model_attribute_names_with_irregular_inflection -    ActiveSupport::Inflector.inflections do |inflect| -      inflect.irregular 'paramswrappernews_item', 'paramswrappernews' -    end +    with_dup do +      ActiveSupport::Inflector.inflections do |inflect| +        inflect.irregular 'paramswrappernews_item', 'paramswrappernews' +      end -    with_default_wrapper_options do -      @request.env['CONTENT_TYPE'] = 'application/json' -      post :parse, { 'username' => 'sikachu', 'test_attr' => 'test_value' } -      assert_parameters({ 'username' => 'sikachu', 'test_attr' => 'test_value', 'paramswrappernews_item' => { 'test_attr' => 'test_value' }}) +      with_default_wrapper_options do +        @request.env['CONTENT_TYPE'] = 'application/json' +        post :parse, { 'username' => 'sikachu', 'test_attr' => 'test_value' } +        assert_parameters({ 'username' => 'sikachu', 'test_attr' => 'test_value', 'paramswrappernews_item' => { 'test_attr' => 'test_value' }}) +      end      end    end + +  private + +  def with_dup +    original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en] +    ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup) +    yield +  ensure +    ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original) +  end  end diff --git a/actionpack/test/controller/render_other_test.rb b/actionpack/test/controller/render_other_test.rb index b5e74e373d..af50e11261 100644 --- a/actionpack/test/controller/render_other_test.rb +++ b/actionpack/test/controller/render_other_test.rb @@ -1,9 +1,5 @@  require 'abstract_unit' -ActionController.add_renderer :simon do |says, options| -  self.content_type  = Mime::TEXT -  self.response_body = "Simon says: #{says}" -end  class RenderOtherTest < ActionController::TestCase    class TestController < ActionController::Base @@ -15,7 +11,14 @@ class RenderOtherTest < ActionController::TestCase    tests TestController    def test_using_custom_render_option +    ActionController.add_renderer :simon do |says, options| +      self.content_type  = Mime::TEXT +      self.response_body = "Simon says: #{says}" +    end +      get :render_simon_says      assert_equal "Simon says: foo", @response.body +  ensure +    ActionController.remove_renderer :simon    end  end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 07c2115832..2a5aad9c0e 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -127,11 +127,12 @@ module RequestForgeryProtectionTests      @token      = "cf50faa3fe97702ca1ae"      SecureRandom.stubs(:base64).returns(@token) +    @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token      ActionController::Base.request_forgery_protection_token = :custom_authenticity_token    end    def teardown -    ActionController::Base.request_forgery_protection_token = nil +    ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token    end    def test_should_render_form_with_token_tag @@ -376,11 +377,12 @@ class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController    include RequestForgeryProtectionTests    setup do +    @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token      ActionController::Base.request_forgery_protection_token = :custom_authenticity_token    end    teardown do -    ActionController::Base.request_forgery_protection_token = nil +    ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token    end    test 'should emit a csrf-param meta tag and a csrf-token meta tag' do @@ -465,11 +467,12 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase      @old_logger = ActionController::Base.logger      @logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new      @token = "foobar" +    @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token      ActionController::Base.request_forgery_protection_token = @token    end    def teardown -    ActionController::Base.request_forgery_protection_token = nil +    ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token      super    end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index b22bc2dc25..660589a86e 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -87,7 +87,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase    def test_symbols_with_dashes      rs.draw do        get '/:artist/:song-omg', :to => lambda { |env| -        resp = ActiveSupport::JSON.encode env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] +        resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters          [200, {}, [resp]]        }      end @@ -99,7 +99,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase    def test_id_with_dash      rs.draw do        get '/journey/:id', :to => lambda { |env| -        resp = ActiveSupport::JSON.encode env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] +        resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters          [200, {}, [resp]]        }      end @@ -111,7 +111,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase    def test_dash_with_custom_regexp      rs.draw do        get '/:artist/:song-omg', :constraints => { :song => /\d+/ }, :to => lambda { |env| -        resp = ActiveSupport::JSON.encode env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] +        resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters          [200, {}, [resp]]        }      end @@ -124,7 +124,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase    def test_pre_dash      rs.draw do        get '/:artist/omg-:song', :to => lambda { |env| -        resp = ActiveSupport::JSON.encode env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] +        resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters          [200, {}, [resp]]        }      end @@ -136,7 +136,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase    def test_pre_dash_with_custom_regexp      rs.draw do        get '/:artist/omg-:song', :constraints => { :song => /\d+/ }, :to => lambda { |env| -        resp = ActiveSupport::JSON.encode env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] +        resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters          [200, {}, [resp]]        }      end @@ -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/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index aee139b95e..2c833aa2e5 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -32,14 +32,18 @@ end  class SendFileTest < ActionController::TestCase    include TestFileUtils -  Mime::Type.register "image/png", :png unless defined? Mime::PNG -    def setup +    @mime_type_defined = defined? Mime::PNG +    Mime::Type.register "image/png", :png unless @mime_type_defined      @controller = SendFileController.new      @request = ActionController::TestRequest.new      @response = ActionController::TestResponse.new    end +  teardown do +    Mime::Type.unregister :png unless @mime_type_defined +  end +    def test_file_nostream      @controller.options = { :stream => false }      response = nil diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 18a5d8b094..1141feeff7 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -164,7 +164,7 @@ XML    end    class DefaultUrlOptionsCachingController < ActionController::Base -    before_filter { @dynamic_opt = 'opt' } +    before_action { @dynamic_opt = 'opt' }      def test_url_options_reset        render text: url_for(params) 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/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index a427113763..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 @@ -3478,6 +3570,35 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest      @app.draw(&block)    end +  def test_missing_controller +    ex = assert_raises(ArgumentError) { +      draw do +        get '/foo/bar', :action => :index +      end +    } +    assert_match(/Missing :controller/, ex.message) +  end + +  def test_missing_action +    ex = assert_raises(ArgumentError) { +      assert_deprecated do +        draw do +          get '/foo/bar', :to => 'foo' +        end +      end +    } +    assert_match(/Missing :action/, ex.message) +  end + +  def test_missing_action_on_hash +    ex = assert_raises(ArgumentError) { +      draw do +        get '/foo/bar', :to => 'foo#' +      end +    } +    assert_match(/Missing :action/, ex.message) +  end +    def test_valid_controller_options_inside_namespace      draw do        namespace :admin do @@ -3519,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 @@ -3960,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/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb index 92544230b2..f7a06cfed4 100644 --- a/actionpack/test/dispatch/session/mem_cache_store_test.rb +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -49,6 +49,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest          assert_response :success          assert_equal 'foo: "bar"', response.body        end +    rescue Dalli::RingError => ex +      skip ex.message, ex.backtrace      end      def test_getting_nil_session_value @@ -57,6 +59,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest          assert_response :success          assert_equal 'foo: nil', response.body        end +    rescue Dalli::RingError => ex +      skip ex.message, ex.backtrace      end      def test_getting_session_value_after_session_reset @@ -76,6 +80,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest          assert_response :success          assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from memcached"        end +    rescue Dalli::RingError => ex +      skip ex.message, ex.backtrace      end      def test_getting_from_nonexistent_session @@ -85,6 +91,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest          assert_equal 'foo: nil', response.body          assert_nil cookies['_session_id'], "should only create session on write, not read"        end +    rescue Dalli::RingError => ex +      skip ex.message, ex.backtrace      end      def test_setting_session_value_after_session_reset @@ -106,6 +114,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest          assert_response :success          assert_not_equal session_id, response.body        end +    rescue Dalli::RingError => ex +      skip ex.message, ex.backtrace      end      def test_getting_session_id @@ -119,6 +129,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest          assert_response :success          assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"        end +    rescue Dalli::RingError => ex +      skip ex.message, ex.backtrace      end      def test_deserializes_unloaded_class @@ -133,6 +145,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest            assert_response :success          end        end +    rescue Dalli::RingError => ex +      skip ex.message, ex.backtrace      end      def test_doesnt_write_session_cookie_if_session_id_is_already_exists @@ -145,6 +159,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest          assert_response :success          assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"        end +    rescue Dalli::RingError => ex +      skip ex.message, ex.backtrace      end      def test_prevents_session_fixation @@ -160,6 +176,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest          assert_response :success          assert_not_equal session_id, cookies['_session_id']        end +    rescue Dalli::RingError => ex +      skip ex.message, ex.backtrace      end    rescue LoadError, RuntimeError, Dalli::DalliError      $stderr.puts "Skipping MemCacheStoreTest tests. Start memcached and try again." 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 db2d3bc10d..e092432b01 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -4,17 +4,10 @@ require 'abstract_unit'  module ActionDispatch    module Journey      class TestRouter < ActiveSupport::TestCase -      # TODO : clean up routing tests so we don't need this hack -      class StubDispatcher < Routing::RouteSet::Dispatcher -        def initialize -          super({}) -        end -      end -        attr_reader :routes        def setup -        @app       = StubDispatcher.new +        @app       = Routing::RouteSet::Dispatcher.new({})          @routes    = Routes.new          @router    = Router.new(@routes)          @formatter = Formatter.new(@routes) @@ -39,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}, {} @@ -56,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}, {} @@ -75,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}, {} @@ -95,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}, {} @@ -122,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, {}, {}, {} @@ -140,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' @@ -159,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 @@ -169,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' }, { }) @@ -182,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' }, { }) @@ -197,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 @@ -217,19 +210,22 @@ module ActionDispatch        end        def test_clear_trailing_slash_from_script_name_on_root_unanchored_routes -        strexp = Router::Strexp.new("/", {}, ['/', '.', '?'], false) +        route_set = Routing::RouteSet.new +        mapper = Routing::Mapper.new route_set + +        strexp = Router::Strexp.build("/", {}, ['/', '.', '?'], false)          path   = Path::Pattern.new strexp          app    = lambda { |env| [200, {}, ['success!']] } -        @router.routes.add_route(app, path, {}, {}, {}) +        mapper.get '/weblog', :to => app          env  = rack_env('SCRIPT_NAME' => '', 'PATH_INFO' => '/weblog') -        resp = @router.serve rails_env env +        resp = route_set.call env          assert_equal ['success!'], resp.last          assert_equal '', env['SCRIPT_NAME']        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' @@ -245,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' @@ -258,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' @@ -312,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 } @@ -325,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, {}, {}, {} @@ -335,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 = [] @@ -352,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( @@ -362,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, @@ -373,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( @@ -384,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( @@ -398,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( @@ -410,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( @@ -427,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, {}, {}, {}) @@ -449,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, {}, {}, {}) @@ -467,7 +463,7 @@ module ActionDispatch        end        def test_namespaced_controller -        strexp = Router::Strexp.new( +        strexp = Router::Strexp.build(            "/:controller(/:action(/:id))",            { :controller => /.+?/ },            ["/", ".", "?"] @@ -493,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'}) @@ -510,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' @@ -529,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' @@ -557,13 +553,15 @@ 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 -      RailsEnv = Struct.new(:env) -        def rails_env env, klass = ActionDispatch::Request          klass.new env        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/RUNNING_UNIT_TESTS.rdoc b/actionview/RUNNING_UNIT_TESTS.rdoc index 104b3e288d..c408882827 100644 --- a/actionview/RUNNING_UNIT_TESTS.rdoc +++ b/actionview/RUNNING_UNIT_TESTS.rdoc @@ -4,7 +4,7 @@ The easiest way to run the unit tests is through Rake. The default task runs  the entire test suite for all classes. For more information, checkout the  full array of rake tasks with "rake -T" -Rake can be found at http://rake.rubyforge.org +Rake can be found at http://docs.seattlerb.org/rake/.  == Running by hand diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index 824cdaa45e..f53cce32b0 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -11,7 +11,7 @@ module ActionView      # the assets exist before linking to them:      #      #   image_tag("rails.png") -    #   # => <img alt="Rails" src="/assets/rails.png" /> +    #   # => <img alt="Rails" src="/images/rails.png" />      #   stylesheet_link_tag("application")      #   # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />      module AssetTagHelper @@ -20,7 +20,8 @@ module ActionView        include AssetUrlHelper        include TagHelper -      # Returns an HTML script tag for each of the +sources+ provided. +      # Returns an HTML script tag for each of the +sources+ provided. If +      # you don't specify an extension, <tt>.js</tt> will be appended automatically.        #        # Sources may be paths to JavaScript files. Relative paths are assumed to be relative        # to <tt>assets/javascripts</tt>, full paths are assumed to be relative to the document @@ -33,19 +34,19 @@ module ActionView        # last argument.        #        # When the Asset Pipeline is enabled, you can pass the name of your manifest as -      # source, and include other JavaScript or CoffeeScript files inside the manifest. +      # source and include other JavaScript or CoffeeScript files inside the manifest.        #        #   javascript_include_tag "xmlhr" -      #   # => <script src="/assets/xmlhr.js?1284139606"></script> +      #   # => <script src="/javascripts/xmlhr.js?1284139606"></script>        #        #   javascript_include_tag "template.jst", extname: false -      #   # => <script src="/assets/template.jst?1284139606"></script> +      #   # => <script src="/javascripts/template.jst?1284139606"></script>        #        #   javascript_include_tag "xmlhr.js" -      #   # => <script src="/assets/xmlhr.js?1284139606"></script> +      #   # => <script src="/javascripts/xmlhr.js?1284139606"></script>        #        #   javascript_include_tag "common.javascript", "/elsewhere/cools" -      #   # => <script src="/assets/common.javascript?1284139606"></script> +      #   # => <script src="/javascripts/common.javascript?1284139606"></script>        #   #    <script src="/elsewhere/cools.js?1423139606"></script>        #        #   javascript_include_tag "http://www.example.com/xmlhr" @@ -72,22 +73,22 @@ module ActionView        # apply to all media types.        #        #   stylesheet_link_tag "style" -      #   # => <link href="/assets/style.css" media="screen" rel="stylesheet" /> +      #   # => <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />        #        #   stylesheet_link_tag "style.css" -      #   # => <link href="/assets/style.css" media="screen" rel="stylesheet" /> +      #   # => <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />        #        #   stylesheet_link_tag "http://www.example.com/style.css"        #   # => <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />        #        #   stylesheet_link_tag "style", media: "all" -      #   # => <link href="/assets/style.css" media="all" rel="stylesheet" /> +      #   # => <link href="/stylesheets/style.css" media="all" rel="stylesheet" />        #        #   stylesheet_link_tag "style", media: "print" -      #   # => <link href="/assets/style.css" media="print" rel="stylesheet" /> +      #   # => <link href="/stylesheets/style.css" media="print" rel="stylesheet" />        #        #   stylesheet_link_tag "random.styles", "/css/stylish" -      #   # => <link href="/assets/random.styles" media="screen" rel="stylesheet" /> +      #   # => <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" />        #   #    <link href="/css/stylish.css" media="screen" rel="stylesheet" />        def stylesheet_link_tag(*sources)          options = sources.extract_options!.stringify_keys @@ -158,17 +159,17 @@ module ActionView        # respectively:        #        #   favicon_link_tag -      #   # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon" /> +      #   # => <link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />        #        #   favicon_link_tag 'myicon.ico' -      #   # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" /> +      #   # => <link href="/images/myicon.ico" rel="shortcut icon" type="image/x-icon" />        #        # Mobile Safari looks for a different link tag, pointing to an image that        # will be used if you add the page to the home screen of an iOS device.        # The following call would generate such a tag:        #        #   favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' -      #   # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" /> +      #   # => <link href="/images/mb-icon.png" rel="apple-touch-icon" type="image/png" />        def favicon_link_tag(source='favicon.ico', options={})          tag('link', {            :rel  => 'shortcut icon', @@ -258,19 +259,19 @@ module ActionView        # ==== Examples        #        #   video_tag("trailer") -      #   # => <video src="/videos/trailer" /> +      #   # => <video src="/videos/trailer"></video>        #   video_tag("trailer.ogg") -      #   # => <video src="/videos/trailer.ogg" /> +      #   # => <video src="/videos/trailer.ogg"></video>        #   video_tag("trailer.ogg", controls: true, autobuffer: true) -      #   # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" /> +      #   # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" ></video>        #   video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png") -      #   # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" /> +      #   # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/images/screenshot.png"></video>        #   video_tag("/trailers/hd.avi", size: "16x16") -      #   # => <video src="/trailers/hd.avi" width="16" height="16" /> +      #   # => <video src="/trailers/hd.avi" width="16" height="16"></video>        #   video_tag("/trailers/hd.avi", size: "16") -      #   # => <video height="16" src="/trailers/hd.avi" width="16" /> +      #   # => <video height="16" src="/trailers/hd.avi" width="16"></video>        #   video_tag("/trailers/hd.avi", height: '32', width: '32') -      #   # => <video height="32" src="/trailers/hd.avi" width="32" /> +      #   # => <video height="32" src="/trailers/hd.avi" width="32"></video>        #   video_tag("trailer.ogg", "trailer.flv")        #   # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>        #   video_tag(["trailer.ogg", "trailer.flv"]) @@ -289,11 +290,11 @@ module ActionView        # your public audios directory.        #        #   audio_tag("sound") -      #   # => <audio src="/audios/sound" /> +      #   # => <audio src="/audios/sound"></audio>        #   audio_tag("sound.wav") -      #   # => <audio src="/audios/sound.wav" /> +      #   # => <audio src="/audios/sound.wav"></audio>        #   audio_tag("sound.wav", autoplay: true, controls: true) -      #   # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" /> +      #   # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio>        #   audio_tag("sound.wav", "sound.mid")        #   # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>        def audio_tag(*sources) diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb index 2b009ba961..006b15be91 100644 --- a/actionview/lib/action_view/helpers/asset_url_helper.rb +++ b/actionview/lib/action_view/helpers/asset_url_helper.rb @@ -7,10 +7,10 @@ module ActionView      # urls.      #      #   image_path("rails.png") -    #   # => "/assets/rails.png" +    #   # => "/images/rails.png"      #      #   image_url("rails.png") -    #   # => "http://www.example.com/assets/rails.png" +    #   # => "http://www.example.com/images/rails.png"      #      # === Using asset hosts      # @@ -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" @@ -113,9 +116,9 @@ module ActionView        #        # All other asset *_path helpers delegate through this method.        # -      #   asset_path "application.js"                     # => /application.js -      #   asset_path "application", type: :javascript     # => /javascripts/application.js -      #   asset_path "application", type: :stylesheet     # => /stylesheets/application.css +      #   asset_path "application.js"                     # => /assets/application.js +      #   asset_path "application", type: :javascript     # => /assets/application.js +      #   asset_path "application", type: :stylesheet     # => /assets/application.css        #   asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js        def asset_path(source, options = {})          return "" unless source.present? @@ -153,7 +156,7 @@ module ActionView        # All other options provided are forwarded to +asset_path+ call.        #        #   asset_url "application.js"                                 # => http://example.com/application.js -      #   asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/javascripts/application.js +      #   asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js        #        def asset_url(source, options = {})          path_to_asset(source, options.merge(:protocol => :request)) @@ -231,7 +234,7 @@ module ActionView        # Computes the path to a javascript asset in the public javascripts directory.        # If the +source+ filename has no extension, .js will be appended (except for explicit URIs)        # Full paths from the document root will be passed through. -      # Used internally by javascript_include_tag to build the script path. +      # Used internally by +javascript_include_tag+ to build the script path.        #        #   javascript_path "xmlhr"                              # => /javascripts/xmlhr.js        #   javascript_path "dir/xmlhr.js"                       # => /javascripts/dir/xmlhr.js @@ -251,7 +254,7 @@ module ActionView        alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route        # Computes the path to a stylesheet asset in the public stylesheets directory. -      # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs). +      # If the +source+ filename has no extension, .css will be appended (except for explicit URIs).        # Full paths from the document root will be passed through.        # Used internally by +stylesheet_link_tag+ to build the stylesheet path.        # @@ -276,9 +279,9 @@ module ActionView        # Full paths from the document root will be passed through.        # Used internally by +image_tag+ to build the image path:        # -      #   image_path("edit")                                         # => "/assets/edit" -      #   image_path("edit.png")                                     # => "/assets/edit.png" -      #   image_path("icons/edit.png")                               # => "/assets/icons/edit.png" +      #   image_path("edit")                                         # => "/images/edit" +      #   image_path("edit.png")                                     # => "/images/edit.png" +      #   image_path("icons/edit.png")                               # => "/images/icons/edit.png"        #   image_path("/icons/edit.png")                              # => "/icons/edit.png"        #   image_path("http://www.example.com/img/edit.png")          # => "http://www.example.com/img/edit.png"        # @@ -342,9 +345,9 @@ module ActionView        # Computes the path to a font asset.        # Full paths from the document root will be passed through.        # -      #   font_path("font")                                           # => /assets/font -      #   font_path("font.ttf")                                       # => /assets/font.ttf -      #   font_path("dir/font.ttf")                                   # => /assets/dir/font.ttf +      #   font_path("font")                                           # => /fonts/font +      #   font_path("font.ttf")                                       # => /fonts/font.ttf +      #   font_path("dir/font.ttf")                                   # => /fonts/dir/font.ttf        #   font_path("/dir/font.ttf")                                  # => /dir/font.ttf        #   font_path("http://www.example.com/dir/font.ttf")            # => http://www.example.com/dir/font.ttf        def font_path(source, options = {}) 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:        #     updated_at:        #     username: testing -      #        #     age: 42        #     password: xyz        #     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/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index e5cb843670..049af275b6 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -48,7 +48,7 @@ module ActionView        # Change allowed default attributes        #        #   class Application < Rails::Application -      #     config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style' +      #     config.action_view.sanitized_allowed_attributes = ['id', 'class', 'style']        #   end        #        # Please note that sanitizing user-provided text does not guarantee that the @@ -204,7 +204,7 @@ module ActionView          # Adds to the Set of allowed HTML attributes for the +sanitize+ helper.          #          #   class Application < Rails::Application -        #     config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc' +        #     config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc']          #   end          #          def sanitized_allowed_attributes=(attributes) 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/abstract_unit.rb b/actionview/test/abstract_unit.rb index 9eae3a4fbd..7c71fdabd1 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -274,7 +274,6 @@ ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)  module ActionController    class Base -    include ActionController::Testing      # This stub emulates the Railtie including the URL helpers from a Rails application      include SharedTestRoutes.url_helpers      include SharedTestRoutes.mounted_helpers 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 500be2a04a..f6beff14e1 100644 --- a/activemodel/README.rdoc +++ b/activemodel/README.rdoc @@ -147,7 +147,7 @@ behavior out of the box:        extend ActiveModel::Naming      end -    NamedPerson.model_name        # => "NamedPerson" +    NamedPerson.model_name.name   # => "NamedPerson"      NamedPerson.model_name.human  # => "Named person"    {Learn more}[link:classes/ActiveModel/Naming.html] @@ -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 11ebfe6cc0..5219de2606 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -204,7 +204,7 @@ module ActiveModel    #     extend ActiveModel::Naming    #   end    # -  #   BookCover.model_name        # => "BookCover" +  #   BookCover.model_name.name   # => "BookCover"    #   BookCover.model_name.human  # => "Book cover"    #    #   BookCover.model_name.i18n_key              # => :book_cover @@ -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 041872034f..3adf7d70be 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,60 @@ +*   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`. + +    Fixes #15369. + +    *Yves Senn* + +*   `rake railties:install:migrations` respects the order of railties. + +    *Arun Agrawal* + +*   Fix redefine a has_and_belongs_to_many inside inherited class +    Fixing regression case, where redefining the same has_an_belongs_to_many +    definition into a subclass would raise. + +    Fixes #14983. + +    *arthurnn* + +*   Fix has_and_belongs_to_many public reflection. +    When defining a has_and_belongs_to_many, internally we convert that to two has_many. +    But as `reflections` is a public API, people expect to see the right macro. + +    Fixes #14682. + +    *arthurnn* +  *   Fixed serialization for records with an attribute named `format`.      Fixes #15188. @@ -22,7 +79,7 @@  *   Change belongs_to touch to be consistent with timestamp updates -    If a model is set up with a belongs_to: touch relatinoship the parent +    If a model is set up with a belongs_to: touch relationship the parent      record will only be touched if the record was modified. This makes it      consistent with timestamp updating on the record itself. diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc index e5b68750e4..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. @@ -130,7 +130,7 @@ This would also define the following accessors: `Product#name` and    SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html]. -* Logging support for Log4r[http://log4r.rubyforge.org] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]. +* Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].      ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)      ActiveRecord::Base.logger = Log4r::Logger.new('Application Log') @@ -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.rb b/activerecord/lib/active_record/associations.rb index 8d77fad2d5..07dfc448e7 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -50,7 +50,7 @@ module ActiveRecord      def initialize(reflection)        through_reflection      = reflection.through_reflection        source_reflection_names = reflection.source_reflection_names -      source_associations     = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect } +      source_associations     = reflection.through_reflection.klass._reflections.keys        super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")      end    end @@ -151,7 +151,7 @@ module ActiveRecord        association = association_instance_get(name)        if association.nil? -        raise AssociationNotFoundError.new(self, name) unless reflection = self.class.reflect_on_association(name) +        raise AssociationNotFoundError.new(self, name) unless reflection = self.class._reflect_on_association(name)          association = reflection.association_class.new(self, reflection)          association_instance_set(name, association)        end @@ -1577,6 +1577,8 @@ module ActiveRecord            scope   = nil          end +        habtm_reflection = ActiveRecord::Reflection::AssociationReflection.new(:has_and_belongs_to_many, name, scope, options, self) +          builder = Builder::HasAndBelongsToMany.new name, self, options          join_model = builder.through_model @@ -1590,6 +1592,7 @@ module ActiveRecord          Builder::HasMany.define_callbacks self, middle_reflection          Reflection.add_reflection self, middle_reflection.name, middle_reflection +        middle_reflection.parent_reflection = [name.to_s, habtm_reflection]          include Module.new {            class_eval <<-RUBY, __FILE__, __LINE__ + 1 @@ -1610,6 +1613,7 @@ module ActiveRecord          end          has_many name, scope, hm_options, &extension +        self._reflections[name.to_s].parent_reflection = [name.to_s, habtm_reflection]        end      end    end diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 85109aee6c..a6a1947148 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -32,8 +32,18 @@ module ActiveRecord              join.left.downcase.scan(                /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/              ).size -          else +          elsif join.respond_to? :left              join.left.table_name == name ? 1 : 0 +          else +            # this branch is reached by two tests: +            # +            # activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37 +            #   with :posts +            # +            # activerecord/test/cases/associations/eager_test.rb:1133 +            #   with :comments +            # +            0            end          end diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 9ad2d2fb12..4a04303fb8 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -160,7 +160,7 @@ module ActiveRecord        def marshal_load(data)          reflection_name, ivars = data          ivars.each { |name, val| instance_variable_set(name, val) } -        @reflection = @owner.class.reflect_on_association(reflection_name) +        @reflection = @owner.class._reflect_on_association(reflection_name)        end        def initialize_attributes(record) #:nodoc: 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/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index 30b11c01eb..0ad5206980 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -66,13 +66,13 @@ module ActiveRecord::Associations::Builder          def self.add_left_association(name, options)            belongs_to name, options -          self.left_reflection = reflect_on_association(name) +          self.left_reflection = _reflect_on_association(name)          end          def self.add_right_association(name, options)            rhs_name = name.to_s.singularize.to_sym            belongs_to rhs_name, options -          self.right_reflection = reflect_on_association(rhs_name) +          self.right_reflection = _reflect_on_association(rhs_name)          end        } diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index f5e911c739..2727e23870 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -100,7 +100,8 @@ module ActiveRecord          # Hence this method.          def inverse_updates_counter_cache?(reflection = reflection())            counter_name = cached_counter_attribute_name(reflection) -          reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection| +          reflection.klass._reflections.values.any? { |inverse_reflection| +            :belongs_to == inverse_reflection.macro &&              inverse_reflection.counter_cache_column == counter_name            }          end diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 5842be3a7b..35659766d3 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -207,7 +207,7 @@ module ActiveRecord        end        def find_reflection(klass, name) -        klass.reflect_on_association(name) or +        klass._reflect_on_association(name) or            raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"        end @@ -217,7 +217,7 @@ module ActiveRecord            reflection.check_validity!            reflection.check_eager_loadable! -          if reflection.options[:polymorphic] +          if reflection.polymorphic?              raise EagerLoadPolymorphicError.new(reflection)            end diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 42571d6af0..7519fec10a 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -143,6 +143,7 @@ module ActiveRecord        def grouped_records(association, records)          h = {}          records.each do |record| +          next unless record            assoc = record.association(association)            klasses = h[assoc.reflection] ||= {}            (klasses[assoc.klass] ||= []) << record 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..b6520b9b3d 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 @@ -424,7 +419,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 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..6cd4e43ddd 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,8 @@ 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 = column_for_attribute(attr) || Type::Value.new +        column.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 53a9c874bf..148fc9eae5 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -58,74 +58,24 @@ module ActiveRecord                      Coders::YAMLColumn.new(class_name_or_coder)                    end +          type = columns_hash[attr_name.to_s].cast_type +          if type.serialized? +            type = type.subtype +          end +          property attr_name, Type::Serialized.new(type, coder) +            # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy            # has its own hash of own serialized attributes            self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)          end        end -      class Type # :nodoc: -        def initialize(column) -          @column = column -        end - -        def type_cast(value) -          if value.state == :serialized -            value.unserialized_value @column.type_cast value.value -          else -            value.unserialized_value -          end -        end - -        def type -          @column.type -        end - -        def accessor -          ActiveRecord::Store::IndifferentHashAccessor -        end -      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 - -        def unserialize(v) -          self.state = :unserialized -          self.value = coder.load(v) -        end - -        def serialize -          self.state = :serialized -          self.value = coder.dump(value) -        end -      end        # This is only added to the model when serialize is called, which        # ensures we do not make things slower when serialization is not used.        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] = 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 @@ -133,66 +83,6 @@ module ActiveRecord          def keys_for_partial_write            super | (attributes.keys & self.class.serialized_attributes.keys)          end - -        def type_cast_attribute_for_write(column, value) -          if column && coder = self.class.serialized_attributes[column.name] -            Attribute.new(coder, value, :unserialized) -          else -            super -          end -        end - -        def raw_type_cast_attribute_for_write(column, value) -          if column && coder = self.class.serialized_attributes[column.name] -            Attribute.new(coder, value, :serialized) -          else -            super -          end -        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 dfebb2cf56..18778698e8 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -2,6 +2,8 @@ module ActiveRecord    module AttributeMethods      module TimeZoneConversion        class Type # :nodoc: +        delegate :type, :type_cast_for_database, to: :@column +          def initialize(column)            @column = column          end @@ -10,10 +12,6 @@ module ActiveRecord            value = @column.type_cast(value)            value.acts_like?(:time) ? value.in_time_zone : value          end - -        def type -          @column.type -        end        end        extend ActiveSupport::Concern @@ -38,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 56441d7324..c3e601a208 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -26,8 +26,6 @@ module ActiveRecord          protected          if Module.methods_transplantable? -          # See define_method_attribute in read.rb for an explanation of -          # this code.            def define_method_attribute=(name)              method = WriterMethodCache[name]              generated_attribute_methods.module_eval { @@ -55,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_attribute_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_attribute_for_write) +        write_attribute_with_type_cast(attr_name, value, false)        end        private @@ -68,27 +66,22 @@ module ActiveRecord          write_attribute(attribute_name, value)        end -      def type_cast_attribute_for_write(column, value) -        return value unless column - -        column.type_cast_for_write value -      end -      alias_method :raw_type_cast_attribute_for_write, :type_cast_attribute_for_write - -      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)          # 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 +          @attributes[attr_name] = value          end -        if column || @attributes.has_key?(attr_name) -          @attributes[attr_name] = send(type_cast_method, column, value) +        if column && should_type_cast +          @raw_attributes[attr_name] = column.type_cast_for_write(value) +        elsif !should_type_cast || @raw_attributes.has_key?(attr_name) +          @raw_attributes[attr_name] = value          else            raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"          end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 1a4d2957ec..74e2a8e6b9 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -147,6 +147,7 @@ module ActiveRecord        private          def define_non_cyclic_method(name, &block) +          return if method_defined?(name)            define_method(name) do |*args|              result = true; @_already_called ||= {}              # Loop prevention for validation of associations @@ -179,30 +180,28 @@ module ActiveRecord            validation_method = :"validate_associated_records_for_#{reflection.name}"            collection = reflection.collection? -          unless method_defined?(save_method) -            if collection -              before_save :before_save_collection_association - -              define_non_cyclic_method(save_method) { save_collection_association(reflection) } -              # Doesn't use after_save as that would save associations added in after_create/after_update twice -              after_create save_method -              after_update save_method -            elsif reflection.macro == :has_one -              define_method(save_method) { save_has_one_association(reflection) } -              # Configures two callbacks instead of a single after_save so that -              # the model may rely on their execution order relative to its -              # own callbacks. -              # -              # For example, given that after_creates run before after_saves, if -              # we configured instead an after_save there would be no way to fire -              # a custom after_create callback after the child association gets -              # created. -              after_create save_method -              after_update save_method -            else -              define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) } -              before_save save_method -            end +          if collection +            before_save :before_save_collection_association + +            define_non_cyclic_method(save_method) { save_collection_association(reflection) } +            # Doesn't use after_save as that would save associations added in after_create/after_update twice +            after_create save_method +            after_update save_method +          elsif reflection.macro == :has_one +            define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method) +            # Configures two callbacks instead of a single after_save so that +            # the model may rely on their execution order relative to its +            # own callbacks. +            # +            # For example, given that after_creates run before after_saves, if +            # we configured instead an after_save there would be no way to fire +            # a custom after_create callback after the child association gets +            # created. +            after_create save_method +            after_update save_method +          else +            define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) } +            before_save save_method            end            if reflection.validate? && !method_defined?(validation_method) @@ -273,9 +272,11 @@ module ActiveRecord        # go through nested autosave associations that are loaded in memory (without loading        # any new ones), and return true if is changed for autosave        def nested_records_changed_for_autosave? -        self.class.reflect_on_all_autosave_associations.any? do |reflection| -          association = association_instance_get(reflection.name) -          association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? } +        self.class._reflections.values.any? do |reflection| +          if reflection.options[:autosave] +            association = association_instance_get(reflection.name) +            association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? } +          end          end        end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index db4d5f0129..8b0fffcf06 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -19,6 +19,7 @@ require 'active_record/errors'  require 'active_record/log_subscriber'  require 'active_record/explain_subscriber'  require 'active_record/relation/delegation' +require 'active_record/properties'  module ActiveRecord #:nodoc:    # = Active Record @@ -321,6 +322,7 @@ module ActiveRecord #:nodoc:      include Reflection      include Serialization      include Store +    include Properties    end    ActiveSupport.run_load_hooks(:active_record, Base) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index bc47412405..7ff5001796 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -227,6 +227,10 @@ module ActiveRecord          end        end +      def open_transactions +        @transaction.number +      end +        def current_transaction #:nodoc:          @transaction        end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 75501852ed..04ae67234f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -9,34 +9,16 @@ module ActiveRecord          # records are quoted as their primary key          return value.quoted_id if value.respond_to?(:quoted_id) -        case value -        when String, ActiveSupport::Multibyte::Chars -          value = value.to_s -          return "'#{quote_string(value)}'" unless column - -          case column.type -          when :integer then value.to_i.to_s -          when :float then value.to_f.to_s -          else -            "'#{quote_string(value)}'" -          end - -        when true, false -          if column && column.type == :integer -            value ? '1' : '0' -          else -            value ? quoted_true : quoted_false -          end -          # BigDecimals need to be put in a non-normalized form and quoted. -        when nil        then "NULL" -        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))}'" +        # FIXME: The only case we get an object other than nil or a real column +        # is `SchemaStatements#add_column` with a PG array that has a non-empty default +        # value. Is this really the only case? Are we missing tests for other types? +        # We should have a real column object passed (or nil) here, and check for that +        # instead +        if column.respond_to?(:type_cast_for_database) +          value = column.type_cast_for_database(value)          end + +        _quote(value)        end        # Cast a +value+ to a type that the database understands. For example, @@ -47,34 +29,19 @@ module ActiveRecord            return value.id          end -        case value -        when String, ActiveSupport::Multibyte::Chars -          value = value.to_s -          return value unless column - -          case column.type -          when :integer then value.to_i -          when :float then value.to_f -          else -            value -          end - -        when true, false -          if column && column.type == :integer -            value ? 1 : 0 -          else -            value ? 't' : 'f' -          end -          # BigDecimals need to be put in a non-normalized form and quoted. -        when nil        then nil -        when BigDecimal then value.to_s('F') -        when Numeric    then value -        when Date, Time then quoted_date(value) -        when Symbol     then value.to_s -        else -          to_type = column ? " to #{column.type}" : "" -          raise TypeError, "can't cast #{value.class}#{to_type}" +        # FIXME: The only case we get an object other than nil or a real column +        # is `SchemaStatements#add_column` with a PG array that has a non-empty default +        # value. Is this really the only case? Are we missing tests for other types? +        # We should have a real column object passed (or nil) here, and check for that +        # instead +        if column.respond_to?(:type_cast_for_database) +          value = column.type_cast_for_database(value)          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) @@ -109,10 +76,18 @@ module ActiveRecord          "'t'"        end +      def unquoted_true +        't' +      end +        def quoted_false          "'f'"        end +      def unquoted_false +        'f' +      end +        def quoted_date(value)          if value.acts_like?(:time)            zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal @@ -124,6 +99,45 @@ module ActiveRecord          value.to_s(:db)        end + +      private + +      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_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index ca5db4095e..b589cfee09 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -1,9 +1,9 @@  require 'date'  require 'bigdecimal'  require 'bigdecimal/util' +require 'active_record/type'  require 'active_support/core_ext/benchmark'  require 'active_record/connection_adapters/schema_cache' -require 'active_record/connection_adapters/type'  require 'active_record/connection_adapters/abstract/schema_dumper'  require 'active_record/connection_adapters/abstract/schema_creation'  require 'monitor' @@ -325,10 +325,6 @@ module ActiveRecord          @connection        end -      def open_transactions -        @transaction.number -      end -        def create_savepoint(name = nil)        end @@ -367,6 +363,10 @@ module ActiveRecord          end        end +      def new_column(name, default, cast_type, sql_type = nil, null = true) +        Column.new(name, default, cast_type, sql_type, null) +      end +        protected        def lookup_cast_type(sql_type) # :nodoc: @@ -396,6 +396,7 @@ module ActiveRecord            precision = extract_precision(sql_type)            if scale == 0 +            # FIXME: Remove this class as well              Type::DecimalWithoutScale.new(precision: precision)            else              Type::Decimal.new(precision: precision, scale: scale) 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 d3f8470c30..759ac9943f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -178,17 +178,6 @@ module ActiveRecord          true        end -      def type_cast(value, column) -        case value -        when TrueClass -          1 -        when FalseClass -          0 -        else -          super -        end -      end -        # MySQL 4 technically support transaction isolation, but it is affected by a bug        # where the transaction level gets persisted for the whole session:        # @@ -217,8 +206,7 @@ module ActiveRecord          raise NotImplementedError        end -      def new_column(field, default, sql_type, null, collation, extra = "") # :nodoc: -        cast_type = lookup_cast_type(sql_type) +      def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:          Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)        end @@ -230,12 +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}'" -        elsif value.kind_of?(BigDecimal) -          value.to_s("F") +      def _quote(value) # :nodoc: +        if value.is_a?(Type::Binary::Data) +          "x'#{value.hex}'"          else            super          end @@ -253,10 +238,18 @@ module ActiveRecord          QUOTED_TRUE        end +      def unquoted_true +        1 +      end +        def quoted_false          QUOTED_FALSE        end +      def unquoted_false +        0 +      end +        # REFERENTIAL INTEGRITY ====================================        def disable_referential_integrity #:nodoc: @@ -430,7 +423,9 @@ module ActiveRecord          execute_and_free(sql, 'SCHEMA') do |result|            each_hash(result).map do |field|              field_name = set_field_encoding(field[:Field]) -            new_column(field_name, field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra]) +            sql_type = field[:Type] +            cast_type = lookup_cast_type(sql_type) +            new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])            end          end        end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 42aabd6d7f..3b0dcbc6a7 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -14,11 +14,12 @@ module ActiveRecord        end        attr_reader :name, :default, :cast_type, :null, :sql_type, :default_function -      attr_accessor :coder -      alias :encoded? :coder - -      delegate :type, :precision, :scale, :limit, :klass, :text?, :number?, :binary?, :type_cast_for_write, to: :cast_type +      delegate :type, :precision, :scale, :limit, :klass, :accessor, +        :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.        # @@ -36,22 +37,12 @@ module ActiveRecord          @null             = null          @default          = extract_default(default)          @default_function = nil -        @coder            = nil        end        def has_default?          !default.nil?        end -      # Casts value to an appropriate instance. -      def type_cast(value) -        if encoded? -          coder.load(value) -        else -          cast_type.type_cast(value) -        end -      end -        # Returns the human name of the column name.        #        # ===== Examples diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index aa8a91ed39..909bba8c7d 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -298,7 +298,7 @@ module ActiveRecord          end          class << self -          TYPES = ConnectionAdapters::Type::HashLookupTypeMap.new # :nodoc: +          TYPES = Type::HashLookupTypeMap.new # :nodoc:            delegate :register_type, :alias_type, to: :TYPES 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/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index 9a5e2d05ef..a579746815 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -35,10 +35,6 @@ module ActiveRecord          end        end        # :startdoc: - -      def accessor -        cast_type.accessor -      end      end    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 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/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb index 98f369a7f8..bf680b6624 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -8,7 +8,13 @@ module ActiveRecord            end            def type_cast_for_write(value) -            ConnectionAdapters::PostgreSQLColumn.hstore_to_string value +            # roundtrip to ensure uniform uniform types +            # TODO: This is not an efficient solution. +            cast_value(type_cast_for_database(value)) +          end + +          def type_cast_for_database(value) +            ConnectionAdapters::PostgreSQLColumn.hstore_to_string(value)            end            def cast_value(value) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb index 42bf5656f4..42a5110ffd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb @@ -8,7 +8,13 @@ module ActiveRecord            end            def type_cast_for_write(value) -            ConnectionAdapters::PostgreSQLColumn.json_to_string value +            # roundtrip to ensure uniform uniform types +            # TODO: This is not an efficient solution. +            cast_value(type_cast_for_database(value)) +          end + +          def type_cast_for_database(value) +            ConnectionAdapters::PostgreSQLColumn.json_to_string(value)            end            def cast_value(value) 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 9e53d10bb4..b2aeb3a058 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -5,7 +5,7 @@ module ActiveRecord          private          def visit_AddColumn(o) -          sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale) +          sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)            sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"            add_column_options!(sql, column_options(o))          end @@ -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,12 +179,16 @@ 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) -            PostgreSQLColumn.new(column_name, default_value, oid, type, notnull == 'f', default_function) +            new_column(column_name, default_value, oid, type, notnull == 'f', default_function)            end          end +        def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc: +          PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function) +        end +          # Returns the current database name.          def current_database            query('select current_database()', 'SCHEMA')[0][0] @@ -269,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) @@ -308,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 @@ -326,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 @@ -338,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 @@ -372,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 @@ -485,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 4620206e1a..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: @@ -119,13 +123,13 @@ module ActiveRecord          ADAPTER_NAME        end -      def schema_creation +      def schema_creation # :nodoc:          PostgreSQL::SchemaCreation.new self        end        # Adds `:array` option to the default set provided by the        # AbstractAdapter -      def prepare_column_options(column, types) +      def prepare_column_options(column, types) # :nodoc:          spec = super          spec[:array] = 'true' if column.respond_to?(:array) && column.array          spec[:default] = "\"#{column.default_function}\"" if column.default_function @@ -406,7 +410,7 @@ module ActiveRecord        private -        def get_oid_type(oid, fmod, column_name, sql_type = '') +        def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:            if !type_map.key?(oid)              load_additional_types(type_map, [oid])            end @@ -419,7 +423,7 @@ module ActiveRecord            }          end -        def initialize_type_map(m) +        def initialize_type_map(m) # :nodoc:            register_class_with_limit m, 'int2', OID::Integer            m.alias_type 'int4', 'int2'            m.alias_type 'int8', 'int2' @@ -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 @@ -478,6 +482,8 @@ module ActiveRecord              # places after decimal  = fmod - 4 & 0xffff              # places before decimal = (fmod - 4) >> 16 & 0xffff              if fmod && (fmod - 4 & 0xffff).zero? +              # FIXME: Remove this class, and the second argument to +              # lookups on PG                Type::DecimalWithoutScale.new(precision: precision)              else                OID::Decimal.new(precision: precision, scale: scale) @@ -496,7 +502,7 @@ module ActiveRecord          end          # Extracts the value from a PostgreSQL column default definition. -        def extract_value_from_default(default) +        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, @@ -505,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 @@ -548,6 +561,8 @@ module ActiveRecord              # JSON              when /\A'(.*)'::json\z/                $1 +            when /\A'(.*)'::money\z/ +              $1              # Object identifier types              when /\A-?\d+\z/                $1 @@ -558,15 +573,15 @@ module ActiveRecord            end          end -        def extract_default_function(default_value, default) +        def extract_default_function(default_value, default) # :nodoc:            default if has_default_function?(default_value, default)          end -        def has_default_function?(default_value, default) +        def has_default_function?(default_value, default) # :nodoc:            !default_value && (%r{\w+\(.*\)} === default)          end -        def load_additional_types(type_map, oids = nil) +        def load_additional_types(type_map, oids = nil) # :nodoc:            if supports_ranges?              query = <<-SQL                SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype @@ -760,16 +775,6 @@ module ActiveRecord            end_sql          end -        def extract_pg_identifier_from_name(name) # :nodoc: -          match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/) - -          if match_data -            rest = name[match_data[0].length, name.length] -            rest = rest[1, rest.length] if rest.start_with? "." -            [match_data[1], (rest.length > 0 ? rest : nil)] -          end -        end -          def extract_table_ref_from_insert_sql(sql) # :nodoc:            sql[/into\s+([^\(]*).*values\s*\(/im]            $1.strip if $1 diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index a5e2619cb8..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 @@ -393,7 +392,7 @@ module ActiveRecord            sql_type = field['type']            cast_type = lookup_cast_type(sql_type) -          Column.new(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0) +          new_column(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0)          end        end diff --git a/activerecord/lib/active_record/connection_adapters/type.rb b/activerecord/lib/active_record/connection_adapters/type.rb deleted file mode 100644 index bab7a3ff7e..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'active_record/connection_adapters/type/numeric' -require 'active_record/connection_adapters/type/time_value' -require 'active_record/connection_adapters/type/value' - -require 'active_record/connection_adapters/type/binary' -require 'active_record/connection_adapters/type/boolean' -require 'active_record/connection_adapters/type/date' -require 'active_record/connection_adapters/type/date_time' -require 'active_record/connection_adapters/type/decimal' -require 'active_record/connection_adapters/type/decimal_without_scale' -require 'active_record/connection_adapters/type/float' -require 'active_record/connection_adapters/type/integer' -require 'active_record/connection_adapters/type/string' -require 'active_record/connection_adapters/type/text' -require 'active_record/connection_adapters/type/time' - -require 'active_record/connection_adapters/type/type_map' -require 'active_record/connection_adapters/type/hash_lookup_type_map' - -module ActiveRecord -  module ConnectionAdapters -    module Type # :nodoc: -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/binary.rb b/activerecord/lib/active_record/connection_adapters/type/binary.rb deleted file mode 100644 index 4b2d1a66e0..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/binary.rb +++ /dev/null @@ -1,19 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      class Binary < Value # :nodoc: -        def type -          :binary -        end - -        def binary? -          true -        end - -        def klass -          ::String -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/boolean.rb b/activerecord/lib/active_record/connection_adapters/type/boolean.rb deleted file mode 100644 index 2337bdd563..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/boolean.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      class Boolean < Value # :nodoc: -        def type -          :boolean -        end - -        private - -        def cast_value(value) -          if value == '' -            nil -          else -            Column::TRUE_VALUES.include?(value) -          end -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/date.rb b/activerecord/lib/active_record/connection_adapters/type/date.rb deleted file mode 100644 index 1e7205fd0b..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/date.rb +++ /dev/null @@ -1,44 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      class Date < Value # :nodoc: -        def type -          :date -        end - -        def klass -          ::Date -        end - -        private - -        def cast_value(value) -          if value.is_a?(::String) -            return if value.empty? -            fast_string_to_date(value) || fallback_string_to_date(value) -          elsif value.respond_to?(:to_date) -            value.to_date -          else -            value -          end -        end - -        def fast_string_to_date(string) -          if string =~ Column::Format::ISO_DATE -            new_date $1.to_i, $2.to_i, $3.to_i -          end -        end - -        def fallback_string_to_date(string) -          new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) -        end - -        def new_date(year, mon, mday) -          if year && year != 0 -            ::Date.new(year, mon, mday) rescue nil -          end -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/date_time.rb b/activerecord/lib/active_record/connection_adapters/type/date_time.rb deleted file mode 100644 index c34f4c5a53..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/date_time.rb +++ /dev/null @@ -1,35 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      class DateTime < Value # :nodoc: -        include TimeValue - -        def type -          :datetime -        end - -        private - -        def cast_value(string) -          return string unless string.is_a?(::String) -          return if string.empty? - -          fast_string_to_time(string) || fallback_string_to_time(string) -        end - -        # '0.123456' -> 123456 -        # '1.123456' -> 123456 -        def microseconds(time) -          time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0 -        end - -        def fallback_string_to_time(string) -          time_hash = ::Date._parse(string) -          time_hash[:sec_fraction] = microseconds(time_hash) - -          new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/decimal.rb b/activerecord/lib/active_record/connection_adapters/type/decimal.rb deleted file mode 100644 index ac5af4b963..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/decimal.rb +++ /dev/null @@ -1,27 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      class Decimal < Value # :nodoc: -        include Numeric - -        def type -          :decimal -        end - -        def klass -          ::BigDecimal -        end - -        private - -        def cast_value(value) -          if value.respond_to?(:to_d) -            value.to_d -          else -            value.to_s.to_d -          end -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/decimal_without_scale.rb b/activerecord/lib/active_record/connection_adapters/type/decimal_without_scale.rb deleted file mode 100644 index e58c6e198d..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/decimal_without_scale.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'active_record/connection_adapters/type/integer' - -module ActiveRecord -  module ConnectionAdapters -    module Type -      class DecimalWithoutScale < Integer # :nodoc: -        def type -          :decimal -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/float.rb b/activerecord/lib/active_record/connection_adapters/type/float.rb deleted file mode 100644 index 51cfa5d86a..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/float.rb +++ /dev/null @@ -1,23 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      class Float < Value # :nodoc: -        include Numeric - -        def type -          :float -        end - -        def klass -          ::Float -        end - -        private - -        def cast_value(value) -          value.to_f -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/connection_adapters/type/hash_lookup_type_map.rb deleted file mode 100644 index bb1abc77ff..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/hash_lookup_type_map.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      class HashLookupTypeMap < TypeMap # :nodoc: -        delegate :key?, to: :@mapping - -        def lookup(type, *args) -          @mapping.fetch(type, proc { default_value }).call(type, *args) -        end - -        def fetch(type, *args, &block) -          @mapping.fetch(type, block).call(type, *args) -        end - -        def alias_type(type, alias_type) -          register_type(type) { |_, *args| lookup(alias_type, *args) } -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/integer.rb b/activerecord/lib/active_record/connection_adapters/type/integer.rb deleted file mode 100644 index 8f3469434c..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/integer.rb +++ /dev/null @@ -1,27 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      class Integer < Value # :nodoc: -        include Numeric - -        def type -          :integer -        end - -        def klass -          ::Fixnum -        end - -        private - -        def cast_value(value) -          case value -          when true then 1 -          when false then 0 -          else value.to_i rescue nil -          end -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/numeric.rb b/activerecord/lib/active_record/connection_adapters/type/numeric.rb deleted file mode 100644 index a3379831cb..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/numeric.rb +++ /dev/null @@ -1,20 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      module Numeric # :nodoc: -        def number? -          true -        end - -        def type_cast_for_write(value) -          case value -          when true then 1 -          when false then 0 -          when ::String then value.presence -          else super -          end -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/string.rb b/activerecord/lib/active_record/connection_adapters/type/string.rb deleted file mode 100644 index 55f0e1ee1c..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/string.rb +++ /dev/null @@ -1,29 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      class String < Value # :nodoc: -        def type -          :string -        end - -        def text? -          true -        end - -        def klass -          ::String -        end - -        private - -        def cast_value(value) -          case value -          when true then "1" -          when false then "0" -          else value.to_s -          end -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/text.rb b/activerecord/lib/active_record/connection_adapters/type/text.rb deleted file mode 100644 index ee5842a3fc..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/text.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'active_record/connection_adapters/type/string' - -module ActiveRecord -  module ConnectionAdapters -    module Type -      class Text < String # :nodoc: -        def type -          :text -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/time.rb b/activerecord/lib/active_record/connection_adapters/type/time.rb deleted file mode 100644 index 4dd201e3fe..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/time.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      class Time < Value # :nodoc: -        include TimeValue - -        def type -          :time -        end - -        private - -        def cast_value(value) -          return value unless value.is_a?(::String) -          return if value.empty? - -          dummy_time_value = "2000-01-01 #{value}" - -          fast_string_to_time(dummy_time_value) || begin -            time_hash = ::Date._parse(dummy_time_value) -            return if time_hash[:hour].nil? -            new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)) -          end -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/time_value.rb b/activerecord/lib/active_record/connection_adapters/type/time_value.rb deleted file mode 100644 index e9ca4adeda..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/time_value.rb +++ /dev/null @@ -1,36 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      module TimeValue # :nodoc: -        def klass -          ::Time -        end - -        private - -        def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil) -          # Treat 0000-00-00 00:00:00 as nil. -          return if year.nil? || (year == 0 && mon == 0 && mday == 0) - -          if offset -            time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil -            return unless time - -            time -= offset -            Base.default_timezone == :utc ? time : time.getlocal -          else -            ::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil -          end -        end - -        # Doesn't handle time zones. -        def fast_string_to_time(string) -          if string =~ Column::Format::ISO_DATETIME -            microsec = ($7.to_r * 1_000_000).to_i -            new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec -          end -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/type_map.rb b/activerecord/lib/active_record/connection_adapters/type/type_map.rb deleted file mode 100644 index 48b8b51417..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/type_map.rb +++ /dev/null @@ -1,50 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      class TypeMap # :nodoc: -        def initialize -          @mapping = {} -        end - -        def lookup(lookup_key, *args) -          matching_pair = @mapping.reverse_each.detect do |key, _| -            key === lookup_key -          end - -          if matching_pair -            matching_pair.last.call(lookup_key, *args) -          else -            default_value -          end -        end - -        def register_type(key, value = nil, &block) -          raise ::ArgumentError unless value || block - -          if block -            @mapping[key] = block -          else -            @mapping[key] = proc { value } -          end -        end - -        def alias_type(key, target_key) -          register_type(key) do |sql_type, *args| -            metadata = sql_type[/\(.*\)/, 0] -            lookup("#{target_key}#{metadata}", *args) -          end -        end - -        def clear -          @mapping.clear -        end - -        private - -        def default_value -          @default_value ||= Value.new -        end -      end -    end -  end -end diff --git a/activerecord/lib/active_record/connection_adapters/type/value.rb b/activerecord/lib/active_record/connection_adapters/type/value.rb deleted file mode 100644 index 54a3e9dd7a..0000000000 --- a/activerecord/lib/active_record/connection_adapters/type/value.rb +++ /dev/null @@ -1,48 +0,0 @@ -module ActiveRecord -  module ConnectionAdapters -    module Type -      class Value # :nodoc: -        attr_reader :precision, :scale, :limit - -        def initialize(options = {}) -          options.assert_valid_keys(:precision, :scale, :limit) -          @precision = options[:precision] -          @scale = options[:scale] -          @limit = options[:limit] -        end - -        def type; end - -        def type_cast(value) -          cast_value(value) unless value.nil? -        end - -        def type_cast_for_write(value) -          value -        end - -        def text? -          false -        end - -        def number? -          false -        end - -        def binary? -          false -        end - -        def klass -          ::Object -        end - -        private - -        def cast_value(value) -          value -        end -      end -    end -  end -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/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 71e176a328..05c4b13016 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -20,7 +20,7 @@ module ActiveRecord        def reset_counters(id, *counters)          object = find(id)          counters.each do |counter_association| -          has_many_association = reflect_on_association(counter_association.to_sym) +          has_many_association = _reflect_on_association(counter_association.to_sym)            unless has_many_association              has_many = reflect_on_all_associations(:has_many)              has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym } @@ -34,8 +34,7 @@ module ActiveRecord            foreign_key  = has_many_association.foreign_key.to_s            child_class  = has_many_association.klass -          belongs_to   = child_class.reflect_on_all_associations(:belongs_to) -          reflection   = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? } +          reflection   = child_class._reflections.values.find { |e| :belongs_to == e.macro && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }            counter_name = reflection.counter_cache_column            stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({ @@ -167,7 +166,7 @@ module ActiveRecord        end        def each_counter_cached_associations -        reflections.each do |name, reflection| +        _reflections.each do |name, reflection|            yield association(name) if reflection.belongs_to? && reflection.counter_cache_column          end        end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 71efbb8f93..2ccb1b0702 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -217,6 +217,13 @@ module ActiveRecord    class ImmutableRelation < ActiveRecordError    end +  # TransactionIsolationError will be raised under the following conditions: +  # +  # * The adapter does not support setting the isolation level +  # * You are joining an existing open transaction +  # * You are creating a nested (savepoint) transaction +  # +  # The mysql, mysql2 and postgresql adapters support setting the transaction isolation level.    class TransactionIsolationError < ActiveRecordError    end  end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 47d32fae05..f3d3cdc9e3 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -649,14 +649,14 @@ module ActiveRecord                model_class              end -          reflection_class.reflect_on_all_associations.each do |association| +          reflection_class._reflections.values.each do |association|              case association.macro              when :belongs_to                # Do not replace association name with association foreign key if they are named the same                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 8449fb1266..baf2b5fbf8 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -217,16 +217,6 @@ module ActiveRecord          connection.schema_cache.table_exists?(table_name)        end -      # Returns an array of column objects for the table associated with this class. -      def columns -        connection.schema_cache.columns(table_name) -      end - -      # Returns a hash of column objects for the table associated with this class. -      def columns_hash -        connection.schema_cache.columns_hash(table_name) -      end -        def column_types # :nodoc:          @column_types ||= decorate_columns(columns_hash.dup)        end @@ -234,14 +224,6 @@ module ActiveRecord        def decorate_columns(columns_hash) # :nodoc:          return if columns_hash.empty? -        @serialized_column_names ||= self.columns_hash.keys.find_all do |name| -          serialized_attributes.key?(name) -        end - -        @serialized_column_names.each do |name| -          columns_hash[name] = AttributeMethods::Serialization::Type.new(columns_hash[name]) -        end -          @time_zone_column_names ||= self.columns_hash.find_all do |name, col|            create_time_zone_conversion_attribute?(name, col)          end.map!(&:first) @@ -259,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 } @@ -303,24 +293,17 @@ module ActiveRecord          @arel_engine             = nil          @column_defaults         = nil +        @raw_column_defauts      = nil          @column_names            = nil          @column_types            = nil          @content_columns         = nil          @dynamic_methods_hash    = nil          @inheritance_column      = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column          @relation                = nil -        @serialized_column_names = nil          @time_zone_column_names  = nil          @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/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 29ed499b1b..1242f49e28 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -305,7 +305,7 @@ module ActiveRecord          options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank          attr_names.each do |association_name| -          if reflection = reflect_on_association(association_name) +          if reflection = _reflect_on_association(association_name)              reflection.autosave = true              add_autosave_association_callbacks(reflection) @@ -516,7 +516,7 @@ module ActiveRecord      # Determines if a hash contains a truthy _destroy key.      def has_destroy_flag?(hash) -      ConnectionAdapters::Type::Boolean.new.type_cast(hash['_destroy']) +      Type::Boolean.new.type_cast(hash['_destroy'])      end      # Determines if a new record should be rejected by checking @@ -542,7 +542,7 @@ module ActiveRecord      end      def raise_nested_attributes_record_not_found!(association_name, record_id) -      raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}" +      raise RecordNotFound, "Couldn't find #{self.class._reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"      end    end  end 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 new file mode 100644 index 0000000000..48ee42aaca --- /dev/null +++ b/activerecord/lib/active_record/properties.rb @@ -0,0 +1,122 @@ +module ActiveRecord +  module Properties # :nodoc: +    extend ActiveSupport::Concern + +    Type = ActiveRecord::Type + +    included do +      class_attribute :user_provided_columns, instance_accessor: false # :internal: +      self.user_provided_columns = {} +    end + +    module ClassMethods +      # Defines or overrides a property on this model. This allows customization of +      # 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. +      # +      #   # db/schema.rb +      #   create_table :store_listings, force: true do |t| +      #     t.decimal :price_in_cents +      #   end +      # +      #   # app/models/store_listing.rb +      #   class StoreListing < ActiveRecord::Base +      #   end +      # +      #   store_listing = StoreListing.new(price_in_cents: '10.1') +      # +      #   # before +      #   store_listing.price_in_cents # => BigDecimal.new(10.1) +      # +      #   class StoreListing < ActiveRecord::Base +      #     property :price_in_cents, Type::Integer.new +      #   end +      # +      #   # after +      #   store_listing.price_in_cents # => 10 +      # +      # Users may also define their own custom types, as long as they respond to the methods +      # defined on the value type. The `type_cast` method on your type object will be called +      # with values both from the database, and from your controllers. See +      # `ActiveRecord::Properties::Type::Value` for the expected API. It is recommended that your +      # type objects inherit from an existing type, or the base value type. +      # +      #   class MoneyType < ActiveRecord::Type::Integer +      #     def type_cast(value) +      #       if value.include?('$') +      #         price_in_dollars = value.gsub(/\$/, '').to_f +      #         price_in_dollars * 100 +      #       else +      #         value.to_i +      #       end +      #     end +      #   end +      # +      #   class StoreListing < ActiveRecord::Base +      #     property :price_in_cents, MoneyType.new +      #   end +      # +      #   store_listing = StoreListing.new(price_in_cents: '$10.00') +      #   store_listing.price_in_cents # => 1000 +      def property(name, cast_type, options = {}) +        name = name.to_s +        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, options[:default], cast_type)) +      end + +      # Returns an array of column objects for the table associated with this class. +      def columns +        @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name)) +      end + +      # Returns a hash of column objects for the table associated with this class. +      def columns_hash +        @columns_hash ||= Hash[columns.map { |c| [c.name, c] }] +      end + +      def reset_column_information # :nodoc: +        super +        clear_caches_calculated_from_columns +      end + +      private + +      def add_user_provided_columns(schema_columns) +        existing_columns = schema_columns.map do |column| +          user_provided_columns[column.name] || column +        end + +        existing_column_names = existing_columns.map(&:name) +        new_columns = user_provided_columns.except(*existing_column_names).values + +        existing_columns + new_columns +      end + +      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 +end diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index ef138c6f80..1fe54cea3f 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -40,7 +40,7 @@ module ActiveRecord        column_types = {}        if result_set.respond_to? :column_types -        column_types = result_set.column_types +        column_types = result_set.column_types.merge(columns_hash)        else          ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"        end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 9e8e5fe94b..fa25ceaefa 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -366,7 +366,7 @@ namespace :railties do      task :migrations => :'db:load_config' do        to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip }        railties = {} -      Rails.application.railties.each do |railtie| +      Rails.application.migration_railties.each do |railtie|          next unless to_load == :all || to_load.include?(railtie.railtie_name)          if railtie.respond_to?(:paths) && (path = railtie.paths['db/migrate'].first) diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 0eec6774a0..4d5203612c 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -6,9 +6,9 @@ module ActiveRecord      extend ActiveSupport::Concern      included do -      class_attribute :reflections +      class_attribute :_reflections        class_attribute :aggregate_reflections -      self.reflections = {} +      self._reflections = {}        self.aggregate_reflections = {}      end @@ -24,7 +24,7 @@ module ActiveRecord      end      def self.add_reflection(ar, name, reflection) -      ar.reflections = ar.reflections.merge(name.to_s => reflection) +      ar._reflections = ar._reflections.merge(name.to_s => reflection)      end      def self.add_aggregate_reflection(ar, name, reflection) @@ -53,6 +53,24 @@ module ActiveRecord          aggregate_reflections[aggregation.to_s]        end +      # Returns a Hash of name of the reflection as the key and a AssociationReflection as the value. +      # +      #   Account.reflections # => {balance: AggregateReflection} +      # +      # @api public +      def reflections +        ref = {} +        _reflections.each do |name, reflection| +          parent_name, parent_reflection = reflection.parent_reflection +          if parent_name +            ref[parent_name] = parent_reflection +          else +            ref[name] = reflection +          end +        end +        ref +      end +        # Returns an array of AssociationReflection objects for all the        # associations in the class. If you only want to reflect on a certain        # association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>, @@ -63,6 +81,7 @@ module ActiveRecord        #   Account.reflect_on_all_associations             # returns an array of all associations        #   Account.reflect_on_all_associations(:has_many)  # returns an array of all has_many associations        # +      #   @api public        def reflect_on_all_associations(macro = nil)          association_reflections = reflections.values          macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections @@ -73,11 +92,19 @@ module ActiveRecord        #   Account.reflect_on_association(:owner)             # returns the owner AssociationReflection        #   Invoice.reflect_on_association(:line_items).macro  # returns :has_many        # +      #   @api public        def reflect_on_association(association)          reflections[association.to_s]        end +      #   @api private +      def _reflect_on_association(association) #:nodoc: +        _reflections[association.to_s] +      end +        # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled. +      # +      #   @api public        def reflect_on_all_autosave_associations          reflections.values.select { |reflection| reflection.options[:autosave] }        end @@ -129,6 +156,10 @@ module ActiveRecord        def autosave=(autosave)          @automatic_inverse_of = false          @options[:autosave] = autosave +        _, parent_reflection = self.parent_reflection +        if parent_reflection +          parent_reflection.autosave = autosave +        end        end        # Returns the class for the macro. @@ -193,10 +224,11 @@ module ActiveRecord        end        attr_reader :type, :foreign_type +      attr_accessor :parent_reflection # [:name, Reflection]        def initialize(macro, name, scope, options, active_record)          super -        @collection = :has_many == macro +        @collection = [:has_many, :has_and_belongs_to_many].include?(macro)          @automatic_inverse_of = nil          @type         = options[:as] && "#{options[:as]}_type"          @foreign_type = options[:foreign_type] || "#{name}_type" @@ -207,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 { @@ -271,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 @@ -330,12 +362,12 @@ Joining, Preloading and eager loading of these associations is deprecated and wi        def inverse_of          return unless inverse_name -        @inverse_of ||= klass.reflect_on_association inverse_name +        @inverse_of ||= klass._reflect_on_association inverse_name        end        def polymorphic_inverse_of(associated_class)          if has_inverse? -          if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of]) +          if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])              inverse_relationship            else              raise InverseOfAssociationNotFoundError.new(self, associated_class) @@ -371,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 @@ -392,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] @@ -409,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 @@ -436,7 +468,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi              inverse_name = ActiveSupport::Inflector.underscore(active_record.name).to_sym              begin -              reflection = klass.reflect_on_association(inverse_name) +              reflection = klass._reflect_on_association(inverse_name)              rescue NameError                # Give up: we couldn't compute the klass type so we won't be able                # to find any associations either. @@ -535,7 +567,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi        #   # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">        #        def source_reflection -        through_reflection.klass.reflect_on_association(source_reflection_name) +        through_reflection.klass._reflect_on_association(source_reflection_name)        end        # Returns the AssociationReflection object specified in the <tt>:through</tt> option @@ -551,7 +583,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi        #   # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">        #        def through_reflection -        active_record.reflect_on_association(options[:through]) +        active_record._reflect_on_association(options[:through])        end        # Returns an array of reflections which are involved in this association. Each item in the @@ -658,7 +690,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi          names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq          names = names.find_all { |n| -          through_reflection.klass.reflect_on_association(n) +          through_reflection.klass._reflect_on_association(n)          }          if names.length > 1 @@ -691,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 @@ -699,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 56cf9bcd27..9ee6422329 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 @@ -277,7 +277,7 @@ module ActiveRecord        group_attrs = group_values        if group_attrs.first.respond_to?(:to_sym) -        association  = @klass.reflect_on_association(group_attrs.first.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          group_fields = Array(associated ? association.foreign_key : group_attrs)        else diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index db32ae12a8..47e90e9021 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -336,7 +336,16 @@ module ActiveRecord      end      def find_with_associations -      join_dependency = construct_join_dependency +      # NOTE: the JoinDependency constructed here needs to know about +      #       any joins already present in `self`, so pass them in +      # +      # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136 +      # incorrect SQL is generated. In that case, the join dependency for +      # SpecialCategorizations is constructed without knowledge of the +      # preexisting join in joins_values to categorizations (by way of +      # the `has_many :through` for categories). +      # +      join_dependency = construct_join_dependency(joins_values)        aliases  = join_dependency.aliases        relation = select aliases.columns diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index d40f276968..eff5c8f09c 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -26,7 +26,7 @@ module ActiveRecord              queries << '1=0'            else              table       = Arel::Table.new(column, default_table.engine) -            association = klass.reflect_on_association(column.to_sym) +            association = klass._reflect_on_association(column.to_sym)              value.each do |k, v|                queries.concat expand(association && association.klass, table, k, v) @@ -55,7 +55,7 @@ module ActiveRecord        #        # For polymorphic relationships, find the foreign key and type:        # PriceEstimate.where(estimate_of: treasure) -      if klass && reflection = klass.reflect_on_association(column.to_sym) +      if klass && reflection = klass._reflect_on_association(column.to_sym)          if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value)            queries << build(table[reflection.foreign_type], base_class)          end diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb index 2f6c34ac08..78dba8be06 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb @@ -2,28 +2,33 @@ module ActiveRecord    class PredicateBuilder      class ArrayHandler # :nodoc:        def call(attribute, value) +        return attribute.in([]) if value.empty? +          values = value.map { |x| x.is_a?(Base) ? x.id : x }          ranges, values = values.partition { |v| v.is_a?(Range) } +        nils, values = values.partition(&:nil?) -        values_predicate = if values.include?(nil) -          values = values.compact - +        values_predicate =            case values.length -          when 0 -            attribute.eq(nil) -          when 1 -            attribute.eq(values.first).or(attribute.eq(nil)) -          else -            attribute.in(values).or(attribute.eq(nil)) +          when 0 then NullPredicate +          when 1 then attribute.eq(values.first) +          else attribute.in(values)            end -        else -          attribute.in(values) + +        unless nils.empty? +          values_predicate = values_predicate.or(attribute.eq(nil))          end          array_predicates = ranges.map { |range| attribute.in(range) }          array_predicates << values_predicate          array_predicates.inject { |composite, predicate| composite.or(predicate) }        end + +      module NullPredicate +        def self.or(other) +          other +        end +      end      end    end  end 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.rb b/activerecord/lib/active_record/type.rb new file mode 100644 index 0000000000..e9b827886a --- /dev/null +++ b/activerecord/lib/active_record/type.rb @@ -0,0 +1,19 @@ +require 'active_record/type/numeric' +require 'active_record/type/time_value' +require 'active_record/type/value' + +require 'active_record/type/binary' +require 'active_record/type/boolean' +require 'active_record/type/date' +require 'active_record/type/date_time' +require 'active_record/type/decimal' +require 'active_record/type/decimal_without_scale' +require 'active_record/type/float' +require 'active_record/type/integer' +require 'active_record/type/serialized' +require 'active_record/type/string' +require 'active_record/type/text' +require 'active_record/type/time' + +require 'active_record/type/type_map' +require 'active_record/type/hash_lookup_type_map' diff --git a/activerecord/lib/active_record/type/binary.rb b/activerecord/lib/active_record/type/binary.rb new file mode 100644 index 0000000000..9d10c91fc1 --- /dev/null +++ b/activerecord/lib/active_record/type/binary.rb @@ -0,0 +1,35 @@ +module ActiveRecord +  module Type +    class Binary < Value # :nodoc: +      def type +        :binary +      end + +      def binary? +        true +      end + +      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/boolean.rb b/activerecord/lib/active_record/type/boolean.rb new file mode 100644 index 0000000000..06dd17ed28 --- /dev/null +++ b/activerecord/lib/active_record/type/boolean.rb @@ -0,0 +1,19 @@ +module ActiveRecord +  module Type +    class Boolean < Value # :nodoc: +      def type +        :boolean +      end + +      private + +      def cast_value(value) +        if value == '' +          nil +        else +          ConnectionAdapters::Column::TRUE_VALUES.include?(value) +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/date.rb b/activerecord/lib/active_record/type/date.rb new file mode 100644 index 0000000000..d90a6069b7 --- /dev/null +++ b/activerecord/lib/active_record/type/date.rb @@ -0,0 +1,46 @@ +module ActiveRecord +  module Type +    class Date < Value # :nodoc: +      def type +        :date +      end + +      def klass +        ::Date +      end + +      def type_cast_for_schema(value) +        "'#{value.to_s(:db)}'" +      end + +      private + +      def cast_value(value) +        if value.is_a?(::String) +          return if value.empty? +          fast_string_to_date(value) || fallback_string_to_date(value) +        elsif value.respond_to?(:to_date) +          value.to_date +        else +          value +        end +      end + +      def fast_string_to_date(string) +        if string =~ ConnectionAdapters::Column::Format::ISO_DATE +          new_date $1.to_i, $2.to_i, $3.to_i +        end +      end + +      def fallback_string_to_date(string) +        new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) +      end + +      def new_date(year, mon, mday) +        if year && year != 0 +          ::Date.new(year, mon, mday) rescue nil +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/date_time.rb b/activerecord/lib/active_record/type/date_time.rb new file mode 100644 index 0000000000..560d63c101 --- /dev/null +++ b/activerecord/lib/active_record/type/date_time.rb @@ -0,0 +1,33 @@ +module ActiveRecord +  module Type +    class DateTime < Value # :nodoc: +      include TimeValue + +      def type +        :datetime +      end + +      private + +      def cast_value(string) +        return string unless string.is_a?(::String) +        return if string.empty? + +        fast_string_to_time(string) || fallback_string_to_time(string) +      end + +      # '0.123456' -> 123456 +      # '1.123456' -> 123456 +      def microseconds(time) +        time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0 +      end + +      def fallback_string_to_time(string) +        time_hash = ::Date._parse(string) +        time_hash[:sec_fraction] = microseconds(time_hash) + +        new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb new file mode 100644 index 0000000000..6eed005345 --- /dev/null +++ b/activerecord/lib/active_record/type/decimal.rb @@ -0,0 +1,29 @@ +module ActiveRecord +  module Type +    class Decimal < Value # :nodoc: +      include Numeric + +      def type +        :decimal +      end + +      def klass +        ::BigDecimal +      end + +      def type_cast_for_schema(value) +        value.to_s +      end + +      private + +      def cast_value(value) +        if value.respond_to?(:to_d) +          value.to_d +        else +          value.to_s.to_d +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/decimal_without_scale.rb b/activerecord/lib/active_record/type/decimal_without_scale.rb new file mode 100644 index 0000000000..cabdcecdd7 --- /dev/null +++ b/activerecord/lib/active_record/type/decimal_without_scale.rb @@ -0,0 +1,11 @@ +require 'active_record/type/integer' + +module ActiveRecord +  module Type +    class DecimalWithoutScale < Integer # :nodoc: +      def type +        :decimal +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/float.rb b/activerecord/lib/active_record/type/float.rb new file mode 100644 index 0000000000..dc50dae328 --- /dev/null +++ b/activerecord/lib/active_record/type/float.rb @@ -0,0 +1,23 @@ +module ActiveRecord +  module Type +    class Float < Value # :nodoc: +      include Numeric + +      def type +        :float +      end + +      def klass +        ::Float +      end + +      alias type_cast_for_database type_cast + +      private + +      def cast_value(value) +        value.to_f +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/type/hash_lookup_type_map.rb new file mode 100644 index 0000000000..bf92680268 --- /dev/null +++ b/activerecord/lib/active_record/type/hash_lookup_type_map.rb @@ -0,0 +1,19 @@ +module ActiveRecord +  module Type +    class HashLookupTypeMap < TypeMap # :nodoc: +      delegate :key?, to: :@mapping + +      def lookup(type, *args) +        @mapping.fetch(type, proc { default_value }).call(type, *args) +      end + +      def fetch(type, *args, &block) +        @mapping.fetch(type, block).call(type, *args) +      end + +      def alias_type(type, alias_type) +        register_type(type) { |_, *args| lookup(alias_type, *args) } +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb new file mode 100644 index 0000000000..1e2147dec9 --- /dev/null +++ b/activerecord/lib/active_record/type/integer.rb @@ -0,0 +1,27 @@ +module ActiveRecord +  module Type +    class Integer < Value # :nodoc: +      include Numeric + +      def type +        :integer +      end + +      def klass +        ::Fixnum +      end + +      alias type_cast_for_database type_cast + +      private + +      def cast_value(value) +        case value +        when true then 1 +        when false then 0 +        else value.to_i rescue nil +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb new file mode 100644 index 0000000000..9cc6411e77 --- /dev/null +++ b/activerecord/lib/active_record/type/numeric.rb @@ -0,0 +1,41 @@ +module ActiveRecord +  module Type +    module Numeric # :nodoc: +      def number? +        true +      end + +      def type_cast_for_write(value) +        case value +        when true then 1 +        when false then 0 +        when ::String then value.presence +        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 new file mode 100644 index 0000000000..78a6d31e26 --- /dev/null +++ b/activerecord/lib/active_record/type/serialized.rb @@ -0,0 +1,48 @@ +module ActiveRecord +  module Type +    class Serialized < SimpleDelegator # :nodoc: +      attr_reader :subtype, :coder + +      def initialize(subtype, coder) +        @subtype = subtype +        @coder = coder +        super(subtype) +      end + +      def type_cast(value) +        if is_default_value?(value) +          value +        else +          coder.load(super) +        end +      end + +      def type_cast_for_write(value) +        return if value.nil? +        unless is_default_value?(value) +          coder.dump(value) +        end +      end + +      alias type_cast_for_database type_cast_for_write + +      def serialized? +        true +      end + +      def accessor +        ActiveRecord::Store::IndifferentHashAccessor +      end + +      private + +      def changed?(old_value, new_value) # :nodoc: +        old_value != new_value +      end + +      def is_default_value?(value) +        value == coder.load(nil) +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/string.rb b/activerecord/lib/active_record/type/string.rb new file mode 100644 index 0000000000..b3f6ca7691 --- /dev/null +++ b/activerecord/lib/active_record/type/string.rb @@ -0,0 +1,27 @@ +module ActiveRecord +  module Type +    class String < Value # :nodoc: +      def type +        :string +      end + +      def text? +        true +      end + +      def klass +        ::String +      end + +      private + +      def cast_value(value) +        case value +        when true then "1" +        when false then "0" +        else value.to_s +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/text.rb b/activerecord/lib/active_record/type/text.rb new file mode 100644 index 0000000000..26f980f060 --- /dev/null +++ b/activerecord/lib/active_record/type/text.rb @@ -0,0 +1,11 @@ +require 'active_record/type/string' + +module ActiveRecord +  module Type +    class Text < String # :nodoc: +      def type +        :text +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb new file mode 100644 index 0000000000..41f7d97f0c --- /dev/null +++ b/activerecord/lib/active_record/type/time.rb @@ -0,0 +1,26 @@ +module ActiveRecord +  module Type +    class Time < Value # :nodoc: +      include TimeValue + +      def type +        :time +      end + +      private + +      def cast_value(value) +        return value unless value.is_a?(::String) +        return if value.empty? + +        dummy_time_value = "2000-01-01 #{value}" + +        fast_string_to_time(dummy_time_value) || begin +          time_hash = ::Date._parse(dummy_time_value) +          return if time_hash[:hour].nil? +          new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)) +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/time_value.rb b/activerecord/lib/active_record/type/time_value.rb new file mode 100644 index 0000000000..d611d72dd4 --- /dev/null +++ b/activerecord/lib/active_record/type/time_value.rb @@ -0,0 +1,38 @@ +module ActiveRecord +  module Type +    module TimeValue # :nodoc: +      def klass +        ::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) +        # Treat 0000-00-00 00:00:00 as nil. +        return if year.nil? || (year == 0 && mon == 0 && mday == 0) + +        if offset +          time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil +          return unless time + +          time -= offset +          Base.default_timezone == :utc ? time : time.getlocal +        else +          ::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil +        end +      end + +      # Doesn't handle time zones. +      def fast_string_to_time(string) +        if string =~ ConnectionAdapters::Column::Format::ISO_DATETIME +          microsec = ($7.to_r * 1_000_000).to_i +          new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb new file mode 100644 index 0000000000..88c5f9c497 --- /dev/null +++ b/activerecord/lib/active_record/type/type_map.rb @@ -0,0 +1,48 @@ +module ActiveRecord +  module Type +    class TypeMap # :nodoc: +      def initialize +        @mapping = {} +      end + +      def lookup(lookup_key, *args) +        matching_pair = @mapping.reverse_each.detect do |key, _| +          key === lookup_key +        end + +        if matching_pair +          matching_pair.last.call(lookup_key, *args) +        else +          default_value +        end +      end + +      def register_type(key, value = nil, &block) +        raise ::ArgumentError unless value || block + +        if block +          @mapping[key] = block +        else +          @mapping[key] = proc { value } +        end +      end + +      def alias_type(key, target_key) +        register_type(key) do |sql_type, *args| +          metadata = sql_type[/\(.*\)/, 0] +          lookup("#{target_key}#{metadata}", *args) +        end +      end + +      def clear +        @mapping.clear +      end + +      private + +      def default_value +        @default_value ||= Value.new +      end +    end +  end +end diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb new file mode 100644 index 0000000000..c072c1e2b6 --- /dev/null +++ b/activerecord/lib/active_record/type/value.rb @@ -0,0 +1,76 @@ +module ActiveRecord +  module Type +    class Value # :nodoc: +      attr_reader :precision, :scale, :limit + +      # Valid options are +precision+, +scale+, and +limit+. +      # They are only used when dumping schema. +      def initialize(options = {}) +        options.assert_valid_keys(:precision, :scale, :limit) +        @precision = options[:precision] +        @scale = options[:scale] +        @limit = options[:limit] +      end + +      # 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, +      # and casts it to a type appropriate for this object. This method +      # should not be overriden by subclasses. Instead, override `cast_value`. +      def type_cast(value) +        cast_value(value) unless value.nil? +      end + +      def type_cast_for_database(value) +        type_cast_for_write(value) +      end + +      def type_cast_for_schema(value) +        value.inspect +      end + +      def text? +        false +      end + +      def number? +        false +      end + +      def binary? +        false +      end + +      def serialized? +        false +      end + +      def klass # :nodoc: +        ::Object +      end + +      def type_cast_for_write(value) # :nodoc: +        value +      end + +      # +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 + +      # Responsible for casting values from external sources to the appropriate +      # type. Called by `type_cast` for all values except `nil`. +      def cast_value(value) # :api: public +        value +      end +    end +  end +end diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb index 9a19483da3..e586744818 100644 --- a/activerecord/lib/active_record/validations/presence.rb +++ b/activerecord/lib/active_record/validations/presence.rb @@ -4,7 +4,7 @@ module ActiveRecord        def validate(record)          super          attributes.each do |attribute| -          next unless record.class.reflect_on_association(attribute) +          next unless record.class._reflect_on_association(attribute)            associated_records = Array.wrap(record.send(attribute))            # Superclass validates presence. Ensure present records aren't about to be destroyed. diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index ee080451a9..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? @@ -47,7 +46,7 @@ module ActiveRecord        end        def build_relation(klass, table, attribute, value) #:nodoc: -        if reflection = klass.reflect_on_association(attribute) +        if reflection = klass._reflect_on_association(attribute)            attribute = reflection.foreign_key            value = value.attributes[reflection.primary_key_column.name] unless value.nil?          end @@ -74,7 +73,7 @@ module ActiveRecord        def scope_relation(record, table, relation)          Array(options[:scope]).each do |scope_item| -          if reflection = record.class.reflect_on_association(scope_item) +          if reflection = record.class._reflect_on_association(scope_item)              scope_value = record.send(reflection.foreign_key)              scope_item  = reflection.foreign_key            else @@ -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/mysql/consistency_test.rb b/activerecord/test/cases/adapters/mysql/consistency_test.rb new file mode 100644 index 0000000000..083d533bb2 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/consistency_test.rb @@ -0,0 +1,48 @@ +require "cases/helper" + +class MysqlConsistencyTest < ActiveRecord::TestCase +  self.use_transactional_fixtures = false + +  class Consistency < ActiveRecord::Base +    self.table_name = "mysql_consistency" +  end + +  setup do +    @old_emulate_booleans = ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans +    ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false + +    @connection = ActiveRecord::Base.connection +    @connection.create_table("mysql_consistency") do |t| +      t.boolean "a_bool" +      t.string "a_string" +    end +    Consistency.reset_column_information +    Consistency.create! +  end + +  teardown do +    ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = @old_emulate_booleans +    @connection.drop_table "mysql_consistency" +  end + +  test "boolean columns with random value type cast to 0 when emulate_booleans is false" do +    with_new = Consistency.new +    with_last = Consistency.last +    with_new.a_bool = 'wibble' +    with_last.a_bool = 'wibble' + +    assert_equal 0, with_new.a_bool +    assert_equal 0, with_last.a_bool +  end + +  test "string columns call #to_s" do +    with_new = Consistency.new +    with_last = Consistency.last +    thing = Object.new +    with_new.a_string = thing +    with_last.a_string = thing + +    assert_equal thing.to_s, with_new.a_string +    assert_equal thing.to_s, with_last.a_string +  end +end 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 1f55cce352..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 @@ -83,7 +81,7 @@ end  class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase    include PostgresqlCompositeBehavior -  class FullAddressType < ActiveRecord::ConnectionAdapters::Type::Value +  class FullAddressType < ActiveRecord::Type::Value      def type; :full_address end      def type_cast(value) @@ -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 @@ -122,7 +121,6 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase      assert_equal "Champs-Élysées", composite.address.street      composite.address = FullAddress.new("Paris", "Rue Basse") -    skip "Saving with custom OID type is currently not supported."      composite.save!      assert_equal 'Paris', composite.reload.address.city 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/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 67a610b459..1fef4899be 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -142,6 +142,16 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase        assert_equal "GMT", x.timezone      end +    def test_duplication_with_store_accessors +      x = Hstore.new(language: "fr", timezone: "GMT") +      assert_equal "fr", x.language +      assert_equal "GMT", x.timezone + +      y = x.dup +      assert_equal "fr", y.language +      assert_equal "GMT", y.timezone +    end +      def test_gen1        assert_equal(%q(" "=>""), @column.class.hstore_to_string({' '=>''}))      end diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index d25f8bf958..03b546119d 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -139,6 +139,14 @@ class PostgresqlJSONTest < ActiveRecord::TestCase      assert_equal "640×1136", x.resolution    end +  def test_duplication_with_store_accessors +    x = JsonDataType.new(resolution: "320×480") +    assert_equal "320×480", x.resolution + +    y = x.dup +    assert_equal "320×480", y.resolution +  end +    def test_update_all      json = JsonDataType.create! payload: { "one" => "two" } 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 b9e296ed8f..9e5fd17dc4 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -129,7 +129,7 @@ class SchemaTest < ActiveRecord::TestCase      SQL      song = Song.create -    album = Album.create +    Album.create      assert_equal song, Song.includes(:albums).references(:albums).first    ensure      ActiveRecord::Base.connection.execute "DROP SCHEMA music CASCADE;" @@ -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/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 0c4f06d6a9..8c9a051eea 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -15,10 +15,10 @@ module ActiveRecord          def test_type_cast_binary_encoding_without_logger            @conn.extend(Module.new { def logger; end }) -          column = Struct.new(:type, :name).new(:string, "foo") +          cast_type = Type::String.new            binary = SecureRandom.hex            expected = binary.dup.encode!(Encoding::UTF_8) -          assert_equal expected, @conn.type_cast(binary, column) +          assert_equal expected, @conn.type_cast(binary, cast_type)          end          def test_type_cast_symbol @@ -84,7 +84,7 @@ module ActiveRecord            assert_raise(TypeError) { @conn.type_cast(obj, nil) }          end -        def test_quoted_id +        def test_type_cast_object_which_responds_to_quoted_id            quoted_id_obj = Class.new {              def quoted_id                "'zomg'" diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index ebcf453305..4bd4486b41 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1167,6 +1167,13 @@ class EagerAssociationTest < ActiveRecord::TestCase      )    end +  test "deep preload" do +    post = Post.preload(author: :posts, comments: :post).first + +    assert_predicate post.author.association(:posts), :loaded? +    assert_predicate post.comments.first.association(:post), :loaded? +  end +    test "preloading does not cache has many association subset when preloaded with a through association" do      author = Author.includes(:comments_with_order_and_conditions, :posts).first      assert_no_queries { assert_equal 2, author.comments_with_order_and_conditions.size } @@ -1196,6 +1203,15 @@ class EagerAssociationTest < ActiveRecord::TestCase      end    end +  test "preloading the same association twice works" do +    Member.create! +    members = Member.preload(:current_membership).includes(current_membership: :club).all.to_a +    assert_no_queries { +      members_with_membership = members.select(&:current_membership) +      assert_equal 3, members_with_membership.map(&:current_membership).map(&:club).size +    } +  end +    test "preloading with a polymorphic association and using the existential predicate" do      assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer 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 878f1877db..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 @@ -70,6 +70,14 @@ class DeveloperWithSymbolsForKeys < ActiveRecord::Base      :foreign_key => "developer_id"  end +class SubDeveloper < Developer +  self.table_name = 'developers' +  has_and_belongs_to_many :special_projects, +    :join_table => 'developers_projects', +    :foreign_key => "project_id", +    :association_foreign_key => "developer_id" +end +  class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase    fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,             :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings @@ -814,7 +822,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      assert_equal [], Pirate.where(id: redbeard.id)    end -  test "has and belongs to many associations on new records use null relations" do +  def test_has_and_belongs_to_many_associations_on_new_records_use_null_relations      projects = Developer.new.projects      assert_no_queries do        assert_equal [], projects @@ -860,4 +868,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      assert_includes magazine.articles, article    end + +  def test_redefine_habtm +    child = SubDeveloper.new("name" => "Aredridel") +    child.special_projects << SpecialProject.new("name" => "Special Project") +    assert child.save, 'child object should be saved' +  end  end diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index b23517b2f9..07cf65a760 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -126,4 +126,14 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase      categories = author.categories.includes(:special_categorizations).references(:special_categorizations).to_a      assert_equal 2, categories.size    end + +  test "the correct records are loaded when including an aliased association" do +    author = Author.create! name: "Jon" +    author.categories.create! name: 'Not Special' +    author.special_categories.create! name: 'Special' + +    categories = author.categories.eager_load(:special_categorizations).order(:name).to_a +    assert_equal 0, categories.first.special_categorizations.size +    assert_equal 1, categories.second.special_categorizations.size +  end  end diff --git a/activerecord/test/cases/attribute_methods/serialization_test.rb b/activerecord/test/cases/attribute_methods/serialization_test.rb deleted file mode 100644 index 75de773961..0000000000 --- a/activerecord/test/cases/attribute_methods/serialization_test.rb +++ /dev/null @@ -1,29 +0,0 @@ -require "cases/helper" - -module ActiveRecord -  module AttributeMethods -    class SerializationTest < ActiveSupport::TestCase -      class FakeColumn < Struct.new(:name) -        def type; :integer; end -        def type_cast(s); "#{s}!"; end -      end - -      class NullCoder -        def load(v); v; end -      end - -      def test_type_cast_serialized_value -        value = Serialization::Attribute.new(NullCoder.new, "Hello world", :serialized) -        type = Serialization::Type.new(FakeColumn.new) -        assert_equal "Hello world!", type.type_cast(value) -      end - -      def test_type_cast_unserialized_value -        value = Serialization::Attribute.new(nil, "Hello world", :unserialized) -        type = Serialization::Type.new(FakeColumn.new) -        type.type_cast(value) -        assert_equal "Hello world", type.type_cast(value) -      end -    end -  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/base_test.rb b/activerecord/test/cases/base_test.rb index c565daba65..d65c4b0638 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -518,6 +518,10 @@ class BasicsTest < ActiveRecord::TestCase      assert_equal Topic.find('1-meowmeow'), Topic.find(1)    end +  def test_find_by_slug_with_array +    assert_equal Topic.find(['1-meowmeow', '2-hello']), Topic.find([1, 2]) +  end +    def test_equality_of_new_records      assert_not_equal Topic.new, Topic.new      assert_equal false, Topic.new == Topic.new @@ -980,6 +984,10 @@ class BasicsTest < ActiveRecord::TestCase    class NumericData < ActiveRecord::Base      self.table_name = 'numeric_data' + +    property :world_population, Type::Integer.new +    property :my_house_population, Type::Integer.new +    property :atoms_in_universe, Type::Integer.new    end    def test_big_decimal_conditions diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index b8de78934e..b9445ee072 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -15,6 +15,10 @@ Company.has_many :accounts  class NumericData < ActiveRecord::Base    self.table_name = 'numeric_data' + +  property :world_population, Type::Integer.new +  property :my_house_population, Type::Integer.new +  property :atoms_in_universe, Type::Integer.new  end  class CalculationsTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index 45e48900ee..bcfd66b4bf 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -11,26 +11,6 @@ module ActiveRecord          @viz = @adapter.schema_creation        end -      def test_can_set_coder -        column = Column.new("title", nil, Type::String.new, "varchar(20)") -        column.coder = YAML -        assert_equal YAML, column.coder -      end - -      def test_encoded? -        column = Column.new("title", nil, Type::String.new, "varchar(20)") -        assert !column.encoded? - -        column.coder = YAML -        assert column.encoded? -      end - -      def test_type_case_coded_column -        column = Column.new("title", nil, Type::String.new, "varchar(20)") -        column.coder = YAML -        assert_equal "hello", column.type_cast("--- hello") -      end -        # Avoid column definitions in create table statements like:        # `title` varchar(255) DEFAULT NULL        def test_should_not_include_default_clause_when_default_is_null diff --git a/activerecord/test/cases/connection_adapters/type/type_map_test.rb b/activerecord/test/cases/connection_adapters/type/type_map_test.rb deleted file mode 100644 index 3abd7a276e..0000000000 --- a/activerecord/test/cases/connection_adapters/type/type_map_test.rb +++ /dev/null @@ -1,132 +0,0 @@ -require "cases/helper" - -module ActiveRecord -  module ConnectionAdapters -    module Type -      class TypeMapTest < ActiveRecord::TestCase -        def test_default_type -          mapping = TypeMap.new - -          assert_kind_of Value, mapping.lookup(:undefined) -        end - -        def test_registering_types -          boolean = Boolean.new -          mapping = TypeMap.new - -          mapping.register_type(/boolean/i, boolean) - -          assert_equal mapping.lookup('boolean'), boolean -        end - -        def test_overriding_registered_types -          time = Time.new -          timestamp = DateTime.new -          mapping = TypeMap.new - -          mapping.register_type(/time/i, time) -          mapping.register_type(/time/i, timestamp) - -          assert_equal mapping.lookup('time'), timestamp -        end - -        def test_fuzzy_lookup -          string = String.new -          mapping = TypeMap.new - -          mapping.register_type(/varchar/i, string) - -          assert_equal mapping.lookup('varchar(20)'), string -        end - -        def test_aliasing_types -          string = String.new -          mapping = TypeMap.new - -          mapping.register_type(/string/i, string) -          mapping.alias_type(/varchar/i, 'string') - -          assert_equal mapping.lookup('varchar'), string -        end - -        def test_changing_type_changes_aliases -          time = Time.new -          timestamp = DateTime.new -          mapping = TypeMap.new - -          mapping.register_type(/timestamp/i, time) -          mapping.alias_type(/datetime/i, 'timestamp') -          mapping.register_type(/timestamp/i, timestamp) - -          assert_equal mapping.lookup('datetime'), timestamp -        end - -        def test_aliases_keep_metadata -          mapping = TypeMap.new - -          mapping.register_type(/decimal/i) { |sql_type| sql_type } -          mapping.alias_type(/number/i, 'decimal') - -          assert_equal mapping.lookup('number(20)'), 'decimal(20)' -          assert_equal mapping.lookup('number'), 'decimal' -        end - -        def test_register_proc -          string = String.new -          binary = Binary.new -          mapping = TypeMap.new - -          mapping.register_type(/varchar/i) do |type| -            if type.include?('(') -              string -            else -              binary -            end -          end - -          assert_equal mapping.lookup('varchar(20)'), string -          assert_equal mapping.lookup('varchar'), binary -        end - -        def test_additional_lookup_args -          mapping = TypeMap.new - -          mapping.register_type(/varchar/i) do |type, limit| -            if limit > 255 -              'text' -            else -              'string' -            end -          end -          mapping.alias_type(/string/i, 'varchar') - -          assert_equal mapping.lookup('varchar', 200), 'string' -          assert_equal mapping.lookup('varchar', 400), 'text' -          assert_equal mapping.lookup('string', 400), 'text' -        end - -        def test_requires_value_or_block -          mapping = TypeMap.new - -          assert_raises(ArgumentError) do -            mapping.register_type(/only key/i) -          end -        end - -        def test_lookup_non_strings -          mapping = HashLookupTypeMap.new - -          mapping.register_type(1, 'string') -          mapping.register_type(2, 'int') -          mapping.alias_type(3, 1) - -          assert_equal mapping.lookup(1), 'string' -          assert_equal mapping.lookup(2), 'int' -          assert_equal mapping.lookup(3), 'string' -          assert_kind_of Type::Value, mapping.lookup(4) -        end -      end -    end -  end -end - 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 new file mode 100644 index 0000000000..9ba1e83df6 --- /dev/null +++ b/activerecord/test/cases/custom_properties_test.rb @@ -0,0 +1,111 @@ +require 'cases/helper' + +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 +end + +class GrandchildOfOverloadedType < ChildOfOverloadedType +  property :overloaded_float, Type::Float.new +end + +class UnoverloadedType < ActiveRecord::Base +  self.table_name = 'overloaded_types' +end + +module ActiveRecord +  class CustomPropertiesTest < ActiveRecord::TestCase +    def test_overloading_types +      data = OverloadedType.new + +      data.overloaded_float = "1.1" +      data.unoverloaded_float = "1.1" + +      assert_equal 1, data.overloaded_float +      assert_equal 1.1, data.unoverloaded_float +    end + +    def test_overloaded_properties_save +      data = OverloadedType.new + +      data.overloaded_float = "2.2" +      data.save! +      data.reload + +      assert_equal 2, data.overloaded_float +      assert_kind_of Fixnum, OverloadedType.last.overloaded_float +      assert_equal 2.0, UnoverloadedType.last.overloaded_float +      assert_kind_of Float, UnoverloadedType.last.overloaded_float +    end + +    def test_properties_assigned_in_constructor +      data = OverloadedType.new(overloaded_float: '3.3') + +      assert_equal 3, data.overloaded_float +    end + +    def test_overloaded_properties_with_limit +      assert_equal 50, OverloadedType.columns_hash['overloaded_string_with_limit'].limit +      assert_equal 255, UnoverloadedType.columns_hash['overloaded_string_with_limit'].limit +    end + +    def test_nonexistent_property +      data = OverloadedType.new(non_existent_decimal: 1) + +      assert_equal BigDecimal.new(1), data.non_existent_decimal +      assert_raise ActiveRecord::UnknownAttributeError do +        UnoverloadedType.new(non_existent_decimal: 1) +      end +    end + +    def test_changing_defaults +      data = OverloadedType.new +      unoverloaded_data = UnoverloadedType.new + +      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 +      data = ChildOfOverloadedType.new(overloaded_float: '4.4') + +      assert_equal 4, data.overloaded_float +    end + +    def test_children_can_override_parents +      data = GrandchildOfOverloadedType.new(overloaded_float: '4.4') + +      assert_equal 4.4, data.overloaded_float +    end + +    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 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/helper.rb b/activerecord/test/cases/helper.rb index eaf2cada9d..937646b09a 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -9,6 +9,7 @@ require 'active_record'  require 'cases/test_case'  require 'active_support/dependencies'  require 'active_support/logger' +require 'active_support/core_ext/string/strip'  require 'support/config'  require 'support/connection' 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/migration_test.rb b/activerecord/test/cases/migration_test.rb index 455ec78f68..aa679d4a35 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -11,7 +11,13 @@ require MIGRATIONS_ROOT + "/rename/1_we_need_things"  require MIGRATIONS_ROOT + "/rename/2_rename_things"  require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers" -class BigNumber < ActiveRecord::Base; end +class BigNumber < ActiveRecord::Base +  unless current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) +    property :value_of_e, Type::Integer.new +  end +  property :world_population, Type::Integer.new +  property :my_house_population, Type::Integer.new +end  class Reminder < ActiveRecord::Base; end diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index e2439b9a24..bbd5298da1 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -3,14 +3,6 @@ require "cases/helper"  module ActiveRecord    module ConnectionAdapters      class QuotingTest < ActiveRecord::TestCase -      class FakeColumn < ActiveRecord::ConnectionAdapters::Column -        attr_accessor :type - -        def initialize type -          @type = type -        end -      end -        def setup          @quoter = Class.new { include Quoting }.new        end @@ -101,12 +93,12 @@ module ActiveRecord        def test_quote_true          assert_equal @quoter.quoted_true, @quoter.quote(true, nil) -        assert_equal '1', @quoter.quote(true, Struct.new(:type).new(:integer)) +        assert_equal '1', @quoter.quote(true, Type::Integer.new)        end        def test_quote_false          assert_equal @quoter.quoted_false, @quoter.quote(false, nil) -        assert_equal '0', @quoter.quote(false, Struct.new(:type).new(:integer)) +        assert_equal '0', @quoter.quote(false, Type::Integer.new)        end        def test_quote_float @@ -166,26 +158,26 @@ module ActiveRecord        end        def test_quote_string_int_column -        assert_equal "1", @quoter.quote('1', FakeColumn.new(:integer)) -        assert_equal "1", @quoter.quote('1.2', FakeColumn.new(:integer)) +        assert_equal "1", @quoter.quote('1', Type::Integer.new) +        assert_equal "1", @quoter.quote('1.2', Type::Integer.new)        end        def test_quote_string_float_column -        assert_equal "1.0", @quoter.quote('1', FakeColumn.new(:float)) -        assert_equal "1.2", @quoter.quote('1.2', FakeColumn.new(:float)) +        assert_equal "1.0", @quoter.quote('1', Type::Float.new) +        assert_equal "1.2", @quoter.quote('1.2', Type::Float.new)        end        def test_quote_as_mb_chars_binary_column          string = ActiveSupport::Multibyte::Chars.new('lo\l') -        assert_equal "'lo\\\\l'", @quoter.quote(string, FakeColumn.new(:binary)) +        assert_equal "'lo\\\\l'", @quoter.quote(string, Type::Binary.new)        end        def test_quote_binary_without_string_to_binary -        assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:binary)) +        assert_equal "'lo\\\\l'", @quoter.quote('lo\l', Type::Binary.new)        end        def test_string_with_crazy_column -        assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:foo)) +        assert_equal "'lo\\\\l'", @quoter.quote('lo\l')        end        def test_quote_duration @@ -193,7 +185,7 @@ module ActiveRecord        end        def test_quote_duration_int_column -        assert_equal "7200", @quoter.quote(2.hours, FakeColumn.new(:integer)) +        assert_equal "7200", @quoter.quote(2.hours, Type::Integer.new)        end      end    end diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index c085fcf161..e6603f28be 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -200,7 +200,12 @@ class ReflectionTest < ActiveRecord::TestCase    end    def test_reflection_should_not_raise_error_when_compared_to_other_object -    assert_nothing_raised { Firm.reflections['clients'] == Object.new } +    assert_not_equal Object.new, Firm._reflections['clients'] +  end + +  def test_has_and_belongs_to_many_reflection +    assert_equal :has_and_belongs_to_many, Category.reflections['posts'].macro +    assert_equal :posts, Category.reflect_on_all_associations(:has_and_belongs_to_many).first.name    end    def test_has_many_through_reflection diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 4b146c11bc..88df997a2f 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -552,13 +552,6 @@ class RelationTest < ActiveRecord::TestCase      end    end -  def test_deep_preload -    post = Post.preload(author: :posts, comments: :post).first - -    assert_predicate post.author.association(:posts), :loaded? -    assert_predicate post.comments.first.association(:post), :loaded? -  end -    def test_preload_applies_to_all_chained_preloaded_scopes      assert_queries(3) do        post = Post.with_comments.with_tags.first @@ -750,6 +743,13 @@ class RelationTest < ActiveRecord::TestCase      assert_equal [], relation.to_a    end +  def test_typecasting_where_with_array +    ids = Author.pluck(:id) +    slugs = ids.map { |id| "#{id}-as-a-slug" } + +    assert_equal Author.all.to_a, Author.where(id: slugs).to_a +  end +    def test_find_all_using_where_with_relation      david = authors(:david)      # switching the lines below would succeed in current rails 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 c8f9d7cf87..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 @@ -235,16 +202,6 @@ class SerializedAttributeTest < ActiveRecord::TestCase      assert_equal [], light.long_state    end -  def test_serialized_column_should_not_be_wrapped_twice -    Topic.serialize(:content, MyObject) - -    myobj = MyObject.new('value1', 'value2') -    Topic.create(content: myobj) -    Topic.create(content: myobj) -    type = Topic.column_types["content"] -    assert !type.instance_variable_get("@column").is_a?(ActiveRecord::AttributeMethods::Serialization::Type) -  end -    def test_serialized_column_should_unserialize_after_update_column      t = Topic.create(content: "first")      assert_equal("first", t.content) 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/type/type_map_test.rb b/activerecord/test/cases/type/type_map_test.rb new file mode 100644 index 0000000000..4e32f92dd0 --- /dev/null +++ b/activerecord/test/cases/type/type_map_test.rb @@ -0,0 +1,130 @@ +require "cases/helper" + +module ActiveRecord +  module Type +    class TypeMapTest < ActiveRecord::TestCase +      def test_default_type +        mapping = TypeMap.new + +        assert_kind_of Value, mapping.lookup(:undefined) +      end + +      def test_registering_types +        boolean = Boolean.new +        mapping = TypeMap.new + +        mapping.register_type(/boolean/i, boolean) + +        assert_equal mapping.lookup('boolean'), boolean +      end + +      def test_overriding_registered_types +        time = Time.new +        timestamp = DateTime.new +        mapping = TypeMap.new + +        mapping.register_type(/time/i, time) +        mapping.register_type(/time/i, timestamp) + +        assert_equal mapping.lookup('time'), timestamp +      end + +      def test_fuzzy_lookup +        string = String.new +        mapping = TypeMap.new + +        mapping.register_type(/varchar/i, string) + +        assert_equal mapping.lookup('varchar(20)'), string +      end + +      def test_aliasing_types +        string = String.new +        mapping = TypeMap.new + +        mapping.register_type(/string/i, string) +        mapping.alias_type(/varchar/i, 'string') + +        assert_equal mapping.lookup('varchar'), string +      end + +      def test_changing_type_changes_aliases +        time = Time.new +        timestamp = DateTime.new +        mapping = TypeMap.new + +        mapping.register_type(/timestamp/i, time) +        mapping.alias_type(/datetime/i, 'timestamp') +        mapping.register_type(/timestamp/i, timestamp) + +        assert_equal mapping.lookup('datetime'), timestamp +      end + +      def test_aliases_keep_metadata +        mapping = TypeMap.new + +        mapping.register_type(/decimal/i) { |sql_type| sql_type } +        mapping.alias_type(/number/i, 'decimal') + +        assert_equal mapping.lookup('number(20)'), 'decimal(20)' +        assert_equal mapping.lookup('number'), 'decimal' +      end + +      def test_register_proc +        string = String.new +        binary = Binary.new +        mapping = TypeMap.new + +        mapping.register_type(/varchar/i) do |type| +          if type.include?('(') +            string +          else +            binary +          end +        end + +        assert_equal mapping.lookup('varchar(20)'), string +        assert_equal mapping.lookup('varchar'), binary +      end + +      def test_additional_lookup_args +        mapping = TypeMap.new + +        mapping.register_type(/varchar/i) do |type, limit| +          if limit > 255 +            'text' +          else +            'string' +          end +        end +        mapping.alias_type(/string/i, 'varchar') + +        assert_equal mapping.lookup('varchar', 200), 'string' +        assert_equal mapping.lookup('varchar', 400), 'text' +        assert_equal mapping.lookup('string', 400), 'text' +      end + +      def test_requires_value_or_block +        mapping = TypeMap.new + +        assert_raises(ArgumentError) do +          mapping.register_type(/only key/i) +        end +      end + +      def test_lookup_non_strings +        mapping = HashLookupTypeMap.new + +        mapping.register_type(1, 'string') +        mapping.register_type(2, 'int') +        mapping.alias_type(3, 1) + +        assert_equal mapping.lookup(1), 'string' +        assert_equal mapping.lookup(2), 'int' +        assert_equal mapping.lookup(3), 'string' +        assert_kind_of Type::Value, mapping.lookup(4) +      end +    end +  end +end + 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 c15ee5022e..5f459cf682 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -855,6 +855,13 @@ ActiveRecord::Schema.define do      execute "ALTER TABLE lessons_students ADD CONSTRAINT student_id_fk FOREIGN KEY (#{quote_column_name 'student_id'}) REFERENCES #{quote_table_name 'students'} (#{quote_column_name 'id'})"    end + +  create_table :overloaded_types, force: true do |t| +    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  Course.connection.create_table :courses, force: true do |t| 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 2a992f60bb..8a9d31a7bb 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,43 @@ +*   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: + +        DateTime.now.advance(days: 1, hours: 12) + +    After: + +        DateTime.now.advance(days: 1.5) + +    Fixes #12005. + +    *Shay Davidson* +  *   `Hash#deep_transform_keys` and `Hash#deep_transform_keys!` now transform hashes      in nested arrays.  This change also applies to `Hash#deep_stringify_keys`,      `Hash#deep_stringify_keys!`, `Hash#deep_symbolize_keys` and 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/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 73ad0aa097..289ca12b5e 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -53,6 +53,16 @@ class DateTime    # <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,    # <tt>:minutes</tt>, <tt>:seconds</tt>.    def advance(options) +    unless options[:weeks].nil? +      options[:weeks], partial_weeks = options[:weeks].divmod(1) +      options[:days] = options.fetch(:days, 0) + 7 * partial_weeks +    end + +    unless options[:days].nil? +      options[:days], partial_days = options[:days].divmod(1) +      options[:hours] = options.fetch(:hours, 0) + 24 * partial_days +    end +      d = to_date.advance(options)      datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)      seconds_to_advance = \ @@ -63,7 +73,7 @@ class DateTime      if seconds_to_advance.zero?        datetime_advanced_by_date      else -      datetime_advanced_by_date.since seconds_to_advance +      datetime_advanced_by_date.since(seconds_to_advance)      end    end diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index 28536e32a4..5934c578ea 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -27,7 +27,7 @@ class Hash    #   hash = { name: 'Rob', age: '28' }    #    #   hash.stringify_keys -  #   # => { "name" => "Rob", "age" => "28" } +  #   # => {"name"=>"Rob", "age"=>"28"}    def stringify_keys      transform_keys{ |key| key.to_s }    end @@ -44,7 +44,7 @@ class Hash    #   hash = { 'name' => 'Rob', 'age' => '28' }    #    #   hash.symbolize_keys -  #   # => { name: "Rob", age: "28" } +  #   # => {:name=>"Rob", :age=>"28"}    def symbolize_keys      transform_keys{ |key| key.to_sym rescue key }    end diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb index 704c4248d9..689fae4830 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -61,25 +61,7 @@ class Numeric    end    alias :fortnight :fortnights -  # Reads best without arguments:  10.minutes.ago -  def ago(time = ::Time.current) -    ActiveSupport::Deprecation.warn "Calling #ago or #until on a number (e.g. 5.ago) is deprecated and will be removed in the future, use 5.seconds.ago instead" -    time - self -  end - -  # Reads best with argument:  10.minutes.until(time) -  alias :until :ago - -  # Reads best with argument:  10.minutes.since(time) -  def since(time = ::Time.current) -    ActiveSupport::Deprecation.warn "Calling #since or #from_now on a number (e.g. 5.since) is deprecated and will be removed in the future, use 5.seconds.since instead" -    time + self -  end - -  # Reads best without arguments:  10.minutes.from_now -  alias :from_now :since - -  # Used with the standard time durations, like 1.hour.in_milliseconds --  +  # Used with the standard time durations, like 1.hour.in_milliseconds --    # so we can feed them to JavaScript functions like getTime().    def in_milliseconds      self * 1000 diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 2c8995be9a..46cd170c1d 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -19,12 +19,7 @@ class ERB      #   puts html_escape('is a > 0 & a < 10?')      #   # => is a > 0 & a < 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 & 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.rb b/activesupport/lib/active_support/time.rb index 92a593965e..ea2d3391bd 100644 --- a/activesupport/lib/active_support/time.rb +++ b/activesupport/lib/active_support/time.rb @@ -1,5 +1,3 @@ -require 'active_support' -  module ActiveSupport    autoload :Duration, 'active_support/duration'    autoload :TimeWithZone, 'active_support/time_with_zone' 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/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 0a40aeb96c..224172e39f 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -162,6 +162,12 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase      assert_equal DateTime.civil(2013,10,17,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9)    end +  def test_advance_partial_days +    assert_equal DateTime.civil(2012,9,29,13,15,10),  DateTime.civil(2012,9,28,1,15,10).advance(:days => 1.5) +    assert_equal DateTime.civil(2012,9,28,13,15,10),  DateTime.civil(2012,9,28,1,15,10).advance(:days => 0.5) +    assert_equal DateTime.civil(2012,10,29,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(:days => 1.5, :months => 1) +  end +    def test_advanced_processes_first_the_date_deltas_and_then_the_time_deltas      # If the time deltas were processed first, the following datetimes would be advanced to 2010/04/01 instead.      assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59, 59).advance(:months => 1, :seconds => 1) 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/inflector_test.rb b/activesupport/test/inflector_test.rb index b0b4738eb3..eb8b0d878e 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -498,10 +498,10 @@ class InflectorTest < ActiveSupport::TestCase    end    %w(plurals singulars uncountables humans acronyms).each do |scope| -    ActiveSupport::Inflector.inflections do |inflect| -      define_method("test_clear_inflections_with_#{scope}") do -        with_dup do -          # clear the inflections +    define_method("test_clear_inflections_with_#{scope}") do +      with_dup do +        # clear the inflections +        ActiveSupport::Inflector.inflections do |inflect|            inflect.clear(scope)            assert_equal [], inflect.send(scope)          end @@ -516,9 +516,10 @@ class InflectorTest < ActiveSupport::TestCase    # there are module functions that access ActiveSupport::Inflector.inflections,    # so we need to replace the singleton itself.    def with_dup -    original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__) -    ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original.dup) +    original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en] +    ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup) +    yield    ensure -    ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original) +    ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original)    end  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/CHANGELOG.md b/guides/CHANGELOG.md index 5a6e8add6c..4cd4a9d70c 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,3 +1,8 @@ +* Change Posts to Articles in Getting Started sample application in order to +better align with the actual guides. + +    * John Kelly Ferguson* +  * Update all Rails 4.1.0 references to 4.1.1 within the guides and code.      * John Kelly Ferguson* @@ -5,7 +10,7 @@  * Split up rows in the Explain Queries table of the ActiveRecord Querying section  in order to improve readability. -    * John Kelly Ferguson * +    * John Kelly Ferguson*  *   Change all non-HTTP method 'post' references to 'article'. diff --git a/guides/code/getting_started/app/assets/javascripts/posts.js.coffee b/guides/code/getting_started/app/assets/javascripts/articles.js.coffee index 24f83d18bb..24f83d18bb 100644 --- a/guides/code/getting_started/app/assets/javascripts/posts.js.coffee +++ b/guides/code/getting_started/app/assets/javascripts/articles.js.coffee diff --git a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss b/guides/code/getting_started/app/assets/stylesheets/articles.css.scss index 1a7e15390c..cca548710d 100644 --- a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss +++ b/guides/code/getting_started/app/assets/stylesheets/articles.css.scss @@ -1,3 +1,3 @@ -// Place all the styles related to the posts controller here. +// Place all the styles related to the articles controller here.  // They will automatically be included in application.css.  // You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/guides/code/getting_started/app/controllers/articles_controller.rb b/guides/code/getting_started/app/controllers/articles_controller.rb new file mode 100644 index 0000000000..275b84e8b7 --- /dev/null +++ b/guides/code/getting_started/app/controllers/articles_controller.rb @@ -0,0 +1,53 @@ +class ArticlesController < ApplicationController + +  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] + +  def index +    @articles = Article.all +  end + +  def show +    @article = Article.find(params[:id]) +  end + +  def edit +    @article = Article.find(params[:id]) +  end + +  def update +    @article = Article.find(params[:id]) + +    if @article.update(article_params) +      redirect_to action: :show, id: @article.id +    else +      render 'edit' +    end +  end + +  def new +    @article = Article.new +  end + +  def create +    @article = Article.new(article_params) + +    if @article.save +      redirect_to action: :show, id: @article.id +    else +      render 'new' +    end +  end + +  def destroy +    @article = Article.find(params[:id]) +    @article.destroy + +    redirect_to action: :index +  end + +  private + +    def article_params +      params.require(:article).permit(:title, :text) +    end +end diff --git a/guides/code/getting_started/app/controllers/comments_controller.rb b/guides/code/getting_started/app/controllers/comments_controller.rb index b2d9bcdf7f..61813b1003 100644 --- a/guides/code/getting_started/app/controllers/comments_controller.rb +++ b/guides/code/getting_started/app/controllers/comments_controller.rb @@ -3,16 +3,16 @@ class CommentsController < ApplicationController    http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy    def create -    @post = Post.find(params[:post_id]) -    @comment = @post.comments.create(comment_params) -    redirect_to post_path(@post) +    @article = Article.find(params[:article_id]) +    @comment = @article.comments.create(comment_params) +    redirect_to article_path(@article)    end    def destroy -    @post = Post.find(params[:post_id]) -    @comment = @post.comments.find(params[:id]) +    @article = Article.find(params[:article_id]) +    @comment = @article.comments.find(params[:id])      @comment.destroy -    redirect_to post_path(@post) +    redirect_to article_path(@article)    end    private diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb deleted file mode 100644 index 02689ad67b..0000000000 --- a/guides/code/getting_started/app/controllers/posts_controller.rb +++ /dev/null @@ -1,53 +0,0 @@ -class PostsController < ApplicationController - -  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] - -  def index -    @posts = Post.all -  end - -  def show -    @post = Post.find(params[:id]) -  end - -  def edit -    @post = Post.find(params[:id]) -  end - -  def update -    @post = Post.find(params[:id]) - -    if @post.update(post_params) -      redirect_to action: :show, id: @post.id -    else -      render 'edit' -    end -  end - -  def new -    @post = Post.new -  end - -  def create -    @post = Post.new(post_params) - -    if @post.save -      redirect_to action: :show, id: @post.id -    else -      render 'new' -    end -  end - -  def destroy -    @post = Post.find(params[:id]) -    @post.destroy - -    redirect_to action: :index -  end - -  private - -    def post_params -      params.require(:post).permit(:title, :text) -    end -end diff --git a/guides/code/getting_started/app/helpers/articles_helper.rb b/guides/code/getting_started/app/helpers/articles_helper.rb new file mode 100644 index 0000000000..2968277595 --- /dev/null +++ b/guides/code/getting_started/app/helpers/articles_helper.rb @@ -0,0 +1,2 @@ +module ArticlesHelper +end diff --git a/guides/code/getting_started/app/helpers/posts_helper.rb b/guides/code/getting_started/app/helpers/posts_helper.rb deleted file mode 100644 index a7b8cec898..0000000000 --- a/guides/code/getting_started/app/helpers/posts_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module PostsHelper -end diff --git a/guides/code/getting_started/app/models/post.rb b/guides/code/getting_started/app/models/article.rb index 64e0d721fd..6fc7888be2 100644 --- a/guides/code/getting_started/app/models/post.rb +++ b/guides/code/getting_started/app/models/article.rb @@ -1,6 +1,6 @@ -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    has_many :comments, dependent: :destroy -   +    validates :title,      presence: true,      length: { minimum: 5 } diff --git a/guides/code/getting_started/app/models/comment.rb b/guides/code/getting_started/app/models/comment.rb index 4e76c5b5b0..e2646a324f 100644 --- a/guides/code/getting_started/app/models/comment.rb +++ b/guides/code/getting_started/app/models/comment.rb @@ -1,3 +1,3 @@  class Comment < ActiveRecord::Base -  belongs_to :post +  belongs_to :article  end diff --git a/guides/code/getting_started/app/views/posts/_form.html.erb b/guides/code/getting_started/app/views/articles/_form.html.erb index f2f83585e1..87e3353ed2 100644 --- a/guides/code/getting_started/app/views/posts/_form.html.erb +++ b/guides/code/getting_started/app/views/articles/_form.html.erb @@ -1,10 +1,10 @@ -<%= form_for @post do |f| %> -  <% if @post.errors.any? %> +<%= form_for @article do |f| %> +  <% if @article.errors.any? %>    <div id="error_explanation"> -    <h2><%= pluralize(@post.errors.count, "error") %> prohibited -      this post from being saved:</h2> +    <h2><%= pluralize(@article.errors.count, "error") %> prohibited +      this article from being saved:</h2>      <ul> -    <% @post.errors.full_messages.each do |msg| %> +    <% @article.errors.full_messages.each do |msg| %>        <li><%= msg %></li>      <% end %>      </ul> diff --git a/guides/code/getting_started/app/views/posts/edit.html.erb b/guides/code/getting_started/app/views/articles/edit.html.erb index 393e7430d0..14236e2a98 100644 --- a/guides/code/getting_started/app/views/posts/edit.html.erb +++ b/guides/code/getting_started/app/views/articles/edit.html.erb @@ -1,5 +1,5 @@ -<h1>Edit post</h1> -  +<h1>Edit article</h1> +  <%= render 'form' %> -  +  <%= link_to 'Back', action: :index %> diff --git a/guides/code/getting_started/app/views/articles/index.html.erb b/guides/code/getting_started/app/views/articles/index.html.erb new file mode 100644 index 0000000000..80e9c8c60c --- /dev/null +++ b/guides/code/getting_started/app/views/articles/index.html.erb @@ -0,0 +1,21 @@ +<h1>Listing Articles</h1> +<table> +  <tr> +    <th>Title</th> +    <th>Text</th> +    <th></th> +    <th></th> +    <th></th> +  </tr> + +<% @articles.each do |article| %> +  <tr> +    <td><%= article.title %></td> +    <td><%= article.text %></td> +    <td><%= link_to 'Show', action: :show, id: article.id %></td> +    <td><%= link_to 'Edit', action: :edit, id: article.id %></td> +    <td><%= link_to 'Destroy', { action: :destroy, id: article.id }, +                    method: :delete, data: { confirm: 'Are you sure?' } %></td> +  </tr> +<% end %> +</table> diff --git a/guides/code/getting_started/app/views/posts/new.html.erb b/guides/code/getting_started/app/views/articles/new.html.erb index efa81038ec..652b1c9c0b 100644 --- a/guides/code/getting_started/app/views/posts/new.html.erb +++ b/guides/code/getting_started/app/views/articles/new.html.erb @@ -1,5 +1,5 @@ -<h1>New post</h1> -  +<h1>New article</h1> +  <%= render 'form' %> -  +  <%= link_to 'Back', action: :index %> diff --git a/guides/code/getting_started/app/views/articles/show.html.erb b/guides/code/getting_started/app/views/articles/show.html.erb new file mode 100644 index 0000000000..6959c80bdb --- /dev/null +++ b/guides/code/getting_started/app/views/articles/show.html.erb @@ -0,0 +1,18 @@ +<p> +  <strong>Title:</strong> +  <%= @article.title %> +</p> + +<p> +  <strong>Text:</strong> +  <%= @article.text %> +</p> + +<h2>Comments</h2> +<%= render @article.comments %> + +<h2>Add a comment:</h2> +<%= render "comments/form" %> + +<%= link_to 'Edit Article', edit_article_path(@article) %> | +<%= link_to 'Back to Articles', articles_path %> diff --git a/guides/code/getting_started/app/views/comments/_comment.html.erb b/guides/code/getting_started/app/views/comments/_comment.html.erb index 593493339e..f7cbfaebfa 100644 --- a/guides/code/getting_started/app/views/comments/_comment.html.erb +++ b/guides/code/getting_started/app/views/comments/_comment.html.erb @@ -2,14 +2,14 @@    <strong>Commenter:</strong>    <%= comment.commenter %>  </p> -  +  <p>    <strong>Comment:</strong>    <%= comment.body %>  </p>  <p> -  <%= link_to 'Destroy Comment', [comment.post, comment], +  <%= link_to 'Destroy Comment', [comment.article, comment],                 method: :delete,                 data: { confirm: 'Are you sure?' } %>  </p> diff --git a/guides/code/getting_started/app/views/comments/_form.html.erb b/guides/code/getting_started/app/views/comments/_form.html.erb index 00cb3a08f0..5850c41a17 100644 --- a/guides/code/getting_started/app/views/comments/_form.html.erb +++ b/guides/code/getting_started/app/views/comments/_form.html.erb @@ -1,4 +1,4 @@ -<%= form_for([@post, @post.comments.build]) do |f| %> +<%= form_for([@article, @article.comments.build]) do |f| %>    <p>      <%= f.label :commenter %><br />      <%= f.text_field :commenter %> diff --git a/guides/code/getting_started/app/views/posts/index.html.erb b/guides/code/getting_started/app/views/posts/index.html.erb deleted file mode 100644 index 7369f0396f..0000000000 --- a/guides/code/getting_started/app/views/posts/index.html.erb +++ /dev/null @@ -1,21 +0,0 @@ -<h1>Listing Posts</h1> -<table> -  <tr> -    <th>Title</th> -    <th>Text</th> -    <th></th> -    <th></th> -    <th></th> -  </tr> -  -<% @posts.each do |post| %> -  <tr> -    <td><%= post.title %></td> -    <td><%= post.text %></td> -    <td><%= link_to 'Show', action: :show, id: post.id %></td> -    <td><%= link_to 'Edit', action: :edit, id: post.id %></td> -    <td><%= link_to 'Destroy', { action: :destroy, id: post.id }, -                    method: :delete, data: { confirm: 'Are you sure?' } %></td> -  </tr> -<% end %> -</table> diff --git a/guides/code/getting_started/app/views/posts/show.html.erb b/guides/code/getting_started/app/views/posts/show.html.erb deleted file mode 100644 index e99e9edbb3..0000000000 --- a/guides/code/getting_started/app/views/posts/show.html.erb +++ /dev/null @@ -1,18 +0,0 @@ -<p> -  <strong>Title:</strong> -  <%= @post.title %> -</p> -  -<p> -  <strong>Text:</strong> -  <%= @post.text %> -</p> - -<h2>Comments</h2> -<%= render @post.comments %> -  -<h2>Add a comment:</h2> -<%= render "comments/form" %> -  -<%= link_to 'Edit Post', edit_post_path(@post) %> | -<%= link_to 'Back to Posts', posts_path %> diff --git a/guides/code/getting_started/app/views/welcome/index.html.erb b/guides/code/getting_started/app/views/welcome/index.html.erb index 56be8dd3cc..1cabd0d217 100644 --- a/guides/code/getting_started/app/views/welcome/index.html.erb +++ b/guides/code/getting_started/app/views/welcome/index.html.erb @@ -1,4 +1,4 @@  <h1>Hello, Rails!</h1> -<%= link_to "My Blog", controller: "posts" %> -<%= link_to "New Post", new_post_path %> +<%= link_to "My Blog", controller: "articles" %> +<%= link_to "New Article", new_article_path %> diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb index 65d273b58d..97abca99b9 100644 --- a/guides/code/getting_started/config/routes.rb +++ b/guides/code/getting_started/config/routes.rb @@ -1,5 +1,5 @@  Rails.application.routes.draw do -  resources :posts do +  resources :articles do      resources :comments    end diff --git a/guides/code/getting_started/db/migrate/20130122042648_create_posts.rb b/guides/code/getting_started/db/migrate/20130122042648_create_articles.rb index 602bef31ab..6bb255e89f 100644 --- a/guides/code/getting_started/db/migrate/20130122042648_create_posts.rb +++ b/guides/code/getting_started/db/migrate/20130122042648_create_articles.rb @@ -1,6 +1,6 @@ -class CreatePosts < ActiveRecord::Migration +class CreateArticles < ActiveRecord::Migration    def change -    create_table :posts do |t| +    create_table :articles do |t|        t.string :title        t.text :text diff --git a/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb b/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb index 3e51f9c0f7..1f765839ac 100644 --- a/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb +++ b/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb @@ -3,7 +3,7 @@ class CreateComments < ActiveRecord::Migration      create_table :comments do |t|        t.string :commenter        t.text :body -      t.references :post, index: true +      t.references :article, index: true        t.timestamps      end diff --git a/guides/code/getting_started/db/schema.rb b/guides/code/getting_started/db/schema.rb index 101fe712a1..be40f7cb0e 100644 --- a/guides/code/getting_started/db/schema.rb +++ b/guides/code/getting_started/db/schema.rb @@ -13,21 +13,21 @@  ActiveRecord::Schema.define(version: 20130122045842) do -  create_table "comments", force: true do |t| -    t.string   "commenter" -    t.text     "body" -    t.integer  "post_id" +  create_table "articles", force: true do |t| +    t.string   "title" +    t.text     "text"      t.datetime "created_at"      t.datetime "updated_at"    end -  add_index "comments", ["post_id"], name: "index_comments_on_post_id" - -  create_table "posts", force: true do |t| -    t.string   "title" -    t.text     "text" +  create_table "comments", force: true do |t| +    t.string   "commenter" +    t.text     "body" +    t.integer  "article_id"      t.datetime "created_at"      t.datetime "updated_at"    end +  add_index "comments", ["article_id"], name: "index_comments_on_article_id" +  end diff --git a/guides/code/getting_started/test/controllers/posts_controller_test.rb b/guides/code/getting_started/test/controllers/articles_controller_test.rb index 7a6ee4f1db..361aa0f47f 100644 --- a/guides/code/getting_started/test/controllers/posts_controller_test.rb +++ b/guides/code/getting_started/test/controllers/articles_controller_test.rb @@ -1,6 +1,6 @@  require 'test_helper' -class PostsControllerTest < ActionController::TestCase +class ArticlesControllerTest < ActionController::TestCase    # test "the truth" do    #   assert true    # end diff --git a/guides/code/getting_started/test/fixtures/posts.yml b/guides/code/getting_started/test/fixtures/articles.yml index 46b01c3bb4..46b01c3bb4 100644 --- a/guides/code/getting_started/test/fixtures/posts.yml +++ b/guides/code/getting_started/test/fixtures/articles.yml diff --git a/guides/code/getting_started/test/fixtures/comments.yml b/guides/code/getting_started/test/fixtures/comments.yml index 9e409d8a61..05ad26f051 100644 --- a/guides/code/getting_started/test/fixtures/comments.yml +++ b/guides/code/getting_started/test/fixtures/comments.yml @@ -3,9 +3,9 @@  one:    commenter: MyString    body: MyText -  post_id:  +  article_id:  two:    commenter: MyString    body: MyText -  post_id:  +  article_id: diff --git a/guides/code/getting_started/test/helpers/articles_helper_test.rb b/guides/code/getting_started/test/helpers/articles_helper_test.rb new file mode 100644 index 0000000000..b341344067 --- /dev/null +++ b/guides/code/getting_started/test/helpers/articles_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class ArticlesHelperTest < ActionView::TestCase +end diff --git a/guides/code/getting_started/test/helpers/posts_helper_test.rb b/guides/code/getting_started/test/helpers/posts_helper_test.rb deleted file mode 100644 index 48549c2ea1..0000000000 --- a/guides/code/getting_started/test/helpers/posts_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class PostsHelperTest < ActionView::TestCase -end diff --git a/guides/code/getting_started/test/models/post_test.rb b/guides/code/getting_started/test/models/article_test.rb index 6d9d463a71..11c8abe5f4 100644 --- a/guides/code/getting_started/test/models/post_test.rb +++ b/guides/code/getting_started/test/models/article_test.rb @@ -1,6 +1,6 @@  require 'test_helper' -class PostTest < ActiveSupport::TestCase +class ArticleTest < ActiveSupport::TestCase    # test "the truth" do    #   assert true    # end diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md index db34fa401f..2d7c06837b 100644 --- a/guides/source/3_0_release_notes.md +++ b/guides/source/3_0_release_notes.md @@ -308,7 +308,7 @@ More Information:  Major re-write was done in the Action View helpers, implementing Unobtrusive JavaScript (UJS) hooks and removing the old inline AJAX commands. This enables Rails to use any compliant UJS driver to implement the UJS hooks in the helpers. -What this means is that all previous `remote_<method>` helpers have been removed from Rails core and put into the [Prototype Legacy Helper](http://github.com/rails/prototype_legacy_helper.) To get UJS hooks into your HTML, you now pass `:remote => true` instead. For example: +What this means is that all previous `remote_<method>` helpers have been removed from Rails core and put into the [Prototype Legacy Helper](http://github.com/rails/prototype_legacy_helper). To get UJS hooks into your HTML, you now pass `:remote => true` instead. For example:  ```ruby  form_for @post, :remote => true 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_postgresql.md b/guides/source/active_record_postgresql.md index b69c8dbb7a..dfa488773e 100644 --- a/guides/source/active_record_postgresql.md +++ b/guides/source/active_record_postgresql.md @@ -429,5 +429,5 @@ first.archive!  p Article.count # => 2  ``` -Note: This application only cares about non-archived `Articles`. A view also +NOTE: This application only cares about non-archived `Articles`. A view also  allows for conditions so we can exclude the archived `Articles` directly. 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/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index d3a96daf7b..133ef58fd6 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -215,6 +215,36 @@ Rails follows a simple set of coding style conventions:  The above are guidelines - please use your best judgment in using them. +### Benchmark Your Code + +If your change has an impact on the performance of Rails, please use the +[benchmark-ips](https://github.com/evanphx/benchmark-ips) gem to provide +benchmark results for comparison. + +Here's an example of using benchmark-ips: + +```ruby +require 'benchmark/ips' + +Benchmark.ips do |x| +  x.report('addition') { 1 + 2 } +  x.report('addition with send') { 1.send(:+, 2) } +end +``` + +This will generate a report with the following information: + +``` +Calculating ------------------------------------- +            addition     69114 i/100ms +  addition with send     64062 i/100ms +------------------------------------------------- +            addition  5307644.4 (±3.5%) i/s -   26539776 in   5.007219s +  addition with send  3702897.9 (±3.5%) i/s -   18513918 in   5.006723s +``` + +Please see the benchmark/ips [README](https://github.com/evanphx/benchmark-ips/blob/master/README.md) for more information. +  ### Running Tests  It is not customary in Rails to run the full test suite before pushing diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index a160c462b2..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. @@ -162,7 +157,6 @@      -        name: Upgrading Ruby on Rails        url: upgrading_ruby_on_rails.html -      work_in_progress: true        description: This guide helps in upgrading applications to latest Ruby on Rails versions.      -        name: Ruby on Rails 4.1 Release Notes diff --git a/guides/source/engines.md b/guides/source/engines.md index 1321fa3870..e7f024f1fc 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -471,7 +471,7 @@ called `Blorgh::Comment`. Now run the migration to create our blorgh_comments  table:  ```bash -$ bin/rake db:migrate +$ rake db:migrate  ```  To show the comments on an article, edit `app/views/blorgh/articles/show.html.erb` and @@ -682,14 +682,14 @@ engine's models can query them correctly. To copy these migrations into the  application use this command:  ```bash -$ bin/rake blorgh:install:migrations +$ rake blorgh:install:migrations  ```  If you have multiple engines that need migrations copied over, use  `railties:install:migrations` instead:  ```bash -$ bin/rake railties:install:migrations +$ rake railties:install:migrations  ```  This command, when run for the first time, will copy over all the migrations @@ -822,7 +822,7 @@ This migration will need to be run on the application. To do that, it must first  be copied using this command:  ```bash -$ bin/rake blorgh:install:migrations +$ rake blorgh:install:migrations  ```  Notice that only _one_ migration was copied over here. This is because the first @@ -839,7 +839,7 @@ with the same name already exists. Copied migration  Run the migration using:  ```bash -$ bin/rake db:migrate +$ rake db:migrate  ```  Now with all the pieces in place, an action will take place that will associate diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index 027b6303fc..048eb9a6e3 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -17,7 +17,6 @@ After reading this guide, you will know:  NOTE: This guide is not intended to be a complete documentation of available form helpers and their arguments. Please visit [the Rails API documentation](http://api.rubyonrails.org/) for a complete reference. -  Dealing with Basic Forms  ------------------------ @@ -32,18 +31,14 @@ The most basic form helper is `form_tag`.  When called without arguments like this, it creates a `<form>` tag which, when submitted, will POST to the current page. For instance, assuming the current page is `/home/index`, the generated HTML will look like this (some line breaks added for readability):  ```html -<form accept-charset="UTF-8" action="/home/index" method="post"> -  <div style="margin:0;padding:0"> -    <input name="utf8" type="hidden" value="✓" /> -    <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" /> -  </div> +<form accept-charset="UTF-8" action="/" method="post"> +  <input name="utf8" type="hidden" value="✓" /> +  <input name="authenticity_token" type="hidden" value="J7CBxfHalt49OSHp27hblqK20c9PgwJ108nDHX/8Cts=" />    Form contents  </form>  ``` -Now, you'll notice that the HTML contains something extra: a `div` element with two hidden input elements inside. This div is important, because the form cannot be successfully submitted without it. The first input element with name `utf8` enforces browsers to properly respect your form's character encoding and is generated for all forms whether their actions are "GET" or "POST". The second input element with name `authenticity_token` is a security feature of Rails called **cross-site request forgery protection**, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the [Security Guide](./security.html#cross-site-request-forgery-csrf). - -NOTE: Throughout this guide, the `div` with the hidden input elements will be excluded from code samples for brevity. +You'll notice that the HTML contains `input` element with type `hidden`. This `input` is important, because the form cannot be successfully submitted without it. The hidden input element has name attribute of `utf8` enforces browsers to properly respect your form's character encoding and is generated for all forms whether their actions are "GET" or "POST". The second input element with name `authenticity_token` is a security feature of Rails called **cross-site request forgery protection**, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the [Security Guide](security.html#cross-site-request-forgery-csrf).  ### A Generic Search Form @@ -67,14 +62,15 @@ To create this form you will use `form_tag`, `label_tag`, `text_field_tag`, and  This will generate the following HTML:  ```html -<form accept-charset="UTF-8" action="/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /></div> +<form accept-charset="UTF-8" action="/search" method="get"> +  <input name="utf8" type="hidden" value="✓" />    <label for="q">Search for:</label>    <input id="q" name="q" type="text" />    <input name="commit" type="submit" value="Search" />  </form>  ``` -TIP: For every form input, an ID attribute is generated from its name ("q" in the example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript. +TIP: For every form input, an ID attribute is generated from its name (`"q"` in above example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript.  Besides `text_field_tag` and `submit_tag`, there is a similar helper for _every_ form control in HTML. @@ -146,7 +142,7 @@ Output:  <label for="age_adult">I'm over 21</label>  ``` -As with `check_box_tag`, the second parameter to `radio_button_tag` is the value of the input. Because these two radio buttons share the same name (`age`), the user will only be able to select one of them, and `params[:age]` will contain either "child" or "adult". +As with `check_box_tag`, the second parameter to `radio_button_tag` is the value of the input. Because these two radio buttons share the same name (`age`), the user will only be able to select one of them, and `params[:age]` will contain either `"child"` or `"adult"`.  NOTE: Always use labels for checkbox and radio buttons. They associate text with a specific option and,  by expanding the clickable region, @@ -217,7 +213,7 @@ Dealing with Model Objects  ### Model Object Helpers -A particularly common task for a form is editing or creating a model object. While the `*_tag` helpers can certainly be used for this task they are somewhat verbose as for each tag you would have to ensure the correct parameter name is used and set the default value of the input appropriately. Rails provides helpers tailored to this task. These helpers lack the _tag suffix, for example `text_field`, `text_area`. +A particularly common task for a form is editing or creating a model object. While the `*_tag` helpers can certainly be used for this task they are somewhat verbose as for each tag you would have to ensure the correct parameter name is used and set the default value of the input appropriately. Rails provides helpers tailored to this task. These helpers lack the `_tag` suffix, for example `text_field`, `text_area`.  For these helpers the first argument is the name of an instance variable and the second is the name of a method (usually an attribute) to call on that object. Rails will set the value of the input control to the return value of that method for the object and set an appropriate input name. If your controller has defined `@person` and that person's name is Henry then a form containing: @@ -239,7 +235,7 @@ Rails provides helpers for displaying the validation errors associated with a mo  ### Binding a Form to an Object -While this is an increase in comfort it is far from perfect. If Person has many attributes to edit then we would be repeating the name of the edited object many times. What we want to do is somehow bind a form to a model object, which is exactly what `form_for` does. +While this is an increase in comfort it is far from perfect. If `Person` has many attributes to edit then we would be repeating the name of the edited object many times. What we want to do is somehow bind a form to a model object, which is exactly what `form_for` does.  Assume we have a controller for dealing with articles `app/controllers/articles_controller.rb`: @@ -264,7 +260,7 @@ There are a few things to note here:  * `@article` is the actual object being edited.  * There is a single hash of options. Routing options are passed in the `:url` hash, HTML options are passed in the `:html` hash. Also you can provide a `:namespace` option for your form to ensure uniqueness of id attributes on form elements. The namespace attribute will be prefixed with underscore on the generated HTML id.  * The `form_for` method yields a **form builder** object (the `f` variable). -* Methods to create form controls are called **on** the form builder object `f` +* Methods to create form controls are called **on** the form builder object `f`.  The resulting HTML is: @@ -280,7 +276,7 @@ The name passed to `form_for` controls the key used in `params` to access the fo  The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder. -You can create a similar binding without actually creating `<form>` tags with the `fields_for` helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for creating both like so: +You can create a similar binding without actually creating `<form>` tags with the `fields_for` helper. This is useful for editing additional model objects with the same form. For example if you had a `Person` model with an associated `ContactDetail` model you could create a form for creating both like so:  ```erb  <%= form_for @person, url: {action: "create"} do |person_form| %> @@ -350,7 +346,6 @@ form_for [:admin, :management, @article]  For more information on Rails' routing system and the associated conventions, please see the [routing guide](routing.html). -  ### How do forms with PATCH, PUT, or DELETE methods work?  The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PATCH" and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms. @@ -365,12 +360,11 @@ output:  ```html  <form accept-charset="UTF-8" action="/search" method="post"> -  <div style="margin:0;padding:0"> -    <input name="_method" type="hidden" value="patch" /> -    <input name="utf8" type="hidden" value="✓" /> -    <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" /> -  </div> +  <input name="_method" type="hidden" value="patch" /> +  <input name="utf8" type="hidden" value="✓" /> +  <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />    ... +</form>  ```  When parsing POSTed data, Rails will take into account the special `_method` parameter and acts as if the HTTP method was the one specified inside it ("PATCH" in this example). @@ -435,7 +429,7 @@ output:  Whenever Rails sees that the internal value of an option being generated matches this value, it will add the `selected` attribute to that option. -TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer 2 you cannot pass "2" to `options_for_select` - you must pass 2. Be aware of values extracted from the `params` hash as they are all strings. +TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer `2` you cannot pass `"2"` to `options_for_select` - you must pass `2`. Be aware of values extracted from the `params` hash as they are all strings.  WARNING: when `:include_blank` or `:prompt` are not present, `:include_blank` is forced true if the select attribute `required` is true, display `size` is one and `multiple` is not true. @@ -489,11 +483,11 @@ You can also pass a block to `select` helper:  <% end %>  ``` -WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of ` ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) ` when you pass the `params` hash to `Person.new` or `update`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. +WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of `ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750)` when you pass the `params` hash to `Person.new` or `update`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly.  ### Option Tags from a Collection of Arbitrary Objects -Generating options tags with `options_for_select` requires that you create an array containing the text and value for each option. But what if you had a City model (perhaps an Active Record one) and you wanted to generate option tags from a collection of those objects? One solution would be to make a nested array by iterating over them: +Generating options tags with `options_for_select` requires that you create an array containing the text and value for each option. But what if you had a `City` model (perhaps an Active Record one) and you wanted to generate option tags from a collection of those objects? One solution would be to make a nested array by iterating over them:  ```erb  <% cities_array = City.all.map { |city| [city.name, city.id] } %> @@ -540,7 +534,7 @@ Both of these families of helpers will create a series of select boxes for the d  ### Barebones Helpers -The `select_*` family of helpers take as their first argument an instance of Date, Time or DateTime that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example +The `select_*` family of helpers take as their first argument an instance of `Date`, `Time` or `DateTime` that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example  ```erb  <%= select_date Date.today, prefix: :start_date %> @@ -554,7 +548,7 @@ outputs (with actual option values omitted for brevity)  <select id="start_date_day" name="start_date[day]"> ... </select>  ``` -The above inputs would result in `params[:start_date]` being a hash with keys `:year`, `:month`, `:day`. To get an actual Time or Date object you would have to extract these values and pass them to the appropriate constructor, for example +The above inputs would result in `params[:start_date]` being a hash with keys `:year`, `:month`, `:day`. To get an actual `Date`, `Time` or `DateTime` object you would have to extract these values and pass them to the appropriate constructor, for example  ```ruby  Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i) @@ -599,7 +593,7 @@ NOTE: In many cases the built-in date pickers are clumsy as they do not aid the  Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for `select_year`, "month" for `select_month` etc.) although this can be overridden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value. -The first parameter specifies which value should be selected and can either be an instance of a Date, Time or DateTime, in which case the relevant component will be extracted, or a numerical value. For example +The first parameter specifies which value should be selected and can either be an instance of a `Date`, `Time` or `DateTime`, in which case the relevant component will be extracted, or a numerical value. For example  ```erb  <%= select_year(2009) %> @@ -629,7 +623,7 @@ Rails provides the usual pair of helpers: the barebones `file_field_tag` and the  ### What Gets Uploaded -The object in the `params` hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#{Rails.root}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example). +The object in the `params` hash is an instance of a subclass of `IO`. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of `File` backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#{Rails.root}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example).  ```ruby  def upload @@ -640,7 +634,7 @@ def upload  end  ``` -Once a file has been uploaded, there are a multitude of potential tasks, ranging from where to store the files (on disk, Amazon S3, etc) and associating them with models to resizing image files and generating thumbnails. The intricacies of this are beyond the scope of this guide, but there are several libraries designed to assist with these. Two of the better known ones are [CarrierWave](https://github.com/jnicklas/carrierwave) and [Paperclip](http://www.thoughtbot.com/projects/paperclip). +Once a file has been uploaded, there are a multitude of potential tasks, ranging from where to store the files (on disk, Amazon S3, etc) and associating them with models to resizing image files and generating thumbnails. The intricacies of this are beyond the scope of this guide, but there are several libraries designed to assist with these. Two of the better known ones are [CarrierWave](https://github.com/jnicklas/carrierwave) and [Paperclip](https://github.com/thoughtbot/paperclip).  NOTE: If the user has not selected a file the corresponding parameter will be an empty string. @@ -651,7 +645,7 @@ Unlike other forms making an asynchronous file upload form is not as simple as p  Customizing Form Builders  ------------------------- -As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way, you can also subclass FormBuilder and add the helpers there. For example +As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of `FormBuilder` (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way, you can also subclass `FormBuilder` and add the helpers there. For example  ```erb  <%= form_for @person do |f| %> @@ -667,7 +661,7 @@ can be replaced with  <% end %>  ``` -by defining a LabellingFormBuilder class similar to the following: +by defining a `LabellingFormBuilder` class similar to the following:  ```ruby  class LabellingFormBuilder < ActionView::Helpers::FormBuilder @@ -685,7 +679,7 @@ The form builder used also determines what happens when you do  <%= render partial: f %>  ``` -If `f` is an instance of FormBuilder then this will render the `form` partial, setting the partial's object to the form builder. If the form builder is of class LabellingFormBuilder then the `labelling_form` partial would be rendered instead. +If `f` is an instance of `FormBuilder` then this will render the `form` partial, setting the partial's object to the form builder. If the form builder is of class `LabellingFormBuilder` then the `labelling_form` partial would be rendered instead.  Understanding Parameter Naming Conventions  ------------------------------------------ @@ -862,7 +856,7 @@ Or if you don't want to render an `authenticity_token` field:  Building Complex Forms  ---------------------- -Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. +Many apps grow beyond simple forms editing a single object. For example when creating a `Person` you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary.  ### Configuring the Model 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/routing.md b/guides/source/routing.md index 0ff13cb07d..c8f8ba3044 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -1044,6 +1044,28 @@ end  This will create routing helpers such as `magazine_periodical_ads_url` and `edit_magazine_periodical_ad_path`. +### Overriding Named Route Parameters + +The `:param` option overrides the default resource identifier `:id` (name of +the [dynamic segment](routing.html#dynamic-segments) used to generate the +routes). You can access that segment from your controller using +`params[<:param>]`. + +```ruby +resources :videos, param: :identifier +``` + +``` +     videos GET  /videos(.:format)                  videos#index +            POST /videos(.:format)                  videos#create + new_videos GET  /videos/new(.:format)              videos#new +edit_videos GET  /videos/:identifier/edit(.:format) videos#edit +``` + +```ruby +Video.find_by(identifier: params[:identifier]) +``` +  Inspecting and Testing Routes  ----------------------------- 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 9e7569465f..b328619646 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,11 @@ +*   Add `bin/setup` script to bootstrap an application. + +    *Yves Senn* + +*   Replace double quotes with single quotes while adding an entry into Gemfile. + +    *Alexander Belaev* +  *   Default `config.assets.digest` to `true` in development.      *Dan Kang* @@ -22,10 +30,6 @@      *Matthew Draper* -*   Do not set the Rails environment to test by default when using test_unit Railtie. - -    *Konstantin Shabanov* -  *   Remove sqlite3 lines from `.gitignore` if the application is not using sqlite3.      *Dmitrii Golub* 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/application.rb b/railties/lib/rails/application.rb index 2fde974732..362713eb75 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -364,6 +364,10 @@ module Rails        end      end +    def migration_railties # :nodoc: +      (ordered_railties & railties_without_main_app).reverse +    end +    protected      alias :build_middleware_stack :app @@ -394,6 +398,11 @@ module Rails        super      end +    def railties_without_main_app # :nodoc: +      @railties_without_main_app ||= Rails::Railtie.subclasses.map(&:instance) + +        Rails::Engine.subclasses.map(&:instance) +    end +      # Returns the ordered railties for this application considering railties_order.      def ordered_railties #:nodoc:        @ordered_railties ||= begin diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index dce734b54e..2a0148fe9d 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -156,10 +156,16 @@ module Rails          args << "--help" if args.empty? && klass.arguments.any? { |a| a.required? }          klass.start(args, config)        else -        puts "Could not find generator #{namespace}." +        puts "Could not find generator '#{namespace}'. Please choose a generator below." +        print_generators        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] @@ -199,17 +205,6 @@ module Rails      # Show help message with available generators.      def self.help(command = 'generate') -      lookup! - -      namespaces = subclasses.map{ |k| k.namespace } -      namespaces.sort! - -      groups = Hash.new { |h,k| h[k] = [] } -      namespaces.each do |namespace| -        base = namespace.split(':').first -        groups[base] << namespace -      end -        puts "Usage: rails #{command} GENERATOR [args] [options]"        puts        puts "General options:" @@ -222,6 +217,20 @@ module Rails        puts "Please choose a generator below."        puts +      print_generators +    end + +    def self.print_generators +      lookup! + +      namespaces = subclasses.map{ |k| k.namespace } +      namespaces.sort! + +      groups = Hash.new { |h,k| h[k] = [] } +      namespaces.each do |namespace| +        base = namespace.split(':').first +        groups[base] << namespace +      end        # Print Rails defaults first.        rails = groups.delete("rails")        rails.map! { |n| n.sub(/^rails:/, '') } diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 625f031c94..a239874df0 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -20,9 +20,9 @@ module Rails          # Set the message to be shown in logs. Uses the git repo if one is given,          # otherwise use name (version). -        parts, message = [ name.inspect ], name +        parts, message = [ quote(name) ], name          if version ||= options.delete(:version) -          parts   << version.inspect +          parts   << quote(version)            message << " (#{version})"          end          message = options[:git] if options[:git] @@ -30,7 +30,7 @@ module Rails          log :gemfile, message          options.each do |option, value| -          parts << "#{option}: #{value.inspect}" +          parts << "#{option}: #{quote(value)}"          end          in_root do @@ -68,7 +68,7 @@ module Rails          log :source, source          in_root do -          prepend_file "Gemfile", "source #{source.inspect}\n", verbose: false +          prepend_file "Gemfile", "source #{quote(source)}\n", verbose: false          end        end @@ -255,6 +255,15 @@ module Rails            end          end +        # Surround string with single quotes if there is no quotes. +        # Otherwise fall back to double quotes +        def quote(str) +          if str.include?("'") +            str.inspect +          else +            "'#{str}'" +          end +        end      end    end  end 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/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index 908c4ce65e..49e5431a16 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -5,7 +5,7 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:    prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH    layout -> { request.xhr? ? false : 'application' } -  before_filter :require_local! +  before_action :require_local!    def index      redirect_to action: :routes diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb index dd318f52e5..32740d66da 100644 --- a/railties/lib/rails/mailers_controller.rb +++ b/railties/lib/rails/mailers_controller.rb @@ -3,8 +3,8 @@ require 'rails/application_controller'  class Rails::MailersController < Rails::ApplicationController # :nodoc:    prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH -  before_filter :require_local! -  before_filter :find_preview, only: :preview +  before_action :require_local! +  before_action :find_preview, only: :preview    def index      @previews = ActionMailer::Preview.all @@ -70,4 +70,4 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:          @email        end      end -end
\ No newline at end of file +end diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb index 3b7f358a5b..df74643a59 100644 --- a/railties/lib/rails/ruby_version_check.rb +++ b/railties/lib/rails/ruby_version_check.rb @@ -2,7 +2,7 @@ if RUBY_VERSION < '1.9.3'    desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"    abort <<-end_message -    Rails 4 prefers to run on Ruby 2.0. +    Rails 4 prefers to run on Ruby 2.1 or newer.      You're running        #{desc} diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake index 019aaf9add..ae5a7d2759 100644 --- a/railties/lib/rails/tasks/statistics.rake +++ b/railties/lib/rails/tasks/statistics.rake @@ -1,23 +1,27 @@ +# while having global constant is not good, +# many 3rd party tools depend on it, like rspec-rails, cucumber-rails, etc +# so if will be removed - deprecation warning is needed +STATS_DIRECTORIES = [ +  %w(Controllers        app/controllers), +  %w(Helpers            app/helpers), +  %w(Models             app/models), +  %w(Mailers            app/mailers), +  %w(Javascripts        app/assets/javascripts), +  %w(Libraries          lib/), +  %w(APIs               app/apis), +  %w(Controller\ tests  test/controllers), +  %w(Helper\ tests      test/helpers), +  %w(Model\ tests       test/models), +  %w(Mailer\ tests      test/mailers), +  %w(Integration\ tests test/integration), +  %w(Functional\ tests\ (old)  test/functional), +  %w(Unit\ tests \ (old)       test/unit) +].collect do |name, dir|  +  [ name, "#{File.dirname(Rake.application.rakefile_location)}/#{dir}" ] +end.select { |name, dir| File.directory?(dir) } +  desc "Report code statistics (KLOCs, etc) from the application or engine"  task :stats do    require 'rails/code_statistics' - -  STATS_DIRECTORIES = [ -    %w(Controllers        app/controllers), -    %w(Helpers            app/helpers), -    %w(Models             app/models), -    %w(Mailers            app/mailers), -    %w(Javascripts        app/assets/javascripts), -    %w(Libraries          lib/), -    %w(APIs               app/apis), -    %w(Controller\ tests  test/controllers), -    %w(Helper\ tests      test/helpers), -    %w(Model\ tests       test/models), -    %w(Mailer\ tests      test/mailers), -    %w(Integration\ tests test/integration), -    %w(Functional\ tests\ (old)  test/functional), -    %w(Unit\ tests \ (old)       test/unit) -  ].collect { |name, dir| [ name, "#{defined?(ENGINE_PATH)? ENGINE_PATH : Rails.root}/#{dir}" ] }.select { |name, dir| File.directory?(dir) } -    CodeStatistics.new(*STATS_DIRECTORIES).to_s  end
\ No newline at end of file diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb index 878b9b7930..75180ff978 100644 --- a/railties/lib/rails/test_unit/railtie.rb +++ b/railties/lib/rails/test_unit/railtie.rb @@ -1,4 +1,4 @@ -if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^test(?::|$)/).any? +if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any?    ENV['RAILS_ENV'] ||= 'test'  end diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 0db40c1d32..6d6de0fb52 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -41,13 +41,13 @@ class ActionsTest < Rails::Generators::TestCase    def test_add_source_adds_source_to_gemfile      run_generator      action :add_source, 'http://gems.github.com' -    assert_file 'Gemfile', /source "http:\/\/gems\.github\.com"/ +    assert_file 'Gemfile', /source 'http:\/\/gems\.github\.com'/    end    def test_gem_should_put_gem_dependency_in_gemfile      run_generator      action :gem, 'will-paginate' -    assert_file 'Gemfile', /gem "will\-paginate"/ +    assert_file 'Gemfile', /gem 'will\-paginate'/    end    def test_gem_with_version_should_include_version_in_gemfile @@ -55,7 +55,7 @@ class ActionsTest < Rails::Generators::TestCase      action :gem, 'rspec', '>=2.0.0.a5' -    assert_file 'Gemfile', /gem "rspec", ">=2.0.0.a5"/ +    assert_file 'Gemfile', /gem 'rspec', '>=2.0.0.a5'/    end    def test_gem_should_insert_on_separate_lines @@ -66,8 +66,8 @@ class ActionsTest < Rails::Generators::TestCase      action :gem, 'rspec'      action :gem, 'rspec-rails' -    assert_file 'Gemfile', /^gem "rspec"$/ -    assert_file 'Gemfile', /^gem "rspec-rails"$/ +    assert_file 'Gemfile', /^gem 'rspec'$/ +    assert_file 'Gemfile', /^gem 'rspec-rails'$/    end    def test_gem_should_include_options @@ -75,7 +75,15 @@ class ActionsTest < Rails::Generators::TestCase      action :gem, 'rspec', github: 'dchelimsky/rspec', tag: '1.2.9.rc1' -    assert_file 'Gemfile', /gem "rspec", github: "dchelimsky\/rspec", tag: "1\.2\.9\.rc1"/ +    assert_file 'Gemfile', /gem 'rspec', github: 'dchelimsky\/rspec', tag: '1\.2\.9\.rc1'/ +  end + +  def test_gem_falls_back_to_inspect_if_string_contains_single_quote +    run_generator + +    action :gem, 'rspec', ">=2.0'0" + +    assert_file 'Gemfile', /^gem 'rspec', ">=2\.0'0"$/    end    def test_gem_group_should_wrap_gems_in_a_group @@ -89,7 +97,7 @@ class ActionsTest < Rails::Generators::TestCase        gem 'fakeweb'      end -    assert_file 'Gemfile', /\ngroup :development, :test do\n  gem "rspec-rails"\nend\n\ngroup :test do\n  gem "fakeweb"\nend/ +    assert_file 'Gemfile', /\ngroup :development, :test do\n  gem 'rspec-rails'\nend\n\ngroup :test do\n  gem 'fakeweb'\nend/    end    def test_environment_should_include_data_in_environment_initializer_block diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 1cbbf62459..74cff08676 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -21,6 +21,7 @@ DEFAULT_APP_FILES = %w(    bin/bundle    bin/rails    bin/rake +  bin/setup    config/environments    config/initializers    config/locales diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index 8e198d5fe1..b998fef42e 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -78,9 +78,12 @@ module SharedGeneratorTests    end    def test_template_raises_an_error_with_invalid_path -    content = capture(:stderr){ run_generator([destination_root, "-m", "non/existent/path"]) } -    assert_match(/The template \[.*\] could not be loaded/, content) -    assert_match(/non\/existent\/path/, content) +    quietly do +      content = capture(:stderr){ run_generator([destination_root, "-m", "non/existent/path"]) } + +      assert_match(/The template \[.*\] could not be loaded/, content) +      assert_match(/non\/existent\/path/, content) +    end    end    def test_template_is_executed_when_supplied @@ -89,7 +92,7 @@ module SharedGeneratorTests      template.instance_eval "def read; self; end" # Make the string respond to read      generator([destination_root], template: path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) -    assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) +    quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }    end    def test_template_is_executed_when_supplied_an_https_path @@ -98,7 +101,7 @@ module SharedGeneratorTests      template.instance_eval "def read; self; end" # Make the string respond to read      generator([destination_root], template: path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) -    assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) +    quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }    end    def test_dev_option diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index eac28badfe..8d6dbf80c2 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -21,8 +21,10 @@ class GeneratorsTest < Rails::Generators::TestCase    end    def test_invoke_when_generator_is_not_found -    output = capture(:stdout){ Rails::Generators.invoke :unknown } -    assert_equal "Could not find generator unknown.\n", output +    name = :unknown +    output = capture(:stdout){ Rails::Generators.invoke name } +    assert_match "Could not find generator '#{name}'", output +    assert_match "scaffold", output    end    def test_help_when_a_generator_with_required_arguments_is_invoked_without_arguments diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 6240dc04ec..ec64ce5941 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -112,6 +112,38 @@ module RailtiesTest        end      end +    test 'respects the order of railties when installing migrations' do +      @blog = engine "blog" do |plugin| +        plugin.write "lib/blog.rb", <<-RUBY +          module Blog +            class Engine < ::Rails::Engine +            end +          end +        RUBY +      end + +      @plugin.write "db/migrate/1_create_users.rb", <<-RUBY +        class CreateUsers < ActiveRecord::Migration +        end +      RUBY + +      @blog.write "db/migrate/2_create_blogs.rb", <<-RUBY +        class CreateBlogs < ActiveRecord::Migration +        end +      RUBY + +      add_to_config("config.railties_order = [Bukkits::Engine, Blog::Engine, :all, :main_app]") + +      boot_rails + +      Dir.chdir(app_path) do +        output  = `bundle exec rake railties:install:migrations`.split("\n") + +        assert_match(/Copied migration \d+_create_users.bukkits.rb from bukkits/, output.first) +        assert_match(/Copied migration \d+_create_blogs.blog_engine.rb from blog_engine/, output.last) +      end +    end +      test "mountable engine should copy migrations within engine_path" do        @plugin.write "lib/bukkits.rb", <<-RUBY          module Bukkits  | 
