diff options
307 files changed, 5594 insertions, 4002 deletions
@@ -12,7 +12,7 @@ gem 'jquery-rails', '~> 3.1.0'  gem 'turbolinks'  gem 'coffee-rails', '~> 4.0.0'  gem 'arel', github: 'rails/arel', branch: 'master' -gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: '2-1-stable' +gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: 'master'  gem 'i18n', github: 'svenfuchs/i18n', branch: 'master'  # require: false so bcrypt is loaded only when has_secure_password is used. 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 be1f53faf5..86e98c011c 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,4 +1,14 @@ -*   Fix 'Stack level too deep' when rendering `head :ok` in an action method +*   Fix URL generation with `:trailing_slash` such that it does not add +    a trailing slash after `.:format` + +    *Dan Langevin* + +*   Build full URI as string when processing path in integration tests for +    performance reasons. + +    *Guo Xiang Tan* + +*   Fix `'Stack level too deep'` when rendering `head :ok` in an action method      called 'status' in a controller.      Fixes #13905. @@ -66,7 +76,7 @@      4. Use `escape_segment` rather than `escape_path` in URL generation      For point 4 there are two exceptions. Firstly, when a route uses wildcard segments -    (e.g. *foo) then we use `escape_path` as the value may contain '/' characters. This +    (e.g. `*foo`) then we use `escape_path` as the value may contain '/' characters. This      means that wildcard routes can't be optimized. Secondly, if a `:controller` segment      is used in the path then this uses `escape_path` as the controller may be namespaced. @@ -96,7 +106,7 @@      *Andrew White*, *James Coglan* -*   Append link to bad code to backtrace when exception is SyntaxError. +*   Append link to bad code to backtrace when exception is `SyntaxError`.      *Boris Kuznetsov* @@ -126,4 +136,5 @@      *Tony Wooster* +  Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionpack/CHANGELOG.md) for previous changes. 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/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 696fbf6e09..3bfc8d6460 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -30,10 +30,8 @@ module ActionController        end      end -    def build(action, app=nil, &block) -      app  ||= block +    def build(action, app = Proc.new)        action = action.to_s -      raise "MiddlewareStack#build requires an app" unless app        middlewares.reverse.inject(app) do |a, middleware|          middleware.valid?(action) ? middleware.build(a) : a @@ -228,7 +226,7 @@ module ActionController      # Returns a Rack endpoint for the given action name.      def self.action(name, klass = ActionDispatch::Request) -      middleware_stack.build(name.to_s) do |env| +      middleware_stack.build(name) do |env|          new.dispatch(name, klass.new(env))        end      end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 2eb7853aa6..3111992f82 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -90,17 +90,29 @@ module ActionController        end        def authenticate(request, &login_procedure) -        unless request.authorization.blank? +        if has_basic_credentials?(request)            login_procedure.call(*user_name_and_password(request))          end        end +      def has_basic_credentials?(request) +        request.authorization.present? && (auth_scheme(request) == 'Basic') +      end +        def user_name_and_password(request)          decode_credentials(request).split(':', 2)        end        def decode_credentials(request) -        ::Base64.decode64(request.authorization.split(' ', 2).last || '') +        ::Base64.decode64(auth_param(request) || '') +      end + +      def auth_scheme(request) +        request.authorization.split(' ', 2).first +      end + +      def auth_param(request) +        request.authorization.split(' ', 2).second        end        def encode_credentials(user_name, password) diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 2812038938..136e086d0d 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -14,7 +14,7 @@ module ActionController      include ActionController::RackDelegation      include ActionController::UrlFor -    # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: +    # Redirects the browser to the target specified in +options+. This parameter can be any one of:      #      # * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.      # * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. @@ -24,6 +24,8 @@ module ActionController      # * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.      #   Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>      # +    # === Examples: +    #      #   redirect_to action: "show", id: 5      #   redirect_to post      #   redirect_to "http://www.rubyonrails.org" @@ -32,7 +34,7 @@ module ActionController      #   redirect_to :back      #   redirect_to proc { edit_post_url(@post) }      # -    # The redirection happens as a "302 Found" header unless otherwise specified. +    # The redirection happens as a "302 Found" header unless otherwise specified using the <tt>:status</tt> option:      #      #   redirect_to post_url(@post), status: :found      #   redirect_to action: 'atom', status: :moved_permanently @@ -60,8 +62,10 @@ module ActionController      #   redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }      #   redirect_to({ action: 'atom' }, alert: "Something serious happened")      # -    # When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback -    # behavior for this case by rescuing ActionController::RedirectBackError. +    # When using <tt>redirect_to :back</tt>, if there is no referrer, +    # <tt>ActionController::RedirectBackError</tt> will be raised. You +    # may specify some fallback behavior for this case by rescuing +    # <tt>ActionController::RedirectBackError</tt>.      def redirect_to(options = {}, response_status = {}) #:doc:        raise ActionControllerError.new("Cannot redirect to nil!") unless options        raise AbstractController::DoubleRenderError if response_body diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 0443b73953..46405cef55 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -6,6 +6,11 @@ module ActionController      Renderers.add(key, &block)    end +  # See <tt>Renderers.remove</tt> +  def self.remove_renderer(key) +    Renderers.remove(key) +  end +    class MissingRenderer < LoadError      def initialize(format)        super "No renderer defined for format: #{format}" @@ -73,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 @@ -83,6 +88,17 @@ module ActionController        RENDERERS << key.to_sym      end +    # This method is the opposite of add method. +    # +    # Usage: +    # +    #   ActionController::Renderers.remove(:csv) +    def self.remove(key) +      RENDERERS.delete(key.to_sym) +      method = "_render_option_#{key}" +      remove_method(method) if method_defined?(method) +    end +      module All        extend ActiveSupport::Concern        include Renderers diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 37d4a96ee1..07265be3fe 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -23,12 +23,12 @@ module ActionController      include AbstractController::UrlFor      def url_options -      @_url_options ||= super.reverse_merge( +      @_url_options ||= {          :host => request.host,          :port => request.optional_port,          :protocol => request.protocol, -        :_recall => request.symbolized_path_parameters -      ).freeze +        :_recall => request.path_parameters +      }.merge(super).freeze        if (same_origin = _routes.equal?(env["action_dispatch.routes".freeze])) ||           (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) || diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index c6a8f581de..e6695ffc90 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -199,7 +199,7 @@ module ActionController            value = value.dup          end -        if extra_keys.include?(key.to_sym) +        if extra_keys.include?(key)            non_path_parameters[key] = value          else            if value.is_a?(Array) @@ -208,7 +208,7 @@ module ActionController              value = value.to_param            end -          path_parameters[key.to_s] = value +          path_parameters[key] = value          end        end @@ -583,6 +583,7 @@ module ActionController          end          parameters, session, flash = args +        parameters ||= {}          # Ensure that numbers and symbols passed as params are converted to          # proper params, as is the case when engaging rack. @@ -601,7 +602,6 @@ module ActionController          @request.env['REQUEST_METHOD'] = http_method -        parameters ||= {}          controller_class_name = @controller.class.anonymous? ?            "anonymous" :            @controller.class.controller_path @@ -695,7 +695,7 @@ module ActionController              :only_path => true,              :action => action,              :relative_url_root => nil, -            :_recall => @request.symbolized_path_parameters) +            :_recall => @request.path_parameters)            url, query_string = @routes.url_for(options).split("?", 2) @@ -706,7 +706,7 @@ module ActionController        end        def html_format?(parameters) -        return true unless parameters.is_a?(Hash) +        return true unless parameters.key?(:format)          Mime.fetch(parameters[:format]) { Mime['html'] }.html?        end      end diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index dcb299ed03..5b22cd1fcd 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -24,14 +24,13 @@ module ActionDispatch        alias :params :parameters        def path_parameters=(parameters) #:nodoc: -        @symbolized_path_params = nil -        @env.delete("action_dispatch.request.parameters") -        @env["action_dispatch.request.path_parameters"] = parameters +        @env.delete('action_dispatch.request.parameters') +        @env[Routing::RouteSet::PARAMETERS_KEY] = parameters        end        # The same as <tt>path_parameters</tt> with explicitly symbolized keys.        def symbolized_path_parameters -        @symbolized_path_params ||= path_parameters.symbolize_keys +        path_parameters        end        # Returns a hash with the \parameters used to form the \path of the request. @@ -41,7 +40,7 @@ module ActionDispatch        #        # See <tt>symbolized_path_parameters</tt> for symbolized keys.        def path_parameters -        @env["action_dispatch.request.path_parameters"] ||= {} +        @env[Routing::RouteSet::PARAMETERS_KEY] ||= {}        end        def reset_parameters #:nodoc: diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index c9860af909..4cba4f5f37 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -30,19 +30,19 @@ module ActionDispatch          end          def url_for(options) +          unless options[:host] || options[:only_path] +            raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true' +          end +            path  = options[:script_name].to_s.chomp("/")            path << options[:path].to_s -          result = build_host_url(options) +          add_trailing_slash(path) if options[:trailing_slash] -          if options[:trailing_slash] -            if path.include?('?') -              result << path.sub(/\?/, '/\&') -            else -              result << path.sub(/[^\/]\z|\A\z/, '\&/') -            end -          else -            result << path +          result = path + +          unless options[:only_path] +            result.prepend build_host_url(options)            end            if options.key? :params @@ -60,29 +60,38 @@ module ActionDispatch          private +        def add_trailing_slash(path) +          # includes querysting +          if path.include?('?') +            path.sub!(/\?/, '/\&') +          # does not have a .format +          elsif !path.include?(".") +            path.sub!(/[^\/]\z|\A\z/, '\&/') +          end + +          path +        end +          def build_host_url(options) -          unless options[:host] || options[:only_path] -            raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true' +          if match = options[:host].match(HOST_REGEXP) +            options[:protocol] ||= match[1] unless options[:protocol] == false +            options[:host]       = match[2] +            options[:port]       = match[3] unless options.key?(:port)            end -          result = "" +          options[:protocol] = normalize_protocol(options) +          options[:host]     = normalize_host(options) +          options[:port]     = normalize_port(options) -          unless options[:only_path] -            if match = options[:host].match(HOST_REGEXP) -              options[:protocol] ||= match[1] unless options[:protocol] == false -              options[:host]       = match[2] -              options[:port]       = match[3] unless options.key?(:port) -            end - -            options[:protocol] = normalize_protocol(options) -            options[:host]     = normalize_host(options) -            options[:port]     = normalize_port(options) - -            result << options[:protocol] -            result << rewrite_authentication(options) -            result << options[:host] -            result << ":#{options[:port]}" if options[:port] +          result = options[:protocol] + +          if options[:user] && options[:password] +            result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"            end + +          result << options[:host] +          result << ":#{options[:port]}" if options[:port] +            result          end @@ -94,14 +103,6 @@ module ActionDispatch            (options[:subdomain] == true || !options.key?(:subdomain)) && options[:domain].nil?          end -        def rewrite_authentication(options) -          if options[:user] && options[:password] -            "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@" -          else -            "" -          end -        end -          def normalize_protocol(options)            case options[:protocol]            when nil diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 57f0963731..6d58323789 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -12,7 +12,7 @@ module ActionDispatch          @cache  = nil        end -      def generate(type, name, options, recall = {}, parameterize = nil) +      def generate(name, options, recall = {}, parameterize = nil)          constraints = recall.merge(options)          missing_keys = [] @@ -30,6 +30,12 @@ module ActionDispatch              parameterized_parts.key?(key) || route.defaults.key?(key)            end +          defaults       = route.defaults +          required_parts = route.required_parts +          parameterized_parts.delete_if do |key, value| +            value.to_s == defaults[key].to_s && !required_parts.include?(key) +          end +            return [route.format(parameterized_parts), params]          end @@ -74,12 +80,12 @@ module ActionDispatch            if named_routes.key?(name)              yield named_routes[name]            else -            routes = non_recursive(cache, options.to_a) +            routes = non_recursive(cache, options)              hash = routes.group_by { |_, r| r.score(options) }              hash.keys.sort.reverse_each do |score| -              next if score < 0 +              break if score < 0                hash[score].sort_by { |i, _| i }.each do |_, route|                  yield route @@ -90,14 +96,14 @@ module ActionDispatch          def non_recursive(cache, options)            routes = [] -          stack  = [cache] +          queue  = [cache] -          while stack.any? -            c = stack.shift +          while queue.any? +            c = queue.shift              routes.concat(c[:___routes]) if c.key?(:___routes)              options.each do |pair| -              stack << c[pair] if c.key?(pair) +              queue << c[pair] if c.key?(pair)              end            end @@ -126,11 +132,6 @@ module ActionDispatch            }          end -        # Returns +true+ if no missing keys are present, otherwise +false+. -        def verify_required_parts!(route, parts) -          missing_keys(route, parts).empty? -        end -          def build_cache            root = { ___routes: [] }            routes.each_with_index do |route, i| diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb index 430812fafe..d129ba7e16 100644 --- a/actionpack/lib/action_dispatch/journey/parser.rb +++ b/actionpack/lib/action_dispatch/journey/parser.rb @@ -1,7 +1,7 @@  #  # DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.4.9 -# from Racc grammar file "". +# This file is automatically generated by Racc 1.4.11 +# from Racc grammer file "".  #  require 'racc/parser.rb' @@ -9,42 +9,38 @@ require 'racc/parser.rb'  require 'action_dispatch/journey/parser_extras'  module ActionDispatch -  module Journey # :nodoc: -    class Parser < Racc::Parser # :nodoc: +  module Journey +    class Parser < Racc::Parser  ##### State transition tables begin ###  racc_action_table = [ -    17,    21,    13,    15,    14,     7,   nil,    16,     8,    19, -    13,    15,    14,     7,    23,    16,     8,    19,    13,    15, -    14,     7,   nil,    16,     8,    13,    15,    14,     7,   nil, -    16,     8,    13,    15,    14,     7,   nil,    16,     8 ] +    13,    15,    14,     7,    21,    16,     8,    19,    13,    15, +    14,     7,    17,    16,     8,    13,    15,    14,     7,    24, +    16,     8,    13,    15,    14,     7,    19,    16,     8 ]  racc_action_check = [ -     1,    17,     1,     1,     1,     1,   nil,     1,     1,     1, -    20,    20,    20,    20,    20,    20,    20,    20,     7,     7, -     7,     7,   nil,     7,     7,    19,    19,    19,    19,   nil, -    19,    19,     0,     0,     0,     0,   nil,     0,     0 ] +     2,     2,     2,     2,    17,     2,     2,     2,     0,     0, +     0,     0,     1,     0,     0,    19,    19,    19,    19,    20, +    19,    19,     7,     7,     7,     7,    22,     7,     7 ]  racc_action_pointer = [ -    30,     0,   nil,   nil,   nil,   nil,   nil,    16,   nil,   nil, -   nil,   nil,   nil,   nil,   nil,   nil,   nil,     1,   nil,    23, -     8,   nil,   nil,   nil ] +     6,    12,    -2,   nil,   nil,   nil,   nil,    20,   nil,   nil, +   nil,   nil,   nil,   nil,   nil,   nil,   nil,     4,   nil,    13, +    13,   nil,    17,   nil,   nil ]  racc_action_default = [ -   -18,   -18,    -2,    -3,    -4,    -5,    -6,   -18,    -9,   -10, -   -11,   -12,   -13,   -14,   -15,   -16,   -17,   -18,    -1,   -18, -   -18,    24,    -8,    -7 ] +   -19,   -19,    -2,    -3,    -4,    -5,    -6,   -19,   -10,   -11, +   -12,   -13,   -14,   -15,   -16,   -17,   -18,   -19,    -1,   -19, +   -19,    25,    -8,    -9,    -7 ]  racc_goto_table = [ -    18,     1,   nil,   nil,   nil,   nil,   nil,   nil,    20,   nil, -   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,    22,    18 ] +     1,    22,    18,    23,   nil,   nil,   nil,    20 ]  racc_goto_check = [ -     2,     1,   nil,   nil,   nil,   nil,   nil,   nil,     1,   nil, -   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,     2,     2 ] +     1,     2,     1,     3,   nil,   nil,   nil,     1 ]  racc_goto_pointer = [ -   nil,     1,    -1,   nil,   nil,   nil,   nil,   nil,   nil,   nil, +   nil,     0,   -18,   -16,   nil,   nil,   nil,   nil,   nil,   nil,     nil ]  racc_goto_default = [ @@ -61,19 +57,20 @@ racc_reduce_table = [    1, 12, :_reduce_none,    3, 15, :_reduce_7,    3, 13, :_reduce_8, -  1, 16, :_reduce_9, +  3, 13, :_reduce_9, +  1, 16, :_reduce_10,    1, 14, :_reduce_none,    1, 14, :_reduce_none,    1, 14, :_reduce_none,    1, 14, :_reduce_none, -  1, 19, :_reduce_14, -  1, 17, :_reduce_15, -  1, 18, :_reduce_16, -  1, 20, :_reduce_17 ] +  1, 19, :_reduce_15, +  1, 17, :_reduce_16, +  1, 18, :_reduce_17, +  1, 20, :_reduce_18 ] -racc_reduce_n = 18 +racc_reduce_n = 19 -racc_shift_n = 24 +racc_shift_n = 25  racc_token_table = {    false => 0, @@ -137,12 +134,12 @@ Racc_debug_parser = false  # reduce 0 omitted  def _reduce_1(val, _values, result) - result = Cat.new(val.first, val.last) + result = Cat.new(val.first, val.last)       result  end  def _reduce_2(val, _values, result) - result = val.first + result = val.first       result  end @@ -155,21 +152,24 @@ end  # reduce 6 omitted  def _reduce_7(val, _values, result) - result = Group.new(val[1]) + result = Group.new(val[1])       result  end  def _reduce_8(val, _values, result) - result = Or.new([val.first, val.last]) + result = Or.new([val.first, val.last])       result  end  def _reduce_9(val, _values, result) - result = Star.new(Symbol.new(val.last)) + result = Or.new([val.first, val.last])       result  end -# reduce 10 omitted +def _reduce_10(val, _values, result) + result = Star.new(Symbol.new(val.last))  +    result +end  # reduce 11 omitted @@ -177,23 +177,25 @@ end  # reduce 13 omitted -def _reduce_14(val, _values, result) - result = Slash.new('/') -    result -end +# reduce 14 omitted  def _reduce_15(val, _values, result) - result = Symbol.new(val.first) + result = Slash.new('/')       result  end  def _reduce_16(val, _values, result) - result = Literal.new(val.first) + result = Symbol.new(val.first)       result  end  def _reduce_17(val, _values, result) - result = Dot.new(val.first) + result = Literal.new(val.first)  +    result +end + +def _reduce_18(val, _values, result) + result = Dot.new(val.first)       result  end diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y index 040f8d5922..0ead222551 100644 --- a/actionpack/lib/action_dispatch/journey/parser.y +++ b/actionpack/lib/action_dispatch/journey/parser.y @@ -4,7 +4,7 @@ token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR  rule    expressions -    : expressions expression  { result = Cat.new(val.first, val.last) } +    : expression expressions  { result = Cat.new(val.first, val.last) }      | expression              { result = val.first }      | or      ; @@ -17,7 +17,8 @@ rule      : LPAREN expressions RPAREN { result = Group.new(val[1]) }      ;    or -    : expressions OR expression { result = Or.new([val.first, val.last]) } +    : expression OR expression { result = Or.new([val.first, val.last]) } +    | expression OR or { result = Or.new([val.first, val.last]) }      ;    star      : STAR       { result = Star.new(Symbol.new(val.last)) } diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb index fb155e516f..cb0a02c298 100644 --- a/actionpack/lib/action_dispatch/journey/path/pattern.rb +++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb @@ -30,6 +30,10 @@ module ActionDispatch            @offsets        = nil          end +        def build_formatter +          Visitors::FormatBuilder.new.accept(spec) +        end +          def ast            @spec.grep(Nodes::Symbol).each do |node|              re = @requirements[node.to_sym] diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 2b399d3ee3..1ba91d548e 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -19,7 +19,7 @@ module ActionDispatch          # 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. -        while app.is_a?(Routing::Mapper::Constraints) do +        if app.is_a?(Routing::Mapper::Constraints)            app = app.app          end          @dispatcher  = app.is_a?(Routing::RouteSet::Dispatcher) @@ -31,6 +31,7 @@ module ActionDispatch          @parts             = nil          @decorated_ast     = nil          @precedence        = 0 +        @path_formatter    = @path.build_formatter        end        def ast @@ -72,15 +73,7 @@ module ActionDispatch        alias :segment_keys :parts        def format(path_options) -        path_options.delete_if do |key, value| -          value.to_s == defaults[key].to_s && !required_parts.include?(key) -        end - -        Visitors::Formatter.new(path_options).accept(path.spec) -      end - -      def optimized_path -        Visitors::OptimizedPath.new.accept(path.spec) +        @path_formatter.evaluate path_options        end        def optional_parts diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 36561c71a1..c0317e3ad2 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -20,60 +20,31 @@ module ActionDispatch        # :nodoc:        VERSION = '2.0.0' -      class NullReq # :nodoc: -        attr_reader :env -        def initialize(env) -          @env = env -        end - -        def request_method -          env['REQUEST_METHOD'] -        end - -        def path_info -          env['PATH_INFO'] -        end - -        def ip -          env['REMOTE_ADDR'] -        end - -        def [](k) -          env[k] -        end -      end - -      attr_reader :request_class, :formatter        attr_accessor :routes -      def initialize(routes, options) -        @options       = options -        @params_key    = options[:parameters_key] -        @request_class = options[:request_class] || NullReq -        @routes        = routes +      def initialize(routes) +        @routes = routes        end -      def call(env) -        env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO']) - -        find_routes(env).each do |match, parameters, route| -          script_name, path_info, set_params = env.values_at('SCRIPT_NAME', -                                                             'PATH_INFO', -                                                             @params_key) +      def serve(req) +        find_routes(req).each do |match, parameters, route| +          set_params  = req.path_parameters +          path_info   = req.path_info +          script_name = req.script_name            unless route.path.anchored -            env['SCRIPT_NAME'] = (script_name.to_s + match.to_s).chomp('/') -            env['PATH_INFO']   = match.post_match +            req.script_name = (script_name.to_s + match.to_s).chomp('/') +            req.path_info = match.post_match            end -          env[@params_key] = (set_params || {}).merge parameters +          req.path_parameters = set_params.merge parameters -          status, headers, body = route.app.call(env) +          status, headers, body = route.app.call(req.env)            if 'pass' == headers['X-Cascade'] -            env['SCRIPT_NAME'] = script_name -            env['PATH_INFO']   = path_info -            env[@params_key]   = set_params +            req.script_name     = script_name +            req.path_info       = path_info +            req.path_parameters = set_params              next            end @@ -83,14 +54,14 @@ module ActionDispatch          return [404, {'X-Cascade' => 'pass'}, ['Not Found']]        end -      def recognize(req) -        find_routes(req.env).each do |match, parameters, route| +      def recognize(rails_req) +        find_routes(rails_req).each do |match, parameters, route|            unless route.path.anchored -            req.env['SCRIPT_NAME'] = match.to_s -            req.env['PATH_INFO']   = match.post_match.sub(/^([^\/])/, '/\1') +            rails_req.script_name = match.to_s +            rails_req.path_info   = match.post_match.sub(/^([^\/])/, '/\1')            end -          yield(route, nil, parameters) +          yield(route, parameters)          end        end @@ -124,9 +95,7 @@ module ActionDispatch            simulator.memos(path) { [] }          end -        def find_routes env -          req = request_class.new(env) - +        def find_routes req            routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|              r.path.match(req.path_info)            } @@ -136,17 +105,17 @@ module ActionDispatch            routes.map! { |r|              match_data  = r.path.match(req.path_info) -            match_names = match_data.names.map { |n| n.to_sym } -            match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) } -            info = Hash[match_names.zip(match_values).find_all { |_, y| y }] - -            [match_data, r.defaults.merge(info), r] +            path_parameters = r.defaults.dup +            match_data.names.zip(match_data.captures) { |name,val| +              path_parameters[name.to_sym] = Utils.unescape_uri(val) if val +            } +            [match_data, path_parameters, r]            }          end          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, @@ -157,8 +126,6 @@ module ActionDispatch                          route.precedence = r.precedence + precedence                        end            } -          routes.flatten! -          routes          end      end    end diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index d9f634623d..52b4c8b489 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -1,14 +1,57 @@  # encoding: utf-8 -require 'thread_safe' -  module ActionDispatch    module Journey # :nodoc: +    class Format +      ESCAPE_PATH    = ->(value) { Router::Utils.escape_path(value) } +      ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) } + +      class Parameter < Struct.new(:name, :escaper) +        def escape(value); escaper.call value; end +      end + +      def self.required_path(symbol) +        Parameter.new symbol, ESCAPE_PATH +      end + +      def self.required_segment(symbol) +        Parameter.new symbol, ESCAPE_SEGMENT +      end + +      def initialize(parts) +        @parts      = parts +        @children   = [] +        @parameters = [] + +        parts.each_with_index do |object,i| +          case object +          when Journey::Format +            @children << i +          when Parameter +            @parameters << i +          end +        end +      end + +      def evaluate(hash) +        parts = @parts.dup + +        @parameters.each do |index| +          param = parts[index] +          value = hash[param.name] +          return ''.freeze unless value +          parts[index] = param.escape value +        end + +        @children.each { |index| parts[index] = parts[index].evaluate(hash) } + +        parts.join +      end +    end +      module Visitors # :nodoc:        class Visitor # :nodoc: -        DISPATCH_CACHE = ThreadSafe::Cache.new { |h,k| -          h[k] = :"visit_#{k}" -        } +        DISPATCH_CACHE = {}          def accept(node)            visit(node) @@ -38,11 +81,41 @@ module ActionDispatch            def visit_STAR(n); unary(n); end            def terminal(node); end -          %w{ LITERAL SYMBOL SLASH DOT }.each do |t| -            class_eval %{ def visit_#{t}(n); terminal(n); end }, __FILE__, __LINE__ +          def visit_LITERAL(n); terminal(n); end +          def visit_SYMBOL(n);  terminal(n); end +          def visit_SLASH(n);   terminal(n); end +          def visit_DOT(n);     terminal(n); end + +          private_instance_methods(false).each do |pim| +            next unless pim =~ /^visit_(.*)$/ +            DISPATCH_CACHE[$1.to_sym] = pim            end        end +      class FormatBuilder < Visitor # :nodoc: +        def accept(node); Journey::Format.new(super); end +        def terminal(node); [node.left]; end + +        def binary(node) +          visit(node.left) + visit(node.right) +        end + +        def visit_GROUP(n); [Journey::Format.new(unary(n))]; end + +        def visit_STAR(n) +          [Journey::Format.required_path(n.left.to_sym)] +        end + +        def visit_SYMBOL(n) +          symbol = n.to_sym +          if symbol == :controller +            [Journey::Format.required_path(symbol)] +          else +            [Journey::Format.required_segment(symbol)] +          end +        end +      end +        # Loop through the requirements AST        class Each < Visitor # :nodoc:          attr_reader :block @@ -52,8 +125,8 @@ module ActionDispatch          end          def visit(node) -          super            block.call(node) +          super          end        end @@ -77,90 +150,6 @@ module ActionDispatch          end        end -      class OptimizedPath < Visitor # :nodoc: -        def accept(node) -          Array(visit(node)) -        end - -        private - -          def visit_CAT(node) -            [visit(node.left), visit(node.right)].flatten -          end - -          def visit_SYMBOL(node) -            node.left[1..-1].to_sym -          end - -          def visit_STAR(node) -            visit(node.left) -          end - -          def visit_GROUP(node) -            [] -          end - -          %w{ LITERAL SLASH DOT }.each do |t| -            class_eval %{ def visit_#{t}(n); n.left; end }, __FILE__, __LINE__ -          end -      end - -      # Used for formatting urls (url_for) -      class Formatter < Visitor # :nodoc: -        attr_reader :options - -        def initialize(options) -          @options  = options -        end - -        private -          def escape_path(value) -            Router::Utils.escape_path(value) -          end - -          def escape_segment(value) -            Router::Utils.escape_segment(value) -          end - -          def visit(node, optional = false) -            case node.type -            when :LITERAL, :SLASH, :DOT -              node.left -            when :STAR -              visit_STAR(node.left) -            when :GROUP -              visit(node.left, true) -            when :CAT -              visit_CAT(node, optional) -            when :SYMBOL -              visit_SYMBOL(node, node.to_sym) -            end -          end - -          def visit_CAT(node, optional) -            left = visit(node.left, optional) -            right = visit(node.right, optional) - -            if optional && !(right && left) -              "" -            else -              [left, right].join -            end -          end - -          def visit_STAR(node) -            if value = options[node.to_sym] -              escape_path(value) -            end -          end - -          def visit_SYMBOL(node, name) -            if value = options[name] -              name == :controller ? escape_path(value) : escape_segment(value) -            end -          end -      end -        class Dot < Visitor # :nodoc:          def initialize            @nodes = [] diff --git a/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb b/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb index 6aff10956a..9b28a65200 100644 --- a/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb +++ b/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb @@ -2,13 +2,13 @@  <html>    <head>      <title><%= title %></title> -    <link rel="stylesheet" href="https://raw.github.com/gist/1706081/af944401f75ea20515a02ddb3fb43d23ecb8c662/reset.css" type="text/css"> +    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.css" type="text/css">      <style>        <% stylesheets.each do |style| %>          <%= style %>        <% end %>      </style> -    <script src="https://raw.github.com/gist/1706081/df464722a05c3c2bec450b7b5c8240d9c31fa52d/d3.min.js" type="text/javascript"></script> +    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js" type="text/javascript"></script>    </head>    <body>      <div id="wrapper"> diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb index cbb2d475b1..6c8944e067 100644 --- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -32,9 +32,8 @@ module ActionDispatch      end      def render_html(status) -      found = false -      path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale -      path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path)) +      path = "#{public_path}/#{status}.#{I18n.locale}.html" +      path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))        if found || File.exist?(path)          render_format(status, 'text/html', File.read(path)) 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/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 71a0c5e826..2135b280da 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -16,7 +16,7 @@ module ActionDispatch          @rack_app ||= begin            class_name = app.class.name.to_s            if class_name == "ActionDispatch::Routing::Mapper::Constraints" -            rack_app(app.app) +            app.app            elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/              app            end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 58c7f5330e..b33c5e0dfd 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -16,17 +16,18 @@ module ActionDispatch                         :shallow, :blocks, :defaults, :options]        class Constraints #:nodoc: -        def self.new(app, constraints, request = Rack::Request) -          if constraints.any? -            super(app, constraints, request) -          else -            app -          end -        end -          attr_reader :app, :constraints          def initialize(app, constraints, request) +          # 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) +            constraints += app.constraints +            app = app.app +          end +            @app, @constraints, @request = app, constraints, request          end @@ -47,7 +48,7 @@ module ActionDispatch          private            def constraint_args(constraint, request) -            constraint.arity == 1 ? [request] : [request.symbolized_path_parameters, request] +            constraint.arity == 1 ? [request] : [request.path_parameters, request]            end        end @@ -57,12 +58,18 @@ module ActionDispatch          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, @options = set, scope, path, options +          @set, @scope, @path = set, scope, path            @requirements, @conditions, @defaults = {}, {}, {} -          normalize_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! @@ -94,14 +101,13 @@ module ActionDispatch              options[:format] != false && !path.include?(':format') && !path.end_with?('/')            end -          def normalize_options! -            @options.reverse_merge!(scope[:options]) if scope[:options] +          def normalize_options!(options)              path_without_format = path.sub(/\(\.:format\)$/, '')              # 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 path_without_format.match(WILDCARD_PATH) && options[:format] != false +              options[$1.to_sym] ||= /.+?/              end              if path_without_format.match(':controller') @@ -111,10 +117,10 @@ module ActionDispatch                # controllers with default routes like :controller/:action/:id(.:format), e.g:                # GET /admin/products/show/1                # => { controller: 'admin/products', action: 'show', id: '1' } -              @options[:controller] ||= /.+?/ +              options[:controller] ||= /.+?/              end -            @options.merge!(default_controller_and_action) +            options.merge!(default_controller_and_action)            end            def normalize_requirements! @@ -210,7 +216,11 @@ module ActionDispatch            end            def app -            Constraints.new(endpoint, blocks, @set.request_class) +            if blocks.any? +              Constraints.new(endpoint, blocks, @set.request_class) +            else +              endpoint +            end            end            def default_controller_and_action @@ -301,19 +311,7 @@ module ActionDispatch            end            def dispatcher -            Routing::RouteSet::Dispatcher.new(:defaults => defaults) -          end - -          def to -            options[:to] -          end - -          def default_controller -            options[:controller] || scope[:controller] -          end - -          def default_action -            options[:action] || scope[:action] +            Routing::RouteSet::Dispatcher.new(defaults)            end        end diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index b08e62543b..f8ed0cbe6a 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -19,13 +19,13 @@ module ActionDispatch          # If any of the path parameters has an invalid encoding then          # raise since it's likely to trigger errors further on. -        req.symbolized_path_parameters.each do |key, value| +        req.path_parameters.each do |key, value|            unless value.valid_encoding?              raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"            end          end -        uri = URI.parse(path(req.symbolized_path_parameters, req)) +        uri = URI.parse(path(req.path_parameters, req))          unless uri.host            if relative_path?(uri.path) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index e699419f23..924455bce2 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -21,9 +21,8 @@ module ActionDispatch        PARAMETERS_KEY = 'action_dispatch.request.path_parameters'        class Dispatcher #:nodoc: -        def initialize(options={}) -          @defaults = options[:defaults] -          @glob_param = options.delete(:glob) +        def initialize(defaults) +          @defaults = defaults            @controller_class_names = ThreadSafe::Cache.new          end @@ -53,7 +52,6 @@ module ActionDispatch          def prepare_params!(params)            normalize_controller!(params)            merge_default_action!(params) -          split_glob_param!(params) if @glob_param          end          # If this is a default_controller (i.e. a controller specified by the user) @@ -89,10 +87,6 @@ module ActionDispatch          def merge_default_action!(params)            params[:action] ||= 'index'          end - -        def split_glob_param!(params) -          params[@glob_param] = params[@glob_param].split('/').map { |v| URI.parser.unescape(v) } -        end        end        # A NamedRouteCollection instance is a collection of named routes, and also @@ -165,10 +159,8 @@ module ActionDispatch              def initialize(route, options)                super -              @klass          = Journey::Router::Utils                @required_parts = @route.required_parts                @arg_size       = @required_parts.size -              @optimized_path = @route.optimized_path              end              def call(t, args) @@ -184,18 +176,14 @@ module ActionDispatch              private              def optimized_helper(args) -              params = Hash[parameterize_args(args)] +              params = parameterize_args(args)                missing_keys = missing_keys(params)                unless missing_keys.empty?                  raise_generation_error(params, missing_keys)                end -              @optimized_path.map{ |segment| replace_segment(params, segment) }.join -            end - -            def replace_segment(params, segment) -              Symbol === segment ? @klass.escape_segment(params[segment]) : segment +              @route.format params              end              def optimize_routes_generation?(t) @@ -203,7 +191,9 @@ module ActionDispatch              end              def parameterize_args(args) -              @required_parts.zip(args.map(&:to_param)) +              params = {} +              @required_parts.zip(args.map(&:to_param)) { |k,v| params[k] = v } +              params              end              def missing_keys(args) @@ -226,21 +216,23 @@ module ActionDispatch            end            def call(t, args) -            options = t.url_options.merge @options -            hash = handle_positional_args(t, args, options, @segment_keys) +            controller_options = t.url_options +            options = controller_options.merge @options +            hash = handle_positional_args(controller_options, args, options, @segment_keys)              t._routes.url_for(hash)            end -          def handle_positional_args(t, args, result, keys) +          def handle_positional_args(controller_options, args, result, path_params)              inner_options = args.extract_options!              if args.size > 0 -              if args.size < keys.size - 1 # take format into account -                keys -= t.url_options.keys -                keys -= result.keys +              if args.size < path_params.size - 1 # take format into account +                path_params -= controller_options.keys +                path_params -= result.keys                end -              keys -= inner_options.keys -              result.merge!(Hash[keys.zip(args)]) +              path_params.each { |param| +                result[param] = inner_options[param] || args.shift +              }              end              result.merge!(inner_options) @@ -304,9 +296,7 @@ module ActionDispatch          @finalized                  = false          @set    = Journey::Routes.new -        @router = Journey::Router.new(@set, { -          :parameters_key => PARAMETERS_KEY, -          :request_class  => request_class}) +        @router = Journey::Router.new @set          @formatter = Journey::Formatter.new @set        end @@ -598,7 +588,7 @@ module ActionDispatch          # Generates a path from routes, returns [path, params].          # If no route is generated the formatter will raise ActionController::UrlGenerationError          def generate -          @set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE) +          @set.formatter.generate(named_route, options, recall, PARAMETERIZE)          end          def different_controller? @@ -671,19 +661,24 @@ module ActionDispatch          RESERVED_OPTIONS.each { |ro| path_options.delete ro }          path, params = generate(path_options, recall) -        params.merge!(options[:params] || {}) - -        ActionDispatch::Http::URL.url_for(options.merge!({ -          :path => path, -          :script_name => script_name, -          :params => params, -          :user => user, -          :password => password -        })) + +        if options.key? :params +          params.merge! options[:params] +        end + +        options[:path]        = path +        options[:script_name] = script_name +        options[:params]      = params +        options[:user]        = user +        options[:password]    = password + +        ActionDispatch::Http::URL.url_for(options)        end        def call(env) -        @router.call(env) +        req = request_class.new(env) +        req.path_info = Journey::Router::Utils.normalize_path(req.path_info) +        @router.serve(req)        end        def recognize_path(path, environment = {}) @@ -697,8 +692,8 @@ module ActionDispatch            raise ActionController::RoutingError, e.message          end -        req = @request_class.new(env) -        @router.recognize(req) do |route, _matches, params| +        req = request_class.new(env) +        @router.recognize(req) do |route, params|            params.merge!(extras)            params.each do |key, value|              if value.is_a?(String) @@ -706,10 +701,10 @@ module ActionDispatch                params[key] = URI.parser.unescape(value)              end            end -          old_params = env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] -          env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] = (old_params || {}).merge(params) +          old_params = req.path_parameters +          req.path_parameters = old_params.merge params            dispatcher = route.app -          while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do +          if dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env)              dispatcher = dispatcher.app            end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index cc6b763093..107e62d20f 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -3,7 +3,6 @@ require 'uri'  require 'active_support/core_ext/kernel/singleton_class'  require 'active_support/core_ext/object/try'  require 'rack/test' -require 'minitest'  module ActionDispatch    module Integration #:nodoc: @@ -300,13 +299,7 @@ module ActionDispatch            # NOTE: rack-test v0.5 doesn't build a default uri correctly            # Make sure requested path is always a full uri -          uri = URI.parse('/') -          uri.scheme ||= env['rack.url_scheme'] -          uri.host   ||= env['SERVER_NAME'] -          uri.port   ||= env['SERVER_PORT'].try(:to_i) -          uri += path - -          session.request(uri.to_s, env) +          session.request(build_full_uri(path, env), env)            @request_count += 1            @request  = ActionDispatch::Request.new(session.last_request.env) @@ -319,6 +312,10 @@ module ActionDispatch            return response.status          end + +        def build_full_uri(path, env) +          "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}" +        end      end      module Runner diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb index b1a5044399..fc59bf19c4 100644 --- a/actionpack/test/abstract/collector_test.rb +++ b/actionpack/test/abstract/collector_test.rb @@ -24,15 +24,21 @@ module AbstractController        test "does not respond to unknown mime types" do          collector = MyCollector.new -        assert !collector.respond_to?(:unknown) +        assert_not_respond_to collector, :unknown        end        test "register mime types on method missing" do          AbstractController::Collector.send(:remove_method, :js) -        collector = MyCollector.new -        assert !collector.respond_to?(:js) -        collector.js -        assert_respond_to collector, :js +        begin +          collector = MyCollector.new +          assert_not_respond_to collector, :js +          collector.js +          assert_respond_to collector, :js +        ensure +          unless AbstractController::Collector.method_defined? :js +            AbstractController::Collector.generate_method_for_mime :js +          end +        end        end        test "does not register unknown mime types" do diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb index 90548d4294..9052fc6962 100644 --- a/actionpack/test/controller/http_basic_authentication_test.rb +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -129,6 +129,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase      assert_response :unauthorized    end +  test "authentication request with wrong scheme" do +    header = 'Bearer ' + encode_credentials('David', 'Goliath').split(' ', 2)[1] +    @request.env['HTTP_AUTHORIZATION'] = header +    get :search +    assert_response :unauthorized +  end +    private    def encode_credentials(username, password) diff --git a/actionpack/test/controller/mime/respond_with_test.rb b/actionpack/test/controller/mime/respond_with_test.rb index 416b3b81a5..115f3b2f41 100644 --- a/actionpack/test/controller/mime/respond_with_test.rb +++ b/actionpack/test/controller/mime/respond_with_test.rb @@ -2,6 +2,10 @@ require 'abstract_unit'  require 'controller/fake_models'  class RespondWithController < ActionController::Base +  class CustomerWithJson < Customer +    def to_json; super; end +  end +    respond_to :html, :json, :touch    respond_to :xml, :except => :using_resource_with_block    respond_to :js,  :only => [ :using_resource_with_block, :using_resource, 'using_hash_resource' ] @@ -38,6 +42,10 @@ class RespondWithController < ActionController::Base      respond_with(resource, :location => "http://test.host/", :status => :created)    end +  def using_resource_with_json +    respond_with(CustomerWithJson.new("david", request.delete? ? nil : 13)) +  end +    def using_invalid_resource_with_template      respond_with(resource)    end @@ -380,9 +388,8 @@ class RespondWithControllerTest < ActionController::TestCase    end    def test_using_resource_for_put_with_json_yields_no_content_on_success -    Customer.any_instance.stubs(:to_json).returns('{"name": "David"}')      @request.accept = "application/json" -    put :using_resource +    put :using_resource_with_json      assert_equal "application/json", @response.content_type      assert_equal 204, @response.status      assert_equal "", @response.body @@ -431,10 +438,9 @@ class RespondWithControllerTest < ActionController::TestCase    end    def test_using_resource_for_delete_with_json_yields_no_content_on_success -    Customer.any_instance.stubs(:to_json).returns('{"name": "David"}')      Customer.any_instance.stubs(:destroyed?).returns(true)      @request.accept = "application/json" -    delete :using_resource +    delete :using_resource_with_json      assert_equal "application/json", @response.content_type      assert_equal 204, @response.status      assert_equal "", @response.body @@ -643,6 +649,8 @@ class RespondWithControllerTest < ActionController::TestCase      get :index, format: 'csv'      assert_equal Mime::CSV, @response.content_type      assert_equal "c,s,v", @response.body +  ensure +    ActionController::Renderers.remove :csv    end    def test_raises_missing_renderer_if_an_api_behavior_with_no_renderer @@ -652,6 +660,23 @@ class RespondWithControllerTest < ActionController::TestCase      end    end +  def test_removing_renderers +    ActionController::Renderers.add :csv do |obj, options| +      send_data obj.to_csv, type: Mime::CSV +    end +    @controller = CsvRespondWithController.new +    @request.accept = "text/csv" +    get :index, format: 'csv' +    assert_equal Mime::CSV, @response.content_type + +    ActionController::Renderers.remove :csv +    assert_raise ActionController::MissingRenderer do +      get :index, format: 'csv' +    end +  ensure +    ActionController::Renderers.remove :csv +  end +    def test_error_is_raised_if_no_respond_to_is_declared_and_respond_with_is_called      @controller = EmptyRespondWithController.new      @request.accept = "*/*" diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb index 7396c850ad..2ddc07ef72 100644 --- a/actionpack/test/controller/new_base/bare_metal_test.rb +++ b/actionpack/test/controller/new_base/bare_metal_test.rb @@ -81,8 +81,8 @@ module BareMetalTest        assert_nil headers['Content-Length']      end -    test "head :continue (101) does not return a content-type header" do -      headers = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).second +    test "head :switching_protocols (101) does not return a content-type header" do +      headers = HeadController.action(:switching_protocols).call(Rack::MockRequest.env_for("/")).second        assert_nil headers['Content-Type']        assert_nil headers['Content-Length']      end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index fbc10baf21..18a5d8b094 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -662,7 +662,7 @@ XML    def test_id_converted_to_string      get :test_params, :id => 20, :foo => Object.new -    assert_kind_of String, @request.path_parameters['id'] +    assert_kind_of String, @request.path_parameters[:id]    end    def test_array_path_parameter_handled_properly @@ -673,17 +673,17 @@ XML        end        get :test_params, :path => ['hello', 'world'] -      assert_equal ['hello', 'world'], @request.path_parameters['path'] -      assert_equal 'hello/world', @request.path_parameters['path'].to_param +      assert_equal ['hello', 'world'], @request.path_parameters[:path] +      assert_equal 'hello/world', @request.path_parameters[:path].to_param      end    end    def test_assert_realistic_path_parameters      get :test_params, :id => 20, :foo => Object.new -    # All elements of path_parameters should use string keys +    # All elements of path_parameters should use Symbol keys      @request.path_parameters.keys.each do |key| -      assert_kind_of String, key +      assert_kind_of Symbol, key      end    end diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 0c6df16325..f52f8be101 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -11,6 +11,26 @@ module AbstractController          W.default_url_options.clear        end +      def test_nested_optional +        klass = Class.new { +          include ActionDispatch::Routing::RouteSet.new.tap { |r| +            r.draw { +              get "/foo/(:bar/(:baz))/:zot", :as         => 'fun', +                                             :controller => :articles, +                                             :action     => :index +            } +          }.url_helpers +          self.default_url_options[:host] = 'example.com' +        } + +        path = klass.new.fun_path({:controller => :articles, +                                   :baz        => "baz", +                                   :zot        => "zot", +                                   :only_path  => true }) +        # :bar key isn't provided +        assert_equal '/foo/zot', path +      end +        def add_host!          W.default_url_options[:host] = 'www.basecamphq.com'        end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index cae6b312b6..a427113763 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1723,7 +1723,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest        get "whatever/:controller(/:action(/:id))"      end -    get 'whatever/foo/bar' +    get '/whatever/foo/bar'      assert_equal 'foo#bar', @response.body      assert_equal 'http://www.example.com/whatever/foo/bar/1', @@ -1735,10 +1735,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest        get "whatever/:controller(/:action(/:id))", :id => /\d+/      end -    get 'whatever/foo/bar/show' +    get '/whatever/foo/bar/show'      assert_equal 'foo/bar#show', @response.body -    get 'whatever/foo/bar/show/1' +    get '/whatever/foo/bar/show/1'      assert_equal 'foo/bar#show', @response.body      assert_equal 'http://www.example.com/whatever/foo/bar/show', @@ -2287,12 +2287,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest        get "(/user/:username)/photos" => "photos#index"      end -    get 'user/bob/photos' +    get '/user/bob/photos'      assert_equal 'photos#index', @response.body      assert_equal 'http://www.example.com/user/bob/photos',        url_for(:controller => "photos", :action => "index", :username => "bob") -    get 'photos' +    get '/photos'      assert_equal 'photos#index', @response.body      assert_equal 'http://www.example.com/photos',        url_for(:controller => "photos", :action => "index", :username => nil) @@ -3368,12 +3368,14 @@ end  class TestAltApp < ActionDispatch::IntegrationTest    class AltRequest +    attr_accessor :path_parameters, :path_info, :script_name +    attr_reader :env +      def initialize(env) +      @path_parameters = {}        @env = env -    end - -    def path_info -      "/" +      @path_info = "/" +      @script_name = ""      end      def request_method @@ -3492,7 +3494,7 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest        resources :storage_files, :controller => 'admin/storage_files'      end -    get 'storage_files' +    get '/storage_files'      assert_equal "admin/storage_files#index", @response.body    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/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb index 910ff8a80f..a4dfd0a63d 100644 --- a/actionpack/test/dispatch/url_generation_test.rb +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -15,6 +15,8 @@ module TestUrlGeneration      Routes.draw do        get "/foo", :to => "my_route_generating#index", :as => :foo +      resources :bars +        mount MyRouteGeneratingController.action(:index), at: '/bar'      end @@ -109,6 +111,22 @@ module TestUrlGeneration      test "omit subdomain when key is blank" do        assert_equal "http://example.com/foo", foo_url(subdomain: "")      end + +    test "generating URLs with trailing slashes" do +      assert_equal "/bars.json", bars_path( +        trailing_slash: true, +        format: 'json' +      ) +    end + +    test "generating URLS with querystring and trailing slashes" do +      assert_equal "/bars.json?a=b", bars_path( +        trailing_slash: true, +        a: 'b', +        format: 'json' +      ) +    end +    end  end diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb index e54b64e0f3..db2d3bc10d 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -5,23 +5,21 @@ 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; end +      class StubDispatcher < Routing::RouteSet::Dispatcher +        def initialize +          super({}) +        end +      end        attr_reader :routes        def setup          @app       = StubDispatcher.new          @routes    = Routes.new -        @router    = Router.new(@routes, {}) +        @router    = Router.new(@routes)          @formatter = Formatter.new(@routes)        end -      def test_request_class_reader -        klass = Object.new -        router = Router.new(routes, :request_class => klass) -        assert_equal klass, router.request_class -      end -        class FakeRequestFeeler < Struct.new(:env, :called)          def new env            self.env = env @@ -39,7 +37,7 @@ module ActionDispatch        end        def test_dashes -        router = Router.new(routes, {}) +        router = Router.new(routes)          exp = Router::Strexp.new '/foo-bar-baz', {}, ['/.?']          path  = Path::Pattern.new exp @@ -48,14 +46,14 @@ module ActionDispatch          env = rails_env 'PATH_INFO' => '/foo-bar-baz'          called = false -        router.recognize(env) do |r, _, params| +        router.recognize(env) do |r, params|            called = true          end          assert called        end        def test_unicode -        router = Router.new(routes, {}) +        router = Router.new(routes)          #match the escaped version of /ほげ          exp = Router::Strexp.new '/%E3%81%BB%E3%81%92', {}, ['/.?'] @@ -65,7 +63,7 @@ module ActionDispatch          env = rails_env 'PATH_INFO' => '/%E3%81%BB%E3%81%92'          called = false -        router.recognize(env) do |r, _, params| +        router.recognize(env) do |r, params|            called = true          end          assert called @@ -73,7 +71,7 @@ module ActionDispatch        def test_request_class_and_requirements_success          klass  = FakeRequestFeeler.new nil -        router = Router.new(routes, {:request_class => klass }) +        router = Router.new(routes)          requirements = { :hello => /world/ } @@ -82,8 +80,8 @@ module ActionDispatch          routes.add_route nil, path, requirements, {:id => nil}, {} -        env = rails_env 'PATH_INFO' => '/foo/10' -        router.recognize(env) do |r, _, params| +        env = rails_env({'PATH_INFO' => '/foo/10'}, klass) +        router.recognize(env) do |r, params|            assert_equal({:id => '10'}, params)          end @@ -93,7 +91,7 @@ module ActionDispatch        def test_request_class_and_requirements_fail          klass  = FakeRequestFeeler.new nil -        router = Router.new(routes, {:request_class => klass }) +        router = Router.new(routes)          requirements = { :hello => /mom/ } @@ -102,8 +100,8 @@ module ActionDispatch          router.routes.add_route nil, path, requirements, {:id => nil}, {} -        env = rails_env 'PATH_INFO' => '/foo/10' -        router.recognize(env) do |r, _, params| +        env = rails_env({'PATH_INFO' => '/foo/10'}, klass) +        router.recognize(env) do |r, params|            flunk 'route should not be found'          end @@ -111,24 +109,29 @@ module ActionDispatch          assert_equal env.env, klass.env        end -      class CustomPathRequest < Router::NullReq +      class CustomPathRequest < ActionDispatch::Request          def path_info            env['custom.path_info']          end + +        def path_info=(x) +          env['custom.path_info'] = x +        end        end        def test_request_class_overrides_path_info -        router = Router.new(routes, {:request_class => CustomPathRequest }) +        router = Router.new(routes)          exp = Router::Strexp.new '/bar', {}, ['/.?']          path = Path::Pattern.new exp          routes.add_route nil, path, {}, {}, {} -        env = rails_env 'PATH_INFO' => '/foo', 'custom.path_info' => '/bar' +        env = rails_env({'PATH_INFO' => '/foo', +                         'custom.path_info' => '/bar'}, CustomPathRequest)          recognized = false -        router.recognize(env) do |r, _, params| +        router.recognize(env) do |r, params|            recognized = true          end @@ -144,7 +147,7 @@ module ActionDispatch          env = rails_env 'PATH_INFO' => '/whois/example.com'          list = [] -        @router.recognize(env) do |r, _, params| +        @router.recognize(env) do |r, params|            list << r          end          assert_equal 2, list.length @@ -160,7 +163,7 @@ module ActionDispatch          ]          assert_raises(ActionController::UrlGenerationError) do -          @formatter.generate(:path_info, nil, { :id => '10' }, { }) +          @formatter.generate(nil, { :id => '10' }, { })          end        end @@ -169,11 +172,11 @@ module ActionDispatch            Router::Strexp.new("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)          ] -        path, _ = @formatter.generate(:path_info, nil, { :id => '10' }, { }) +        path, _ = @formatter.generate(nil, { :id => '10' }, { })          assert_equal '/foo/10', path          assert_raises(ActionController::UrlGenerationError) do -          @formatter.generate(:path_info, nil, { :id => 'aa' }, { }) +          @formatter.generate(nil, { :id => 'aa' }, { })          end        end @@ -182,13 +185,13 @@ module ActionDispatch            Router::Strexp.new("/foo(/:id)", {:id => /\d/}, ['/', '.', '?'], false)          ] -        path, _ = @formatter.generate(:path_info, nil, { :id => '10' }, { }) +        path, _ = @formatter.generate(nil, { :id => '10' }, { })          assert_equal '/foo/10', path -        path, _ = @formatter.generate(:path_info, nil, { }, { }) +        path, _ = @formatter.generate(nil, { }, { })          assert_equal '/foo', path -        path, _ = @formatter.generate(:path_info, nil, { :id => 'aa' }, { }) +        path, _ = @formatter.generate(nil, { :id => 'aa' }, { })          assert_equal '/foo/aa', path        end @@ -199,7 +202,7 @@ module ActionDispatch          @router.routes.add_route nil, path, {}, {}, route_name          error = assert_raises(ActionController::UrlGenerationError) do -          @formatter.generate(:path_info, route_name, { }, { }) +          @formatter.generate(route_name, { }, { })          end          assert_match(/missing required keys: \[:id\]/, error.message) @@ -207,7 +210,7 @@ module ActionDispatch        def test_X_Cascade          add_routes @router, [ "/messages(.:format)" ] -        resp = @router.call({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' }) +        resp = @router.serve(rails_env({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' }))          assert_equal ['Not Found'], resp.last          assert_equal 'pass', resp[1]['X-Cascade']          assert_equal 404, resp.first @@ -220,7 +223,7 @@ module ActionDispatch          @router.routes.add_route(app, path, {}, {}, {})          env  = rack_env('SCRIPT_NAME' => '', 'PATH_INFO' => '/weblog') -        resp = @router.call(env) +        resp = @router.serve rails_env env          assert_equal ['success!'], resp.last          assert_equal '', env['SCRIPT_NAME']        end @@ -230,12 +233,12 @@ module ActionDispatch          @router.routes.add_route nil, path, {}, {:id => nil}, {}          env = rails_env 'PATH_INFO' => '/foo/10' -        @router.recognize(env) do |r, _, params| +        @router.recognize(env) do |r, params|            assert_equal({:id => '10'}, params)          end          env = rails_env 'PATH_INFO' => '/foo' -        @router.recognize(env) do |r, _, params| +        @router.recognize(env) do |r, params|            assert_equal({:id => nil}, params)          end        end @@ -287,14 +290,14 @@ module ActionDispatch        def test_required_part_in_recall          add_routes @router, [ "/messages/:a/:b" ] -        path, _ = @formatter.generate(:path_info, nil, { :a => 'a' }, { :b => 'b' }) +        path, _ = @formatter.generate(nil, { :a => 'a' }, { :b => 'b' })          assert_equal "/messages/a/b", path        end        def test_splat_in_recall          add_routes @router, [ "/*path" ] -        path, _ = @formatter.generate(:path_info, nil, { }, { :path => 'b' }) +        path, _ = @formatter.generate(nil, { }, { :path => 'b' })          assert_equal "/b", path        end @@ -304,7 +307,7 @@ module ActionDispatch            "/messages/:id(.:format)"          ] -        path, _ = @formatter.generate(:path_info, nil, { :id => 10 }, { :action => 'index' }) +        path, _ = @formatter.generate(nil, { :id => 10 }, { :action => 'index' })          assert_equal "/messages/index/10", path        end @@ -315,7 +318,7 @@ module ActionDispatch          params = { :controller => "tasks", :format => nil }          extras = { :action => 'lol' } -        path, _ = @formatter.generate(:path_info, nil, params, extras) +        path, _ = @formatter.generate(nil, params, extras)          assert_equal '/tasks', path        end @@ -327,7 +330,7 @@ module ActionDispatch          @router.routes.add_route @app, path, {}, {}, {} -        path, _ = @formatter.generate(:path_info, nil, Hash[params], {}) +        path, _ = @formatter.generate(nil, Hash[params], {})          assert_equal '/', path        end @@ -340,7 +343,6 @@ module ActionDispatch                     [:action, "show"] ]          @formatter.generate( -          :path_info,            nil,            Hash[params],            {}, @@ -354,7 +356,7 @@ module ActionDispatch          @router.routes.add_route @app, path, {}, {}, {}          path, params = @formatter.generate( -          :path_info, nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {}) +          nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {})          assert_equal '/tasks/show', path          assert_equal({:id => 1}, params)        end @@ -363,8 +365,8 @@ module ActionDispatch          path  = Path::Pattern.new '/:controller(/:action)'          @router.routes.add_route @app, path, {}, {}, {} -        path, _ = @formatter.generate(:path_info, -          nil, { :controller        => "tasks", +        path, _ = @formatter.generate(nil, +          { :controller        => "tasks",                   :action            => "a/b c+d",          }, {})          assert_equal '/tasks/a%2Fb%20c+d', path @@ -374,7 +376,7 @@ module ActionDispatch          path  = Path::Pattern.new '/:controller(/:action)'          @router.routes.add_route @app, path, {}, {}, {} -        path, _ = @formatter.generate(:path_info, +        path, _ = @formatter.generate(            nil, { :controller        => "admin/tasks",                   :action            => "a/b c+d",          }, {}) @@ -385,7 +387,7 @@ module ActionDispatch          path  = Path::Pattern.new '/:controller(/:action)'          @router.routes.add_route @app, path, {}, {}, {} -        path, params = @formatter.generate(:path_info, +        path, params = @formatter.generate(            nil, { :id                => 1,                   :controller        => "tasks",                   :action            => "show", @@ -399,7 +401,7 @@ module ActionDispatch          path  = Path::Pattern.new '/:controller(/:action(/:id))'          @router.routes.add_route @app, path, {}, {}, {} -        path, params = @formatter.generate(:path_info, +        path, params = @formatter.generate(            nil,            {:controller =>"tasks", :id => 10},            {:action     =>"index"}) @@ -411,7 +413,7 @@ module ActionDispatch          path  = Path::Pattern.new '/:controller(/:action)'          @router.routes.add_route @app, path, {}, {}, {} -        path, params = @formatter.generate(:path_info, +        path, params = @formatter.generate(            "tasks",            {:controller=>"tasks"},            {:controller=>"tasks", :action=>"index"}) @@ -432,7 +434,7 @@ module ActionDispatch            env = rails_env 'PATH_INFO' => request_path            called   = false -          @router.recognize(env) do |r, _, params| +          @router.recognize(env) do |r, params|              assert_equal route, r              assert_equal(expected, params)              called = true @@ -454,7 +456,7 @@ module ActionDispatch            env = rails_env 'PATH_INFO' => request_path            called   = false -          @router.recognize(env) do |r, _, params| +          @router.recognize(env) do |r, params|              assert_equal route, r              assert_equal(expected, params)              called = true @@ -482,7 +484,7 @@ module ActionDispatch            :id         => '10'          } -        @router.recognize(env) do |r, _, params| +        @router.recognize(env) do |r, params|            assert_equal route, r            assert_equal(expected, params)            called = true @@ -498,7 +500,7 @@ module ActionDispatch          env    = rails_env 'PATH_INFO' => '/books/list.rss'          expected = { :controller => 'books', :action => 'list', :format => 'rss' }          called = false -        @router.recognize(env) do |r, _, params| +        @router.recognize(env) do |r, params|            assert_equal route, r            assert_equal(expected, params)            called = true @@ -519,7 +521,7 @@ module ActionDispatch                          "REQUEST_METHOD"    => "HEAD"          called = false -        @router.recognize(env) do |r, _, params| +        @router.recognize(env) do |r, params|            called = true          end @@ -543,7 +545,7 @@ module ActionDispatch                          "REQUEST_METHOD"    => "POST"          called = false -        @router.recognize(env) do |r, _, params| +        @router.recognize(env) do |r, params|            assert_equal post, r            called = true          end @@ -562,8 +564,8 @@ module ActionDispatch        RailsEnv = Struct.new(:env) -      def rails_env env -        RailsEnv.new rack_env env +      def rails_env env, klass = ActionDispatch::Request +        klass.new env        end        def rack_env env 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 ae684af87b..d86e7e490c 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      # @@ -113,13 +113,13 @@ 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 = {}) -        source = source.to_s          return "" unless source.present? +        source = source.to_s          return source if source =~ URI_REGEXP          tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '') @@ -153,7 +153,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 +231,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 +251,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 +276,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 +342,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/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 10dcb5c28c..1e818083cc 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -109,8 +109,8 @@ module ActionView        #   # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>        #   #    <option>Out</option></select>        # -      #   select_tag "access", "<option>Read</option><option>Write</option>".html_safe, multiple: true, class: 'form_input' -      #   # => <select class="form_input" id="access" multiple="multiple" name="access[]"><option>Read</option> +      #   select_tag "access", "<option>Read</option><option>Write</option>".html_safe, multiple: true, class: 'form_input', id: 'unique_id' +      #   # => <select class="form_input" id="unique_id" multiple="multiple" name="access[]"><option>Read</option>        #   #    <option>Write</option></select>        #        #   select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: true diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index 017302d40f..c92d090cce 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -62,8 +62,8 @@ module ActionView      #      # The view class must have the following methods:      # View.new[lookup_context, assigns, controller] -    #   Create a new ActionView instance for a controller -    # View#render[options] +    #   Create a new ActionView instance for a controller and we can also pass the arguments. +    # View#render(option)      #   Returns String with the rendered template      #      # Override this method in a module to change the default behavior. diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 189086132e..d29d020c17 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -196,7 +196,7 @@ module ActionView        }      end -    if File.const_defined? :FNM_EXTGLOB +    if RUBY_VERSION >= '2.2.0'        def find_template_paths(query)          Dir[query].reject { |filename|            File.directory?(filename) || diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index 826e89bf9d..4033eb5808 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -52,8 +52,6 @@ module ActiveModel            raise          end -        attr_reader :password -          include InstanceMethodsOnActivation          if options.fetch(:validations, true) @@ -68,6 +66,7 @@ module ActiveModel            validates_confirmation_of :password, if: ->{ password.present? }          end +        # This code is necessary as long as the protected_attributes gem is supported.          if respond_to?(:attributes_protected_by_default)            def self.attributes_protected_by_default #:nodoc:              super + ['password_digest'] @@ -91,6 +90,8 @@ module ActiveModel          BCrypt::Password.new(password_digest) == unencrypted_password && self        end +      attr_reader :password +        # Encrypts the password into the +password_digest+ attribute, only if the        # new password is not blank.        # diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 5647204859..736745c3cd 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,92 @@ +*   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* + +*   Add a properties API to allow custom types and type casting behavior +    to be specified. Will enable many edge cases to be deprecated, and +    allow for additional interesting features in the future. + +    *Sean Griffin* + +*   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. + +    *Godfrey Chan* + +*   When a `group` is set, `sum`, `size`, `average`, `minimum` and `maximum` +    on a NullRelation should return a Hash. + +    *Kuldeep Aggarwal* + +*   Fixed serialized fields returning serialized data after being updated with +    `update_column`. + +    *Simon Hørup Eskildsen* + +*   Fixed polymorphic eager loading when using a String as foreign key. + +    Fixes #14734. + +    *Lauro Caetano* + +*   Change belongs_to touch to be consistent with timestamp updates + +    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. + +    *Brock Trappitt* + +*   Fixed the inferred table name of a has_and_belongs_to_many auxiliar +    table inside a schema. + +    Fixes #14824 + +    *Eric Chahin* + +*   Remove unused `:timestamp` type. Transparently alias it to `:datetime` +    in all cases. Fixes inconsistencies when column types are sent outside of +    `ActiveRecord`, such as for XML Serialization. + +    *Sean Griffin* + +*   Fix bug that added `table_name_prefix` and `table_name_suffix` to +    extension names in PostgreSQL when migrating. + +    *Joao Carlos* + +*   The `:index` option in migrations, which previously was only available for +    `references`, now works with any column types. + +    *Marc Schütz* + +*   Add support for counter name to be passed as parameter on `CounterCache::ClassMethods#reset_counters`. + +    *jnormore* + +*   Restrict deletion of record when using `delete_all` with `uniq`, `group`, `having` +    or `offset`. + +    In these cases the generated query ignored them and that caused unintended +    records to be deleted. + +    Fixes #11985. + +    *Leandro Facchinetti* +  *   Floats with limit >= 25 that get turned into doubles in MySQL no longer have      their limit dropped from the schema. @@ -5,7 +94,7 @@      *Aaron Nelson* -*   Fix how to calculate associated class name when using namespaced `has_and_belongs_to_many` +*   Fix how to calculate associated class name when using namespaced has_and_belongs_to_many      association.      Fixes #14709. @@ -58,7 +147,7 @@      *Innokenty Mikhailov* -*   Allow the PostgreSQL adapter to handle bigserial pk types again. +*   Allow the PostgreSQL adapter to handle bigserial primary key types again.      Fixes #10410. @@ -73,10 +162,10 @@      *Yves Senn* -*   Fixed HABTM's CollectionAssociation size calculation. +*   Fixed has_and_belongs_to_many's CollectionAssociation size calculation. -    HABTM should fall back to using the normal CollectionAssociation's size -    calculation if the collection is not cached or loaded. +    has_and_belongs_to_many should fall back to using the normal CollectionAssociation's +    size calculation if the collection is not cached or loaded.      Fixes #14913, #14914. @@ -120,10 +209,10 @@      *Eric Chahin*, *Aaron Nelson*, *Kevin Casey* -*   Stringify all variable keys of mysql connection configuration. +*   Stringify all variables keys of MySQL connection configuration. -    When the `sql_mode` variable for mysql adapters is set in the configuration -    as a `String`, it was ignored and overwritten by the strict mode option. +    When `sql_mode` variable for MySQL adapters set in configuration as `String` +    was ignored and overwritten by strict mode option.      Fixes #14895. @@ -398,12 +487,6 @@      *Cody Cutrer*, *Steve Rice*, *Rafael Mendonça Franca* -*   Save `has_one` association even if the record doesn't changed. - -    Fixes #14407. - -    *Rafael Mendonça França* -  *   Use singular table name in generated migrations when      `ActiveRecord::Base.pluralize_table_names` is `false`. @@ -482,6 +565,12 @@      *Troy Kruthoff*, *Lachlan Sylvester* +*   Only save has_one associations if record has changes. +    Previously after save related callbacks, such as `#after_commit`, were triggered when the has_one +    object did not get saved to the db. + +    *Alan Kennedy* +  *   Allow strings to specify the `#order` value.      Example: diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 01ca4c82f2..7769966a22 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -38,33 +38,36 @@ namespace :test do    end  end +desc 'Build MySQL and PostgreSQL test databases'  namespace :db do -  desc 'Build MySQL and PostgreSQL test databases' -  task create: ['mysql:build_databases', 'postgresql:build_databases'] -  desc 'Drop MySQL and PostgreSQL test databases' -  task drop: ['mysql:drop_databases', 'postgresql:drop_databases'] +  task :create => ['db:mysql:build', 'db:postgresql:build'] +  task :drop => ['db:mysql:drop', 'db:postgresql:drop']  end  %w( mysql mysql2 postgresql sqlite3 sqlite3_mem db2 oracle jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| -  Rake::TestTask.new("test_#{adapter}") { |t| -    adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] -    t.libs << 'test' -    t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject { -      |x| x =~ /\/adapters\// -    } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort - -    t.warning = true -    t.verbose = true -  } - -  task "isolated_test_#{adapter}" do -    adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] -    puts [adapter, adapter_short].inspect -    (Dir["test/cases/**/*_test.rb"].reject { -      |x| x =~ /\/adapters\// -    } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file| -      sh(Gem.ruby, '-w' ,"-Itest", file) -    end or raise "Failures" +  namespace :test do +    Rake::TestTask.new(adapter => "#{adapter}:env") { |t| +      adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] +      t.libs << 'test' +      t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject { +        |x| x =~ /\/adapters\// +      } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort + +      t.warning = true +      t.verbose = true +    } + +    namespace :isolated do +      task adapter => "#{adapter}:env" do +        adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] +        puts [adapter, adapter_short].inspect +        (Dir["test/cases/**/*_test.rb"].reject { +          |x| x =~ /\/adapters\// +        } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file| +          sh(Gem.ruby, '-w' ,"-Itest", file) +        end or raise "Failures" +      end +    end    end    namespace adapter do @@ -76,8 +79,8 @@ end    end    # Make sure the adapter test evaluates the env setting task -  task "test_#{adapter}" => "#{adapter}:env" -  task "isolated_test_#{adapter}" => "#{adapter}:env" +  task "test_#{adapter}" => ["#{adapter}:env", "test:#{adapter}"] +  task "isolated_test_#{adapter}" => ["#{adapter}:env", "test:isolated:#{adapter}"]  end  rule '.sqlite3' do |t| @@ -89,63 +92,58 @@ task :test_sqlite3 => [    'test/fixtures/fixture_database_2.sqlite3'  ] -namespace :mysql do -  desc 'Build the MySQL test databases' -  task :build_databases do -    config = ARTest.config['connections']['mysql'] -    %x( mysql --user=#{config['arunit']['username']} -e "create DATABASE #{config['arunit']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") -    %x( mysql --user=#{config['arunit2']['username']} -e "create DATABASE #{config['arunit2']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") -  end - -  desc 'Drop the MySQL test databases' -  task :drop_databases do -    config = ARTest.config['connections']['mysql'] -    %x( mysqladmin --user=#{config['arunit']['username']} -f drop #{config['arunit']['database']} ) -    %x( mysqladmin --user=#{config['arunit2']['username']} -f drop #{config['arunit2']['database']} ) -  end +namespace :db do +  namespace :mysql do +    desc 'Build the MySQL test databases' +    task :build do +      config = ARTest.config['connections']['mysql'] +      %x( mysql --user=#{config['arunit']['username']} -e "create DATABASE #{config['arunit']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") +      %x( mysql --user=#{config['arunit2']['username']} -e "create DATABASE #{config['arunit2']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") +    end -  desc 'Rebuild the MySQL test databases' -  task :rebuild_databases => [:drop_databases, :build_databases] -end +    desc 'Drop the MySQL test databases' +    task :drop do +      config = ARTest.config['connections']['mysql'] +      %x( mysqladmin --user=#{config['arunit']['username']} -f drop #{config['arunit']['database']} ) +      %x( mysqladmin --user=#{config['arunit2']['username']} -f drop #{config['arunit2']['database']} ) +    end -task :build_mysql_databases => 'mysql:build_databases' -task :drop_mysql_databases => 'mysql:drop_databases' -task :rebuild_mysql_databases => 'mysql:rebuild_databases' +    desc 'Rebuild the MySQL test databases' +    task :rebuild => [:drop, :build] +  end +  namespace :postgresql do +    desc 'Build the PostgreSQL test databases' +    task :build do +      config = ARTest.config['connections']['postgresql'] +      %x( createdb -E UTF8 -T template0 #{config['arunit']['database']} ) +      %x( createdb -E UTF8 -T template0 #{config['arunit2']['database']} ) -namespace :postgresql do -  desc 'Build the PostgreSQL test databases' -  task :build_databases do -    config = ARTest.config['connections']['postgresql'] -    %x( createdb -E UTF8 -T template0 #{config['arunit']['database']} ) -    %x( createdb -E UTF8 -T template0 #{config['arunit2']['database']} ) +      # prepare hstore +      if %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2") < "9.1.0" +        puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html" +      end +    end -    # notify about preparing hstore -    if %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2") < "9.1.0" -      puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html" +    desc 'Drop the PostgreSQL test databases' +    task :drop do +      config = ARTest.config['connections']['postgresql'] +      %x( dropdb #{config['arunit']['database']} ) +      %x( dropdb #{config['arunit2']['database']} )      end -  end -  desc 'Drop the PostgreSQL test databases' -  task :drop_databases do -    config = ARTest.config['connections']['postgresql'] -    %x( dropdb #{config['arunit']['database']} ) -    %x( dropdb #{config['arunit2']['database']} ) +    desc 'Rebuild the PostgreSQL test databases' +    task :rebuild => [:drop, :build]    end - -  desc 'Rebuild the PostgreSQL test databases' -  task :rebuild_databases => [:drop_databases, :build_databases]  end -task :build_postgresql_databases => 'postgresql:build_databases' -task :drop_postgresql_databases => 'postgresql:drop_databases' -task :rebuild_postgresql_databases => 'postgresql:rebuild_databases' - +task :build_mysql_databases => 'db:mysql:build' +task :drop_mysql_databases => 'db:mysql:drop' +task :rebuild_mysql_databases => 'db:mysql:rebuild' -spec = eval(File.read('activerecord.gemspec')) -Gem::PackageTask.new(spec) do |p| -  p.gem_spec = spec -end +task :build_postgresql_databases => 'db:postgresql:build' +task :drop_postgresql_databases => 'db:postgresql:drop' +task :rebuild_postgresql_databases => 'db:postgresql:rebuild'  task :lines do    lines, codelines, total_lines, total_codelines = 0, 0, 0, 0 @@ -171,6 +169,11 @@ task :lines do    puts "Total: Lines #{total_lines}, LOC #{total_codelines}"  end +spec = eval(File.read('activerecord.gemspec')) + +Gem::PackageTask.new(spec) do |p| +  p.gem_spec = spec +end  # Publishing ------------------------------------------------------ diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 0d5313956b..45c275a017 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -230,8 +230,8 @@ module ActiveRecord        private          def reader_method(name, class_name, mapping, allow_nil, constructor)            define_method(name) do -            if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? }) -              attrs = mapping.collect {|pair| read_attribute(pair.first)} +            if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !read_attribute(key).nil? }) +              attrs = mapping.collect {|key, _| read_attribute(key)}                object = constructor.respond_to?(:call) ?                  constructor.call(*attrs) :                  class_name.constantize.send(constructor, *attrs) @@ -249,10 +249,10 @@ module ActiveRecord              end              if part.nil? && allow_nil -              mapping.each { |pair| self[pair.first] = nil } +              mapping.each { |key, _| self[key] = nil }                @aggregation_cache[name] = nil              else -              mapping.each { |pair| self[pair.first] = part.send(pair.last) } +              mapping.each { |key, value| self[key] = part.send(value) }                @aggregation_cache[name] = part.freeze              end            end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 727ee5f65f..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 @@ -202,12 +202,13 @@ module ActiveRecord      # For instance, +attributes+ and +connection+ would be bad choices for association names.      #      # == Auto-generated methods +    # See also Instance Public methods below for more details.      #      # === Singular associations (one-to-one)      #                                     |            |  belongs_to  |      #   generated methods                 | belongs_to | :polymorphic | has_one      #   ----------------------------------+------------+--------------+--------- -    #   other                             |     X      |      X       |    X +    #   other(force_reload=false)         |     X      |      X       |    X      #   other=(other)                     |     X      |      X       |    X      #   build_other(attributes={})        |     X      |              |    X      #   create_other(attributes={})       |     X      |              |    X @@ -217,7 +218,7 @@ module ActiveRecord      #                                     |       |          | has_many      #   generated methods                 | habtm | has_many | :through      #   ----------------------------------+-------+----------+---------- -    #   others                            |   X   |    X     |    X +    #   others(force_reload=false)        |   X   |    X     |    X      #   others=(other,other,...)          |   X   |    X     |    X      #   other_ids                         |   X   |    X     |    X      #   other_ids=(id,id,...)             |   X   |    X     |    X @@ -1576,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 @@ -1589,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 @@ -1609,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/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/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 47cc1f4b34..3998aca23e 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -103,7 +103,7 @@ module ActiveRecord::Associations::Builder          BelongsTo.touch_record(record, foreign_key, n, touch)        } -      model.after_save    callback +      model.after_save    callback, if: :changed?        model.after_touch   callback        model.after_destroy callback      end 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 44486ad758..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 @@ -11,7 +11,7 @@ module ActiveRecord::Associations::Builder          end          def join_table -          @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_") +          @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")          end          private @@ -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/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index caf4e612f9..c5f7bcae7d 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -244,6 +244,7 @@ module ActiveRecord        # are actually removed from the database, that depends precisely on        # +delete_records+. They are in any case removed from the collection.        def delete(*records) +        return if records.empty?          _options = records.extract_options!          dependent = _options[:dependent] || options[:dependent] @@ -257,6 +258,7 @@ module ActiveRecord        # Note that this method removes records from the database ignoring the        # +:dependent+ option.        def destroy(*records) +        return if records.empty?          records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }          delete_or_destroy(records, :destroy)        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..01173b68f3 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 diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 20bd4947dc..7519fec10a 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -112,13 +112,14 @@ module ActiveRecord        end        def preloaders_for_hash(association, records, scope) -        parent, child = association.to_a.first # hash should only be of length 1 +        association.flat_map { |parent, child| +          loaders = preloaders_for_one parent, records, scope -        loaders = preloaders_for_one parent, records, scope - -        recs = loaders.flat_map(&:preloaded_records).uniq -        loaders.concat Array.wrap(child).flat_map { |assoc| -          preloaders_on assoc, recs, scope +          recs = loaders.flat_map(&:preloaded_records).uniq +          loaders.concat Array.wrap(child).flat_map { |assoc| +            preloaders_on assoc, recs, scope +          } +          loaders          }        end @@ -142,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/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index bf461070e0..63773bd5e1 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -57,9 +57,15 @@ module ActiveRecord          end          def owners_by_key -          @owners_by_key ||= owners.group_by do |owner| -            owner[owner_key_name] -          end +          @owners_by_key ||= if key_conversion_required? +                               owners.group_by do |owner| +                                 owner[owner_key_name].to_s +                               end +                             else +                               owners.group_by do |owner| +                                 owner[owner_key_name] +                               end +                             end          end          def options @@ -93,13 +99,28 @@ module ActiveRecord            records_by_owner          end +        def key_conversion_required? +          association_key_type != owner_key_type +        end + +        def association_key_type +          @klass.column_types[association_key_name.to_s].type +        end + +        def owner_key_type +          @model.column_types[owner_key_name.to_s].type +        end +          def load_slices(slices)            @preloaded_records = slices.flat_map { |slice|              records_for(slice)            }            @preloaded_records.map { |record| -            [record, record[association_key_name]] +            key = record[association_key_name] +            key = key.to_s if key_conversion_required? + +            [record, key]            }          end diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index 87ecbe54f1..816fb51942 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -149,7 +149,7 @@ module ActiveRecord        end        def read_time -        # If column is a :time (and not :date or :timestamp) there is no need to validate if +        # If column is a :time (and not :date or :datetime) there is no need to validate if          # there are year/month/day fields          if column.type == :time            # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 8bd51dc71f..a0a0214eae 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -48,7 +48,11 @@ module ActiveRecord        end        private -      def method_body; raise NotImplementedError; end + +      # Override this method in the subclasses for method body. +      def method_body(method_name, const_name) +        raise NotImplementedError, "Subclasses must implement a method_body(method_name, const_name) method." +      end      end      module ClassMethods @@ -66,6 +70,7 @@ module ActiveRecord        # Generates all the attribute related methods for columns in the database        # accessors, mutators and query methods.        def define_attribute_methods # :nodoc: +        return false if @attribute_methods_generated          # Use a mutex; we don't want two thread simultaneously trying to define          # attribute methods.          generated_attribute_methods.synchronize do @@ -200,6 +205,7 @@ module ActiveRecord            # this is probably horribly slow, but should only happen at most once for a given AR class            attribute_method.bind(self).call(*args, &block)          else +          return super unless respond_to_missing?(method, true)            send(method, *args, &block)          end        else @@ -455,7 +461,7 @@ module ActiveRecord      end      def pk_attribute?(name) -      column_for_attribute(name).primary +      name == self.class.primary_key      end      def typecasted_attribute_value(name) diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index d01e9aea59..979dfb207e 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -35,7 +35,7 @@ module ActiveRecord        extend ActiveSupport::Concern -      ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] +      ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :time, :date]        included do          class_attribute :attribute_types_cached_by_default, instance_writer: false @@ -52,7 +52,7 @@ module ActiveRecord          end          # Returns the attributes which are cached. By default time related columns -        # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached. +        # with datatype <tt>:datetime, :time, :date</tt> are cached.          def cached_attributes            @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set          end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index c3466153d6..47c6f94ba7 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -65,6 +65,8 @@ module ActiveRecord        end        class Type # :nodoc: +        delegate :type, :type_cast_for_database, to: :@column +          def initialize(column)            @column = column          end @@ -77,10 +79,6 @@ module ActiveRecord            end          end -        def type -          @column.type -        end -          def accessor            ActiveRecord::Store::IndifferentHashAccessor          end @@ -142,6 +140,14 @@ module ActiveRecord            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 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 f168282ea3..6149ac4906 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 @@ -28,7 +26,7 @@ module ActiveRecord        module ClassMethods          protected -        # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled. +        # Defined for all +datetime+ attributes when +time_zone_aware_attributes+ are enabled.          # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.          def define_method_attribute=(attr_name)            if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) @@ -51,7 +49,7 @@ module ActiveRecord          def create_time_zone_conversion_attribute?(name, column)            time_zone_aware_attributes &&              !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && -            (:datetime == column.type || :timestamp == column.type) +            (:datetime == column.type)          end        end      end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index c853fc0917..56441d7324 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -55,6 +55,27 @@ 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) +      end + +      def raw_write_attribute(attr_name, value) +        write_attribute_with_type_cast(attr_name, value, :raw_type_cast_attribute_for_write) +      end + +      private +      # Handle *= for method_missing. +      def attribute=(attribute_name, value) +        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)          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) @@ -67,24 +88,11 @@ module ActiveRecord          end          if column || @attributes.has_key?(attr_name) -          @attributes[attr_name] = type_cast_attribute_for_write(column, value) +          @attributes[attr_name] = send(type_cast_method, column, value)          else            raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"          end        end -      alias_method :raw_write_attribute, :write_attribute - -      private -      # Handle *= for method_missing. -      def attribute=(attribute_name, value) -        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      end    end  end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 80cf7572df..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 @@ -381,15 +382,16 @@ module ActiveRecord        def save_has_one_association(reflection)          association = association_instance_get(reflection.name)          record      = association && association.load_target +          if record && !record.destroyed?            autosave = reflection.options[:autosave]            if autosave && record.marked_for_destruction?              record.destroy -          else +          elsif autosave != false              key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id -            if autosave != false && (autosave || new_record? || record_changed?(reflection, record, key)) +            if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)                unless reflection.through_reflection                  record[reflection.foreign_key] = key                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/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 75501852ed..f836e60988 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -9,26 +9,22 @@ module ActiveRecord          # records are quoted as their primary key          return value.quoted_id if value.respond_to?(:quoted_id) +        # 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 +          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. +          "'#{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)}'" @@ -47,30 +43,25 @@ 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 +        # 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 -        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 +        case value +        when Symbol, ActiveSupport::Multibyte::Chars +          value.to_s +        when true       then unquoted_true +        when false      then unquoted_false +        # BigDecimals need to be put in a non-normalized form and quoted.          when BigDecimal then value.to_s('F') -        when Numeric    then value          when Date, Time then quoted_date(value) -        when Symbol     then value.to_s +        when *types_which_need_no_typecasting +          value          else            to_type = column ? " to #{column.type}" : ""            raise TypeError, "can't cast #{value.class}#{to_type}" @@ -109,10 +100,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 +123,12 @@ module ActiveRecord          value.to_s(:db)        end + +      private + +      def types_which_need_no_typecasting +        [nil, Numeric, String] +      end      end    end  end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 71c3a4378b..117c0f0969 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -99,6 +99,8 @@ module ActiveRecord        #   Specifies the precision for a <tt>:decimal</tt> column.        # * <tt>:scale</tt> -        #   Specifies the scale for a <tt>:decimal</tt> column. +      # * <tt>:index</tt> - +      #   Create an index for the column. Can be either <tt>true</tt> or an options hash.        #        # For clarity's sake: the precision is the number of significant digits,        # while the scale is the number of digits that can be stored following @@ -123,17 +125,8 @@ module ActiveRecord        #   Default is (38,0).        # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].        #   Default unknown. -      # * Firebird: <tt>:precision</tt> [1..18], <tt>:scale</tt> [0..18]. -      #   Default (9,0). Internal types NUMERIC and DECIMAL have different -      #   storage rules, decimal being better. -      # * FrontBase?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38]. -      #   Default (38,0). WARNING Max <tt>:precision</tt>/<tt>:scale</tt> for -      #   NUMERIC is 19, and DECIMAL is 38.        # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].        #   Default (38,0). -      # * Sybase: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38]. -      #   Default (38,0). -      # * OpenBase?: Documentation unclear. Claims storage in <tt>double</tt>.        #        # This method returns <tt>self</tt>.        # @@ -172,18 +165,21 @@ module ActiveRecord        # What can be written like this with the regular calls to column:        #        #   create_table :products do |t| -      #     t.column :shop_id,    :integer -      #     t.column :creator_id, :integer -      #     t.column :name,       :string, default: "Untitled" -      #     t.column :value,      :string, default: "Untitled" -      #     t.column :created_at, :datetime -      #     t.column :updated_at, :datetime +      #     t.column :shop_id,     :integer +      #     t.column :creator_id,  :integer +      #     t.column :item_number, :string +      #     t.column :name,        :string, default: "Untitled" +      #     t.column :value,       :string, default: "Untitled" +      #     t.column :created_at,  :datetime +      #     t.column :updated_at,  :datetime        #   end +      #   add_index :products, :item_number        #        # can also be written as follows using the short-hand:        #        #   create_table :products do |t|        #     t.integer :shop_id, :creator_id +      #     t.string  :item_number, index: true        #     t.string  :name, :value, default: "Untitled"        #     t.timestamps        #   end @@ -219,6 +215,8 @@ module ActiveRecord            raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."          end +        index_options = options.delete(:index) +        index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options          @columns_hash[name] = new_column_definition(name, type, options)          self        end @@ -264,6 +262,7 @@ module ActiveRecord        alias :belongs_to :references        def new_column_definition(name, type, options) # :nodoc: +        type = aliased_types[type] || type          column = create_column_definition name, type          limit = options.fetch(:limit) do            native[type][:limit] if native[type].is_a?(Hash) @@ -294,6 +293,12 @@ module ActiveRecord        def native          @native        end + +      def aliased_types +        HashWithIndifferentAccess.new( +          timestamp: :datetime, +        ) +      end      end      class AlterTable # :nodoc: 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 cdf0cbe218..ac14740cfe 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -20,15 +20,8 @@ module ActiveRecord        def prepare_column_options(column, types)          spec = {}          spec[:name]      = column.name.inspect - -        # AR has an optimization which handles zero-scale decimals as integers. This -        # code ensures that the dumper still dumps the column as a decimal. -        spec[:type]      = if column.type == :integer && /^(numeric|decimal)/ =~ column.sql_type -                             'decimal' -                           else -                             column.type.to_s -                           end -        spec[:limit]     = column.limit.inspect if column.limit != types[column.type][:limit] && spec[:type] != 'decimal' +        spec[:type]      = column.type.to_s +        spec[:limit]     = column.limit.inspect if column.limit != types[column.type][:limit]          spec[:precision] = column.precision.inspect if column.precision          spec[:scale]     = column.scale.inspect if column.scale          spec[:null]      = 'false' unless column.null diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 3b3b03ff6e..6ecd4efdc8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -3,6 +3,7 @@ require 'bigdecimal'  require 'bigdecimal/util'  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' @@ -360,8 +361,76 @@ module ActiveRecord          pool.checkin self        end +      def type_map # :nodoc: +        @type_map ||= Type::TypeMap.new.tap do |mapping| +          initialize_type_map(mapping) +        end +      end +        protected +      def lookup_cast_type(sql_type) # :nodoc: +        type_map.lookup(sql_type) +      end + +      def initialize_type_map(m) # :nodoc: +        register_class_with_limit m, %r(boolean)i,   Type::Boolean +        register_class_with_limit m, %r(char)i,      Type::String +        register_class_with_limit m, %r(binary)i,    Type::Binary +        register_class_with_limit m, %r(text)i,      Type::Text +        register_class_with_limit m, %r(date)i,      Type::Date +        register_class_with_limit m, %r(time)i,      Type::Time +        register_class_with_limit m, %r(datetime)i,  Type::DateTime +        register_class_with_limit m, %r(float)i,     Type::Float +        register_class_with_limit m, %r(int)i,       Type::Integer + +        m.alias_type %r(blob)i,      'binary' +        m.alias_type %r(clob)i,      'text' +        m.alias_type %r(timestamp)i, 'datetime' +        m.alias_type %r(numeric)i,   'decimal' +        m.alias_type %r(number)i,    'decimal' +        m.alias_type %r(double)i,    'float' + +        m.register_type(%r(decimal)i) do |sql_type| +          scale = extract_scale(sql_type) +          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) +          end +        end +      end + +      def reload_type_map # :nodoc: +        type_map.clear +        initialize_type_map(type_map) +      end + +      def register_class_with_limit(mapping, key, klass) # :nodoc: +        mapping.register_type(key) do |*args| +          limit = extract_limit(args.last) +          klass.new(limit: limit) +        end +      end + +      def extract_scale(sql_type) # :nodoc: +        case sql_type +          when /\((\d+)\)/ then 0 +          when /\((\d+)(,(\d+))\)/ then $3.to_i +        end +      end + +      def extract_precision(sql_type) # :nodoc: +        $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/ +      end + +      def extract_limit(sql_type) # :nodoc: +        $1.to_i if sql_type =~ /\((.*)\)/ +      end +        def translate_exception_class(e, sql)          message = "#{e.class.name}: #{e.message}: #{sql}"          @logger.error message if @logger 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 35045b5258..82e62786ca 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -56,11 +56,11 @@ module ActiveRecord        class Column < ConnectionAdapters::Column # :nodoc:          attr_reader :collation, :strict, :extra -        def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "") +        def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")            @strict    = strict            @collation = collation            @extra     = extra -          super(name, default, sql_type, null) +          super(name, default, cast_type, sql_type, null)          end          def extract_default(default) @@ -86,56 +86,12 @@ module ActiveRecord            sql_type =~ /blob/i || type == :text          end -        # Must return the relevant concrete adapter -        def adapter -          raise NotImplementedError -        end -          def case_sensitive?            collation && !collation.match(/_ci$/)          end          private -        def simplified_type(field_type) -          return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)") - -          case field_type -          when /enum/i, /set/i then :string -          when /year/i         then :integer -          when /bit/i          then :binary -          else -            super -          end -        end - -        def extract_limit(sql_type) -          case sql_type -          when /^enum\((.+)\)/i -            $1.split(',').map{|enum| enum.strip.length - 2}.max -          when /blob|text/i -            case sql_type -            when /tiny/i -              255 -            when /medium/i -              16777215 -            when /long/i -              2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases -            else -              super # we could return 65535 here, but we leave it undecorated by default -            end -          when /^bigint/i;    8 -          when /^int/i;       4 -          when /^mediumint/i; 3 -          when /^smallint/i;  2 -          when /^tinyint/i;   1 -          when /^float/i;     24 -          when /^double/i;    53 -          else -            super -          end -        end -          # MySQL misreports NOT NULL column default when none is given.          # We can't detect this for columns which may have a legitimate ''          # default (string) but we can for others (integer, datetime, boolean, @@ -175,7 +131,6 @@ module ActiveRecord          :float       => { :name => "float" },          :decimal     => { :name => "decimal" },          :datetime    => { :name => "datetime" }, -        :timestamp   => { :name => "datetime" },          :time        => { :name => "time" },          :date        => { :name => "date" },          :binary      => { :name => "blob" }, @@ -223,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:        # @@ -262,9 +206,9 @@ module ActiveRecord          raise NotImplementedError        end -      # Overridden by the adapters to instantiate their specific Column type. -      def new_column(field, default, type, null, collation, extra = "") # :nodoc: -        Column.new(field, default, type, null, collation, extra) +      def new_column(field, default, sql_type, null, collation, extra = "") # :nodoc: +        cast_type = lookup_cast_type(sql_type) +        Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)        end        # Must return the Mysql error number from the exception, if the exception has an @@ -279,8 +223,6 @@ module ActiveRecord          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")          else            super          end @@ -298,10 +240,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: @@ -317,6 +267,11 @@ module ActiveRecord        # DATABASE STATEMENTS ====================================== +      def clear_cache! +        super +        reload_type_map +      end +        # Executes the SQL statement in the context of this connection.        def execute(sql, name = nil)          log(sql, name) { @connection.query(sql) } @@ -644,6 +599,34 @@ module ActiveRecord        protected +      def initialize_type_map(m) # :nodoc: +        super +        m.register_type(%r(enum)i) do |sql_type| +          limit = sql_type[/^enum\((.+)\)/i, 1] +            .split(',').map{|enum| enum.strip.length - 2}.max +          Type::String.new(limit: limit) +        end + +        m.register_type %r(tinytext)i,   Type::Text.new(limit: 255) +        m.register_type %r(tinyblob)i,   Type::Binary.new(limit: 255) +        m.register_type %r(mediumtext)i, Type::Text.new(limit: 16777215) +        m.register_type %r(mediumblob)i, Type::Binary.new(limit: 16777215) +        m.register_type %r(longtext)i,   Type::Text.new(limit: 2147483647) +        m.register_type %r(longblob)i,   Type::Binary.new(limit: 2147483647) +        m.register_type %r(^bigint)i,    Type::Integer.new(limit: 8) +        m.register_type %r(^int)i,       Type::Integer.new(limit: 4) +        m.register_type %r(^mediumint)i, Type::Integer.new(limit: 3) +        m.register_type %r(^smallint)i,  Type::Integer.new(limit: 2) +        m.register_type %r(^tinyint)i,   Type::Integer.new(limit: 1) +        m.register_type %r(^float)i,     Type::Float.new(limit: 24) +        m.register_type %r(^double)i,    Type::Float.new(limit: 53) + +        m.alias_type %r(tinyint\(1\))i,  'boolean' if emulate_booleans +        m.alias_type %r(set)i,           'varchar' +        m.alias_type %r(year)i,          'integer' +        m.alias_type %r(bit)i,           'binary' +      end +        # MySQL is too stupid to create a temporary table for use subquery, so we have        # to give it some prompting in the form of a subsubquery. Ugh!        def subquery_for(key, select) diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 38efebeaf3..86232f9d3f 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -13,106 +13,43 @@ module ActiveRecord          ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/        end -      attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale, :default_function -      attr_accessor :primary, :coder +      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, :type_cast_for_database, to: :cast_type +        # Instantiates a new column in the table.        #        # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.        # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>. +      # +cast_type+ is the object used for type casting and type information.        # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in        # <tt>company_name varchar(60)</tt>.        # It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.        # +null+ determines if this column allows +NULL+ values. -      def initialize(name, default, sql_type = nil, null = true) +      def initialize(name, default, cast_type, sql_type = nil, null = true)          @name             = name +        @cast_type        = cast_type          @sql_type         = sql_type          @null             = null -        @limit            = extract_limit(sql_type) -        @precision        = extract_precision(sql_type) -        @scale            = extract_scale(sql_type) -        @type             = simplified_type(sql_type)          @default          = extract_default(default)          @default_function = nil -        @primary          = nil          @coder            = nil        end -      # Returns +true+ if the column is either of type string or text. -      def text? -        type == :string || type == :text -      end - -      # Returns +true+ if the column is either of type integer, float or decimal. -      def number? -        type == :integer || type == :float || type == :decimal -      end -        def has_default?          !default.nil?        end -      # Returns the Ruby class that corresponds to the abstract data type. -      def klass -        case type -        when :integer                     then Fixnum -        when :float                       then Float -        when :decimal                     then BigDecimal -        when :datetime, :timestamp, :time then Time -        when :date                        then Date -        when :text, :string, :binary      then String -        when :boolean                     then Object -        end -      end - -      def binary? -        type == :binary -      end - -      # Casts a Ruby value to something appropriate for writing to the database. -      # Numeric columns will typecast boolean and string to appropriate numeric -      # values. -      def type_cast_for_write(value) -        return value unless number? - -        case value -        when FalseClass -          0 -        when TrueClass -          1 -        when String -          value.presence -        else -          value -        end -      end -        # Casts value to an appropriate instance.        def type_cast(value) -        return nil if value.nil? -        return coder.load(value) if encoded? - -        klass = self.class - -        case type -        when :string, :text -          case value -          when TrueClass; "1" -          when FalseClass; "0" -          else -            value.to_s -          end -        when :integer              then klass.value_to_integer(value) -        when :float                then value.to_f -        when :decimal              then klass.value_to_decimal(value) -        when :datetime, :timestamp then klass.string_to_time(value) -        when :time                 then klass.string_to_dummy_time(value) -        when :date                 then klass.value_to_date(value) -        when :binary               then klass.binary_to_string(value) -        when :boolean              then klass.value_to_boolean(value) -        else value +        if encoded? +          coder.load(value) +        else +          cast_type.type_cast(value)          end        end @@ -127,174 +64,6 @@ module ActiveRecord        def extract_default(default)          type_cast(default)        end - -      class << self -        # Used to convert from BLOBs to Strings -        def binary_to_string(value) -          value -        end - -        def value_to_date(value) -          if value.is_a?(String) -            return nil 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 string_to_time(string) -          return string unless string.is_a?(String) -          return nil if string.empty? - -          fast_string_to_time(string) || fallback_string_to_time(string) -        end - -        def string_to_dummy_time(string) -          return string unless string.is_a?(String) -          return nil if string.empty? - -          dummy_time_string = "2000-01-01 #{string}" - -          fast_string_to_time(dummy_time_string) || begin -            time_hash = Date._parse(dummy_time_string) -            return nil if time_hash[:hour].nil? -            new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)) -          end -        end - -        # convert something to a boolean -        def value_to_boolean(value) -          if value.is_a?(String) && value.empty? -            nil -          else -            TRUE_VALUES.include?(value) -          end -        end - -        # Used to convert values to integer. -        # handle the case when an integer column is used to store boolean values -        def value_to_integer(value) -          case value -          when TrueClass, FalseClass -            value ? 1 : 0 -          else -            value.to_i rescue nil -          end -        end - -        # convert something to a BigDecimal -        def value_to_decimal(value) -          # Using .class is faster than .is_a? and -          # subclasses of BigDecimal will be handled -          # in the else clause -          if value.class == BigDecimal -            value -          elsif value.respond_to?(:to_d) -            value.to_d -          else -            value.to_s.to_d -          end -        end - -        protected -          # '0.123456' -> 123456 -          # '1.123456' -> 123456 -          def microseconds(time) -            time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0 -          end - -          def new_date(year, mon, mday) -            if year && year != 0 -              Date.new(year, mon, mday) rescue nil -            end -          end - -          def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil) -            # Treat 0000-00-00 00:00:00 as nil. -            return nil if year.nil? || (year == 0 && mon == 0 && mday == 0) - -            if offset -              time = Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil -              return nil 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 - -          def fast_string_to_date(string) -            if string =~ Format::ISO_DATE -              new_date $1.to_i, $2.to_i, $3.to_i -            end -          end - -          # Doesn't handle time zones. -          def fast_string_to_time(string) -            if string =~ 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 - -          def fallback_string_to_date(string) -            new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) -          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 - -      private -        def extract_limit(sql_type) -          $1.to_i if sql_type =~ /\((.*)\)/ -        end - -        def extract_precision(sql_type) -          $2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i -        end - -        def extract_scale(sql_type) -          case sql_type -            when /^(numeric|decimal|number)\((\d+)\)/i then 0 -            when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i -          end -        end - -        def simplified_type(field_type) -          case field_type -          when /int/i -            :integer -          when /float|double/i -            :float -          when /decimal|numeric|number/i -            extract_scale(field_type) == 0 ? :integer : :decimal -          when /datetime/i -            :datetime -          when /timestamp/i -            :timestamp -          when /time/i -            :time -          when /date/i -            :date -          when /clob/i, /text/i -            :text -          when /blob/i, /binary/i -            :binary -          when /char/i -            :string -          when /boolean/i -            :boolean -          end -        end      end    end    # :startdoc: diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 233af252d6..0a14cdfe89 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -29,13 +29,6 @@ module ActiveRecord    module ConnectionAdapters      class Mysql2Adapter < AbstractMysqlAdapter - -      class Column < AbstractMysqlAdapter::Column # :nodoc: -        def adapter -          Mysql2Adapter -        end -      end -        ADAPTER_NAME = 'Mysql2'        def initialize(connection, logger, connection_options, config) @@ -69,10 +62,6 @@ module ActiveRecord          end        end -      def new_column(field, default, type, null, collation, extra = "") # :nodoc: -        Column.new(field, default, type, null, collation, strict_mode?, extra) -      end -        def error_number(exception)          exception.error_number if exception.respond_to?(:error_number)        end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index e6aa2ba921..aa8a91ed39 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -66,35 +66,6 @@ module ActiveRecord      # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.      #      class MysqlAdapter < AbstractMysqlAdapter - -      class Column < AbstractMysqlAdapter::Column #:nodoc: -        def self.string_to_time(value) -          return super unless Mysql::Time === value -          new_time( -            value.year, -            value.month, -            value.day, -            value.hour, -            value.minute, -            value.second, -            value.second_part) -        end - -        def self.string_to_dummy_time(v) -          return super unless Mysql::Time === v -          new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part) -        end - -        def self.string_to_date(v) -          return super unless Mysql::Time === v -          new_date(v.year, v.month, v.day) -        end - -        def adapter -          MysqlAdapter -        end -      end -        ADAPTER_NAME = 'MySQL'        class StatementPool < ConnectionAdapters::StatementPool @@ -156,10 +127,6 @@ module ActiveRecord          end        end -      def new_column(field, default, type, null, collation, extra = "") # :nodoc: -        Column.new(field, default, type, null, collation, strict_mode?, extra) -      end -        def error_number(exception) # :nodoc:          exception.errno if exception.respond_to?(:errno)        end @@ -222,6 +189,7 @@ module ActiveRecord        # Clears the prepared statements cache.        def clear_cache! +        super          @statements.clear        end @@ -294,126 +262,70 @@ module ActiveRecord          @connection.insert_id        end -      module Fields -        class Type -          def type; end - -          def type_cast_for_write(value) -            value -          end -        end - -        class Identity < Type -          def type_cast(value); value; end -        end - -        class Integer < Type -          def type_cast(value) -            return if value.nil? - -            value.to_i rescue value ? 1 : 0 -          end -        end - -        class Date < Type -          def type; :date; end - -          def type_cast(value) -            return if value.nil? - -            # FIXME: probably we can improve this since we know it is mysql -            # specific -            ConnectionAdapters::Column.value_to_date value -          end -        end - -        class DateTime < Type -          def type; :datetime; end - -          def type_cast(value) -            return if value.nil? - -            # FIXME: probably we can improve this since we know it is mysql -            # specific -            ConnectionAdapters::Column.string_to_time value -          end -        end - -        class Time < Type -          def type; :time; end - -          def type_cast(value) -            return if value.nil? - -            # FIXME: probably we can improve this since we know it is mysql -            # specific -            ConnectionAdapters::Column.string_to_dummy_time value -          end -        end - -        class Float < Type -          def type; :float; end - -          def type_cast(value) -            return if value.nil? - -            value.to_f +      module Fields # :nodoc: +        class DateTime < Type::DateTime +          def cast_value(value) +            if Mysql::Time === value +              new_time( +                value.year, +                value.month, +                value.day, +                value.hour, +                value.minute, +                value.second, +                value.second_part) +            else +              super +            end            end          end -        class Decimal < Type -          def type_cast(value) -            return if value.nil? - -            ConnectionAdapters::Column.value_to_decimal value +        class Time < Type::Time +          def cast_value(value) +            if Mysql::Time === value +              new_time( +                2000, +                01, +                01, +                value.hour, +                value.minute, +                value.second, +                value.second_part) +            else +              super +            end            end          end -        class Boolean < Type -          def type_cast(value) -            return if value.nil? +        class << self +          TYPES = ConnectionAdapters::Type::HashLookupTypeMap.new # :nodoc: -            ConnectionAdapters::Column.value_to_boolean value -          end -        end - -        TYPES = {} - -        # Register an MySQL +type_id+ with a typecasting object in -        # +type+. -        def self.register_type(type_id, type) -          TYPES[type_id] = type -        end +          delegate :register_type, :alias_type, to: :TYPES -        def self.alias_type(new, old) -          TYPES[new] = TYPES[old] -        end - -        def self.find_type(field) -          if field.type == Mysql::Field::TYPE_TINY && field.length > 1 -            TYPES[Mysql::Field::TYPE_LONG] -          else -            TYPES.fetch(field.type) { Fields::Identity.new } +          def find_type(field) +            if field.type == Mysql::Field::TYPE_TINY && field.length > 1 +              TYPES.lookup(Mysql::Field::TYPE_LONG) +            else +              TYPES.lookup(field.type) +            end            end          end -        register_type Mysql::Field::TYPE_TINY,    Fields::Boolean.new -        register_type Mysql::Field::TYPE_LONG,    Fields::Integer.new +        register_type Mysql::Field::TYPE_TINY,    Type::Boolean.new +        register_type Mysql::Field::TYPE_LONG,    Type::Integer.new          alias_type Mysql::Field::TYPE_LONGLONG,   Mysql::Field::TYPE_LONG          alias_type Mysql::Field::TYPE_NEWDECIMAL, Mysql::Field::TYPE_LONG -        register_type Mysql::Field::TYPE_VAR_STRING, Fields::Identity.new -        register_type Mysql::Field::TYPE_BLOB, Fields::Identity.new -        register_type Mysql::Field::TYPE_DATE, Fields::Date.new +        register_type Mysql::Field::TYPE_DATE, Type::Date.new          register_type Mysql::Field::TYPE_DATETIME, Fields::DateTime.new          register_type Mysql::Field::TYPE_TIME, Fields::Time.new -        register_type Mysql::Field::TYPE_FLOAT, Fields::Float.new +        register_type Mysql::Field::TYPE_FLOAT, Type::Float.new +      end -        Mysql::Field.constants.grep(/TYPE/).map { |class_name| -          Mysql::Field.const_get class_name -        }.reject { |const| TYPES.key? const }.each do |const| -          register_type const, Fields::Identity.new -        end +      def initialize_type_map(m) # :nodoc: +        super +        m.register_type %r(datetime)i, Fields::DateTime.new +        m.register_type %r(time)i,     Fields::Time.new        end        def exec_without_stmt(sql, name = 'SQL') # :nodoc: @@ -431,7 +343,7 @@ module ActiveRecord                fields << field_name                if field.decimals > 0 -                types[field_name] = Fields::Decimal.new +                types[field_name] = Type::Decimal.new                else                  types[field_name] = Fields.find_type field                end @@ -447,7 +359,7 @@ module ActiveRecord          end        end -      def execute_and_free(sql, name = nil) +      def execute_and_free(sql, name = nil) # :nodoc:          result = execute(sql, name)          ret = yield result          result.free @@ -460,7 +372,7 @@ module ActiveRecord        end        alias :create :insert_sql -      def exec_delete(sql, name, binds) +      def exec_delete(sql, name, binds) # :nodoc:          affected_rows = 0          exec_query(sql, name, binds) do |n| diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb index 743bf68fe6..1b74c039ce 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb @@ -1,7 +1,7 @@  module ActiveRecord    module ConnectionAdapters      module PostgreSQL -      module ArrayParser +      module ArrayParser # :nodoc:          DOUBLE_QUOTE = '"'          BACKSLASH = "\\" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index b612602216..f7bad20f00 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -1,31 +1,11 @@  module ActiveRecord    module ConnectionAdapters      module PostgreSQL -      module Cast +      module Cast # :nodoc:          def point_to_string(point) # :nodoc:            "(#{point[0]},#{point[1]})"          end -        def string_to_point(string) # :nodoc: -          if string[0] == '(' && string[-1] == ')' -            string = string[1...-1] -          end -          string.split(',').map{ |v| Float(v) } -        end - -        def string_to_time(string) # :nodoc: -          return string unless String === string - -          case string -          when 'infinity'; Float::INFINITY -          when '-infinity'; -Float::INFINITY -          when / BC$/ -            super("-" + string.sub(/ BC$/, "")) -          else -            super -          end -        end -          def string_to_bit(value) # :nodoc:            case value            when /^0x/i diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index 97a93ce87a..9a5e2d05ef 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -6,27 +6,16 @@ module ActiveRecord      class PostgreSQLColumn < Column #:nodoc:        attr_accessor :array -      def initialize(name, default, oid_type, sql_type = nil, null = true) -        @oid_type = oid_type -        default_value     = self.class.extract_value_from_default(default) - +      def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil)          if sql_type =~ /\[\]$/            @array = true -          super(name, default_value, sql_type[0..sql_type.length - 3], null) +          super(name, default, cast_type, sql_type[0..sql_type.length - 3], null)          else            @array = false -          super(name, default_value, sql_type, null) +          super(name, default, cast_type, sql_type, null)          end -        @default_function = default if has_default_function?(default_value, default) -      end - -      def number? -        !array && super -      end - -      def text? -        !array && super +        @default_function = default_function        end        # :stopdoc: @@ -44,132 +33,12 @@ module ActiveRecord            require 'active_record/connection_adapters/postgresql/array_parser'            include PostgreSQL::ArrayParser          end - -        attr_accessor :money_precision        end        # :startdoc: -      # Extracts the value from a PostgreSQL column default definition. -      def self.extract_value_from_default(default) -        # 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, -        # Regexp#=== will call NilClass#to_str, which will trigger -        # method_missing (defined by whiny nil in ActiveSupport) which -        # makes this method very very slow. -        return default unless default - -        case default -          when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m -            $1 -          # Numeric types -          when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/ -            $1 -          # Character types -          when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m -            $1.gsub(/''/, "'") -          # Binary data types -          when /\A'(.*)'::bytea\z/m -            $1 -          # Date/time types -          when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/ -            $1 -          when /\A'(.*)'::interval\z/ -            $1 -          # Boolean type -          when 'true' -            true -          when 'false' -            false -          # Geometric types -          when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/ -            $1 -          # Network address types -          when /\A'(.*)'::(?:cidr|inet|macaddr)\z/ -            $1 -          # Bit string types -          when /\AB'(.*)'::"?bit(?: varying)?"?\z/ -            $1 -          # XML type -          when /\A'(.*)'::xml\z/m -            $1 -          # Arrays -          when /\A'(.*)'::"?\D+"?\[\]\z/ -            $1 -          # Hstore -          when /\A'(.*)'::hstore\z/ -            $1 -          # JSON -          when /\A'(.*)'::json\z/ -            $1 -          # Object identifier types -          when /\A-?\d+\z/ -            $1 -          else -            # Anything else is blank, some user type, or some function -            # and we can't know the value of that, so return nil. -            nil -        end -      end - -      # Casts a Ruby value to something appropriate for writing to PostgreSQL. -      # see ActiveRecord::ConnectionAdapters::Class#type_cast_for_write -      # see ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID::Type -      def type_cast_for_write(value) -        if @oid_type.respond_to?(:type_cast_for_write) -          @oid_type.type_cast_for_write(value) -        else -          super -        end -      end - -      def type_cast(value) -        return if value.nil? -        return super if encoded? - -        @oid_type.type_cast value -      end -        def accessor -        @oid_type.accessor +        cast_type.accessor        end - -      private - -        def has_default_function?(default_value, default) -          !default_value && (%r{\w+\(.*\)} === default) -        end - -        def extract_limit(sql_type) -          case sql_type -          when /^bigint/i;    8 -          when /^smallint/i;  2 -          when /^timestamp/i; nil -          else super -          end -        end - -        # Extracts the scale from PostgreSQL-specific data types. -        def extract_scale(sql_type) -          # Money type has a fixed scale of 2. -          sql_type =~ /^money/ ? 2 : super -        end - -        # Extracts the precision from PostgreSQL-specific data types. -        def extract_precision(sql_type) -          if sql_type == 'money' -            self.class.money_precision -          elsif sql_type =~ /timestamp/i -            $1.to_i if sql_type =~ /\((\d+)\)/ -          else -            super -          end -        end - -        # Maps PostgreSQL-specific data types to logical Rails types. -        def simplified_type(field_type) -          @oid_type.simplified_type(field_type) || super -        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 cf6a375704..2494e19f84 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -1,515 +1,32 @@ +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/bytea' +require 'active_record/connection_adapters/postgresql/oid/cidr' +require 'active_record/connection_adapters/postgresql/oid/date' +require 'active_record/connection_adapters/postgresql/oid/date_time' +require 'active_record/connection_adapters/postgresql/oid/decimal' +require 'active_record/connection_adapters/postgresql/oid/enum' +require 'active_record/connection_adapters/postgresql/oid/float' +require 'active_record/connection_adapters/postgresql/oid/hstore' +require 'active_record/connection_adapters/postgresql/oid/inet' +require 'active_record/connection_adapters/postgresql/oid/integer' +require 'active_record/connection_adapters/postgresql/oid/json' +require 'active_record/connection_adapters/postgresql/oid/money' +require 'active_record/connection_adapters/postgresql/oid/point' +require 'active_record/connection_adapters/postgresql/oid/range' +require 'active_record/connection_adapters/postgresql/oid/specialized_string' +require 'active_record/connection_adapters/postgresql/oid/time' +require 'active_record/connection_adapters/postgresql/oid/uuid' +require 'active_record/connection_adapters/postgresql/oid/vector' + +require 'active_record/connection_adapters/postgresql/oid/type_map_initializer' +  module ActiveRecord    module ConnectionAdapters      module PostgreSQL -      module OID -        class Type -          def type; end -          def simplified_type(sql_type); type end - -          def infinity(options = {}) -            ::Float::INFINITY * (options[:negative] ? -1 : 1) -          end -        end - -        class Identity < Type -          def type_cast(value) -            value -          end -        end - -        class String < Type -          def type; :string end - -          def type_cast(value) -            return if value.nil? - -            value.to_s -          end -        end - -        class SpecializedString < OID::String -          def type; @type end - -          def initialize(type) -            @type = type -          end -        end - -        class Text < OID::String -          def type; :text end -        end - -        class Bit < Type -          def type; :string end - -          def type_cast(value) -            if ::String === value -              ConnectionAdapters::PostgreSQLColumn.string_to_bit value -            else -              value -            end -          end -        end - -        class Bytea < Type -          def type; :binary end - -          def type_cast(value) -            return if value.nil? -            PGconn.unescape_bytea value -          end -        end - -        class Money < Type -          def type; :decimal end - -          def type_cast(value) -            return if value.nil? -            return value unless ::String === value - -            # Because money output is formatted according to the locale, there are two -            # cases to consider (note the decimal separators): -            #  (1) $12,345,678.12 -            #  (2) $12.345.678,12 -            # Negative values are represented as follows: -            #  (3) -$2.55 -            #  (4) ($2.55) - -            value.sub!(/^\((.+)\)$/, '-\1') # (4) -            case value -            when /^-?\D+[\d,]+\.\d{2}$/  # (1) -              value.gsub!(/[^-\d.]/, '') -            when /^-?\D+[\d.]+,\d{2}$/  # (2) -              value.gsub!(/[^-\d,]/, '').sub!(/,/, '.') -            end - -            ConnectionAdapters::Column.value_to_decimal value -          end -        end - -        class Vector < Type -          attr_reader :delim, :subtype - -          # +delim+ corresponds to the `typdelim` column in the pg_types -          # table.  +subtype+ is derived from the `typelem` column in the -          # pg_types table. -          def initialize(delim, subtype) -            @delim   = delim -            @subtype = subtype -          end - -          # FIXME: this should probably split on +delim+ and use +subtype+ -          # to cast the values.  Unfortunately, the current Rails behavior -          # is to just return the string. -          def type_cast(value) -            value -          end -        end - -        class Point < Type -          def type; :string end - -          def type_cast(value) -            if ::String === value -              ConnectionAdapters::PostgreSQLColumn.string_to_point value -            else -              value -            end -          end -        end - -        class Array < Type -          def type; @subtype.type end - -          attr_reader :subtype -          def initialize(subtype) -            @subtype = subtype -          end - -          def type_cast(value) -            if ::String === value -              ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype -            else -              value -            end -          end -        end - -        class Range < Type -          attr_reader :subtype -          def simplified_type(sql_type); sql_type.to_sym end - -          def initialize(subtype) -            @subtype = subtype -          end - -          def extract_bounds(value) -            from, to = value[1..-2].split(',') -            { -              from:          (value[1] == ',' || from == '-infinity') ? @subtype.infinity(negative: true) : from, -              to:            (value[-2] == ',' || to == 'infinity') ? @subtype.infinity : to, -              exclude_start: (value[0] == '('), -              exclude_end:   (value[-1] == ')') -            } -          end - -          def infinity?(value) -            value.respond_to?(:infinite?) && value.infinite? -          end - -          def type_cast_single(value) -            infinity?(value) ? value : @subtype.type_cast(value) -          end - -          def type_cast(value) -            return if value.nil? || value == 'empty' -            return value if value.is_a?(::Range) - -            extracted = extract_bounds(value) -            from = type_cast_single extracted[:from] -            to = type_cast_single extracted[:to] - -            if !infinity?(from) && extracted[:exclude_start] -              if from.respond_to?(:succ) -                from = from.succ -                ActiveSupport::Deprecation.warn <<-MESSAGE -Excluding the beginning of a Range is only partialy supported through `#succ`. -This is not reliable and will be removed in the future. -                MESSAGE -              else -                raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')" -              end -            end -            ::Range.new(from, to, extracted[:exclude_end]) -          end -        end - -        class Integer < Type -          def type; :integer end - -          def type_cast(value) -            return if value.nil? - -            ConnectionAdapters::Column.value_to_integer value -          end -        end - -        class Boolean < Type -          def type; :boolean end - -          def type_cast(value) -            return if value.nil? - -            ConnectionAdapters::Column.value_to_boolean value -          end -        end - -        class Timestamp < Type -          def type; :timestamp; end -          def simplified_type(sql_type) -            :datetime -          end - -          def type_cast(value) -            return if value.nil? - -            # FIXME: probably we can improve this since we know it is PG -            # specific -            ConnectionAdapters::PostgreSQLColumn.string_to_time value -          end -        end - -        class Date < Type -          def type; :date; end - -          def type_cast(value) -            return if value.nil? - -            # FIXME: probably we can improve this since we know it is PG -            # specific -            ConnectionAdapters::Column.value_to_date value -          end -        end - -        class Time < Type -          def type; :time end - -          def type_cast(value) -            return if value.nil? - -            # FIXME: probably we can improve this since we know it is PG -            # specific -            ConnectionAdapters::Column.string_to_dummy_time value -          end -        end - -        class Float < Type -          def type; :float end - -          def type_cast(value) -            case value -              when nil;         nil -              when 'Infinity';  ::Float::INFINITY -              when '-Infinity'; -::Float::INFINITY -              when 'NaN';       ::Float::NAN -            else -              value.to_f -            end -          end -        end - -        class Decimal < Type -          def type; :decimal end - -          def type_cast(value) -            return if value.nil? - -            ConnectionAdapters::Column.value_to_decimal value -          end - -          def infinity(options = {}) -            BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1) -          end -        end - -        class Enum < Type -          def type; :enum end - -          def type_cast(value) -            value.to_s -          end -        end - -        class Hstore < Type -          def type; :hstore end - -          def type_cast_for_write(value) -            ConnectionAdapters::PostgreSQLColumn.hstore_to_string value -          end - -          def type_cast(value) -            return if value.nil? - -            ConnectionAdapters::PostgreSQLColumn.string_to_hstore value -          end - -          def accessor -            ActiveRecord::Store::StringKeyedHashAccessor -          end -        end - -        class Cidr < Type -          def type; :cidr end -          def type_cast(value) -            return if value.nil? - -            ConnectionAdapters::PostgreSQLColumn.string_to_cidr value -          end -        end -        class Inet < Cidr -          def type; :inet end -        end - -        class Json < Type -          def type; :json end - -          def type_cast_for_write(value) -            ConnectionAdapters::PostgreSQLColumn.json_to_string value -          end - -          def type_cast(value) -            return if value.nil? - -            ConnectionAdapters::PostgreSQLColumn.string_to_json value -          end - -          def accessor -            ActiveRecord::Store::StringKeyedHashAccessor -          end -        end - -        class Uuid < Type -          def type; :uuid end -          def type_cast(value) -            value.presence -          end -        end - -        class TypeMap -          def initialize -            @mapping = {} -          end - -          def []=(oid, type) -            @mapping[oid] = type -          end - -          def [](oid) -            @mapping[oid] -          end - -          def clear -            @mapping.clear -          end - -          def key?(oid) -            @mapping.key? oid -          end - -          def fetch(ftype, fmod) -            # The type for the numeric depends on the width of the field, -            # so we'll do something special here. -            # -            # When dealing with decimal columns: -            # -            # places after decimal  = fmod - 4 & 0xffff -            # places before decimal = (fmod - 4) >> 16 & 0xffff -            if ftype == 1700 && (fmod - 4 & 0xffff).zero? -              ftype = 23 -            end - -            @mapping.fetch(ftype) { |oid| yield oid, fmod } -          end -        end - -        # This class uses the data from PostgreSQL pg_type table to build -        # the OID -> Type mapping. -        #   - OID is and integer representing the type. -        #   - Type is an OID::Type object. -        # This class has side effects on the +store+ passed during initialization. -        class TypeMapInitializer # :nodoc: -          def initialize(store) -            @store = store -          end - -          def run(records) -            mapped, nodes = records.partition { |row| OID.registered_type? row['typname'] } -            ranges, nodes = nodes.partition { |row| row['typtype'] == 'r' } -            enums, nodes = nodes.partition { |row| row['typtype'] == 'e' } -            domains, nodes = nodes.partition { |row| row['typtype'] == 'd' } -            arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' } -            composites, nodes = nodes.partition { |row| row['typelem'] != '0' } - -            mapped.each     { |row| register_mapped_type(row)    } -            enums.each      { |row| register_enum_type(row)      } -            domains.each    { |row| register_domain_type(row)    } -            arrays.each     { |row| register_array_type(row)     } -            ranges.each     { |row| register_range_type(row)     } -            composites.each { |row| register_composite_type(row) } -          end - -          private -          def register_mapped_type(row) -            register row['oid'], OID::NAMES[row['typname']] -          end - -          def register_enum_type(row) -            register row['oid'], OID::Enum.new -          end - -          def register_array_type(row) -            if subtype = @store[row['typelem'].to_i] -              register row['oid'], OID::Array.new(subtype) -            end -          end - -          def register_range_type(row) -            if subtype = @store[row['rngsubtype'].to_i] -              register row['oid'], OID::Range.new(subtype) -            end -          end - -          def register_domain_type(row) -            if base_type = @store[row["typbasetype"].to_i] -              register row['oid'], base_type -            else -              warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}." -            end -          end - -          def register_composite_type(row) -            if subtype = @store[row['typelem'].to_i] -              register row['oid'], OID::Vector.new(row['typdelim'], subtype) -            end -          end - -          def register(oid, oid_type) -            oid = oid.to_i - -            raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil? -            return if @store.key?(oid) - -            @store[oid] = oid_type -          end -        end - -        # When the PG adapter connects, the pg_type table is queried. The -        # key of this hash maps to the `typname` column from the table. -        # type_map is then dynamically built with oids as the key and type -        # objects as values. -        NAMES = Hash.new { |h,k| # :nodoc: -          h[k] = OID::Identity.new -        } - -        # Register an OID type named +name+ with a typecasting object in -        # +type+. +name+ should correspond to the `typname` column in -        # the `pg_type` table. -        def self.register_type(name, type) -          NAMES[name] = type -        end - -        # Alias the +old+ type to the +new+ type. -        def self.alias_type(new, old) -          NAMES[new] = NAMES[old] -        end - -        # Is +name+ a registered type? -        def self.registered_type?(name) -          NAMES.key? name -        end - -        register_type 'int2', OID::Integer.new -        alias_type 'int4', 'int2' -        alias_type 'int8', 'int2' -        alias_type 'oid', 'int2' -        register_type 'numeric', OID::Decimal.new -        register_type 'float4', OID::Float.new -        alias_type 'float8', 'float4' -        register_type 'text', OID::Text.new -        register_type 'varchar', OID::String.new -        alias_type 'char', 'varchar' -        alias_type 'name', 'varchar' -        alias_type 'bpchar', 'varchar' -        register_type 'bool', OID::Boolean.new -        register_type 'bit', OID::Bit.new -        alias_type 'varbit', 'bit' -        register_type 'timestamp', OID::Timestamp.new -        alias_type 'timestamptz', 'timestamp' -        register_type 'date', OID::Date.new -        register_type 'time', OID::Time.new - -        register_type 'money', OID::Money.new -        register_type 'bytea', OID::Bytea.new -        register_type 'point', OID::Point.new -        register_type 'hstore', OID::Hstore.new -        register_type 'json', OID::Json.new -        register_type 'cidr', OID::Cidr.new -        register_type 'inet', OID::Inet.new -        register_type 'uuid', OID::Uuid.new -        register_type 'xml', SpecializedString.new(:xml) -        register_type 'tsvector', SpecializedString.new(:tsvector) -        register_type 'macaddr', SpecializedString.new(:macaddr) -        register_type 'citext', SpecializedString.new(:citext) -        register_type 'ltree', SpecializedString.new(:ltree) - -        # FIXME: why are we keeping these types as strings? -        alias_type 'interval', 'varchar' -        alias_type 'path', 'varchar' -        alias_type 'line', 'varchar' -        alias_type 'polygon', 'varchar' -        alias_type 'circle', 'varchar' -        alias_type 'lseg', 'varchar' -        alias_type 'box', 'varchar' +      module OID # :nodoc:        end      end    end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb new file mode 100644 index 0000000000..0e9dcd8c0c --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -0,0 +1,24 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Array < Type::Value +          attr_reader :subtype +          delegate :type, to: :subtype + +          def initialize(subtype) +            @subtype = subtype +          end + +          def type_cast(value) +            if ::String === value +              ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype +            else +              value +            end +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb new file mode 100644 index 0000000000..9b2d887d07 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb @@ -0,0 +1,17 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Bit < Type::String +          def type_cast(value) +            if ::String === value +              ConnectionAdapters::PostgreSQLColumn.string_to_bit value +            else +              value +            end +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb new file mode 100644 index 0000000000..36c53d8732 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb @@ -0,0 +1,13 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Bytea < Type::Binary +          def cast_value(value) +            PGconn.unescape_bytea value +          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 new file mode 100644 index 0000000000..507c3a62b0 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb @@ -0,0 +1,17 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Cidr < Type::Value +          def type +            :cidr +          end + +          def cast_value(value) +            ConnectionAdapters::PostgreSQLColumn.string_to_cidr value +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb new file mode 100644 index 0000000000..3c30ad5fec --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb @@ -0,0 +1,11 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Date < Type::Date +          include Infinity +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb new file mode 100644 index 0000000000..9ccbf71159 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb @@ -0,0 +1,26 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class DateTime < Type::DateTime +          include Infinity + +          def cast_value(value) +            if value.is_a?(::String) +              case value +              when 'infinity' then ::Float::INFINITY +              when '-infinity' then -::Float::INFINITY +              when / BC$/ +                super("-" + value.sub(/ BC$/, "")) +              else +                super +              end +            else +              value +            end +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb new file mode 100644 index 0000000000..ed4b8911d9 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb @@ -0,0 +1,13 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Decimal < Type::Decimal +          def infinity(options = {}) +            BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1) +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb new file mode 100644 index 0000000000..5fed8b0f89 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb @@ -0,0 +1,17 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Enum < Type::Value +          def type +            :enum +          end + +          def type_cast(value) +            value.to_s +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb new file mode 100644 index 0000000000..9753d71461 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb @@ -0,0 +1,21 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Float < Type::Float +          include Infinity + +          def type_cast(value) +            case value +            when nil then         nil +            when 'Infinity' then  ::Float::INFINITY +            when '-Infinity' then -::Float::INFINITY +            when 'NaN' then       ::Float::NAN +            else                  value.to_f +            end +          end +        end +      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 new file mode 100644 index 0000000000..98f369a7f8 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -0,0 +1,25 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Hstore < Type::Value +          def type +            :hstore +          end + +          def type_cast_for_write(value) +            ConnectionAdapters::PostgreSQLColumn.hstore_to_string value +          end + +          def cast_value(value) +            ConnectionAdapters::PostgreSQLColumn.string_to_hstore value +          end + +          def accessor +            ActiveRecord::Store::StringKeyedHashAccessor +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/inet.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/inet.rb new file mode 100644 index 0000000000..7ed8f5f031 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/inet.rb @@ -0,0 +1,13 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Inet < Cidr +          def type +            :inet +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb new file mode 100644 index 0000000000..d438ffa140 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb @@ -0,0 +1,13 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        module Infinity +          def infinity(options = {}) +            options[:negative] ? -::Float::INFINITY : ::Float::INFINITY +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb new file mode 100644 index 0000000000..388d3dd9ed --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb @@ -0,0 +1,11 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Integer < Type::Integer +          include Infinity +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb new file mode 100644 index 0000000000..42bf5656f4 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb @@ -0,0 +1,25 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Json < Type::Value +          def type +            :json +          end + +          def type_cast_for_write(value) +            ConnectionAdapters::PostgreSQLColumn.json_to_string value +          end + +          def cast_value(value) +            ConnectionAdapters::PostgreSQLColumn.string_to_json value +          end + +          def accessor +            ActiveRecord::Store::StringKeyedHashAccessor +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb new file mode 100644 index 0000000000..697dceb7c2 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -0,0 +1,39 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Money < Type::Decimal +          include Infinity + +          class_attribute :precision + +          def scale +            2 +          end + +          def cast_value(value) +            return value unless ::String === value + +            # Because money output is formatted according to the locale, there are two +            # cases to consider (note the decimal separators): +            #  (1) $12,345,678.12 +            #  (2) $12.345.678,12 +            # Negative values are represented as follows: +            #  (3) -$2.55 +            #  (4) ($2.55) + +            value.sub!(/^\((.+)\)$/, '-\1') # (4) +            case value +            when /^-?\D+[\d,]+\.\d{2}$/  # (1) +              value.gsub!(/[^-\d.]/, '') +            when /^-?\D+[\d.]+,\d{2}$/  # (2) +              value.gsub!(/[^-\d,]/, '').sub!(/,/, '.') +            end + +            super(value) +          end +        end +      end +    end +  end +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 new file mode 100644 index 0000000000..f9531ddee3 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb @@ -0,0 +1,20 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Point < Type::String +          def type_cast(value) +            if ::String === value +              if value[0] == '(' && value[-1] == ')' +                value = value[1...-1] +              end +              value.split(',').map{ |v| Float(v) } +            else +              value +            end +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb new file mode 100644 index 0000000000..c2262c1599 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -0,0 +1,56 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Range < Type::Value +          attr_reader :subtype, :type + +          def initialize(subtype, type) +            @subtype = subtype +            @type = type +          end + +          def extract_bounds(value) +            from, to = value[1..-2].split(',') +            { +              from:          (value[1] == ',' || from == '-infinity') ? @subtype.infinity(negative: true) : from, +              to:            (value[-2] == ',' || to == 'infinity') ? @subtype.infinity : to, +              exclude_start: (value[0] == '('), +              exclude_end:   (value[-1] == ')') +            } +          end + +          def infinity?(value) +            value.respond_to?(:infinite?) && value.infinite? +          end + +          def type_cast_single(value) +            infinity?(value) ? value : @subtype.type_cast(value) +          end + +          def cast_value(value) +            return if value == 'empty' +            return value if value.is_a?(::Range) + +            extracted = extract_bounds(value) +            from = type_cast_single extracted[:from] +            to = type_cast_single extracted[:to] + +            if !infinity?(from) && extracted[:exclude_start] +              if from.respond_to?(:succ) +                from = from.succ +                ActiveSupport::Deprecation.warn <<-MESSAGE +Excluding the beginning of a Range is only partialy supported through `#succ`. +This is not reliable and will be removed in the future. +                MESSAGE +              else +                raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')" +              end +            end +            ::Range.new(from, to, extracted[:exclude_end]) +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb new file mode 100644 index 0000000000..7b1ca16bc4 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb @@ -0,0 +1,19 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class SpecializedString < Type::String +          attr_reader :type + +          def initialize(type) +            @type = type +          end + +          def text? +            false +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb new file mode 100644 index 0000000000..ea1f599b0f --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb @@ -0,0 +1,11 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Time < Type::Time +          include Infinity +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb new file mode 100644 index 0000000000..28f7a4eafb --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb @@ -0,0 +1,85 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        # This class uses the data from PostgreSQL pg_type table to build +        # the OID -> Type mapping. +        #   - OID is and integer representing the type. +        #   - Type is an OID::Type object. +        # This class has side effects on the +store+ passed during initialization. +        class TypeMapInitializer # :nodoc: +          def initialize(store) +            @store = store +          end + +          def run(records) +            nodes = records.reject { |row| @store.key? row['oid'].to_i } +            mapped, nodes = nodes.partition { |row| @store.key? row['typname'] } +            ranges, nodes = nodes.partition { |row| row['typtype'] == 'r' } +            enums, nodes = nodes.partition { |row| row['typtype'] == 'e' } +            domains, nodes = nodes.partition { |row| row['typtype'] == 'd' } +            arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' } +            composites, nodes = nodes.partition { |row| row['typelem'] != '0' } + +            mapped.each     { |row| register_mapped_type(row)    } +            enums.each      { |row| register_enum_type(row)      } +            domains.each    { |row| register_domain_type(row)    } +            arrays.each     { |row| register_array_type(row)     } +            ranges.each     { |row| register_range_type(row)     } +            composites.each { |row| register_composite_type(row) } +          end + +          private +          def register_mapped_type(row) +            alias_type row['oid'], row['typname'] +          end + +          def register_enum_type(row) +            register row['oid'], OID::Enum.new +          end + +          def register_array_type(row) +            if subtype = @store.lookup(row['typelem'].to_i) +              register row['oid'], OID::Array.new(subtype) +            end +          end + +          def register_range_type(row) +            if subtype = @store.lookup(row['rngsubtype'].to_i) +              register row['oid'], OID::Range.new(subtype, row['typname'].to_sym) +            end +          end + +          def register_domain_type(row) +            if base_type = @store.lookup(row["typbasetype"].to_i) +              register row['oid'], base_type +            else +              warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}." +            end +          end + +          def register_composite_type(row) +            if subtype = @store.lookup(row['typelem'].to_i) +              register row['oid'], OID::Vector.new(row['typdelim'], subtype) +            end +          end + +          def register(oid, oid_type) +            oid = assert_valid_registration(oid, oid_type) +            @store.register_type(oid, oid_type) +          end + +          def alias_type(oid, target) +            oid = assert_valid_registration(oid, target) +            @store.alias_type(oid, target) +          end + +          def assert_valid_registration(oid, oid_type) +            raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil? +            oid.to_i +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb new file mode 100644 index 0000000000..0ed5491887 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb @@ -0,0 +1,17 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Uuid < Type::Value +          def type +            :uuid +          end + +          def type_cast(value) +            value.presence +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb new file mode 100644 index 0000000000..2f7d1be197 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb @@ -0,0 +1,26 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module OID # :nodoc: +        class Vector < Type::Value +          attr_reader :delim, :subtype + +          # +delim+ corresponds to the `typdelim` column in the pg_types +          # table.  +subtype+ is derived from the `typelem` column in the +          # pg_types table. +          def initialize(delim, subtype) +            @delim   = delim +            @subtype = subtype +          end + +          # FIXME: this should probably split on +delim+ and use +subtype+ +          # to cast the values.  Unfortunately, the current Rails behavior +          # is to just return the string. +          def type_cast(value) +            value +          end +        end +      end +    end +  end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 0883b02a35..ad12298013 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -150,13 +150,11 @@ module ActiveRecord          # - "schema.name".table_name          # - "schema.name"."table.name"          def quote_table_name(name) -          schema, name_part = extract_pg_identifier_from_name(name.to_s) - -          unless name_part -            quote_column_name(schema) +          schema, table = Utils.extract_schema_and_table(name.to_s) +          if schema +            "#{quote_column_name(schema)}.#{quote_column_name(table)}"            else -            table_name, name_part = extract_pg_identifier_from_name(name_part) -            "#{quote_column_name(schema)}.#{quote_column_name(table_name)}" +            quote_column_name(table)            end          end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb index 98dcf441ff..52b307c432 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb @@ -1,12 +1,12 @@  module ActiveRecord    module ConnectionAdapters      module PostgreSQL -      module ReferentialIntegrity -        def supports_disable_referential_integrity? #:nodoc: +      module ReferentialIntegrity # :nodoc: +        def supports_disable_referential_integrity? # :nodoc:            true          end -        def disable_referential_integrity #:nodoc: +        def disable_referential_integrity # :nodoc:            if supports_disable_referential_integrity?              begin                execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb new file mode 100644 index 0000000000..bcfd605165 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -0,0 +1,134 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      module ColumnMethods +        def xml(*args) +          options = args.extract_options! +          column(args[0], 'xml', options) +        end + +        def tsvector(*args) +          options = args.extract_options! +          column(args[0], 'tsvector', options) +        end + +        def int4range(name, options = {}) +          column(name, 'int4range', options) +        end + +        def int8range(name, options = {}) +          column(name, 'int8range', options) +        end + +        def tsrange(name, options = {}) +          column(name, 'tsrange', options) +        end + +        def tstzrange(name, options = {}) +          column(name, 'tstzrange', options) +        end + +        def numrange(name, options = {}) +          column(name, 'numrange', options) +        end + +        def daterange(name, options = {}) +          column(name, 'daterange', options) +        end + +        def hstore(name, options = {}) +          column(name, 'hstore', options) +        end + +        def ltree(name, options = {}) +          column(name, 'ltree', options) +        end + +        def inet(name, options = {}) +          column(name, 'inet', options) +        end + +        def cidr(name, options = {}) +          column(name, 'cidr', options) +        end + +        def macaddr(name, options = {}) +          column(name, 'macaddr', options) +        end + +        def uuid(name, options = {}) +          column(name, 'uuid', options) +        end + +        def json(name, options = {}) +          column(name, 'json', options) +        end + +        def citext(name, options = {}) +          column(name, 'citext', options) +        end +      end + +      class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition +        attr_accessor :array +      end + +      class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition +        include ColumnMethods + +        # Defines the primary key field. +        # Use of the native PostgreSQL UUID type is supported, and can be used +        # by defining your tables as such: +        # +        #   create_table :stuffs, id: :uuid do |t| +        #     t.string :content +        #     t.timestamps +        #   end +        # +        # By default, this will use the +uuid_generate_v4()+ function from the +        # +uuid-ossp+ extension, which MUST be enabled on your database. To enable +        # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your +        # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can +        # set the +:default+ option to +nil+: +        # +        #   create_table :stuffs, id: false do |t| +        #     t.primary_key :id, :uuid, default: nil +        #     t.uuid :foo_id +        #     t.timestamps +        #   end +        # +        # You may also pass a different UUID generation function from +uuid-ossp+ +        # or another library. +        # +        # Note that setting the UUID primary key default value to +nil+ will +        # require you to assure that you always provide a UUID value before saving +        # a record (as primary keys cannot be +nil+). This might be done via the +        # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. +        def primary_key(name, type = :primary_key, options = {}) +          return super unless type == :uuid +          options[:default] = options.fetch(:default, 'uuid_generate_v4()') +          options[:primary_key] = true +          column name, type, options +        end + +        def column(name, type = nil, options = {}) +          super +          column = self[name] +          column.array = options[:array] + +          self +        end + +        private + +          def create_column_definition(name, type) +            PostgreSQL::ColumnDefinition.new name, type +          end +      end + +      class Table < ActiveRecord::ConnectionAdapters::Table +        include ColumnMethods +      end +    end +  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 dd983562fb..484c44dc8d 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,7 +97,7 @@ 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 = extract_schema_and_table(name.to_s) +          schema, table = Utils.extract_schema_and_table(name.to_s)            return false unless table            exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 @@ -178,8 +178,10 @@ module ActiveRecord          def columns(table_name)            # 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) -            PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f') +            oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type) +            default_value = extract_value_from_default(default) +            default_function = extract_default_function(default_value, default) +            PostgreSQLColumn.new(column_name, default_value, oid, type, notnull == 'f', default_function)            end          end @@ -488,23 +490,6 @@ module ActiveRecord            [super, *order_columns].join(', ')          end - -        private - -        # 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: -        # -        # * <tt>table_name</tt> -        # * <tt>"table.name"</tt> -        # * <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] -        end        end      end    end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb new file mode 100644 index 0000000000..60ffd3a114 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb @@ -0,0 +1,25 @@ +module ActiveRecord +  module ConnectionAdapters +    module PostgreSQL +      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: +        # +        # * <tt>table_name</tt> +        # * <tt>"table.name"</tt> +        # * <tt>schema_name.table_name</tt> +        # * <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] +        end +      end +    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 23b91be0f3..027169ae3c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1,10 +1,12 @@  require 'active_record/connection_adapters/abstract_adapter'  require 'active_record/connection_adapters/statement_pool' +require 'active_record/connection_adapters/postgresql/utils'  require 'active_record/connection_adapters/postgresql/column'  require 'active_record/connection_adapters/postgresql/oid'  require 'active_record/connection_adapters/postgresql/quoting'  require 'active_record/connection_adapters/postgresql/referential_integrity' +require 'active_record/connection_adapters/postgresql/schema_definitions'  require 'active_record/connection_adapters/postgresql/schema_statements'  require 'active_record/connection_adapters/postgresql/database_statements' @@ -72,139 +74,6 @@ module ActiveRecord      # In addition, default connection parameters of libpq can be set per environment variables.      # See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .      class PostgreSQLAdapter < AbstractAdapter -      class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition -        attr_accessor :array -      end - -      module ColumnMethods -        def xml(*args) -          options = args.extract_options! -          column(args[0], 'xml', options) -        end - -        def tsvector(*args) -          options = args.extract_options! -          column(args[0], 'tsvector', options) -        end - -        def int4range(name, options = {}) -          column(name, 'int4range', options) -        end - -        def int8range(name, options = {}) -          column(name, 'int8range', options) -        end - -        def tsrange(name, options = {}) -          column(name, 'tsrange', options) -        end - -        def tstzrange(name, options = {}) -          column(name, 'tstzrange', options) -        end - -        def numrange(name, options = {}) -          column(name, 'numrange', options) -        end - -        def daterange(name, options = {}) -          column(name, 'daterange', options) -        end - -        def hstore(name, options = {}) -          column(name, 'hstore', options) -        end - -        def ltree(name, options = {}) -          column(name, 'ltree', options) -        end - -        def inet(name, options = {}) -          column(name, 'inet', options) -        end - -        def cidr(name, options = {}) -          column(name, 'cidr', options) -        end - -        def macaddr(name, options = {}) -          column(name, 'macaddr', options) -        end - -        def uuid(name, options = {}) -          column(name, 'uuid', options) -        end - -        def json(name, options = {}) -          column(name, 'json', options) -        end - -        def citext(name, options = {}) -          column(name, 'citext', options) -        end -      end - -      class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition -        include ColumnMethods - -        # Defines the primary key field. -        # Use of the native PostgreSQL UUID type is supported, and can be used -        # by defining your tables as such: -        # -        #   create_table :stuffs, id: :uuid do |t| -        #     t.string :content -        #     t.timestamps -        #   end -        # -        # By default, this will use the +uuid_generate_v4()+ function from the -        # +uuid-ossp+ extension, which MUST be enabled on your database. To enable -        # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your -        # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can -        # set the +:default+ option to +nil+: -        # -        #   create_table :stuffs, id: false do |t| -        #     t.primary_key :id, :uuid, default: nil -        #     t.uuid :foo_id -        #     t.timestamps -        #   end -        # -        # You may also pass a different UUID generation function from +uuid-ossp+ -        # or another library. -        # -        # Note that setting the UUID primary key default value to +nil+ will -        # require you to assure that you always provide a UUID value before saving -        # a record (as primary keys cannot be +nil+). This might be done via the -        # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. -        def primary_key(name, type = :primary_key, options = {}) -          return super unless type == :uuid -          options[:default] = options.fetch(:default, 'uuid_generate_v4()') -          options[:primary_key] = true -          column name, type, options -        end - -        def citext(name, options = {}) -          column(name, 'citext', options) -        end - -        def column(name, type = nil, options = {}) -          super -          column = self[name] -          column.array = options[:array] - -          self -        end - -        private - -          def create_column_definition(name, type) -            ColumnDefinition.new name, type -          end -      end - -      class Table < ActiveRecord::ConnectionAdapters::Table -        include ColumnMethods -      end -        ADAPTER_NAME = 'PostgreSQL'        NATIVE_DATABASE_TYPES = { @@ -215,7 +84,6 @@ module ActiveRecord          float:       { name: "float" },          decimal:     { name: "decimal" },          datetime:    { name: "timestamp" }, -        timestamp:   { name: "timestamp" },          time:        { name: "time" },          date:        { name: "date" },          daterange:   { name: "daterange" }, @@ -251,13 +119,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 @@ -369,7 +237,7 @@ module ActiveRecord            raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"          end -        @type_map = OID::TypeMap.new +        @type_map = Type::HashLookupTypeMap.new          initialize_type_map(type_map)          @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]          @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true @@ -509,7 +377,7 @@ module ActiveRecord        end        def update_table_definition(table_name, base) #:nodoc: -        Table.new(table_name, base) +        PostgreSQL::Table.new(table_name, base)        end        protected @@ -538,27 +406,169 @@ module ActiveRecord        private -        def type_map -          @type_map -        end - -        def get_oid_type(oid, fmod, column_name) +        def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:            if !type_map.key?(oid) -            initialize_type_map(type_map, [oid]) +            load_additional_types(type_map, [oid])            end -          type_map.fetch(oid, fmod) { +          type_map.fetch(oid, fmod, sql_type) {              warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String." -            type_map[oid] = OID::Identity.new +            Type::Value.new.tap do |cast_type| +              type_map.register_type(oid, cast_type) +            end            }          end -        def reload_type_map -          type_map.clear -          initialize_type_map(type_map) +        def initialize_type_map(m) # :nodoc: +          register_class_with_limit m, 'int2', OID::Integer +          m.alias_type 'int4', 'int2' +          m.alias_type 'int8', 'int2' +          m.alias_type 'oid', 'int2' +          m.register_type 'float4', OID::Float.new +          m.alias_type 'float8', 'float4' +          m.register_type 'text', Type::Text.new +          register_class_with_limit m, 'varchar', Type::String +          m.alias_type 'char', 'varchar' +          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' +          m.alias_type 'timestamptz', 'timestamp' +          m.register_type 'date', OID::Date.new +          m.register_type 'time', OID::Time.new + +          m.register_type 'money', OID::Money.new +          m.register_type 'bytea', OID::Bytea.new +          m.register_type 'point', OID::Point.new +          m.register_type 'hstore', OID::Hstore.new +          m.register_type 'json', OID::Json.new +          m.register_type 'cidr', OID::Cidr.new +          m.register_type 'inet', OID::Inet.new +          m.register_type 'uuid', OID::Uuid.new +          m.register_type 'xml', OID::SpecializedString.new(:xml) +          m.register_type 'tsvector', OID::SpecializedString.new(:tsvector) +          m.register_type 'macaddr', OID::SpecializedString.new(:macaddr) +          m.register_type 'citext', OID::SpecializedString.new(:citext) +          m.register_type 'ltree', OID::SpecializedString.new(:ltree) + +          # FIXME: why are we keeping these types as strings? +          m.alias_type 'interval', 'varchar' +          m.alias_type 'path', 'varchar' +          m.alias_type 'line', 'varchar' +          m.alias_type 'polygon', 'varchar' +          m.alias_type 'circle', 'varchar' +          m.alias_type 'lseg', 'varchar' +          m.alias_type 'box', 'varchar' + +          m.register_type 'timestamp' do |_, _, sql_type| +            precision = extract_precision(sql_type) +            OID::DateTime.new(precision: precision) +          end + +          m.register_type 'numeric' do |_, fmod, sql_type| +            precision = extract_precision(sql_type) +            scale = extract_scale(sql_type) + +            # The type for the numeric depends on the width of the field, +            # so we'll do something special here. +            # +            # When dealing with decimal columns: +            # +            # 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) +            end +          end + +          load_additional_types(m)          end -        def initialize_type_map(type_map, oids = nil) +        def extract_limit(sql_type) # :nodoc: +          case sql_type +          when /^bigint/i;    8 +          when /^smallint/i;  2 +          else super +          end +        end + +        # Extracts the value from a PostgreSQL column default definition. +        def extract_value_from_default(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, +          # Regexp#=== will call NilClass#to_str, which will trigger +          # method_missing (defined by whiny nil in ActiveSupport) which +          # makes this method very very slow. +          return default unless default + +          case default +            when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m +              $1 +            # Numeric types +            when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/ +              $1 +            # Character types +            when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m +              $1.gsub(/''/, "'") +            # Binary data types +            when /\A'(.*)'::bytea\z/m +              $1 +            # Date/time types +            when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/ +              $1 +            when /\A'(.*)'::interval\z/ +              $1 +            # Boolean type +            when 'true' +              true +            when 'false' +              false +            # Geometric types +            when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/ +              $1 +            # Network address types +            when /\A'(.*)'::(?:cidr|inet|macaddr)\z/ +              $1 +            # Bit string types +            when /\AB'(.*)'::"?bit(?: varying)?"?\z/ +              $1 +            # XML type +            when /\A'(.*)'::xml\z/m +              $1 +            # Arrays +            when /\A'(.*)'::"?\D+"?\[\]\z/ +              $1 +            # Hstore +            when /\A'(.*)'::hstore\z/ +              $1 +            # JSON +            when /\A'(.*)'::json\z/ +              $1 +            # Object identifier types +            when /\A-?\d+\z/ +              $1 +            else +              # Anything else is blank, some user type, or some function +              # and we can't know the value of that, so return nil. +              nil +          end +        end + +        def extract_default_function(default_value, default) # :nodoc: +          default if has_default_function?(default_value, default) +        end + +        def has_default_function?(default_value, default) # :nodoc: +          !default_value && (%r{\w+\(.*\)} === default) +        end + +        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 @@ -658,7 +668,7 @@ module ActiveRecord            # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of            # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision            # should know about this but can't detect it there, so deal with it here. -          PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10 +          OID::Money.precision = (postgresql_version >= 80300) ? 19 : 10            configure_connection          rescue ::PG::Error => error @@ -740,7 +750,7 @@ module ActiveRecord          # Query implementation notes:          #  - format_type includes the column size constraint, e.g. varchar(50)          #  - ::regclass is a function that gives the id for a table name -        def column_definitions(table_name) #:nodoc: +        def column_definitions(table_name) # :nodoc:            exec_query(<<-end_sql, 'SCHEMA').rows                SELECT a.attname, format_type(a.atttypid, a.atttypmod),                       pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod @@ -752,23 +762,13 @@ module ActiveRecord            end_sql          end -        def extract_pg_identifier_from_name(name) -          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) +        def extract_table_ref_from_insert_sql(sql) # :nodoc:            sql[/into\s+([^\(]*).*values\s*\(/im]            $1.strip if $1          end -        def create_table_definition(name, temporary, options, as = nil) -          TableDefinition.new native_database_types, name, temporary, options, as +        def create_table_definition(name, temporary, options, as = nil) # :nodoc: +          PostgreSQL::TableDefinition.new native_database_types, name, temporary, options, as          end      end    end diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index e5c9f6f54a..4d8afcf16a 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -12,11 +12,10 @@ module ActiveRecord          @columns_hash = {}          @primary_keys = {}          @tables       = {} -        prepare_default_proc        end        def primary_keys(table_name) -        @primary_keys[table_name] +        @primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil        end        # A cached lookup for table existence. @@ -29,9 +28,9 @@ module ActiveRecord        # Add internal cache for table with +table_name+.        def add(table_name)          if table_exists?(table_name) -          @primary_keys[table_name] -          @columns[table_name] -          @columns_hash[table_name] +          primary_keys(table_name) +          columns(table_name) +          columns_hash(table_name)          end        end @@ -40,14 +39,16 @@ module ActiveRecord        end        # Get the columns for a table -      def columns(table) -        @columns[table] +      def columns(table_name) +        @columns[table_name] ||= connection.columns(table_name)        end        # Get the columns for a table as a hash, key is the column name        # value is the column object. -      def columns_hash(table) -        @columns_hash[table] +      def columns_hash(table_name) +        @columns_hash[table_name] ||= Hash[columns(table_name).map { |col| +          [col.name, col] +        }]        end        # Clears out internal caches @@ -76,32 +77,11 @@ module ActiveRecord        def marshal_dump          # if we get current version during initialization, it happens stack over flow.          @version = ActiveRecord::Migrator.current_version -        [@version] + [@columns, @columns_hash, @primary_keys, @tables].map { |val| -          Hash[val] -        } +        [@version, @columns, @columns_hash, @primary_keys, @tables]        end        def marshal_load(array)          @version, @columns, @columns_hash, @primary_keys, @tables = array -        prepare_default_proc -      end - -      private - -      def prepare_default_proc -        @columns.default_proc = Proc.new do |h, table_name| -          h[table_name] = connection.columns(table_name) -        end - -        @columns_hash.default_proc = Proc.new do |h, table_name| -          h[table_name] = Hash[columns(table_name).map { |col| -            [col.name, col] -          }] -        end - -        @primary_keys.default_proc = Proc.new do |h, table_name| -          h[table_name] = table_exists?(table_name) ? connection.primary_key(table_name) : nil -        end        end      end    end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 737f2daa63..a5e2619cb8 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -41,14 +41,12 @@ module ActiveRecord    end    module ConnectionAdapters #:nodoc: -    class SQLite3Column < Column #:nodoc: -      class <<  self -        def binary_to_string(value) -          if value.encoding != Encoding::ASCII_8BIT -            value = value.force_encoding(Encoding::ASCII_8BIT) -          end -          value +    class SQLite3Binary < Type::Binary # :nodoc: +      def cast_value(value) +        if value.encoding != Encoding::ASCII_8BIT +          value = value.force_encoding(Encoding::ASCII_8BIT)          end +        value        end      end @@ -69,7 +67,6 @@ module ActiveRecord          float:        { name: "float" },          decimal:      { name: "decimal" },          datetime:     { name: "datetime" }, -        timestamp:    { name: "datetime" },          time:         { name: "time" },          date:         { name: "date" },          binary:       { name: "blob" }, @@ -394,7 +391,9 @@ module ActiveRecord              field["dflt_value"] = $1.gsub('""', '"')            end -          SQLite3Column.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0) +          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)          end        end @@ -501,6 +500,12 @@ module ActiveRecord        end        protected + +        def initialize_type_map(m) +          super +          m.register_type(/binary/i, SQLite3Binary.new) +        end +          def select(sql, name = nil, binds = []) #:nodoc:            exec_query(sql, name, binds)          end diff --git a/activerecord/lib/active_record/connection_adapters/type.rb b/activerecord/lib/active_record/connection_adapters/type.rb new file mode 100644 index 0000000000..bab7a3ff7e --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type.rb @@ -0,0 +1,25 @@ +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 new file mode 100644 index 0000000000..60afe44de1 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/binary.rb @@ -0,0 +1,19 @@ +module ActiveRecord +  module ConnectionAdapters +    module Type +      class Binary < Value +        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 new file mode 100644 index 0000000000..0d97379189 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/boolean.rb @@ -0,0 +1,21 @@ +module ActiveRecord +  module ConnectionAdapters +    module Type +      class Boolean < Value +        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 new file mode 100644 index 0000000000..e8becbe1f4 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/date.rb @@ -0,0 +1,44 @@ +module ActiveRecord +  module ConnectionAdapters +    module Type +      class Date < Value +        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 new file mode 100644 index 0000000000..64f5d05301 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/date_time.rb @@ -0,0 +1,35 @@ +module ActiveRecord +  module ConnectionAdapters +    module Type +      class DateTime < Value +        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 new file mode 100644 index 0000000000..e93906ba19 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/decimal.rb @@ -0,0 +1,27 @@ +module ActiveRecord +  module ConnectionAdapters +    module Type +      class Decimal < Value +        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 new file mode 100644 index 0000000000..e58c6e198d --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/decimal_without_scale.rb @@ -0,0 +1,13 @@ +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 new file mode 100644 index 0000000000..f2427d2dfa --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/float.rb @@ -0,0 +1,25 @@ +module ActiveRecord +  module ConnectionAdapters +    module Type +      class Float < Value +        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 +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 new file mode 100644 index 0000000000..bb1abc77ff --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/hash_lookup_type_map.rb @@ -0,0 +1,21 @@ +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 new file mode 100644 index 0000000000..596f4de2a8 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/integer.rb @@ -0,0 +1,29 @@ +module ActiveRecord +  module ConnectionAdapters +    module Type +      class Integer < Value +        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 +end diff --git a/activerecord/lib/active_record/connection_adapters/type/numeric.rb b/activerecord/lib/active_record/connection_adapters/type/numeric.rb new file mode 100644 index 0000000000..a3379831cb --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/numeric.rb @@ -0,0 +1,20 @@ +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 new file mode 100644 index 0000000000..471f949e09 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/string.rb @@ -0,0 +1,29 @@ +module ActiveRecord +  module ConnectionAdapters +    module Type +      class String < Value +        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 new file mode 100644 index 0000000000..61095ebb38 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/text.rb @@ -0,0 +1,13 @@ +require 'active_record/connection_adapters/type/string' + +module ActiveRecord +  module ConnectionAdapters +    module Type +      class Text < String +        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 new file mode 100644 index 0000000000..bc331b0fa7 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/time.rb @@ -0,0 +1,28 @@ +module ActiveRecord +  module ConnectionAdapters +    module Type +      class Time < Value +        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 new file mode 100644 index 0000000000..e9ca4adeda --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/time_value.rb @@ -0,0 +1,36 @@ +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 new file mode 100644 index 0000000000..48b8b51417 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/type_map.rb @@ -0,0 +1,50 @@ +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 new file mode 100644 index 0000000000..60b443004c --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/value.rb @@ -0,0 +1,61 @@ +module ActiveRecord +  module ConnectionAdapters +    module Type +      class Value +        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 that this object represents. Subclasses +        # should 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_write(value) +          value +        end + +        def type_cast_for_database(value) +          type_cast_for_write(value) +        end + +        def text? +          false +        end + +        def number? +          false +        end + +        def binary? +          false +        end + +        def klass +          ::Object +        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 +end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 4571cc0786..07eafef788 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -286,6 +286,8 @@ module ActiveRecord        @new_record = false +      self.class.define_attribute_methods +        run_callbacks :find        run_callbacks :initialize diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index b7b790322a..05c4b13016 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -11,7 +11,7 @@ module ActiveRecord        # ==== Parameters        #        # * +id+ - The id of the object you wish to reset a counter on. -      # * +counters+ - One or more association counters to reset +      # * +counters+ - One or more association counters to reset. Association name or counter name can be given.        #        # ==== Examples        # @@ -19,9 +19,14 @@ module ActiveRecord        #   Post.reset_counters(1, :comments)        def reset_counters(id, *counters)          object = find(id) -        counters.each do |association| -          has_many_association = reflect_on_association(association.to_sym) -          raise ArgumentError, "'#{self.name}' has no association called '#{association}'" unless has_many_association +        counters.each do |counter_association| +          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 } +            counter_association = has_many_association.plural_name if has_many_association +          end +          raise ArgumentError, "'#{self.name}' has no association called '#{counter_association}'" unless has_many_association            if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection              has_many_association = has_many_association.through_reflection @@ -29,12 +34,11 @@ 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({ -            arel_table[counter_name] => object.send(association).count +            arel_table[counter_name] => object.send(counter_association).count            }, primary_key)            connection.update stmt          end @@ -162,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/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 47d32fae05..d40bea5ea7 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -649,7 +649,7 @@ 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 diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index b6b02322d7..8fe32bcb6c 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -195,7 +195,7 @@ module ActiveRecord    # == Database support    #    # Migrations are currently supported in MySQL, PostgreSQL, SQLite, -  # SQL Server, Sybase, and Oracle (all supported databases except DB2). +  # SQL Server, and Oracle (all supported databases except DB2).    #    # == More examples    # @@ -640,7 +640,7 @@ module ActiveRecord        say_with_time "#{method}(#{arg_list})" do          unless @connection.respond_to? :revert -          unless arguments.empty? || method == :execute +          unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method)              arguments[0] = proper_table_name(arguments.first, table_name_options)              arguments[1] = proper_table_name(arguments.second, table_name_options) if method == :rename_table            end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 002bd16976..a4e10ed2e7 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -198,7 +198,7 @@ module ActiveRecord        # given block. This is required for Oracle and is useful for any        # database which relies on sequences for primary key generation.        # -      # If a sequence name is not explicitly set when using Oracle or Firebird, +      # If a sequence name is not explicitly set when using Oracle,        # it will default to the commonly used pattern of: #{table_name}_seq        #        # If a sequence name is not explicitly set when using PostgreSQL, it @@ -217,20 +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 -        @columns ||= connection.schema_cache.columns(table_name).map do |col| -          col = col.dup -          col.primary = (col.name == primary_key) -          col -        end -      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 column_types # :nodoc:          @column_types ||= decorate_columns(columns_hash.dup)        end @@ -271,7 +257,7 @@ module ActiveRecord        # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",        # and columns used for single table inheritance have been removed.        def content_columns -        @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column } +        @content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }        end        # Resets all the cached information about columns, which will cause them @@ -308,8 +294,6 @@ module ActiveRecord          @arel_engine             = nil          @column_defaults         = nil          @column_names            = nil -        @columns                 = nil -        @columns_hash            = nil          @column_types            = nil          @content_columns         = nil          @dynamic_methods_hash    = nil diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index e6195e48a5..7dc7169a02 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::Column.value_to_boolean(hash['_destroy']) +      ConnectionAdapters::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/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index 05d0c41678..807c301596 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -23,7 +23,7 @@ module ActiveRecord      end      def size -      0 +      calculate :size, nil      end      def empty? @@ -47,14 +47,28 @@ module ActiveRecord      end      def sum(*) -      0 +      calculate :sum, nil +    end + +    def average(*) +      calculate :average, nil +    end + +    def minimum(*) +      calculate :minimum, nil +    end + +    def maximum(*) +      calculate :maximum, nil      end      def calculate(operation, _column_name, _options = {})        # TODO: Remove _options argument as soon we remove support to        # activerecord-deprecated_finders. -      if operation == :count +      if [:count, :sum, :size].include? operation          group_values.any? ? Hash.new : 0 +      elsif [:average, :minimum, :maximum].include?(operation) && group_values.any? +        Hash.new        else          nil        end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 13d7432773..b74e340b3e 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -37,7 +37,7 @@ module ActiveRecord        end        # Given an attributes hash, +instantiate+ returns a new instance of -      # the appropriate class. +      # the appropriate class. Accepts only keys as strings.        #        # For example, +Post.all+ may return Comments, Messages, and Emails        # by storing the record's subclass in a +type+ attribute. By calling @@ -46,10 +46,10 @@ module ActiveRecord        #        # See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see        # how this "single-table" inheritance mapping is implemented. -      def instantiate(record, column_types = {}) -        klass = discriminate_class_for_record(record) +      def instantiate(attributes, column_types = {}) +        klass = discriminate_class_for_record(attributes)          column_types = klass.decorate_columns(column_types.dup) -        klass.allocate.init_with('attributes' => record, 'column_types' => column_types) +        klass.allocate.init_with('attributes' => attributes, 'column_types' => column_types)        end        private diff --git a/activerecord/lib/active_record/properties.rb b/activerecord/lib/active_record/properties.rb new file mode 100644 index 0000000000..39c39ad9ff --- /dev/null +++ b/activerecord/lib/active_record/properties.rb @@ -0,0 +1,107 @@ +module ActiveRecord +  module Properties +    extend ActiveSupport::Concern + +    Type = ConnectionAdapters::Type + +    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. +      # +      # ==== 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) +        name = name.to_s +        user_provided_columns[name] = ConnectionAdapters::Column.new(name, nil, 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)).each do |column| +          if Type::DecimalWithoutScale === column.cast_type +            ActiveSupport::Deprecation.warn <<-MESSAGE.strip_heredoc +              Decimal columns with 0 scale being automatically treated as integers +              is deprecated, and will be removed in a future version of Rails. If +              you'd like to keep this behavior, add + +                property :#{column.name}, Type::Integer.new + +              to your #{name} model. +            MESSAGE +          end +        end +      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 + +        @columns = nil +        @columns_hash = nil +      end + +      private + +      def user_provided_columns +        @user_provided_columns ||= {} +      end + +      def add_user_provided_columns(schema_columns) +        schema_columns.reject { |column| +          user_provided_columns.key? column.name +        } + user_provided_columns.values +      end +    end +  end +end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 0eec6774a0..dd80ec6274 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" @@ -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) @@ -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 diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 24b33ab0a8..d92ff781ee 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -12,6 +12,7 @@ module ActiveRecord      SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,                              :reverse_order, :distinct, :create_with, :uniq] +    INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having]      VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS @@ -430,12 +431,21 @@ module ActiveRecord      # If you need to destroy dependent associations or call your <tt>before_*</tt> or      # +after_destroy+ callbacks, use the +destroy_all+ method instead.      # -    # If a limit scope is supplied, +delete_all+ raises an ActiveRecord error: +    # If an invalid method is supplied, +delete_all+ raises an ActiveRecord error:      #      #   Post.limit(100).delete_all -    #   # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit scope +    #   # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit      def delete_all(conditions = nil) -      raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value +      invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method| +        if MULTI_VALUE_METHODS.include?(method) +          send("#{method}_values").any? +        else +          send("#{method}_value") +        end +      } +      if invalid_methods.any? +        raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}") +      end        if conditions          where(conditions).delete_all diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 56cf9bcd27..d155517b18 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -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/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/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 416f2305d2..1262b2c291 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -573,15 +573,11 @@ WARNING        end      end -    def where!(opts = :chain, *rest) # :nodoc: -      if opts == :chain -        WhereChain.new(self) -      else -        references!(PredicateBuilder.references(opts)) if Hash === opts +    def where!(opts, *rest) # :nodoc: +      references!(PredicateBuilder.references(opts)) if Hash === opts -        self.where_values += build_where(opts, rest) -        self -      end +      self.where_values += build_where(opts, rest) +      self      end      # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition. @@ -950,7 +946,6 @@ WARNING          [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]        when Hash          opts = PredicateBuilder.resolve_column_aliases(klass, opts) -        attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)          bv_len = bind_values.length          tmp_opts, bind_values = create_binds(opts, bv_len) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 9f4532b316..17d1ae1ba0 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -1,5 +1,3 @@ -require 'thread' -  module ActiveRecord    # See ActiveRecord::Transactions::ClassMethods for documentation.    module Transactions @@ -358,7 +356,7 @@ module ActiveRecord        force_clear_transaction_record_state if @_start_transaction_state[:level] < 1      end -    # Force to clear the teansaction record state. +    # Force to clear the transaction record state.      def force_clear_transaction_record_state #:nodoc:        @_start_transaction_state.clear      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..b6fccc9b94 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -47,7 +47,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 +74,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 diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb index 59324c4857..64cde143a1 100644 --- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb +++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb @@ -29,6 +29,7 @@ module ActiveRecord          @columns[table_name] << ActiveRecord::ConnectionAdapters::Column.new(            name.to_s,            options[:default], +          lookup_cast_type(sql_type.to_s),            sql_type.to_s,            options[:null])        end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 90953ce6cd..778c4ed7e5 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -46,9 +46,7 @@ module ActiveRecord          @connection.add_index :accounts, :firm_id, :name => idx_name          indexes = @connection.indexes("accounts")          assert_equal "accounts", indexes.first.table -        # OpenBase does not have the concept of a named index -        # Indexes are merely properties of columns. -        assert_equal idx_name, indexes.first.name unless current_adapter?(:OpenBaseAdapter) +        assert_equal idx_name, indexes.first.name          assert !indexes.first.unique          assert_equal ["firm_id"], indexes.first.columns        else @@ -127,14 +125,12 @@ module ActiveRecord          assert_equal 1, Movie.create(:name => 'fight club').id        end -      if ActiveRecord::Base.connection.adapter_name != "FrontBase" -        def test_reset_table_with_non_integer_pk -          Subscriber.delete_all -          Subscriber.connection.reset_pk_sequence! 'subscribers' -          sub = Subscriber.new(:name => 'robert drake') -          sub.id = 'bob drake' -          assert_nothing_raised { sub.save! } -        end +      def test_reset_table_with_non_integer_pk +        Subscriber.delete_all +        Subscriber.connection.reset_pk_sequence! 'subscribers' +        sub = Subscriber.new(:name => 'robert drake') +        sub.id = 'bob drake' +        assert_nothing_raised { sub.save! }        end      end @@ -144,7 +140,7 @@ module ActiveRecord          @connection.execute "INSERT INTO subscribers(nick) VALUES('me')"        end      end -     +      unless current_adapter?(:SQLite3Adapter)        def test_foreign_key_violations_are_translated_to_specific_exception          assert_raises(ActiveRecord::InvalidForeignKey) do @@ -157,7 +153,7 @@ module ActiveRecord            end          end        end -  +        def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false          klass_has_fk = Class.new(ActiveRecord::Base) do            self.table_name = 'fk_test_has_fk' diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb index 3d1330efb8..d8a954efa8 100644 --- a/activerecord/test/cases/adapters/mysql/quoting_test.rb +++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb @@ -9,13 +9,13 @@ module ActiveRecord          end          def test_type_cast_true -          c = Column.new(nil, 1, 'boolean') +          c = Column.new(nil, 1, Type::Boolean.new)            assert_equal 1, @conn.type_cast(true, nil)            assert_equal 1, @conn.type_cast(true, c)          end          def test_type_cast_false -          c = Column.new(nil, 1, 'boolean') +          c = Column.new(nil, 1, Type::Boolean.new)            assert_equal 0, @conn.type_cast(false, nil)            assert_equal 0, @conn.type_cast(false, c)          end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index c20030ca64..34c2008ab4 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -89,16 +89,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase    end    def test_type_cast_array -    data = '{1,2,3}' -    oid_type  = @column.instance_variable_get('@oid_type').subtype -    # we are getting the instance variable in this test, but in the -    # normal use of string_to_array, it's called from the OID::Array -    # class and will have the OID instance that will provide the type -    # casting -    array = @column.class.string_to_array data, oid_type -    assert_equal(['1', '2', '3'], array) -    assert_equal(['1', '2', '3'], @column.type_cast(data)) - +    assert_equal(['1', '2', '3'], @column.type_cast('{1,2,3}'))      assert_equal([], @column.type_cast('{}'))      assert_equal([nil], @column.type_cast('{NULL}'))    end diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb index 68b9e6daf7..972abf7cdc 100644 --- a/activerecord/test/cases/adapters/postgresql/composite_test.rb +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -83,9 +83,8 @@ end  class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase    include PostgresqlCompositeBehavior -  class FullAddressType +  class FullAddressType < ActiveRecord::ConnectionAdapters::Type::Value      def type; :full_address end -    def simplified_type(sql_type); type end      def type_cast(value)        if value =~ /\("?([^",]*)"?,"?([^",]*)"?\)/ @@ -103,15 +102,7 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase    def setup      super -    @registration = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID -    @registration.register_type "full_address", FullAddressType.new -  end - -  def teardown -    super - -    # there is currently no clean way to unregister a OID::Type -    @registration::NAMES.delete("full_address") +    @connection.type_map.register_type "full_address", FullAddressType.new    end    def test_column @@ -131,7 +122,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 ea433d391f..0dad89c67a 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -1,14 +1,6 @@  require "cases/helper"  require 'support/ddl_helper' -class PostgresqlArray < ActiveRecord::Base -end - -class PostgresqlTsvector < ActiveRecord::Base -end - -class PostgresqlMoney < ActiveRecord::Base -end  class PostgresqlNumber < ActiveRecord::Base  end @@ -16,18 +8,12 @@ end  class PostgresqlTime < ActiveRecord::Base  end -class PostgresqlNetworkAddress < ActiveRecord::Base -end -  class PostgresqlBitString < ActiveRecord::Base  end  class PostgresqlOid < ActiveRecord::Base  end -class PostgresqlTimestampWithZone < ActiveRecord::Base -end -  class PostgresqlLtree < ActiveRecord::Base  end @@ -36,16 +22,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase    def setup      @connection = ActiveRecord::Base.connection -    @connection.execute("set lc_monetary = 'C'") - -    @connection.execute("INSERT INTO postgresql_tsvectors (id, text_vector) VALUES (1, ' ''text'' ''vector'' ')") - -    @first_tsvector = PostgresqlTsvector.find(1) - -    @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)") -    @first_money = PostgresqlMoney.find(1) -    @second_money = PostgresqlMoney.find(2)      @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (1, 123.456, 123456.789)")      @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (2, '-Infinity', 'Infinity')") @@ -57,29 +33,15 @@ 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_network_addresses (id, cidr_address, inet_address, mac_address) VALUES(1, '192.168.0/24', '172.16.1.254/32', '01:23:45:67:89:0a')") -    @first_network_address = PostgresqlNetworkAddress.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) - -    @connection.execute("INSERT INTO postgresql_timestamp_with_zones (id, time) VALUES (1, '2010-01-01 10:00:00-1')")    end    teardown do -    [PostgresqlTsvector, PostgresqlMoney, PostgresqlNumber, PostgresqlTime, PostgresqlNetworkAddress, -     PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone].each(&:delete_all) -  end - -  def test_data_type_of_tsvector_types -    assert_equal :tsvector, @first_tsvector.column_for_attribute(:text_vector).type -  end - -  def test_data_type_of_money_types -    assert_equal :decimal, @first_money.column_for_attribute(:wealth).type +    [PostgresqlNumber, PostgresqlTime, PostgresqlBitString, PostgresqlOid].each(&:delete_all)    end    def test_data_type_of_number_types @@ -92,12 +54,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase      assert_equal :string, @first_time.column_for_attribute(:scaled_time_interval).type    end -  def test_data_type_of_network_address_types -    assert_equal :cidr, @first_network_address.column_for_attribute(:cidr_address).type -    assert_equal :inet, @first_network_address.column_for_attribute(:inet_address).type -    assert_equal :macaddr, @first_network_address.column_for_attribute(:mac_address).type -  end -    def test_data_type_of_bit_string_types      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 @@ -107,34 +63,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase      assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type    end -  def test_tsvector_values -    assert_equal "'text' 'vector'", @first_tsvector.text_vector -  end - -  def test_money_values -    assert_equal 567.89, @first_money.wealth -    assert_equal(-567.89, @second_money.wealth) -  end - -  def test_money_type_cast -    column = PostgresqlMoney.columns_hash['wealth'] -    assert_equal(12345678.12, column.type_cast("$12,345,678.12")) -    assert_equal(12345678.12, column.type_cast("$12.345.678,12")) -    assert_equal(-1.15, column.type_cast("-$1.15")) -    assert_equal(-2.25, column.type_cast("($2.25)")) -  end - -  def test_update_tsvector -    new_text_vector = "'new' 'text' 'vector'" -    @first_tsvector.text_vector = new_text_vector -    assert @first_tsvector.save -    assert @first_tsvector.reload -    @first_tsvector.text_vector = new_text_vector -    assert @first_tsvector.save -    assert @first_tsvector.reload -    assert_equal new_text_vector, @first_tsvector.text_vector -  end -    def test_number_values      assert_equal 123.456, @first_number.single      assert_equal 123456.789, @first_number.double @@ -148,15 +76,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase      assert_equal '-21 days', @first_time.scaled_time_interval    end -  def test_network_address_values_ipaddr -    cidr_address = IPAddr.new '192.168.0.0/24' -    inet_address = IPAddr.new '172.16.1.254' - -    assert_equal cidr_address, @first_network_address.cidr_address -    assert_equal inet_address, @first_network_address.inet_address -    assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address -  end -    def test_bit_string_values      assert_equal '00010101', @first_bit_string.bit_string      assert_equal '00010101', @first_bit_string.bit_string_varying @@ -166,14 +85,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase      assert_equal 1234, @first_oid.obj_id    end -  def test_update_money -    new_value = BigDecimal.new('123.45') -    @first_money.wealth = new_value -    assert @first_money.save -    assert @first_money.reload -    assert_equal new_value, @first_money.wealth -  end -    def test_update_number      new_single = 789.012      new_double = 789012.345 @@ -192,20 +103,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase      assert_equal '2 years 00:03:00', @first_time.time_interval    end -  def test_update_network_address -    new_inet_address = '10.1.2.3/32' -    new_cidr_address = '10.0.0.0/8' -    new_mac_address = 'bc:de:f0:12:34:56' -    @first_network_address.cidr_address = new_cidr_address -    @first_network_address.inet_address = new_inet_address -    @first_network_address.mac_address = new_mac_address -    assert @first_network_address.save -    assert @first_network_address.reload -    assert_equal @first_network_address.cidr_address, new_cidr_address -    assert_equal @first_network_address.inet_address, new_inet_address -    assert_equal @first_network_address.mac_address, new_mac_address -  end -    def test_update_bit_string      new_bit_string = '11111111'      new_bit_string_varying = '0xFF' @@ -223,20 +120,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase      assert_raise(ActiveRecord::StatementInvalid) { assert @first_bit_string.save }    end -  def test_invalid_network_address -    @first_network_address.cidr_address = 'invalid addr' -    assert_nil @first_network_address.cidr_address -    assert_equal 'invalid addr', @first_network_address.cidr_address_before_type_cast -    assert @first_network_address.save - -    @first_network_address.reload - -    @first_network_address.inet_address = 'invalid addr' -    assert_nil @first_network_address.inet_address -    assert_equal 'invalid addr', @first_network_address.inet_address_before_type_cast -    assert @first_network_address.save -  end -    def test_update_oid      new_value = 567890      @first_oid.obj_id = new_value @@ -244,32 +127,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase      assert @first_oid.reload      assert_equal new_value, @first_oid.obj_id    end - -  def test_timestamp_with_zone_values_with_rails_time_zone_support -    with_timezone_config default: :utc, aware_attributes: true do -      @connection.reconnect! - -      @first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1) -      assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time -      assert_instance_of Time, @first_timestamp_with_zone.time -    end -  ensure -    @connection.reconnect! -  end - -  def test_timestamp_with_zone_values_without_rails_time_zone_support -    with_timezone_config default: :local, aware_attributes: false do -      @connection.reconnect! -      # make sure to use a non-UTC time zone -      @connection.execute("SET time zone 'America/Jamaica'", 'SCHEMA') - -      @first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1) -      assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time -      assert_instance_of Time, @first_timestamp_with_zone.time -    end -  ensure -    @connection.reconnect! -  end  end  class PostgresqlInternalDataTypeTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb new file mode 100644 index 0000000000..91058f8681 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb @@ -0,0 +1,65 @@ +require "cases/helper" +require "active_record/base" +require "active_record/connection_adapters/postgresql_adapter" + +class PostgresqlExtensionMigrationTest < ActiveRecord::TestCase +  self.use_transactional_fixtures = false + +  class EnableHstore < ActiveRecord::Migration +    def change +      enable_extension "hstore" +    end +  end + +  class DisableHstore < ActiveRecord::Migration +    def change +      disable_extension "hstore" +    end +  end + +  def setup +    super + +    @connection = ActiveRecord::Base.connection + +    unless @connection.supports_extensions? +      return skip("no extension support") +    end + +    @old_schema_migration_tabel_name = ActiveRecord::SchemaMigration.table_name +    @old_tabel_name_prefix = ActiveRecord::Base.table_name_prefix +    @old_tabel_name_suffix = ActiveRecord::Base.table_name_suffix + +    ActiveRecord::Base.table_name_prefix = "p_" +    ActiveRecord::Base.table_name_suffix = "_s" +    ActiveRecord::SchemaMigration.delete_all rescue nil +    ActiveRecord::SchemaMigration.table_name = "p_schema_migrations_s" +    ActiveRecord::Migration.verbose = false +  end + +  def teardown +    ActiveRecord::Base.table_name_prefix = @old_tabel_name_prefix +    ActiveRecord::Base.table_name_suffix = @old_tabel_name_suffix +    ActiveRecord::SchemaMigration.delete_all rescue nil +    ActiveRecord::Migration.verbose = true +    ActiveRecord::SchemaMigration.table_name = @old_schema_migration_tabel_name + +    super +  end + +  def test_enable_extension_migration_ignores_prefix_and_suffix +    @connection.disable_extension("hstore") + +    migrations = [EnableHstore.new(nil, 1)] +    ActiveRecord::Migrator.new(:up, migrations).migrate +    assert @connection.extension_enabled?("hstore"), "extension hstore should be enabled" +  end + +  def test_disable_extension_migration_ignores_prefix_and_suffix +    @connection.enable_extension("hstore") + +    migrations = [DisableHstore.new(nil, 1)] +    ActiveRecord::Migrator.new(:up, migrations).migrate +    assert_not @connection.extension_enabled?("hstore"), "extension hstore should not be enabled" +  end +end diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb new file mode 100644 index 0000000000..4442abcbc4 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb @@ -0,0 +1,30 @@ +# 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 + +  def test_tsvector_column +    column = PostgresqlTsvector.columns_hash["text_vector"] +    assert_equal :tsvector, column.type +    assert_equal "tsvector", column.sql_type +    assert_not column.number? +    assert_not column.text? +    assert_not column.binary? +    assert_not column.array +  end + +  def test_update_tsvector +    PostgresqlTsvector.create text_vector: "'text' 'vector'" +    tsvector = PostgresqlTsvector.first +    assert_equal "'text' 'vector'", tsvector.text_vector + +    tsvector.text_vector = "'new' 'text' 'vector'" +    tsvector.save! +    assert tsvector.reload +    assert_equal "'new' 'text' 'vector'", tsvector.text_vector +  end +end diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb new file mode 100644 index 0000000000..e109f1682b --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/money_test.rb @@ -0,0 +1,54 @@ +# encoding: utf-8 + +require "cases/helper" +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlMoneyTest < ActiveRecord::TestCase +  class PostgresqlMoney < ActiveRecord::Base; end + +  setup do +    @connection = ActiveRecord::Base.connection +    @connection.execute("set lc_monetary = 'C'") +  end + +  def test_column +    column = PostgresqlMoney.columns_hash["wealth"] +    assert_equal :decimal, column.type +    assert_equal "money", column.sql_type +    assert_equal 2, column.scale +    assert column.number? +    assert_not column.text? +    assert_not column.binary? +    assert_not column.array +  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)") + +    first_money = PostgresqlMoney.find(1) +    second_money = PostgresqlMoney.find(2) +    assert_equal 567.89, first_money.wealth +    assert_equal(-567.89, second_money.wealth) +  end + +  def test_money_type_cast +    column = PostgresqlMoney.columns_hash['wealth'] +    assert_equal(12345678.12, column.type_cast("$12,345,678.12")) +    assert_equal(12345678.12, column.type_cast("$12.345.678,12")) +    assert_equal(-1.15, column.type_cast("-$1.15")) +    assert_equal(-2.25, column.type_cast("($2.25)")) +  end + +  def test_create_and_update_money +    money = PostgresqlMoney.create(wealth: "987.65") +    assert_equal 987.65, money.wealth + +    new_value = BigDecimal.new('123.45') +    money.wealth = new_value +    money.save! +    money.reload +    assert_equal new_value, money.wealth +  end +end diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb new file mode 100644 index 0000000000..e99af07970 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/network_test.rb @@ -0,0 +1,77 @@ +# 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 +  end + +  def test_cidr_column +    column = PostgresqlNetworkAddress.columns_hash["cidr_address"] +    assert_equal :cidr, column.type +    assert_equal "cidr", column.sql_type +    assert_not column.number? +    assert_not column.text? +    assert_not column.binary? +    assert_not column.array +  end + +  def test_inet_column +    column = PostgresqlNetworkAddress.columns_hash["inet_address"] +    assert_equal :inet, column.type +    assert_equal "inet", column.sql_type +    assert_not column.number? +    assert_not column.text? +    assert_not column.binary? +    assert_not column.array +  end + +  def test_macaddr_column +    column = PostgresqlNetworkAddress.columns_hash["mac_address"] +    assert_equal :macaddr, column.type +    assert_equal "macaddr", column.sql_type +    assert_not column.number? +    assert_not column.text? +    assert_not column.binary? +    assert_not column.array +  end + +  def test_network_types +    PostgresqlNetworkAddress.create(cidr_address: '192.168.0.0/24', +                                    inet_address: '172.16.1.254/32', +                                    mac_address: '01:23:45:67:89:0a') + +    address = PostgresqlNetworkAddress.first +    assert_equal IPAddr.new('192.168.0.0/24'), address.cidr_address +    assert_equal IPAddr.new('172.16.1.254'), address.inet_address +    assert_equal '01:23:45:67:89:0a', address.mac_address + +    address.cidr_address = '10.1.2.3/32' +    address.inet_address = '10.0.0.0/8' +    address.mac_address = 'bc:de:f0:12:34:56' + +    address.save! +    assert address.reload +    assert_equal IPAddr.new('10.1.2.3/32'), address.cidr_address +    assert_equal IPAddr.new('10.0.0.0/8'), address.inet_address +    assert_equal 'bc:de:f0:12:34:56', address.mac_address +  end + +  def test_invalid_network_address +    invalid_address = PostgresqlNetworkAddress.new(cidr_address: 'invalid addr', +                                                   inet_address: 'invalid addr') +    assert_nil invalid_address.cidr_address +    assert_nil invalid_address.inet_address +    assert_equal 'invalid addr', invalid_address.cidr_address_before_type_cast +    assert_equal 'invalid addr', invalid_address.inet_address_before_type_cast +    assert invalid_address.save + +    invalid_address.reload +    assert_nil invalid_address.cidr_address +    assert_nil invalid_address.inet_address +    assert_nil invalid_address.cidr_address_before_type_cast +    assert_nil invalid_address.inet_address_before_type_cast +  end +end diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index 51846e22d9..218c59247e 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -10,13 +10,13 @@ module ActiveRecord          end          def test_type_cast_true -          c = PostgreSQLColumn.new(nil, 1, OID::Boolean.new, 'boolean') +          c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean')            assert_equal 't', @conn.type_cast(true, nil)            assert_equal 't', @conn.type_cast(true, c)          end          def test_type_cast_false -          c = PostgreSQLColumn.new(nil, 1, OID::Boolean.new, 'boolean') +          c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean')            assert_equal 'f', @conn.type_cast(false, nil)            assert_equal 'f', @conn.type_cast(false, c)          end @@ -47,9 +47,9 @@ module ActiveRecord          def test_quote_cast_numeric            fixnum = 666 -          c = PostgreSQLColumn.new(nil, nil, OID::String.new, 'varchar') +          c = PostgreSQLColumn.new(nil, nil, Type::String.new, 'varchar')            assert_equal "'666'", @conn.quote(fixnum, c) -          c = PostgreSQLColumn.new(nil, nil, OID::Text.new, 'text') +          c = PostgreSQLColumn.new(nil, nil, Type::Text.new, 'text')            assert_equal "'666'", @conn.quote(fixnum, c)          end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 9d4d79c0c6..b6c6e38f62 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -50,6 +50,16 @@ class SchemaTest < ActiveRecord::TestCase      self.table_name = 'things'    end +  class Song < ActiveRecord::Base +    self.table_name = "music.songs" +    has_and_belongs_to_many :albums +  end + +  class Album < ActiveRecord::Base +    self.table_name = "music.albums" +    has_and_belongs_to_many :songs +  end +    def setup      @connection = ActiveRecord::Base.connection      @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" @@ -109,6 +119,22 @@ class SchemaTest < ActiveRecord::TestCase      assert !@connection.schema_names.include?("test_schema3")    end +  def test_habtm_table_name_with_schema +    ActiveRecord::Base.connection.execute <<-SQL +      DROP SCHEMA IF EXISTS music CASCADE; +      CREATE SCHEMA music; +      CREATE TABLE music.albums (id serial primary key); +      CREATE TABLE music.songs (id serial primary key); +      CREATE TABLE music.albums_songs (album_id integer, song_id integer); +    SQL + +    song = Song.create +    Album.create +    assert_equal song, Song.includes(:albums).references(:albums).first +  ensure +    ActiveRecord::Base.connection.execute "DROP SCHEMA music CASCADE;" +  end +    def test_raise_drop_schema_with_nonexisting_schema      assert_raises(ActiveRecord::StatementInvalid) do        @connection.drop_schema "test_schema3" @@ -352,21 +378,6 @@ class SchemaTest < ActiveRecord::TestCase      end    end -  def test_extract_schema_and_table -    { -      %(table_name)            => [nil,'table_name'], -      %("table.name")          => [nil,'table.name'], -      %(schema.table_name)     => %w{schema table_name}, -      %("schema".table_name)   => %w{schema table_name}, -      %(schema."table_name")   => %w{schema table_name}, -      %("schema"."table_name") => %w{schema table_name}, -      %("even spaces".table)   => ['even spaces','table'], -      %(schema."table.name")   => ['schema', 'table.name'] -    }.each do |given, expect| -      assert_equal expect, @connection.send(:extract_schema_and_table, given) -    end -  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/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb index 4d29a20e66..d4102bf7be 100644 --- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb +++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb @@ -2,6 +2,47 @@ require 'cases/helper'  require 'models/developer'  require 'models/topic' +class PostgresqlTimestampTest < ActiveRecord::TestCase +  class PostgresqlTimestampWithZone < ActiveRecord::Base; end + +  self.use_transactional_fixtures = false + +  setup do +    @connection = ActiveRecord::Base.connection +    @connection.execute("INSERT INTO postgresql_timestamp_with_zones (id, time) VALUES (1, '2010-01-01 10:00:00-1')") +  end + +  teardown do +    PostgresqlTimestampWithZone.delete_all +  end + +  def test_timestamp_with_zone_values_with_rails_time_zone_support +    with_timezone_config default: :utc, aware_attributes: true do +      @connection.reconnect! + +      timestamp = PostgresqlTimestampWithZone.find(1) +      assert_equal Time.utc(2010,1,1, 11,0,0), timestamp.time +      assert_instance_of Time, timestamp.time +    end +  ensure +    @connection.reconnect! +  end + +  def test_timestamp_with_zone_values_without_rails_time_zone_support +    with_timezone_config default: :local, aware_attributes: false do +      @connection.reconnect! +      # make sure to use a non-UTC time zone +      @connection.execute("SET time zone 'America/Jamaica'", 'SCHEMA') + +      timestamp = PostgresqlTimestampWithZone.find(1) +      assert_equal Time.utc(2010,1,1, 11,0,0), timestamp.time +      assert_instance_of Time, timestamp.time +    end +  ensure +    @connection.reconnect! +  end +end +  class TimestampTest < ActiveRecord::TestCase    fixtures :topics @@ -84,18 +125,18 @@ class TimestampTest < ActiveRecord::TestCase    private -    def pg_datetime_precision(table_name, column_name) -      results = ActiveRecord::Base.connection.execute("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name ='#{table_name}'") -      result = results.find do |result_hash| -        result_hash["column_name"] == column_name -      end -      result && result["datetime_precision"] +  def pg_datetime_precision(table_name, column_name) +    results = ActiveRecord::Base.connection.execute("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name ='#{table_name}'") +    result = results.find do |result_hash| +      result_hash["column_name"] == column_name      end +    result && result["datetime_precision"] +  end -    def activerecord_column_option(tablename, column_name, option) -      result = ActiveRecord::Base.connection.columns(tablename).find do |column| -        column.name == column_name -      end -      result && result.send(option) +  def activerecord_column_option(tablename, column_name, option) +    result = ActiveRecord::Base.connection.columns(tablename).find do |column| +      column.name == column_name      end +    result && result.send(option) +  end  end diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb new file mode 100644 index 0000000000..e6d7868e9a --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb @@ -0,0 +1,20 @@ +require 'cases/helper' + +class PostgreSQLUtilsTest < ActiveSupport::TestCase +  include ActiveRecord::ConnectionAdapters::PostgreSQL::Utils + +  def test_extract_schema_and_table +    { +      %(table_name)            => [nil,'table_name'], +      %("table.name")          => [nil,'table.name'], +      %(schema.table_name)     => %w{schema table_name}, +      %("schema".table_name)   => %w{schema table_name}, +      %(schema."table_name")   => %w{schema table_name}, +      %("schema"."table_name") => %w{schema table_name}, +      %("even spaces".table)   => ['even spaces','table'], +      %(schema."table.name")   => ['schema', 'table.name'] +    }.each do |given, expect| +      assert_equal expect, extract_schema_and_table(given) +    end +  end +end diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index b478db749d..13b754d226 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -60,7 +60,6 @@ class CopyTableTest < ActiveRecord::TestCase        assert_equal original_id.type, copied_id.type        assert_equal original_id.sql_type, copied_id.sql_type        assert_equal original_id.limit, copied_id.limit -      assert_equal original_id.primary, copied_id.primary      end    end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index e4b69fdf7b..209b7f70c9 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 @@ -47,13 +47,13 @@ module ActiveRecord          end          def test_type_cast_true -          c = Column.new(nil, 1, 'int') +          c = Column.new(nil, 1, Type::Integer.new)            assert_equal 't', @conn.type_cast(true, nil)            assert_equal 1, @conn.type_cast(true, c)          end          def test_type_cast_false -          c = Column.new(nil, 1, 'int') +          c = Column.new(nil, 1, Type::Integer.new)            assert_equal 'f', @conn.type_cast(false, nil)            assert_equal 0, @conn.type_cast(false, c)          end @@ -61,16 +61,16 @@ module ActiveRecord          def test_type_cast_string            assert_equal '10', @conn.type_cast('10', nil) -          c = Column.new(nil, 1, 'int') +          c = Column.new(nil, 1, Type::Integer.new)            assert_equal 10, @conn.type_cast('10', c) -          c = Column.new(nil, 1, 'float') +          c = Column.new(nil, 1, Type::Float.new)            assert_equal 10.1, @conn.type_cast('10.1', c) -          c = Column.new(nil, 1, 'binary') +          c = Column.new(nil, 1, Type::Binary.new)            assert_equal '10.1', @conn.type_cast('10.1', c) -          c = Column.new(nil, 1, 'date') +          c = Column.new(nil, 1, Type::Date.new)            assert_equal '10.1', @conn.type_cast('10.1', c)          end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 3b484a0d64..9c92dc1141 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -369,6 +369,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase      assert_queries(2) { line_item.update amount: 10 }    end +  def test_belongs_to_with_touch_option_on_empty_update +    line_item = LineItem.create! +    Invoice.create!(line_items: [line_item]) + +    assert_queries(0) { line_item.save } +  end +    def test_belongs_to_with_touch_option_on_destroy      line_item = LineItem.create!      Invoice.create!(line_items: [line_item]) @@ -563,6 +570,19 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase      assert companies(:first_client).readonly_firm.readonly?    end +  def test_test_polymorphic_assignment_foreign_key_type_string +    comment = Comment.first +    comment.author   = Author.first +    comment.resource = Member.first +    comment.save + +    assert_equal Comment.all.to_a, +      Comment.includes(:author).to_a + +    assert_equal Comment.all.to_a, +      Comment.includes(:resource).to_a +  end +    def test_polymorphic_assignment_foreign_type_field_updating      # should update when assigning a saved record      sponsor = Sponsor.new diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 07903a3441..4bd4486b41 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -534,21 +534,13 @@ class EagerAssociationTest < ActiveRecord::TestCase    end    def test_eager_with_has_many_and_limit_and_conditions -    if current_adapter?(:OpenBaseAdapter) -      posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "FETCHBLOB(posts.body) = 'hello'", :order => "posts.id").to_a -    else -      posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").to_a -    end +    posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").to_a      assert_equal 2, posts.size      assert_equal [4,5], posts.collect { |p| p.id }    end    def test_eager_with_has_many_and_limit_and_conditions_array -    if current_adapter?(:OpenBaseAdapter) -      posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id").to_a -    else -      posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").to_a -    end +    posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").to_a      assert_equal 2, posts.size      assert_equal [4,5], posts.collect { |p| p.id }    end @@ -940,13 +932,7 @@ class EagerAssociationTest < ActiveRecord::TestCase    end    def test_count_with_include -    if current_adapter?(:SybaseAdapter) -      assert_equal 3, authors(:david).posts_with_comments.where("len(comments.body) > 15").references(:comments).count -    elsif current_adapter?(:OpenBaseAdapter) -      assert_equal 3, authors(:david).posts_with_comments.where("length(FETCHBLOB(comments.body)) > 15").references(:comments).count -    else -      assert_equal 3, authors(:david).posts_with_comments.where("length(comments.body) > 15").references(:comments).count -    end +    assert_equal 3, authors(:david).posts_with_comments.where("length(comments.body) > 15").references(:comments).count    end    def test_load_with_sti_sharing_association @@ -1181,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 } @@ -1210,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..8d8201ddae 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_equal true, child.save +  end  end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 38e93288e4..4c96c2f4fd 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -516,7 +516,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase    end    def test_only_time_related_columns_are_meant_to_be_cached_by_default -    expected = %w(datetime timestamp time date).sort +    expected = %w(datetime time date).sort      assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort    end @@ -843,6 +843,18 @@ class AttributeMethodsTest < ActiveRecord::TestCase      assert_equal !real_topic.title?, klass.find(real_topic.id).title?    end +  def test_calling_super_when_parent_does_not_define_method_raises_error +    klass = new_topic_like_ar_class do +      def some_method_that_is_not_on_super +        super +      end +    end + +    assert_raise(NoMethodError) do +      klass.new.some_method_that_is_not_on_super +    end +  end +    private    def new_topic_like_ar_class(&block) diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index f7584c3a51..09892d50ba 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -683,10 +683,23 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase        end      end +    @ship.pirate.catchphrase = "Changed Catchphrase" +      assert_raise(RuntimeError) { assert !@pirate.save }      assert_not_nil @pirate.reload.ship    end +  def test_should_save_changed_has_one_changed_object_if_child_is_saved +    @pirate.ship.name = "NewName" +    assert @pirate.save +    assert_equal "NewName", @pirate.ship.reload.name +  end + +  def test_should_not_save_changed_has_one_unchanged_object_if_child_is_saved +    @pirate.ship.expects(:save).never +    assert @pirate.save +  end +    # belongs_to    def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal      assert !@ship.pirate.marked_for_destruction? diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 2e5b8cffa6..d65c4b0638 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -102,8 +102,8 @@ class BasicsTest < ActiveRecord::TestCase    end    def test_columns_should_obey_set_primary_key -    pk = Subscriber.columns.find { |x| x.name == 'nick' } -    assert pk.primary, 'nick should be primary key' +    pk = Subscriber.columns_hash[Subscriber.primary_key] +    assert_equal 'nick', pk.name, 'nick should be primary key'    end    def test_primary_key_with_no_id @@ -160,19 +160,11 @@ class BasicsTest < ActiveRecord::TestCase    end    def test_preserving_date_objects -    if current_adapter?(:SybaseAdapter) -      # Sybase ctlib does not (yet?) support the date type; use datetime instead. -      assert_kind_of( -        Time, Topic.find(1).last_read, -        "The last_read attribute should be of the Time class" -      ) -    else -      # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) -      assert_kind_of( -        Date, Topic.find(1).last_read, -        "The last_read attribute should be of the Date class" -      ) -    end +    # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) +    assert_kind_of( +      Date, Topic.find(1).last_read, +      "The last_read attribute should be of the Date class" +    )    end    def test_previously_changed @@ -480,8 +472,8 @@ class BasicsTest < ActiveRecord::TestCase      end    end -  # Oracle, and Sybase do not have a TIME datatype. -  unless current_adapter?(:OracleAdapter, :SybaseAdapter) +  # Oracle does not have a TIME datatype. +  unless current_adapter?(:OracleAdapter)      def test_utc_as_time_zone        with_timezone_config default: :utc do          attributes = { "bonus_time" => "5:42:00AM" } @@ -515,12 +507,7 @@ class BasicsTest < ActiveRecord::TestCase      topic = Topic.find(topic.id)      assert_nil topic.last_read -    # Sybase adapter does not allow nulls in boolean columns -    if current_adapter?(:SybaseAdapter) -      assert topic.approved == false -    else -      assert_nil topic.approved -    end +    assert_nil topic.approved    end    def test_equality @@ -531,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 @@ -685,8 +676,8 @@ class BasicsTest < ActiveRecord::TestCase    end    def test_attributes_on_dummy_time -    # Oracle, and Sybase do not have a TIME datatype. -    return true if current_adapter?(:OracleAdapter, :SybaseAdapter) +    # Oracle does not have a TIME datatype. +    return true if current_adapter?(:OracleAdapter)      with_timezone_config default: :local do        attributes = { @@ -699,8 +690,8 @@ class BasicsTest < ActiveRecord::TestCase    end    def test_attributes_on_dummy_time_with_invalid_time -    # Oracle, and Sybase do not have a TIME datatype. -    return true if current_adapter?(:OracleAdapter, :SybaseAdapter) +    # Oracle does not have a TIME datatype. +    return true if current_adapter?(:OracleAdapter)      attributes = {        "bonus_time" => "not a time" @@ -787,8 +778,14 @@ class BasicsTest < ActiveRecord::TestCase      assert_equal("c", duped_topic.title)    end +  DeveloperSalary = Struct.new(:amount)    def test_dup_with_aggregate_of_same_name_as_attribute -    dev = DeveloperWithAggregate.find(1) +    developer_with_aggregate = Class.new(ActiveRecord::Base) do +      self.table_name = 'developers' +      composed_of :salary, :class_name => 'BasicsTest::DeveloperSalary', :mapping => [%w(salary amount)] +    end + +    dev = developer_with_aggregate.find(1)      assert_kind_of DeveloperSalary, dev.salary      dup = nil @@ -987,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/binary_test.rb b/activerecord/test/cases/binary_test.rb index 9a486cf8b8..b41b95309b 100644 --- a/activerecord/test/cases/binary_test.rb +++ b/activerecord/test/cases/binary_test.rb @@ -2,9 +2,9 @@  require "cases/helper"  # Without using prepared statements, it makes no sense to test -# BLOB data with DB2 or Firebird, because the length of a statement +# BLOB data with DB2, because the length of a statement  # is limited to 32KB. -unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter) +unless current_adapter?(:DB2Adapter)    require 'models/binary'    class BinaryTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index 40f73cd68c..0bc7ee6d64 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -21,7 +21,7 @@ module ActiveRecord        super        @connection = ActiveRecord::Base.connection        @subscriber   = LogListener.new -      @pk         = Topic.columns.find { |c| c.primary } +      @pk         = Topic.columns_hash[Topic.primary_key]        @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)      end @@ -60,12 +60,10 @@ module ActiveRecord        end        def test_logs_bind_vars -        pk = Topic.columns.find { |x| x.primary } -          payload = {            :name  => 'SQL',            :sql   => 'select * from topics where id = ?', -          :binds => [[pk, 10]] +          :binds => [[@pk, 10]]          }          event  = ActiveSupport::Notifications::Event.new(            'foo', @@ -87,7 +85,7 @@ module ActiveRecord          }.new          logger.sql event -        assert_match([[pk.name, 10]].inspect, logger.debugs.first) +        assert_match([[@pk.name, 10]].inspect, logger.debugs.first)        end      end    end 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 c1dd1f1c69..45e48900ee 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -12,13 +12,13 @@ module ActiveRecord        end        def test_can_set_coder -        column = Column.new("title", nil, "varchar(20)") +        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, "varchar(20)") +        column = Column.new("title", nil, Type::String.new, "varchar(20)")          assert !column.encoded?          column.coder = YAML @@ -26,7 +26,7 @@ module ActiveRecord        end        def test_type_case_coded_column -        column = Column.new("title", nil, "varchar(20)") +        column = Column.new("title", nil, Type::String.new, "varchar(20)")          column.coder = YAML          assert_equal "hello", column.type_cast("--- hello")        end @@ -34,7 +34,7 @@ module ActiveRecord        # Avoid column definitions in create table statements like:        # `title` varchar(255) DEFAULT NULL        def test_should_not_include_default_clause_when_default_is_null -        column = Column.new("title", nil, "varchar(20)") +        column = Column.new("title", nil, Type::String.new(limit: 20))          column_def = ColumnDefinition.new(            column.name, "string",            column.limit, column.precision, column.scale, column.default, column.null) @@ -42,7 +42,7 @@ module ActiveRecord        end        def test_should_include_default_clause_when_default_is_present -        column = Column.new("title", "Hello", "varchar(20)") +        column = Column.new("title", "Hello", Type::String.new(limit: 20))          column_def = ColumnDefinition.new(            column.name, "string",            column.limit, column.precision, column.scale, column.default, column.null) @@ -50,7 +50,7 @@ module ActiveRecord        end        def test_should_specify_not_null_if_null_option_is_false -        column = Column.new("title", "Hello", "varchar(20)", false) +        column = Column.new("title", "Hello", Type::String.new(limit: 20), "varchar(20)", false)          column_def = ColumnDefinition.new(            column.name, "string",            column.limit, column.precision, column.scale, column.default, column.null) @@ -59,68 +59,68 @@ module ActiveRecord        if current_adapter?(:MysqlAdapter)          def test_should_set_default_for_mysql_binary_data_types -          binary_column = MysqlAdapter::Column.new("title", "a", "binary(1)") +          binary_column = MysqlAdapter::Column.new("title", "a", Type::Binary.new, "binary(1)")            assert_equal "a", binary_column.default -          varbinary_column = MysqlAdapter::Column.new("title", "a", "varbinary(1)") +          varbinary_column = MysqlAdapter::Column.new("title", "a", Type::Binary.new, "varbinary(1)")            assert_equal "a", varbinary_column.default          end          def test_should_not_set_default_for_blob_and_text_data_types            assert_raise ArgumentError do -            MysqlAdapter::Column.new("title", "a", "blob") +            MysqlAdapter::Column.new("title", "a", Type::Binary.new, "blob")            end            assert_raise ArgumentError do -            MysqlAdapter::Column.new("title", "Hello", "text") +            MysqlAdapter::Column.new("title", "Hello", Type::Text.new)            end -          text_column = MysqlAdapter::Column.new("title", nil, "text") +          text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new)            assert_equal nil, text_column.default -          not_null_text_column = MysqlAdapter::Column.new("title", nil, "text", false) +          not_null_text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new, "text", false)            assert_equal "", not_null_text_column.default          end          def test_has_default_should_return_false_for_blob_and_text_data_types -          blob_column = MysqlAdapter::Column.new("title", nil, "blob") +          blob_column = MysqlAdapter::Column.new("title", nil, Type::Binary.new, "blob")            assert !blob_column.has_default? -          text_column = MysqlAdapter::Column.new("title", nil, "text") +          text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new)            assert !text_column.has_default?          end        end        if current_adapter?(:Mysql2Adapter)          def test_should_set_default_for_mysql_binary_data_types -          binary_column = Mysql2Adapter::Column.new("title", "a", "binary(1)") +          binary_column = Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "binary(1)")            assert_equal "a", binary_column.default -          varbinary_column = Mysql2Adapter::Column.new("title", "a", "varbinary(1)") +          varbinary_column = Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "varbinary(1)")            assert_equal "a", varbinary_column.default          end          def test_should_not_set_default_for_blob_and_text_data_types            assert_raise ArgumentError do -            Mysql2Adapter::Column.new("title", "a", "blob") +            Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "blob")            end            assert_raise ArgumentError do -            Mysql2Adapter::Column.new("title", "Hello", "text") +            Mysql2Adapter::Column.new("title", "Hello", Type::Text.new)            end -          text_column = Mysql2Adapter::Column.new("title", nil, "text") +          text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new)            assert_equal nil, text_column.default -          not_null_text_column = Mysql2Adapter::Column.new("title", nil, "text", false) +          not_null_text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new, "text", false)            assert_equal "", not_null_text_column.default          end          def test_has_default_should_return_false_for_blob_and_text_data_types -          blob_column = Mysql2Adapter::Column.new("title", nil, "blob") +          blob_column = Mysql2Adapter::Column.new("title", nil, Type::Binary.new, "blob")            assert !blob_column.has_default? -          text_column = Mysql2Adapter::Column.new("title", nil, "text") +          text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new)            assert !text_column.has_default?          end        end diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb deleted file mode 100644 index 2a6d8cc2ab..0000000000 --- a/activerecord/test/cases/column_test.rb +++ /dev/null @@ -1,123 +0,0 @@ -require "cases/helper" -require 'models/company' - -module ActiveRecord -  module ConnectionAdapters -    class ColumnTest < ActiveRecord::TestCase -      def test_type_cast_boolean -        column = Column.new("field", nil, "boolean") -        assert column.type_cast('').nil? -        assert column.type_cast(nil).nil? - -        assert column.type_cast(true) -        assert column.type_cast(1) -        assert column.type_cast('1') -        assert column.type_cast('t') -        assert column.type_cast('T') -        assert column.type_cast('true') -        assert column.type_cast('TRUE') -        assert column.type_cast('on') -        assert column.type_cast('ON') - -        # explicitly check for false vs nil -        assert_equal false, column.type_cast(false) -        assert_equal false, column.type_cast(0) -        assert_equal false, column.type_cast('0') -        assert_equal false, column.type_cast('f') -        assert_equal false, column.type_cast('F') -        assert_equal false, column.type_cast('false') -        assert_equal false, column.type_cast('FALSE') -        assert_equal false, column.type_cast('off') -        assert_equal false, column.type_cast('OFF') -        assert_equal false, column.type_cast(' ') -        assert_equal false, column.type_cast("\u3000\r\n") -        assert_equal false, column.type_cast("\u0000") -        assert_equal false, column.type_cast('SOMETHING RANDOM') -      end - -      def test_type_cast_integer -        column = Column.new("field", nil, "integer") -        assert_equal 1, column.type_cast(1) -        assert_equal 1, column.type_cast('1') -        assert_equal 1, column.type_cast('1ignore') -        assert_equal 0, column.type_cast('bad1') -        assert_equal 0, column.type_cast('bad') -        assert_equal 1, column.type_cast(1.7) -        assert_equal 0, column.type_cast(false) -        assert_equal 1, column.type_cast(true) -        assert_nil column.type_cast(nil) -      end - -      def test_type_cast_non_integer_to_integer -        column = Column.new("field", nil, "integer") -        assert_nil column.type_cast([1,2]) -        assert_nil column.type_cast({1 => 2}) -        assert_nil column.type_cast((1..2)) -      end - -      def test_type_cast_activerecord_to_integer -        column = Column.new("field", nil, "integer") -        firm = Firm.create(:name => 'Apple') -        assert_nil column.type_cast(firm) -      end - -      def test_type_cast_object_without_to_i_to_integer -        column = Column.new("field", nil, "integer") -        assert_nil column.type_cast(Object.new) -      end - -      def test_type_cast_nan_and_infinity_to_integer -        column = Column.new("field", nil, "integer") -        assert_nil column.type_cast(Float::NAN) -        assert_nil column.type_cast(1.0/0.0) -      end - -      def test_type_cast_time -        column = Column.new("field", nil, "time") -        assert_equal nil, column.type_cast(nil) -        assert_equal nil, column.type_cast('') -        assert_equal nil, column.type_cast('ABC') - -        time_string = Time.now.utc.strftime("%T") -        assert_equal time_string, column.type_cast(time_string).strftime("%T") -      end - -      def test_type_cast_datetime_and_timestamp -        [Column.new("field", nil, "datetime"), Column.new("field", nil, "timestamp")].each do |column| -          assert_equal nil, column.type_cast(nil) -          assert_equal nil, column.type_cast('') -          assert_equal nil, column.type_cast('  ') -          assert_equal nil, column.type_cast('ABC') - -          datetime_string = Time.now.utc.strftime("%FT%T") -          assert_equal datetime_string, column.type_cast(datetime_string).strftime("%FT%T") -        end -      end - -      def test_type_cast_date -        column = Column.new("field", nil, "date") -        assert_equal nil, column.type_cast(nil) -        assert_equal nil, column.type_cast('') -        assert_equal nil, column.type_cast(' ') -        assert_equal nil, column.type_cast('ABC') - -        date_string = Time.now.utc.strftime("%F") -        assert_equal date_string, column.type_cast(date_string).strftime("%F") -      end - -      def test_type_cast_duration_to_integer -        column = Column.new("field", nil, "integer") -        assert_equal 1800, column.type_cast(30.minutes) -        assert_equal 7200, column.type_cast(2.hours) -      end - -      def test_string_to_time_with_timezone -        [:utc, :local].each do |zone| -          with_timezone_config default: zone do -            assert_equal Time.utc(2013, 9, 4, 0, 0, 0), Column.string_to_time("Wed, 04 Sep 2013 03:00:00 EAT") -          end -        end -      end -    end -  end -end diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb new file mode 100644 index 0000000000..d4d67487db --- /dev/null +++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb @@ -0,0 +1,61 @@ +require "cases/helper" + +if current_adapter?(:MysqlAdapter, :Mysql2Adapter) +module ActiveRecord +  module ConnectionAdapters +    class MysqlTypeLookupTest < ActiveRecord::TestCase +      setup do +        @connection = ActiveRecord::Base.connection +      end + +      def test_boolean_types +        emulate_booleans(true) do +          assert_lookup_type :boolean, 'tinyint(1)' +          assert_lookup_type :boolean, 'TINYINT(1)' +        end +      end + +      def test_string_types +        assert_lookup_type :string, "enum('one', 'two', 'three')" +        assert_lookup_type :string, "ENUM('one', 'two', 'three')" +        assert_lookup_type :string, "set('one', 'two', 'three')" +        assert_lookup_type :string, "SET('one', 'two', 'three')" +      end + +      def test_binary_types +        assert_lookup_type :binary, 'bit' +        assert_lookup_type :binary, 'BIT' +      end + +      def test_integer_types +        emulate_booleans(false) do +          assert_lookup_type :integer, 'tinyint(1)' +          assert_lookup_type :integer, 'TINYINT(1)' +          assert_lookup_type :integer, 'year' +          assert_lookup_type :integer, 'YEAR' +        end +      end + +      private + +      def assert_lookup_type(type, lookup) +        cast_type = @connection.type_map.lookup(lookup) +        assert_equal type, cast_type.type +      end + +      def emulate_booleans(value) +        old_emulate_booleans = @connection.emulate_booleans +        change_emulate_booleans(value) +        yield +      ensure +        change_emulate_booleans(old_emulate_booleans) +      end + +      def change_emulate_booleans(value) +        @connection.emulate_booleans = value +        @connection.clear_cache! +      end +    end +  end +end +end diff --git a/activerecord/test/cases/connection_adapters/type/type_map_test.rb b/activerecord/test/cases/connection_adapters/type/type_map_test.rb new file mode 100644 index 0000000000..3abd7a276e --- /dev/null +++ b/activerecord/test/cases/connection_adapters/type/type_map_test.rb @@ -0,0 +1,132 @@ +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/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb new file mode 100644 index 0000000000..3958c3bfff --- /dev/null +++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb @@ -0,0 +1,101 @@ +require "cases/helper" + +unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strigns for lookup +module ActiveRecord +  module ConnectionAdapters +    class TypeLookupTest < ActiveRecord::TestCase +      setup do +        @connection = ActiveRecord::Base.connection +      end + +      def test_boolean_types +        assert_lookup_type :boolean, 'boolean' +        assert_lookup_type :boolean, 'BOOLEAN' +      end + +      def test_string_types +        assert_lookup_type :string, 'char' +        assert_lookup_type :string, 'varchar' +        assert_lookup_type :string, 'VARCHAR' +        assert_lookup_type :string, 'varchar(255)' +        assert_lookup_type :string, 'character varying' +      end + +      def test_binary_types +        assert_lookup_type :binary, 'binary' +        assert_lookup_type :binary, 'BINARY' +        assert_lookup_type :binary, 'blob' +        assert_lookup_type :binary, 'BLOB' +      end + +      def test_text_types +        assert_lookup_type :text, 'text' +        assert_lookup_type :text, 'TEXT' +        assert_lookup_type :text, 'clob' +        assert_lookup_type :text, 'CLOB' +      end + +      def test_date_types +        assert_lookup_type :date, 'date' +        assert_lookup_type :date, 'DATE' +      end + +      def test_time_types +        assert_lookup_type :time, 'time' +        assert_lookup_type :time, 'TIME' +      end + +      def test_datetime_types +        assert_lookup_type :datetime, 'datetime' +        assert_lookup_type :datetime, 'DATETIME' +        assert_lookup_type :datetime, 'timestamp' +        assert_lookup_type :datetime, 'TIMESTAMP' +      end + +      def test_decimal_types +        assert_lookup_type :decimal, 'decimal' +        assert_lookup_type :decimal, 'decimal(2,8)' +        assert_lookup_type :decimal, 'DECIMAL' +        assert_lookup_type :decimal, 'numeric' +        assert_lookup_type :decimal, 'numeric(2,8)' +        assert_lookup_type :decimal, 'NUMERIC' +        assert_lookup_type :decimal, 'number' +        assert_lookup_type :decimal, 'number(2,8)' +        assert_lookup_type :decimal, 'NUMBER' +      end + +      def test_float_types +        assert_lookup_type :float, 'float' +        assert_lookup_type :float, 'FLOAT' +        assert_lookup_type :float, 'double' +        assert_lookup_type :float, 'DOUBLE' +      end + +      def test_integer_types +        assert_lookup_type :integer, 'integer' +        assert_lookup_type :integer, 'INTEGER' +        assert_lookup_type :integer, 'tinyint' +        assert_lookup_type :integer, 'smallint' +        assert_lookup_type :integer, 'bigint' +      end + +      def test_decimal_without_scale +        types = %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0) number(2) number(2,0)} +        types.each do |type| +          cast_type = @connection.type_map.lookup(type) + +          assert_equal :decimal, cast_type.type +          assert_equal 2, cast_type.type_cast(2.1) +        end +      end + +      private + +      def assert_lookup_type(type, lookup) +        cast_type = @connection.type_map.lookup(lookup) +        assert_equal type, cast_type.type +      end +    end +  end +end +end diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index ee3d8a81c2..ab2a749ba8 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -51,6 +51,16 @@ class CounterCacheTest < ActiveRecord::TestCase      end    end +  test "reset counters by counter name" do +    # throw the count off by 1 +    Topic.increment_counter(:replies_count, @topic.id) + +    # check that it gets reset +    assert_difference '@topic.reload.replies_count', -1 do +      Topic.reset_counters(@topic.id, :replies_count) +    end +  end +    test 'reset multiple counters' do      Topic.update_counters @topic.id, replies_count: 1, unique_replies_count: 1      assert_difference ['@topic.reload.replies_count', '@topic.reload.unique_replies_count'], -1 do @@ -154,10 +164,10 @@ class CounterCacheTest < ActiveRecord::TestCase      end    end -  test "the passed symbol needs to be an association name" do +  test "the passed symbol needs to be an association name or counter name" do      e = assert_raises(ArgumentError) do -      Topic.reset_counters(@topic.id, :replies_count) +      Topic.reset_counters(@topic.id, :undefined_count)      end -    assert_equal "'Topic' has no association called 'replies_count'", e.message +    assert_equal "'Topic' has no association called 'undefined_count'", e.message    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..9598f0299c --- /dev/null +++ b/activerecord/test/cases/custom_properties_test.rb @@ -0,0 +1,64 @@ +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 +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_equal 2.0, 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_overloaded_properties_have_no_default +      data = OverloadedType.new +      unoverloaded_data = UnoverloadedType.new + +      assert_nil data.overloaded_float +      assert unoverloaded_data.overloaded_float +    end +  end +end diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index 7d438803a1..f885a8cbc0 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -18,7 +18,7 @@ class DefaultTest < ActiveRecord::TestCase      end    end -  if current_adapter?(:PostgreSQLAdapter, :FirebirdAdapter, :OpenBaseAdapter, :OracleAdapter) +  if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)      def test_default_integers        default = Default.new        assert_instance_of Fixnum, default.positive_integer 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/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index f5f85f2412..792950d24d 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -95,16 +95,8 @@ class InheritanceTest < ActiveRecord::TestCase    end    def test_a_bad_type_column -    #SQLServer need to turn Identity Insert On before manually inserting into the Identity column -    if current_adapter?(:SybaseAdapter) -      Company.connection.execute "SET IDENTITY_INSERT companies ON" -    end      Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')" -    #We then need to turn it back Off before continuing. -    if current_adapter?(:SybaseAdapter) -      Company.connection.execute "SET IDENTITY_INSERT companies OFF" -    end      assert_raise(ActiveRecord::SubclassNotFound) { Company.find(100) }    end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index c373dc1511..93fd3b9605 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -339,8 +339,6 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase      def add_counter_column_to(model, col='test_count')        model.connection.add_column model.table_name, col, :integer, :null => false, :default => 0        model.reset_column_information -      # OpenBase does not set a value to existing rows when adding a not null default column -      model.update_all(col => 0) if current_adapter?(:OpenBaseAdapter)      end      def remove_counter_column_from(model, col = :test_count) @@ -367,7 +365,7 @@ end  # is so cumbersome. Will deadlock Ruby threads if the underlying db.execute  # blocks, so separate script called by Kernel#system is needed.  # (See exec vs. async_exec in the PostgreSQL adapter.) -unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? +unless in_memory_db?    class PessimisticLockingTest < ActiveRecord::TestCase      self.use_transactional_fixtures = false      fixtures :people, :readers diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index 5418d913b0..9b26c30d14 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -14,6 +14,7 @@ module ActiveRecord        teardown do          connection.drop_table :testings rescue nil          ActiveRecord::Base.primary_key_prefix_type = nil +        ActiveRecord::Base.clear_cache!        end        def test_create_table_without_id @@ -204,9 +205,9 @@ module ActiveRecord          connection.create_table table_name        end -      # Sybase, and SQLite3 will not allow you to add a NOT NULL +      # SQLite3 will not allow you to add a NOT NULL        # column to a table without a default value. -      unless current_adapter?(:SybaseAdapter, :SQLite3Adapter) +      unless current_adapter?(:SQLite3Adapter)          def test_add_column_not_null_without_default            connection.create_table :testings do |t|              t.column :foo, :string @@ -225,18 +226,28 @@ module ActiveRecord          end          con = connection -        connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter)          connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')" -        connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter)          assert_nothing_raised {connection.add_column :testings, :bar, :string, :null => false, :default => "default" }          assert_raises(ActiveRecord::StatementInvalid) do -          unless current_adapter?(:OpenBaseAdapter) -            connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)" -          else -            connection.insert("INSERT INTO testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) VALUES (2, 'hello', NULL)", -                                     "Testing Insert","id",2) -          end +          connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)" +        end +      end + +      def test_add_column_with_timestamp_type +        connection.create_table :testings do |t| +          t.column :foo, :timestamp +        end + +        klass = Class.new(ActiveRecord::Base) +        klass.table_name = 'testings' + +        assert_equal :datetime, klass.columns_hash['foo'].type + +        if current_adapter?(:PostgreSQLAdapter) +          assert_equal 'timestamp without time zone', klass.columns_hash['foo'].sql_type +        else +          assert_equal klass.connection.type_to_sql('datetime'), klass.columns_hash['foo'].sql_type          end        end diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb index 6a02873cba..984d1c2597 100644 --- a/activerecord/test/cases/migration/column_attributes_test.rb +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -62,7 +62,7 @@ module ActiveRecord          # Do a manual insertion          if current_adapter?(:OracleAdapter)            connection.execute "insert into test_models (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)" -        elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings +        elsif current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003 #before mysql 5.0.3 decimals stored as strings            connection.execute "insert into test_models (wealth) values ('12345678901234567890.0123456789')"          elsif current_adapter?(:PostgreSQLAdapter)            connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)" @@ -160,8 +160,8 @@ module ActiveRecord          assert_equal Fixnum, bob.age.class          assert_equal Time, bob.birthday.class -        if current_adapter?(:OracleAdapter, :SybaseAdapter) -          # Sybase, and Oracle don't differentiate between date/time +        if current_adapter?(:OracleAdapter) +          # Oracle doesn't differentiate between date/time            assert_equal Time, bob.favorite_day.class          else            assert_equal Date, bob.favorite_day.class diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb index 2d7a7ec73a..a7c287515d 100644 --- a/activerecord/test/cases/migration/columns_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -274,6 +274,16 @@ module ActiveRecord        ensure          connection.drop_table(:my_table) rescue nil        end + +      def test_column_with_index +        connection.create_table "my_table", force: true do |t| +          t.string :item_number, index: true +        end + +        assert connection.index_exists?("my_table", :item_number, name: :index_my_table_on_item_number) +      ensure +        connection.drop_table(:my_table) rescue nil +      end      end    end  end diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb index 35af11f672..93c3bfae7a 100644 --- a/activerecord/test/cases/migration/index_test.rb +++ b/activerecord/test/cases/migration/index_test.rb @@ -26,28 +26,25 @@ module ActiveRecord          ActiveRecord::Base.primary_key_prefix_type = nil        end -      unless current_adapter?(:OpenBaseAdapter) -        def test_rename_index -          # keep the names short to make Oracle and similar behave -          connection.add_index(table_name, [:foo], :name => 'old_idx') -          connection.rename_index(table_name, 'old_idx', 'new_idx') - -          # if the adapter doesn't support the indexes call, pick defaults that let the test pass -          assert_not connection.index_name_exists?(table_name, 'old_idx', false) -          assert connection.index_name_exists?(table_name, 'new_idx', true) -        end +      def test_rename_index +        # keep the names short to make Oracle and similar behave +        connection.add_index(table_name, [:foo], :name => 'old_idx') +        connection.rename_index(table_name, 'old_idx', 'new_idx') + +        # if the adapter doesn't support the indexes call, pick defaults that let the test pass +        assert_not connection.index_name_exists?(table_name, 'old_idx', false) +        assert connection.index_name_exists?(table_name, 'new_idx', true) +      end -        def test_double_add_index +      def test_double_add_index +        connection.add_index(table_name, [:foo], :name => 'some_idx') +        assert_raises(ArgumentError) {            connection.add_index(table_name, [:foo], :name => 'some_idx') -          assert_raises(ArgumentError) { -            connection.add_index(table_name, [:foo], :name => 'some_idx') -          } -        end +        } +      end -        def test_remove_nonexistent_index -          # we do this by name, so OpenBase is a wash as noted above -          assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") } -        end +      def test_remove_nonexistent_index +        assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") }        end        def test_add_index_works_with_long_index_names @@ -126,50 +123,37 @@ module ActiveRecord          connection.add_index("testings", "last_name")          connection.remove_index("testings", "last_name") -        # Orcl nds shrt indx nms.  Sybs 2. -        # OpenBase does not have named indexes.  You must specify a single column name -        unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) +        connection.add_index("testings", ["last_name", "first_name"]) +        connection.remove_index("testings", :column => ["last_name", "first_name"]) + +        # Oracle adapter cannot have specified index name larger than 30 characters +        # Oracle adapter is shortening index name when just column list is given +        unless current_adapter?(:OracleAdapter)            connection.add_index("testings", ["last_name", "first_name"]) -          connection.remove_index("testings", :column => ["last_name", "first_name"]) - -          # Oracle adapter cannot have specified index name larger than 30 characters -          # Oracle adapter is shortening index name when just column list is given -          unless current_adapter?(:OracleAdapter) -            connection.add_index("testings", ["last_name", "first_name"]) -            connection.remove_index("testings", :name => :index_testings_on_last_name_and_first_name) -            connection.add_index("testings", ["last_name", "first_name"]) -            connection.remove_index("testings", "last_name_and_first_name") -          end +          connection.remove_index("testings", :name => :index_testings_on_last_name_and_first_name)            connection.add_index("testings", ["last_name", "first_name"]) -          connection.remove_index("testings", ["last_name", "first_name"]) +          connection.remove_index("testings", "last_name_and_first_name") +        end +        connection.add_index("testings", ["last_name", "first_name"]) +        connection.remove_index("testings", ["last_name", "first_name"]) -          connection.add_index("testings", ["last_name"], :length => 10) -          connection.remove_index("testings", "last_name") +        connection.add_index("testings", ["last_name"], :length => 10) +        connection.remove_index("testings", "last_name") -          connection.add_index("testings", ["last_name"], :length => {:last_name => 10}) -          connection.remove_index("testings", ["last_name"]) +        connection.add_index("testings", ["last_name"], :length => {:last_name => 10}) +        connection.remove_index("testings", ["last_name"]) -          connection.add_index("testings", ["last_name", "first_name"], :length => 10) -          connection.remove_index("testings", ["last_name", "first_name"]) +        connection.add_index("testings", ["last_name", "first_name"], :length => 10) +        connection.remove_index("testings", ["last_name", "first_name"]) -          connection.add_index("testings", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) -          connection.remove_index("testings", ["last_name", "first_name"]) -        end +        connection.add_index("testings", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) +        connection.remove_index("testings", ["last_name", "first_name"]) -        # quoting -        # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word -        # OpenBase does not have named indexes.  You must specify a single column name -        unless current_adapter?(:OpenBaseAdapter) -          connection.add_index("testings", ["key"], :name => "key_idx", :unique => true) -          connection.remove_index("testings", :name => "key_idx", :unique => true) -        end +        connection.add_index("testings", ["key"], :name => "key_idx", :unique => true) +        connection.remove_index("testings", :name => "key_idx", :unique => true) -        # Sybase adapter does not support indexes on :boolean columns -        # OpenBase does not have named indexes.  You must specify a single column -        unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) -          connection.add_index("testings", %w(last_name first_name administrator), :name => "named_admin") -          connection.remove_index("testings", :name => "named_admin") -        end +        connection.add_index("testings", %w(last_name first_name administrator), :name => "named_admin") +        connection.remove_index("testings", :name => "named_admin")          # Selected adapters support index sort order          if current_adapter?(:SQLite3Adapter, :MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb index 2a7fafc559..e0b03f4735 100644 --- a/activerecord/test/cases/migration/rename_table_test.rb +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -42,13 +42,8 @@ module ActiveRecord        def test_rename_table          rename_table :test_models, :octopi -        # Using explicit id in insert for compatibility across all databases -        connection.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter) -          connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" -        connection.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter) -          assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")        end @@ -57,10 +52,7 @@ module ActiveRecord          rename_table :test_models, :octopi -        # Using explicit id in insert for compatibility across all databases -        connection.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)          connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" -        connection.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)          assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")          index = connection.indexes(:octopi).first diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 455ec78f68..46f43f60ac 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -11,7 +11,10 @@ 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 +  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/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb index c70a8f296f..14d4ef457d 100644 --- a/activerecord/test/cases/multiparameter_attributes_test.rb +++ b/activerecord/test/cases/multiparameter_attributes_test.rb @@ -240,8 +240,8 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase      Topic.skip_time_zone_conversion_for_attributes = []    end -  # Oracle, and Sybase do not have a TIME datatype. -  unless current_adapter?(:OracleAdapter, :SybaseAdapter) +  # Oracle does not have a TIME datatype. +  unless current_adapter?(:OracleAdapter)      def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion        with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do          attributes = { diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 5d963098fb..bc5ccd0fe9 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -843,4 +843,15 @@ class PersistenceTest < ActiveRecord::TestCase      assert_equal "Wright Glider", Aircraft.last.name    end + +  def test_instantiate_creates_a_new_instance +    post = Post.instantiate("title" => "appropriate documentation", "type" => "SpecialPost") +    assert_equal "appropriate documentation", post.title +    assert_instance_of SpecialPost, post + +    # body was not initialized +    assert_raises ActiveModel::MissingAttributeError do +      post.body +    end +  end  end diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index dd0e934ec2..8eea10143f 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -48,4 +48,4 @@ class PooledConnectionsTest < ActiveRecord::TestCase    def add_record(name)      ActiveRecord::Base.connection_pool.with_connection { Project.create! :name => name }    end -end unless current_adapter?(:FrontBase) || in_memory_db? +end unless in_memory_db? diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 56d0dd6a77..c719918fd7 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -149,38 +149,6 @@ class PrimaryKeysTest < ActiveRecord::TestCase      assert_equal k.connection.quote_column_name("foo"), k.quoted_primary_key    end -  def test_two_models_with_same_table_but_different_primary_key -    k1 = Class.new(ActiveRecord::Base) -    k1.table_name = 'posts' -    k1.primary_key = 'id' - -    k2 = Class.new(ActiveRecord::Base) -    k2.table_name = 'posts' -    k2.primary_key = 'title' - -    assert k1.columns.find { |c| c.name == 'id' }.primary -    assert !k1.columns.find { |c| c.name == 'title' }.primary -    assert k1.columns_hash['id'].primary -    assert !k1.columns_hash['title'].primary - -    assert !k2.columns.find { |c| c.name == 'id' }.primary -    assert k2.columns.find { |c| c.name == 'title' }.primary -    assert !k2.columns_hash['id'].primary -    assert k2.columns_hash['title'].primary -  end - -  def test_models_with_same_table_have_different_columns -    k1 = Class.new(ActiveRecord::Base) -    k1.table_name = 'posts' - -    k2 = Class.new(ActiveRecord::Base) -    k2.table_name = 'posts' - -    k1.columns.zip(k2.columns).each do |col1, col2| -      assert !col1.equal?(col2) -    end -  end -    def test_auto_detect_primary_key_from_schema      MixedCaseMonkey.reset_primary_key      assert_equal "monkeyID", MixedCaseMonkey.primary_key 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/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb index c6decaad89..b9e69bdb08 100644 --- a/activerecord/test/cases/relation/where_chain_test.rb +++ b/activerecord/test/cases/relation/where_chain_test.rb @@ -99,7 +99,7 @@ module ActiveRecord        assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual        assert_equal 'ruby on rails', bind.last      end -     +      def test_rewhere_with_one_condition        relation = Post.where(title: 'hello').where(title: 'world').rewhere(title: 'alone') diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 6a880c6680..88df997a2f 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -366,6 +366,14 @@ class RelationTest < ActiveRecord::TestCase      assert_equal({ 'salary' => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash)    end +  def test_null_relation_sum +    ac = Aircraft.new +    assert_equal Hash.new, ac.engines.group(:id).sum(:id) +    assert_equal        0, ac.engines.count +    ac.save +    assert_equal Hash.new, ac.engines.group(:id).sum(:id) +    assert_equal        0, ac.engines.count +  end    def test_null_relation_count      ac = Aircraft.new @@ -376,6 +384,42 @@ class RelationTest < ActiveRecord::TestCase      assert_equal        0, ac.engines.count    end +  def test_null_relation_size +    ac = Aircraft.new +    assert_equal Hash.new, ac.engines.group(:id).size +    assert_equal        0, ac.engines.size +    ac.save +    assert_equal Hash.new, ac.engines.group(:id).size +    assert_equal        0, ac.engines.size +  end + +  def test_null_relation_average +    ac = Aircraft.new +    assert_equal Hash.new, ac.engines.group(:car_id).average(:id) +    assert_equal        nil, ac.engines.average(:id) +    ac.save +    assert_equal Hash.new, ac.engines.group(:car_id).average(:id) +    assert_equal        nil, ac.engines.average(:id) +  end + +  def test_null_relation_minimum +    ac = Aircraft.new +    assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id) +    assert_equal        nil, ac.engines.minimum(:id) +    ac.save +    assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id) +    assert_equal        nil, ac.engines.minimum(:id) +  end + +  def test_null_relation_maximum +    ac = Aircraft.new +    assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id) +    assert_equal        nil, ac.engines.maximum(:id) +    ac.save +    assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id) +    assert_equal        nil, ac.engines.maximum(:id) +  end +    def test_joins_with_nil_argument      assert_nothing_raised { DependentFirm.joins(nil).first }    end @@ -699,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 @@ -831,8 +882,12 @@ class RelationTest < ActiveRecord::TestCase      assert davids.loaded?    end -  def test_delete_all_limit_error +  def test_delete_all_with_unpermitted_relation_raises_error      assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all } +    assert_raises(ActiveRecord::ActiveRecordError) { Author.uniq.delete_all } +    assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all } +    assert_raises(ActiveRecord::ActiveRecordError) { Author.having('SUM(id) < 3').delete_all } +    assert_raises(ActiveRecord::ActiveRecordError) { Author.offset(10).delete_all }    end    def test_select_with_aggregates diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index fd0ef2f89f..9602252b2e 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -1,10 +1,8 @@  require "cases/helper"  class SchemaDumperTest < ActiveRecord::TestCase -  def setup -    super +  setup do      ActiveRecord::SchemaMigration.create_table -    @stream = StringIO.new    end    def standard_dump @@ -25,7 +23,8 @@ class SchemaDumperTest < ActiveRecord::TestCase    end    def test_magic_comment -    assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump +    output = standard_dump +    assert_match "# encoding: #{@stream.external_encoding.name}", output    end    def test_schema_dump @@ -353,9 +352,9 @@ class SchemaDumperTest < ActiveRecord::TestCase      output = standard_dump      # Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers      if current_adapter?(:OracleAdapter) -      assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38,\s+scale: 0}, output +      assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38}, output      else -      assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55,\s+scale: 0}, output +      assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55}, output      end    end diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index c46060a646..7dd1f10ce9 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -1,8 +1,11 @@  require "cases/helper"  require 'models/contact'  require 'models/topic' +require 'models/book'  class SerializationTest < ActiveRecord::TestCase +  fixtures :books +    FORMATS = [ :xml, :json ]    def setup @@ -65,4 +68,20 @@ class SerializationTest < ActiveRecord::TestCase    ensure      ActiveRecord::Base.include_root_in_json = original_root_in_json    end + +  def test_read_attribute_for_serialization_with_format_after_init +    klazz = Class.new(ActiveRecord::Base) +    klazz.table_name = 'books' + +    book = klazz.new(format: 'paperback') +    assert_equal 'paperback', book.read_attribute_for_serialization(:format) +  end + +  def test_read_attribute_for_serialization_with_format_after_find +    klazz = Class.new(ActiveRecord::Base) +    klazz.table_name = 'books' + +    book = klazz.find(books(:awdr).id) +    assert_equal 'paperback', book.read_attribute_for_serialization(:format) +  end  end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 5609cf310c..c8f9d7cf87 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -244,4 +244,20 @@ class SerializedAttributeTest < ActiveRecord::TestCase      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) + +    t.update_column(:content, Topic.serialized_attributes["content"].dump("second")) +    assert_equal("second", t.content) +  end + +  def test_serialized_column_should_unserialize_after_update_attribute +    t = Topic.create(content: "first") +    assert_equal("first", t.content) + +    t.update_attribute(:content, "second") +    assert_equal("second", t.content) +  end  end diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index 803a054d7e..b6c5511849 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -10,13 +10,7 @@ module ActiveRecord      end      def assert_date_from_db(expected, actual, message = nil) -      # SybaseAdapter doesn't have a separate column type just for dates, -      # so the time is in the string and incorrectly formatted -      if current_adapter?(:SybaseAdapter) -        assert_equal expected.to_s, actual.to_date.to_s, message -      else -        assert_equal expected.to_s, actual.to_s, message -      end +      assert_equal expected.to_s, actual.to_s, message      end      def capture_sql diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb new file mode 100644 index 0000000000..5d5f442d3a --- /dev/null +++ b/activerecord/test/cases/types_test.rb @@ -0,0 +1,159 @@ +require "cases/helper" +require 'models/company' + +module ActiveRecord +  module ConnectionAdapters +    class TypesTest < ActiveRecord::TestCase +      def test_type_cast_boolean +        type = Type::Boolean.new +        assert type.type_cast('').nil? +        assert type.type_cast(nil).nil? + +        assert type.type_cast(true) +        assert type.type_cast(1) +        assert type.type_cast('1') +        assert type.type_cast('t') +        assert type.type_cast('T') +        assert type.type_cast('true') +        assert type.type_cast('TRUE') +        assert type.type_cast('on') +        assert type.type_cast('ON') + +        # explicitly check for false vs nil +        assert_equal false, type.type_cast(false) +        assert_equal false, type.type_cast(0) +        assert_equal false, type.type_cast('0') +        assert_equal false, type.type_cast('f') +        assert_equal false, type.type_cast('F') +        assert_equal false, type.type_cast('false') +        assert_equal false, type.type_cast('FALSE') +        assert_equal false, type.type_cast('off') +        assert_equal false, type.type_cast('OFF') +        assert_equal false, type.type_cast(' ') +        assert_equal false, type.type_cast("\u3000\r\n") +        assert_equal false, type.type_cast("\u0000") +        assert_equal false, type.type_cast('SOMETHING RANDOM') +      end + +      def test_type_cast_string +        type = Type::String.new +        assert_equal "1", type.type_cast(true) +        assert_equal "0", type.type_cast(false) +        assert_equal "123", type.type_cast(123) +      end + +      def test_type_cast_integer +        type = Type::Integer.new +        assert_equal 1, type.type_cast(1) +        assert_equal 1, type.type_cast('1') +        assert_equal 1, type.type_cast('1ignore') +        assert_equal 0, type.type_cast('bad1') +        assert_equal 0, type.type_cast('bad') +        assert_equal 1, type.type_cast(1.7) +        assert_equal 0, type.type_cast(false) +        assert_equal 1, type.type_cast(true) +        assert_nil type.type_cast(nil) +      end + +      def test_type_cast_non_integer_to_integer +        type = Type::Integer.new +        assert_nil type.type_cast([1,2]) +        assert_nil type.type_cast({1 => 2}) +        assert_nil type.type_cast((1..2)) +      end + +      def test_type_cast_activerecord_to_integer +        type = Type::Integer.new +        firm = Firm.create(:name => 'Apple') +        assert_nil type.type_cast(firm) +      end + +      def test_type_cast_object_without_to_i_to_integer +        type = Type::Integer.new +        assert_nil type.type_cast(Object.new) +      end + +      def test_type_cast_nan_and_infinity_to_integer +        type = Type::Integer.new +        assert_nil type.type_cast(Float::NAN) +        assert_nil type.type_cast(1.0/0.0) +      end + +      def test_type_cast_float +        type = Type::Float.new +        assert_equal 1.0, type.type_cast("1") +      end + +      def test_type_cast_decimal +        type = Type::Decimal.new +        assert_equal BigDecimal.new("0"), type.type_cast(BigDecimal.new("0")) +        assert_equal BigDecimal.new("123"), type.type_cast(123.0) +        assert_equal BigDecimal.new("1"), type.type_cast(:"1") +      end + +      def test_type_cast_binary +        type = Type::Binary.new +        assert_equal nil, type.type_cast(nil) +        assert_equal "1", type.type_cast("1") +        assert_equal 1, type.type_cast(1) +      end + +      def test_type_cast_time +        type = Type::Time.new +        assert_equal nil, type.type_cast(nil) +        assert_equal nil, type.type_cast('') +        assert_equal nil, type.type_cast('ABC') + +        time_string = Time.now.utc.strftime("%T") +        assert_equal time_string, type.type_cast(time_string).strftime("%T") +      end + +      def test_type_cast_datetime_and_timestamp +        type = Type::DateTime.new +        assert_equal nil, type.type_cast(nil) +        assert_equal nil, type.type_cast('') +        assert_equal nil, type.type_cast('  ') +        assert_equal nil, type.type_cast('ABC') + +        datetime_string = Time.now.utc.strftime("%FT%T") +        assert_equal datetime_string, type.type_cast(datetime_string).strftime("%FT%T") +      end + +      def test_type_cast_date +        type = Type::Date.new +        assert_equal nil, type.type_cast(nil) +        assert_equal nil, type.type_cast('') +        assert_equal nil, type.type_cast(' ') +        assert_equal nil, type.type_cast('ABC') + +        date_string = Time.now.utc.strftime("%F") +        assert_equal date_string, type.type_cast(date_string).strftime("%F") +      end + +      def test_type_cast_duration_to_integer +        type = Type::Integer.new +        assert_equal 1800, type.type_cast(30.minutes) +        assert_equal 7200, type.type_cast(2.hours) +      end + +      def test_string_to_time_with_timezone +        [:utc, :local].each do |zone| +          with_timezone_config default: zone do +            type = Type::DateTime.new +            assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.type_cast("Wed, 04 Sep 2013 03:00:00 EAT") +          end +        end +      end + +      if current_adapter?(:SQLite3Adapter) +        def test_binary_encoding +          type = SQLite3Binary.new +          utf8_string = "a string".encode(Encoding::UTF_8) +          type_cast = type.type_cast(utf8_string) + +          assert_equal Encoding::ASCII_8BIT, type_cast.encoding +        end +      end +    end +  end +end diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb index 3cb617497d..1a690c01a6 100644 --- a/activerecord/test/cases/xml_serialization_test.rb +++ b/activerecord/test/cases/xml_serialization_test.rb @@ -226,7 +226,6 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase      xml = REXML::Document.new(topics(:first).to_xml(:indent => 0))      bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema      written_on_in_current_timezone = topics(:first).written_on.xmlschema -    last_read_in_current_timezone = topics(:first).last_read.xmlschema      assert_equal "topic", xml.root.name      assert_equal "The First Topic" , xml.elements["//title"].text @@ -248,14 +247,9 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase      assert_equal "integer", xml.elements["//parent-id"].attributes['type']      assert_equal "true", xml.elements["//parent-id"].attributes['nil'] -    if current_adapter?(:SybaseAdapter) -      assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text -      assert_equal "dateTime" , xml.elements["//last-read"].attributes['type'] -    else -      # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) -      assert_equal "2004-04-15", xml.elements["//last-read"].text -      assert_equal "date" , xml.elements["//last-read"].attributes['type'] -    end +    # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) +    assert_equal "2004-04-15", xml.elements["//last-read"].text +    assert_equal "date" , xml.elements["//last-read"].attributes['type']      # Oracle and DB2 don't have true boolean or time-only fields      unless current_adapter?(:OracleAdapter, :DB2Adapter) diff --git a/activerecord/test/fixtures/books.yml b/activerecord/test/fixtures/books.yml index fb48645456..abe56752c6 100644 --- a/activerecord/test/fixtures/books.yml +++ b/activerecord/test/fixtures/books.yml @@ -2,8 +2,10 @@ awdr:    author_id: 1    id: 1    name: "Agile Web Development with Rails" +  format: "paperback"  rfr:    author_id: 1    id: 2    name: "Ruby for Rails" +  format: "ebook" diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index bf0162d09b..15970758db 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -7,6 +7,9 @@ class Comment < ActiveRecord::Base    scope :created, -> { all }    belongs_to :post, :counter_cache => true +  belongs_to :author,   polymorphic: true +  belongs_to :resource, polymorphic: true +    has_many :ratings    belongs_to :first_post, :foreign_key => :post_id diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 0a614c3bfd..5bd2f00129 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -76,12 +76,6 @@ class AuditLog < ActiveRecord::Base    belongs_to :unvalidated_developer, :class_name => 'Developer'  end -DeveloperSalary = Struct.new(:amount) -class DeveloperWithAggregate < ActiveRecord::Base -  self.table_name = 'developers' -  composed_of :salary, :class_name => 'DeveloperSalary', :mapping => [%w(salary amount)] -end -  class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base    self.table_name = 'developers'    has_and_belongs_to_many :projects, :join_table => 'developers_projects', :foreign_key => 'developer_id' diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index a78074d530..4cce58f4f4 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -9,14 +9,6 @@ ActiveRecord::Schema.define do    #put adapter specific setup here    case adapter_name -    # For Firebird, set the sequence values 10000 when create_table is called; -    # this prevents primary key collisions between "normally" created records -    # and fixture-based (YAML) records. -  when "Firebird" -    def create_table(*args, &block) -      ActiveRecord::Base.connection.create_table(*args, &block) -      ActiveRecord::Base.connection.execute "SET GENERATOR #{args.first}_seq TO 10000" -    end    when "PostgreSQL"      enable_uuid_ossp!(ActiveRecord::Base.connection)      create_table :uuid_parents, id: :uuid, force: true do |t| @@ -111,6 +103,7 @@ ActiveRecord::Schema.define do    create_table :books, force: true do |t|      t.integer :author_id +    t.string :format      t.column :name, :string      t.column :status, :integer, default: 0      t.column :read_status, :integer, default: 0 @@ -195,6 +188,9 @@ ActiveRecord::Schema.define do      t.integer :taggings_count, default: 0      t.integer :children_count, default: 0      t.integer :parent_id +    t.references :author, polymorphic: true +    t.string :resource_id +    t.string :resource_type    end    create_table :companies, force: true do |t| @@ -859,6 +855,12 @@ 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 +  end  end  Course.connection.create_table :courses, force: true do |t| diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 866a5a958d..2a992f60bb 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,21 @@ +*   `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 +    `Hash#deep_symbolize_keys!`. + +    *OZAWA Sakuro* + +*   Fixed confusing `DelegationError` in `Module#delegate`. + +    See #15186. + +    *Vladimir Yarotsky* + +*   Fixed `ActiveSupport::Subscriber` so that no duplicate subscriber is created +    when a subscriber method is redefined. + +    *Dennis Schön* +  *   Remove deprecated string based terminators for `ActiveSupport::Callbacks`.      *Eileen M. Uchitelle* diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 67f58bc0fe..caa499dfa2 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -5,6 +5,8 @@ class Array    #   %w( a b c d ).from(2)  # => ["c", "d"]    #   %w( a b c d ).from(10) # => []    #   %w().from(0)           # => [] +  #   %w( a b c d ).from(-2) # => ["c", "d"] +  #   %w( a b c ).from(-10)  # => []    def from(position)      self[position, length] || []    end @@ -15,8 +17,10 @@ class Array    #   %w( a b c d ).to(2)  # => ["a", "b", "c"]    #   %w( a b c d ).to(10) # => ["a", "b", "c", "d"]    #   %w().to(0)           # => [] +  #   %w( a b c d ).to(-2) # => ["a", "b", "c"] +  #   %w( a b c ).to(-10)  # => []    def to(position) -    first position + 1 +    self[0..position]    end    # Equal to <tt>self[1]</tt>. diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index 3d41aa8572..28536e32a4 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -75,34 +75,26 @@ class Hash    # Returns a new hash with all keys converted by the block operation.    # This includes the keys from the root hash and from all -  # nested hashes. +  # nested hashes and arrays.    #    #  hash = { person: { name: 'Rob', age: '28' } }    #    #  hash.deep_transform_keys{ |key| key.to_s.upcase }    #  # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}    def deep_transform_keys(&block) -    result = {} -    each do |key, value| -      result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value -    end -    result +    _deep_transform_keys_in_object(self, &block)    end    # Destructively convert all keys by using the block operation.    # This includes the keys from the root hash and from all -  # nested hashes. +  # nested hashes and arrays.    def deep_transform_keys!(&block) -    keys.each do |key| -      value = delete(key) -      self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value -    end -    self +    _deep_transform_keys_in_object!(self, &block)    end    # Returns a new hash with all keys converted to strings.    # This includes the keys from the root hash and from all -  # nested hashes. +  # nested hashes and arrays.    #    #   hash = { person: { name: 'Rob', age: '28' } }    # @@ -114,14 +106,14 @@ class Hash    # Destructively convert all keys to strings.    # This includes the keys from the root hash and from all -  # nested hashes. +  # nested hashes and arrays.    def deep_stringify_keys!      deep_transform_keys!{ |key| key.to_s }    end    # Returns a new hash with all keys converted to symbols, as long as    # they respond to +to_sym+. This includes the keys from the root hash -  # and from all nested hashes. +  # and from all nested hashes and arrays.    #    #   hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }    # @@ -133,8 +125,38 @@ class Hash    # Destructively convert all keys to symbols, as long as they respond    # to +to_sym+. This includes the keys from the root hash and from all -  # nested hashes. +  # nested hashes and arrays.    def deep_symbolize_keys!      deep_transform_keys!{ |key| key.to_sym rescue key }    end + +  private +    # support methods for deep transforming nested hashes and arrays +    def _deep_transform_keys_in_object(object, &block) +      case object +      when Hash +        object.each_with_object({}) do |(key, value), result| +          result[yield(key)] = _deep_transform_keys_in_object(value, &block) +        end +      when Array +        object.map {|e| _deep_transform_keys_in_object(e, &block) } +      else +        object +      end +    end + +    def _deep_transform_keys_in_object!(object, &block) +      case object +      when Hash +        object.keys.each do |key| +          value = object.delete(key) +          object[yield(key)] = _deep_transform_keys_in_object!(value, &block) +        end +        object +      when Array +        object.map! {|e| _deep_transform_keys_in_object!(e, &block)} +      else +        object +      end +    end  end diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index f855833a24..e926392952 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -170,38 +170,26 @@ class Module        # methods still accept two arguments.        definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block' -      # The following generated methods call the target exactly once, storing +      # The following generated method calls the target exactly once, storing        # the returned value in a dummy variable.        #        # Reason is twofold: On one hand doing less calls is in general better.        # On the other hand it could be that the target has side-effects,        # whereas conceptually, from the user point of view, the delegator should        # be doing one call. -      if allow_nil -        method_def = [ -          "def #{method_prefix}#{method}(#{definition})",  # def customer_name(*args, &block) -          "_ = #{to}",                                     #   _ = client -          "if !_.nil? || nil.respond_to?(:#{method})",     #   if !_.nil? || nil.respond_to?(:name) -          "  _.#{method}(#{definition})",                  #     _.name(*args, &block) -          "end",                                           #   end -        "end"                                              # end -        ].join ';' -      else -        exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") -        method_def = [ -          "def #{method_prefix}#{method}(#{definition})",  # def customer_name(*args, &block) -          " _ = #{to}",                                    #   _ = client -          "  _.#{method}(#{definition})",                  #   _.name(*args, &block) -          "rescue NoMethodError => e",                     # rescue NoMethodError => e -          "  if _.nil? && e.name == :#{method}",           #   if _.nil? && e.name == :name -          "    #{exception}",                              #     # add helpful message to the exception -          "  else",                                        #   else -          "    raise",                                     #     raise -          "  end",                                         #   end -          "end"                                            # end -        ].join ';' -      end +      exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") + +      method_def = [ +        "def #{method_prefix}#{method}(#{definition})", +        "  _ = #{to}", +        "  if !_.nil? || nil.respond_to?(:#{method})", +        "    _.#{method}(#{definition})", +        "  else", +        "    #{exception unless allow_nil}", +        "  end", +        "end" +      ].join ';'        module_eval(method_def, file, line)      end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 59675d744e..8a545e4386 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -180,10 +180,11 @@ module ActiveSupport #:nodoc:          Dependencies.load_missing_constant(from_mod, const_name)        end -      # Dependencies assumes the name of the module reflects the nesting (unless -      # it can be proven that is not the case), and the path to the file that -      # defines the constant. Anonymous modules cannot follow these conventions -      # and we assume therefore the user wants to refer to a top-level constant. +      # We assume that the name of the module reflects the nesting +      # (unless it can be proven that is not the case) and the path to the file +      # that defines the constant. Anonymous modules cannot follow these +      # conventions and therefore we assume that the user wants to refer to a +      # top-level constant.        def guess_for_anonymous(const_name)          if Object.const_defined?(const_name)            raise NameError, "#{const_name} cannot be autoloaded from an anonymous class or module" diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 09eb732ef7..0ae641d05b 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -105,8 +105,7 @@ module ActiveSupport        # We define it as a workaround to Ruby 2.0.0-p353 bug.        # For more information, check rails/rails#13055. -      # It should be dropped once a new Ruby patch-level -      # release after 2.0.0-p353 happens. +      # Remove it when we drop support for 2.0.0-p353.        def ===(other) #:nodoc:          value === other        end diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index 23cd6716e3..a45f009859 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -1,4 +1,3 @@ -require "active_support"  require "active_support/file_update_checker"  require "active_support/core_ext/array/wrap" diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index 133aa6a054..6d09ad4bd4 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -1,4 +1,3 @@ -require "active_support"  require "active_support/i18n_railtie"  module ActiveSupport diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb index 4b9b48539f..98be78b41b 100644 --- a/activesupport/lib/active_support/subscriber.rb +++ b/activesupport/lib/active_support/subscriber.rb @@ -64,12 +64,21 @@ module ActiveSupport        def add_event_subscriber(event)          return if %w{ start finish }.include?(event.to_s) -        notifier.subscribe("#{event}.#{namespace}", subscriber) +        pattern = "#{event}.#{namespace}" + +        # don't add multiple subscribers (eg. if methods are redefined) +        return if subscriber.patterns.include?(pattern) + +        subscriber.patterns << pattern +        notifier.subscribe(pattern, subscriber)        end      end +    attr_reader :patterns # :nodoc: +      def initialize        @queue_key = [self.class.name, object_id].join "-" +      @patterns  = []        super      end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 2fb5c04316..e6c125bfdd 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -1,5 +1,3 @@ -gem 'minitest' # make sure we get the gem, not stdlib -require 'minitest'  require 'active_support/testing/tagged_logging'  require 'active_support/testing/setup_and_teardown'  require 'active_support/testing/assertions' 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/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 72efb09fbe..ee62523824 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -188,16 +188,72 @@ module ActiveSupport      @lazy_zones_map = ThreadSafe::Cache.new -    # Assumes self represents an offset from UTC in seconds (as returned from -    # Time#utc_offset) and turns this into an +HH:MM formatted string. -    # -    #   TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00" -    def self.seconds_to_utc_offset(seconds, colon = true) -      format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON -      sign = (seconds < 0 ? '-' : '+') -      hours = seconds.abs / 3600 -      minutes = (seconds.abs % 3600) / 60 -      format % [sign, hours, minutes] +    class << self +      # Assumes self represents an offset from UTC in seconds (as returned from +      # Time#utc_offset) and turns this into an +HH:MM formatted string. +      # +      #   TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00" +      def seconds_to_utc_offset(seconds, colon = true) +        format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON +        sign = (seconds < 0 ? '-' : '+') +        hours = seconds.abs / 3600 +        minutes = (seconds.abs % 3600) / 60 +        format % [sign, hours, minutes] +      end + +      def find_tzinfo(name) +        TZInfo::TimezoneProxy.new(MAPPING[name] || name) +      end + +      alias_method :create, :new + +      # Returns a TimeZone instance with the given name, or +nil+ if no +      # such TimeZone instance exists. (This exists to support the use of +      # this class with the +composed_of+ macro.) +      def new(name) +        self[name] +      end + +      # Returns an array of all TimeZone objects. There are multiple +      # TimeZone objects per time zone, in many cases, to make it easier +      # for users to find their own time zone. +      def all +        @zones ||= zones_map.values.sort +      end + +      def zones_map +        @zones_map ||= begin +          MAPPING.each_key {|place| self[place]} # load all the zones +          @lazy_zones_map +        end +      end + +      # Locate a specific time zone object. If the argument is a string, it +      # is interpreted to mean the name of the timezone to locate. If it is a +      # numeric value it is either the hour offset, or the second offset, of the +      # timezone to find. (The first one with that offset will be returned.) +      # Returns +nil+ if no such time zone is known to the system. +      def [](arg) +        case arg +          when String +          begin +            @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset } +          rescue TZInfo::InvalidTimezoneIdentifier +            nil +          end +          when Numeric, ActiveSupport::Duration +            arg *= 3600 if arg.abs <= 13 +            all.find { |z| z.utc_offset == arg.to_i } +          else +            raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" +        end +      end + +      # A convenience method for returning a collection of TimeZone objects +      # for time zones in the USA. +      def us_zones +        @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ } +      end      end      include Comparable @@ -361,66 +417,9 @@ module ActiveSupport        tzinfo.periods_for_local(time)      end -    def self.find_tzinfo(name) -      TZInfo::TimezoneProxy.new(MAPPING[name] || name) -    end - -    class << self -      alias_method :create, :new - -      # Returns a TimeZone instance with the given name, or +nil+ if no -      # such TimeZone instance exists. (This exists to support the use of -      # this class with the +composed_of+ macro.) -      def new(name) -        self[name] -      end - -      # Returns an array of all TimeZone objects. There are multiple -      # TimeZone objects per time zone, in many cases, to make it easier -      # for users to find their own time zone. -      def all -        @zones ||= zones_map.values.sort -      end - -      def zones_map -        @zones_map ||= begin -          MAPPING.each_key {|place| self[place]} # load all the zones -          @lazy_zones_map -        end -      end - -      # Locate a specific time zone object. If the argument is a string, it -      # is interpreted to mean the name of the timezone to locate. If it is a -      # numeric value it is either the hour offset, or the second offset, of the -      # timezone to find. (The first one with that offset will be returned.) -      # Returns +nil+ if no such time zone is known to the system. -      def [](arg) -        case arg -          when String -          begin -            @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset } -          rescue TZInfo::InvalidTimezoneIdentifier -            nil -          end -          when Numeric, ActiveSupport::Duration -            arg *= 3600 if arg.abs <= 13 -            all.find { |z| z.utc_offset == arg.to_i } -          else -            raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" -        end -      end - -      # A convenience method for returning a collection of TimeZone objects -      # for time zones in the USA. -      def us_zones -        @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ } -      end -    end -      private - -    def time_now -      Time.now -    end +      def time_now +        Time.now +      end    end  end diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index e0e54f47e4..bd1b818717 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -10,12 +10,16 @@ class ArrayExtAccessTests < ActiveSupport::TestCase      assert_equal %w( a b c d ), %w( a b c d ).from(0)      assert_equal %w( c d ), %w( a b c d ).from(2)      assert_equal %w(), %w( a b c d ).from(10) +    assert_equal %w( d e ), %w( a b c d e ).from(-2) +    assert_equal %w(), %w( a b c d e ).from(-10)    end    def test_to      assert_equal %w( a ), %w( a b c d ).to(0)      assert_equal %w( a b c ), %w( a b c d ).to(2)      assert_equal %w( a b c d ), %w( a b c d ).to(10) +    assert_equal %w( a b c ), %w( a b c d ).to(-2) +    assert_equal %w(), %w( a b c ).to(-10)    end    def test_second_through_tenth diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index ad354a4c30..cb706d77c2 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -46,6 +46,10 @@ class HashExtTest < ActiveSupport::TestCase      @nested_illegal_symbols = { [] => { [] => 3} }      @upcase_strings = { 'A' => 1, 'B' => 2 }      @nested_upcase_strings = { 'A' => { 'B' => { 'C' => 3 } } } +    @string_array_of_hashes = { 'a' => [ { 'b' => 2 }, { 'c' => 3 }, 4 ] } +    @symbol_array_of_hashes = { :a => [ { :b => 2 }, { :c => 3 }, 4 ] } +    @mixed_array_of_hashes = { :a => [ { :b => 2 }, { 'c' => 3 }, 4 ] } +    @upcase_array_of_hashes = { 'A' => [ { 'B' => 2 }, { 'C' => 3 }, 4 ] }    end    def test_methods @@ -84,6 +88,9 @@ class HashExtTest < ActiveSupport::TestCase      assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys{ |key| key.to_s.upcase }      assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys{ |key| key.to_s.upcase }      assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys{ |key| key.to_s.upcase } +    assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase } +    assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase } +    assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }    end    def test_deep_transform_keys_not_mutates @@ -109,6 +116,9 @@ class HashExtTest < ActiveSupport::TestCase      assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }      assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }      assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } +    assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } +    assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } +    assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }    end    def test_deep_transform_keys_with_bang_mutates @@ -134,6 +144,9 @@ class HashExtTest < ActiveSupport::TestCase      assert_equal @nested_symbols, @nested_symbols.deep_symbolize_keys      assert_equal @nested_symbols, @nested_strings.deep_symbolize_keys      assert_equal @nested_symbols, @nested_mixed.deep_symbolize_keys +    assert_equal @symbol_array_of_hashes, @string_array_of_hashes.deep_symbolize_keys +    assert_equal @symbol_array_of_hashes, @symbol_array_of_hashes.deep_symbolize_keys +    assert_equal @symbol_array_of_hashes, @mixed_array_of_hashes.deep_symbolize_keys    end    def test_deep_symbolize_keys_not_mutates @@ -159,6 +172,9 @@ class HashExtTest < ActiveSupport::TestCase      assert_equal @nested_symbols, @nested_symbols.deep_dup.deep_symbolize_keys!      assert_equal @nested_symbols, @nested_strings.deep_dup.deep_symbolize_keys!      assert_equal @nested_symbols, @nested_mixed.deep_dup.deep_symbolize_keys! +    assert_equal @symbol_array_of_hashes, @string_array_of_hashes.deep_dup.deep_symbolize_keys! +    assert_equal @symbol_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_symbolize_keys! +    assert_equal @symbol_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_symbolize_keys!    end    def test_deep_symbolize_keys_with_bang_mutates @@ -204,6 +220,9 @@ class HashExtTest < ActiveSupport::TestCase      assert_equal @nested_strings, @nested_symbols.deep_stringify_keys      assert_equal @nested_strings, @nested_strings.deep_stringify_keys      assert_equal @nested_strings, @nested_mixed.deep_stringify_keys +    assert_equal @string_array_of_hashes, @string_array_of_hashes.deep_stringify_keys +    assert_equal @string_array_of_hashes, @symbol_array_of_hashes.deep_stringify_keys +    assert_equal @string_array_of_hashes, @mixed_array_of_hashes.deep_stringify_keys    end    def test_deep_stringify_keys_not_mutates @@ -229,6 +248,9 @@ class HashExtTest < ActiveSupport::TestCase      assert_equal @nested_strings, @nested_symbols.deep_dup.deep_stringify_keys!      assert_equal @nested_strings, @nested_strings.deep_dup.deep_stringify_keys!      assert_equal @nested_strings, @nested_mixed.deep_dup.deep_stringify_keys! +    assert_equal @string_array_of_hashes, @string_array_of_hashes.deep_dup.deep_stringify_keys! +    assert_equal @string_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_stringify_keys! +    assert_equal @string_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_stringify_keys!    end    def test_deep_stringify_keys_with_bang_mutates diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index ff6e21854e..380f5ad42b 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -72,7 +72,7 @@ Product = Struct.new(:name) do    def type      @type ||= begin -      nil.type_name +      :thing_without_same_method_name_as_delegated.name      end    end  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/subscriber_test.rb b/activesupport/test/subscriber_test.rb index 253411aa3d..21e4ba0cee 100644 --- a/activesupport/test/subscriber_test.rb +++ b/activesupport/test/subscriber_test.rb @@ -4,20 +4,28 @@ require 'active_support/subscriber'  class TestSubscriber < ActiveSupport::Subscriber    attach_to :doodle -  cattr_reader :event +  cattr_reader :events    def self.clear -    @@event = nil +    @@events = []    end    def open_party(event) -    @@event = event +    events << event    end    private    def private_party(event) -    @@event = event +    events << event +  end +end + +# Monkey patch subscriber to test that only one subscriber per method is added. +class TestSubscriber +  remove_method :open_party +  def open_party(event) +    events << event    end  end @@ -29,12 +37,18 @@ class SubscriberTest < ActiveSupport::TestCase    def test_attaches_subscribers      ActiveSupport::Notifications.instrument("open_party.doodle") -    assert_equal "open_party.doodle", TestSubscriber.event.name +    assert_equal "open_party.doodle", TestSubscriber.events.first.name +  end + +  def test_attaches_only_one_subscriber +    ActiveSupport::Notifications.instrument("open_party.doodle") + +    assert_equal 1, TestSubscriber.events.size    end    def test_does_not_attach_private_methods      ActiveSupport::Notifications.instrument("private_party.doodle") -    assert_nil TestSubscriber.event +    assert_equal TestSubscriber.events, []    end  end diff --git a/ci/travis.rb b/ci/travis.rb index 7e68993332..956a01dbee 100755 --- a/ci/travis.rb +++ b/ci/travis.rb @@ -52,7 +52,7 @@ class Build    def tasks      if activerecord? -      ['mysql:rebuild_databases', "#{adapter}:#{'isolated_' if isolated?}test"] +      ['db:mysql:rebuild', "#{adapter}:#{'isolated_' if isolated?}test"]      else        ["test#{':isolated' if isolated?}"]      end diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index 14ad4fd424..4cd4a9d70c 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,3 +1,21 @@ +* 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* + +* Split up rows in the Explain Queries table of the ActiveRecord Querying section +in order to improve readability. + +    * John Kelly Ferguson* + +*   Change all non-HTTP method 'post' references to 'article'. + +    *John Kelly Ferguson* +  *   Updates the maintenance policy to match the latest versions of Rails      *Matias Korhonen* diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile index c3d7e96c4d..13a0ef44a9 100644 --- a/guides/code/getting_started/Gemfile +++ b/guides/code/getting_started/Gemfile @@ -2,11 +2,11 @@ source 'https://rubygems.org'  # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '4.1.0' -# Use sqlite3 as the database for Active Record +gem 'rails', '4.1.1' +# Use SQLite3 as the database for Active Record  gem 'sqlite3'  # Use SCSS for stylesheets -gem 'sass-rails', '~> 4.0.1' +gem 'sass-rails', '~> 4.0.3'  # Use Uglifier as compressor for JavaScript assets  gem 'uglifier', '>= 1.3.0'  # Use CoffeeScript for .js.coffee assets and views @@ -14,7 +14,7 @@ gem 'coffee-rails', '~> 4.0.0'  # See https://github.com/sstephenson/execjs#readme for more supported runtimes  # gem 'therubyracer',  platforms: :ruby -# Use jquery as the JavaScript library +# Use jQuery as the JavaScript library  gem 'jquery-rails'  # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks  gem 'turbolinks' @@ -29,7 +29,7 @@ gem 'spring',        group: :development  # Use ActiveModel has_secure_password  # gem 'bcrypt', '~> 3.1.7' -# Use unicorn as the app server +# Use Unicorn as the app server  # gem 'unicorn'  # Use Capistrano for deployment diff --git a/guides/code/getting_started/Gemfile.lock b/guides/code/getting_started/Gemfile.lock index a2ab76c908..f26cf58e6a 100644 --- a/guides/code/getting_started/Gemfile.lock +++ b/guides/code/getting_started/Gemfile.lock @@ -1,34 +1,33 @@  GEM    remote: https://rubygems.org/    specs: -    actionmailer (4.1.0) -      actionpack (= 4.1.0) -      actionview (= 4.1.0) +    actionmailer (4.1.1) +      actionpack (= 4.1.1) +      actionview (= 4.1.1)        mail (~> 2.5.4) -    actionpack (4.1.0) -      actionview (= 4.1.0) -      activesupport (= 4.1.0) +    actionpack (4.1.1) +      actionview (= 4.1.1) +      activesupport (= 4.1.1)        rack (~> 1.5.2)        rack-test (~> 0.6.2) -    actionview (4.1.0) -      activesupport (= 4.1.0) +    actionview (4.1.1) +      activesupport (= 4.1.1)        builder (~> 3.1)        erubis (~> 2.7.0) -    activemodel (4.1.0) -      activesupport (= 4.1.0) +    activemodel (4.1.1) +      activesupport (= 4.1.1)        builder (~> 3.1) -    activerecord (4.1.0) -      activemodel (= 4.1.0) -      activesupport (= 4.1.0) +    activerecord (4.1.1) +      activemodel (= 4.1.1) +      activesupport (= 4.1.1)        arel (~> 5.0.0) -    activesupport (4.1.0) +    activesupport (4.1.1)        i18n (~> 0.6, >= 0.6.9)        json (~> 1.7, >= 1.7.7)        minitest (~> 5.1)        thread_safe (~> 0.1)        tzinfo (~> 1.1) -    arel (5.0.0) -    atomic (1.1.14) +    arel (5.0.1.20140414130214)      builder (3.2.2)      coffee-rails (4.0.1)        coffee-script (>= 2.2.0) @@ -52,52 +51,52 @@ GEM        mime-types (~> 1.16)        treetop (~> 1.4.8)      mime-types (1.25.1) -    minitest (5.2.1) -    multi_json (1.8.4) -    polyglot (0.3.3) +    minitest (5.3.4) +    multi_json (1.10.1) +    polyglot (0.3.4)      rack (1.5.2)      rack-test (0.6.2)        rack (>= 1.0) -    rails (4.1.0) -      actionmailer (= 4.1.0) -      actionpack (= 4.1.0) -      actionview (= 4.1.0) -      activemodel (= 4.1.0) -      activerecord (= 4.1.0) -      activesupport (= 4.1.0) +    rails (4.1.1) +      actionmailer (= 4.1.1) +      actionpack (= 4.1.1) +      actionview (= 4.1.1) +      activemodel (= 4.1.1) +      activerecord (= 4.1.1) +      activesupport (= 4.1.1)        bundler (>= 1.3.0, < 2.0) -      railties (= 4.1.0) -      sprockets-rails (~> 2.0.0) -    railties (4.1.0) -      actionpack (= 4.1.0) -      activesupport (= 4.1.0) +      railties (= 4.1.1) +      sprockets-rails (~> 2.0) +    railties (4.1.1) +      actionpack (= 4.1.1) +      activesupport (= 4.1.1)        rake (>= 0.8.7)        thor (>= 0.18.1, < 2.0) -    rake (10.1.1) +    rake (10.3.2)      rdoc (4.1.1)        json (~> 1.4) -    sass (3.2.13) -    sass-rails (4.0.1) +    sass (3.2.19) +    sass-rails (4.0.3)        railties (>= 4.0.0, < 5.0) -      sass (>= 3.1.10) -      sprockets-rails (~> 2.0.0) +      sass (~> 3.2.0) +      sprockets (~> 2.8, <= 2.11.0) +      sprockets-rails (~> 2.0)      sdoc (0.4.0)        json (~> 1.8)        rdoc (~> 4.0, < 5.0)      spring (1.0.0) -    sprockets (2.10.1) +    sprockets (2.11.0)        hike (~> 1.2)        multi_json (~> 1.0)        rack (~> 1.0)        tilt (~> 1.1, != 1.3.0) -    sprockets-rails (2.0.1) +    sprockets-rails (2.1.3)        actionpack (>= 3.0)        activesupport (>= 3.0)        sprockets (~> 2.8)      sqlite3 (1.3.8) -    thor (0.18.1) -    thread_safe (0.1.3) -      atomic +    thor (0.19.1) +    thread_safe (0.3.3)      tilt (1.4.1)      treetop (1.4.15)        polyglot @@ -117,8 +116,8 @@ DEPENDENCIES    coffee-rails (~> 4.0.0)    jbuilder (~> 2.0)    jquery-rails -  rails (= 4.1.0) -  sass-rails (~> 4.0.1) +  rails (= 4.1.1) +  sass-rails (~> 4.0.3)    sdoc (~> 0.4.0)    spring    sqlite3 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/environments/development.rb b/guides/code/getting_started/config/environments/development.rb index ae9ffe209a..5c1c600feb 100644 --- a/guides/code/getting_started/config/environments/development.rb +++ b/guides/code/getting_started/config/environments/development.rb @@ -27,4 +27,12 @@ Rails.application.configure do    # Debug mode disables concatenation and preprocessing of assets.    config.assets.debug = true + +  # Generate digests for assets URLs. +  config.assets.digest = true + +  # Adds additional error checking when serving assets at runtime. +  # Checks for improperly declared sprockets dependencies. +  # Raises helpful error messages. +  config.assets.raise_runtime_errors = true  end 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/code/getting_started/test/test_helper.rb b/guides/code/getting_started/test/test_helper.rb index f91a4375dc..ecbaf77ea7 100644 --- a/guides/code/getting_started/test/test_helper.rb +++ b/guides/code/getting_started/test/test_helper.rb @@ -6,9 +6,6 @@ class ActiveSupport::TestCase    ActiveRecord::Migration.check_pending!    # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. -  # -  # Note: You'll currently still have to declare fixtures explicitly in integration tests -  # -- they do not yet inherit this setting    fixtures :all    # Add more helper methods to be used by all tests here... diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb index dbf7ff311d..1ea18ba9f5 100644 --- a/guides/rails_guides/markdown.rb +++ b/guides/rails_guides/markdown.rb @@ -47,8 +47,7 @@ module RailsGuides        end        def dom_id_text(text) -        text.downcase.gsub(/\?/, '-questionmark').gsub(/!/, '-bang').gsub(/[^a-z0-9]+/, ' ') -          .strip.gsub(/\s+/, '-') +        text.downcase.gsub(/\?/, '-questionmark').gsub(/!/, '-bang').gsub(/\s+/, '-')        end        def engine diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 6dc7fb1606..c67f6188c4 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -30,7 +30,7 @@ views.  #### Create the Mailer  ```bash -$ rails generate mailer UserMailer +$ bin/rails generate mailer UserMailer  create  app/mailers/user_mailer.rb  invoke  erb  create    app/views/user_mailer @@ -146,8 +146,8 @@ Setting this up is painfully simple.  First, let's create a simple `User` scaffold:  ```bash -$ rails generate scaffold user name email login -$ rake db:migrate +$ bin/rails generate scaffold user name email login +$ bin/rake db:migrate  ```  Now that we have a user model to play with, we will just edit the diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index 74f95bfcfd..ef7ef5a50e 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -28,22 +28,22 @@ For each controller there is an associated directory in the `app/views` director  Let's take a look at what Rails does by default when creating a new resource using the scaffold generator:  ```bash -$ rails generate scaffold post +$ bin/rails generate scaffold article        [...]        invoke  scaffold_controller -      create    app/controllers/posts_controller.rb +      create    app/controllers/articles_controller.rb        invoke    erb -      create      app/views/posts -      create      app/views/posts/index.html.erb -      create      app/views/posts/edit.html.erb -      create      app/views/posts/show.html.erb -      create      app/views/posts/new.html.erb -      create      app/views/posts/_form.html.erb +      create      app/views/articles +      create      app/views/articles/index.html.erb +      create      app/views/articles/edit.html.erb +      create      app/views/articles/show.html.erb +      create      app/views/articles/new.html.erb +      create      app/views/articles/_form.html.erb        [...]  ```  There is a naming convention for views in Rails. Typically, the views share their name with the associated controller action, as you can see above. -For example, the index controller action of the `posts_controller.rb` will use the `index.html.erb` view file in the `app/views/posts` directory. +For example, the index controller action of the `articles_controller.rb` will use the `index.html.erb` view file in the `app/views/articles` directory.  The complete HTML returned to the client is composed of a combination of this ERB file, a layout template that wraps it, and all the partials that the view may reference. Later on this guide you can find a more detailed documentation of each one of these three components. @@ -276,23 +276,23 @@ Partial Layouts  Partials can have their own layouts applied to them. These layouts are different than the ones that are specified globally for the entire action, but they work in a similar fashion. -Let's say we're displaying a post on a page, that should be wrapped in a `div` for display purposes. First, we'll create a new `Post`: +Let's say we're displaying an article on a page, that should be wrapped in a `div` for display purposes. First, we'll create a new `Article`:  ```ruby -Post.create(body: 'Partial Layouts are cool!') +Article.create(body: 'Partial Layouts are cool!')  ``` -In the `show` template, we'll render the `_post` partial wrapped in the `box` layout: +In the `show` template, we'll render the `_article` partial wrapped in the `box` layout: -**posts/show.html.erb** +**articles/show.html.erb**  ```erb -<%= render partial: 'post', layout: 'box', locals: {post: @post} %> +<%= render partial: 'article', layout: 'box', locals: {article: @article} %>  ``` -The `box` layout simply wraps the `_post` partial in a `div`: +The `box` layout simply wraps the `_article` partial in a `div`: -**posts/_box.html.erb** +**articles/_box.html.erb**  ```html+erb  <div class='box'> @@ -300,13 +300,13 @@ The `box` layout simply wraps the `_post` partial in a `div`:  </div>  ``` -The `_post` partial wraps the post's `body` in a `div` with the `id` of the post using the `div_for` helper: +The `_article` partial wraps the article's `body` in a `div` with the `id` of the article using the `div_for` helper: -**posts/_post.html.erb** +**articles/_article.html.erb**  ```html+erb -<%= div_for(post) do %> -  <p><%= post.body %></p> +<%= div_for(article) do %> +  <p><%= article.body %></p>  <% end %>  ``` @@ -314,22 +314,22 @@ this would output the following:  ```html  <div class='box'> -  <div id='post_1'> +  <div id='article_1'>      <p>Partial Layouts are cool!</p>    </div>  </div>  ``` -Note that the partial layout has access to the local `post` variable that was passed into the `render` call. However, unlike application-wide layouts, partial layouts still have the underscore prefix. +Note that the partial layout has access to the local `article` variable that was passed into the `render` call. However, unlike application-wide layouts, partial layouts still have the underscore prefix. -You can also render a block of code within a partial layout instead of calling `yield`. For example, if we didn't have the `_post` partial, we could do this instead: +You can also render a block of code within a partial layout instead of calling `yield`. For example, if we didn't have the `_article` partial, we could do this instead: -**posts/show.html.erb** +**articles/show.html.erb**  ```html+erb -<% render(layout: 'box', locals: {post: @post}) do %> -  <%= div_for(post) do %> -    <p><%= post.body %></p> +<% render(layout: 'box', locals: {article: @article}) do %> +  <%= div_for(article) do %> +    <p><%= article.body %></p>    <% end %>  <% end %>  ``` @@ -356,18 +356,18 @@ This module provides methods for generating container tags, such as `div`, for y  Renders a container tag that relates to your Active Record Object. -For example, given `@post` is the object of `Post` class, you can do: +For example, given `@article` is the object of `Article` class, you can do:  ```html+erb -<%= content_tag_for(:tr, @post) do %> -  <td><%= @post.title %></td> +<%= content_tag_for(:tr, @article) do %> +  <td><%= @article.title %></td>  <% end %>  ```  This will generate this HTML output:  ```html -<tr id="post_1234" class="post"> +<tr id="article_1234" class="article">    <td>Hello World!</td>  </tr>  ``` @@ -375,34 +375,34 @@ This will generate this HTML output:  You can also supply HTML attributes as an additional option hash. For example:  ```html+erb -<%= content_tag_for(:tr, @post, class: "frontpage") do %> -  <td><%= @post.title %></td> +<%= content_tag_for(:tr, @article, class: "frontpage") do %> +  <td><%= @article.title %></td>  <% end %>  ```  Will generate this HTML output:  ```html -<tr id="post_1234" class="post frontpage"> +<tr id="article_1234" class="article frontpage">    <td>Hello World!</td>  </tr>  ``` -You can pass a collection of Active Record objects. This method will loop through your objects and create a container for each of them. For example, given `@posts` is an array of two `Post` objects: +You can pass a collection of Active Record objects. This method will loop through your objects and create a container for each of them. For example, given `@articles` is an array of two `Article` objects:  ```html+erb -<%= content_tag_for(:tr, @posts) do |post| %> -  <td><%= post.title %></td> +<%= content_tag_for(:tr, @articles) do |article| %> +  <td><%= article.title %></td>  <% end %>  ```  Will generate this HTML output:  ```html -<tr id="post_1234" class="post"> +<tr id="article_1234" class="article">    <td>Hello World!</td>  </tr> -<tr id="post_1235" class="post"> +<tr id="article_1235" class="article">    <td>Ruby on Rails Rocks!</td>  </tr>  ``` @@ -412,15 +412,15 @@ Will generate this HTML output:  This is actually a convenient method which calls `content_tag_for` internally with `:div` as the tag name. You can pass either an Active Record object or a collection of objects. For example:  ```html+erb -<%= div_for(@post, class: "frontpage") do %> -  <td><%= @post.title %></td> +<%= div_for(@article, class: "frontpage") do %> +  <td><%= @article.title %></td>  <% end %>  ```  Will generate this HTML output:  ```html -<div id="post_1234" class="post frontpage"> +<div id="article_1234" class="article frontpage">    <td>Hello World!</td>  </div>  ``` @@ -590,14 +590,14 @@ This helper makes building an Atom feed easy. Here's a full usage example:  **config/routes.rb**  ```ruby -resources :posts +resources :articles  ``` -**app/controllers/posts_controller.rb** +**app/controllers/articles_controller.rb**  ```ruby  def index -  @posts = Post.all +  @articles = Article.all    respond_to do |format|      format.html @@ -606,20 +606,20 @@ def index  end  ``` -**app/views/posts/index.atom.builder** +**app/views/articles/index.atom.builder**  ```ruby  atom_feed do |feed| -  feed.title("Posts Index") -  feed.updated((@posts.first.created_at)) +  feed.title("Articles Index") +  feed.updated((@articles.first.created_at)) -  @posts.each do |post| -    feed.entry(post) do |entry| -      entry.title(post.title) -      entry.content(post.body, type: 'html') +  @articles.each do |article| +    feed.entry(article) do |entry| +      entry.title(article.title) +      entry.content(article.body, type: 'html')        entry.author do |author| -        author.name(post.author_name) +        author.name(article.author_name)        end      end    end @@ -697,7 +697,7 @@ For example, let's say we have a standard application layout, but also a special  </html>  ``` -**app/views/posts/special.html.erb** +**app/views/articles/special.html.erb**  ```html+erb  <p>This is a special page.</p> @@ -714,7 +714,7 @@ For example, let's say we have a standard application layout, but also a special  Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute.  ```ruby -date_select("post", "published_on") +date_select("article", "published_on")  ```  #### datetime_select @@ -722,7 +722,7 @@ date_select("post", "published_on")  Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based attribute.  ```ruby -datetime_select("post", "published_on") +datetime_select("article", "published_on")  ```  #### distance_of_time_in_words @@ -904,10 +904,10 @@ The params hash has a nested person value, which can therefore be accessed with  Returns a checkbox tag tailored for accessing a specified attribute.  ```ruby -# Let's say that @post.validated? is 1: -check_box("post", "validated") -# => <input type="checkbox" id="post_validated" name="post[validated]" value="1" /> -#    <input name="post[validated]" type="hidden" value="0" /> +# Let's say that @article.validated? is 1: +check_box("article", "validated") +# => <input type="checkbox" id="article_validated" name="article[validated]" value="1" /> +#    <input name="article[validated]" type="hidden" value="0" />  ```  #### fields_for @@ -939,7 +939,7 @@ file_field(:user, :avatar)  Creates a form and a scope around a specific model object that is used as a base for questioning about values for the fields.  ```html+erb -<%= form_for @post do |f| %> +<%= form_for @article do |f| %>    <%= f.label :title, 'Title' %>:    <%= f.text_field :title %><br>    <%= f.label :body, 'Body' %>: @@ -961,8 +961,8 @@ hidden_field(:user, :token)  Returns a label tag tailored for labelling an input field for a specified attribute.  ```ruby -label(:post, :title) -# => <label for="post_title">Title</label> +label(:article, :title) +# => <label for="article_title">Title</label>  ```  #### password_field @@ -979,11 +979,11 @@ password_field(:login, :pass)  Returns a radio button tag for accessing a specified attribute.  ```ruby -# Let's say that @post.category returns "rails": -radio_button("post", "category", "rails") -radio_button("post", "category", "java") -# => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" /> -#    <input type="radio" id="post_category_java" name="post[category]" value="java" /> +# Let's say that @article.category returns "rails": +radio_button("article", "category", "rails") +radio_button("article", "category", "java") +# => <input type="radio" id="article_category_rails" name="article[category]" value="rails" checked="checked" /> +#    <input type="radio" id="article_category_java" name="article[category]" value="java" />  ```  #### text_area @@ -1002,8 +1002,8 @@ text_area(:comment, :text, size: "20x30")  Returns an input tag of the "text" type tailored for accessing a specified attribute.  ```ruby -text_field(:post, :title) -# => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" /> +text_field(:article, :title) +# => <input type="text" id="article_title" name="article[title]" value="#{@article.title}" />  ```  #### email_field @@ -1035,28 +1035,28 @@ Returns `select` and `option` tags for the collection of existing return values  Example object structure for use with this method:  ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    belongs_to :author  end  class Author < ActiveRecord::Base -  has_many :posts +  has_many :articles    def name_with_initial      "#{first_name.first}. #{last_name}"    end  end  ``` -Sample usage (selecting the associated Author for an instance of Post, `@post`): +Sample usage (selecting the associated Author for an instance of Article, `@article`):  ```ruby -collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {prompt: true}) +collection_select(:article, :author_id, Author.all, :id, :name_with_initial, {prompt: true})  ``` -If `@post.author_id` is 1, this would return: +If `@article.author_id` is 1, this would return:  ```html -<select name="post[author_id]"> +<select name="article[author_id]">    <option value="">Please select</option>    <option value="1" selected="selected">D. Heinemeier Hansson</option>    <option value="2">D. Thomas</option> @@ -1071,33 +1071,33 @@ Returns `radio_button` tags for the collection of existing return values of `met  Example object structure for use with this method:  ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    belongs_to :author  end  class Author < ActiveRecord::Base -  has_many :posts +  has_many :articles    def name_with_initial      "#{first_name.first}. #{last_name}"    end  end  ``` -Sample usage (selecting the associated Author for an instance of Post, `@post`): +Sample usage (selecting the associated Author for an instance of Article, `@article`):  ```ruby -collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) +collection_radio_buttons(:article, :author_id, Author.all, :id, :name_with_initial)  ``` -If `@post.author_id` is 1, this would return: +If `@article.author_id` is 1, this would return:  ```html -<input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" /> -<label for="post_author_id_1">D. Heinemeier Hansson</label> -<input id="post_author_id_2" name="post[author_id]" type="radio" value="2" /> -<label for="post_author_id_2">D. Thomas</label> -<input id="post_author_id_3" name="post[author_id]" type="radio" value="3" /> -<label for="post_author_id_3">M. Clark</label> +<input id="article_author_id_1" name="article[author_id]" type="radio" value="1" checked="checked" /> +<label for="article_author_id_1">D. Heinemeier Hansson</label> +<input id="article_author_id_2" name="article[author_id]" type="radio" value="2" /> +<label for="article_author_id_2">D. Thomas</label> +<input id="article_author_id_3" name="article[author_id]" type="radio" value="3" /> +<label for="article_author_id_3">M. Clark</label>  ```  #### collection_check_boxes @@ -1107,34 +1107,34 @@ Returns `check_box` tags for the collection of existing return values of `method  Example object structure for use with this method:  ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    has_and_belongs_to_many :authors  end  class Author < ActiveRecord::Base -  has_and_belongs_to_many :posts +  has_and_belongs_to_many :articles    def name_with_initial      "#{first_name.first}. #{last_name}"    end  end  ``` -Sample usage (selecting the associated Authors for an instance of Post, `@post`): +Sample usage (selecting the associated Authors for an instance of Article, `@article`):  ```ruby -collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) +collection_check_boxes(:article, :author_ids, Author.all, :id, :name_with_initial)  ``` -If `@post.author_ids` is [1], this would return: +If `@article.author_ids` is [1], this would return:  ```html -<input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" /> -<label for="post_author_ids_1">D. Heinemeier Hansson</label> -<input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" /> -<label for="post_author_ids_2">D. Thomas</label> -<input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" /> -<label for="post_author_ids_3">M. Clark</label> -<input name="post[author_ids][]" type="hidden" value="" /> +<input id="article_author_ids_1" name="article[author_ids][]" type="checkbox" value="1" checked="checked" /> +<label for="article_author_ids_1">D. Heinemeier Hansson</label> +<input id="article_author_ids_2" name="article[author_ids][]" type="checkbox" value="2" /> +<label for="article_author_ids_2">D. Thomas</label> +<input id="article_author_ids_3" name="article[author_ids][]" type="checkbox" value="3" /> +<label for="article_author_ids_3">M. Clark</label> +<input name="article[author_ids][]" type="hidden" value="" />  ```  #### country_options_for_select @@ -1222,13 +1222,13 @@ Create a select tag and a series of contained option tags for the provided objec  Example:  ```ruby -select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: true}) +select("article", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: true})  ``` -If `@post.person_id` is 1, this would become: +If `@article.person_id` is 1, this would become:  ```html -<select name="post[person_id]"> +<select name="article[person_id]">    <option value=""></option>    <option value="1" selected="selected">David</option>    <option value="2">Sam</option> @@ -1303,10 +1303,10 @@ file_field_tag 'attachment'  Starts a form tag that points the action to an url configured with `url_for_options` just like `ActionController::Base#url_for`.  ```html+erb -<%= form_tag '/posts' do %> +<%= form_tag '/articles' do %>    <div><%= submit_tag 'Save' %></div>  <% end %> -# => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form> +# => <form action="/articles" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>  ```  #### hidden_field_tag @@ -1368,8 +1368,8 @@ select_tag "people", "<option>David</option>"  Creates a submit button with the text provided as the caption.  ```ruby -submit_tag "Publish this post" -# => <input name="commit" type="submit" value="Publish this post" /> +submit_tag "Publish this article" +# => <input name="commit" type="submit" value="Publish this article" />  ```  #### text_area_tag @@ -1377,8 +1377,8 @@ submit_tag "Publish this post"  Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions.  ```ruby -text_area_tag 'post' -# => <textarea id="post" name="post"></textarea> +text_area_tag 'article' +# => <textarea id="article" name="article"></textarea>  ```  #### text_field_tag @@ -1602,7 +1602,7 @@ Localized Views  Action View has the ability render different templates depending on the current locale. -For example, suppose you have a `PostsController` with a show action. By default, calling this action will render `app/views/posts/show.html.erb`. But if you set `I18n.locale = :de`, then `app/views/posts/show.de.html.erb` will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available. +For example, suppose you have a `ArticlesController` with a show action. By default, calling this action will render `app/views/articles/show.html.erb`. But if you set `I18n.locale = :de`, then `app/views/articles/show.de.html.erb` will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available.  You can use the same technique to localize the rescue files in your public directory. For example, setting `I18n.locale = :de` and creating `public/500.de.html` and `public/404.de.html` would allow you to have localized rescue pages. @@ -1616,6 +1616,6 @@ def set_expert_locale  end  ``` -Then you could create special views like `app/views/posts/show.expert.html.erb` that would only be displayed to expert users. +Then you could create special views like `app/views/articles/show.expert.html.erb` that would only be displayed to expert users.  You can read more about the Rails Internationalization (I18n) API [here](i18n.html). diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index e815f6b674..21022f1abb 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -82,13 +82,13 @@ by underscores. Examples:  * Model Class - Singular with the first letter of each word capitalized (e.g.,  `BookClub`). -| Model / Class | Table / Schema | -| ------------- | -------------- | -| `Post`        | `posts`        | -| `LineItem`    | `line_items`   | -| `Deer`        | `deers`        | -| `Mouse`       | `mice`         | -| `Person`      | `people`       | +| Model / Class    | Table / Schema | +| ---------------- | -------------- | +| `Article`        | `articles`     | +| `LineItem`       | `line_items`   | +| `Deer`           | `deers`        | +| `Mouse`          | `mice`         | +| `Person`         | `people`       |  ### Schema Conventions @@ -120,9 +120,9 @@ to Active Record instances:  * `(association_name)_type` - Stores the type for    [polymorphic associations](association_basics.html#polymorphic-associations).  * `(table_name)_count` - Used to cache the number of belonging objects on -  associations. For example, a `comments_count` column in a `Post` class that +  associations. For example, a `comments_count` column in a `Articles` class that    has many instances of `Comment` will cache the number of existent comments -  for each post. +  for each article.  NOTE: While these column names are optional, they are in fact reserved by Active Record. Steer clear of reserved keywords unless you want the extra functionality. For example, `type` is a reserved keyword used to designate a table using Single Table Inheritance (STI). If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling. diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index fbcce325ed..f0ae3c729e 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -261,27 +261,27 @@ WARNING. Any exception that is not `ActiveRecord::Rollback` will be re-raised by  Relational Callbacks  -------------------- -Callbacks work through model relationships, and can even be defined by them. Suppose an example where a user has many posts. A user's posts should be destroyed if the user is destroyed. Let's add an `after_destroy` callback to the `User` model by way of its relationship to the `Post` model: +Callbacks work through model relationships, and can even be defined by them. Suppose an example where a user has many articles. A user's articles should be destroyed if the user is destroyed. Let's add an `after_destroy` callback to the `User` model by way of its relationship to the `Article` model:  ```ruby  class User < ActiveRecord::Base -  has_many :posts, dependent: :destroy +  has_many :articles, dependent: :destroy  end -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    after_destroy :log_destroy_action    def log_destroy_action -    puts 'Post destroyed' +    puts 'Article destroyed'    end  end  >> user = User.first  => #<User id: 1> ->> user.posts.create! -=> #<Post id: 1, user_id: 1> +>> user.articles.create! +=> #<Article id: 1, user_id: 1>  >> user.destroy -Post destroyed +Article destroyed  => #<User id: 1>  ``` @@ -328,7 +328,7 @@ When writing conditional callbacks, it is possible to mix both `:if` and `:unles  ```ruby  class Comment < ActiveRecord::Base    after_create :send_email_to_author, if: :author_wants_emails?, -    unless: Proc.new { |comment| comment.post.ignore_comments? } +    unless: Proc.new { |comment| comment.article.ignore_comments? }  end  ``` diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md index 169a48afb9..b69c8dbb7a 100644 --- a/guides/source/active_record_postgresql.md +++ b/guides/source/active_record_postgresql.md @@ -3,13 +3,6 @@ Active Record and PostgreSQL  This guide covers PostgreSQL specific usage of Active Record. -In order to use the PostgreSQL adapter you need to have at least version 8.2 -installed. Older versions are not supported. - -To get started with PostgreSQL have a look at the -[configuring Rails guide](configuring.html#configuring-a-postgresql-database). -It describes how to properly setup Active Record for PostgreSQL. -  After reading this guide, you will know:  * How to use PostgreSQL's datatypes. @@ -18,6 +11,13 @@ After reading this guide, you will know:  -------------------------------------------------------------------------------- +In order to use the PostgreSQL adapter you need to have at least version 8.2 +installed. Older versions are not supported. + +To get started with PostgreSQL have a look at the +[configuring Rails guide](configuring.html#configuring-a-postgresql-database). +It describes how to properly setup Active Record for PostgreSQL. +  Datatypes  --------- @@ -171,7 +171,7 @@ event.ends_at # => Thu, 13 Feb 2014  * [type definition](http://www.postgresql.org/docs/9.3/static/rowtypes.html) -Currently there is no special support for composite types. They are mapped to as +Currently there is no special support for composite types. They are mapped to  normal text columns:  ```sql @@ -287,8 +287,9 @@ user.save!  * [type definition](http://www.postgresql.org/docs/9.3/static/datatype-net-types.html) -The types `inet` and `cidr` are mapped to Ruby [`IPAddr`](http://www.ruby-doc.org/stdlib-2.1.1/libdoc/ipaddr/rdoc/IPAddr.html) objects. The -`macaddr` type is mapped to normal text. +The types `inet` and `cidr` are mapped to Ruby +[`IPAddr`](http://www.ruby-doc.org/stdlib-2.1.1/libdoc/ipaddr/rdoc/IPAddr.html) +objects. The `macaddr` type is mapped to normal text.  ```ruby  # db/migrate/20140508144913_create_devices.rb diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 2a76df156c..ee8cf4ade6 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -472,8 +472,8 @@ Client.where('locked' => true)  In the case of a belongs_to relationship, an association key can be used to specify the model if an Active Record object is used as the value. This method works with polymorphic relationships as well.  ```ruby -Post.where(author: author) -Author.joins(:posts).where(posts: { author: author }) +Article.where(author: author) +Author.joins(:articles).where(articles: { author: author })  ```  NOTE: The values cannot be symbols. For example, you cannot do `Client.where(status: :active)`. @@ -511,7 +511,7 @@ SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))  `NOT` SQL queries can be built by `where.not`.  ```ruby -Post.where.not(author: author) +Article.where.not(author: author)  ```  In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions. @@ -690,32 +690,32 @@ Overriding Conditions  You can specify certain conditions to be removed using the `unscope` method. For example:  ```ruby -Post.where('id > 10').limit(20).order('id asc').except(:order) +Article.where('id > 10').limit(20).order('id asc').except(:order)  ```  The SQL that would be executed:  ```sql -SELECT * FROM posts WHERE id > 10 LIMIT 20 +SELECT * FROM articles WHERE id > 10 LIMIT 20  # Original query without `unscope` -SELECT * FROM posts WHERE id > 10 ORDER BY id asc LIMIT 20 +SELECT * FROM articles WHERE id > 10 ORDER BY id asc LIMIT 20  ```  You can additionally unscope specific where clauses. For example:  ```ruby -Post.where(id: 10, trashed: false).unscope(where: :id) -# SELECT "posts".* FROM "posts" WHERE trashed = 0 +Article.where(id: 10, trashed: false).unscope(where: :id) +# SELECT "articles".* FROM "articles" WHERE trashed = 0  ```  A relation which has used `unscope` will affect any relation it is  merged in to:  ```ruby -Post.order('id asc').merge(Post.unscope(:order)) -# SELECT "posts".* FROM "posts" +Article.order('id asc').merge(Article.unscope(:order)) +# SELECT "articles".* FROM "articles"  ```  ### `only` @@ -723,16 +723,16 @@ Post.order('id asc').merge(Post.unscope(:order))  You can also override conditions using the `only` method. For example:  ```ruby -Post.where('id > 10').limit(20).order('id desc').only(:order, :where) +Article.where('id > 10').limit(20).order('id desc').only(:order, :where)  ```  The SQL that would be executed:  ```sql -SELECT * FROM posts WHERE id > 10 ORDER BY id DESC +SELECT * FROM articles WHERE id > 10 ORDER BY id DESC  # Original query without `only` -SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20 +SELECT "articles".* FROM "articles" WHERE (id > 10) ORDER BY id desc LIMIT 20  ``` @@ -741,25 +741,27 @@ SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20  The `reorder` method overrides the default scope order. For example:  ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    ..    ..    has_many :comments, -> { order('posted_at DESC') }  end -Post.find(10).comments.reorder('name') +Article.find(10).comments.reorder('name')  ```  The SQL that would be executed:  ```sql -SELECT * FROM posts WHERE id = 10 ORDER BY name +SELECT * FROM articles WHERE id = 10 +SELECT * FROM comments WHERE article_id = 10 ORDER BY name  ```  In case the `reorder` clause is not used, the SQL executed would be:  ```sql -SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC +SELECT * FROM articles WHERE id = 10 +SELECT * FROM comments WHERE article_id = 10 ORDER BY posted_at DESC  ```  ### `reverse_order` @@ -795,25 +797,25 @@ This method accepts **no** arguments.  The `rewhere` method overrides an existing, named where condition. For example:  ```ruby -Post.where(trashed: true).rewhere(trashed: false) +Article.where(trashed: true).rewhere(trashed: false)  ```  The SQL that would be executed:  ```sql -SELECT * FROM posts WHERE `trashed` = 0 +SELECT * FROM articles WHERE `trashed` = 0  ```  In case the `rewhere` clause is not used,  ```ruby -Post.where(trashed: true).where(trashed: false) +Article.where(trashed: true).where(trashed: false)  ```  the SQL executed would be:  ```sql -SELECT * FROM posts WHERE `trashed` = 1 AND `trashed` = 0 +SELECT * FROM articles WHERE `trashed` = 1 AND `trashed` = 0  ```  Null Relation @@ -822,21 +824,21 @@ Null Relation  The `none` method returns a chainable relation with no records. Any subsequent conditions chained to the returned relation will continue generating empty relations. This is useful in scenarios where you need a chainable response to a method or a scope that could return zero results.  ```ruby -Post.none # returns an empty Relation and fires no queries. +Article.none # returns an empty Relation and fires no queries.  ```  ```ruby -# The visible_posts method below is expected to return a Relation. -@posts = current_user.visible_posts.where(name: params[:name]) +# The visible_articles method below is expected to return a Relation. +@articles = current_user.visible_articles.where(name: params[:name]) -def visible_posts +def visible_articles    case role    when 'Country Manager' -    Post.where(country: country) +    Article.where(country: country)    when 'Reviewer' -    Post.published +    Article.published    when 'Bad User' -    Post.none # => returning [] or nil breaks the caller code in this case +    Article.none # => returning [] or nil breaks the caller code in this case    end  end  ``` @@ -963,21 +965,21 @@ WARNING: This method only works with `INNER JOIN`.  Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clauses for those associations when using the `joins` method. -For example, consider the following `Category`, `Post`, `Comment`, `Guest` and `Tag` models: +For example, consider the following `Category`, `Article`, `Comment`, `Guest` and `Tag` models:  ```ruby  class Category < ActiveRecord::Base -  has_many :posts +  has_many :articles  end -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    belongs_to :category    has_many :comments    has_many :tags  end  class Comment < ActiveRecord::Base -  belongs_to :post +  belongs_to :article    has_one :guest  end @@ -986,7 +988,7 @@ class Guest < ActiveRecord::Base  end  class Tag < ActiveRecord::Base -  belongs_to :post +  belongs_to :article  end  ``` @@ -995,64 +997,64 @@ Now all of the following will produce the expected join queries using `INNER JOI  #### Joining a Single Association  ```ruby -Category.joins(:posts) +Category.joins(:articles)  ```  This produces:  ```sql  SELECT categories.* FROM categories -  INNER JOIN posts ON posts.category_id = categories.id +  INNER JOIN articles ON articles.category_id = categories.id  ``` -Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use `Category.joins(:posts).uniq`. +Or, in English: "return a Category object for all categories with articles". Note that you will see duplicate categories if more than one article has the same category. If you want unique categories, you can use `Category.joins(:articles).uniq`.  #### Joining Multiple Associations  ```ruby -Post.joins(:category, :comments) +Article.joins(:category, :comments)  ```  This produces:  ```sql -SELECT posts.* FROM posts -  INNER JOIN categories ON posts.category_id = categories.id -  INNER JOIN comments ON comments.post_id = posts.id +SELECT articles.* FROM articles +  INNER JOIN categories ON articles.category_id = categories.id +  INNER JOIN comments ON comments.article_id = articles.id  ``` -Or, in English: "return all posts that have a category and at least one comment". Note again that posts with multiple comments will show up multiple times. +Or, in English: "return all articles that have a category and at least one comment". Note again that articles with multiple comments will show up multiple times.  #### Joining Nested Associations (Single Level)  ```ruby -Post.joins(comments: :guest) +Article.joins(comments: :guest)  ```  This produces:  ```sql -SELECT posts.* FROM posts -  INNER JOIN comments ON comments.post_id = posts.id +SELECT articles.* FROM articles +  INNER JOIN comments ON comments.article_id = articles.id    INNER JOIN guests ON guests.comment_id = comments.id  ``` -Or, in English: "return all posts that have a comment made by a guest." +Or, in English: "return all articles that have a comment made by a guest."  #### Joining Nested Associations (Multiple Level)  ```ruby -Category.joins(posts: [{ comments: :guest }, :tags]) +Category.joins(articles: [{ comments: :guest }, :tags])  ```  This produces:  ```sql  SELECT categories.* FROM categories -  INNER JOIN posts ON posts.category_id = categories.id -  INNER JOIN comments ON comments.post_id = posts.id +  INNER JOIN articles ON articles.category_id = categories.id +  INNER JOIN comments ON comments.article_id = articles.id    INNER JOIN guests ON guests.comment_id = comments.id -  INNER JOIN tags ON tags.post_id = posts.id +  INNER JOIN tags ON tags.article_id = articles.id  ```  ### Specifying Conditions on the Joined Tables @@ -1121,18 +1123,18 @@ Active Record lets you eager load any number of associations with a single `Mode  #### Array of Multiple Associations  ```ruby -Post.includes(:category, :comments) +Article.includes(:category, :comments)  ``` -This loads all the posts and the associated category and comments for each post. +This loads all the articles and the associated category and comments for each article.  #### Nested Associations Hash  ```ruby -Category.includes(posts: [{ comments: :guest }, :tags]).find(1) +Category.includes(articles: [{ comments: :guest }, :tags]).find(1)  ``` -This will find the category with id 1 and eager load all of the associated posts, the associated posts' tags and comments, and every comment's guest association. +This will find the category with id 1 and eager load all of the associated articles, the associated articles' tags and comments, and every comment's guest association.  ### Specifying Conditions on Eager Loaded Associations @@ -1141,18 +1143,18 @@ Even though Active Record lets you specify conditions on the eager loaded associ  However if you must do this, you may use `where` as you would normally.  ```ruby -Post.includes(:comments).where("comments.visible" => true) +Article.includes(:comments).where("comments.visible" => true)  ```  This would generate a query which contains a `LEFT OUTER JOIN` whereas the `joins` method would generate one using the `INNER JOIN` function instead.  ```ruby -  SELECT "posts"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (comments.visible = 1) +  SELECT "articles"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "articles" LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id" WHERE (comments.visible = 1)  ```  If there was no `where` condition, this would generate the normal set of two queries. -If, in the case of this `includes` query, there were no comments for any posts, all the posts would still be loaded. By using `joins` (an INNER JOIN), the join conditions **must** match, otherwise no records will be returned. +If, in the case of this `includes` query, there were no comments for any articles, all the articles would still be loaded. By using `joins` (an INNER JOIN), the join conditions **must** match, otherwise no records will be returned.  Scopes  ------ @@ -1162,7 +1164,7 @@ Scoping allows you to specify commonly-used queries which can be referenced as m  To define a simple scope, we use the `scope` method inside the class, passing the query that we'd like to run when this scope is called:  ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    scope :published, -> { where(published: true) }  end  ``` @@ -1170,7 +1172,7 @@ end  This is exactly the same as defining a class method, and which you use is a matter of personal preference:  ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    def self.published      where(published: true)    end @@ -1180,7 +1182,7 @@ end  Scopes are also chainable within scopes:  ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    scope :published,               -> { where(published: true) }    scope :published_and_commented, -> { published.where("comments_count > 0") }  end @@ -1189,14 +1191,14 @@ end  To call this `published` scope we can call it on either the class:  ```ruby -Post.published # => [published posts] +Article.published # => [published articles]  ``` -Or on an association consisting of `Post` objects: +Or on an association consisting of `Article` objects:  ```ruby  category = Category.first -category.posts.published # => [published posts belonging to this category] +category.articles.published # => [published articles belonging to this category]  ```  ### Passing in arguments @@ -1204,7 +1206,7 @@ category.posts.published # => [published posts belonging to this category]  Your scope can take arguments:  ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    scope :created_before, ->(time) { where("created_at < ?", time) }  end  ``` @@ -1212,13 +1214,13 @@ end  Call the scope as if it were a class method:  ```ruby -Post.created_before(Time.zone.now) +Article.created_before(Time.zone.now)  ```  However, this is just duplicating the functionality that would be provided to you by a class method.  ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    def self.created_before(time)      where("created_at < ?", time)    end @@ -1228,7 +1230,7 @@ end  Using a class method is the preferred way to accept arguments for scopes. These methods will still be accessible on the association objects:  ```ruby -category.posts.created_before(time) +category.articles.created_before(time)  ```  ### Applying a default scope @@ -1591,20 +1593,20 @@ You can also use `any?` and `many?` to check for existence on a model or relatio  ```ruby  # via a model -Post.any? -Post.many? +Article.any? +Article.many?  # via a named scope -Post.recent.any? -Post.recent.many? +Article.recent.any? +Article.recent.many?  # via a relation -Post.where(published: true).any? -Post.where(published: true).many? +Article.where(published: true).any? +Article.where(published: true).many?  # via an association -Post.first.categories.any? -Post.first.categories.many? +Article.first.categories.any? +Article.first.categories.many?  ```  Calculations @@ -1694,19 +1696,26 @@ Running EXPLAIN  You can run EXPLAIN on the queries triggered by relations. For example,  ```ruby -User.where(id: 1).joins(:posts).explain +User.where(id: 1).joins(:articles).explain  ```  may yield  ``` -EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ -| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra       | -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ -|  1 | SIMPLE      | users | const | PRIMARY       | PRIMARY | 4       | const |    1 |             | -|  1 | SIMPLE      | posts | ALL   | NULL          | NULL    | NULL    | NULL  |    1 | Using where | -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ +EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `articles` ON `articles`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 ++----+-------------+----------+-------+---------------+ +| id | select_type | table    | type  | possible_keys | ++----+-------------+----------+-------+---------------+ +|  1 | SIMPLE      | users    | const | PRIMARY       | +|  1 | SIMPLE      | articles | ALL   | NULL          | ++----+-------------+----------+-------+---------------+ ++---------+---------+-------+------+-------------+ +| key     | key_len | ref   | rows | Extra       | ++---------+---------+-------+------+-------------+ +| PRIMARY | 4       | const |    1 |             | +| NULL    | NULL    | NULL  |    1 | Using where | ++---------+---------+-------+------+-------------+ +  2 rows in set (0.00 sec)  ``` @@ -1716,15 +1725,15 @@ Active Record performs a pretty printing that emulates the one of the database  shells. So, the same query running with the PostgreSQL adapter would yield instead  ``` -EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."id" = 1 +EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "articles" ON "articles"."user_id" = "users"."id" WHERE "users"."id" = 1                                    QUERY PLAN  ------------------------------------------------------------------------------   Nested Loop Left Join  (cost=0.00..37.24 rows=8 width=0) -   Join Filter: (posts.user_id = users.id) +   Join Filter: (articles.user_id = users.id)     ->  Index Scan using users_pkey on users  (cost=0.00..8.27 rows=1 width=4)           Index Cond: (id = 1) -   ->  Seq Scan on posts  (cost=0.00..28.88 rows=8 width=4) -         Filter: (posts.user_id = 1) +   ->  Seq Scan on articles  (cost=0.00..28.88 rows=8 width=4) +         Filter: (articles.user_id = 1)  (6 rows)  ``` @@ -1733,26 +1742,39 @@ may need the results of previous ones. Because of that, `explain` actually  executes the query, and then asks for the query plans. For example,  ```ruby -User.where(id: 1).includes(:posts).explain +User.where(id: 1).includes(:articles).explain  ```  yields  ```  EXPLAIN for: SELECT `users`.* FROM `users`  WHERE `users`.`id` = 1 -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ -| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra | -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ -|  1 | SIMPLE      | users | const | PRIMARY       | PRIMARY | 4       | const |    1 |       | -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ ++----+-------------+-------+-------+---------------+ +| id | select_type | table | type  | possible_keys | ++----+-------------+-------+-------+---------------+ +|  1 | SIMPLE      | users | const | PRIMARY       | ++----+-------------+-------+-------+---------------+ ++---------+---------+-------+------+-------+ +| key     | key_len | ref   | rows | Extra | ++---------+---------+-------+------+-------+ +| PRIMARY | 4       | const |    1 |       | ++---------+---------+-------+------+-------+ +  1 row in set (0.00 sec) -EXPLAIN for: SELECT `posts`.* FROM `posts`  WHERE `posts`.`user_id` IN (1) -+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ -| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       | -+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ -|  1 | SIMPLE      | posts | ALL  | NULL          | NULL | NULL    | NULL |    1 | Using where | -+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ +EXPLAIN for: SELECT `articles`.* FROM `articles`  WHERE `articles`.`user_id` IN (1) ++----+-------------+----------+------+---------------+ +| id | select_type | table    | type | possible_keys | ++----+-------------+----------+------+---------------+ +|  1 | SIMPLE      | articles | ALL  | NULL          | ++----+-------------+----------+------+---------------+ ++------+---------+------+------+-------------+ +| key  | key_len | ref  | rows | Extra       | ++------+---------+------+------+-------------+ +| NULL | NULL    | NULL |    1 | Using where | ++------+---------+------+------+-------------+ + +  1 row in set (0.00 sec)  ``` diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index a483a6dd24..cb459626d5 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -85,7 +85,7 @@ end  We can see how it works by looking at some `rails console` output:  ```ruby -$ rails console +$ bin/rails console  >> p = Person.new(name: "John Doe")  => #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil>  >> p.new_record? @@ -1129,15 +1129,15 @@ generating a scaffold, Rails will put some ERB into the `_form.html.erb` that  it generates that displays the full list of errors on that model.  Assuming we have a model that's been saved in an instance variable named -`@post`, it looks like this: +`@article`, it looks like this:  ```ruby -<% if @post.errors.any? %> +<% 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> @@ -1151,7 +1151,7 @@ the entry.  ```  <div class="field_with_errors"> - <input id="post_title" name="post[title]" size="30" type="text" value=""> + <input id="article_title" name="article[title]" size="30" type="text" value="">  </div>  ``` diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 8d0d6d260d..4f37bf971a 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -572,12 +572,12 @@ NOTE: Defined in `active_support/core_ext/module/aliasing.rb`.  #### `alias_attribute` -Model attributes have a reader, a writer, and a predicate. You can alias a model attribute having the corresponding three methods defined for you in one shot. As in other aliasing methods, the new name is the first argument, and the old name is the second (my mnemonic is they go in the same order as if you did an assignment): +Model attributes have a reader, a writer, and a predicate. You can alias a model attribute having the corresponding three methods defined for you in one shot. As in other aliasing methods, the new name is the first argument, and the old name is the second (one mnemonic is that they go in the same order as if you did an assignment):  ```ruby  class User < ActiveRecord::Base -  # let me refer to the email column as "login", -  # possibly meaningful for authentication code +  # You can refer to the email column as "login". +  # This can be meaningful for authentication code.    alias_attribute :login, :email  end  ``` @@ -3838,7 +3838,7 @@ The name may be given as a symbol or string. A symbol is tested against the bare  TIP: A symbol can represent a fully-qualified constant name as in `:"ActiveRecord::Base"`, so the behavior for symbols is defined for convenience, not because it has to be that way technically. -For example, when an action of `PostsController` is called Rails tries optimistically to use `PostsHelper`. It is OK that the helper module does not exist, so if an exception for that constant name is raised it should be silenced. But it could be the case that `posts_helper.rb` raises a `NameError` due to an actual unknown constant. That should be reraised. The method `missing_name?` provides a way to distinguish both cases: +For example, when an action of `ArticlesController` is called Rails tries optimistically to use `ArticlesHelper`. It is OK that the helper module does not exist, so if an exception for that constant name is raised it should be silenced. But it could be the case that `articles_helper.rb` raises a `NameError` due to an actual unknown constant. That should be reraised. The method `missing_name?` provides a way to distinguish both cases:  ```ruby  def default_helper_module! @@ -3861,7 +3861,7 @@ Active Support adds `is_missing?` to `LoadError`, and also assigns that class to  Given a path name `is_missing?` tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension). -For example, when an action of `PostsController` is called Rails tries to load `posts_helper.rb`, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist and in turn requires another library that is missing. In that case Rails must reraise the exception. The method `is_missing?` provides a way to distinguish both cases: +For example, when an action of `ArticlesController` is called Rails tries to load `articles_helper.rb`, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist and in turn requires another library that is missing. In that case Rails must reraise the exception. The method `is_missing?` provides a way to distinguish both cases:  ```ruby  def default_helper_module! diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index d947c5d9d5..7033947468 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -364,7 +364,7 @@ INFO. Options passed to fetch will be merged with the payload.  | ------ | --------------------- |  | `:key` | Key used in the store | -INFO. Cache stores my add their own keys +INFO. Cache stores may add their own keys  ```ruby  { diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index 261538d0be..2a3bb4e34d 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -110,14 +110,14 @@ The results of expressions follow them and are introduced by "# => ", vertically  If a line is too long, the comment may be placed on the next line:  ```ruby -#   label(:post, :title) -#   # => <label for="post_title">Title</label> +#   label(:article, :title) +#   # => <label for="article_title">Title</label>  # -#   label(:post, :title, "A short title") -#   # => <label for="post_title">A short title</label> +#   label(:article, :title, "A short title") +#   # => <label for="article_title">A short title</label>  # -#   label(:post, :title, "A short title", class: "title_label") -#   # => <label for="post_title" class="title_label">A short title</label> +#   label(:article, :title, "A short title", class: "title_label") +#   # => <label for="article_title" class="title_label">A short title</label>  ```  Avoid using any printing methods like `puts` or `p` for that purpose. @@ -175,8 +175,8 @@ end  The API is careful not to commit to any particular value, the method has  predicate semantics, that's enough. -Filenames ---------- +File Names +----------  As a rule of thumb, use filenames relative to the application root: @@ -286,3 +286,36 @@ self.class_eval %{    end  }  ``` + +Method Visibility +----------------- + +When writing documentation for Rails, it's important to understand the difference between public user-facing API vs internal API. + +Rails, like most libraries, uses the private keyword from Ruby for defining internal API. However, public API follows a slightly different convention. Instead of assuming all public methods are designed for user consumption, Rails uses the `:nodoc:` directive to annotate these kinds of methods as internal API. + +This means that there are methods in Rails with `public` visibility that aren't meant for user consumption. + +An example of this is `ActiveRecord::Core::ClassMethods#arel_table`: + +```ruby +module ActiveRecord::Core::ClassMethods +  def arel_table #:nodoc: +    # do some magic.. +  end +end +``` + +If you thought, "this method looks like a public class method for `ActiveRecord::Core`", you were right. But actually the Rails team doesn't want users to rely on this method. So they mark it as `:nodoc:` and it's removed from public documentation. The reasoning behind this is to allow the team to change these methods according to their internal needs across releases as they see fit. The name of this method could change, or the return value, or this entire class may disappear; there's no guarantee and so you shouldn't depend on this API in your plugins or applications. Otherwise, you risk your app or gem breaking when you upgrade to a newer release of Rails. + +As a contributor, it's important to think about whether this API is meant for end-user consumption. The Rails team is committed to not making any breaking changes to public API across releases without going through a full deprecation cycle. It's recommended that you `:nodoc:` any of your internal methods/classes unless they're already private (meaning visibility), in which case it's internal by default. Once the API stabilizes the visibility can change, but changing public API is much harder due to backwards compatibility. + +A class or module is marked with `:nodoc:` to indicate that all methods are internal API and should never be used directly. + +If you come across an existing `:nodoc:` you should tread lightly. Consider asking someone from the core team or author of the code before removing it. This should almost always happen through a pull request instead of the docrails project. + +A `:nodoc:` should never be added simply because a method or class is missing documentation. There may be an instance where an internal public method wasn't given a `:nodoc:` by mistake, for example when switching a method from private to public visibility. When this happens it should be discussed over a PR on a case-by-case basis and never committed directly to docrails. + +To summarize, the Rails team uses `:nodoc:` to mark publicly visible methods and classes for internal use; changes to the visibility of API should be considered carefully and discussed over a pull request first. + +If you have a question on how the Rails team handles certain API, don't hesitate to open a ticket or send a patch to the [issue tracker](https://github.com/rails/rails/issues). diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 950cfdca29..984480c70f 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -198,12 +198,9 @@ will result in your assets being included more than once.  WARNING: When using asset precompilation, you will need to ensure that your  controller assets will be precompiled when loading them on a per page basis. By -default .coffee and .scss files will not be precompiled on their own. This will -result in false positives during development as these files will work just fine -since assets are compiled on the fly in development mode. When running in -production, however, you will see 500 errors since live compilation is turned -off by default. See [Precompiling Assets](#precompiling-assets) for more -information on how precompiling works. +default .coffee and .scss files will not be precompiled on their own. See +[Precompiling Assets](#precompiling-assets) for more information on how +precompiling works.  NOTE: You must have an ExecJS supported runtime in order to use CoffeeScript.  If you are using Mac OS X or Windows, you have a JavaScript runtime installed in @@ -581,8 +578,21 @@ runtime. To disable this behavior you can set:  config.assets.raise_runtime_errors = false  ``` -When this option is true asset pipeline will check if all the assets loaded in your application -are included in the `config.assets.precompile` list. +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 +all requests for assets include digests. + +### Turning Digests Off + +You can turn off digests by updating `config/environments/development.rb` to +include: + +```ruby +config.assets.digests = false +``` + +When this option is true, digests will be generated for asset URLs.  ### Turning Debugging Off @@ -676,7 +686,7 @@ information on compiling locally.  The rake task is:  ```bash -$ RAILS_ENV=production bundle exec rake assets:precompile +$ RAILS_ENV=production bin/rake assets:precompile  ```  Capistrano (v2.15.1 and above) includes a recipe to handle this in deployment. diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index df38bd7321..22f6f5e7d6 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -1725,58 +1725,58 @@ mostly useful together with the `:through` option.  ```ruby  class Person < ActiveRecord::Base    has_many :readings -  has_many :posts, through: :readings +  has_many :articles, through: :readings  end  person = Person.create(name: 'John') -post   = Post.create(name: 'a1') -person.posts << post -person.posts << post -person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">] -Reading.all.inspect  # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>] +article   = Article.create(name: 'a1') +person.articles << article +person.articles << article +person.articles.inspect # => [#<Article id: 5, name: "a1">, #<Article id: 5, name: "a1">] +Reading.all.inspect  # => [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>]  ``` -In the above case there are two readings and `person.posts` brings out both of -them even though these records are pointing to the same post. +In the above case there are two readings and `person.articles` brings out both of +them even though these records are pointing to the same article.  Now let's set `distinct`:  ```ruby  class Person    has_many :readings -  has_many :posts, -> { distinct }, through: :readings +  has_many :articles, -> { distinct }, through: :readings  end  person = Person.create(name: 'Honda') -post   = Post.create(name: 'a1') -person.posts << post -person.posts << post -person.posts.inspect # => [#<Post id: 7, name: "a1">] -Reading.all.inspect  # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>] +article   = Article.create(name: 'a1') +person.articles << article +person.articles << article +person.articles.inspect # => [#<Article id: 7, name: "a1">] +Reading.all.inspect  # => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>]  ``` -In the above case there are still two readings. However `person.posts` shows -only one post because the collection loads only unique records. +In the above case there are still two readings. However `person.articles` shows +only one article because the collection loads only unique records.  If you want to make sure that, upon insertion, all of the records in the  persisted association are distinct (so that you can be sure that when you  inspect the association that you will never find duplicate records), you should  add a unique index on the table itself. For example, if you have a table named -`person_posts` and you want to make sure all the posts are unique, you could +`person_articles` and you want to make sure all the articles are unique, you could  add the following in a migration:  ```ruby -add_index :person_posts, :post, unique: true +add_index :person_articles, :article, unique: true  ```  Note that checking for uniqueness using something like `include?` is subject  to race conditions. Do not attempt to use `include?` to enforce distinctness -in an association. For instance, using the post example from above, the +in an association. For instance, using the article example from above, the  following code would be racy because multiple users could be attempting this  at the same time:  ```ruby -person.posts << post unless person.posts.include?(post) +person.articles << article unless person.articles.include?(article)  ```  #### When are Objects Saved? diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index b6423dd44e..c652aa6a80 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -105,7 +105,7 @@ This method generates a cache key that depends on all products and can be used i  <% end %>  ``` -If you want to cache a fragment under certain condition you can use `cache_if` or `cache_unless`  +If you want to cache a fragment under certain condition you can use `cache_if` or `cache_unless`  ```erb  <% cache_if (condition, cache_key_for_products) do %> diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 756c8f8b51..6efc64c385 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -60,7 +60,7 @@ With no further work, `rails server` will run our new shiny Rails app:  ```bash  $ cd commandsapp -$ rails server +$ bin/rails server  => Booting WEBrick  => Rails 4.0.0 application starting in development on http://0.0.0.0:3000  => Call with -d to detach @@ -77,7 +77,7 @@ INFO: You can also use the alias "s" to start the server: `rails s`.  The server can be run on a different port using the `-p` option. The default development environment can be changed using `-e`.  ```bash -$ rails server -e production -p 4000 +$ bin/rails server -e production -p 4000  ```  The `-b` option binds Rails to the specified IP, by default it is 0.0.0.0. You can run a server as a daemon by passing a `-d` option. @@ -89,7 +89,7 @@ The `rails generate` command uses templates to create a whole lot of things. Run  INFO: You can also use the alias "g" to invoke the generator command: `rails g`.  ```bash -$ rails generate +$ bin/rails generate  Usage: rails generate GENERATOR [args] [options]  ... @@ -114,7 +114,7 @@ Let's make our own controller with the controller generator. But what command sh  INFO: All Rails console utilities have help text. As with most *nix utilities, you can try adding `--help` or `-h` to the end, for example `rails server --help`.  ```bash -$ rails generate controller +$ bin/rails generate controller  Usage: rails generate controller NAME [action action] [options]  ... @@ -141,7 +141,7 @@ Example:  The controller generator is expecting parameters in the form of `generate controller ControllerName action1 action2`. Let's make a `Greetings` controller with an action of **hello**, which will say something nice to us.  ```bash -$ rails generate controller Greetings hello +$ bin/rails generate controller Greetings hello       create  app/controllers/greetings_controller.rb        route  get "greetings/hello"       invoke  erb @@ -182,7 +182,7 @@ Then the view, to display our message (in `app/views/greetings/hello.html.erb`):  Fire up your server using `rails server`.  ```bash -$ rails server +$ bin/rails server  => Booting WEBrick...  ``` @@ -193,7 +193,7 @@ INFO: With a normal, plain-old Rails application, your URLs will generally follo  Rails comes with a generator for data models too.  ```bash -$ rails generate model +$ bin/rails generate model  Usage:    rails generate model NAME [field[:type][:index] field[:type][:index]] [options] @@ -216,7 +216,7 @@ But instead of generating a model directly (which we'll be doing later), let's s  We will set up a simple resource called "HighScore" that will keep track of our highest score on video games we play.  ```bash -$ rails generate scaffold HighScore game:string score:integer +$ bin/rails generate scaffold HighScore game:string score:integer      invoke  active_record      create    db/migrate/20130717151933_create_high_scores.rb      create    app/models/high_score.rb @@ -257,7 +257,7 @@ The generator checks that there exist the directories for models, controllers, h  The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The SQLite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while.  ```bash -$ rake db:migrate +$ bin/rake db:migrate  ==  CreateHighScores: migrating ===============================================  -- create_table(:high_scores)     -> 0.0017s @@ -269,7 +269,7 @@ INFO: Let's talk about unit tests. Unit tests are code that tests and makes asse  Let's see the interface Rails created for us.  ```bash -$ rails server +$ bin/rails server  ```  Go to your browser and open [http://localhost:3000/high_scores](http://localhost:3000/high_scores), now we can create new high scores (55,160 on Space Invaders!) @@ -283,13 +283,13 @@ INFO: You can also use the alias "c" to invoke the console: `rails c`.  You can specify the environment in which the `console` command should operate.  ```bash -$ rails console staging +$ bin/rails console staging  ```  If you wish to test out some code without changing any data, you can do that by invoking `rails console --sandbox`.  ```bash -$ rails console --sandbox +$ bin/rails console --sandbox  Loading development environment in sandbox (Rails 4.0.0)  Any modifications you make will be rolled back on exit  irb(main):001:0> @@ -306,7 +306,7 @@ INFO: You can also use the alias "db" to invoke the dbconsole: `rails db`.  `runner` runs Ruby code in the context of Rails non-interactively. For instance:  ```bash -$ rails runner "Model.long_running_method" +$ bin/rails runner "Model.long_running_method"  ```  INFO: You can also use the alias "r" to invoke the runner: `rails r`. @@ -314,7 +314,7 @@ INFO: You can also use the alias "r" to invoke the runner: `rails r`.  You can specify the environment in which the `runner` command should operate using the `-e` switch.  ```bash -$ rails runner -e staging "Model.long_running_method" +$ bin/rails runner -e staging "Model.long_running_method"  ```  ### `rails destroy` @@ -324,7 +324,7 @@ Think of `destroy` as the opposite of `generate`. It'll figure out what generate  INFO: You can also use the alias "d" to invoke the destroy command: `rails d`.  ```bash -$ rails generate model Oops +$ bin/rails generate model Oops        invoke  active_record        create    db/migrate/20120528062523_create_oops.rb        create    app/models/oops.rb @@ -333,7 +333,7 @@ $ rails generate model Oops        create      test/fixtures/oops.yml  ```  ```bash -$ rails destroy model Oops +$ bin/rails destroy model Oops        invoke  active_record        remove    db/migrate/20120528062523_create_oops.rb        remove    app/models/oops.rb @@ -353,9 +353,10 @@ To get the full backtrace for running rake task you can pass the option  ```--trace``` to command line, for example ```rake db:create --trace```.  ```bash -$ rake --tasks +$ bin/rake --tasks  rake about              # List versions of all Rails frameworks and the environment -rake assets:clean       # Remove compiled assets +rake assets:clean       # Remove old compiled assets +rake assets:clobber     # Remove compiled assets  rake assets:precompile  # Compile all the assets named in config.assets.precompile  rake db:create          # Create the database from config/database.yml for the current Rails.env  ... @@ -372,18 +373,18 @@ INFO: You can also use ```rake -T```  to get the list of tasks.  `rake about` gives information about version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version. It is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation.  ```bash -$ rake about +$ bin/rake about  About your application's environment  Ruby version              1.9.3 (x86_64-linux)  RubyGems version          1.3.6  Rack version              1.3 -Rails version             4.1.0 +Rails version             4.1.1  JavaScript Runtime        Node.js (V8) -Active Record version     4.1.0 -Action Pack version       4.1.0 -Action View version       4.1.0 -Action Mailer version     4.1.0 -Active Support version    4.1.0 +Active Record version     4.1.1 +Action Pack version       4.1.1 +Action View version       4.1.1 +Action Mailer version     4.1.1 +Active Support version    4.1.1  Middleware                Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag  Application root          /home/foobar/commandsapp  Environment               development @@ -393,7 +394,12 @@ Database schema version   20110805173523  ### `assets` -You can precompile the assets in `app/assets` using `rake assets:precompile` and remove those compiled assets using `rake assets:clean`. +You can precompile the assets in `app/assets` using `rake assets:precompile`, +and remove older compiled assets using `rake assets:clean`. The `assets:clean` +task allows for rolling deploys that may still be linking to an old asset while +the new assets are being built. + +If you want to clear `public/assets` completely, you can use `rake assets:clobber`.  ### `db` @@ -414,7 +420,7 @@ The `doc:` namespace has the tools to generate documentation for your app, API d  `rake notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.rake`, `.yml`, `.yaml`, `.ruby`, `.css`, `.js` and `.erb` for both default and custom annotations.  ```bash -$ rake notes +$ bin/rake notes  (in /home/foobar/commandsapp)  app/controllers/admin/users_controller.rb:    * [ 20] [TODO] any other way to do this? @@ -434,7 +440,7 @@ config.annotations.register_extensions("scss", "sass", "less") { |annotation| /\  If you are looking for a specific annotation, say FIXME, you can use `rake notes:fixme`. Note that you have to lower case the annotation's name.  ```bash -$ rake notes:fixme +$ bin/rake notes:fixme  (in /home/foobar/commandsapp)  app/controllers/admin/users_controller.rb:    * [132] high priority for next deploy @@ -446,9 +452,9 @@ app/models/school.rb:  You can also use custom annotations in your code and list them using `rake notes:custom` by specifying the annotation using an environment variable `ANNOTATION`.  ```bash -$ rake notes:custom ANNOTATION=BUG +$ bin/rake notes:custom ANNOTATION=BUG  (in /home/foobar/commandsapp) -app/models/post.rb: +app/models/article.rb:    * [ 23] Have to fix this one before pushing!  ``` @@ -458,7 +464,7 @@ By default, `rake notes` will look in the `app`, `config`, `lib`, `bin` and `tes  ```bash  $ export SOURCE_ANNOTATION_DIRECTORIES='spec,vendor' -$ rake notes +$ bin/rake notes  (in /home/foobar/commandsapp)  app/models/user.rb:    * [ 35] [FIXME] User should have a subscription at this point @@ -530,9 +536,9 @@ end  Invocation of the tasks will look like:  ```bash -rake task_name -rake "task_name[value 1]" # entire argument string should be quoted -rake db:nothing +$ bin/rake task_name +$ bin/rake "task_name[value 1]" # entire argument string should be quoted +$ bin/rake db:nothing  ```  NOTE: If your need to interact with your application models, perform database queries and so on, your task should depend on the `environment` task, which will load your application code. diff --git a/guides/source/configuring.md b/guides/source/configuring.md index ae382fc54d..7a9e1beb23 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -388,13 +388,13 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.  * `config.action_view.embed_authenticity_token_in_remote_forms` allows you to set the default behavior for `authenticity_token` in forms with `:remote => true`. By default it's set to false, which means that remote forms will not include `authenticity_token`, which is helpful when you're fragment-caching the form. Remote forms get the authenticity from the `meta` tag, so embedding is unnecessary unless you support browsers without JavaScript. In such case you can either pass `:authenticity_token => true` as a form option or set this config setting to `true` -* `config.action_view.prefix_partial_path_with_controller_namespace` determines whether or not partials are looked up from a subdirectory in templates rendered from namespaced controllers. For example, consider a controller named `Admin::PostsController` which renders this template: +* `config.action_view.prefix_partial_path_with_controller_namespace` determines whether or not partials are looked up from a subdirectory in templates rendered from namespaced controllers. For example, consider a controller named `Admin::ArticlesController` which renders this template:      ```erb -    <%= render @post %> +    <%= render @article %>      ``` -    The default setting is `true`, which uses the partial at `/admin/posts/_post.erb`. Setting the value to `false` would render `/posts/_post.erb`, which is the same behavior as rendering from a non-namespaced controller such as `PostsController`. +    The default setting is `true`, which uses the partial at `/admin/articles/_article.erb`. Setting the value to `false` would render `/articles/_article.erb`, which is the same behavior as rendering from a non-namespaced controller such as `ArticlesController`.  * `config.action_view.raise_on_missing_translations` determines whether an error should be raised for missing translations @@ -552,7 +552,7 @@ development:  $ echo $DATABASE_URL  postgresql://localhost/my_database -$ rails runner 'puts ActiveRecord::Base.connections' +$ bin/rails runner 'puts ActiveRecord::Base.connections'  {"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database"}}  ``` @@ -569,7 +569,7 @@ development:  $ echo $DATABASE_URL  postgresql://localhost/my_database -$ rails runner 'puts ActiveRecord::Base.connections' +$ bin/rails runner 'puts ActiveRecord::Base.connections'  {"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database", "pool"=>5}}  ``` @@ -585,7 +585,7 @@ development:  $ echo $DATABASE_URL  postgresql://localhost/my_database -$ rails runner 'puts ActiveRecord::Base.connections' +$ bin/rails runner 'puts ActiveRecord::Base.connections'  {"development"=>{"adapter"=>"sqlite3", "database"=>"NOT_my_database"}}  ``` @@ -729,13 +729,47 @@ Rails will now prepend "/app1" when generating links.  #### Using Passenger -Passenger makes it easy to run your application in a subdirectory. You can find -the relevant configuration in the -[passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri). +Passenger makes it easy to run your application in a subdirectory. You can find the relevant configuration in the [passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri).  #### Using a Reverse Proxy -TODO +Deploying your application using a reverse proxy has definite advantages over traditional deploys. They allow you to have more control over your server by layering the components required by your application. + +Many modern web servers can be used as a proxy server to balance third-party elements such as caching servers or application servers. + +One such application server you can use is [Unicorn](http://unicorn.bogomips.org/) to run behind a reverse proxy. + +In this case, you would need to configure the proxy server (nginx, apache, etc) to accept connections from your application server (Unicorn). By default Unicorn will listen for TCP connections on port 8080, but you can change the port or configure it to use sockets instead. + +You can find more information in the [Unicorn readme](http://unicorn.bogomips.org/README.html) and understand the [philosophy](http://unicorn.bogomips.org/PHILOSOPHY.html) behind it. + +Once you've configured the application server, you must proxy requests to it by configuring your web server appropriately. For example your nginx config may include: + +``` +upstream application_server { +  server 0.0.0.0:8080 +} + +server { +  listen 80; +  server_name localhost; + +  root /root/path/to/your_app/public; + +  try_files $uri/index.html $uri.html @app; + +  location @app { +    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +    proxy_set_header Host $http_host; +    proxy_redirect off; +    proxy_pass http://application_server; +  } + +  # some other configuration +} +``` + +Be sure to read the [nginx documentation](http://nginx.org/en/docs/) for the most up-to-date information.  #### Considerations when deploying to a subdirectory diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 57010b85c3..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 @@ -265,15 +295,15 @@ This is how you run the Active Record test suite only for SQLite3:  ```bash  $ cd activerecord -$ bundle exec rake test_sqlite3 +$ bundle exec rake test:sqlite3  ```  You can now run the tests as you did for `sqlite3`. The tasks are respectively  ```bash -test_mysql -test_mysql2 -test_postgresql +test:mysql +test:mysql2 +test:postgresql  ```  Finally, @@ -361,9 +391,9 @@ it should not be necessary to visit a webpage to check the history.  Description can have multiple paragraphs and you can use code examples  inside, just indent it with 4 spaces: -    class PostsController +    class ArticlesController        def index -        respond_with Post.limit(10) +        respond_with Article.limit(10)        end      end diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index b067d9efb7..5f738b76af 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -26,17 +26,17 @@ One common task is to inspect the contents of a variable. In Rails, you can do t  The `debug` helper will return a \<pre> tag that renders the object using the YAML format. This will generate human-readable data from any object. For example, if you have this code in a view:  ```html+erb -<%= debug @post %> +<%= debug @article %>  <p>    <b>Title:</b> -  <%= @post.title %> +  <%= @article.title %>  </p>  ```  You'll see something like this:  ```yaml ---- !ruby/object:Post +--- !ruby/object Article  attributes:    updated_at: 2008-09-05 22:55:47    body: It's a very helpful guide for debugging your Rails app. @@ -55,10 +55,10 @@ Title: Rails debugging guide  Displaying an instance variable, or any other object or method, in YAML format can be achieved this way:  ```html+erb -<%= simple_format @post.to_yaml %> +<%= simple_format @article.to_yaml %>  <p>    <b>Title:</b> -  <%= @post.title %> +  <%= @article.title %>  </p>  ``` @@ -67,7 +67,7 @@ The `to_yaml` method converts the method to YAML format leaving it more readable  As a result of this, you will have something like this in your view:  ```yaml ---- !ruby/object:Post +--- !ruby/object Article  attributes:  updated_at: 2008-09-05 22:55:47  body: It's a very helpful guide for debugging your Rails app. @@ -88,7 +88,7 @@ Another useful method for displaying object values is `inspect`, especially when  <%= [1, 2, 3, 4, 5].inspect %>  <p>    <b>Title:</b> -  <%= @post.title %> +  <%= @article.title %>  </p>  ``` @@ -153,18 +153,18 @@ logger.fatal "Terminating application, raised unrecoverable error!!!"  Here's an example of a method instrumented with extra logging:  ```ruby -class PostsController < ApplicationController +class ArticlesController < ApplicationController    # ...    def create -    @post = Post.new(params[:post]) -    logger.debug "New post: #{@post.attributes.inspect}" -    logger.debug "Post should be valid: #{@post.valid?}" - -    if @post.save -      flash[:notice] = 'Post was successfully created.' -      logger.debug "The post was saved and now the user is going to be redirected..." -      redirect_to(@post) +    @article = Article.new(params[:article]) +    logger.debug "New article: #{@article.attributes.inspect}" +    logger.debug  Article should be valid: #{@article.valid?}" + +    if @article.save +      flash[:notice] =  Article was successfully created.' +      logger.debug "The article was saved and now the user is going to be redirected..." +      redirect_to(@article)      else        render action: "new"      end @@ -177,21 +177,20 @@ end  Here's an example of the log generated when this controller action is executed:  ``` -Processing PostsController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST] +Processing ArticlesController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST]    Session ID: BAh7BzoMY3NyZl9pZCIlMDY5MWU1M2I1ZDRjODBlMzkyMWI1OTg2NWQyNzViZjYiCmZsYXNoSUM6J0FjdGl  vbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7AAY6CkB1c2VkewA=--b18cd92fba90eacf8137e5f6b3b06c4d724596a4 -  Parameters: {"commit"=>"Create", "post"=>{"title"=>"Debugging Rails", +  Parameters: {"commit"=>"Create", "article"=>{"title"=>"Debugging Rails",   "body"=>"I'm learning how to print in logs!!!", "published"=>"0"}, - "authenticity_token"=>"2059c1286e93402e389127b1153204e0d1e275dd", "action"=>"create", "controller"=>"posts"} -New post: {"updated_at"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", - "published"=>false, "created_at"=>nil} -Post should be valid: true -  Post Create (0.000443)   INSERT INTO "posts" ("updated_at", "title", "body", "published", + "authenticity_token"=>"2059c1286e93402e389127b1153204e0d1e275dd", "action"=>"create", "controller"=>"articles"} +New article: {"updated_at"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", + "published"=>false, "created_at"=>nil} Article should be valid: true +  Article Create (0.000443)   INSERT INTO "articles" ("updated_at", "title", "body", "published",   "created_at") VALUES('2008-09-08 14:52:54', 'Debugging Rails',   'I''m learning how to print in logs!!!', 'f', '2008-09-08 14:52:54') -The post was saved and now the user is going to be redirected... -Redirected to #<Post:0x20af760> -Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/posts] +The article was saved and now the user is going to be redirected... +Redirected to # Article:0x20af760> +Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/articles]  ```  Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels to avoid filling your production logs with useless trivia. @@ -286,17 +285,17 @@ Before the prompt, the code around the line that is about to be run will be  displayed and the current line will be marked by '=>'. Like this:  ``` -[1, 10] in /PathTo/project/app/controllers/posts_controller.rb +[1, 10] in /PathTo/project/app/controllers/articles_controller.rb      3: -    4:   # GET /posts -    5:   # GET /posts.json +    4:   # GET /articles +    5:   # GET /articles.json      6:   def index      7:     byebug -=>  8:     @posts = Post.find_recent +=>  8:     @articles = Article.find_recent      9:     10:     respond_to do |format|     11:       format.html # index.html.erb -   12:       format.json { render json: @posts } +   12:       format.json { render json: @articles }  (byebug)  ``` @@ -309,7 +308,7 @@ For example:  ```bash  => Booting WEBrick -=> Rails 4.1.0 application starting in development on http://0.0.0.0:3000 +=> Rails 4.1.1 application starting in development on http://0.0.0.0:3000  => Run `rails server -h` for more startup options  => Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)  => Ctrl-C to shutdown server @@ -320,19 +319,19 @@ For example:  Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200    ActiveRecord::SchemaMigration Load (0.2ms)  SELECT "schema_migrations".* FROM "schema_migrations" -Processing by PostsController#index as HTML +Processing by ArticlesController#index as HTML -[3, 12] in /PathTo/project/app/controllers/posts_controller.rb +[3, 12] in /PathTo/project/app/controllers/articles_controller.rb      3: -    4:   # GET /posts -    5:   # GET /posts.json +    4:   # GET /articles +    5:   # GET /articles.json      6:   def index      7:     byebug -=>  8:     @posts = Post.find_recent +=>  8:     @articles = Article.find_recent      9:     10:     respond_to do |format|     11:       format.html # index.html.erb -   12:       format.json { render json: @posts } +   12:       format.json { render json: @articles }  (byebug)  ``` @@ -365,15 +364,15 @@ To see the previous ten lines you should type `list-` (or `l-`)  ```  (byebug) l- -[1, 10] in /PathTo/project/app/controllers/posts_controller.rb -   1  class PostsController < ApplicationController -   2    before_action :set_post, only: [:show, :edit, :update, :destroy] +[1, 10] in /PathTo/project/app/controllers/articles_controller.rb +   1  class ArticlesController < ApplicationController +   2    before_action :set_article, only: [:show, :edit, :update, :destroy]     3 -   4    # GET /posts -   5    # GET /posts.json +   4    # GET /articles +   5    # GET /articles.json     6    def index     7      byebug -   8      @posts = Post.find_recent +   8      @articles = Article.find_recent     9     10      respond_to do |format| @@ -386,17 +385,17 @@ the code again you can type `list=`  ```  (byebug) list= -[3, 12] in /PathTo/project/app/controllers/posts_controller.rb +[3, 12] in /PathTo/project/app/controllers/articles_controller.rb      3: -    4:   # GET /posts -    5:   # GET /posts.json +    4:   # GET /articles +    5:   # GET /articles.json      6:   def index      7:     byebug -=>  8:     @posts = Post.find_recent +=>  8:     @articles = Article.find_recent      9:     10:     respond_to do |format|     11:       format.html # index.html.erb -   12:       format.json { render json: @posts } +   12:       format.json { render json: @articles }  (byebug)  ``` @@ -419,14 +418,14 @@ then `backtrace` will supply the answer.  ```  (byebug) where ---> #0  PostsController.index -      at /PathTo/project/test_app/app/controllers/posts_controller.rb:8 +--> #0  ArticlesController.index +      at /PathTo/project/test_app/app/controllers/articles_controller.rb:8      #1  ActionController::ImplicitRender.send_action(method#String, *args#Array) -      at /PathToGems/actionpack-4.1.0/lib/action_controller/metal/implicit_render.rb:4 +      at /PathToGems/actionpack-4.1.1/lib/action_controller/metal/implicit_render.rb:4      #2  AbstractController::Base.process_action(action#NilClass, *args#Array) -      at /PathToGems/actionpack-4.1.0/lib/abstract_controller/base.rb:189 +      at /PathToGems/actionpack-4.1.1/lib/abstract_controller/base.rb:189      #3  ActionController::Rendering.process_action(action#NilClass, *args#NilClass) -      at /PathToGems/actionpack-4.1.0/lib/action_controller/metal/rendering.rb:10 +      at /PathToGems/actionpack-4.1.1/lib/action_controller/metal/rendering.rb:10  ...  ``` @@ -438,7 +437,7 @@ context.  ```  (byebug) frame 2 -[184, 193] in /PathToGems/actionpack-4.1.0/lib/abstract_controller/base.rb +[184, 193] in /PathToGems/actionpack-4.1.1/lib/abstract_controller/base.rb     184:       # is the intended way to override action dispatching.     185:       #     186:       # Notice that the first argument is the method to be dispatched @@ -487,17 +486,17 @@ This example shows how you can print the instance variables defined within the  current context:  ``` -[3, 12] in /PathTo/project/app/controllers/posts_controller.rb +[3, 12] in /PathTo/project/app/controllers/articles_controller.rb      3: -    4:   # GET /posts -    5:   # GET /posts.json +    4:   # GET /articles +    5:   # GET /articles.json      6:   def index      7:     byebug -=>  8:     @posts = Post.find_recent +=>  8:     @articles = Article.find_recent      9:     10:     respond_to do |format|     11:       format.html # index.html.erb -   12:       format.json { render json: @posts } +   12:       format.json { render json: @articles }  (byebug) instance_variables  [:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request, @@ -512,15 +511,15 @@ command later in this guide).  ```  (byebug) next -[5, 14] in /PathTo/project/app/controllers/posts_controller.rb -   5     # GET /posts.json +[5, 14] in /PathTo/project/app/controllers/articles_controller.rb +   5     # GET /articles.json     6     def index     7       byebug -   8       @posts = Post.find_recent +   8       @articles = Article.find_recent     9  => 10       respond_to do |format|     11         format.html # index.html.erb -   12        format.json { render json: @posts } +   12        format.json { render json: @articles }     13      end     14    end     15 @@ -530,11 +529,11 @@ command later in this guide).  And then ask again for the instance_variables:  ``` -(byebug) instance_variables.include? "@posts" +(byebug) instance_variables.include? "@articles"  true  ``` -Now `@posts` is included in the instance variables, because the line defining it +Now `@articles` is included in the instance variables, because the line defining it  was executed.  TIP: You can also step into **irb** mode with the command `irb` (of course!). @@ -564,7 +563,7 @@ example, to check that we have no local variables currently defined.  You can also inspect for an object method this way:  ``` -(byebug) var instance Post.new +(byebug) var instance Article.new  @_start_transaction_state = {}  @aggregation_cache = {}  @association_cache = {} @@ -581,8 +580,8 @@ You can use also `display` to start watching variables. This is a good way of  tracking the values of a variable while the execution goes on.  ``` -(byebug) display @posts -1: @posts = nil +(byebug) display @articles +1: @articles = nil  ```  The variables inside the displaying list will be printed with their values after @@ -611,10 +610,10 @@ For example, consider the following situation:  ```ruby  Started GET "/" for 127.0.0.1 at 2014-04-11 13:39:23 +0200 -Processing by PostsController#index as HTML +Processing by ArticlesController#index as HTML -[1, 8] in /home/davidr/Proyectos/test_app/app/models/post.rb -   1: class Post < ActiveRecord::Base +[1, 8] in /home/davidr/Proyectos/test_app/app/models/article.rb +   1: class Article < ActiveRecord::Base     2:     3:   def self.find_recent(limit = 10)     4:     byebug @@ -634,15 +633,15 @@ the method, so `byebug` will jump to next next line of the previous frame.  (byebug) next  Next went up a frame because previous frame finished -[4, 13] in /PathTo/project/test_app/app/controllers/posts_controller.rb -    4:   # GET /posts -    5:   # GET /posts.json +[4, 13] in /PathTo/project/test_app/app/controllers/articles_controller.rb +    4:   # GET /articles +    5:   # GET /articles.json      6:   def index -    7:     @posts = Post.find_recent +    7:     @articles = Article.find_recent      8:  =>  9:     respond_to do |format|     10:       format.html # index.html.erb -   11:       format.json { render json: @posts } +   11:       format.json { render json: @articles }     12:     end     13:   end @@ -655,7 +654,7 @@ instruction to be executed. In this case, the activesupport's `week` method.  ```  (byebug) step -[50, 59] in /PathToGems/activesupport-4.1.0/lib/active_support/core_ext/numeric/time.rb +[50, 59] in /PathToGems/activesupport-4.1.1/lib/active_support/core_ext/numeric/time.rb     50:     ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])     51:   end     52:   alias :day :days @@ -693,20 +692,20 @@ _expression_ works the same way as with file:line.  For example, in the previous situation  ``` -[4, 13] in /PathTo/project/app/controllers/posts_controller.rb -    4:   # GET /posts -    5:   # GET /posts.json +[4, 13] in /PathTo/project/app/controllers/articles_controller.rb +    4:   # GET /articles +    5:   # GET /articles.json      6:   def index -    7:     @posts = Post.find_recent +    7:     @articles = Article.find_recent      8:  =>  9:     respond_to do |format|     10:       format.html # index.html.erb -   11:       format.json { render json: @posts } +   11:       format.json { render json: @articles }     12:     end     13:   end  (byebug) break 11 -Created breakpoint 1 at /PathTo/project/app/controllers/posts_controller.rb:11 +Created breakpoint 1 at /PathTo/project/app/controllers/articles_controller.rb:11  ``` @@ -716,7 +715,7 @@ supply a number, it lists that breakpoint. Otherwise it lists all breakpoints.  ```  (byebug) info breakpoints  Num Enb What -1   y   at /PathTo/project/app/controllers/posts_controller.rb:11 +1   y   at /PathTo/project/app/controllers/articles_controller.rb:11  ```  To delete breakpoints: use the command `delete _n_` to remove the breakpoint diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index b0e070120d..b134c9d2d0 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -249,7 +249,7 @@ and create the test databases:  ```bash  $ cd activerecord -$ bundle exec rake mysql:build_databases +$ bundle exec rake db:mysql:build  ```  PostgreSQL's authentication works differently. A simple way to set up the development environment for example is to run with your development account @@ -267,7 +267,7 @@ and then create the test databases with  ```bash  $ cd activerecord -$ bundle exec rake postgresql:build_databases +$ bundle exec rake db:postgresql:build  ```  It is possible to build databases for both PostgreSQL and MySQL with diff --git a/guides/source/engines.md b/guides/source/engines.md index 8f9ba0995f..1321fa3870 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -36,14 +36,14 @@ engine **can** be a plugin, and a plugin **can** be an engine.  The engine that will be created in this guide will be called "blorgh". The  engine will provide blogging functionality to its host applications, allowing -for new posts and comments to be created. At the beginning of this guide, you +for new articles and comments to be created. At the beginning of this guide, you  will be working solely within the engine itself, but in later sections you'll  see how to hook it into an application.  Engines can also be isolated from their host applications. This means that an  application is able to have a path provided by a routing helper such as -`posts_path` and use an engine also that provides a path also called -`posts_path`, and the two would not clash. Along with this, controllers, models +`articles_path` and use an engine also that provides a path also called +`articles_path`, and the two would not clash. Along with this, controllers, models  and table names are also namespaced. You'll see how to do this later in this  guide. @@ -73,13 +73,13 @@ options as appropriate to the need. For the "blorgh" example, you will need to  create a "mountable" engine, running this command in a terminal:  ```bash -$ rails plugin new blorgh --mountable +$ bin/rails plugin new blorgh --mountable  ```  The full list of options for the plugin generator may be seen by typing:  ```bash -$ rails plugin --help +$ bin/rails plugin --help  ```  The `--full` option tells the generator that you want to create an engine, @@ -197,12 +197,12 @@ within the `Engine` class definition. Without it, classes generated in an engine  **may** conflict with an application.  What this isolation of the namespace means is that a model generated by a call -to `rails g model`, such as `rails g model post`, won't be called `Post`, but -instead be namespaced and called `Blorgh::Post`. In addition, the table for the -model is namespaced, becoming `blorgh_posts`, rather than simply `posts`. -Similar to the model namespacing, a controller called `PostsController` becomes -`Blorgh::PostsController` and the views for that controller will not be at -`app/views/posts`, but `app/views/blorgh/posts` instead. Mailers are namespaced +to `bin/rails g model`, such as `bin/rails g model article`, won't be called `Article`, but +instead be namespaced and called `Blorgh::Article`. In addition, the table for the +model is namespaced, becoming `blorgh_articles`, rather than simply `articles`. +Similar to the model namespacing, a controller called `ArticlesController` becomes +`Blorgh::ArticlesController` and the views for that controller will not be at +`app/views/articles`, but `app/views/blorgh/articles` instead. Mailers are namespaced  as well.  Finally, routes will also be isolated within the engine. This is one of the most @@ -253,7 +253,7 @@ This means that you will be able to generate new controllers and models for this  engine very easily by running commands like this:  ```bash -rails g model +$ bin/rails g model  ```  Keep in mind, of course, that anything generated with these commands inside of @@ -283,74 +283,74 @@ created in the `test` directory as well. For example, you may wish to create a  Providing engine functionality  ------------------------------ -The engine that this guide covers provides posting and commenting functionality -and follows a similar thread to the [Getting Started +The engine that this guide covers provides submitting articles and commenting +functionality and follows a similar thread to the [Getting Started  Guide](getting_started.html), with some new twists. -### Generating a Post Resource +### Generating an Article Resource -The first thing to generate for a blog engine is the `Post` model and related +The first thing to generate for a blog engine is the `Article` model and related  controller. To quickly generate this, you can use the Rails scaffold generator.  ```bash -$ rails generate scaffold post title:string text:text +$ bin/rails generate scaffold article title:string text:text  ```  This command will output this information:  ```  invoke  active_record -create    db/migrate/[timestamp]_create_blorgh_posts.rb -create    app/models/blorgh/post.rb +create    db/migrate/[timestamp]_create_blorgh_articles.rb +create    app/models/blorgh/article.rb  invoke    test_unit -create      test/models/blorgh/post_test.rb -create      test/fixtures/blorgh/posts.yml +create      test/models/blorgh/article_test.rb +create      test/fixtures/blorgh/articles.yml  invoke  resource_route - route    resources :posts + route    resources :articles  invoke  scaffold_controller -create    app/controllers/blorgh/posts_controller.rb +create    app/controllers/blorgh/articles_controller.rb  invoke    erb -create      app/views/blorgh/posts -create      app/views/blorgh/posts/index.html.erb -create      app/views/blorgh/posts/edit.html.erb -create      app/views/blorgh/posts/show.html.erb -create      app/views/blorgh/posts/new.html.erb -create      app/views/blorgh/posts/_form.html.erb +create      app/views/blorgh/articles +create      app/views/blorgh/articles/index.html.erb +create      app/views/blorgh/articles/edit.html.erb +create      app/views/blorgh/articles/show.html.erb +create      app/views/blorgh/articles/new.html.erb +create      app/views/blorgh/articles/_form.html.erb  invoke    test_unit -create      test/controllers/blorgh/posts_controller_test.rb +create      test/controllers/blorgh/articles_controller_test.rb  invoke    helper -create      app/helpers/blorgh/posts_helper.rb +create      app/helpers/blorgh/articles_helper.rb  invoke      test_unit -create        test/helpers/blorgh/posts_helper_test.rb +create        test/helpers/blorgh/articles_helper_test.rb  invoke  assets  invoke    js -create      app/assets/javascripts/blorgh/posts.js +create      app/assets/javascripts/blorgh/articles.js  invoke    css -create      app/assets/stylesheets/blorgh/posts.css +create      app/assets/stylesheets/blorgh/articles.css  invoke  css  create    app/assets/stylesheets/scaffold.css  ```  The first thing that the scaffold generator does is invoke the `active_record`  generator, which generates a migration and a model for the resource. Note here, -however, that the migration is called `create_blorgh_posts` rather than the -usual `create_posts`. This is due to the `isolate_namespace` method called in +however, that the migration is called `create_blorgh_articles` rather than the +usual `create_articles`. This is due to the `isolate_namespace` method called in  the `Blorgh::Engine` class's definition. The model here is also namespaced, -being placed at `app/models/blorgh/post.rb` rather than `app/models/post.rb` due +being placed at `app/models/blorgh/article.rb` rather than `app/models/article.rb` due  to the `isolate_namespace` call within the `Engine` class.  Next, the `test_unit` generator is invoked for this model, generating a model -test at `test/models/blorgh/post_test.rb` (rather than -`test/models/post_test.rb`) and a fixture at `test/fixtures/blorgh/posts.yml` -(rather than `test/fixtures/posts.yml`). +test at `test/models/blorgh/article_test.rb` (rather than +`test/models/article_test.rb`) and a fixture at `test/fixtures/blorgh/articles.yml` +(rather than `test/fixtures/articles.yml`).  After that, a line for the resource is inserted into the `config/routes.rb` file -for the engine. This line is simply `resources :posts`, turning the +for the engine. This line is simply `resources :articles`, turning the  `config/routes.rb` file for the engine into this:  ```ruby  Blorgh::Engine.routes.draw do -  resources :posts +  resources :articles  end  ``` @@ -362,18 +362,18 @@ be isolated from those routes that are within the application. The  [Routes](#routes) section of this guide describes it in detail.  Next, the `scaffold_controller` generator is invoked, generating a controller -called `Blorgh::PostsController` (at -`app/controllers/blorgh/posts_controller.rb`) and its related views at -`app/views/blorgh/posts`. This generator also generates a test for the -controller (`test/controllers/blorgh/posts_controller_test.rb`) and a helper -(`app/helpers/blorgh/posts_controller.rb`). +called `Blorgh::ArticlesController` (at +`app/controllers/blorgh/articles_controller.rb`) and its related views at +`app/views/blorgh/articles`. This generator also generates a test for the +controller (`test/controllers/blorgh/articles_controller_test.rb`) and a helper +(`app/helpers/blorgh/articles_controller.rb`).  Everything this generator has created is neatly namespaced. The controller's  class is defined within the `Blorgh` module:  ```ruby  module Blorgh -  class PostsController < ApplicationController +  class ArticlesController < ApplicationController      ...    end  end @@ -382,22 +382,22 @@ end  NOTE: The `ApplicationController` class being inherited from here is the  `Blorgh::ApplicationController`, not an application's `ApplicationController`. -The helper inside `app/helpers/blorgh/posts_helper.rb` is also namespaced: +The helper inside `app/helpers/blorgh/articles_helper.rb` is also namespaced:  ```ruby  module Blorgh -  module PostsHelper +  module ArticlesHelper      ...    end  end  ```  This helps prevent conflicts with any other engine or application that may have -a post resource as well. +a article resource as well.  Finally, the assets for this resource are generated in two files: -`app/assets/javascripts/blorgh/posts.js` and -`app/assets/stylesheets/blorgh/posts.css`. You'll see how to use these a little +`app/assets/javascripts/blorgh/articles.js` and +`app/assets/stylesheets/blorgh/articles.css`. You'll see how to use these a little  later.  By default, the scaffold styling is not applied to the engine because the @@ -412,46 +412,46 @@ tag of this layout:  You can see what the engine has so far by running `rake db:migrate` at the root  of our engine to run the migration generated by the scaffold generator, and then  running `rails server` in `test/dummy`. When you open -`http://localhost:3000/blorgh/posts` you will see the default scaffold that has +`http://localhost:3000/blorgh/articles` you will see the default scaffold that has  been generated. Click around! You've just generated your first engine's first  functions.  If you'd rather play around in the console, `rails console` will also work just -like a Rails application. Remember: the `Post` model is namespaced, so to -reference it you must call it as `Blorgh::Post`. +like a Rails application. Remember: the `Article` model is namespaced, so to +reference it you must call it as `Blorgh::Article`.  ```ruby ->> Blorgh::Post.find(1) -=> #<Blorgh::Post id: 1 ...> +>> Blorgh::Article.find(1) +=> #<Blorgh::Article id: 1 ...>  ``` -One final thing is that the `posts` resource for this engine should be the root +One final thing is that the `articles` resource for this engine should be the root  of the engine. Whenever someone goes to the root path where the engine is -mounted, they should be shown a list of posts. This can be made to happen if +mounted, they should be shown a list of articles. This can be made to happen if  this line is inserted into the `config/routes.rb` file inside the engine:  ```ruby -root to: "posts#index" +root to: "articles#index"  ``` -Now people will only need to go to the root of the engine to see all the posts, -rather than visiting `/posts`. This means that instead of -`http://localhost:3000/blorgh/posts`, you only need to go to +Now people will only need to go to the root of the engine to see all the articles, +rather than visiting `/articles`. This means that instead of +`http://localhost:3000/blorgh/articles`, you only need to go to  `http://localhost:3000/blorgh` now.  ### Generating a Comments Resource -Now that the engine can create new blog posts, it only makes sense to add +Now that the engine can create new articles, it only makes sense to add  commenting functionality as well. To do this, you'll need to generate a comment -model, a comment controller and then modify the posts scaffold to display +model, a comment controller and then modify the articles scaffold to display  comments and allow people to create new ones.  From the application root, run the model generator. Tell it to generate a -`Comment` model, with the related table having two columns: a `post_id` integer +`Comment` model, with the related table having two columns: a `article_id` integer  and `text` text column.  ```bash -$ rails generate model Comment post_id:integer text:text +$ bin/rails generate model Comment article_id:integer text:text  ```  This will output the following: @@ -471,20 +471,20 @@ called `Blorgh::Comment`. Now run the migration to create our blorgh_comments  table:  ```bash -$ rake db:migrate +$ bin/rake db:migrate  ``` -To show the comments on a post, edit `app/views/blorgh/posts/show.html.erb` and +To show the comments on an article, edit `app/views/blorgh/articles/show.html.erb` and  add this line before the "Edit" link:  ```html+erb  <h3>Comments</h3> -<%= render @post.comments %> +<%= render @article.comments %>  ```  This line will require there to be a `has_many` association for comments defined -on the `Blorgh::Post` model, which there isn't right now. To define one, open -`app/models/blorgh/post.rb` and add this line into the model: +on the `Blorgh::Article` model, which there isn't right now. To define one, open +`app/models/blorgh/article.rb` and add this line into the model:  ```ruby  has_many :comments @@ -494,7 +494,7 @@ Turning the model into this:  ```ruby  module Blorgh -  class Post < ActiveRecord::Base +  class Article < ActiveRecord::Base      has_many :comments    end  end @@ -505,9 +505,9 @@ NOTE: Because the `has_many` is defined inside a class that is inside the  model for these objects, so there's no need to specify that using the  `:class_name` option here. -Next, there needs to be a form so that comments can be created on a post. To add -this, put this line underneath the call to `render @post.comments` in -`app/views/blorgh/posts/show.html.erb`: +Next, there needs to be a form so that comments can be created on a article. To add +this, put this line underneath the call to `render @article.comments` in +`app/views/blorgh/articles/show.html.erb`:  ```erb  <%= render "blorgh/comments/form" %> @@ -519,7 +519,7 @@ directory at `app/views/blorgh/comments` and in it a new file called  ```html+erb  <h3>New comment</h3> -<%= form_for [@post, @post.comments.build] do |f| %> +<%= form_for [@article, @article.comments.build] do |f| %>    <p>      <%= f.label :text %><br>      <%= f.text_area :text %> @@ -529,12 +529,12 @@ directory at `app/views/blorgh/comments` and in it a new file called  ```  When this form is submitted, it is going to attempt to perform a `POST` request -to a route of `/posts/:post_id/comments` within the engine. This route doesn't -exist at the moment, but can be created by changing the `resources :posts` line +to a route of `/articles/:article_id/comments` within the engine. This route doesn't +exist at the moment, but can be created by changing the `resources :articles` line  inside `config/routes.rb` into these lines:  ```ruby -resources :posts do +resources :articles do    resources :comments  end  ``` @@ -545,7 +545,7 @@ The route now exists, but the controller that this route goes to does not. To  create it, run this command from the application root:  ```bash -$ rails g controller comments +$ bin/rails g controller comments  ```  This will generate the following things: @@ -567,17 +567,17 @@ invoke    css  create      app/assets/stylesheets/blorgh/comments.css  ``` -The form will be making a `POST` request to `/posts/:post_id/comments`, which +The form will be making a `POST` request to `/articles/:article_id/comments`, which  will correspond with the `create` action in `Blorgh::CommentsController`. This  action needs to be created, which can be done by putting the following lines  inside the class definition in `app/controllers/blorgh/comments_controller.rb`:  ```ruby  def create -  @post = Post.find(params[:post_id]) -  @comment = @post.comments.create(comment_params) +  @article = Article.find(params[:article_id]) +  @comment = @article.comments.create(comment_params)    flash[:notice] = "Comment has been created!" -  redirect_to posts_path +  redirect_to articles_path  end  private @@ -590,11 +590,11 @@ This is the final step required to get the new comment form working. Displaying  the comments, however, is not quite right yet. If you were to create a comment  right now, you would see this error: -```  +```  Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder],  :formats=>[:html], :locale=>[:en, :en]}. Searched in:   *  "/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views"   * -"/Users/ryan/Sites/side_projects/blorgh/app/views"  +"/Users/ryan/Sites/side_projects/blorgh/app/views"  ```  The engine is unable to find the partial required for rendering the comments. @@ -612,7 +612,7 @@ line inside it:  ```  The `comment_counter` local variable is given to us by the `<%= render -@post.comments %>` call, which will define it automatically and increment the +@article.comments %>` call, which will define it automatically and increment the  counter as it iterates through each comment. It's used in this example to  display a small number next to each comment when it's created. @@ -625,7 +625,7 @@ Hooking Into an Application  Using an engine within an application is very easy. This section covers how to  mount the engine into an application and the initial setup required, as well as  linking the engine to a `User` class provided by the application to provide -ownership for posts and comments within the engine. +ownership for articles and comments within the engine.  ### Mounting the Engine @@ -676,20 +676,20 @@ pre-defined path which may be customizable.  ### Engine setup -The engine contains migrations for the `blorgh_posts` and `blorgh_comments` +The engine contains migrations for the `blorgh_articles` and `blorgh_comments`  table which need to be created in the application's database so that the  engine's models can query them correctly. To copy these migrations into the  application use this command:  ```bash -$ rake blorgh:install:migrations +$ bin/rake blorgh:install:migrations  ```  If you have multiple engines that need migrations copied over, use  `railties:install:migrations` instead:  ```bash -$ rake railties:install:migrations +$ bin/rake railties:install:migrations  ```  This command, when run for the first time, will copy over all the migrations @@ -698,7 +698,7 @@ haven't been copied over already. The first run for this command will output  something such as this:  ```bash -Copied migration [timestamp_1]_create_blorgh_posts.rb from blorgh +Copied migration [timestamp_1]_create_blorgh_articles.rb from blorgh  Copied migration [timestamp_2]_create_blorgh_comments.rb from blorgh  ``` @@ -709,7 +709,7 @@ migrations in the application.  To run these migrations within the context of the application, simply run `rake  db:migrate`. When accessing the engine through `http://localhost:3000/blog`, the -posts will be empty. This is because the table created inside the application is +articles will be empty. This is because the table created inside the application is  different from the one created within the engine. Go ahead, play around with the  newly mounted engine. You'll find that it's the same as when it was only an  engine. @@ -734,11 +734,11 @@ rake db:migrate SCOPE=blorgh VERSION=0  When an engine is created, it may want to use specific classes from an  application to provide links between the pieces of the engine and the pieces of -the application. In the case of the `blorgh` engine, making posts and comments +the application. In the case of the `blorgh` engine, making articles and comments  have authors would make a lot of sense.  A typical application might have a `User` class that would be used to represent -authors for a post or a comment. But there could be a case where the application +authors for a article or a comment. But there could be a case where the application  calls this class something different, such as `Person`. For this reason, the  engine should not hardcode associations specifically for a `User` class. @@ -753,14 +753,14 @@ rails g model user name:string  The `rake db:migrate` command needs to be run here to ensure that our  application has the `users` table for future use. -Also, to keep it simple, the posts form will have a new text field called +Also, to keep it simple, the articles form will have a new text field called  `author_name`, where users can elect to put their name. The engine will then  take this name and either create a new `User` object from it, or find one that -already has that name. The engine will then associate the post with the found or +already has that name. The engine will then associate the article with the found or  created `User` object.  First, the `author_name` text field needs to be added to the -`app/views/blorgh/posts/_form.html.erb` partial inside the engine. This can be +`app/views/blorgh/articles/_form.html.erb` partial inside the engine. This can be  added above the `title` field with this code:  ```html+erb @@ -770,23 +770,23 @@ added above the `title` field with this code:  </div>  ``` -Next, we need to update our `Blorgh::PostController#post_params` method to +Next, we need to update our `Blorgh::ArticleController#article_params` method to  permit the new form parameter:  ```ruby -def post_params -  params.require(:post).permit(:title, :text, :author_name) +def article_params +  params.require(:article).permit(:title, :text, :author_name)  end  ``` -The `Blorgh::Post` model should then have some code to convert the `author_name` -field into an actual `User` object and associate it as that post's `author` -before the post is saved. It will also need to have an `attr_accessor` set up +The `Blorgh::Article` model should then have some code to convert the `author_name` +field into an actual `User` object and associate it as that article's `author` +before the article is saved. It will also need to have an `attr_accessor` set up  for this field, so that the setter and getter methods are defined for it.  To do all this, you'll need to add the `attr_accessor` for `author_name`, the  association for the author and the `before_save` call into -`app/models/blorgh/post.rb`. The `author` association will be hard-coded to the +`app/models/blorgh/article.rb`. The `author` association will be hard-coded to the  `User` class for the time being.  ```ruby @@ -803,14 +803,14 @@ private  By representing the `author` association's object with the `User` class, a link  is established between the engine and the application. There needs to be a way -of associating the records in the `blorgh_posts` table with the records in the +of associating the records in the `blorgh_articles` table with the records in the  `users` table. Because the association is called `author`, there should be an -`author_id` column added to the `blorgh_posts` table. +`author_id` column added to the `blorgh_articles` table.  To generate this new column, run this command within the engine:  ```bash -$ rails g migration add_author_id_to_blorgh_posts author_id:integer +$ bin/rails g migration add_author_id_to_blorgh_articles author_id:integer  ```  NOTE: Due to the migration's name and the column specification after it, Rails @@ -822,41 +822,41 @@ This migration will need to be run on the application. To do that, it must first  be copied using this command:  ```bash -$ rake blorgh:install:migrations +$ bin/rake blorgh:install:migrations  ```  Notice that only _one_ migration was copied over here. This is because the first  two migrations were copied over the first time this command was run. -```  -NOTE Migration [timestamp]_create_blorgh_posts.rb from blorgh has been +``` +NOTE Migration [timestamp]_create_blorgh_articles.rb from blorgh has been  skipped. Migration with the same name already exists. NOTE Migration  [timestamp]_create_blorgh_comments.rb from blorgh has been skipped. Migration  with the same name already exists. Copied migration -[timestamp]_add_author_id_to_blorgh_posts.rb from blorgh  +[timestamp]_add_author_id_to_blorgh_articles.rb from blorgh  ```  Run the migration using:  ```bash -$ rake db:migrate +$ bin/rake db:migrate  ```  Now with all the pieces in place, an action will take place that will associate -an author - represented by a record in the `users` table - with a post, -represented by the `blorgh_posts` table from the engine. +an author - represented by a record in the `users` table - with an article, +represented by the `blorgh_articles` table from the engine. -Finally, the author's name should be displayed on the post's page. Add this code -above the "Title" output inside `app/views/blorgh/posts/show.html.erb`: +Finally, the author's name should be displayed on the article's page. Add this code +above the "Title" output inside `app/views/blorgh/articles/show.html.erb`:  ```html+erb  <p>    <b>Author:</b> -  <%= @post.author %> +  <%= @article.author %>  </p>  ``` -By outputting `@post.author` using the `<%=` tag, the `to_s` method will be +By outputting `@article.author` using the `<%=` tag, the `to_s` method will be  called on the object. By default, this will look quite ugly:  ``` @@ -925,15 +925,15 @@ This method works like its brothers, `attr_accessor` and `cattr_accessor`, but  provides a setter and getter method on the module with the specified name. To  use it, it must be referenced using `Blorgh.author_class`. -The next step is to switch the `Blorgh::Post` model over to this new setting. +The next step is to switch the `Blorgh::Article` model over to this new setting.  Change the `belongs_to` association inside this model -(`app/models/blorgh/post.rb`) to this: +(`app/models/blorgh/article.rb`) to this:  ```ruby  belongs_to :author, class_name: Blorgh.author_class  ``` -The `set_author` method in the `Blorgh::Post` model should also use this class: +The `set_author` method in the `Blorgh::Article` model should also use this class:  ```ruby  self.author = Blorgh.author_class.constantize.find_or_create_by(name: author_name) @@ -960,7 +960,7 @@ Resulting in something a little shorter, and more implicit in its behavior. The  `author_class` method should always return a `Class` object.  Since we changed the `author_class` method to return a `Class` instead of a -`String`, we must also modify our `belongs_to` definition in the `Blorgh::Post` +`String`, we must also modify our `belongs_to` definition in the `Blorgh::Article`  model:  ```ruby @@ -985,14 +985,14 @@ to load that class and then reference the related table. This could lead to  problems if the table wasn't already existing. Therefore, a `String` should be  used and then converted to a class using `constantize` in the engine later on. -Go ahead and try to create a new post. You will see that it works exactly in the +Go ahead and try to create a new article. You will see that it works exactly in the  same way as before, except this time the engine is using the configuration  setting in `config/initializers/blorgh.rb` to learn what the class is.  There are now no strict dependencies on what the class is, only what the API for  the class must be. The engine simply requires this class to define a  `find_or_create_by` method which returns an object of that class, to be -associated with a post when it's created. This object, of course, should have +associated with an article when it's created. This object, of course, should have  some sort of identifier by which it can be referenced.  #### General Engine Configuration @@ -1107,12 +1107,12 @@ that isn't referenced by your main application.  #### Implementing Decorator Pattern Using Class#class_eval -**Adding** `Post#time_since_created`: +**Adding** `Article#time_since_created`:  ```ruby -# MyApp/app/decorators/models/blorgh/post_decorator.rb +# MyApp/app/decorators/models/blorgh/article_decorator.rb -Blorgh::Post.class_eval do +Blorgh::Article.class_eval do    def time_since_created      Time.current - created_at    end @@ -1120,20 +1120,20 @@ end  ```  ```ruby -# Blorgh/app/models/post.rb +# Blorgh/app/models/article.rb -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    has_many :comments  end  ``` -**Overriding** `Post#summary`: +**Overriding** `Article#summary`:  ```ruby -# MyApp/app/decorators/models/blorgh/post_decorator.rb +# MyApp/app/decorators/models/blorgh/article_decorator.rb -Blorgh::Post.class_eval do +Blorgh::Article.class_eval do    def summary      "#{title} - #{truncate(text)}"    end @@ -1141,9 +1141,9 @@ end  ```  ```ruby -# Blorgh/app/models/post.rb +# Blorgh/app/models/article.rb -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    has_many :comments    def summary      "#{title}" @@ -1159,13 +1159,13 @@ class modifications, you might want to consider using [`ActiveSupport::Concern`]  ActiveSupport::Concern manages load order of interlinked dependent modules and  classes at run time allowing you to significantly modularize your code. -**Adding** `Post#time_since_created` and **Overriding** `Post#summary`: +**Adding** `Article#time_since_created` and **Overriding** `Article#summary`:  ```ruby -# MyApp/app/models/blorgh/post.rb +# MyApp/app/models/blorgh/article.rb -class Blorgh::Post < ActiveRecord::Base -  include Blorgh::Concerns::Models::Post +class Blorgh::Article < ActiveRecord::Base +  include Blorgh::Concerns::Models::Article    def time_since_created      Time.current - created_at @@ -1178,22 +1178,22 @@ end  ```  ```ruby -# Blorgh/app/models/post.rb +# Blorgh/app/models/article.rb -class Post < ActiveRecord::Base -  include Blorgh::Concerns::Models::Post +class Article < ActiveRecord::Base +  include Blorgh::Concerns::Models::Article  end  ```  ```ruby -# Blorgh/lib/concerns/models/post +# Blorgh/lib/concerns/models/article -module Blorgh::Concerns::Models::Post +module Blorgh::Concerns::Models::Article    extend ActiveSupport::Concern    # 'included do' causes the included code to be evaluated in the -  # context where it is included (post.rb), rather than being -  # executed in the module's context (blorgh/concerns/models/post). +  # context where it is included (article.rb), rather than being +  # executed in the module's context (blorgh/concerns/models/article).    included do      attr_accessor :author_name      belongs_to :author, class_name: "User" @@ -1224,25 +1224,25 @@ When Rails looks for a view to render, it will first look in the `app/views`  directory of the application. If it cannot find the view there, it will check in  the `app/views` directories of all engines that have this directory. -When the application is asked to render the view for `Blorgh::PostsController`'s +When the application is asked to render the view for `Blorgh::ArticlesController`'s  index action, it will first look for the path -`app/views/blorgh/posts/index.html.erb` within the application. If it cannot +`app/views/blorgh/articles/index.html.erb` within the application. If it cannot  find it, it will look inside the engine.  You can override this view in the application by simply creating a new file at -`app/views/blorgh/posts/index.html.erb`. Then you can completely change what +`app/views/blorgh/articles/index.html.erb`. Then you can completely change what  this view would normally output. -Try this now by creating a new file at `app/views/blorgh/posts/index.html.erb` +Try this now by creating a new file at `app/views/blorgh/articles/index.html.erb`  and put this content in it:  ```html+erb -<h1>Posts</h1> -<%= link_to "New Post", new_post_path %> -<% @posts.each do |post| %> -  <h2><%= post.title %></h2> -  <small>By <%= post.author %></small> -  <%= simple_format(post.text) %> +<h1>Articles</h1> +<%= link_to "New Article", new_article_path %> +<% @articles.each do |article| %> +  <h2><%= article.title %></h2> +  <small>By <%= article.author %></small> +  <%= simple_format(article.text) %>    <hr>  <% end %>  ``` @@ -1259,30 +1259,30 @@ Routes inside an engine are drawn on the `Engine` class within  ```ruby  Blorgh::Engine.routes.draw do -  resources :posts +  resources :articles  end  ```  By having isolated routes such as this, if you wish to link to an area of an  engine from within an application, you will need to use the engine's routing -proxy method. Calls to normal routing methods such as `posts_path` may end up +proxy method. Calls to normal routing methods such as `articles_path` may end up  going to undesired locations if both the application and the engine have such a  helper defined. -For instance, the following example would go to the application's `posts_path` -if that template was rendered from the application, or the engine's `posts_path` +For instance, the following example would go to the application's `articles_path` +if that template was rendered from the application, or the engine's `articles_path`  if it was rendered from the engine:  ```erb -<%= link_to "Blog posts", posts_path %> +<%= link_to "Blog articles", articles_path %>  ``` -To make this route always use the engine's `posts_path` routing helper method, +To make this route always use the engine's `articles_path` routing helper method,  we must call the method on the routing proxy method that shares the same name as  the engine.  ```erb -<%= link_to "Blog posts", blorgh.posts_path %> +<%= link_to "Blog articles", blorgh.articles_path %>  ```  If you wish to reference the application inside the engine in a similar way, use diff --git a/guides/source/generators.md b/guides/source/generators.md index a1ba97fd35..25c67de993 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -23,13 +23,13 @@ When you create an application using the `rails` command, you are in fact using  ```bash  $ rails new myapp  $ cd myapp -$ rails generate +$ bin/rails generate  ```  You will get a list of all generators that comes with Rails. If you need a detailed description of the helper generator, for example, you can simply do:  ```bash -$ rails generate helper --help +$ bin/rails generate helper --help  ```  Creating Your First Generator @@ -54,13 +54,13 @@ Our new generator is quite simple: it inherits from `Rails::Generators::Base` an  To invoke our new generator, we just need to do:  ```bash -$ rails generate initializer +$ bin/rails generate initializer  ```  Before we go on, let's see our brand new generator description:  ```bash -$ rails generate initializer --help +$ bin/rails generate initializer --help  ```  Rails is usually able to generate good descriptions if a generator is namespaced, as `ActiveRecord::Generators::ModelGenerator`, but not in this particular case. We can solve this problem in two ways. The first one is calling `desc` inside our generator: @@ -82,7 +82,7 @@ Creating Generators with Generators  Generators themselves have a generator:  ```bash -$ rails generate generator initializer +$ bin/rails generate generator initializer        create  lib/generators/initializer        create  lib/generators/initializer/initializer_generator.rb        create  lib/generators/initializer/USAGE @@ -102,7 +102,7 @@ First, notice that we are inheriting from `Rails::Generators::NamedBase` instead  We can see that by invoking the description of this new generator (don't forget to delete the old generator file):  ```bash -$ rails generate initializer --help +$ bin/rails generate initializer --help  Usage:    rails generate initializer NAME [options]  ``` @@ -130,7 +130,7 @@ end  And let's execute our generator:  ```bash -$ rails generate initializer core_extensions +$ bin/rails generate initializer core_extensions  ```  We can see that now an initializer named core_extensions was created at `config/initializers/core_extensions.rb` with the contents of our template. That means that `copy_file` copied a file in our source root to the destination path we gave. The method `file_name` is automatically created when we inherit from `Rails::Generators::NamedBase`. @@ -169,7 +169,7 @@ end  Before we customize our workflow, let's first see what our scaffold looks like:  ```bash -$ rails generate scaffold User name:string +$ bin/rails generate scaffold User name:string        invoke  active_record        create    db/migrate/20130924151154_create_users.rb        create    app/models/user.rb @@ -224,7 +224,7 @@ If we generate another resource with the scaffold generator, we can see that sty  To demonstrate this, we are going to create a new helper generator that simply adds some instance variable readers. First, we create a generator within the rails namespace, as this is where rails searches for generators used as hooks:  ```bash -$ rails generate generator rails/my_helper +$ bin/rails generate generator rails/my_helper        create  lib/generators/rails/my_helper        create  lib/generators/rails/my_helper/my_helper_generator.rb        create  lib/generators/rails/my_helper/USAGE @@ -251,7 +251,7 @@ end  We can try out our new generator by creating a helper for products:  ```bash -$ rails generate my_helper products +$ bin/rails generate my_helper products        create  app/helpers/products_helper.rb  ``` @@ -279,10 +279,10 @@ end  and see it in action when invoking the generator:  ```bash -$ rails generate scaffold Post body:text +$ bin/rails generate scaffold Article body:text        [...]        invoke    my_helper -      create      app/helpers/posts_helper.rb +      create      app/helpers/articles_helper.rb  ```  We can notice on the output that our new helper was invoked instead of the Rails default. However one thing is missing, which is tests for our new generator and to do that, we are going to reuse old helpers test generators. @@ -365,7 +365,7 @@ end  Now, if you create a Comment scaffold, you will see that the shoulda generators are being invoked, and at the end, they are just falling back to TestUnit generators:  ```bash -$ rails generate scaffold Comment body:text +$ bin/rails generate scaffold Comment body:text        invoke  active_record        create    db/migrate/20130924143118_create_comments.rb        create    app/models/comment.rb diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 389ffdac6e..530232f3f3 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -89,7 +89,7 @@ Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose  dollar sign `$` should be run in the command line. Verify that you have a  current version of Ruby installed: -TIP. A number of tools exist to help you quickly install Ruby and Ruby +TIP: A number of tools exist to help you quickly install Ruby and Ruby  on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org),  while Mac OS X users can use [Tokaido](https://github.com/tokaido/tokaidoapp). @@ -122,10 +122,10 @@ To verify that you have everything installed correctly, you should be able to  run the following:  ```bash -$ rails --version +$ bin/rails --version  ``` -If it says something like "Rails 4.1.0", you are ready to continue. +If it says something like "Rails 4.1.1", you are ready to continue.  ### Creating the Blog Application @@ -190,7 +190,7 @@ start a web server on your development machine. You can do this by running the  following in the `blog` directory:  ```bash -$ rails server +$ bin/rails server  ```  TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the @@ -243,7 +243,7 @@ tell it you want a controller called "welcome" with an action called "index",  just like this:  ```bash -$ rails generate controller welcome index +$ bin/rails generate controller welcome index  ```  Rails will create several files and a route for you. @@ -359,7 +359,7 @@ will be seen later, but for now notice that Rails has inferred the  singular form `article` and makes meaningful use of the distinction.  ```bash -$ rake routes +$ bin/rake routes        Prefix Verb   URI Pattern                  Controller#Action      articles GET    /articles(.:format)          articles#index               POST   /articles(.:format)          articles#create @@ -397,7 +397,7 @@ a controller called `ArticlesController`. You can do this by running this  command:  ```bash -$ rails g controller articles +$ bin/rails g controller articles  ```  If you open up the newly generated `app/controllers/articles_controller.rb` @@ -556,7 +556,7 @@ To see what Rails will do with this, we look back at the output of  `rake routes`:  ```bash -$ rake routes +$ bin/rake routes        Prefix Verb   URI Pattern                  Controller#Action      articles GET    /articles(.:format)          articles#index               POST   /articles(.:format)          articles#create @@ -642,7 +642,7 @@ Rails developers tend to use when creating new models. To create the new model,  run this command in your terminal:  ```bash -$ rails generate model Article title:string text:text +$ bin/rails generate model Article title:string text:text  ```  With that command we told Rails that we want a `Article` model, together @@ -697,7 +697,7 @@ TIP: For more information about migrations, refer to [Rails Database Migrations]  At this point, you can use a rake command to run the migration:  ```bash -$ rake db:migrate +$ bin/rake db:migrate  ```  Rails will execute this migration command and tell you it created the Articles @@ -1502,7 +1502,7 @@ the `Article` model. This time we'll create a `Comment` model to hold  reference of article comments. Run this command in your terminal:  ```bash -$ rails generate model Comment commenter:string body:text article:references +$ bin/rails generate model Comment commenter:string body:text article:references  ```  This command will generate four files: @@ -1550,7 +1550,7 @@ the two models. An index for this association is also created on this column.  Go ahead and run the migration:  ```bash -$ rake db:migrate +$ bin/rake db:migrate  ```  Rails is smart enough to only execute the migrations that have not already been @@ -1626,7 +1626,7 @@ With the model in hand, you can turn your attention to creating a matching  controller. Again, we'll use the same generator we used before:  ```bash -$ rails generate controller Comments +$ bin/rails generate controller Comments  ```  This creates six files and one empty directory: diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index 1d375f806c..1005057ca9 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -35,7 +35,6 @@          <li class="more-info"><a href="https://github.com/rails/rails">Code</a></li>          <li class="more-info"><a href="http://rubyonrails.org/screencasts">Screencasts</a></li>          <li class="more-info"><a href="http://rubyonrails.org/documentation">Documentation</a></li> -        <li class="more-info"><a href="http://rubyonrails.org/ecosystem">Ecosystem</a></li>          <li class="more-info"><a href="http://rubyonrails.org/community">Community</a></li>          <li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>        </ul> diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index bd33c5a146..5b75540c05 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -506,33 +506,33 @@ Layout declarations cascade downward in the hierarchy, and more specific layout      end      ``` -* `posts_controller.rb` +* `articles_controller.rb`      ```ruby -    class PostsController < ApplicationController +    class ArticlesController < ApplicationController      end      ``` -* `special_posts_controller.rb` +* `special_articles_controller.rb`      ```ruby -    class SpecialPostsController < PostsController +    class SpecialArticlesController < ArticlesController        layout "special"      end      ``` -* `old_posts_controller.rb` +* `old_articles_controller.rb`      ```ruby -    class OldPostsController < SpecialPostsController +    class OldArticlesController < SpecialArticlesController        layout false        def show -        @post = Post.find(params[:id]) +        @article = Article.find(params[:id])        end        def index -        @old_posts = Post.older +        @old_articles = Article.older          render layout: "old"        end        # ... @@ -542,10 +542,10 @@ Layout declarations cascade downward in the hierarchy, and more specific layout  In this application:  * In general, views will be rendered in the `main` layout -* `PostsController#index` will use the `main` layout -* `SpecialPostsController#index` will use the `special` layout -* `OldPostsController#show` will use no layout at all -* `OldPostsController#index` will use the `old` layout +* `ArticlesController#index` will use the `main` layout +* `SpecialArticlesController#index` will use the `special` layout +* `OldArticlesController#show` will use no layout at all +* `OldArticlesController#index` will use the `old` layout  #### Avoiding Double Render Errors diff --git a/guides/source/migrations.md b/guides/source/migrations.md index fe1a6a4697..6742c05946 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -121,7 +121,7 @@ Of course, calculating timestamps is no fun, so Active Record provides a  generator to handle making it for you:  ```bash -$ rails generate migration AddPartNumberToProducts +$ bin/rails generate migration AddPartNumberToProducts  ```  This will create an empty but appropriately named migration: @@ -138,7 +138,7 @@ followed by a list of column names and types then a migration containing the  appropriate `add_column` and `remove_column` statements will be created.  ```bash -$ rails generate migration AddPartNumberToProducts part_number:string +$ bin/rails generate migration AddPartNumberToProducts part_number:string  ```  will generate @@ -154,7 +154,7 @@ end  If you'd like to add an index on the new column, you can do that as well:  ```bash -$ rails generate migration AddPartNumberToProducts part_number:string:index +$ bin/rails generate migration AddPartNumberToProducts part_number:string:index  ```  will generate @@ -172,7 +172,7 @@ end  Similarly, you can generate a migration to remove a column from the command line:  ```bash -$ rails generate migration RemovePartNumberFromProducts part_number:string +$ bin/rails generate migration RemovePartNumberFromProducts part_number:string  ```  generates @@ -188,7 +188,7 @@ end  You are not limited to one magically generated column. For example:  ```bash -$ rails generate migration AddDetailsToProducts part_number:string price:decimal +$ bin/rails generate migration AddDetailsToProducts part_number:string price:decimal  ```  generates @@ -207,7 +207,7 @@ followed by a list of column names and types then a migration creating the table  XXX with the columns listed will be generated. For example:  ```bash -$ rails generate migration CreateProducts name:string part_number:string +$ bin/rails generate migration CreateProducts name:string part_number:string  ```  generates @@ -231,7 +231,7 @@ Also, the generator accepts column type as `references`(also available as  `belongs_to`). For instance:  ```bash -$ rails generate migration AddUserRefToProducts user:references +$ bin/rails generate migration AddUserRefToProducts user:references  ```  generates @@ -249,7 +249,7 @@ This migration will create a `user_id` column and appropriate index.  There is also a generator which will produce join tables if `JoinTable` is part of the name:  ```bash -rails g migration CreateJoinTableCustomerProduct customer product +$ bin/rails g migration CreateJoinTableCustomerProduct customer product  ```  will produce the following migration: @@ -273,7 +273,7 @@ relevant table. If you tell Rails what columns you want, then statements for  adding these columns will also be created. For example, running:  ```bash -$ rails generate model Product name:string description:text +$ bin/rails generate model Product name:string description:text  ```  will create a migration that looks like this @@ -307,7 +307,7 @@ braces. You can use the following modifiers:  For instance, running:  ```bash -$ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic} +$ bin/rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}  ```  will produce a migration that looks like this @@ -653,7 +653,7 @@ is the numerical prefix on the migration's filename. For example, to migrate  to version 20080906120000 run:  ```bash -$ rake db:migrate VERSION=20080906120000 +$ bin/rake db:migrate VERSION=20080906120000  ```  If version 20080906120000 is greater than the current version (i.e., it is @@ -670,7 +670,7 @@ mistake in it and wish to correct it. Rather than tracking down the version  number associated with the previous migration you can run:  ```bash -$ rake db:rollback +$ bin/rake db:rollback  ```  This will rollback the latest migration, either by reverting the `change` @@ -678,7 +678,7 @@ method or by running the `down` method. If you need to undo  several migrations you can provide a `STEP` parameter:  ```bash -$ rake db:rollback STEP=3 +$ bin/rake db:rollback STEP=3  ```  will revert the last 3 migrations. @@ -688,7 +688,7 @@ back up again. As with the `db:rollback` task, you can use the `STEP` parameter  if you need to go more than one version back, for example:  ```bash -$ rake db:migrate:redo STEP=3 +$ bin/rake db:migrate:redo STEP=3  ```  Neither of these Rake tasks do anything you could not do with `db:migrate`. They @@ -718,7 +718,7 @@ the corresponding migration will have its `change`, `up` or `down` method  invoked, for example:  ```bash -$ rake db:migrate:up VERSION=20080906120000 +$ bin/rake db:migrate:up VERSION=20080906120000  ```  will run the 20080906120000 migration by running the `change` method (or the @@ -734,7 +734,7 @@ To run migrations against another environment you can specify it using the  migrations against the `test` environment you could run:  ```bash -$ rake db:migrate RAILS_ENV=test +$ bin/rake db:migrate RAILS_ENV=test  ```  ### Changing the Output of Running Migrations diff --git a/guides/source/plugins.md b/guides/source/plugins.md index fe4215839f..a35648d341 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -39,13 +39,13 @@ to run integration tests using a dummy Rails application. Create your  plugin with the command:  ```bash -$ rails plugin new yaffle +$ bin/rails plugin new yaffle  ```  See usage and options by asking for help:  ```bash -$ rails plugin --help +$ bin/rails plugin --help  ```  Testing Your Newly Generated Plugin @@ -124,7 +124,7 @@ To test that your method does what it says it does, run the unit tests with `rak  To see this in action, change to the test/dummy directory, fire up a console and start squawking:  ```bash -$ rails console +$ bin/rails console  >> "Hello World".to_squawk  => "squawk! Hello World"  ``` @@ -214,8 +214,8 @@ test/dummy directory:  ```bash  $ cd test/dummy -$ rails generate model Hickwall last_squawk:string -$ rails generate model Wickwall last_squawk:string last_tweet:string +$ bin/rails generate model Hickwall last_squawk:string +$ bin/rails generate model Wickwall last_squawk:string last_tweet:string  ```  Now you can create the necessary database tables in your testing database by navigating to your dummy app @@ -223,7 +223,7 @@ and migrating the database. First, run:  ```bash  $ cd test/dummy -$ rake db:migrate +$ bin/rake db:migrate  ```  While you are here, change the Hickwall and Wickwall models so that they know that they are supposed to act @@ -433,7 +433,7 @@ Once your README is solid, go through and add rdoc comments to all of the method  Once your comments are good to go, navigate to your plugin directory and run:  ```bash -$ rake rdoc +$ bin/rake rdoc  ```  ### References diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index e4222e1283..0bd608c007 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -23,8 +23,8 @@ $ rails new blog -m http://example.com/template.rb  You can use the rake task `rails:template` to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL.  ```bash -$ rake rails:template LOCATION=~/template.rb -$ rake rails:template LOCATION=http://example.com/template.rb +$ bin/rake rails:template LOCATION=~/template.rb +$ bin/rake rails:template LOCATION=http://example.com/template.rb  ```  Template API diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index b1b4c8fa4e..9053f31b8e 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -111,7 +111,7 @@ NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`,  Rails has a handy rake task for inspecting the middleware stack in use:  ```bash -$ rake middleware +$ bin/rake middleware  ```  For a freshly generated Rails application, this might produce something like: @@ -194,7 +194,7 @@ And now if you inspect the middleware stack, you'll find that `Rack::Lock` is  not a part of it.  ```bash -$ rake middleware +$ bin/rake middleware  (in /Users/lifo/Rails/blog)  use ActionDispatch::Static  use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8> diff --git a/guides/source/routing.md b/guides/source/routing.md index 9dab946c72..0ff13cb07d 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -183,61 +183,61 @@ You may wish to organize groups of controllers under a namespace. Most commonly,  ```ruby  namespace :admin do -  resources :posts, :comments +  resources :articles, :comments  end  ``` -This will create a number of routes for each of the `posts` and `comments` controller. For `Admin::PostsController`, Rails will create: +This will create a number of routes for each of the `articles` and `comments` controller. For `Admin::ArticlesController`, Rails will create: -| HTTP Verb | Path                  | Controller#Action   | Named Helper              | -| --------- | --------------------- | ------------------- | ------------------------- | -| GET       | /admin/posts          | admin/posts#index   | admin_posts_path          | -| GET       | /admin/posts/new      | admin/posts#new     | new_admin_post_path       | -| POST      | /admin/posts          | admin/posts#create  | admin_posts_path          | -| GET       | /admin/posts/:id      | admin/posts#show    | admin_post_path(:id)      | -| GET       | /admin/posts/:id/edit | admin/posts#edit    | edit_admin_post_path(:id) | -| PATCH/PUT | /admin/posts/:id      | admin/posts#update  | admin_post_path(:id)      | -| DELETE    | /admin/posts/:id      | admin/posts#destroy | admin_post_path(:id)      | +| HTTP Verb | Path                     | Controller#Action      | Named Helper              | +| --------- | ------------------------ | ---------------------- | ---------------------------- | +| GET       | /admin/articles          | admin/articles#index   | admin_articles_path          | +| GET       | /admin/articles/new      | admin/articles#new     | new_admin_article_path       | +| POST      | /admin/articles          | admin/articles#create  | admin_articles_path          | +| GET       | /admin/articles/:id      | admin/articles#show    | admin_article_path(:id)      | +| GET       | /admin/articles/:id/edit | admin/articles#edit    | edit_admin_article_path(:id) | +| PATCH/PUT | /admin/articles/:id      | admin/articles#update  | admin_article_path(:id)      | +| DELETE    | /admin/articles/:id      | admin/articles#destroy | admin_article_path(:id)      | -If you want to route `/posts` (without the prefix `/admin`) to `Admin::PostsController`, you could use: +If you want to route `/articles` (without the prefix `/admin`) to `Admin::ArticlesController`, you could use:  ```ruby  scope module: 'admin' do -  resources :posts, :comments +  resources :articles, :comments  end  ```  or, for a single case:  ```ruby -resources :posts, module: 'admin' +resources :articles, module: 'admin'  ``` -If you want to route `/admin/posts` to `PostsController` (without the `Admin::` module prefix), you could use: +If you want to route `/admin/articles` to `ArticlesController` (without the `Admin::` module prefix), you could use:  ```ruby  scope '/admin' do -  resources :posts, :comments +  resources :articles, :comments  end  ```  or, for a single case:  ```ruby -resources :posts, path: '/admin/posts' +resources :articles, path: '/admin/articles'  ```  In each of these cases, the named routes remain the same as if you did not use `scope`. In the last case, the following paths map to `PostsController`: -| HTTP Verb | Path                  | Controller#Action | Named Helper        | -| --------- | --------------------- | ----------------- | ------------------- | -| GET       | /admin/posts          | posts#index       | posts_path          | -| GET       | /admin/posts/new      | posts#new         | new_post_path       | -| POST      | /admin/posts          | posts#create      | posts_path          | -| GET       | /admin/posts/:id      | posts#show        | post_path(:id)      | -| GET       | /admin/posts/:id/edit | posts#edit        | edit_post_path(:id) | -| PATCH/PUT | /admin/posts/:id      | posts#update      | post_path(:id)      | -| DELETE    | /admin/posts/:id      | posts#destroy     | post_path(:id)      | +| HTTP Verb | Path                     | Controller#Action    | Named Helper           | +| --------- | ------------------------ | -------------------- | ---------------------- | +| GET       | /admin/articles          | articles#index       | articles_path          | +| GET       | /admin/articles/new      | articles#new         | new_article_path       | +| POST      | /admin/articles          | articles#create      | articles_path          | +| GET       | /admin/articles/:id      | articles#show        | article_path(:id)      | +| GET       | /admin/articles/:id/edit | articles#edit        | edit_article_path(:id) | +| PATCH/PUT | /admin/articles/:id      | articles#update      | article_path(:id)      | +| DELETE    | /admin/articles/:id      | articles#destroy     | article_path(:id)      |  TIP: _If you need to use a different controller namespace inside a `namespace` block you can specify an absolute controller path, e.g: `get '/foo' => '/foo#index'`._ @@ -304,7 +304,7 @@ TIP: _Resources should never be nested more than 1 level deep._  One way to avoid deep nesting (as recommended above) is to generate the collection actions scoped under the parent, so as to get a sense of the hierarchy, but to not nest the member actions. In other words, to only build routes with the minimal amount of information to uniquely identify the resource, like this:  ```ruby -resources :posts do +resources :articles do    resources :comments, only: [:index, :new, :create]  end  resources :comments, only: [:show, :edit, :update, :destroy] @@ -313,7 +313,7 @@ resources :comments, only: [:show, :edit, :update, :destroy]  This idea strikes a balance between descriptive routes and deep nesting. There exists shorthand syntax to achieve just that, via the `:shallow` option:  ```ruby -resources :posts do +resources :articles do    resources :comments, shallow: true  end  ``` @@ -321,7 +321,7 @@ end  This will generate the exact same routes as the first example. You can also specify the `:shallow` option in the parent resource, in which case all of the nested resources will be shallow:  ```ruby -resources :posts, shallow: true do +resources :articles, shallow: true do    resources :comments    resources :quotes    resources :drafts @@ -332,7 +332,7 @@ The `shallow` method of the DSL creates a scope inside of which every nesting is  ```ruby  shallow do -  resources :posts do +  resources :articles do      resources :comments      resources :quotes      resources :drafts @@ -344,7 +344,7 @@ There exist two options for `scope` to customize shallow routes. `:shallow_path`  ```ruby  scope shallow_path: "sekret" do -  resources :posts do +  resources :articles do      resources :comments, shallow: true    end  end @@ -352,21 +352,21 @@ end  The comments resource here will have the following routes generated for it: -| HTTP Verb | Path                                   | Controller#Action | Named Helper          | -| --------- | -------------------------------------- | ----------------- | --------------------- | -| GET       | /posts/:post_id/comments(.:format)     | comments#index    | post_comments_path    | -| POST      | /posts/:post_id/comments(.:format)     | comments#create   | post_comments_path    | -| GET       | /posts/:post_id/comments/new(.:format) | comments#new      | new_post_comment_path | -| GET       | /sekret/comments/:id/edit(.:format)    | comments#edit     | edit_comment_path     | -| GET       | /sekret/comments/:id(.:format)         | comments#show     | comment_path          | -| PATCH/PUT | /sekret/comments/:id(.:format)         | comments#update   | comment_path          | -| DELETE    | /sekret/comments/:id(.:format)         | comments#destroy  | comment_path          | +| HTTP Verb | Path                                         | Controller#Action | Named Helper             | +| --------- | -------------------------------------------- | ----------------- | ------------------------ | +| GET       | /articles/:article_id/comments(.:format)     | comments#index    | article_comments_path    | +| POST      | /articles/:article_id/comments(.:format)     | comments#create   | article_comments_path    | +| GET       | /articles/:article_id/comments/new(.:format) | comments#new      | new_article_comment_path | +| GET       | /sekret/comments/:id/edit(.:format)          | comments#edit     | edit_comment_path        | +| GET       | /sekret/comments/:id(.:format)               | comments#show     | comment_path             | +| PATCH/PUT | /sekret/comments/:id(.:format)               | comments#update   | comment_path             | +| DELETE    | /sekret/comments/:id(.:format)               | comments#destroy  | comment_path             |  The `:shallow_prefix` option adds the specified parameter to the named helpers:  ```ruby  scope shallow_prefix: "sekret" do -  resources :posts do +  resources :articles do      resources :comments, shallow: true    end  end @@ -374,15 +374,15 @@ end  The comments resource here will have the following routes generated for it: -| HTTP Verb | Path                                   | Controller#Action | Named Helper             | -| --------- | -------------------------------------- | ----------------- | ------------------------ | -| GET       | /posts/:post_id/comments(.:format)     | comments#index    | post_comments_path       | -| POST      | /posts/:post_id/comments(.:format)     | comments#create   | post_comments_path       | -| GET       | /posts/:post_id/comments/new(.:format) | comments#new      | new_post_comment_path    | -| GET       | /comments/:id/edit(.:format)           | comments#edit     | edit_sekret_comment_path | -| GET       | /comments/:id(.:format)                | comments#show     | sekret_comment_path      | -| PATCH/PUT | /comments/:id(.:format)                | comments#update   | sekret_comment_path      | -| DELETE    | /comments/:id(.:format)                | comments#destroy  | sekret_comment_path      | +| HTTP Verb | Path                                         | Controller#Action | Named Helper                | +| --------- | -------------------------------------------- | ----------------- | --------------------------- | +| GET       | /articles/:article_id/comments(.:format)     | comments#index    | article_comments_path       | +| POST      | /articles/:article_id/comments(.:format)     | comments#create   | article_comments_path       | +| GET       | /articles/:article_id/comments/new(.:format) | comments#new      | new_article_comment_path    | +| GET       | /comments/:id/edit(.:format)                 | comments#edit     | edit_sekret_comment_path    | +| GET       | /comments/:id(.:format)                      | comments#show     | sekret_comment_path         | +| PATCH/PUT | /comments/:id(.:format)                      | comments#update   | sekret_comment_path         | +| DELETE    | /comments/:id(.:format)                      | comments#destroy  | sekret_comment_path         |  ### Routing concerns @@ -403,7 +403,7 @@ These concerns can be used in resources to avoid code duplication and share beha  ```ruby  resources :messages, concerns: :commentable -resources :posts, concerns: [:commentable, :image_attachable] +resources :articles, concerns: [:commentable, :image_attachable]  ```  The above is equivalent to: @@ -413,7 +413,7 @@ resources :messages do    resources :comments  end -resources :posts do +resources :articles do    resources :comments    resources :images, only: :index  end @@ -422,7 +422,7 @@ end  Also you can use them in any place that you want inside the routes, for example in a scope or namespace call:  ```ruby -namespace :posts do +namespace :articles do    concerns :commentable  end  ``` @@ -662,15 +662,15 @@ get 'photos/:id', to: 'photos#show', id: /[A-Z]\d{5}/  `:constraints` takes regular expressions with the restriction that regexp anchors can't be used. For example, the following route will not work:  ```ruby -get '/:id', to: 'posts#show', constraints: { id: /^\d/ } +get '/:id', to: 'articles#show', constraints: { id: /^\d/ }  ```  However, note that you don't need to use anchors because all routes are anchored at the start. -For example, the following routes would allow for `posts` with `to_param` values like `1-hello-world` that always begin with a number and `users` with `to_param` values like `david` that never begin with a number to share the root namespace: +For example, the following routes would allow for `articles` with `to_param` values like `1-hello-world` that always begin with a number and `users` with `to_param` values like `david` that never begin with a number to share the root namespace:  ```ruby -get '/:id', to: 'posts#show', constraints: { id: /\d.+/ } +get '/:id', to: 'articles#show', constraints: { id: /\d.+/ }  get '/:username', to: 'users#show'  ``` @@ -771,20 +771,20 @@ get '*pages', to: 'pages#show', format: true  You can redirect any path to another path using the `redirect` helper in your router:  ```ruby -get '/stories', to: redirect('/posts') +get '/stories', to: redirect('/articles')  ```  You can also reuse dynamic segments from the match in the path to redirect to:  ```ruby -get '/stories/:name', to: redirect('/posts/%{name}') +get '/stories/:name', to: redirect('/articles/%{name}')  ```  You can also provide a block to redirect, which receives the symbolized path parameters and the request object:  ```ruby -get '/stories/:name', to: redirect { |path_params, req| "/posts/#{path_params[:name].pluralize}" } -get '/stories', to: redirect { |path_params, req| "/posts/#{req.subdomain}" } +get '/stories/:name', to: redirect { |path_params, req| "/articles/#{path_params[:name].pluralize}" } +get '/stories', to: redirect { |path_params, req| "/articles/#{req.subdomain}" }  ```  Please note that this redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible. @@ -793,7 +793,7 @@ In all of these cases, if you don't provide the leading host (`http://www.exampl  ### Routing to Rack Applications -Instead of a String like `'posts#index'`, which corresponds to the `index` action in the `PostsController`, you can specify any [Rack application](rails_on_rack.html) as the endpoint for a matcher: +Instead of a String like `'articles#index'`, which corresponds to the `index` action in the `ArticlesController`, you can specify any [Rack application](rails_on_rack.html) as the endpoint for a matcher:  ```ruby  match '/application.js', to: Sprockets, via: :all @@ -801,7 +801,7 @@ match '/application.js', to: Sprockets, via: :all  As long as `Sprockets` responds to `call` and returns a `[status, headers, body]`, the router won't know the difference between the Rack application and an action. This is an appropriate use of `via: :all`, as you will want to allow your Rack application to handle all verbs as it considers appropriate. -NOTE: For the curious, `'posts#index'` actually expands out to `PostsController.action(:index)`, which returns a valid Rack application. +NOTE: For the curious, `'articles#index'` actually expands out to `ArticlesController.action(:index)`, which returns a valid Rack application.  ### Using `root` @@ -837,7 +837,7 @@ get 'こんにちは', to: 'welcome#index'  Customizing Resourceful Routes  ------------------------------ -While the default routes and helpers generated by `resources :posts` will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers. +While the default routes and helpers generated by `resources :articles` will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers.  ### Specifying a Controller to Use @@ -974,11 +974,11 @@ You can prefix routes with a named parameter also:  ```ruby  scope ':username' do -  resources :posts +  resources :articles  end  ``` -This will provide you with URLs such as `/bob/posts/1` and will allow you to reference the `username` part of the path as `params[:username]` in controllers, helpers and views. +This will provide you with URLs such as `/bob/articles/1` and will allow you to reference the `username` part of the path as `params[:username]` in controllers, helpers and views.  ### Restricting the Routes Created @@ -1072,7 +1072,7 @@ edit_user GET    /users/:id/edit(.:format) users#edit  You may restrict the listing to the routes that map to a particular controller setting the `CONTROLLER` environment variable:  ```bash -$ CONTROLLER=users rake routes +$ CONTROLLER=users bin/rake routes  ```  TIP: You'll find that the output from `rake routes` is much more readable if you widen your terminal window until the output lines don't wrap. diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md index 8faf03e58c..f0230b428b 100644 --- a/guides/source/ruby_on_rails_guides_guidelines.md +++ b/guides/source/ruby_on_rails_guides_guidelines.md @@ -13,7 +13,7 @@ After reading this guide, you will know:  Markdown  ------- -Guides are written in [GitHub Flavored Markdown](http://github.github.com/github-flavored-markdown/). There is comprehensive [documentation for Markdown](http://daringfireball.net/projects/markdown/syntax), a [cheatsheet](http://daringfireball.net/projects/markdown/basics), and [additional documentation](http://github.github.com/github-flavored-markdown/) on the differences from traditional Markdown. +Guides are written in [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown). There is comprehensive [documentation for Markdown](http://daringfireball.net/projects/markdown/syntax), a [cheatsheet](http://daringfireball.net/projects/markdown/basics).  Prologue  -------- diff --git a/guides/source/security.md b/guides/source/security.md index 0f4d4e712b..75d8c8e4c8 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -17,7 +17,7 @@ After reading this guide, you will know:  Introduction  ------------ -Web application frameworks are made to help developers build web applications. Some of them also help you with securing the web application. In fact one framework is not more secure than another: If you use it correctly, you will be able to build secure apps with many frameworks. Ruby on Rails has some clever helper methods, for example against SQL injection, so that this is hardly a problem. It's nice to see that all of the Rails applications I audited had a good level of security. +Web application frameworks are made to help developers build web applications. Some of them also help you with securing the web application. In fact one framework is not more secure than another: If you use it correctly, you will be able to build secure apps with many frameworks. Ruby on Rails has some clever helper methods, for example against SQL injection, so that this is hardly a problem.  In general there is no such thing as plug-n-play security. Security depends on the people using the framework, and sometimes on the development method. And it depends on all layers of a web application environment: The back-end storage, the web server and the web application itself (and possibly other layers or applications). @@ -25,7 +25,7 @@ The Gartner Group however estimates that 75% of attacks are at the web applicati  The threats against web applications include user account hijacking, bypass of access control, reading or modifying sensitive data, or presenting fraudulent content. Or an attacker might be able to install a Trojan horse program or unsolicited e-mail sending software, aim at financial enrichment or cause brand name damage by modifying company resources. In order to prevent attacks, minimize their impact and remove points of attack, first of all, you have to fully understand the attack methods in order to find the correct countermeasures. That is what this guide aims at. -In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the <a href="#additional-resources">Additional Resources</a> chapter). I do it manually because that's how you find the nasty logical security problems. +In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the <a href="#additional-resources">Additional Resources</a> chapter). It is done manually because that's how you find the nasty logical security problems.  Sessions  -------- @@ -198,7 +198,7 @@ In the <a href="#sessions">session chapter</a> you have learned that most Rails  It is important to notice that the actual crafted image or link doesn't necessarily have to be situated in the web application's domain, it can be anywhere - in a forum, blog post or email. -CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) - less than 0.1% in 2006 - but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in my (and others) security contract work - _CSRF is an important security issue_. +CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) - less than 0.1% in 2006 - but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in many security contract works - _CSRF is an important security issue_.  ### CSRF Countermeasures @@ -374,7 +374,7 @@ For _countermeasures against CSRF in administration interfaces and Intranet appl  The common admin interface works like this: it's located at www.example.com/admin, may be accessed only if the admin flag is set in the User model, re-displays user input and allows the admin to delete/add/edit whatever data desired. Here are some thoughts about this: -* It is very important to _think about the worst case_: What if someone really got hold of my cookie or user credentials. You could _introduce roles_ for the admin interface to limit the possibilities of the attacker. Or how about _special login credentials_ for the admin interface, other than the ones used for the public part of the application. Or a _special password for very serious actions_? +* It is very important to _think about the worst case_: What if someone really got hold of your cookies or user credentials. You could _introduce roles_ for the admin interface to limit the possibilities of the attacker. Or how about _special login credentials_ for the admin interface, other than the ones used for the public part of the application. Or a _special password for very serious actions_?  * Does the admin really have to access the interface from everywhere in the world? Think about _limiting the login to a bunch of source IP addresses_. Examine request.remote_ip to find out about the user's IP address. This is not bullet-proof, but a great barrier. Remember that there might be a proxy in use, though. @@ -406,7 +406,7 @@ If the parameter was nil, the resulting SQL query will be  SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1  ``` -And thus it found the first user in the database, returned it and logged them in. You can find out more about it in [my blog post](http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/). _It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this. +And thus it found the first user in the database, returned it and logged them in. You can find out more about it in [this blog post](http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/). _It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this.  ### Brute-Forcing Accounts @@ -732,7 +732,7 @@ Imagine a blacklist deletes "script" from the user input. Now the attacker injec  strip_tags("some<<b>script>alert('hello')<</b>/script>")  ``` -This returned "some<script>alert('hello')</script>", which makes an attack work. That's why I vote for a whitelist approach, using the updated Rails 2 method sanitize(): +This returned "some<script>alert('hello')</script>", which makes an attack work. That's why a whitelist approach is better, using the updated Rails 2 method sanitize():  ```ruby  tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p) @@ -812,7 +812,7 @@ The [moz-binding](http://www.securiteam.com/securitynews/5LP051FHPE.html) CSS pr  #### Countermeasures -This example, again, showed that a blacklist filter is never complete. However, as custom CSS in web applications is a quite rare feature, I am not aware of a whitelist CSS filter. _If you want to allow custom colors or images, you can allow the user to choose them and build the CSS in the web application_. Use Rails' `sanitize()` method as a model for a whitelist CSS filter, if you really need one. +This example, again, showed that a blacklist filter is never complete. However, as custom CSS in web applications is a quite rare feature, it may be hard to find a good whitelist CSS filter. _If you want to allow custom colors or images, you can allow the user to choose them and build the CSS in the web application_. Use Rails' `sanitize()` method as a model for a whitelist CSS filter, if you really need one.  ### Textile Injection diff --git a/guides/source/testing.md b/guides/source/testing.md index 36d37f3af0..4149146c4c 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -134,27 +134,27 @@ Unit Testing your Models  In Rails, models tests are what you write to test your models. -For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. I will be using examples from this generated code and will be supplementing it with additional examples where necessary. +For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. We will be using examples from this generated code and will be supplementing it with additional examples where necessary.  NOTE: For more information on Rails <i>scaffolding</i>, refer to [Getting Started with Rails](getting_started.html)  When you use `rails generate scaffold`, for a resource among other things it creates a test stub in the `test/models` folder:  ```bash -$ rails generate scaffold post title:string body:text +$ bin/rails generate scaffold article title:string body:text  ... -create  app/models/post.rb -create  test/models/post_test.rb -create  test/fixtures/posts.yml +create  app/models/article.rb +create  test/models/article_test.rb +create  test/fixtures/articles.yml  ...  ``` -The default test stub in `test/models/post_test.rb` looks like this: +The default test stub in `test/models/article_test.rb` looks like this:  ```ruby  require 'test_helper' -class PostTest < ActiveSupport::TestCase +class ArticleTest < ActiveSupport::TestCase    # test "the truth" do    #   assert true    # end @@ -170,10 +170,10 @@ require 'test_helper'  As you know by now, `test_helper.rb` specifies the default configuration to run our tests. This is included with all the tests, so any methods added to this file are available to all your tests.  ```ruby -class PostTest < ActiveSupport::TestCase +class ArticleTest < ActiveSupport::TestCase  ``` -The `PostTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `PostTest` thus has all the methods available from `ActiveSupport::TestCase`. You'll see those methods a little later in this guide. +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. @@ -220,7 +220,7 @@ In order to run your tests, your test database will need to have the current str  Running a test is as simple as invoking the file containing the test cases through `rake test` command.  ```bash -$ rake test test/models/post_test.rb +$ bin/rake test test/models/article_test.rb  .  Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s. @@ -231,7 +231,7 @@ Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.  You can also run a particular test method from the test case by running the test and providing the `test method name`.  ```bash -$ rake test test/models/post_test.rb test_the_truth +$ bin/rake test test/models/article_test.rb test_the_truth  .  Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s. @@ -243,25 +243,25 @@ This will run all test methods from the test case. Note that `test_helper.rb` is  The `.` (dot) above indicates a passing test. When a test fails you see an `F`; when a test throws an error you see an `E` in its place. The last line of the output is the summary. -To see how a test failure is reported, you can add a failing test to the `post_test.rb` test case. +To see how a test failure is reported, you can add a failing test to the `article_test.rb` test case.  ```ruby -test "should not save post without title" do -  post = Post.new -  assert_not post.save +test "should not save article without title" do +  article = Article.new +  assert_not article.save  end  ```  Let us run this newly added test.  ```bash -$ rake test test/models/post_test.rb test_should_not_save_post_without_title +$ bin/rake test test/models/article_test.rb test_should_not_save_article_without_title  F  Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s.    1) Failure: -test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]: +test_should_not_save_article_without_title(ArticleTest) [test/models/article_test.rb:6]:  Failed assertion, no message given.  1 tests, 1 assertions, 1 failures, 0 errors, 0 skips @@ -270,9 +270,9 @@ Failed assertion, no message given.  In the output, `F` denotes a failure. You can see the corresponding trace shown under `1)` along with the name of the failing test. The next few lines contain the stack trace followed by a message which mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable, every assertion provides an optional message parameter, as shown here:  ```ruby -test "should not save post without title" do -  post = Post.new -  assert_not post.save, "Saved the post without a title" +test "should not save article without title" do +  article = Article.new +  assert_not article.save, "Saved the article without a title"  end  ``` @@ -280,14 +280,14 @@ Running this test shows the friendlier assertion message:  ```bash    1) Failure: -test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]: -Saved the post without a title +test_should_not_save_article_without_title(ArticleTest) [test/models/article_test.rb:6]: +Saved the article without a title  ```  Now to get this test to pass we can add a model level validation for the _title_ field.  ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base    validates :title, presence: true  end  ``` @@ -295,7 +295,7 @@ end  Now the test should pass. Let us verify by running the test again:  ```bash -$ rake test test/models/post_test.rb test_should_not_save_post_without_title +$ bin/rake test test/models/article_test.rb test_should_not_save_article_without_title  .  Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s. @@ -320,15 +320,15 @@ end  Now you can see even more output in the console from running the tests:  ```bash -$ rake test test/models/post_test.rb test_should_report_error +$ bin/rake test test/models/article_test.rb test_should_report_error  E  Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s.    1) Error: -test_should_report_error(PostTest): -NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x007fe32e24afe0> -    test/models/post_test.rb:10:in `block in <class:PostTest>' +test_should_report_error(ArticleTest): +NameError: undefined local variable or method `some_undefined_variable' for #<ArticleTest:0x007fe32e24afe0> +    test/models/article_test.rb:10:in `block in <class:ArticleTest>'  1 tests, 0 assertions, 0 failures, 1 errors, 0 skips  ``` @@ -345,7 +345,7 @@ backtrace. simply set the `BACKTRACE` environment variable to enable this  behavior:  ```bash -$ BACKTRACE=1 rake test test/models/post_test.rb +$ BACKTRACE=1 bin/rake test test/models/article_test.rb  ```  ### What to Include in Your Unit Tests @@ -422,26 +422,26 @@ You should test for things such as:  * was the correct object stored in the response template?  * was the appropriate message displayed to the user in the view? -Now that we have used Rails scaffold generator for our `Post` resource, it has already created the controller code and tests. You can take look at the file `posts_controller_test.rb` in the `test/controllers` directory. +Now that we have used Rails scaffold generator for our `Article` resource, it has already created the controller code and tests. You can take look at the file `articles_controller_test.rb` in the `test/controllers` directory. -Let me take you through one such test, `test_should_get_index` from the file `posts_controller_test.rb`. +Let me take you through one such test, `test_should_get_index` from the file `articles_controller_test.rb`.  ```ruby -class PostsControllerTest < ActionController::TestCase +class ArticlesControllerTest < ActionController::TestCase    test "should get index" do      get :index      assert_response :success -    assert_not_nil assigns(:posts) +    assert_not_nil assigns(:articles)    end  end  ``` -In the `test_should_get_index` test, Rails simulates a request on the action called `index`, making sure the request was successful and also ensuring that it assigns a valid `posts` instance variable. +In the `test_should_get_index` test, Rails simulates a request on the action called `index`, making sure the request was successful and also ensuring that it assigns a valid `articles` instance variable.  The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments:  * The action of the controller you are requesting. This can be in the form of a string or a symbol. -* An optional hash of request parameters to pass into the action (eg. query string parameters or post variables). +* An optional hash of request parameters to pass into the action (eg. query string parameters or article variables).  * An optional hash of session variables to pass along with the request.  * An optional hash of flash values. @@ -457,17 +457,17 @@ Another example: Calling the `:view` action, passing an `id` of 12 as the `param  get(:view, {'id' => '12'}, nil, {'message' => 'booya!'})  ``` -NOTE: If you try running `test_should_create_post` test from `posts_controller_test.rb` it will fail on account of the newly added model level validation and rightly so. +NOTE: If you try running `test_should_create_article` test from `articles_controller_test.rb` it will fail on account of the newly added model level validation and rightly so. -Let us modify `test_should_create_post` test in `posts_controller_test.rb` so that all our test pass: +Let us modify `test_should_create_article` test in `articles_controller_test.rb` so that all our test pass:  ```ruby -test "should create post" do -  assert_difference('Post.count') do -    post :create, post: {title: 'Some title'} +test "should create article" do +  assert_difference('Article.count') do +    post :create, article: {title: 'Some title'}    end -  assert_redirected_to post_path(assigns(:post)) +  assert_redirected_to article_path(assigns(:article))  end  ``` @@ -576,12 +576,12 @@ is the correct way to assert for the layout when the view renders a partial with  Here's another example that uses `flash`, `assert_redirected_to`, and `assert_difference`:  ```ruby -test "should create post" do -  assert_difference('Post.count') do -    post :create, post: {title: 'Hi', body: 'This is my first post.'} +test "should create article" do +  assert_difference('article.count') do +    post :create, article: {title: 'Hi', body: 'This is my first article.'}    end -  assert_redirected_to post_path(assigns(:post)) -  assert_equal 'Post was successfully created.', flash[:notice] +  assert_redirected_to article_path(assigns(:article)) +  assert_equal 'Article was successfully created.', flash[:notice]  end  ``` @@ -653,7 +653,7 @@ Integration tests are used to test the interaction among any number of controlle  Unlike Unit and Functional tests, integration tests have to be explicitly created under the 'test/integration' folder within your application. Rails provides a generator to create an integration test skeleton for you.  ```bash -$ rails generate integration_test user_flows +$ bin/rails generate integration_test user_flows        exists  test/integration/        create  test/integration/user_flows_test.rb  ``` @@ -699,8 +699,6 @@ A simple integration test that exercises multiple controllers:  require 'test_helper'  class UserFlowsTest < ActionDispatch::IntegrationTest -  fixtures :users -    test "login and browse site" do      # login via https      https! @@ -712,7 +710,7 @@ class UserFlowsTest < ActionDispatch::IntegrationTest      assert_equal 'Welcome david!', flash[:notice]      https!(false) -    get "/posts/all" +    get "/articles/all"      assert_response :success      assert assigns(:products)    end @@ -727,10 +725,7 @@ Here's an example of multiple sessions and custom DSL in an integration test  require 'test_helper'  class UserFlowsTest < ActionDispatch::IntegrationTest -  fixtures :users -    test "login and browse site" do -      # User david logs in      david = login(:david)      # User guest logs in @@ -807,43 +802,43 @@ For more information on `MiniTest`, refer to [Minitest](http://www.ruby-doc.org/  Setup and Teardown  ------------------ -If you would like to run a block of code before the start of each test and another block of code after the end of each test you have two special callbacks for your rescue. Let's take note of this by looking at an example for our functional test in `Posts` controller: +If you would like to run a block of code before the start of each test and another block of code after the end of each test you have two special callbacks for your rescue. Let's take note of this by looking at an example for our functional test in `Articles` controller:  ```ruby  require 'test_helper' -class PostsControllerTest < ActionController::TestCase +class ArticlesControllerTest < ActionController::TestCase    # called before every single test    def setup -    @post = posts(:one) +    @article = articles(:one)    end    # called after every single test    def teardown -    # as we are re-initializing @post before every test +    # as we are re-initializing @article before every test      # setting it to nil here is not essential but I hope      # you understand how you can use the teardown method -    @post = nil +    @article = nil    end -  test "should show post" do -    get :show, id: @post.id +  test "should show article" do +    get :show, id: @article.id      assert_response :success    end -  test "should destroy post" do -    assert_difference('Post.count', -1) do -      delete :destroy, id: @post.id +  test "should destroy article" do +    assert_difference('Article.count', -1) do +      delete :destroy, id: @article.id      end -    assert_redirected_to posts_path +    assert_redirected_to articles_path    end  end  ``` -Above, the `setup` method is called before each test and so `@post` is available for each of the tests. Rails implements `setup` and `teardown` as `ActiveSupport::Callbacks`. Which essentially means you need not only use `setup` and `teardown` as methods in your tests. You could specify them by using: +Above, the `setup` method is called before each test and so `@article` is available for each of the tests. Rails implements `setup` and `teardown` as `ActiveSupport::Callbacks`. Which essentially means you need not only use `setup` and `teardown` as methods in your tests. You could specify them by using:  * a block  * a method (like in the earlier example) @@ -855,38 +850,38 @@ Let's see the earlier example by specifying `setup` callback by specifying a met  ```ruby  require 'test_helper' -class PostsControllerTest < ActionController::TestCase +class ArticlesControllerTest < ActionController::TestCase    # called before every single test -  setup :initialize_post +  setup :initialize_article    # called after every single test    def teardown -    @post = nil +    @article = nil    end -  test "should show post" do -    get :show, id: @post.id +  test "should show article" do +    get :show, id: @article.id      assert_response :success    end -  test "should update post" do -    patch :update, id: @post.id, post: {} -    assert_redirected_to post_path(assigns(:post)) +  test "should update article" do +    patch :update, id: @article.id, article: {} +    assert_redirected_to article_path(assigns(:article))    end -  test "should destroy post" do -    assert_difference('Post.count', -1) do -      delete :destroy, id: @post.id +  test "should destroy article" do +    assert_difference('Article.count', -1) do +      delete :destroy, id: @article.id      end -    assert_redirected_to posts_path +    assert_redirected_to articles_path    end    private -    def initialize_post -      @post = posts(:one) +    def initialize_article +      @article = articles(:one)      end  end  ``` @@ -894,11 +889,11 @@ end  Testing Routes  -------------- -Like everything else in your Rails application, it is recommended that you test your routes. An example test for a route in the default `show` action of `Posts` controller above should look like: +Like everything else in your Rails application, it is recommended that you test your routes. An example test for a route in the default `show` action of `Articles` controller above should look like:  ```ruby -test "should route to post" do -  assert_routing '/posts/1', {controller: "posts", action: "show", id: "1"} +test "should route to article" do +  assert_routing '/articles/1', {controller: "articles", action: "show", id: "1"}  end  ``` @@ -1011,7 +1006,7 @@ located under the `test/helpers` directory. Rails provides a generator which  generates both the helper and the test file:  ```bash -$ rails generate helper User +$ bin/rails generate helper User        create  app/helpers/user_helper.rb        invoke  test_unit        create    test/helpers/user_helper_test.rb diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index da161f84c9..eab5779533 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -82,10 +82,10 @@ secrets, you need to:  2. Use your existing `secret_key_base` from the `secret_token.rb` initializer to     set the SECRET_KEY_BASE environment variable for whichever users run the Rails -   app in production mode. Alternately, you can simply copy the existing  -   `secret_key_base` from the `secret_token.rb` initializer to `secrets.yml`  +   app in production mode. Alternately, you can simply copy the existing +   `secret_key_base` from the `secret_token.rb` initializer to `secrets.yml`     under the `production` section, replacing '<%= ENV["SECRET_KEY_BASE"] %>'. -    +  3. Remove the `secret_token.rb` initializer.  4. Use `rake secret` to generate new keys for the `development` and `test` sections. @@ -393,6 +393,14 @@ start using the more precise `:plain:`, `:html`, and `:body` options instead.  Using `render :text` may pose a security risk, as the content is sent as  `text/html`. +### PostgreSQL json and hstore datatypes + +Rails 4.1 will map `json` and `hstore` columns to a string-keyed Ruby `Hash`. +In earlier versions a `HashWithIndifferentAccess` was used. This means that +symbol access is no longer supported. This is also the case for +`store_accessors` based on top of `json` or `hstore` columns. Make sure to use +string keys consistently. +  Upgrading from Rails 3.2 to Rails 4.0  ------------------------------------- @@ -480,7 +488,7 @@ def update    respond_to do |format|      format.json do        # perform a partial update -      @post.update params[:post] +      @article.update params[:article]      end      format.json_patch do @@ -883,7 +891,7 @@ AppName::Application.config.session_store :cookie_store, key: 'SOMETHINGNEW'  or  ```bash -$ rake db:sessions:clear +$ bin/rake db:sessions:clear  ```  ### Remove :cache and :concat options in asset helpers references in views diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index aba3c9ed61..7c3fd9f69d 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -158,7 +158,7 @@ is a helper that assists with writing forms. `form_for` takes a `:remote`  option. It works like this:  ```erb -<%= form_for(@post, remote: true) do |f| %> +<%= form_for(@article, remote: true) do |f| %>    ...  <% end %>  ``` @@ -166,7 +166,7 @@ option. It works like this:  This will generate the following HTML:  ```html -<form accept-charset="UTF-8" action="/posts" class="new_post" data-remote="true" id="new_post" method="post"> +<form accept-charset="UTF-8" action="/articles" class="new_article" data-remote="true" id="new_article" method="post">    ...  </form>  ``` @@ -180,10 +180,10 @@ bind to the `ajax:success` event. On failure, use `ajax:error`. Check it out:  ```coffeescript  $(document).ready -> -  $("#new_post").on("ajax:success", (e, data, status, xhr) -> -    $("#new_post").append xhr.responseText +  $("#new_article").on("ajax:success", (e, data, status, xhr) -> +    $("#new_article").append xhr.responseText    ).on "ajax:error", (e, xhr, status, error) -> -    $("#new_post").append "<p>ERROR</p>" +    $("#new_article").append "<p>ERROR</p>"  ```  Obviously, you'll want to be a bit more sophisticated than that, but it's a @@ -196,7 +196,7 @@ is very similar to `form_for`. It has a `:remote` option that you can use like  this:  ```erb -<%= form_tag('/posts', remote: true) do %> +<%= form_tag('/articles', remote: true) do %>    ...  <% end %>  ``` @@ -204,7 +204,7 @@ this:  This will generate the following HTML:  ```html -<form accept-charset="UTF-8" action="/posts" data-remote="true" method="post"> +<form accept-charset="UTF-8" action="/articles" data-remote="true" method="post">    ...  </form>  ``` @@ -219,21 +219,21 @@ is a helper that assists with generating links. It has a `:remote` option you  can use like this:  ```erb -<%= link_to "a post", @post, remote: true %> +<%= link_to "an article", @article, remote: true %>  ```  which generates  ```html -<a href="/posts/1" data-remote="true">a post</a> +<a href="/articles/1" data-remote="true">an article</a>  ```  You can bind to the same Ajax events as `form_for`. Here's an example. Let's -assume that we have a list of posts that can be deleted with just one +assume that we have a list of articles that can be deleted with just one  click. We would generate some HTML like this:  ```erb -<%= link_to "Delete post", @post, remote: true, method: :delete %> +<%= link_to "Delete article", @article, remote: true, method: :delete %>  ```  and write some CoffeeScript like this: @@ -241,7 +241,7 @@ and write some CoffeeScript like this:  ```coffeescript  $ ->    $("a[data-remote]").on "ajax:success", (e, data, status, xhr) -> -    alert "The post was deleted." +    alert "The article was deleted."  ```  ### button_to @@ -249,14 +249,14 @@ $ ->  [`button_to`](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to) is a helper that helps you create buttons. It has a `:remote` option that you can call like this:  ```erb -<%= button_to "A post", @post, remote: true %> +<%= button_to "An article", @article, remote: true %>  ```  this generates  ```html -<form action="/posts/1" class="button_to" data-remote="true" method="post"> -  <div><input type="submit" value="A post"></div> +<form action="/articles/1" class="button_to" data-remote="true" method="post"> +  <div><input type="submit" value="An article"></div>  </form>  ``` diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 577bc86fa9..1343f0a628 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,12 @@ -*   Load database configuration from the first -    database.yml available in paths. +*   Replace double quotes with single quotes while adding an entry into Gemfile. + +    *Alexander Belaev* + +*   Default `config.assets.digest` to `true` in development. + +    *Dan Kang* + +*   Load database configuration from the first `database.yml` available in paths.      *Pier-Olivier Thibault* diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index dce734b54e..04ce38f841 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -156,7 +156,8 @@ 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 @@ -199,17 +200,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 +212,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/named_base.rb b/railties/lib/rails/generators/named_base.rb index 5a92ab3e95..b7da44ca2d 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -30,7 +30,12 @@ module Rails        protected          attr_reader :file_name -        alias :singular_name :file_name + +        # FIXME: We are avoiding to use alias because a bug on thor that make +        # this method public and add it to the task list. +        def singular_name +          file_name +        end          # Wrap block with namespace of current application          # if namespace exists and is not skipped diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 8675d8bc1e..188e62b6c8 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -86,6 +86,16 @@ module Rails        end      end +    def config_when_updating +      cookie_serializer_config_exist = File.exist?('config/initializers/cookies_serializer.rb') + +      config + +      unless cookie_serializer_config_exist +        gsub_file 'config/initializers/cookies_serializer.rb', /json/, 'marshal' +      end +    end +      def database_yml        template "config/databases/#{options[:database]}.yml", "config/database.yml"      end @@ -188,6 +198,11 @@ module Rails          build(:config)        end +      def update_config_files +        build(:config_when_updating) +      end +      remove_task :update_config_files +        def create_boot_file          template "config/boot.rb"        end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 448b6f4845..5bdbd58097 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -16,7 +16,7 @@ source 'https://rubygems.org'  # Use ActiveModel has_secure_password  # gem 'bcrypt', '~> 3.1.7' -# Use unicorn as the app server +# Use Unicorn as the app server  # gem 'unicorn'  # Use Capistrano for deployment diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index de12565a73..bbb409616d 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -30,6 +30,9 @@ Rails.application.configure do    # number of complex assets.    config.assets.debug = true +  # Generate digests for assets URLs. +  config.assets.digest = true +    # Adds additional error checking when serving assets at runtime.    # Checks for improperly declared sprockets dependencies.    # Raises helpful error messages. diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb index 6b011e577a..87b8fe3516 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb @@ -5,9 +5,6 @@ require 'rails/test_help'  class ActiveSupport::TestCase  <% unless options[:skip_active_record] -%>    # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. -  # -  # Note: You'll currently still have to declare fixtures explicitly in integration tests -  # -- they do not yet inherit this setting    fixtures :all  <% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile index 0ba899176c..c338a0bdb1 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile +++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile @@ -19,6 +19,10 @@ APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__)  load 'rails/tasks/engine.rake'  <% end %> +<% if engine? -%> +load 'rails/tasks/statistics.rake' +<% end %> +  <% unless options[:skip_gemspec] -%>  Bundler::GemHelper.install_tasks 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/framework.rake b/railties/lib/rails/tasks/framework.rake index 3c8f8c6b87..a1c805f8aa 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -55,7 +55,7 @@ namespace :rails do      # desc "Update config/boot.rb from your current rails install"      task :configs do        invoke_from_app_generator :create_boot_file -      invoke_from_app_generator :create_config_files +      invoke_from_app_generator :update_config_files      end      # desc "Adds new executables to the application bin/ directory" diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake index c1674c72ad..ae5a7d2759 100644 --- a/railties/lib/rails/tasks/statistics.rake +++ b/railties/lib/rails/tasks/statistics.rake @@ -1,3 +1,6 @@ +# 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), @@ -13,10 +16,12 @@ STATS_DIRECTORIES = [    %w(Integration\ tests test/integration),    %w(Functional\ tests\ (old)  test/functional),    %w(Unit\ tests \ (old)       test/unit) -].collect { |name, dir| [ name, "#{Rails.root}/#{dir}" ] }.select { |name, dir| File.directory?(dir) } +].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" +desc "Report code statistics (KLOCs, etc) from the application or engine"  task :stats do    require 'rails/code_statistics'    CodeStatistics.new(*STATS_DIRECTORIES).to_s -end +end
\ No newline at end of file diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 410b0f7d70..8f091cfdbf 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -50,6 +50,8 @@ module ApplicationTests          end        RUBY +      add_to_env_config "development", "config.assets.digest = false" +        require "#{app_path}/config/environment"        get "/assets/demo.js" @@ -189,7 +191,6 @@ module ApplicationTests      end      test "asset pipeline should use a Sprockets::Index when config.assets.digest is true" do -      add_to_config "config.assets.digest = true"        add_to_config "config.action_controller.perform_caching = false"        ENV["RAILS_ENV"] = "production" @@ -202,8 +203,6 @@ module ApplicationTests        app_file "app/assets/images/rails.png", "notactuallyapng"        app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"        app_file "app/assets/javascripts/application.js", "alert();" -      # digest is default in false, we must enable it for test environment -      add_to_config "config.assets.digest = true"        precompile!        manifest = Dir["#{app_path}/public/assets/manifest-*.json"].first @@ -215,8 +214,6 @@ module ApplicationTests      test "the manifest file should be saved by default in the same assets folder" do        app_file "app/assets/javascripts/application.js", "alert();" -      # digest is default in false, we must enable it for test environment -      add_to_config "config.assets.digest = true"        add_to_config "config.assets.prefix = '/x'"        precompile! @@ -249,7 +246,6 @@ module ApplicationTests      test "precompile properly refers files referenced with asset_path and runs in the provided RAILS_ENV" do        app_file "app/assets/images/rails.png", "notactuallyapng"        app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>" -      # digest is default in false, we must enable it for test environment        add_to_env_config "test", "config.assets.digest = true"        precompile!('RAILS_ENV=test') @@ -281,12 +277,9 @@ module ApplicationTests      test "precompile appends the md5 hash to files referenced with asset_path and run in production with digest true" do        app_file "app/assets/images/rails.png", "notactuallyapng"        app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>" -      add_to_config "config.assets.compile = true" -      add_to_config "config.assets.digest = true" -      ENV["RAILS_ENV"] = nil - -      precompile!('RAILS_GROUPS=assets') +      ENV["RAILS_ENV"] = "production" +      precompile!        file = Dir["#{app_path}/public/assets/application-*.css"].first        assert_match(/\/assets\/rails-([0-z]+)\.png/, File.read(file)) @@ -342,6 +335,8 @@ module ApplicationTests          end        RUBY +      add_to_env_config "development", "config.assets.digest = false" +        require "#{app_path}/config/environment"        class ::OmgController < ActionController::Base @@ -366,6 +361,8 @@ module ApplicationTests        app_file "app/assets/javascripts/demo.js", "alert();" +      add_to_env_config "development", "config.assets.digest = false" +        require "#{app_path}/config/environment"        get "/assets/demo.js" @@ -395,7 +392,6 @@ module ApplicationTests        app_file "app/assets/javascripts/application.js", "//= require_tree ."        app_file "app/assets/javascripts/xmlhr.js.erb", "<%= Post.name %>" -      add_to_config "config.assets.digest = false"        precompile!        assert_equal "Post;\n", File.read(Dir["#{app_path}/public/assets/application-*.js"].first)      end @@ -415,7 +411,6 @@ module ApplicationTests      test "digested assets are not mistakenly removed" do        app_file "app/assets/application.js", "alert();"        add_to_config "config.assets.compile = true" -      add_to_config "config.assets.digest = true"        precompile! @@ -438,6 +433,7 @@ module ApplicationTests      test "asset urls should use the request's protocol by default" do        app_with_assets_in_view        add_to_config "config.asset_host = 'example.com'" +      add_to_env_config "development", "config.assets.digest = false"        require "#{app_path}/config/environment"        class ::PostsController < ActionController::Base; end @@ -452,6 +448,7 @@ module ApplicationTests        app_file "app/assets/javascripts/image_loader.js.erb", "var src='<%= image_path('rails.png') %>';"        add_to_config "config.assets.precompile = %w{rails.png image_loader.js}"        add_to_config "config.asset_host = 'example.com'" +      add_to_env_config "development", "config.assets.digest = false"        precompile!        assert_match "src='//example.com/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/image_loader-*.js"].first) @@ -460,9 +457,9 @@ module ApplicationTests      test "asset paths should use RAILS_RELATIVE_URL_ROOT by default" do        ENV["RAILS_RELATIVE_URL_ROOT"] = "/sub/uri"        app_file "app/assets/images/rails.png", "notreallyapng" -        app_file "app/assets/javascripts/app.js.erb", "var src='<%= image_path('rails.png') %>';"        add_to_config "config.assets.precompile = %w{rails.png app.js}" +      add_to_env_config "development", "config.assets.digest = false"        precompile!        assert_match "src='/sub/uri/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/app-*.js"].first) 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 007dd886da..1cbbf62459 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -119,7 +119,7 @@ class AppGeneratorTest < Rails::Generators::TestCase      generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true },                                                                 destination_root: app_moved_root, shell: @shell      generator.send(:app_const) -    quietly { generator.send(:create_config_files) } +    quietly { generator.send(:update_config_files) }      assert_file "myapp_moved/config/environment.rb", /Rails\.application\.initialize!/      assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/    end @@ -134,10 +134,46 @@ class AppGeneratorTest < Rails::Generators::TestCase      generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell      generator.send(:app_const) -    quietly { generator.send(:create_config_files) } +    quietly { generator.send(:update_config_files) }      assert_file "myapp/config/initializers/session_store.rb", /_myapp_session/    end +  def test_new_application_use_json_serialzier +    run_generator + +    assert_file("config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/) +  end + +  def test_rails_update_keep_the_cookie_serializer_if_it_is_already_configured +    app_root = File.join(destination_root, 'myapp') +    run_generator [app_root] + +    Rails.application.config.root = app_root +    Rails.application.class.stubs(:name).returns("Myapp") +    Rails.application.stubs(:is_a?).returns(Rails::Application) + +    generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell +    generator.send(:app_const) +    quietly { generator.send(:update_config_files) } +    assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/) +  end + +  def test_rails_update_set_the_cookie_serializer_to_marchal_if_it_is_not_already_configured +    app_root = File.join(destination_root, 'myapp') +    run_generator [app_root] + +    FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb") + +    Rails.application.config.root = app_root +    Rails.application.class.stubs(:name).returns("Myapp") +    Rails.application.stubs(:is_a?).returns(Rails::Application) + +    generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell +    generator.send(:app_const) +    quietly { generator.send(:update_config_files) } +    assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/) +  end +    def test_application_names_are_not_singularized      run_generator [File.join(destination_root, "hats")]      assert_file "hats/config/environment.rb", /Rails\.application\.initialize!/ diff --git a/railties/test/generators/argv_scrubber_test.rb b/railties/test/generators/argv_scrubber_test.rb index 31e07bc8da..31c2d846e2 100644 --- a/railties/test/generators/argv_scrubber_test.rb +++ b/railties/test/generators/argv_scrubber_test.rb @@ -1,5 +1,5 @@ -require 'active_support/test_case'  require 'active_support/testing/autorun' +require 'active_support/test_case'  require 'rails/generators/rails/app/app_generator'  require 'tempfile' diff --git a/railties/test/generators/generator_test.rb b/railties/test/generators/generator_test.rb index 7871399dd7..b136239795 100644 --- a/railties/test/generators/generator_test.rb +++ b/railties/test/generators/generator_test.rb @@ -1,5 +1,5 @@ -require 'active_support/test_case'  require 'active_support/testing/autorun' +require 'active_support/test_case'  require 'rails/generators/app_base'  module Rails 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 28e5b2ff1e..6240dc04ec 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -34,6 +34,7 @@ module RailtiesTest      test "serving sprocket's assets" do        @plugin.write "app/assets/javascripts/engine.js.erb", "<%= :alert %>();" +      add_to_env_config "development", "config.assets.digest = false"        boot_rails        require 'rack/test' @@ -1080,6 +1081,7 @@ YAML        RUBY        add_to_config("config.railties_order = [:all, :main_app, Blog::Engine]") +      add_to_env_config "development", "config.assets.digest = false"        boot_rails  | 
