absorb polyamorous

This commit is contained in:
Greg Molnar 2018-06-23 19:00:21 +02:00
parent 0fe5eace73
commit d0805ac0f1
29 changed files with 1056 additions and 8 deletions

View File

@ -11,6 +11,10 @@ env:
- RAILS=5-2-stable DB=mysql
- RAILS=5-2-stable DB=postgres
- RAILS=v5.2.0 DB=sqlite3
- RAILS=v5.2.0 DB=mysql
- RAILS=v5.2.0 DB=postgres
- RAILS=5-0-stable DB=sqlite3
- RAILS=5-0-stable DB=mysql
- RAILS=5-0-stable DB=postgres
@ -18,7 +22,11 @@ env:
- RAILS=4-2-stable DB=sqlite3
- RAILS=4-2-stable DB=mysql
- RAILS=4-2-stable DB=postgres
matrix:
allow_failures:
- env: RAILS=5-2-stable DB=sqlite3
- env: RAILS=5-2-stable DB=mysql
- env: RAILS=5-2-stable DB=postgres
before_script:
- mysql -e 'create database ransack collate utf8_general_ci;'
- mysql -e 'use ransack;show variables like "%character%";show variables like "%collation%";'

View File

@ -5,12 +5,6 @@ gem 'rake'
rails = ENV['RAILS'] || '5-0-stable'
if rails == 'master'
gem 'polyamorous', github: 'activerecord-hackery/polyamorous'
else
gem 'polyamorous', '~> 1.3'
end
gem 'pry'
# Provide timezone information on Windows
@ -28,6 +22,9 @@ when /^v/ # A tagged version
gem 'activerecord', require: false
gem 'actionpack'
end
if rails == 'v5.2.0'
gem 'mysql2', '~> 0.4.4'
end
else
git 'git://github.com/rails/rails.git', :branch => rails do
gem 'activesupport'

55
lib/polyamorous.rb Normal file
View File

@ -0,0 +1,55 @@
if defined?(::ActiveRecord)
module Polyamorous
if defined?(Arel::InnerJoin)
InnerJoin = Arel::InnerJoin
OuterJoin = Arel::OuterJoin
else
InnerJoin = Arel::Nodes::InnerJoin
OuterJoin = Arel::Nodes::OuterJoin
end
if defined?(::ActiveRecord::Associations::JoinDependency)
JoinDependency = ::ActiveRecord::Associations::JoinDependency
JoinAssociation = ::ActiveRecord::Associations::JoinDependency::JoinAssociation
JoinBase = ::ActiveRecord::Associations::JoinDependency::JoinBase
else
JoinDependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency
JoinAssociation = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
JoinBase = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase
end
end
require 'polyamorous/tree_node'
require 'polyamorous/join'
require 'polyamorous/swapping_reflection_class'
ar_version = ::ActiveRecord::VERSION::STRING[0,3]
ar_version = '3_and_4.0' if ar_version < '4.1'
ar_version = ::ActiveRecord::VERSION::STRING[0,5] if ar_version == '5.2'
method, ruby_version =
if RUBY_VERSION >= '2.0' && ar_version >= '4.1'
# Ruby 2; we can use `prepend` to patch Active Record cleanly.
[:prepend, '2']
else
# Ruby 1.9; we must use `alias_method` to patch Active Record.
[:include, '1.9']
end
%w(join_association join_dependency).each do |file|
require "polyamorous/activerecord_#{ar_version}_ruby_#{ruby_version}/#{file}"
end
Polyamorous::JoinDependency.send(method, Polyamorous::JoinDependencyExtensions)
if method == :prepend
Polyamorous::JoinDependency.singleton_class
.send(:prepend, Polyamorous::JoinDependencyExtensions::ClassMethods)
end
Polyamorous::JoinAssociation.send(method, Polyamorous::JoinAssociationExtensions)
Polyamorous::JoinBase.class_eval do
if method_defined?(:active_record)
alias_method :base_klass, :active_record
end
end
end

View File

@ -0,0 +1,76 @@
# active_record_3_and_4.0_ruby_1.9/join_association.rb
module Polyamorous
module JoinAssociationExtensions
include SwappingReflectionClass
def self.included(base)
base.class_eval do
alias_method_chain :initialize, :polymorphism
alias_method :equality_without_polymorphism, :==
alias_method :==, :equality_with_polymorphism
if base.method_defined?(:active_record)
alias_method :base_klass, :active_record
end
if ActiveRecord::VERSION::STRING =~ /^3\.0\./
alias_method_chain :association_join, :polymorphism
else
alias_method_chain :build_constraint, :polymorphism
end
end
end
def initialize_with_polymorphism(
reflection, join_dependency, parent = nil, polymorphic_class = nil
)
if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
initialize_without_polymorphism(reflection, join_dependency, parent)
self.reflection.options[:polymorphic] = true
end
else
initialize_without_polymorphism(reflection, join_dependency, parent)
end
end
def equality_with_polymorphism(other)
equality_without_polymorphism(other) && base_klass == other.base_klass
end
def build_constraint_with_polymorphism(
reflection, table, key, foreign_table, foreign_key
)
if reflection.options[:polymorphic]
build_constraint_without_polymorphism(
reflection, table, key, foreign_table, foreign_key
)
.and(foreign_table[reflection.foreign_type].eq(reflection.klass.name))
else
build_constraint_without_polymorphism(
reflection, table, key, foreign_table, foreign_key
)
end
end
def association_join_with_polymorphism
return @join if @Join
@join = association_join_without_polymorphism
if reflection.macro == :belongs_to && reflection.options[:polymorphic]
aliased_table = Arel::Table.new(
table_name,
as: @aliased_table_name,
engine: arel_engine,
columns: klass.columns
)
parent_table = Arel::Table.new(
parent.table_name,
as: parent.aliased_table_name,
engine: arel_engine,
columns: parent.base_klass.columns
)
@join << parent_table[reflection.options[:foreign_type]]
.eq(reflection.klass.name)
end
@join
end
end
end

View File

@ -0,0 +1,96 @@
# active_record_3_and_4.0_ruby_1.9/join_dependency.rb
module Polyamorous
module JoinDependencyExtensions
def self.included(base)
base.class_eval do
alias_method_chain :build, :polymorphism
alias_method_chain :graft, :polymorphism
if base.method_defined?(:active_record)
alias_method :base_klass, :active_record
end
end
end
def graft_with_polymorphism(*associations)
associations.each do |association|
unless join_associations.detect { |a| association == a }
if association.reflection.options[:polymorphic]
build(
Join.new(
association.reflection.name,
association.join_type,
association.reflection.klass
),
association.find_parent_in(self) || join_base,
association.join_type
)
else
build(
association.reflection.name,
association.find_parent_in(self) || join_base,
association.join_type
)
end
end
end
self
end
if ActiveRecord::VERSION::STRING =~ /^3\.0\./
def _join_parts
@joins
end
else
def _join_parts
@join_parts
end
end
def build_with_polymorphism(
associations, parent = nil, join_type = InnerJoin
)
case associations
when Join
parent ||= _join_parts.last
reflection = parent.reflections[associations.name] or
raise ::ActiveRecord::ConfigurationError,
"Association named '#{associations.name
}' was not found; perhaps you misspelled it?"
unless join_association = find_join_association_respecting_polymorphism(
reflection, parent, associations.klass
)
@reflections << reflection
join_association = build_join_association_respecting_polymorphism(
reflection, parent, associations.klass
)
join_association.join_type = associations.type
_join_parts << join_association
cache_joined_association(join_association)
end
join_association
else
build_without_polymorphism(associations, parent, join_type)
end
end
def find_join_association_respecting_polymorphism(reflection, parent, klass)
if association = find_join_association(reflection, parent)
unless reflection.options[:polymorphic]
association
else
association if association.base_klass == klass
end
end
end
def build_join_association_respecting_polymorphism(reflection, parent, klass)
if reflection.options[:polymorphic] && klass
JoinAssociation.new(reflection, self, parent, klass)
else
JoinAssociation.new(reflection, self, parent)
end
end
end
end

View File

@ -0,0 +1,2 @@
# active_record_4.1_ruby_1.9/join_association.rb
require 'polyamorous/activerecord_4.2_ruby_1.9/join_association'

View File

@ -0,0 +1,4 @@
# active_record_4.1_ruby_1.9/join_dependency.rb
require 'polyamorous/activerecord_4.2_ruby_2/join_dependency'
require 'polyamorous/activerecord_4.2_ruby_1.9/join_dependency'
require 'polyamorous/activerecord_4.1_ruby_2/make_polyamorous_inner_joins'

View File

@ -0,0 +1,2 @@
# active_record_4.1_ruby_2/join_association.rb
require 'polyamorous/activerecord_5.0_ruby_2/join_association'

View File

@ -0,0 +1,3 @@
# active_record_4.1_ruby_2/join_dependency.rb
require 'polyamorous/activerecord_4.2_ruby_2/join_dependency'
require 'polyamorous/activerecord_4.1_ruby_2/make_polyamorous_inner_joins'

View File

@ -0,0 +1,14 @@
module Polyamorous
module JoinDependencyExtensions
# Replaces ActiveRecord::Associations::JoinDependency#make_inner_joins
#
def make_polyamorous_inner_joins(parent, child)
make_constraints(
parent, child, child.tables, child.join_type || Arel::Nodes::InnerJoin
)
.concat child.children.flat_map { |c|
make_polyamorous_inner_joins(child, c)
}
end
end
end

View File

@ -0,0 +1,46 @@
# active_record_4.2_ruby_1.9/join_association.rb
module Polyamorous
module JoinAssociationExtensions
include SwappingReflectionClass
def self.included(base)
base.class_eval do
attr_reader :join_type
alias_method_chain :initialize, :polymorphism
alias_method_chain :build_constraint, :polymorphism
end
end
def initialize_with_polymorphism(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|
initialize_without_polymorphism(reflection, children)
self.reflection.options[:polymorphic] = true
end
else
initialize_without_polymorphism(reflection, children)
end
end
# Reference https://github.com/rails/rails/commit/9b15db51b78028bfecdb85595624de4b838adbd1
def ==(other)
base_klass == other.base_klass
end
def build_constraint_with_polymorphism(
klass, table, key, foreign_table, foreign_key
)
if reflection.polymorphic?
build_constraint_without_polymorphism(
klass, table, key, foreign_table, foreign_key
)
.and(foreign_table[reflection.foreign_type].eq(reflection.klass.name))
else
build_constraint_without_polymorphism(
klass, table, key, foreign_table, foreign_key
)
end
end
end
end

View File

@ -0,0 +1,87 @@
# active_record_4.2_ruby_1.9/join_dependency.rb
require 'polyamorous/activerecord_4.2_ruby_2/join_dependency'
module Polyamorous
module JoinDependencyExtensions
def self.included(base)
base.extend ClassMethods
base.class_eval do
class << self
alias_method :walk_tree_without_polymorphism, :walk_tree
alias_method :walk_tree, :walk_tree_with_polymorphism
end
alias_method :build_without_polymorphism, :build
alias_method :build, :build_with_polymorphism
alias_method :join_constraints_without_polymorphism, :join_constraints
alias_method :join_constraints, :join_constraints_with_polymorphism
end
end
# Replaces ActiveRecord::Associations::JoinDependency#build
#
def build_with_polymorphism(associations, base_klass)
associations.map do |name, right|
if name.is_a? Join
reflection = find_reflection base_klass, name.name
reflection.check_validity!
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!
if reflection.polymorphic?
raise ActiveRecord::EagerLoadPolymorphicError.new(reflection)
end
JoinAssociation.new reflection, build(right, reflection.klass)
end
end
end
# Replaces ActiveRecord::Associations::JoinDependency#join_constraints
# to call #make_polyamorous_inner_joins instead of #make_inner_joins
#
def join_constraints_with_polymorphism(outer_joins)
joins = join_root.children.flat_map { |child|
make_polyamorous_inner_joins join_root, child
}
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(oj.join_root, child)
}
end
}
end
module ClassMethods
# Replaces ActiveRecord::Associations::JoinDependency#self.walk_tree
#
def walk_tree_with_polymorphism(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
walk_tree_without_polymorphism(associations, hash)
end
end
end
end
end

View File

@ -0,0 +1,2 @@
# active_record_4.2_ruby_2/join_association.rb
require 'polyamorous/activerecord_5.0_ruby_2/join_association'

View File

@ -0,0 +1,24 @@
# active_record_4.2_ruby_2/join_dependency.rb
require 'polyamorous/activerecord_5.0_ruby_2/join_dependency'
module Polyamorous
module JoinDependencyExtensions
# Replaces ActiveRecord::Associations::JoinDependency#join_constraints
# to call #make_polyamorous_inner_joins instead of #make_inner_joins.
#
def join_constraints(outer_joins)
joins = join_root.children.flat_map { |child|
make_polyamorous_inner_joins join_root, child
}
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(oj.join_root, child)
}
end
}
end
end
end

View File

@ -0,0 +1,2 @@
# active_record_5.0_ruby_2/join_association.rb
require 'polyamorous/activerecord_5.1_ruby_2/join_association'

View File

@ -0,0 +1,2 @@
# active_record_5.0_ruby_2/join_dependency.rb
require 'polyamorous/activerecord_5.1_ruby_2/join_dependency'

View File

@ -0,0 +1,39 @@
# active_record_5.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

View File

@ -0,0 +1,130 @@
# active_record_5.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! if ActiveRecord::VERSION::MAJOR >= 5
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 ActiveRecord::VERSION::MAJOR >= 5
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(outer_joins, join_type)
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 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(oj.join_root, child)
}
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)
tables = child.tables
join_type = Arel::Nodes::OuterJoin
info = make_constraints parent, child, tables, 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, tables, 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

View File

@ -0,0 +1,39 @@
# active_record_5.2_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, alias_tracker, 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, alias_tracker)
self.reflection.options[:polymorphic] = true
end
else
super(reflection, children, alias_tracker)
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

View File

@ -0,0 +1,131 @@
# active_record_5.2_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! if ActiveRecord::VERSION::MAJOR >= 5
klass = if reflection.polymorphic?
name.klass || base_klass
else
reflection.klass
end
JoinAssociation.new(reflection, build(right, klass), alias_tracker, name.klass, name.type)
else
reflection = find_reflection base_klass, name
reflection.check_validity!
reflection.check_eager_loadable! if ActiveRecord::VERSION::MAJOR >= 5
if reflection.polymorphic?
raise ActiveRecord::EagerLoadPolymorphicError.new(reflection)
end
JoinAssociation.new(reflection, build(right, reflection.klass), alias_tracker)
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, alias_tracker, klass)
else
JoinAssociation.new(reflection, self, alias_tracker)
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(outer_joins, join_type)
@alias_tracker = alias_tracker
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 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(oj.join_root, child)
}
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)
tables = child.tables
join_type = Arel::Nodes::OuterJoin
info = make_constraints parent, child, tables, 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, tables, 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

70
lib/polyamorous/join.rb Normal file
View File

@ -0,0 +1,70 @@
module Polyamorous
class Join
include TreeNode
attr_accessor :name
attr_reader :type, :klass
def initialize(name, type = InnerJoin, klass = nil)
@name = name
@type = convert_to_arel_join_type(type)
@klass = convert_to_class(klass) if klass
end
def klass=(klass)
@klass = convert_to_class(klass) if klass
end
def type=(type)
@type = convert_to_arel_join_type(type) if type
end
def hash
[@name, @type, @klass].hash
end
def eql?(other)
self.class == other.class &&
self.name == other.name &&
self.type == other.type &&
self.klass == other.klass
end
alias :== :eql?
def add_to_tree(hash)
hash[self] ||= {}
end
private
def convert_to_arel_join_type(type)
case type
when 'inner', :inner
InnerJoin
when 'outer', :outer
OuterJoin
when Class
if [InnerJoin, OuterJoin].include? type
type
else
raise ArgumentError, "#{type} cannot be converted to an ARel join type"
end
else
raise ArgumentError, "#{type} cannot be converted to an ARel join type"
end
end
def convert_to_class(value)
case value
when String, Symbol
Kernel.const_get(value)
when Class
value
else
raise ArgumentError, "#{value} cannot be converted to a Class"
end
end
end
end

View File

@ -0,0 +1,11 @@
module Polyamorous
module SwappingReflectionClass
def swapping_reflection_klass(reflection, klass)
new_reflection = reflection.clone
new_reflection.instance_variable_set(:@options, reflection.options.clone)
new_reflection.options.delete(:polymorphic)
new_reflection.instance_variable_set(:@klass, klass)
yield new_reflection
end
end
end

View File

@ -0,0 +1,7 @@
module Polyamorous
module TreeNode
def add_to_tree(hash)
raise NotImplementedError
end
end
end

View File

@ -20,7 +20,6 @@ Gem::Specification.new do |s|
s.add_dependency 'activerecord', '>= 3.0'
s.add_dependency 'activesupport', '>= 3.0'
s.add_dependency 'i18n'
s.add_dependency 'polyamorous', '~> 1.3.2'
s.add_development_dependency 'rspec', '~> 3'
s.add_development_dependency 'machinist', '~> 1.0.6'
s.add_development_dependency 'faker', '~> 0.9.5'

View File

@ -0,0 +1,26 @@
module PolyamorousHelper
if ActiveRecord::VERSION::STRING >= "4.1"
def new_join_association(reflection, children, klass)
Polyamorous::JoinAssociation.new reflection, children, klass
end
else
def new_join_association(reflection, join_dependency, parent, klass)
Polyamorous::JoinAssociation.new reflection, join_dependency, parent, klass
end
end
if ActiveRecord::VERSION::STRING >= "5.2"
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
end
else
def new_join_dependency(klass, associations = {})
Polyamorous::JoinDependency.new klass, associations, []
end
end
def new_join(name, type = Polyamorous::InnerJoin, klass = nil)
Polyamorous::Join.new name, type, klass
end
end

View File

@ -0,0 +1,54 @@
require 'spec_helper'
module Polyamorous
describe JoinAssociation do
join_base, join_association_args, polymorphic =
if ActiveRecord::VERSION::STRING >= '4.1'
[:join_root, 'parent.children', 'reflection.options[:polymorphic]']
else
[:join_base, 'join_dependency, parent', 'options[:polymorphic]']
end
let(:join_dependency) { new_join_dependency Note, {} }
let(:reflection) { Note.reflect_on_association(:notable) }
let(:parent) { join_dependency.send(join_base) }
let(:join_association) {
eval("new_join_association(reflection, #{join_association_args}, Article)")
}
subject {
join_dependency.build_join_association_respecting_polymorphism(
reflection, parent, Person
)
}
it 'respects polymorphism on equality test' do
expect(subject).to eq(
join_dependency.build_join_association_respecting_polymorphism(
reflection, parent, Person
)
)
expect(subject).not_to eq(
join_dependency.build_join_association_respecting_polymorphism(
reflection, parent, Article
)
)
end
it 'leaves the orginal reflection intact for thread safety' do
reflection.instance_variable_set(:@klass, Article)
join_association
.swapping_reflection_klass(reflection, Person) do |new_reflection|
expect(new_reflection.options).not_to equal reflection.options
expect(new_reflection.options).not_to have_key(:polymorphic)
expect(new_reflection.klass).to eq(Person)
expect(reflection.klass).to eq(Article)
end
end
it 'sets the polymorphic option to true after initializing' do
expect(join_association.instance_eval(polymorphic)).to be true
end
end
end

View File

@ -0,0 +1,102 @@
require 'spec_helper'
module Polyamorous
describe JoinDependency do
method, join_associations, join_base =
if ActiveRecord::VERSION::STRING >= '4.1'
[:instance_eval, 'join_root.drop(1)', :join_root]
else
[:send, 'join_associations', :join_base]
end
context 'with symbol joins' do
subject { new_join_dependency Person, articles: :comments }
specify { expect(subject.send(method, join_associations).size)
.to eq(2) }
specify { expect(subject.send(method, join_associations).map(&:join_type))
.to be_all { Polyamorous::InnerJoin } }
end
context 'with has_many :through association' do
subject { new_join_dependency Person, :authored_article_comments }
specify { expect(subject.send(method, join_associations).size)
.to eq 1 }
specify { expect(subject.send(method, join_associations).first.table_name)
.to eq 'comments' }
end
context 'with outer join' do
subject { new_join_dependency Person, new_join(:articles, :outer) }
specify { expect(subject.send(method, join_associations).size)
.to eq 1 }
specify { expect(subject.send(method, join_associations).first.join_type)
.to eq Polyamorous::OuterJoin }
end
context 'with nested outer joins' do
subject { new_join_dependency Person,
new_join(:articles, :outer) => new_join(:comments, :outer) }
specify { expect(subject.send(method, join_associations).size)
.to eq 2 }
specify { expect(subject.send(method, join_associations).map(&:join_type))
.to eq [Polyamorous::OuterJoin, Polyamorous::OuterJoin] }
specify { expect(subject.send(method, join_associations).map(&:join_type))
.to be_all { Polyamorous::OuterJoin } }
end
context 'with polymorphic belongs_to join' do
subject { new_join_dependency Note, new_join(:notable, :inner, Person) }
specify { expect(subject.send(method, join_associations).size)
.to eq 1 }
specify { expect(subject.send(method, join_associations).first.join_type)
.to eq Polyamorous::InnerJoin }
specify { expect(subject.send(method, join_associations).first.table_name)
.to eq 'people' }
it 'finds a join association respecting polymorphism' do
parent = subject.send(join_base)
reflection = Note.reflect_on_association(:notable)
expect(subject.find_join_association_respecting_polymorphism(
reflection, parent, Person))
.to eq subject.send(method, join_associations).first
end
end
context 'with polymorphic belongs_to join and nested symbol join' do
subject { new_join_dependency Note,
new_join(:notable, :inner, Person) => :comments }
specify { expect(subject.send(method, join_associations).size)
.to eq 2 }
specify { expect(subject.send(method, join_associations).map(&:join_type))
.to be_all { Polyamorous::InnerJoin } }
specify { expect(subject.send(method, join_associations).first.table_name)
.to eq 'people' }
specify { expect(subject.send(method, join_associations)[1].table_name)
.to eq 'comments' }
end
context '#left_outer_join in Rails 5 overrides join type specified',
if: ActiveRecord::VERSION::MAJOR >= 5 && ActiveRecord::VERSION::MINOR < 2 do
let(:join_type_class) do
new_join_dependency(
Person,
new_join(:articles)
).join_constraints(
[],
Arel::Nodes::OuterJoin
).first.joins.map(&:class)
end
specify { expect(join_type_class).to eq [Arel::Nodes::OuterJoin] }
end
end
end

19
spec/ransack/join_spec.rb Normal file
View File

@ -0,0 +1,19 @@
require 'spec_helper'
module Polyamorous
describe Join do
it 'is a tree node' do
join = new_join(:articles, :outer)
expect(join).to be_kind_of(TreeNode)
end
it 'can be added to a tree' do
join = new_join(:articles, :outer)
tree_hash = {}
join.add_to_tree(tree_hash)
expect(tree_hash[join]).to be {}
end
end
end

View File

@ -44,6 +44,7 @@ RSpec.configure do |config|
config.before(:each) { Sham.reset(:before_each) }
config.include RansackHelper
config.include PolyamorousHelper
end
RSpec::Matchers.define :be_like do |expected|