diff options
Diffstat (limited to 'activerecord/lib')
37 files changed, 221 insertions, 160 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index eb21712f96..cd5fdd5964 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1176,6 +1176,12 @@ module ActiveRecord # Specify the foreign key used for the association. By default this is guessed to be the name # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ # association will use "person_id" as the default <tt>:foreign_key</tt>. + # [:foreign_type] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the polymorphic association + # specified on "as" option with a "_type" suffix. So a class that defines a + # <tt>has_many :tags, as: :taggable</tt> association will use "taggable_type" as the + # default <tt>:foreign_type</tt>. # [:primary_key] # Specify the name of the column to use as the primary key for the association. By default this is +id+. # [:dependent] @@ -1323,6 +1329,12 @@ module ActiveRecord # Specify the foreign key used for the association. By default this is guessed to be the name # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association # will use "person_id" as the default <tt>:foreign_key</tt>. + # [:foreign_type] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the polymorphic association + # specified on "as" option with a "_type" suffix. So a class that defines a + # <tt>has_one :tag, as: :taggable</tt> association will use "taggable_type" as the + # default <tt>:foreign_type</tt>. # [:primary_key] # Specify the method that returns the primary key used for the association. By default this is +id+. # [:as] diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index dcbd57e61d..0ac10531e5 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -10,8 +10,8 @@ module ActiveRecord @block = block end - def bind_value(scope, column, value, alias_tracker) - substitute = alias_tracker.connection.substitute_at(column) + def bind_value(scope, column, value, connection) + substitute = connection.substitute_at(column) scope.bind_values += [[column, @block.call(value)]] substitute end @@ -81,38 +81,38 @@ module ActiveRecord table.create_join(table, table.create_on(constraint), join_type) end - def column_for(table_name, column_name, alias_tracker) - columns = alias_tracker.connection.schema_cache.columns_hash(table_name) + def column_for(table_name, column_name, connection) + columns = connection.schema_cache.columns_hash(table_name) columns[column_name] end - def bind_value(scope, column, value, alias_tracker) - @bind_substitution.bind_value scope, column, value, alias_tracker + def bind_value(scope, column, value, connection) + @bind_substitution.bind_value scope, column, value, connection end - def bind(scope, table_name, column_name, value, tracker) - column = column_for table_name, column_name, tracker - bind_value scope, column, value, tracker + def bind(scope, table_name, column_name, value, connection) + column = column_for table_name, column_name, connection + bind_value scope, column, value, connection end - def last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass) + def last_chain_scope(scope, table, reflection, owner, connection, assoc_klass) join_keys = reflection.join_keys(assoc_klass) key = join_keys.key foreign_key = join_keys.foreign_key - bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker + bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], connection scope = scope.where(table[key].eq(bind_val)) if reflection.type value = owner.class.base_class.name - bind_val = bind scope, table.table_name, reflection.type, value, tracker + bind_val = bind scope, table.table_name, reflection.type, value, connection scope = scope.where(table[reflection.type].eq(bind_val)) else scope end end - def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection) + def next_chain_scope(scope, table, reflection, connection, assoc_klass, foreign_table, next_reflection) join_keys = reflection.join_keys(assoc_klass) key = join_keys.key foreign_key = join_keys.foreign_key @@ -121,7 +121,7 @@ module ActiveRecord if reflection.type value = next_reflection.klass.base_class.name - bind_val = bind scope, table.table_name, reflection.type, value, tracker + bind_val = bind scope, table.table_name, reflection.type, value, connection scope = scope.where(table[reflection.type].eq(bind_val)) end @@ -131,19 +131,20 @@ module ActiveRecord def add_constraints(scope, owner, assoc_klass, refl, tracker) chain = refl.chain scope_chain = refl.scope_chain + connection = tracker.connection tables = construct_tables(chain, assoc_klass, refl, tracker) owner_reflection = chain.last table = tables.last - scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass) + scope = last_chain_scope(scope, table, owner_reflection, owner, connection, assoc_klass) chain.each_with_index do |reflection, i| table, foreign_table = tables.shift, tables.first unless reflection == chain.last next_reflection = chain[i + 1] - scope = next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection) + scope = next_chain_scope(scope, table, reflection, connection, assoc_klass, foreign_table, next_reflection) end is_first_chain = i == 0 diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb index 4c8c826f76..1b87f92170 100644 --- a/activerecord/lib/active_record/associations/builder/has_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_many.rb @@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder end def valid_options - super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table] + super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type] end def self.valid_dependent_options diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index c194c8ae9a..1387717396 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder end def valid_options - valid = super + [:as] + valid = super + [:as, :foreign_type] valid += [:through, :source, :source_type] if options[:through] valid end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 9ba46ec4c7..033e71f7b9 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -69,6 +69,11 @@ module ActiveRecord end end + def attribute_changed_in_place?(attr_name) + old_value = original_raw_attribute(attr_name) + @attributes[attr_name].changed_in_place_from?(old_value) + end + private def calculate_changes_from_defaults @@ -141,15 +146,10 @@ module ActiveRecord def changed_in_place self.class.attribute_names.select do |attr_name| - changed_in_place?(attr_name) + attribute_changed_in_place?(attr_name) end end - def changed_in_place?(attr_name) - old_value = original_raw_attribute(attr_name) - @attributes[attr_name].changed_in_place_from?(old_value) - end - def original_raw_attribute(attr_name) original_raw_attributes.fetch(attr_name) do read_attribute_before_type_cast(attr_name) 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 33d9d2002c..87274dd4e1 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -12,7 +12,7 @@ module ActiveRecord if value.is_a?(Array) value.map { |v| type_cast_from_user(v) } elsif value.respond_to?(:in_time_zone) - value.in_time_zone + value.in_time_zone || super end end diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index 05138ae36d..3a76f5262d 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -76,7 +76,9 @@ module ActiveRecord unless @materialized values.each_key { |key| self[key] } types.each_key { |key| self[key] } - @materialized = true + unless frozen? + @materialized = true + end end delegate_hash end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 3a9db4a109..0fa00d03a3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -122,13 +122,13 @@ module ActiveRecord # greater than the number of threads currently waiting (that # is, don't jump ahead in line). Otherwise, return nil. # - # If +timeout+ is given, block if it there is no element + # If +timeout+ is given, block if there is no element # available, waiting up to +timeout+ seconds for an element to # become available. # # Raises: # - ConnectionTimeoutError if +timeout+ is given and no element - # becomes available after +timeout+ seconds, + # becomes available within +timeout+ seconds, def poll(timeout = nil) synchronize do if timeout @@ -151,7 +151,7 @@ module ActiveRecord end # A thread can remove an element from the queue without - # waiting if an only if the number of currently available + # waiting if and only if the number of currently available # connections is strictly greater than the number of waiting # threads. def can_remove_no_wait? 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 d2236d046b..537e21029e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -509,8 +509,8 @@ module ActiveRecord # Removes the timestamp columns (+created_at+ and +updated_at+) from the table. # # t.remove_timestamps - def remove_timestamps - @base.remove_timestamps(name) + def remove_timestamps(options = {}) + @base.remove_timestamps(name, options) end # Renames a column. 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 6eab11b88b..0834105079 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -6,8 +6,8 @@ module ActiveRecord # We can then redefine how certain data types may be handled in the schema dumper on the # Adapter level by over-writing this code inside the database specific adapters module ColumnDumper - def column_spec(column, types) - spec = prepare_column_options(column, types) + def column_spec(column) + spec = prepare_column_options(column) (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k}: ")} spec end @@ -15,13 +15,13 @@ module ActiveRecord # This can be overridden on a Adapter level basis to support other # extended datatypes (Example: Adding an array option in the # PostgreSQLAdapter) - def prepare_column_options(column, types) + def prepare_column_options(column) spec = {} spec[:name] = column.name.inspect spec[:type] = column.type.to_s spec[:null] = 'false' unless column.null - limit = column.limit || types[column.type][:limit] + limit = column.limit || native_database_types[column.type][:limit] spec[:limit] = limit.inspect if limit spec[:precision] = column.precision.inspect if column.precision spec[:scale] = column.scale.inspect if column.scale 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 aa7c406050..fd52cdf716 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -854,7 +854,7 @@ module ActiveRecord # # remove_timestamps(:suppliers) # - def remove_timestamps(table_name) + def remove_timestamps(table_name, options = {}) remove_column table_name, :updated_at remove_column table_name, :created_at end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index e0a4af5359..c4506885ed 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -263,6 +263,16 @@ module ActiveRecord # QUOTING ================================================== + # Quote date/time values for use in SQL input. Includes microseconds + # if the value is a Time responding to usec. + def quoted_date(value) #:nodoc: + if value.acts_like?(:time) && value.respond_to?(:usec) + "#{super}.#{sprintf("%06d", value.usec)}" + else + super + end + end + # Returns a bind substitution value given a bind +column+ # NOTE: The column param is currently being used by the sqlserver-adapter def substitute_at(column, _unused = 0) 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 bf16804aa0..69582ebb6f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -656,14 +656,15 @@ module ActiveRecord m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1) m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1) m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) - 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) + register_integer_type m, %r(^bigint)i, limit: 8 + register_integer_type m, %r(^int)i, limit: 4 + register_integer_type m, %r(^mediumint)i, limit: 3 + register_integer_type m, %r(^smallint)i, limit: 2 + register_integer_type m, %r(^tinyint)i, limit: 1 + 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' @@ -676,6 +677,16 @@ module ActiveRecord end end + def register_integer_type(mapping, key, options) # :nodoc: + mapping.register_type(key) do |sql_type| + if /unsigned/i =~ sql_type + Type::UnsignedInteger.new(options) + else + Type::Integer.new(options) + end + end + 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) @@ -779,7 +790,7 @@ module ActiveRecord [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)] end - def remove_timestamps_sql(table_name) + def remove_timestamps_sql(table_name, options = {}) [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index d19bd80576..75f244b3f3 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -74,14 +74,6 @@ module ActiveRecord @connection.escape(string) end - def quoted_date(value) - if value.acts_like?(:time) && value.respond_to?(:usec) - "#{super}.#{sprintf("%06d", value.usec)}" - else - super - end - end - #-- # CONNECTION MANAGEMENT ==================================== #++ diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb index cd5efe2bb8..c203e6c604 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -34,6 +34,9 @@ module ActiveRecord end def type_cast_from_user(value) + if value.is_a?(::String) + value = parse_pg_array(value) + end type_cast_array(value, :type_cast_from_user) 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 index 35e699eeda..9b3de41fab 100644 --- 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 @@ -39,14 +39,14 @@ module ActiveRecord end def register_array_type(row) - if subtype = @store.lookup(row['typelem'].to_i) - register row['oid'], OID::Array.new(subtype, row['typdelim']) + register_with_subtype(row['oid'], row['typelem'].to_i) do |subtype| + OID::Array.new(subtype, row['typdelim']) 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) + register_with_subtype(row['oid'], row['rngsubtype'].to_i) do |subtype| + OID::Range.new(subtype, row['typname'].to_sym) end end @@ -64,9 +64,13 @@ module ActiveRecord end end - def register(oid, oid_type) - oid = assert_valid_registration(oid, oid_type) - @store.register_type(oid, oid_type) + def register(oid, oid_type = nil, &block) + oid = assert_valid_registration(oid, oid_type || block) + if block_given? + @store.register_type(oid, &block) + else + @store.register_type(oid, oid_type) + end end def alias_type(oid, target) @@ -74,6 +78,14 @@ module ActiveRecord @store.alias_type(oid, target) end + def register_with_subtype(oid, target_oid) + if @store.key?(target_oid) + register(oid) do |_, *args| + yield @store.lookup(target_oid, *args) + end + end + 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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 991c41327f..607848884b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -43,16 +43,12 @@ module ActiveRecord # Quote date/time values for use in SQL input. Includes microseconds # if the value is a Time responding to usec. def quoted_date(value) #:nodoc: - result = super - if value.acts_like?(:time) && value.respond_to?(:usec) - result = "#{result}.#{sprintf("%06d", value.usec)}" - end - if value.year <= 0 bce_year = format("%04d", -value.year + 1) - result = result.sub(/^-?\d+/, bce_year) + " BC" + super.sub(/^-?\d+/, bce_year) + " BC" + else + super end - result end # Does not quote function default values for UUID columns diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3698957d8d..6ef47d8a11 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -127,7 +127,7 @@ module ActiveRecord # Adds +:array+ option to the default set provided by the # AbstractAdapter - def prepare_column_options(column, types) # :nodoc: + def prepare_column_options(column) # :nodoc: spec = super spec[:array] = 'true' if column.respond_to?(:array) && column.array spec[:default] = "\"#{column.default_function}\"" if column.default_function @@ -516,9 +516,12 @@ module ActiveRecord def extract_limit(sql_type) # :nodoc: case sql_type - when /^bigint/i; 8 - when /^smallint/i; 2 - else super + when /^bigint/i, /^int8/i + 8 + when /^smallint/i + 2 + else + super end end @@ -532,7 +535,7 @@ module ActiveRecord when 'true', 'false' default # Numeric types - when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/ + when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/ $1 # Object identifier types when /\A-?\d+\z/ diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index c3865a8fdd..0f7e0fac01 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -256,16 +256,6 @@ module ActiveRecord %Q("#{name.to_s.gsub('"', '""')}") end - # Quote date/time values for use in SQL input. Includes microseconds - # if the value is a Time responding to usec. - def quoted_date(value) #:nodoc: - if value.respond_to?(:usec) - "#{super}.#{sprintf("%06d", value.usec)}" - else - super - end - end - #-- # DATABASE STATEMENTS ====================================== #++ diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index c812e7842a..c2d5582f02 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -85,7 +85,6 @@ module ActiveRecord mattr_accessor :dump_schema_after_migration, instance_writer: false self.dump_schema_after_migration = true - # :nodoc: mattr_accessor :maintain_test_schema, instance_accessor: false def self.disable_implicit_join_references=(value) diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 73fd96f979..101889638d 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -123,16 +123,6 @@ module ActiveRecord end end - protected - - def actually_destroyed? - @_actually_destroyed - end - - def clear_destroy_state - @_actually_destroyed = nil - end - private def _create_record(*) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 99e104598b..1fc82f05d4 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -149,6 +149,8 @@ module ActiveRecord # The row is simply removed with an SQL +DELETE+ statement on the # record's primary key, and no callbacks are executed. # + # Note that this will also delete records marked as <tt>readonly?</tt>. + # # To enforce the object's +before_destroy+ and +after_destroy+ # callbacks or any <tt>:dependent</tt> association # options, use <tt>#destroy</tt>. diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 6c09456c4d..4daf2a0e2b 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -240,7 +240,7 @@ db_namespace = namespace :db do end desc 'Load a schema.rb file into the database' - task :load => [:environment, :load_config] do + task :load => [:load_config] do ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA']) end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 96520d1d49..9849e03036 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -39,9 +39,9 @@ module ActiveRecord ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection) end - # \Reflection enables interrogating of Active Record classes and objects - # about their associations and aggregations. This information can, - # for example, be used in a form builder that takes an Active Record object + # \Reflection enables the ability to examine the associations and aggregations of + # Active Record classes and objects. This information, for example, + # can be used in a form builder that takes an Active Record object # and creates input fields for all of the attributes depending on their type # and displays the associations to other objects. # @@ -277,7 +277,7 @@ module ActiveRecord def initialize(name, scope, options, active_record) super @automatic_inverse_of = nil - @type = options[:as] && "#{options[:as]}_type" + @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type") @foreign_type = options[:foreign_type] || "#{name}_type" @constructable = calculate_constructable(macro, options) @association_scope_cache = {} diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 7df50f7c97..daafb0b645 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -26,6 +26,7 @@ module ActiveRecord @values = values @offsets = {} @loaded = false + @predicate_builder = PredicateBuilder.new(klass, table) end def initialize_copy(other) @@ -632,6 +633,10 @@ module ActiveRecord "#<#{self.class.name} [#{entries.join(', ')}]>" end + protected + + attr_reader :predicate_builder + private def exec_queries diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index b069cdce7c..20d24b409b 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -40,8 +40,8 @@ module ActiveRecord # # NOTE: It's not possible to set the order. That is automatically set to # ascending on the primary key ("id ASC") to make the batch ordering - # work. This also means that this method only works with integer-based - # primary keys. + # work. This also means that this method only works when the primary key is + # orderable (e.g. an integer or string). # # NOTE: You can't set the limit either, that's used to control # the batch sizes. @@ -90,8 +90,8 @@ module ActiveRecord # # NOTE: It's not possible to set the order. That is automatically set to # ascending on the primary key ("id ASC") to make the batch ordering - # work. This also means that this method only works with integer-based - # primary keys. + # work. This also means that this method only works when the primary key is + # orderable (e.g. an integer or string). # # NOTE: You can't set the limit either, that's used to control # the batch sizes. diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index c8ebb41131..71673324eb 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -177,7 +177,7 @@ module ActiveRecord relation.select_values = column_names.map { |cn| columns_hash.key?(cn) ? arel_table[cn] : cn } - result = klass.connection.select_all(relation.arel, nil, bind_values) + result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values) result.cast_values(klass.column_types) end end @@ -317,7 +317,7 @@ module ActiveRecord relation.group_values = group relation.select_values = select_values - calculated_data = @klass.connection.select_all(relation, nil, bind_values) + calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values) if association key_ids = calculated_data.collect { |row| row[group_aliases.first] } diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 938bf55c6d..67e646bf18 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -5,7 +5,12 @@ module ActiveRecord autoload :RelationHandler, 'active_record/relation/predicate_builder/relation_handler' autoload :ArrayHandler, 'active_record/relation/predicate_builder/array_handler' - def self.resolve_column_aliases(klass, hash) + def initialize(klass, table) + @klass = klass + @table = table + end + + def resolve_column_aliases(hash) hash = hash.dup hash.keys.grep(Symbol) do |key| if klass.attribute_alias? key @@ -15,39 +20,12 @@ module ActiveRecord hash end - def self.build_from_hash(klass, attributes, default_table) - queries = [] - - attributes.each do |column, value| - table = default_table - - if value.is_a?(Hash) - if value.empty? - queries << '1=0' - else - table = Arel::Table.new(column) - association = klass._reflect_on_association(column) - - value.each do |k, v| - queries.concat expand(association && association.klass, table, k, v) - end - end - else - column = column.to_s - - if column.include?('.') - table_name, column = column.split('.', 2) - table = Arel::Table.new(table_name) - end - - queries.concat expand(klass, table, column, value) - end - end - - queries + def build_from_hash(attributes) + attributes = convert_dot_notation_to_hash(attributes.stringify_keys) + expand_from_hash(attributes) end - def self.expand(klass, table, column, value) + def expand(column, value) queries = [] # Find the foreign key when using queries such as: @@ -57,17 +35,17 @@ module ActiveRecord # PriceEstimate.where(estimate_of: treasure) if klass && reflection = klass._reflect_on_association(column) if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value) - queries << build(table[reflection.foreign_type], base_class) + queries << self.class.build(table[reflection.foreign_type], base_class.name) end column = reflection.foreign_key end - queries << build(table[column], value) + queries << self.class.build(table[column], value) queries end - def self.polymorphic_base_class_from_value(value) + def polymorphic_base_class_from_value(value) case value when Relation value.klass.base_class @@ -106,8 +84,7 @@ module ActiveRecord end register_handler(BasicObject, ->(attribute, value) { attribute.eq(value) }) - # FIXME: I think we need to deprecate this behavior - register_handler(Class, ->(attribute, value) { attribute.eq(value.name) }) + register_handler(Class, ->(attribute, value) { deprecate_class_handler; attribute.eq(value.name) }) register_handler(Base, ->(attribute, value) { attribute.eq(value.id) }) register_handler(Range, ->(attribute, value) { attribute.between(value) }) register_handler(Relation, RelationHandler.new) @@ -116,11 +93,53 @@ module ActiveRecord def self.build(attribute, value) handler_for(value).call(attribute, value) end - private_class_method :build def self.handler_for(object) @handlers.detect { |klass, _| klass === object }.last end private_class_method :handler_for + + def self.deprecate_class_handler + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing a class as a value in an Active Record query is deprecated and + will be removed. Pass a string instead. + MSG + end + + protected + + attr_reader :klass, :table + + def expand_from_hash(attributes) + return ["1=0"] if attributes.empty? + + attributes.flat_map do |key, value| + if value.is_a?(Hash) + arel_table = Arel::Table.new(key) + association = klass._reflect_on_association(key) + builder = self.class.new(association && association.klass, arel_table) + + builder.expand_from_hash(value) + else + expand(key, value) + end + end + end + + private + + def convert_dot_notation_to_hash(attributes) + dot_notation = attributes.keys.select { |s| s.include?(".") } + + dot_notation.each do |key| + table_name, column_name = key.split(".") + value = attributes.delete(key) + attributes[table_name] ||= {} + + attributes[table_name] = attributes[table_name].merge(column_name => value) + end + + attributes + end end 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 b8f3285c3e..4cba297be5 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb @@ -7,16 +7,6 @@ module ActiveRecord values = value.map { |x| x.is_a?(Base) ? x.id : x } nils, values = values.partition(&:nil?) - if values.any? { |val| val.is_a?(Array) } - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing a nested array to Active Record finder methods is - deprecated and will be removed. Flatten your array before using - it for 'IN' conditions. - MSG - - values = values.flatten - end - return attribute.in([]) if values.empty? && nils.empty? ranges, values = values.partition { |v| v.is_a?(Range) } diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index d58af01339..ef380abfe8 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -909,7 +909,7 @@ module ActiveRecord case rel when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right) - subrelation.name == target_value + subrelation.name.to_s == target_value end end @@ -947,7 +947,7 @@ module ActiveRecord when String, Array [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] when Hash - opts = PredicateBuilder.resolve_column_aliases(klass, opts) + opts = predicate_builder.resolve_column_aliases(opts) tmp_opts, bind_values = create_binds(opts) self.bind_values += bind_values @@ -955,7 +955,7 @@ module ActiveRecord attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts) add_relations_to_bind_values(attributes) - PredicateBuilder.build_from_hash(klass, attributes, table) + predicate_builder.build_from_hash(attributes) else [opts] end diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 88dfddebcc..6c103e331f 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -87,14 +87,15 @@ module ActiveRecord # { address: Address.new("123 abc st.", "chicago") } # # => "address_street='123 abc st.' and address_city='chicago'" def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name) + table = Arel::Table.new(table_name).alias(default_table_name) + predicate_builder = PredicateBuilder.new(self, table) ActiveSupport::Deprecation.warn(<<-EOWARN) sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0 EOWARN - attrs = PredicateBuilder.resolve_column_aliases self, attrs + attrs = predicate_builder.resolve_column_aliases(attrs) attrs = expand_hash_conditions_for_aggregates(attrs) - table = Arel::Table.new(table_name).alias(default_table_name) - PredicateBuilder.build_from_hash(self, attrs, table).map { |b| + predicate_builder.build_from_hash(attrs).map { |b| connection.visitor.compile b }.join(' AND ') end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 1dd2cadc01..77aa2efc47 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -44,7 +44,6 @@ module ActiveRecord def initialize(connection, options = {}) @connection = connection - @types = @connection.native_database_types @version = Migrator::current_version rescue nil @options = options end @@ -134,7 +133,7 @@ HEADER column_specs = columns.map do |column| raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type) next if column.name == pk - @connection.column_spec(column, @types) + @connection.column_spec(column) end.compact # find all migration keys used in this table diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index 3c291f28e3..919bc58ba5 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -15,11 +15,15 @@ module ActiveRecord # You can set custom coder to encode/decode your serialized attributes to/from different formats. # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+. # - # NOTE - If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for + # NOTE: If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for # the serialization provided by +store+. Simply use +store_accessor+ instead to generate # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access # using a symbol. # + # NOTE: The default validations with the exception of +uniqueness+ will work. + # For example, if you want to check for +uniqueness+ with +hstore+ you will + # need to use a custom validation to handle it. + # # Examples: # # class User < ActiveRecord::Base diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index f92e1de03b..01e8f69b02 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -265,7 +265,7 @@ module ActiveRecord def assert_valid_transaction_action(actions) if (actions - ACTIONS).any? - raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}" + raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}" end end end diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index e5acbbb6b3..250e8d5b23 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -17,6 +17,7 @@ require 'active_record/type/serialized' require 'active_record/type/string' require 'active_record/type/text' require 'active_record/type/time' +require 'active_record/type/unsigned_integer' require 'active_record/type/type_map' require 'active_record/type/hash_lookup_type_map' diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb index 750f353472..fc260a081a 100644 --- a/activerecord/lib/active_record/type/integer.rb +++ b/activerecord/lib/active_record/type/integer.rb @@ -5,7 +5,7 @@ module ActiveRecord def initialize(*) super - @range = -max_value...max_value + @range = min_value...max_value end def type @@ -46,6 +46,10 @@ module ActiveRecord limit = self.limit || 4 1 << (limit * 8 - 1) # 8 bits per byte with one bit for sign end + + def min_value + -max_value + end end end end diff --git a/activerecord/lib/active_record/type/unsigned_integer.rb b/activerecord/lib/active_record/type/unsigned_integer.rb new file mode 100644 index 0000000000..ed3e527483 --- /dev/null +++ b/activerecord/lib/active_record/type/unsigned_integer.rb @@ -0,0 +1,15 @@ +module ActiveRecord + module Type + class UnsignedInteger < Integer # :nodoc: + private + + def max_value + super * 2 + end + + def min_value + 0 + end + end + end +end |
