diff options
Diffstat (limited to 'activerecord')
19 files changed, 314 insertions, 246 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index a0ec552eb6..25b905891f 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,9 +1,39 @@ * Save `has_one` association when primary key is manually set. - Fixes #12302 + Fixes #12302. *Lauro Caetano* +* Allow any version of BCrypt when using `has_secury_password`. + + *Mike Perham* + +* Sub-query generated for `Relation` passed as array condition did not take in account + bind values and have invalid syntax. + + Generate sub-query with inline bind values. + + Fixes #12586. + + *Paul Nikitochkin* + +* Fix a bug where rake db:structure:load crashed when the path contained + spaces. + + *Kevin Mook* + +* `ActiveRecord::QueryMethods#unscope` unscopes negative equality + + Allows you to call `#unscope` on a relation with negative equality + operators, i.e. `Arel::Nodes::NotIn` and `Arel::Nodes::NotEqual` that have + been generated through the use of `where.not`. + + *Eric Hankins* + +* Raise an exception when model without primary key calls `.find_with_ids`. + + *Shimpei Makimoto* + * Make `Relation#empty?` use `exists?` instead of `count`. *Szymon Nowak* @@ -266,7 +296,7 @@ * Fixes bug when using includes combined with select, the select statement was overwritten. - Fixes #11773 + Fixes #11773. *Edo Balvers* diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 0b6cdf5217..2e70a07962 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -281,7 +281,7 @@ module ActiveRecord # so method calls may be chained. # # class Person < ActiveRecord::Base - # pets :has_many + # has_many :pets # end # # person.pets.size # => 0 diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 6e08f67286..c3ac0680ea 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -4,6 +4,47 @@ module ActiveRecord autoload :JoinBase, 'active_record/associations/join_dependency/join_base' autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association' + class Aliases # :nodoc: + def initialize(tables) + @tables = tables + @alias_cache = tables.each_with_object({}) { |table,h| + h[table.node] = table.columns.each_with_object({}) { |column,i| + i[column.name] = column.alias + } + } + @name_and_alias_cache = tables.each_with_object({}) { |table,h| + h[table.node] = table.columns.map { |column| + [column.name, column.alias] + } + } + end + + def columns + @tables.flat_map { |t| t.column_aliases } + end + + # An array of [column_name, alias] pairs for the table + def column_aliases(node) + @name_and_alias_cache[node] + end + + def column_alias(node, column) + @alias_cache[node][column] + end + + class Table < Struct.new(:node, :columns) + def table + Arel::Nodes::TableAlias.new node.table, node.aliased_table_name + end + + def column_aliases + t = table + columns.map { |column| t[column.name].as Arel.sql column.alias } + end + end + Column = Struct.new(:name, :alias) + end + attr_reader :alias_tracker, :base_klass, :join_root def self.make_tree(associations) @@ -52,101 +93,117 @@ module ActiveRecord # joins #=> [] # def initialize(base, associations, joins) - @base_klass = base - @table_joins = joins - @join_root = JoinBase.new(base) @alias_tracker = AliasTracker.new(base.connection, joins) @alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1 tree = self.class.make_tree associations - build tree, @join_root, Arel::InnerJoin + @join_root = JoinBase.new base, build(tree, base) + @join_root.children.each { |child| construct_tables! @join_root, child } end def reflections join_root.drop(1).map!(&:reflection) end - def merge_outer_joins!(other) - left = join_root - right = other.join_root + def join_constraints(outer_joins) + joins = join_root.children.flat_map { |child| + make_inner_joins join_root, child + } - if left.match? right - merge_node left, right - else - # If the roots aren't the same, then deep copy the RHS to the LHS - left.children.concat right.children.map { |node| - deep_copy left, node - } - end - end - - def join_constraints - join_root.children.flat_map { |c| c.flat_map(&:join_constraints) } + joins.concat outer_joins.flat_map { |oj| + if join_root.match? oj.join_root + walk join_root, oj.join_root + else + oj.join_root.children.flat_map { |child| + make_outer_joins join_root, child + } + end + } end - def columns - join_root.collect { |join_part| - table = join_part.aliased_table - join_part.column_names_with_alias.collect{ |column_name, aliased_name| - table[column_name].as Arel.sql(aliased_name) + def aliases + Aliases.new join_root.each_with_index.map { |join_part,i| + columns = join_part.column_names.each_with_index.map { |column_name,j| + Aliases::Column.new column_name, "t#{i}_r#{j}" } - }.flatten + Aliases::Table.new(join_part, columns) + } end - def instantiate(result_set) - primary_key = join_root.aliased_primary_key - parents = {} - + def instantiate(result_set, aliases) + primary_key = aliases.column_alias(join_root, join_root.primary_key) type_caster = result_set.column_type primary_key - records = result_set.map { |row_hash| + seen = Hash.new { |h,parent_klass| + h[parent_klass] = Hash.new { |i,parent_id| + i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} } + } + } + + model_cache = Hash.new { |h,klass| h[klass] = {} } + parents = model_cache[join_root] + column_aliases = aliases.column_aliases join_root + + result_set.each { |row_hash| primary_id = type_caster.type_cast row_hash[primary_key] - parent = parents[primary_id] ||= join_root.instantiate(row_hash) - construct(parent, join_root, row_hash, result_set) - parent - }.uniq + parent = parents[primary_id] ||= join_root.instantiate(row_hash, column_aliases) + construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases) + } - remove_duplicate_results!(base_klass, records, join_root.children) - records + parents.values end private - def merge_node(left, right) - intersection, missing = right.children.map { |node1| - [left.children.find { |node2| node1.match? node2 }, node1] - }.partition(&:first) + def make_constraints(parent, child, tables, join_type) + chain = child.reflection.chain + foreign_table = parent.table + foreign_klass = parent.base_klass + child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain) + end + + def make_outer_joins(parent, child) + tables = table_aliases_for(parent, child) + join_type = Arel::OuterJoin + joins = make_constraints parent, child, tables, join_type - intersection.each { |l,r| merge_node l, r } + joins.concat child.children.flat_map { |c| make_outer_joins(child, c) } + end + + def make_inner_joins(parent, child) + tables = child.tables + join_type = Arel::InnerJoin + joins = make_constraints parent, child, tables, join_type - left.children.concat missing.map { |_,node| deep_copy left, node } + joins.concat child.children.flat_map { |c| make_inner_joins(child, c) } end - def deep_copy(parent, node) - dup = build_join_association(node.reflection, parent, Arel::OuterJoin) - dup.children.concat node.children.map { |n| deep_copy dup, n } - dup + def table_aliases_for(parent, node) + node.reflection.chain.map { |reflection| + alias_tracker.aliased_table_for( + reflection.table_name, + table_alias_for(reflection, parent, reflection != node.reflection) + ) + } end - def remove_duplicate_results!(base, records, associations) - associations.each do |node| - reflection = base.reflect_on_association(node.name) - remove_uniq_by_reflection(reflection, records) + def construct_tables!(parent, node) + node.tables = table_aliases_for(parent, node) + node.children.each { |child| construct_tables! node, child } + end - parent_records = [] - records.each do |record| - if descendant = record.send(reflection.name) - if reflection.collection? - parent_records.concat descendant.target.uniq - else - parent_records << descendant - end - end - end + def table_alias_for(reflection, parent, join) + name = "#{reflection.plural_name}_#{parent.table_name}" + name << "_join" if join + name + end - unless parent_records.empty? - remove_duplicate_results!(reflection.klass, parent_records, node.children) - end - end + def walk(left, right) + intersection, missing = right.children.map { |node1| + [left.children.find { |node2| node1.match? node2 }, node1] + }.partition(&:first) + + ojs = missing.flat_map { |_,n| make_outer_joins left, n } + intersection.flat_map { |l,r| walk l, r }.concat ojs end def find_reflection(klass, name) @@ -154,75 +211,63 @@ module ActiveRecord raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?" end - def build(associations, parent, join_type) - associations.each do |name, right| - reflection = find_reflection parent.base_klass, name - join_association = build_join_association reflection, parent, join_type - parent.children << join_association - build right, join_association, join_type - end - end + def build(associations, base_klass) + associations.map do |name, right| + reflection = find_reflection base_klass, name + reflection.check_validity! - def build_scalar(reflection, parent, join_type) - join_association = build_join_association(reflection, parent, join_type) - parent.children << join_association - end + if reflection.options[:polymorphic] + raise EagerLoadPolymorphicError.new(reflection) + end - def remove_uniq_by_reflection(reflection, records) - if reflection && reflection.collection? - records.each { |record| record.send(reflection.name).target.uniq! } + JoinAssociation.new reflection, build(right, reflection.klass) end end - def build_join_association(reflection, parent, join_type) - reflection.check_validity! + def construct(ar_parent, parent, row, rs, seen, model_cache, aliases) + primary_id = ar_parent.id - if reflection.options[:polymorphic] - raise EagerLoadPolymorphicError.new(reflection) - end - - JoinAssociation.new(reflection, join_root.to_a.length, parent, join_type, alias_tracker) - end - - def construct(ar_parent, parent, row, rs) parent.children.each do |node| - association = construct_association(ar_parent, parent, node, row, rs) - construct(association, node, row, rs) if association - end - end + if node.reflection.collection? + other = ar_parent.association(node.reflection.name) + other.loaded! + else + if ar_parent.association_cache.key?(node.reflection.name) + model = ar_parent.association(node.reflection.name).target + construct(model, node, row, rs, seen, model_cache, aliases) + next + end + end - def construct_association(record, parent, join_part, row, rs) - caster = rs.column_type(parent.aliased_primary_key) - row_id = caster.type_cast row[parent.aliased_primary_key] + key = aliases.column_alias(node, node.primary_key) + id = row[key] + next if id.nil? - return if record.id != row_id + model = seen[parent.base_klass][primary_id][node.base_klass][id] - macro = join_part.reflection.macro - if macro == :has_one - return record.association(join_part.reflection.name).target if record.association_cache.key?(join_part.reflection.name) - association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil? - set_target_and_inverse(join_part, association, record) - else - association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil? - case macro - when :has_many - other = record.association(join_part.reflection.name) - other.loaded! - other.target.push(association) if association - other.set_inverse_instance(association) - when :belongs_to - set_target_and_inverse(join_part, association, record) + if model + construct(model, node, row, rs, seen, model_cache, aliases) else - raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}" + model = construct_model(ar_parent, node, row, model_cache, id, aliases) + seen[parent.base_klass][primary_id][node.base_klass][id] = model + construct(model, node, row, rs, seen, model_cache, aliases) end end - association end - def set_target_and_inverse(join_part, association, record) - other = record.association(join_part.reflection.name) - other.target = association - other.set_inverse_instance(association) + def construct_model(record, node, row, model_cache, id, aliases) + model = model_cache[node][id] ||= node.instantiate(row, + aliases.column_aliases(node)) + other = record.association(node.reflection.name) + + if node.reflection.collection? + other.target.push(model) + else + other.target = model + end + + other.set_inverse_instance(model) + model end end 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 3af613d2d1..191d430636 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -4,48 +4,28 @@ module ActiveRecord module Associations class JoinDependency # :nodoc: class JoinAssociation < JoinPart # :nodoc: - include JoinHelper - # The reflection of the association represented attr_reader :reflection - # What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin - attr_accessor :join_type - - # These implement abstract methods from the superclass - attr_reader :aliased_prefix - - attr_reader :tables - attr_reader :alias_tracker + attr_accessor :tables - delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection - - def initialize(reflection, index, parent, join_type, alias_tracker) - super(reflection.klass, parent) + def initialize(reflection, children) + super(reflection.klass, children) @reflection = reflection - @alias_tracker = alias_tracker - @join_type = join_type - @aliased_prefix = "t#{ index }" - @tables = construct_tables.reverse + @tables = nil end - def parent_table_name; parent.table_name; end - alias :alias_suffix :parent_table_name - def match?(other) return true if self == other super && reflection == other.reflection end - def join_constraints + def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain) joins = [] - tables = @tables.dup - - foreign_table = parent.table - foreign_klass = parent.base_klass + tables = tables.reverse - scope_chain_iter = reflection.scope_chain.reverse_each + scope_chain_iter = scope_chain.reverse_each # The chain starts with the target table, but we want to end with it here (makes # more sense in this context), so we reverse @@ -68,7 +48,7 @@ module ActiveRecord if item.is_a?(Relation) item else - ActiveRecord::Relation.create(klass, table).instance_exec(self, &item) + ActiveRecord::Relation.create(klass, table).instance_exec(node, &item) end end @@ -88,7 +68,7 @@ module ActiveRecord constraint = constraint.and rel.arel.constraints end - joins << join(table, constraint) + joins << table.create_join(table, table.create_on(constraint), join_type) # The current table in this iteration becomes the foreign table in the next foreign_table, foreign_klass = table, klass @@ -126,7 +106,7 @@ module ActiveRecord end def table - tables.last + tables.first end def aliased_table_name diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb index adc9f63aec..3a26c25737 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb @@ -4,19 +4,11 @@ module ActiveRecord module Associations class JoinDependency # :nodoc: class JoinBase < JoinPart # :nodoc: - def initialize(klass) - super(klass, nil) - end - def match?(other) return true if self == other super && base_klass == other.base_klass end - def aliased_prefix - "t0" - end - def table base_klass.arel_table end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb index e6da4d3c9e..91e1c6a9d7 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb @@ -10,10 +10,6 @@ module ActiveRecord class JoinPart # :nodoc: include Enumerable - # A JoinBase instance representing the active record we are joining onto. - # (So in Author.has_many :posts, the Author would be that base record.) - attr_reader :parent - # The Active Record class which this join part is associated 'about'; for a JoinBase # this is the actual base model, for a JoinAssociation this is the target model of the # association. @@ -21,12 +17,10 @@ module ActiveRecord delegate :table_name, :column_names, :primary_key, :to => :base_klass - def initialize(base_klass, parent) + def initialize(base_klass, children) @base_klass = base_klass - @parent = parent - @cached_record = {} @column_names_with_alias = nil - @children = [] + @children = children end def name @@ -42,43 +36,17 @@ module ActiveRecord children.each { |child| child.each(&block) } end - def aliased_table - Arel::Nodes::TableAlias.new table, aliased_table_name - end - # An Arel::Table for the active_record def table raise NotImplementedError end - # The prefix to be used when aliasing columns in the active_record's table - def aliased_prefix - raise NotImplementedError - end - # The alias for the active_record's table def aliased_table_name raise NotImplementedError end - # The alias for the primary key of the active_record's table - def aliased_primary_key - "#{aliased_prefix}_r0" - end - - # An array of [column_name, alias] pairs for the table - def column_names_with_alias - unless @column_names_with_alias - @column_names_with_alias = [] - - column_names.each_with_index do |column_name, i| - @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"] - end - end - @column_names_with_alias - end - - def extract_record(row) + def extract_record(row, column_names_with_alias) # This code is performance critical as it is called per row. # see: https://github.com/rails/rails/pull/12185 hash = {} @@ -95,12 +63,8 @@ module ActiveRecord hash end - def record_id(row) - row[aliased_primary_key] - end - - def instantiate(row) - @cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row)) + def instantiate(row, aliases) + base_klass.instantiate(extract_record(row, aliases)) end end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index bf270c1829..43419efc75 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -161,12 +161,9 @@ module ActiveRecord # If we haven't generated any methods yet, generate them, then # see if we've created the method we're looking for. def method_missing(method, *args, &block) # :nodoc: - if self.class.define_attribute_methods - if respond_to_without_attributes?(method) - send(method, *args, &block) - else - super - end + self.class.define_attribute_methods + if respond_to_without_attributes?(method) + send(method, *args, &block) 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 771a150eae..c1cc905606 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -151,7 +151,7 @@ module ActiveRecord private def has_default_function?(default_value, default) - !default_value && (%r{\w+(.*)} === default) + !default_value && (%r{\w+\(.*\)} === default) end def extract_limit(sql_type) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 81548117b0..60f2726a6e 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -506,8 +506,7 @@ module ActiveRecord visitor = connection.visitor if eager_loading? - join_dependency = construct_join_dependency - relation = construct_relation_for_association_find(join_dependency) + find_with_associations { |rel| relation = rel } end ast = relation.arel.ast diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index fe75a32545..3a02bf90e9 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -201,7 +201,7 @@ module ActiveRecord conditions = conditions.id if Base === conditions return false if !conditions - relation = construct_relation_for_association_find(construct_join_dependency) + relation = apply_join_dependency(self, construct_join_dependency) return false if ActiveRecord::NullRelation === relation relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1) @@ -242,17 +242,25 @@ module ActiveRecord def find_with_associations join_dependency = construct_join_dependency - relation = construct_relation_for_association_find(join_dependency) - if ActiveRecord::NullRelation === relation - [] + + aliases = join_dependency.aliases + relation = select aliases.columns + relation = apply_join_dependency(relation, join_dependency) + + if block_given? + yield relation else - rows = connection.select_all(relation.arel, 'SQL', relation.bind_values.dup) - join_dependency.instantiate(rows) + if ActiveRecord::NullRelation === relation + [] + else + rows = connection.select_all(relation.arel, 'SQL', relation.bind_values.dup) + join_dependency.instantiate(rows, aliases) + end end end def construct_join_dependency(joins = []) - including = (eager_load_values + includes_values).uniq + including = eager_load_values + includes_values ActiveRecord::Associations::JoinDependency.new(@klass, including, joins) end @@ -260,11 +268,6 @@ module ActiveRecord apply_join_dependency(self, construct_join_dependency(arel.froms.first)) end - def construct_relation_for_association_find(join_dependency) - relation = select(join_dependency.columns) - apply_join_dependency(relation, join_dependency) - end - def apply_join_dependency(relation, join_dependency) relation = relation.except(:includes, :eager_load, :preload) relation = relation.joins join_dependency @@ -297,6 +300,8 @@ module ActiveRecord protected def find_with_ids(*ids) + raise UnknownPrimaryKey.new(@klass) if primary_key.nil? + expects_array = ids.first.kind_of?(Array) return ids.first if expects_array && ids.first.empty? diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 9c9690215a..bffd8b5d0f 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -856,7 +856,7 @@ module ActiveRecord where_values.reject! do |rel| case rel - when Arel::Nodes::In, Arel::Nodes::Equality + when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right) subrelation.name.to_sym == target_value_sym else @@ -894,6 +894,13 @@ module ActiveRecord def build_where(opts, other = []) case opts when String, Array + #TODO: Remove duplication with: /activerecord/lib/active_record/sanitization.rb:113 + values = Hash === other.first ? other.first.values : other + + values.grep(ActiveRecord::Relation) do |rel| + self.bind_values += rel.bind_values + end + [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] when Hash opts = PredicateBuilder.resolve_column_aliases(klass, opts) @@ -950,11 +957,7 @@ module ActiveRecord join_list ) - stashed_association_joins.each do |dep| - join_dependency.merge_outer_joins! dep - end - - joins = join_dependency.join_constraints + joins = join_dependency.join_constraints stashed_association_joins joins.each { |join| manager.from(join) } diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index 4413330fab..3d02ee07d0 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -59,7 +59,7 @@ module ActiveRecord def structure_load(filename) set_psql_env - Kernel.system("psql -q -f #{filename} #{configuration['database']}") + Kernel.system("psql -q -f #{Shellwords.escape(filename)} #{configuration['database']}") end private diff --git a/activerecord/test/cases/associations/join_dependency_test.rb b/activerecord/test/cases/associations/join_dependency_test.rb deleted file mode 100644 index 08c166dc33..0000000000 --- a/activerecord/test/cases/associations/join_dependency_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "cases/helper" -require 'models/edge' - -class JoinDependencyTest < ActiveRecord::TestCase - def test_column_names_with_alias_handles_nil_primary_key - assert_equal Edge.column_names, ActiveRecord::Associations::JoinDependency::JoinBase.new(Edge).column_names_with_alias.map(&:first) - end -end
\ No newline at end of file diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 1b9ef14ec9..8c1974c77b 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -11,6 +11,7 @@ require 'models/project' require 'models/developer' require 'models/customer' require 'models/toy' +require 'models/matey' class FinderTest < ActiveRecord::TestCase fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations @@ -860,6 +861,12 @@ class FinderTest < ActiveRecord::TestCase Toy.reset_primary_key end + def test_find_without_primary_key + assert_raises(ActiveRecord::UnknownPrimaryKey) do + Matey.find(1) + end + end + def test_finder_with_offset_string assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.all.merge!(:offset => "3").to_a } end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 860bd424b7..c9c7ac04b3 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -639,6 +639,31 @@ class RelationTest < ActiveRecord::TestCase relation = Author.where('id in (?)', Author.where(id: david).select(:id)) assert_equal [david], relation.to_a } + + assert_queries(1) do + relation = Author.where('id in (:author_ids)', author_ids: Author.where(id: david).select(:id)) + assert_equal [david], relation.to_a + end + end + + def test_find_all_using_where_with_relation_with_bound_values + david = authors(:david) + davids_posts = david.posts.order(:id).to_a + + assert_queries(1) do + relation = Post.where(id: david.posts.select(:id)) + assert_equal davids_posts, relation.order(:id).to_a + end + + assert_queries(1) do + relation = Post.where('id in (?)', david.posts.select(:id)) + assert_equal davids_posts, relation.order(:id).to_a, 'should process Relation as bind variables' + end + + assert_queries(1) do + relation = Post.where('id in (:post_ids)', post_ids: david.posts.select(:id)) + assert_equal davids_posts, relation.order(:id).to_a, 'should process Relation as named bind variables' + end end def test_find_all_using_where_with_relation_and_alternate_primary_key diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 4c0762deca..766b2ff2ef 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -1,5 +1,7 @@ require "cases/helper" require 'models/binary' +require 'models/author' +require 'models/post' class SanitizeTest < ActiveRecord::TestCase def setup @@ -9,7 +11,7 @@ class SanitizeTest < ActiveRecord::TestCase quoted_bambi = ActiveRecord::Base.connection.quote("Bambi") quoted_column_name = ActiveRecord::Base.connection.quote_column_name("name") quoted_table_name = ActiveRecord::Base.connection.quote_table_name("adorable_animals") - expected_value = "#{quoted_table_name}.#{quoted_column_name} = #{quoted_bambi}" + expected_value = "#{quoted_table_name}.#{quoted_column_name} = #{quoted_bambi}" assert_equal expected_value, Binary.send(:sanitize_sql_hash, {adorable_animals: {name: 'Bambi'}}) end @@ -33,8 +35,15 @@ class SanitizeTest < ActiveRecord::TestCase end def test_sanitize_sql_array_handles_relations - assert_match(/\(\bselect\b.*?\bwhere\b.*?\)/i, - Binary.send(:sanitize_sql_array, ["id in (?)", Binary.where(id: 1)]), - "should sanitize `Relation` as subquery") + david = Author.create!(name: 'David') + david_posts = david.posts.select(:id) + + sub_query_pattern = /\(\bselect\b.*?\bwhere\b.*?\)/i + + select_author_sql = Post.send(:sanitize_sql_array, ['id in (?)', david_posts]) + assert_match(sub_query_pattern, select_author_sql, 'should sanitize `Relation` as subquery for bind variables') + + select_author_sql = Post.send(:sanitize_sql_array, ['id in (:post_ids)', post_ids: david_posts]) + assert_match(sub_query_pattern, select_author_sql, 'should sanitize `Relation` as subquery for named bind variables') end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 32f86f9c88..1ee8e60924 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -202,6 +202,11 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r(primary_key: "movieid"), match[1], "non-standard primary key not preserved" end + def test_schema_dump_should_use_false_as_default + output = standard_dump + assert_match %r{t\.boolean\s+"has_fun",.+default: false}, output + end + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_schema_dump_should_not_add_default_value_for_mysql_text_field output = standard_dump diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index cd7d91ff85..76f395ba83 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -122,17 +122,25 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_unscope_with_where_attributes - expected = Developer.order('salary DESC').collect { |dev| dev.name } - received = DeveloperOrderedBySalary.where(name: 'David').unscope(where: :name).collect { |dev| dev.name } + expected = Developer.order('salary DESC').collect(&:name) + received = DeveloperOrderedBySalary.where(name: 'David').unscope(where: :name).collect(&:name) assert_equal expected, received - expected_2 = Developer.order('salary DESC').collect { |dev| dev.name } - received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({:where => :name}, :select).collect { |dev| dev.name } + expected_2 = Developer.order('salary DESC').collect(&:name) + received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({:where => :name}, :select).collect(&:name) assert_equal expected_2, received_2 - expected_3 = Developer.order('salary DESC').collect { |dev| dev.name } - received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect { |dev| dev.name } + expected_3 = Developer.order('salary DESC').collect(&:name) + received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect(&:name) assert_equal expected_3, received_3 + + expected_4 = Developer.order('salary DESC').collect(&:name) + received_4 = DeveloperOrderedBySalary.where.not("name" => "Jamis").unscope(where: :name).collect(&:name) + assert_equal expected_4, received_4 + + expected_5 = Developer.order('salary DESC').collect(&:name) + received_5 = DeveloperOrderedBySalary.where.not("name" => ["Jamis", "David"]).unscope(where: :name).collect(&:name) + assert_equal expected_5, received_5 end def test_unscope_multiple_where_clauses diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb index f31896bc7f..90dac6399d 100644 --- a/activerecord/test/cases/tasks/postgresql_rake_test.rb +++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb @@ -231,6 +231,13 @@ module ActiveRecord ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) end + + def test_structure_load_accepts_path_with_spaces + filename = "awesome file.sql" + Kernel.expects(:system).with("psql -q -f awesome\\ file.sql my-app-db") + + ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) + end end end |