diff options
| author | Aaron Patterson <aaron.patterson@gmail.com> | 2014-04-07 15:26:13 -0700 | 
|---|---|---|
| committer | Aaron Patterson <aaron.patterson@gmail.com> | 2014-04-07 15:26:13 -0700 | 
| commit | 347d74a03b137ab2e501c710c951a8b059df05ba (patch) | |
| tree | 1733cc97005b30d20ddbed7ab83bcac64705014b /activerecord/lib | |
| parent | 30b94a876f32f5024f841a6de9b5b20a014a41d3 (diff) | |
| parent | 3f5eb59f7a48aa5c08efb8db6cb41cd395c990af (diff) | |
| download | rails-347d74a03b137ab2e501c710c951a8b059df05ba.tar.gz rails-347d74a03b137ab2e501c710c951a8b059df05ba.tar.bz2 rails-347d74a03b137ab2e501c710c951a8b059df05ba.zip  | |
Merge branch 'master' into adequaterecord
* master: (122 commits)
  Rails.application should be set inside before_configuration hook
  remove check for present? from delete_all
  Remove useless begin..end
  Build the reverse_order on its proper method.
  Use connection-specific bytea escaping
  Ignore order when doing count.
  make enums distinct per class
  Remove unused `subclass_controller_with_flash_type_bar` var from flash test.
  fix CollectionProxy delete_all documentation
  Added OS X specific commands to installation guide [ci skip] Recommended using homebrew for installing MySQL and PostgreSQL
  Fix setup of adding _flash_types test.
  Use SVG version of travis build status badge [skip ci]
  W3C CSP document moved to gihub.io URL [ci skip]
  sprockets-rails was released
  Fix the test defining the models in the right place
  Add CHANGELOG entry for #11650 [ci skip]
  Declare the assets dependency
  Use sass-rails 4.0.3
  Make possible to use sprockets-rails 2.1
  add missing parentheses to validates_with documentation [skip ci]
  ...
Diffstat (limited to 'activerecord/lib')
42 files changed, 528 insertions, 490 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 860e76fa18..4abe2ad0a0 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -4,6 +4,12 @@ require 'active_support/core_ext/module/remove_method'  require 'active_record/errors'  module ActiveRecord +  class AssociationNotFoundError < ConfigurationError #:nodoc: +    def initialize(record, association_name) +      super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?") +    end +  end +    class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:      def initialize(reflection, associated_class = nil)        super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})") @@ -145,7 +151,7 @@ module ActiveRecord        association = association_instance_get(name)        if association.nil? -        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 diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 1f314e0677..803e3ab9ab 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -134,11 +134,11 @@ module ActiveRecord        end        def create(attributes = {}, &block) -        create_record(attributes, &block) +        _create_record(attributes, &block)        end        def create!(attributes = {}, &block) -        create_record(attributes, true, &block) +        _create_record(attributes, true, &block)        end        # Add +records+ to this association. Returns +self+ so method calls may @@ -182,11 +182,11 @@ module ActiveRecord        #        # See delete for more info.        def delete_all(dependent = nil) -        if dependent.present? && ![:nullify, :delete_all].include?(dependent) +        if dependent && ![:nullify, :delete_all].include?(dependent)            raise ArgumentError, "Valid values are :nullify or :delete_all"          end -        dependent = if dependent.present? +        dependent = if dependent                        dependent                      elsif options[:dependent] == :destroy                        :delete_all @@ -248,7 +248,7 @@ module ActiveRecord          dependent = _options[:dependent] || options[:dependent]          if records.first == :all -          if loaded? || dependent == :destroy +          if (loaded? || dependent == :destroy) && dependent != :delete_all              delete_or_destroy(load_target, dependent)            else              delete_records(:all, dependent) @@ -449,13 +449,13 @@ module ActiveRecord            persisted + memory          end -        def create_record(attributes, raise = false, &block) +        def _create_record(attributes, raise = false, &block)            unless owner.persisted?              raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"            end            if attributes.is_a?(Array) -            attributes.collect { |attr| create_record(attr, raise, &block) } +            attributes.collect { |attr| _create_record(attr, raise, &block) }            else              transaction do                add_to_target(build_record(attributes)) do |record| diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index eba688866c..84c8cfe72b 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -357,7 +357,7 @@ module ActiveRecord        # Deletes all the records from the collection. For +has_many+ associations,        # the deletion is done according to the strategy specified by the <tt>:dependent</tt> -      # option. Returns an array with the deleted records. +      # option.        #        # If no <tt>:dependent</tt> option is given, then it will follow the        # default strategy. The default strategy is <tt>:nullify</tt>. This @@ -435,11 +435,6 @@ module ActiveRecord        #   #    ]        #        #   person.pets.delete_all -      #   # => [ -      #   #       #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, -      #   #       #<Pet id: 2, name: "Spook", person_id: 1>, -      #   #       #<Pet id: 3, name: "Choo-Choo", person_id: 1> -      #   #    ]        #        #   Pet.find(1, 2, 3)        #   # => ActiveRecord::RecordNotFound @@ -860,6 +855,10 @@ module ActiveRecord          !!@association.include?(record)        end +      def arel +        scope.arel +      end +        def proxy_association          @association        end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index ca36462054..a0e83c0a02 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -57,7 +57,7 @@ module ActiveRecord              end              scope_chain_index += 1 -            scope_chain_items.concat [klass.send(:build_default_scope)].compact +            scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact              rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|                left.merge right diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index e49fc5d5c4..311684d886 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -140,36 +140,13 @@ module ActiveRecord        end        def grouped_records(association, records) -        reflection_records = records_by_reflection(association, records) - -        reflection_records.each_with_object({}) do |(reflection, r_records),h| -          h[reflection] = r_records.group_by { |record| -            association_klass(reflection, record) -          } -        end -      end - -      def records_by_reflection(association, records) -        records.group_by do |record| -          reflection = record.class.reflect_on_association(association) - -          reflection || raise_config_error(record, association) -        end -      end - -      def raise_config_error(record, association) -        raise ActiveRecord::ConfigurationError, -              "Association named '#{association}' was not found on #{record.class.name}; " \ -              "perhaps you misspelled it?" -      end - -      def association_klass(reflection, record) -        if reflection.macro == :belongs_to && reflection.options[:polymorphic] -          klass = record.read_attribute(reflection.foreign_type.to_s) -          klass && klass.constantize -        else -          reflection.klass +        h = {} +        records.each do |record| +          assoc = record.association(association) +          klasses = h[assoc.reflection] ||= {} +          (klasses[assoc.klass] ||= []) << record          end +        h        end        class AlreadyLoaded diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index 399aff378a..747bb5f1d6 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -18,11 +18,11 @@ module ActiveRecord        end        def create(attributes = {}, &block) -        create_record(attributes, &block) +        _create_record(attributes, &block)        end        def create!(attributes = {}, &block) -        create_record(attributes, true, &block) +        _create_record(attributes, true, &block)        end        def build(attributes = {}) @@ -52,7 +52,7 @@ module ActiveRecord            replace(record)          end -        def create_record(attributes, raise_error = false) +        def _create_record(attributes, raise_error = false)            record = build_record(attributes)            yield(record) if block_given?            saved = record.save diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index ea48a13ea8..4b1733619a 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -29,6 +29,8 @@ module ActiveRecord        end      } +    BLACKLISTED_CLASS_METHODS = %w(private public protected) +      class AttributeMethodCache        def initialize          @module = Module.new @@ -132,7 +134,7 @@ module ActiveRecord        # A class method is 'dangerous' if it is already (re)defined by Active Record, but        # not by any ancestors. (So 'puts' is not dangerous but 'new' is.)        def dangerous_class_method?(method_name) -        class_method_defined_within?(method_name, Base) +        BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)        end        def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 8a1b199997..99070f127b 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -79,11 +79,11 @@ module ActiveRecord          end        end -      def update_record(*) +      def _update_record(*)          partial_writes? ? super(keys_for_partial_write) : super        end -      def create_record(*) +      def _create_record(*)          partial_writes? ? super(keys_for_partial_write) : super        end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 213b72b933..e9622ca0c1 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -377,16 +377,15 @@ 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 -          elsif autosave != false +          else              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 9ec1feea97..1d47cba234 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -310,8 +310,8 @@ module ActiveRecord #:nodoc:      include Locking::Optimistic      include Locking::Pessimistic      include AttributeMethods -    include Callbacks      include Timestamp +    include Callbacks      include Associations      include ActiveModel::SecurePassword      include AutosaveAssociation diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 35f19f0bc0..5955673b42 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -302,11 +302,11 @@ module ActiveRecord        run_callbacks(:save) { super }      end -    def create_record #:nodoc: +    def _create_record #:nodoc:        run_callbacks(:create) { super }      end -    def update_record(*) #:nodoc: +    def _update_record(*) #:nodoc:        run_callbacks(:update) { super }      end    end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index deea857a18..e56ece0300 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -324,7 +324,7 @@ module ActiveRecord        def sanitize_limit(limit)          if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)            limit -        elsif limit.to_s =~ /,/ +        elsif limit.to_s.include?(',')            Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')          else            Integer(limit) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index a51691bfa8..47fe501752 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -13,7 +13,7 @@ module ActiveRecord          end          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 #{quote_column_name(o.name)} #{sql_type}"            add_column_options!(sql, column_options(o))          end @@ -26,7 +26,7 @@ module ActiveRecord            end            def visit_ColumnDefinition(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)              column_sql = "#{quote_column_name(o.name)} #{sql_type}"              add_column_options!(column_sql, column_options(o)) unless o.primary_key?              column_sql @@ -64,7 +64,7 @@ module ActiveRecord            end            def add_column_options!(sql, options) -            sql << " DEFAULT #{@conn.quote(options[:default], options[:column])}" if options_include_default?(options) +            sql << " DEFAULT #{quote_value(options[:default], options[:column])}" if options_include_default?(options)              # must explicitly check for :null to allow change_column to work on migrations              if options[:null] == false                sql << " NOT NULL" @@ -75,6 +75,12 @@ module ActiveRecord              sql            end +          def quote_value(value, column) +            column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale) + +            @conn.quote(value, column) +          end +            def options_include_default?(options)              options.include?(:default) && !(options[:null] == false && options[:default].nil?)            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 c39bf15e83..71c3a4378b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -15,7 +15,7 @@ module ActiveRecord      # are typically created by methods in TableDefinition, and added to the      # +columns+ attribute of said TableDefinition object, in order to be used      # for generating a number of table creation or table changing SQL statements. -    class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key) #:nodoc: +    class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key, :sql_type) #:nodoc:        def primary_key?          primary_key || type.to_sym == :primary_key diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 7f530ec5e4..aa99822389 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -186,24 +186,23 @@ module ActiveRecord        def create_table(table_name, options = {})          td = create_table_definition table_name, options[:temporary], options[:options], options[:as] -        if !options[:as] -          unless options[:id] == false -            pk = options.fetch(:primary_key) { -              Base.get_primary_key table_name.to_s.singularize -            } - -            td.primary_key pk, options.fetch(:id, :primary_key), options +        if options[:id] != false && !options[:as] +          pk = options.fetch(:primary_key) do +            Base.get_primary_key table_name.to_s.singularize            end -          yield td if block_given? +          td.primary_key pk, options.fetch(:id, :primary_key), options          end +        yield td if block_given? +          if options[:force] && table_exists?(table_name)            drop_table(table_name, options)          end -        execute schema_creation.accept td -        td.indexes.each_pair { |c,o| add_index table_name, c, o } +        result = execute schema_creation.accept td +        td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create? +        result        end        # Creates a new join table with the name created using the lexical order of the first two @@ -740,6 +739,40 @@ module ActiveRecord          Table.new(table_name, base)        end +      def add_index_options(table_name, column_name, options = {}) #:nodoc: +        column_names = Array(column_name) +        index_name   = index_name(table_name, column: column_names) + +        options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type) + +        index_type = options[:unique] ? "UNIQUE" : "" +        index_type = options[:type].to_s if options.key?(:type) +        index_name = options[:name].to_s if options.key?(:name) +        max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length + +        if options.key?(:algorithm) +          algorithm = index_algorithms.fetch(options[:algorithm]) { +            raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}") +          } +        end + +        using = "USING #{options[:using]}" if options[:using].present? + +        if supports_partial_index? +          index_options = options[:where] ? " WHERE #{options[:where]}" : "" +        end + +        if index_name.length > max_index_length +          raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" +        end +        if table_exists?(table_name) && index_name_exists?(table_name, index_name, false) +          raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" +        end +        index_columns = quoted_columns_for_index(column_names, options).join(", ") + +        [index_name, index_type, index_columns, index_options, algorithm, using] +      end +        protected          def add_index_sort_order(option_strings, column_names, options = {})            if options.is_a?(Hash) && order = options[:order] @@ -770,40 +803,6 @@ module ActiveRecord            options.include?(:default) && !(options[:null] == false && options[:default].nil?)          end -        def add_index_options(table_name, column_name, options = {}) -          column_names = Array(column_name) -          index_name   = index_name(table_name, column: column_names) - -          options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type) - -          index_type = options[:unique] ? "UNIQUE" : "" -          index_type = options[:type].to_s if options.key?(:type) -          index_name = options[:name].to_s if options.key?(:name) -          max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length - -          if options.key?(:algorithm) -            algorithm = index_algorithms.fetch(options[:algorithm]) { -              raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}") -            } -          end - -          using = "USING #{options[:using]}" if options[:using].present? - -          if supports_partial_index? -            index_options = options[:where] ? " WHERE #{options[:where]}" : "" -          end - -          if index_name.length > max_index_length -            raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" -          end -          if index_name_exists?(table_name, index_name, false) -            raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" -          end -          index_columns = quoted_columns_for_index(column_names, options).join(", ") - -          [index_name, index_type, index_columns, index_options, algorithm, using] -        end -          def index_name_for_remove(table_name, options = {})            index_name = index_name(table_name, options) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 2d45079168..78e0cc3d1b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -152,28 +152,19 @@ module ActiveRecord          'Abstract'        end -      # Does this adapter support migrations? Backend specific, as the -      # abstract adapter always returns +false+. +      # Does this adapter support migrations?        def supports_migrations?          false        end        # Can this adapter determine the primary key for tables not attached -      # to an Active Record class, such as join tables? Backend specific, as -      # the abstract adapter always returns +false+. +      # to an Active Record class, such as join tables?        def supports_primary_key?          false        end -      # Does this adapter support using DISTINCT within COUNT? This is +true+ -      # for all adapters except sqlite. -      def supports_count_distinct? -        true -      end -        # Does this adapter support DDL rollbacks in transactions? That is, would -      # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL, -      # SQL Server, and others support this. MySQL and others do not. +      # CREATE TABLE or ALTER TABLE get rolled back by a transaction?        def supports_ddl_transactions?          false        end @@ -182,8 +173,7 @@ module ActiveRecord          false        end -      # Does this adapter support savepoints? PostgreSQL and MySQL do, -      # SQLite < 3.6.8 does not. +      # Does this adapter support savepoints?        def supports_savepoints?          false        end @@ -191,7 +181,6 @@ module ActiveRecord        # Should primary key values be selected from their corresponding        # sequence before the insert statement? If true, next_sequence_value        # is called before each insert to set the record's primary key. -      # This is false for all adapters but Firebird.        def prefetch_primary_key?(table_name = nil)          false        end @@ -206,8 +195,7 @@ module ActiveRecord          false        end -      # Does this adapter support explain? As of this writing sqlite3, -      # mysql2, and postgresql are the only ones that do. +      # Does this adapter support explain?        def supports_explain?          false        end @@ -217,12 +205,17 @@ module ActiveRecord          false        end -      # Does this adapter support database extensions? As of this writing only -      # postgresql does. +      # Does this adapter support database extensions?        def supports_extensions?          false        end +      # Does this adapter support creating indexes in the same statement as +      # creating the table? +      def supports_indexes_in_create? +        false +      end +        # This is meant to be implemented by the adapters that support extensions        def disable_extension(name)        end @@ -231,14 +224,12 @@ module ActiveRecord        def enable_extension(name)        end -      # A list of extensions, to be filled in by adapters that support them. At -      # the moment only postgresql does. +      # A list of extensions, to be filled in by adapters that support them.        def extensions          []        end        # A list of index algorithms, to be filled by adapters that support them. -      # MySQL and PostgreSQL have support for them right now.        def index_algorithms          {}        end @@ -299,7 +290,6 @@ module ActiveRecord        end        # Returns true if its required to reload the connection between requests for development mode. -      # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.        def requires_reloading?          false        end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 9a819720f7..20eea208ec 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -6,18 +6,31 @@ module ActiveRecord        include Savepoints        class SchemaCreation < AbstractAdapter::SchemaCreation -          def visit_AddColumn(o)            add_column_position!(super, column_options(o))          end          private + +        def visit_TableDefinition(o) +          name = o.name +          create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} " + +          statements = o.columns.map { |c| accept c } +          statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) }) + +          create_sql << "(#{statements.join(', ')}) " if statements.present? +          create_sql << "#{o.options}" +          create_sql << " AS #{@conn.to_sql(o.as)}" if o.as +          create_sql +        end +          def visit_ChangeColumnDefinition(o)            column = o.column            options = o.options            sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])            change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}" -          add_column_options!(change_column_sql, options) +          add_column_options!(change_column_sql, options.merge(column: column))            add_column_position!(change_column_sql, options)          end @@ -29,6 +42,11 @@ module ActiveRecord            end            sql          end + +        def index_in_create(table_name, column_name, options) +          index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options) +          "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}" +        end        end        def schema_creation @@ -225,6 +243,10 @@ module ActiveRecord          version[0] >= 5        end +      def supports_indexes_in_create? +        true +      end +        def native_database_types          NATIVE_DATABASE_TYPES        end diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 9a133168f8..e0715f7ce9 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -35,7 +35,12 @@ module ActiveRecord            @uri     = URI.parse(url)            @adapter = @uri.scheme            @adapter = "postgresql" if @adapter == "postgres" -          @query   = @uri.query || '' + +          if @uri.opaque +            @uri.opaque, @query = @uri.opaque.split('?', 2) +          else +            @query = @uri.query +          end          end          # Converts the given URL to a full connection hash. @@ -65,30 +70,38 @@ module ActiveRecord          #   "localhost"          #   # => {}          def query_hash -          Hash[@query.split("&").map { |pair| pair.split("=") }] +          Hash[(@query || '').split("&").map { |pair| pair.split("=") }]          end          def raw_config -          query_hash.merge({ -            "adapter"  => @adapter, -            "username" => uri.user, -            "password" => uri.password, -            "port"     => uri.port, -            "database" => database, -            "host"     => uri.host }) +          if uri.opaque +            query_hash.merge({ +              "adapter"  => @adapter, +              "database" => uri.opaque }) +          else +            query_hash.merge({ +              "adapter"  => @adapter, +              "username" => uri.user, +              "password" => uri.password, +              "port"     => uri.port, +              "database" => database_from_path, +              "host"     => uri.host }) +          end          end          # Returns name of the database. -        # Sqlite3 expects this to be a full path or `:memory:`. -        def database +        def database_from_path            if @adapter == 'sqlite3' -            if '/:memory:' == uri.path -              ':memory:' -            else -              uri.path -            end +            # 'sqlite3:/foo' is absolute, because that makes sense. The +            # corresponding relative version, 'sqlite3:foo', is handled +            # elsewhere, as an "opaque". + +            uri.path            else -            uri.path.sub(%r{^/},"") +            # Only SQLite uses a filename as the "database" name; for +            # anything else, a leading slash would be silly. + +            uri.path.sub(%r{^/}, "")            end          end        end @@ -124,7 +137,7 @@ module ActiveRecord            if config              resolve_connection config            elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call -            resolve_env_connection env.to_sym +            resolve_symbol_connection env.to_sym            else              raise AdapterNotSpecified            end @@ -193,42 +206,41 @@ module ActiveRecord          #          def resolve_connection(spec)            case spec -          when Symbol, String -            resolve_env_connection spec +          when Symbol +            resolve_symbol_connection spec +          when String +            resolve_string_connection spec            when Hash              resolve_hash_connection spec            end          end +        def resolve_string_connection(spec) +          # Rails has historically accepted a string to mean either +          # an environment key or a URL spec, so we have deprecated +          # this ambiguous behaviour and in the future this function +          # can be removed in favor of resolve_url_connection. +          if configurations.key?(spec) +            ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \ +              "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead" +            resolve_connection(configurations[spec]) +          else +            resolve_url_connection(spec) +          end +        end +          # Takes the environment such as `:production` or `:development`.          # This requires that the @configurations was initialized with a key that          # matches.          # -        # -        #   Resolver.new("production" => {}).resolve_env_connection(:production) +        #   Resolver.new("production" => {}).resolve_symbol_connection(:production)          #   # => {}          # -        # Takes a connection URL. -        # -        #   Resolver.new({}).resolve_env_connection("postgresql://localhost/foo") -        #   # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } -        # -        def resolve_env_connection(spec) -          # Rails has historically accepted a string to mean either -          # an environment key or a URL spec, so we have deprecated -          # this ambiguous behaviour and in the future this function -          # can be removed in favor of resolve_string_connection and -          # resolve_symbol_connection. +        def resolve_symbol_connection(spec)            if config = configurations[spec.to_s] -            if spec.is_a?(String) -              ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \ -                "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead" -            end              resolve_connection(config) -          elsif spec.is_a?(String) -            resolve_string_connection(spec)            else -            raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available configuration: #{configurations.inspect}") +            raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}")            end          end @@ -244,7 +256,12 @@ module ActiveRecord            spec          end -        def resolve_string_connection(url) +        # Takes a connection URL. +        # +        #   Resolver.new({}).resolve_url_connection("postgresql://localhost/foo") +        #   # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } +        # +        def resolve_url_connection(url)            ConnectionUrlResolver.new(url).to_hash          end        end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 81b9703684..6cf0c94eb5 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -20,9 +20,9 @@ module ActiveRecord        ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)      rescue Mysql2::Error => error        if error.message.include?("Unknown database") -        raise ActiveRecord::NoDatabaseError.new(error.message) +        raise ActiveRecord::NoDatabaseError.new(error.message, error)        else -        raise error +        raise        end      end    end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 49f0bfbcde..e6aa2ba921 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -36,9 +36,9 @@ module ActiveRecord        ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)      rescue Mysql::Error => error        if error.message.include?("Unknown database") -        raise ActiveRecord::NoDatabaseError.new(error.message) +        raise ActiveRecord::NoDatabaseError.new(error.message, error)        else -        raise error +        raise        end      end    end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb new file mode 100644 index 0000000000..2cbcd5fd50 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -0,0 +1,158 @@ +module ActiveRecord +  module ConnectionAdapters +    # PostgreSQL-specific extensions to column definitions in a table. +    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) + +        if sql_type =~ /\[\]$/ +          @array = true +          super(name, default_value, sql_type[0..sql_type.length - 3], null) +        else +          @array = false +          super(name, default_value, sql_type, null) +        end + +        @default_function = default if has_default_function?(default_value, default) +      end + +      def number? +        !array && super +      end + +      def text? +        !array && super +      end + +      # :stopdoc: +      class << self +        include ConnectionAdapters::PostgreSQLColumn::Cast +        include ConnectionAdapters::PostgreSQLColumn::ArrayParser +        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 + +      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 +      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 5d32aaed50..9e898015a6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -6,6 +6,7 @@ module ActiveRecord        module OID          class Type            def type; end +          def simplified_type(sql_type); type end            def infinity(options = {})              ::Float::INFINITY * (options[:negative] ? -1 : 1) @@ -18,7 +19,9 @@ module ActiveRecord            end          end -        class Text < Type +        class String < Type +          def type; :string end +            def type_cast(value)              return if value.nil? @@ -26,9 +29,23 @@ module ActiveRecord            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 +            if ::String === value                ConnectionAdapters::PostgreSQLColumn.string_to_bit value              else                value @@ -37,6 +54,8 @@ module ActiveRecord          end          class Bytea < Type +          def type; :binary end +            def type_cast(value)              return if value.nil?              PGconn.unescape_bytea value @@ -44,9 +63,11 @@ module ActiveRecord          end          class Money < Type +          def type; :decimal end +            def type_cast(value)              return if value.nil? -            return value unless String === 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): @@ -88,8 +109,10 @@ module ActiveRecord          end          class Point < Type +          def type; :string end +            def type_cast(value) -            if String === value +            if ::String === value                ConnectionAdapters::PostgreSQLColumn.string_to_point value              else                value @@ -98,13 +121,15 @@ module ActiveRecord          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 +            if ::String === value                ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype              else                value @@ -114,6 +139,8 @@ module ActiveRecord          class Range < Type            attr_reader :subtype +          def simplified_type(sql_type); sql_type.to_sym end +            def initialize(subtype)              @subtype = subtype            end @@ -160,6 +187,8 @@ This is not reliable and will be removed in the future.          end          class Integer < Type +          def type; :integer end +            def type_cast(value)              return if value.nil? @@ -168,6 +197,8 @@ This is not reliable and will be removed in the future.          end          class Boolean < Type +          def type; :boolean end +            def type_cast(value)              return if value.nil? @@ -177,6 +208,14 @@ This is not reliable and will be removed in the future.          class Timestamp < Type            def type; :timestamp; end +          def simplified_type(sql_type) +            case sql_type +            when /^timestamp with(?:out)? time zone$/ +              :datetime +            else +              :timestamp +            end +          end            def type_cast(value)              return if value.nil? @@ -188,7 +227,7 @@ This is not reliable and will be removed in the future.          end          class Date < Type -          def type; :datetime; end +          def type; :date; end            def type_cast(value)              return if value.nil? @@ -200,6 +239,8 @@ This is not reliable and will be removed in the future.          end          class Time < Type +          def type; :time end +            def type_cast(value)              return if value.nil? @@ -210,6 +251,8 @@ This is not reliable and will be removed in the future.          end          class Float < Type +          def type; :float end +            def type_cast(value)              return if value.nil? @@ -218,6 +261,8 @@ This is not reliable and will be removed in the future.          end          class Decimal < Type +          def type; :decimal end +            def type_cast(value)              return if value.nil? @@ -230,12 +275,16 @@ This is not reliable and will be removed in the future.          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 @@ -252,14 +301,20 @@ This is not reliable and will be removed in the future.          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 @@ -275,6 +330,13 @@ This is not reliable and will be removed in the future.            end          end +        class Uuid < Type +          def type; :uuid end +          def type_cast(value) +            value.presence +          end +        end +          class TypeMap            def initialize              @mapping = {} @@ -321,7 +383,7 @@ This is not reliable and will be removed in the future.          }          # Register an OID type named +name+ with a typecasting object in -        # +type+.  +name+ should correspond to the `typname` column in +        # +type+. +name+ should correspond to the `typname` column in          # the `pg_type` table.          def self.register_type(name, type)            NAMES[name] = type @@ -338,48 +400,46 @@ This is not reliable and will be removed in the future.          end          register_type 'int2', OID::Integer.new -        alias_type    'int4', 'int2' -        alias_type    'int8', 'int2' -        alias_type    'oid',  'int2' - +        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 -        alias_type 'varchar', 'text' -        alias_type 'char', 'text' -        alias_type 'bpchar', 'text' -        alias_type 'xml', 'text' - -        # FIXME: why are we keeping these types as strings? -        alias_type 'tsvector', 'text' -        alias_type 'interval', 'text' -        alias_type 'macaddr',  'text' -        alias_type 'uuid',     'text' - -        register_type 'money', OID::Money.new -        register_type 'bytea', OID::Bytea.new +        register_type 'varchar', OID::String.new +        alias_type 'char', 'varchar' +        alias_type 'bpchar', 'varchar'          register_type 'bool', OID::Boolean.new          register_type 'bit', OID::Bit.new -        register_type 'varbit', OID::Bit.new - -        register_type 'float4', OID::Float.new -        alias_type 'float8', 'float4' - +        alias_type 'varbit', 'bit'          register_type 'timestamp', OID::Timestamp.new -        register_type 'timestamptz', OID::Timestamp.new +        alias_type 'timestamptz', 'timestamp'          register_type 'date', OID::Date.new          register_type 'time', OID::Time.new -        register_type 'path', OID::Text.new +        register_type 'money', OID::Money.new +        register_type 'bytea', OID::Bytea.new          register_type 'point', OID::Point.new -        register_type 'polygon', OID::Text.new -        register_type 'circle', OID::Text.new          register_type 'hstore', OID::Hstore.new          register_type 'json', OID::Json.new -        register_type 'citext', OID::Text.new -        register_type 'ltree', OID::Text.new -          register_type 'cidr', OID::Cidr.new -        alias_type 'inet', 'cidr' +        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'        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 210172cf32..ac3b0f713d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -4,14 +4,14 @@ module ActiveRecord        module Quoting          # Escapes binary strings for bytea input to the database.          def escape_bytea(value) -          PGconn.escape_bytea(value) if value +          @connection.escape_bytea(value) if value          end          # Unescapes bytea output from a database to the binary string it represents.          # NOTE: This is NOT an inverse of escape_bytea! This is only to be used          # on escaped binary output from database drive.          def unescape_bytea(value) -          PGconn.unescape_bytea(value) if value +          @connection.unescape_bytea(value) if value          end          # Quotes PostgreSQL-specific data types for SQL input. 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 e0afa989cd..50a73aa666 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -104,14 +104,11 @@ module ActiveRecord            schema, table = Utils.extract_schema_and_table(name.to_s)            return false unless table -          binds = [[nil, table]] -          binds << [nil, schema] if schema -            exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0                SELECT COUNT(*)                FROM pg_class c                LEFT JOIN pg_namespace n ON n.oid = c.relnamespace -              WHERE c.relkind in ('v','r') +              WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view                AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'                AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}            SQL diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index bcad9f30d7..3510e4f3b0 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -7,6 +7,7 @@ require 'active_record/connection_adapters/postgresql/quoting'  require 'active_record/connection_adapters/postgresql/schema_statements'  require 'active_record/connection_adapters/postgresql/database_statements'  require 'active_record/connection_adapters/postgresql/referential_integrity' +require 'active_record/connection_adapters/postgresql/column'  require 'arel/visitors/bind_visitor'  # Make sure we're using pg high enough for PGResult#values @@ -43,224 +44,6 @@ module ActiveRecord    end    module ConnectionAdapters -    # PostgreSQL-specific extensions to column definitions in a table. -    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) - -        if sql_type =~ /\[\]$/ -          @array = true -          super(name, default_value, sql_type[0..sql_type.length - 3], null) -        else -          @array = false -          super(name, default_value, sql_type, null) -        end - -        @default_function = default if has_default_function?(default_value, default) -      end - -      def number? -        !array && super -      end - -      def text? -        !array && super -      end - -      # :stopdoc: -      class << self -        include ConnectionAdapters::PostgreSQLColumn::Cast -        include ConnectionAdapters::PostgreSQLColumn::ArrayParser -        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 - -      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 -      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) -          case field_type -          # Numeric and monetary types -          when /^(?:real|double precision)$/ -            :float -          # Monetary types -          when 'money' -            :decimal -          when 'hstore' -            :hstore -          when 'ltree' -            :ltree -          # Network address types -          when 'inet' -            :inet -          when 'cidr' -            :cidr -          when 'macaddr' -            :macaddr -          # Character types -          when /^(?:character varying|bpchar)(?:\(\d+\))?$/ -            :string -          when /^citext(?:\(\d+\))?$/ -            :citext -          # Binary data types -          when 'bytea' -            :binary -          # Date/time types -          when /^timestamp with(?:out)? time zone$/ -            :datetime -          when /^interval(?:|\(\d+\))$/ -            :string -          # Geometric types -          when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/ -            :string -          # Bit strings -          when /^bit(?: varying)?(?:\(\d+\))?$/ -            :string -          # XML type -          when 'xml' -            :xml -          # tsvector type -          when 'tsvector' -            :tsvector -          # Arrays -          when /^\D+\[\]$/ -            :string -          # Object identifier types -          when 'oid' -            :integer -          # UUID type -          when 'uuid' -            :uuid -          # JSON type -          when 'json' -            :json -          # Small and big integer types -          when /^(?:small|big)int$/ -            :integer -          when /(num|date|tstz|ts|int4|int8)range$/ -            field_type.to_sym -          # Pass through all types that are not specific to PostgreSQL. -          else -            super -          end -        end -    end -      # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.      #      # Options: @@ -426,7 +209,7 @@ module ActiveRecord        NATIVE_DATABASE_TYPES = {          primary_key: "serial primary key", -        string:      { name: "character varying", limit: 255 }, +        string:      { name: "character varying" },          text:        { name: "text" },          integer:     { name: "integer" },          float:       { name: "float" }, @@ -671,6 +454,10 @@ module ActiveRecord          postgresql_version >= 90200        end +      def supports_materialized_views? +        postgresql_version >= 90300 +      end +        def enable_extension(name)          exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {            reload_type_map @@ -799,22 +586,23 @@ module ActiveRecord          def initialize_type_map(type_map)            if supports_ranges?              result = execute(<<-SQL, 'SCHEMA') -              SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype +              SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype                FROM pg_type as t                LEFT JOIN pg_range as r ON oid = rngtypid              SQL            else              result = execute(<<-SQL, 'SCHEMA') -              SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput +              SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype                FROM pg_type as t              SQL            end -          ranges, nodes = result.partition { |row| row['typinput'] == 'range_in' } -          leaves, nodes = nodes.partition { |row| row['typelem'] == '0' } +          ranges, nodes = result.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' } +          leaves, nodes = nodes.partition { |row| row['typelem'] == '0' }            # populate the enum types -          enums, leaves = leaves.partition { |row| row['typinput'] == 'enum_in' }            enums.each do |row|              type_map[row['oid'].to_i] = OID::Enum.new            end @@ -843,6 +631,16 @@ module ActiveRecord              range = OID::Range.new subtype              type_map[row['oid'].to_i] = range            end + +          # populate domain types +          domains.each do |row| +            base_type_oid = row["typbasetype"].to_i +            if base_type = type_map[base_type_oid] +              type_map[row['oid'].to_i] = base_type +            else +              warn "unknown base type (OID: #{base_type_oid}) for domain #{row["typname"]}." +            end +          end          end          FEATURE_NOT_SUPPORTED = "0A000" #:nodoc: @@ -924,9 +722,9 @@ module ActiveRecord            configure_connection          rescue ::PG::Error => error            if error.message.include?("does not exist") -            raise ActiveRecord::NoDatabaseError.new(error.message) +            raise ActiveRecord::NoDatabaseError.new(error.message, error)            else -            raise error +            raise            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 1e299f12cd..2cb619881a 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -33,9 +33,9 @@ module ActiveRecord        ConnectionAdapters::SQLite3Adapter.new(db, logger, config)      rescue Errno::ENOENT => error        if error.message.include?("No such file or directory") -        raise ActiveRecord::NoDatabaseError.new(error.message) +        raise ActiveRecord::NoDatabaseError.new(error.message, error)        else -        raise error +        raise        end      end    end @@ -63,7 +63,7 @@ module ActiveRecord        NATIVE_DATABASE_TYPES = {          primary_key:  'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL', -        string:       { name: "varchar", limit: 255 }, +        string:       { name: "varchar" },          text:         { name: "text" },          integer:      { name: "integer" },          float:        { name: "float" }, diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 73ca468d80..eeb2f5430e 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -366,7 +366,7 @@ module ActiveRecord      def ==(comparison_object)        super ||          comparison_object.instance_of?(self.class) && -        id && +        !id.nil? &&          comparison_object.id == id      end      alias :eql? :== diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 4aa323fb00..167f8b7a49 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -65,10 +65,13 @@ module ActiveRecord    #    # Where conditions on an enum attribute must use the ordinal value of an enum.    module Enum -    DEFINED_ENUMS = {} # :nodoc: +    def self.extended(base) +      base.class_attribute(:defined_enums) +      base.defined_enums = {} +    end      def enum_mapping_for(attr_name) # :nodoc: -      DEFINED_ENUMS[attr_name.to_s] +      defined_enums[attr_name.to_s]      end      def enum(definitions) @@ -122,9 +125,8 @@ module ActiveRecord              klass.send(:detect_enum_conflict!, name, value, true)              klass.scope value, -> { klass.where name => i }            end - -          DEFINED_ENUMS[name.to_s] = enum_values          end +        defined_enums[name.to_s] = enum_values        end      end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 7f6228131f..71efbb8f93 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -95,15 +95,7 @@ module ActiveRecord    end    # Raised when a given database does not exist -  class NoDatabaseError < ActiveRecordError -    def initialize(message) -      super extend_message(message) -    end - -    # can be over written to add additional error information. -    def extend_message(message) -      message -    end +  class NoDatabaseError < StatementInvalid    end    # Raised on attempt to save stale record. Record is stale when it's being saved in another query after diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 6f54729b3c..4d63b04d9f 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -66,7 +66,7 @@ module ActiveRecord            send(lock_col + '=', previous_lock_value + 1)          end -        def update_record(attribute_names = @attributes.keys) #:nodoc: +        def _update_record(attribute_names = @attributes.keys) #:nodoc:            return super unless locking_enabled?            return 0 if attribute_names.empty? diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index c20499d081..13d7432773 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -455,6 +455,8 @@ module ActiveRecord          changed_attributes.except!(*changes.keys)          primary_key = self.class.primary_key          self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1 +      else +        true        end      end @@ -482,24 +484,24 @@ module ActiveRecord      def create_or_update        raise ReadOnlyRecord if readonly? -      result = new_record? ? create_record : update_record +      result = new_record? ? _create_record : _update_record        result != false      end      # Updates the associated record with values matching those of the instance attributes.      # Returns the number of affected rows. -    def update_record(attribute_names = @attributes.keys) +    def _update_record(attribute_names = @attributes.keys)        attributes_values = arel_attributes_with_values_for_update(attribute_names)        if attributes_values.empty?          0        else -        self.class.unscoped.update_record attributes_values, id, id_was +        self.class.unscoped._update_record attributes_values, id, id_was        end      end      # Creates a record with values matching those of the instance attributes      # and returns its id. -    def create_record(attribute_names = @attributes.keys) +    def _create_record(attribute_names = @attributes.keys)        attributes_values = arel_attributes_with_values_for_create(attribute_names)        new_id = self.class.unscoped.insert attributes_values diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 11b564f8f9..a4ceacbf44 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -116,17 +116,22 @@ module ActiveRecord      # and then establishes the connection.      initializer "active_record.initialize_database" do |app|        ActiveSupport.on_load(:active_record) do +        self.configurations = Rails.application.config.database_configuration -        class ActiveRecord::NoDatabaseError -          remove_possible_method :extend_message -          def extend_message(message) -            message << "Run `$ bin/rake db:create db:migrate` to create your database" -            message -          end -        end +        begin +          establish_connection +        rescue ActiveRecord::NoDatabaseError +          warn <<-end_warning +Oops - You have a database configured, but it doesn't exist yet! -        self.configurations = Rails.application.config.database_configuration -        establish_connection +Here's how to get started: + +  1. Configure your database in config/database.yml. +  2. Run `bin/rake db:create` to create the database. +  3. Run `bin/rake db:setup` to load your database schema. +end_warning +          raise +        end        end      end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index ff1f0f5911..6b0459ea37 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -186,7 +186,7 @@ db_namespace = namespace :db do        fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact -      (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file| +      (ENV['FIXTURES'] ? ENV['FIXTURES'].split(',') : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|          ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_file)        end      end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index bce7766501..03b5bdc46c 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -151,7 +151,7 @@ module ActiveRecord          super ||            other_aggregation.kind_of?(self.class) &&            name == other_aggregation.name && -          other_aggregation.options && +          !other_aggregation.options.nil? &&            active_record == other_aggregation.active_record        end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d95f01e89c..915035558c 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -70,7 +70,7 @@ module ActiveRecord          binds)      end -    def update_record(values, id, id_was) # :nodoc: +    def _update_record(values, id, id_was) # :nodoc:        substitutes, binds = substitute_values values        um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 6e868902c5..5e525340e0 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -231,7 +231,7 @@ module ActiveRecord      def execute_simple_calculation(operation, column_name, distinct) #:nodoc:        # Postgresql doesn't like ORDER BY when there are no GROUP BY -      relation = reorder(nil) +      relation = unscope(:order)        column_alias = column_name diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 182b9ed89c..be44fccad5 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -139,7 +139,6 @@ module ActiveRecord        def merge_single_values          relation.from_value          = values[:from] unless relation.from_value          relation.lock_value          = values[:lock] unless relation.lock_value -        relation.reverse_order_value = values[:reverse_order]          unless values[:create_with].blank?            relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with]) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index db4ad52592..8cd97a3715 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -170,7 +170,7 @@ module ActiveRecord      # Use to indicate that the given +table_names+ are referenced by an SQL string,      # and should therefore be JOINed in any query rather than loaded separately. -    # This method only works in conjuction with +includes+. +    # This method only works in conjunction with +includes+.      # See #includes for more details.      #      #   User.includes(:posts).where("posts.name = 'foo'") @@ -263,6 +263,10 @@ module ActiveRecord      #      #   User.group('name AS grouped_name, age')      #   => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>] +    # +    # Passing in an array of attributes to group by is also supported. +    #   User.select([:id, :first_name]).group(:id, :first_name).first(3) +    #   => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]      def group(*args)        check_if_method_has_arguments!(:group, args)        spawn.group!(*args) @@ -821,7 +825,9 @@ module ActiveRecord      end      def reverse_order! # :nodoc: -      self.reverse_order_value = !reverse_order_value +      orders = order_values.uniq +      orders.reject!(&:blank?) +      self.order_values = reverse_sql_order(orders)        self      end @@ -876,7 +882,6 @@ module ActiveRecord        case scope        when :order -        self.reverse_order_value = false          result = []        when :where          self.bind_values = [] @@ -1062,7 +1067,6 @@ module ActiveRecord      def build_order(arel)        orders = order_values.uniq        orders.reject!(&:blank?) -      orders = reverse_sql_order(orders) if reverse_order_value        arel.order(*orders) unless orders.empty?      end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 73ab3b39aa..18190cb535 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -94,14 +94,14 @@ module ActiveRecord            self.default_scopes += [scope]          end -        def build_default_scope # :nodoc: +        def build_default_scope(base_rel = relation) # :nodoc:            if !Base.is_a?(method(:default_scope).owner)              # The user has defined their own default scope method, so call that              evaluate_default_scope { default_scope }            elsif default_scopes.any?              evaluate_default_scope do -              default_scopes.inject(relation) do |default_scope, scope| -                default_scope.merge(unscoped { scope.call }) +              default_scopes.inject(base_rel) do |default_scope, scope| +                default_scope.merge(base_rel.scoping { scope.call })                end              end            end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 7178bed560..6c30ccab72 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -43,7 +43,7 @@ module ActiveRecord    private -    def create_record +    def _create_record        if self.record_timestamps          current_time = current_time_from_proper_timezone @@ -57,7 +57,7 @@ module ActiveRecord        super      end -    def update_record(*args) +    def _update_record(*args)        if should_record_timestamps?          current_time = current_time_from_proper_timezone diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index ec3e8f281b..17f76b63b3 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -369,7 +369,7 @@ module ActiveRecord            @new_record = restore_state[:new_record]            @destroyed  = restore_state[:destroyed]            if restore_state.has_key?(:id) -            self.id = restore_state[:id] +            write_attribute(self.class.primary_key, restore_state[:id])            else              @attributes.delete(self.class.primary_key)              @attributes_cache.delete(self.class.primary_key) diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 26dca415ff..9999624fcf 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -60,6 +60,8 @@ module ActiveRecord      # Runs all the validations within the specified context. Returns +true+ if      # no errors are found, +false+ otherwise.      # +    # Aliased as validate. +    #      # If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if      # <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.      # @@ -71,6 +73,8 @@ module ActiveRecord        errors.empty? && output      end +    alias_method :validate, :valid? +    protected      def perform_validations(options={}) # :nodoc:  | 
