diff --git a/.travis.yml b/.travis.yml index 2948c99..58be62c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,10 @@ env: - RAILS=5-2-stable DB=mysql - RAILS=5-2-stable DB=postgres + - RAILS=v5.2.1.rc1 DB=sqlite3 + - RAILS=v5.2.1.rc1 DB=mysql + - RAILS=v5.2.1.rc1 DB=postgres + - RAILS=v5.2.0 DB=sqlite3 - RAILS=v5.2.0 DB=mysql - RAILS=v5.2.0 DB=postgres diff --git a/Gemfile b/Gemfile index 2118fca..c48f686 100644 --- a/Gemfile +++ b/Gemfile @@ -22,7 +22,7 @@ when /^v/ # A tagged version gem 'activerecord', require: false gem 'actionpack' end - if rails == 'v5.2.0' + if rails >= 'v5.2.0' gem 'mysql2', '~> 0.4.4' end else diff --git a/lib/polyamorous.rb b/lib/polyamorous.rb index 5f7df78..5a2c03f 100644 --- a/lib/polyamorous.rb +++ b/lib/polyamorous.rb @@ -24,7 +24,7 @@ if defined?(::ActiveRecord) require 'polyamorous/swapping_reflection_class' ar_version = ::ActiveRecord::VERSION::STRING[0,3] - ar_version = ::ActiveRecord::VERSION::STRING[0,5] if ar_version == '5.2' + ar_version = ::ActiveRecord::VERSION::STRING[0,5] if ar_version >= '5.2' %w(join_association join_dependency).each do |file| require "polyamorous/activerecord_#{ar_version}_ruby_2/#{file}" diff --git a/lib/polyamorous/activerecord_5.2.1_ruby_2/join_association.rb b/lib/polyamorous/activerecord_5.2.1_ruby_2/join_association.rb new file mode 100644 index 0000000..d72d766 --- /dev/null +++ b/lib/polyamorous/activerecord_5.2.1_ruby_2/join_association.rb @@ -0,0 +1,38 @@ +# active_record_5.2.1_ruby_2/join_association.rb + +module Polyamorous + module JoinAssociationExtensions + include SwappingReflectionClass + def self.prepended(base) + base.class_eval { attr_reader :join_type } + end + + def initialize(reflection, children, polymorphic_class = nil, join_type = Arel::Nodes::InnerJoin) + @join_type = join_type + if polymorphic_class && ::ActiveRecord::Base > polymorphic_class + swapping_reflection_klass(reflection, polymorphic_class) do |reflection| + super(reflection, children) + self.reflection.options[:polymorphic] = true + end + else + super(reflection, children) + end + end + + # Reference: https://github.com/rails/rails/commit/9b15db5 + # NOTE: Not sure we still need it? + # + def ==(other) + base_klass == other.base_klass + end + + def build_constraint(klass, table, key, foreign_table, foreign_key) + if reflection.polymorphic? + super(klass, table, key, foreign_table, foreign_key) + .and(foreign_table[reflection.foreign_type].eq(reflection.klass.name)) + else + super(klass, table, key, foreign_table, foreign_key) + end + end + end +end diff --git a/lib/polyamorous/activerecord_5.2.1_ruby_2/join_dependency.rb b/lib/polyamorous/activerecord_5.2.1_ruby_2/join_dependency.rb new file mode 100644 index 0000000..6b61546 --- /dev/null +++ b/lib/polyamorous/activerecord_5.2.1_ruby_2/join_dependency.rb @@ -0,0 +1,132 @@ +# active_record_5.2.1_ruby_2/join_dependency.rb + +module Polyamorous + module JoinDependencyExtensions + # Replaces ActiveRecord::Associations::JoinDependency#build + # + def build(associations, base_klass) + associations.map do |name, right| + if name.is_a? Join + reflection = find_reflection base_klass, name.name + reflection.check_validity! + reflection.check_eager_loadable! + + klass = if reflection.polymorphic? + name.klass || base_klass + else + reflection.klass + end + JoinAssociation.new(reflection, build(right, klass), name.klass, name.type) + else + reflection = find_reflection base_klass, name + reflection.check_validity! + reflection.check_eager_loadable! + + if reflection.polymorphic? + raise ActiveRecord::EagerLoadPolymorphicError.new(reflection) + end + JoinAssociation.new(reflection, build(right, reflection.klass)) + end + end + end + + def find_join_association_respecting_polymorphism(reflection, parent, klass) + if association = parent.children.find { |j| j.reflection == reflection } + unless reflection.polymorphic? + association + else + association if association.base_klass == klass + end + end + end + + def build_join_association_respecting_polymorphism(reflection, parent, klass) + if reflection.polymorphic? && klass + JoinAssociation.new(reflection, self, klass) + else + JoinAssociation.new(reflection, self) + end + end + + # Replaces ActiveRecord::Associations::JoinDependency#join_constraints + # + # This internal method was changed in Rails 5.0 by commit + # https://github.com/rails/rails/commit/e038975 which added + # left_outer_joins (see #make_polyamorous_left_outer_joins below) and added + # passing an additional argument, `join_type`, to #join_constraints. + # + def join_constraints(joins_to_add, join_type, alias_tracker) + @alias_tracker = alias_tracker + + construct_tables!(join_root) + + joins = join_root.children.flat_map { |child| + if join_type == Arel::Nodes::OuterJoin + make_polyamorous_left_outer_joins join_root, child + else + make_polyamorous_inner_joins join_root, child + end + } + + joins.concat joins_to_add.flat_map { |oj| + construct_tables!(oj.join_root) + if join_root.match? oj.join_root + walk(join_root, oj.join_root) + else + make_join_constraints(oj.join_root, join_type) + end + } + end + + # Replaces ActiveRecord::Associations::JoinDependency#make_left_outer_joins, + # a new method that was added in Rails 5.0 with the following commit: + # https://github.com/rails/rails/commit/e038975 + # + def make_polyamorous_left_outer_joins(parent, child) + join_type = Arel::Nodes::OuterJoin + info = make_constraints parent, child, join_type + + info + child.children.flat_map { |c| + make_polyamorous_left_outer_joins(child, c) + } + end + + # Replaces ActiveRecord::Associations::JoinDependency#make_inner_joins + # + def make_polyamorous_inner_joins(parent, child) + tables = child.tables + join_type = child.join_type || Arel::Nodes::InnerJoin + info = make_constraints parent, child, join_type + + info + child.children.flat_map { |c| + make_polyamorous_inner_joins(child, c) + } + end + + private :make_polyamorous_inner_joins, :make_polyamorous_left_outer_joins + + module ClassMethods + # Prepended before ActiveRecord::Associations::JoinDependency#walk_tree + # + def walk_tree(associations, hash) + case associations + when TreeNode + associations.add_to_tree(hash) + when Hash + associations.each do |k, v| + cache = + if TreeNode === k + k.add_to_tree(hash) + else + hash[k] ||= {} + end + walk_tree(v, cache) + end + else + super(associations, hash) + end + end + end + + end +end diff --git a/lib/ransack/adapters/active_record/context.rb b/lib/ransack/adapters/active_record/context.rb index dd3c87c..037a517 100644 --- a/lib/ransack/adapters/active_record/context.rb +++ b/lib/ransack/adapters/active_record/context.rb @@ -90,10 +90,19 @@ module Ransack # JoinDependency to track table aliases. # def join_sources - base, joins = [ - Arel::SelectManager.new(@object.table), - @join_dependency.join_constraints(@object.joins_values, @join_type) - ] + base, joins = + if ::ActiveRecord::VERSION::STRING > Constants::RAILS_5_2_0 + alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, @object.table.name, []) + [ + Arel::SelectManager.new(@object.table), + @join_dependency.join_constraints(@object.joins_values, @join_type, alias_tracker) + ] + else + [ + Arel::SelectManager.new(@object.table), + @join_dependency.join_constraints(@object.joins_values, @join_type) + ] + end joins = joins.collect(&:joins).flatten if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_2 joins.each do |aliased_join| base.from(aliased_join) @@ -225,14 +234,21 @@ module Ransack join_list = join_nodes + convert_join_strings_to_ast(relation.table, string_joins) - if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_2 + if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_2_0 join_dependency = JoinDependency.new(relation.klass, association_joins, join_list) join_nodes.each do |join| join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1 end + elsif ::ActiveRecord::VERSION::STRING == Constants::RAILS_5_2_0 + alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list) + join_dependency = JoinDependency.new(relation.klass, relation.table, association_joins, alias_tracker) + join_nodes.each do |join| + join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1 + end else alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list) - join_dependency = JoinDependency.new(relation.klass, relation.table, association_joins, alias_tracker) + join_dependency = JoinDependency.new(relation.klass, relation.table, association_joins) + join_dependency.instance_variable_set(:@alias_tracker, alias_tracker) join_nodes.each do |join| join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1 end @@ -259,14 +275,14 @@ module Ransack end def build_association(name, parent = @base, klass = nil) - if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_2 + if ::ActiveRecord::VERSION::STRING < Constants::RAILS_5_2_0 jd = JoinDependency.new( parent.base_klass, Polyamorous::Join.new(name, @join_type, klass), [] ) found_association = jd.join_root.children.last - else + elsif ::ActiveRecord::VERSION::STRING == Constants::RAILS_5_2_0 alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, parent.table.name, []) jd = JoinDependency.new( parent.base_klass, @@ -275,6 +291,13 @@ module Ransack alias_tracker ) found_association = jd.instance_variable_get(:@join_root).children.last + else + jd = JoinDependency.new( + parent.base_klass, + parent.base_klass.arel_table, + Polyamorous::Join.new(name, @join_type, klass), + ) + found_association = jd.instance_variable_get(:@join_root).children.last end @@ -285,9 +308,13 @@ module Ransack @join_dependency.instance_variable_get(:@join_root).children.push found_association # Builds the arel nodes properly for this association - @join_dependency.send( - :construct_tables!, jd.instance_variable_get(:@join_root), found_association + if ::ActiveRecord::VERSION::STRING > Constants::RAILS_5_2_0 + @join_dependency.send(:construct_tables!, jd.instance_variable_get(:@join_root)) + else + @join_dependency.send( + :construct_tables!, jd.instance_variable_get(:@join_root), found_association ) + end # Leverage the stashed association functionality in AR @object = @object.joins(jd) @@ -308,7 +335,7 @@ module Ransack reflection.scope_chain, reflection.chain ) - else + elsif ::ActiveRecord::VERSION::STRING <= Constants::RAILS_5_2_0 association.join_constraints( parent.table, parent.base_klass, @@ -316,6 +343,13 @@ module Ransack association.tables, reflection.chain ) + else + association.join_constraints( + parent.table, + parent.base_klass, + Arel::Nodes::OuterJoin, + @join_dependency.instance_variable_get(:@alias_tracker) + ) end join_constraints.to_a.flatten end diff --git a/lib/ransack/constants.rb b/lib/ransack/constants.rb index 8c7493f..61cb0c2 100644 --- a/lib/ransack/constants.rb +++ b/lib/ransack/constants.rb @@ -47,6 +47,7 @@ module Ransack RAILS_5_1 = '5.1'.freeze RAILS_5_2 = '5.2'.freeze + RAILS_5_2_0 = '5.2.0'.freeze RANSACK_SLASH_SEARCHES = 'ransack/searches'.freeze RANSACK_SLASH_SEARCHES_SLASH_SEARCH = 'ransack/searches/search'.freeze diff --git a/spec/helpers/polyamorous_helper.rb b/spec/helpers/polyamorous_helper.rb index eca9593..c6c4bca 100644 --- a/spec/helpers/polyamorous_helper.rb +++ b/spec/helpers/polyamorous_helper.rb @@ -3,7 +3,11 @@ module PolyamorousHelper Polyamorous::JoinAssociation.new reflection, children, klass end - if ActiveRecord::VERSION::STRING >= "5.2" + if ActiveRecord::VERSION::STRING > "5.2.0" + def new_join_dependency(klass, associations = {}) + Polyamorous::JoinDependency.new klass, klass.arel_table, associations + end + elsif ActiveRecord::VERSION::STRING == "5.2.0" def new_join_dependency(klass, associations = {}) alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(klass.connection, klass.table_name, []) Polyamorous::JoinDependency.new klass, klass.arel_table, associations, alias_tracker diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index ae224a5..eae3d31 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -461,8 +461,8 @@ module Ransack Comment.create(article: Article.create(title: 'Avenge'), person: Person.create(salary: 50_000)), ] expect(Comment.ransack(article_title_cont: 'aven',s: 'person_salary desc').result).to eq(comments) - expect(Comment.joins(:person).ransack(s: 'person_salary desc', article_title_cont: 'aven').result).to eq(comments) - expect(Comment.joins(:person).ransack(article_title_cont: 'aven',s: 'person_salary desc').result).to eq(comments) + expect(Comment.joins(:person).ransack(s: 'persons_salarydesc', article_title_cont: 'aven').result).to eq(comments) + expect(Comment.joins(:person).ransack(article_title_cont: 'aven',s: 'persons_salary desc').result).to eq(comments) end it 'allows sort by `only_sort` field' do