diff options
Diffstat (limited to 'activerecord/lib')
29 files changed, 256 insertions, 160 deletions
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 1303822868..300f67959d 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -16,6 +16,7 @@ module ActiveRecord def scope scope = klass.unscoped scope.merge! eval_scope(klass, reflection.scope) if reflection.scope + scope.extending! Array(options[:extend]) add_constraints(scope) end diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 75f72c1a46..532af3e83e 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -50,8 +50,11 @@ module ActiveRecord # Checks whether record is different to the current target, without loading it def different_target?(record) - record.nil? && owner[reflection.foreign_key] || - record && record.id != owner[reflection.foreign_key] + if record.nil? + owner[reflection.foreign_key] + else + record.id != owner[reflection.foreign_key] + end end def replace_keys(record) diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index fcdfc1e150..fdead16761 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -6,7 +6,8 @@ module ActiveRecord::Associations::Builder CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove] def valid_options - super + [:table_name, :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove] + super + [:table_name, :finder_sql, :counter_sql, :before_add, + :after_add, :before_remove, :after_remove, :extend] end attr_reader :block_extension, :extension_module diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 832b963052..5feb149946 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -273,7 +273,7 @@ module ActiveRecord if loaded? || options[:counter_sql] size.zero? else - !scope.exists? + @target.blank? && !scope.exists? end end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 33dce58982..e93e700c93 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -33,6 +33,7 @@ module ActiveRecord def initialize(klass, association) #:nodoc: @association = association super klass, klass.arel_table + self.default_scoped = true merge! association.scope(nullify: false) end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index c3266f2bb4..d1458f30ba 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -114,11 +114,7 @@ module ActiveRecord end def target_reflection_has_associated_record? - if through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank? - false - else - true - end + !(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?) end def update_through_counter?(method) diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index cbf5e734ea..82588905c6 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -76,7 +76,7 @@ module ActiveRecord else # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000) # Make several smaller queries if necessary or make one query if the adapter supports it - sliced = owner_keys.each_slice(model.connection.in_clause_length || owner_keys.size) + sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) records = sliced.map { |slice| records_for(slice).to_a }.flatten end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 47a8b576c0..7701001da9 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -39,14 +39,11 @@ module ActiveRecord unless time.acts_like?(:time) time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time end - zoned_time = time && time.in_time_zone rescue nil - rounded_time = round_usec(zoned_time) - rounded_value = round_usec(read_attribute("#{attr_name}")) - if (rounded_value != rounded_time) || (!rounded_value && original_time) - write_attribute("#{attr_name}", original_time) - #{attr_name}_will_change! - @attributes_cache["#{attr_name}"] = zoned_time - end + time = time.in_time_zone rescue nil if time + changed = read_attribute(:#{attr_name}) != time + write_attribute(:#{attr_name}, original_time) + #{attr_name}_will_change! if changed + @attributes_cache["#{attr_name}"] = time end EOV generated_attribute_methods.module_eval(method_body, __FILE__, line) @@ -62,11 +59,6 @@ module ActiveRecord [:datetime, :timestamp].include?(column.type) end end - - private - def round_usec(value) - value.change(usec: 0) if value - end end end 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 27e6e8898c..847d9da6e6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -237,7 +237,7 @@ module ActiveRecord @spec = spec @checkout_timeout = spec.config[:checkout_timeout] || 5 - @dead_connection_timeout = spec.config[:dead_connection_timeout] + @dead_connection_timeout = spec.config[:dead_connection_timeout] || 5 @reaper = Reaper.new self, spec.config[:reaping_frequency] @reaper.run @@ -517,6 +517,7 @@ module ActiveRecord def establish_connection(owner, spec) @class_to_pool.clear + raise RuntimeError, "Anonymous class is not allowed." unless owner.name owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index 9d6111b51e..fd5eaab9c9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -47,6 +47,9 @@ module ActiveRecord value.to_s when Date, DateTime, Time "'#{value.to_s(:db)}'" + when Range + # infinity dumps as Infinity, which causes uninitialized constant error + value.inspect.gsub('Infinity', '::Float::INFINITY') else value.inspect end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 4cca94e40b..3ecef96b10 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -5,6 +5,35 @@ module ActiveRecord def initialize(connection) @connection = connection + @state = TransactionState.new + end + + def state + @state + end + end + + class TransactionState + + VALID_STATES = Set.new([:committed, :rolledback, nil]) + + def initialize(state = nil) + @state = state + end + + def committed? + @state == :committed + end + + def rolledback? + @state == :rolledback + end + + def set_state(state) + if !VALID_STATES.include?(state) + raise ArgumentError, "Invalid transaction state: #{state}" + end + @state = state end end @@ -91,6 +120,7 @@ module ActiveRecord end def rollback_records + @state.set_state(:rolledback) records.uniq.each do |record| begin record.rolledback!(parent.closed?) @@ -101,6 +131,7 @@ module ActiveRecord end def commit_records + @state.set_state(:committed) records.uniq.each do |record| begin record.committed! diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index fb28ecb6cf..747331f3a1 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -126,7 +126,6 @@ module ActiveRecord when :hstore then "#{klass}.string_to_hstore(#{var_name})" when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})" when :json then "#{klass}.string_to_json(#{var_name})" - when :intrange then "#{klass}.string_to_intrange(#{var_name})" else var_name 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 a6013f754a..20a5ca2baa 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -7,13 +7,15 @@ module ActiveRecord module ConnectionHandling # Establishes a connection to the database that's used by all Active Record objects. def mysql2_connection(config) + config = config.symbolize_keys + config[:username] = 'root' if config[:username].nil? if Mysql2::Client.const_defined? :FOUND_ROWS config[:flags] = Mysql2::Client::FOUND_ROWS end - client = Mysql2::Client.new(config.symbolize_keys) + client = Mysql2::Client.new(config) options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0] ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index f7d734a2f1..3d8f0b575c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -62,6 +62,12 @@ module ActiveRecord "{#{casted_values.join(',')}}" end + def range_to_string(object) + from = object.begin.respond_to?(:infinite?) && object.begin.infinite? ? '' : object.begin + to = object.end.respond_to?(:infinite?) && object.end.infinite? ? '' : object.end + "[#{from},#{to}#{object.exclude_end? ? ')' : ']'}" + end + def string_to_json(string) if String === string ActiveSupport::JSON.decode(string) @@ -92,36 +98,6 @@ module ActiveRecord parse_pg_array(string).map{|val| oid.type_cast val} end - def string_to_intrange(string) - if string.nil? - nil - elsif "empty" == string - (nil..nil) - elsif String === string && (matches = /^(\(|\[)([0-9]+),(\s?)([0-9]+)(\)|\])$/i.match(string)) - lower_bound = ("(" == matches[1] ? (matches[2].to_i + 1) : matches[2].to_i) - upper_bound = (")" == matches[5] ? (matches[4].to_i - 1) : matches[4].to_i) - (lower_bound..upper_bound) - else - string - end - end - - def intrange_to_string(object) - if object.nil? - nil - elsif Range === object - if [object.first, object.last].all? { |el| Integer === el } - "[#{object.first.to_i},#{object.exclude_end? ? object.last.to_i : object.last.to_i + 1})" - elsif [object.first, object.last].all? { |el| NilClass === el } - "empty" - else - nil - end - else - object - end - end - private HstorePair = begin diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 02c295983f..d90b9283ef 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -78,6 +78,64 @@ module ActiveRecord end end + class Range < Type + attr_reader :subtype + def initialize(subtype) + @subtype = subtype + end + + def exctract_bounds(value) + from, to = value[1..-2].split(',') + { + from: (value[1] == ',' || from == '-infinity') ? infinity(:negative => true) : from, + to: (value[-2] == ',' || to == 'infinity') ? infinity : to, + exclude_start: (value[0] == '('), + exclude_end: (value[-1] == ')') + } + end + + def infinity(options = {}) + ::Float::INFINITY * (options[:negative] ? -1 : 1) + end + + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end + + def to_integer(value) + infinity?(value) ? value : value.to_i + end + + def type_cast(value) + return if value.nil? || value == 'empty' + return value if value.is_a?(::Range) + + extracted = exctract_bounds(value) + + case @subtype + when :date + from = ConnectionAdapters::Column.value_to_date(extracted[:from]) + from -= 1.day if extracted[:exclude_start] + to = ConnectionAdapters::Column.value_to_date(extracted[:to]) + when :decimal + from = BigDecimal.new(extracted[:from].to_s) + # FIXME: add exclude start for ::Range, same for timestamp ranges + to = BigDecimal.new(extracted[:to].to_s) + when :time + from = ConnectionAdapters::Column.string_to_time(extracted[:from]) + to = ConnectionAdapters::Column.string_to_time(extracted[:to]) + when :integer + from = to_integer(extracted[:from]) rescue value ? 1 : 0 + from -= 1 if extracted[:exclude_start] + to = to_integer(extracted[:to]) rescue value ? 1 : 0 + else + return value + end + + ::Range.new(from, to, extracted[:exclude_end]) + end + end + class Integer < Type def type_cast(value) return if value.nil? @@ -168,14 +226,6 @@ module ActiveRecord end end - class IntRange < Type - def type_cast(value) - return if value.nil? - - ConnectionAdapters::PostgreSQLColumn.string_to_intrange value - end - end - class TypeMap def initialize @mapping = {} @@ -241,6 +291,13 @@ module ActiveRecord alias_type 'int8', 'int2' alias_type 'oid', 'int2' + register_type 'daterange', OID::Range.new(:date) + register_type 'numrange', OID::Range.new(:decimal) + register_type 'tsrange', OID::Range.new(:time) + register_type 'int4range', OID::Range.new(:integer) + alias_type 'tstzrange', 'tsrange' + alias_type 'int8range', 'int4range' + register_type 'numeric', OID::Decimal.new register_type 'text', OID::Identity.new alias_type 'varchar', 'text' @@ -278,9 +335,6 @@ module ActiveRecord register_type 'json', OID::Json.new register_type 'ltree', OID::Identity.new - register_type 'int4range', OID::IntRange.new - alias_type 'int8range', 'int4range' - register_type 'cidr', OID::Cidr.new alias_type 'inet', 'cidr' end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index c2fcef94da..791b032023 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -19,6 +19,12 @@ module ActiveRecord return super unless column case value + when Range + if /range$/ =~ column.sql_type + "'#{PostgreSQLColumn.range_to_string(value)}'::#{column.sql_type}" + else + super + end when Array if column.array "'#{PostgreSQLColumn.array_to_string(value, column, self)}'" @@ -31,11 +37,6 @@ module ActiveRecord when 'json' then super(PostgreSQLColumn.json_to_string(value), column) else super end - when Range - case column.sql_type - when 'int4range', 'int8range' then super(PostgreSQLColumn.intrange_to_string(value), column) - else super - end when IPAddr case column.sql_type when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column) @@ -74,6 +75,9 @@ module ActiveRecord return super(value, column) unless column case value + when Range + return super(value, column) unless /range$/ =~ column.sql_type + PostgreSQLColumn.range_to_string(value) when NilClass if column.array && array_member 'NULL' @@ -94,11 +98,6 @@ module ActiveRecord when 'json' then PostgreSQLColumn.json_to_string(value) else super(value, column) end - when Range - case column.sql_type - when 'int4range', 'int8range' then PostgreSQLColumn.intrange_to_string(value) - else super(value, column) - end when IPAddr return super(value, column) unless ['inet','cidr'].include? column.sql_type PostgreSQLColumn.cidr_to_string(value) 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 e10b562fa4..73ca2c8e61 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -18,9 +18,9 @@ module ActiveRecord # create_database config[:database], config # create_database 'foo_development', encoding: 'unicode' def create_database(name, options = {}) - options = options.reverse_merge(:encoding => "utf8") + options = { encoding: 'utf8' }.merge!(options.symbolize_keys) - option_string = options.symbolize_keys.sum do |key, value| + option_string = options.sum do |key, value| case key when :owner " OWNER = \"#{value}\"" @@ -417,14 +417,6 @@ module ActiveRecord when 0..6; "timestamp(#{precision})" else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6") end - when 'intrange' - return 'int4range' unless limit - - case limit - when 1..4; 'int4range' - when 5..8; 'int8range' - else raise(ActiveRecordError, "No range type has byte size #{limit}. Use a numeric with precision 0 instead.") - end else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index b1b0467379..209553b26e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -77,6 +77,8 @@ module ActiveRecord return default unless default case default + when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m + $1 # Numeric types when /\A\(?(-?\d+(\.\d*)?\)?)\z/ $1 @@ -117,9 +119,6 @@ module ActiveRecord # JSON when /\A'(.*)'::json\z/ $1 - # int4range, int8range - when /\A'(.*)'::int(4|8)range\z/ - $1 # Object identifier types when /\A-?\d+\z/ $1 @@ -220,12 +219,11 @@ module ActiveRecord # JSON type when 'json' :json - # int4range, int8range types - when 'int4range', 'int8range' - :intrange # 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 @@ -276,6 +274,30 @@ module ActiveRecord column(args[0], 'tsvector', options) end + def int4range(name, options = {}) + column(name, 'int4range', options) + end + + def int8range(name, options = {}) + column(name, 'int8range', options) + end + + def tsrange(name, options = {}) + column(name, 'tsrange', options) + end + + def tstzrange(name, options = {}) + column(name, 'tstzrange', options) + end + + def numrange(name, options = {}) + column(name, 'numrange', options) + end + + def daterange(name, options = {}) + column(name, 'daterange', options) + end + def hstore(name, options = {}) column(name, 'hstore', options) end @@ -304,10 +326,6 @@ module ActiveRecord column(name, 'json', options) end - def intrange(name, options = {}) - column(name, 'intrange', options) - end - def column(name, type = nil, options = {}) super column = self[name] @@ -339,6 +357,12 @@ module ActiveRecord timestamp: { name: "timestamp" }, time: { name: "time" }, date: { name: "date" }, + daterange: { name: "daterange" }, + numrange: { name: "numrange" }, + tsrange: { name: "tsrange" }, + tstzrange: { name: "tstzrange" }, + int4range: { name: "int4range" }, + int8range: { name: "int8range" }, binary: { name: "bytea" }, boolean: { name: "boolean" }, xml: { name: "xml" }, @@ -349,7 +373,6 @@ module ActiveRecord macaddr: { name: "macaddr" }, uuid: { name: "uuid" }, json: { name: "json" }, - intrange: { name: "int4range" }, ltree: { name: "ltree" } } @@ -552,6 +575,11 @@ module ActiveRecord true end + # Range datatypes weren't introduced until PostgreSQL 9.2 + def supports_ranges? + postgresql_version >= 90200 + end + # Returns the configured supported identifier length supported by PostgreSQL def table_alias_length @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 94c6684700..63a1197a56 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -365,17 +365,18 @@ module ActiveRecord pk = self.class.primary_key @attributes[pk] = nil unless @attributes.key?(pk) - @aggregation_cache = {} - @association_cache = {} - @attributes_cache = {} - @previously_changed = {} - @changed_attributes = {} - @readonly = false - @destroyed = false - @marked_for_destruction = false - @new_record = true - @txn = nil + @aggregation_cache = {} + @association_cache = {} + @attributes_cache = {} + @previously_changed = {} + @changed_attributes = {} + @readonly = false + @destroyed = false + @marked_for_destruction = false + @new_record = true + @txn = nil @_start_transaction_state = {} + @transaction = nil end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 3011f959a5..1b2aa9349e 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -236,26 +236,26 @@ module ActiveRecord alias update_attributes! update! - # Updates a single attribute of an object, without having to explicitly call save on that object. - # - # * Validation is skipped. - # * Callbacks are skipped. - # * updated_at/updated_on column is not updated if that column is available. - # - # Raises an +ActiveRecordError+ when called on new objects, or when the +name+ - # attribute is marked as readonly. + # Equivalent to <code>update_columns(name => value)</code>. def update_column(name, value) update_columns(name => value) end - # Updates the attributes from the passed-in hash, without having to explicitly call save on that object. + # Updates the attributes directly in the database issuing an UPDATE SQL + # statement and sets them in the receiver: # - # * Validation is skipped. + # user.update_columns(last_request_at: Time.current) + # + # This is the fastest way to update attributes because it goes straight to + # the database, but take into account that in consequence the regular update + # procedures are totally bypassed. In particular: + # + # * Validations are skipped. # * Callbacks are skipped. - # * updated_at/updated_on column is not updated if that column is available. + # * +updated_at+/+updated_on+ are not updated. # - # Raises an +ActiveRecordError+ when called on new objects, or when at least - # one of the attributes is marked as readonly. + # This method raises an +ActiveRecord::ActiveRecordError+ when called on new + # objects, or when at least one of the attributes is marked as readonly. def update_columns(attributes) raise ActiveRecordError, "can not update on a new record object" unless persisted? diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 6ec5cf3e18..0053530f73 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -276,7 +276,7 @@ module ActiveRecord stmt.table(table) stmt.key = table[primary_key] - if joins_values.any? + if with_default_scope.joins_values.any? @klass.connection.join_to_update(stmt, arel) else stmt.take(arel.limit) @@ -401,7 +401,7 @@ module ActiveRecord stmt = Arel::DeleteManager.new(arel.engine) stmt.from(table) - if joins_values.any? + if with_default_scope.joins_values.any? @klass.connection.join_to_delete(stmt, arel, table[primary_key]) else stmt.wheres = arel.constraints @@ -474,16 +474,16 @@ module ActiveRecord # Returns sql statement for the relation. # - # Users.where(name: 'Oscar').to_sql + # User.where(name: 'Oscar').to_sql # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar' def to_sql @to_sql ||= klass.connection.to_sql(arel, bind_values.dup) end - # Returns a hash of where conditions + # Returns a hash of where conditions. # - # Users.where(name: 'Oscar').where_values_hash - # # => {name: "oscar"} + # User.where(name: 'Oscar').where_values_hash + # # => {name: "Oscar"} def where_values_hash equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node| node.left.relation.name == table_name diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 3f154bd1cc..f10c290755 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -192,7 +192,8 @@ module ActiveRecord def perform_calculation(operation, column_name, options = {}) operation = operation.to_s.downcase - distinct = options[:distinct] + # If #count is used in conjuction with #uniq it is considered distinct. (eg. relation.uniq.count) + distinct = options[:distinct] || self.uniq_value if operation == "count" column_name ||= (select_for_count || :all) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 83074e72c1..537ebbef28 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -7,12 +7,12 @@ module ActiveRecord table = default_table if value.is_a?(Hash) - table = Arel::Table.new(column, default_table.engine) - association = klass.reflect_on_association(column.to_sym) - if value.empty? - queries.concat ['1 = 2'] + queries << '1 = 2' else + table = Arel::Table.new(column, default_table.engine) + association = klass.reflect_on_association(column.to_sym) + value.each do |k, v| queries.concat expand(association && association.klass, table, k, v) end @@ -58,7 +58,7 @@ module ActiveRecord key else key = key.to_s - key.split('.').first.to_sym if key.include?('.') + key.split('.').first if key.include?('.') end end.compact end @@ -66,7 +66,7 @@ module ActiveRecord private def self.build(attribute, value) case value - when Array, ActiveRecord::Associations::CollectionProxy + when Array values = value.to_a.map {|x| x.is_a?(Base) ? x.id : x} 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 46c0d6206f..42849d6bc9 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -764,6 +764,11 @@ module ActiveRecord [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] when Hash attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts) + + attributes.values.grep(ActiveRecord::Relation) do |rel| + self.bind_values += rel.bind_values + end + PredicateBuilder.build_from_hash(klass, attributes, table) else [opts] diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index fda51b3d76..67c7e714e6 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -1,5 +1,7 @@ module ActiveRecord module Tasks # :nodoc: + class DatabaseAlreadyExists < StandardError; end # :nodoc: + module DatabaseTasks # :nodoc: extend self @@ -32,6 +34,8 @@ module ActiveRecord def create(*arguments) configuration = arguments.first class_for_adapter(configuration['adapter']).new(*arguments).create + rescue DatabaseAlreadyExists + $stderr.puts "#{configuration['database']} already exists" rescue Exception => error $stderr.puts error, *(error.backtrace) $stderr.puts "Couldn't create database for #{configuration.inspect}" diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index 3d27c97254..17378969a5 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -1,7 +1,6 @@ module ActiveRecord module Tasks # :nodoc: class MySQLDatabaseTasks # :nodoc: - DEFAULT_CHARSET = ENV['CHARSET'] || 'utf8' DEFAULT_COLLATION = ENV['COLLATION'] || 'utf8_unicode_ci' ACCESS_DENIED_ERROR = 1045 @@ -16,18 +15,23 @@ module ActiveRecord establish_connection configuration_without_database connection.create_database configuration['database'], creation_options establish_connection configuration + rescue ActiveRecord::StatementInvalid => error + if /database exists/ === error.message + raise DatabaseAlreadyExists + else + raise + end rescue error_class => error - raise error unless error.errno == ACCESS_DENIED_ERROR - - $stdout.print error.error - establish_connection root_configuration_without_database - connection.create_database configuration['database'], creation_options - connection.execute grant_statement.gsub(/\s+/, ' ').strip - establish_connection configuration - rescue error_class => error - $stderr.puts error.error - $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}" - $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['encoding'] + if error.respond_to?(:errno) && error.errno == ACCESS_DENIED_ERROR + $stdout.print error.error + establish_connection root_configuration_without_database + connection.create_database configuration['database'], creation_options + connection.execute grant_statement.gsub(/\s+/, ' ').strip + establish_connection configuration + else + $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}" + $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['encoding'] + end end def drop @@ -87,14 +91,15 @@ module ActiveRecord end def error_class - case configuration['adapter'] - when /jdbc/ + if configuration['adapter'] =~ /jdbc/ require 'active_record/railties/jdbcmysql_error' ArJdbcMySQL::Error - when /mysql2/ + elsif defined?(Mysql2) Mysql2::Error - else + elsif defined?(Mysql) Mysql::Error + else + StandardError end end @@ -128,7 +133,6 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION; end args end - end end end diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index ea5cb888fb..0b1b030516 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -3,7 +3,6 @@ require 'shellwords' module ActiveRecord module Tasks # :nodoc: class PostgreSQLDatabaseTasks # :nodoc: - DEFAULT_ENCODING = ENV['CHARSET'] || 'utf8' delegate :connection, :establish_connection, :clear_active_connections!, @@ -18,6 +17,12 @@ module ActiveRecord connection.create_database configuration['database'], configuration.merge('encoding' => encoding) establish_connection configuration + rescue ActiveRecord::StatementInvalid => error + if /database .* already exists/ === error.message + raise DatabaseAlreadyExists + else + raise + end end def drop diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb index da01058a82..de8b16627e 100644 --- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb @@ -1,7 +1,6 @@ module ActiveRecord module Tasks # :nodoc: class SQLiteDatabaseTasks # :nodoc: - delegate :connection, :establish_connection, to: ActiveRecord::Base def initialize(configuration, root = Rails.root) @@ -9,10 +8,7 @@ module ActiveRecord end def create - if File.exist?(configuration['database']) - $stderr.puts "#{configuration['database']} already exists" - return - end + raise DatabaseAlreadyExists if File.exist?(configuration['database']) establish_connection configuration connection diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index c035ad43a2..e9142481a3 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -60,16 +60,17 @@ module ActiveRecord self.clear_log - self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] + self.ignored_sql = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] # FIXME: this needs to be refactored so specific database can add their own # ignored SQL, or better yet, use a different notification for the queries # instead examining the SQL content. oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im] mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/] - postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im] + postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i] + sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im] - [oracle_ignored, mysql_ignored, postgresql_ignored].each do |db_ignored_sql| + [oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql| ignored_sql.concat db_ignored_sql end |