2014-08-01 06:42:22 +00:00
|
|
|
require 'ransack/context'
|
|
|
|
require 'polyamorous'
|
|
|
|
|
|
|
|
module Ransack
|
|
|
|
module Adapters
|
|
|
|
module Mongoid
|
|
|
|
class Context < ::Ransack::Context
|
|
|
|
|
|
|
|
# Because the AR::Associations namespace is insane
|
2014-08-01 09:05:25 +00:00
|
|
|
# JoinDependency = ::Mongoid::Associations::JoinDependency
|
|
|
|
# JoinPart = JoinDependency::JoinPart
|
2014-08-01 06:42:22 +00:00
|
|
|
|
|
|
|
def initialize(object, options = {})
|
|
|
|
super
|
2014-08-02 04:06:39 +00:00
|
|
|
# @arel_visitor = @engine.connection.visitor
|
2014-08-01 06:42:22 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def relation_for(object)
|
|
|
|
object.all
|
|
|
|
end
|
|
|
|
|
|
|
|
def type_for(attr)
|
|
|
|
return nil unless attr && attr.valid?
|
|
|
|
name = attr.arel_attribute.name.to_s
|
2014-08-02 04:06:39 +00:00
|
|
|
# table = attr.arel_attribute.relation.table_name
|
2014-08-01 06:42:22 +00:00
|
|
|
|
2014-08-02 04:06:39 +00:00
|
|
|
# schema_cache = @engine.connection.schema_cache
|
|
|
|
# raise "No table named #{table} exists" unless schema_cache.table_exists?(table)
|
|
|
|
# schema_cache.columns_hash(table)[name].type
|
|
|
|
|
|
|
|
# when :date
|
|
|
|
# when :datetime, :timestamp, :time
|
|
|
|
# when :boolean
|
|
|
|
# when :integer
|
|
|
|
# when :float
|
|
|
|
# when :decimal
|
|
|
|
# else # :string
|
|
|
|
|
2014-11-02 19:14:37 +00:00
|
|
|
name = '_id' if name == 'id'
|
|
|
|
|
2014-08-02 04:06:39 +00:00
|
|
|
t = object.klass.fields[name].type
|
|
|
|
|
|
|
|
t.to_s.demodulize.underscore.to_sym
|
2014-08-01 06:42:22 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def evaluate(search, opts = {})
|
|
|
|
viz = Visitor.new
|
|
|
|
relation = @object.where(viz.accept(search.base))
|
|
|
|
if search.sorts.any?
|
2014-08-02 17:36:23 +00:00
|
|
|
ary_sorting = viz.accept(search.sorts)
|
|
|
|
sorting = {}
|
|
|
|
ary_sorting.each do |s|
|
|
|
|
sorting.merge! Hash[s.map { |k, d| [k.to_s == 'id' ? '_id' : k, d] }]
|
|
|
|
end
|
|
|
|
relation = relation.order_by(sorting)
|
|
|
|
# relation = relation.except(:order)
|
|
|
|
# .reorder(viz.accept(search.sorts))
|
2014-08-01 06:42:22 +00:00
|
|
|
end
|
2014-08-02 17:36:23 +00:00
|
|
|
# -- mongoid has different distinct method
|
|
|
|
# opts[:distinct] ? relation.distinct : relation
|
|
|
|
relation
|
2014-08-01 06:42:22 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def attribute_method?(str, klass = @klass)
|
|
|
|
exists = false
|
|
|
|
if ransackable_attribute?(str, klass)
|
|
|
|
exists = true
|
|
|
|
elsif (segments = str.split(/_/)).size > 1
|
|
|
|
remainder = []
|
|
|
|
found_assoc = nil
|
|
|
|
while !found_assoc && remainder.unshift(
|
|
|
|
segments.pop) && segments.size > 0 do
|
|
|
|
assoc, poly_class = unpolymorphize_association(
|
|
|
|
segments.join('_')
|
|
|
|
)
|
|
|
|
if found_assoc = get_association(assoc, klass)
|
|
|
|
exists = attribute_method?(remainder.join('_'),
|
|
|
|
poly_class || found_assoc.klass
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
exists
|
|
|
|
end
|
|
|
|
|
|
|
|
def table_for(parent)
|
2014-08-02 04:06:39 +00:00
|
|
|
# parent.table
|
|
|
|
Ransack::Adapters::Mongoid::Table.new(parent)
|
2014-08-01 06:42:22 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def klassify(obj)
|
2014-08-02 04:06:39 +00:00
|
|
|
if Class === obj && obj.ancestors.include?(::Mongoid::Document)
|
2014-08-01 06:42:22 +00:00
|
|
|
obj
|
|
|
|
elsif obj.respond_to? :klass
|
|
|
|
obj.klass
|
|
|
|
elsif obj.respond_to? :base_klass
|
|
|
|
obj.base_klass
|
|
|
|
else
|
|
|
|
raise ArgumentError, "Don't know how to klassify #{obj}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def get_parent_and_attribute_name(str, parent = @base)
|
|
|
|
attr_name = nil
|
|
|
|
|
|
|
|
if ransackable_attribute?(str, klassify(parent))
|
|
|
|
attr_name = str
|
|
|
|
elsif (segments = str.split(/_/)).size > 1
|
|
|
|
remainder = []
|
|
|
|
found_assoc = nil
|
|
|
|
while remainder.unshift(
|
|
|
|
segments.pop) && segments.size > 0 && !found_assoc do
|
|
|
|
assoc, klass = unpolymorphize_association(segments.join('_'))
|
|
|
|
if found_assoc = get_association(assoc, parent)
|
|
|
|
join = build_or_find_association(found_assoc.name, parent, klass)
|
|
|
|
parent, attr_name = get_parent_and_attribute_name(
|
|
|
|
remainder.join('_'), join
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
[parent, attr_name]
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_association(str, parent = @base)
|
|
|
|
klass = klassify parent
|
|
|
|
ransackable_association?(str, klass) &&
|
2014-08-02 17:36:23 +00:00
|
|
|
klass.reflect_on_all_associations_all.detect { |a| a.name.to_s == str }
|
2014-08-01 06:42:22 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def join_dependency(relation)
|
|
|
|
if relation.respond_to?(:join_dependency) # Squeel will enable this
|
|
|
|
relation.join_dependency
|
|
|
|
else
|
|
|
|
build_join_dependency(relation)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-08-02 04:06:39 +00:00
|
|
|
# Checkout active_record/relation/query_methods.rb +build_joins+ for
|
2014-08-01 06:42:22 +00:00
|
|
|
# reference. Lots of duplicated code maybe we can avoid it
|
|
|
|
def build_join_dependency(relation)
|
|
|
|
buckets = relation.joins_values.group_by do |join|
|
|
|
|
case join
|
|
|
|
when String
|
|
|
|
'string_join'
|
|
|
|
when Hash, Symbol, Array
|
|
|
|
'association_join'
|
|
|
|
when JoinDependency, JoinDependency::JoinAssociation
|
|
|
|
'stashed_join'
|
|
|
|
when Arel::Nodes::Join
|
|
|
|
'join_node'
|
|
|
|
else
|
|
|
|
raise 'unknown class: %s' % join.class.name
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
association_joins = buckets['association_join'] || []
|
|
|
|
|
|
|
|
stashed_association_joins = buckets['stashed_join'] || []
|
|
|
|
|
|
|
|
join_nodes = buckets['join_node'] || []
|
|
|
|
|
|
|
|
string_joins = (buckets['string_join'] || [])
|
|
|
|
.map { |x| x.strip }
|
|
|
|
.uniq
|
|
|
|
|
|
|
|
join_list = relation.send :custom_join_ast,
|
|
|
|
relation.table.from(relation.table), string_joins
|
|
|
|
|
|
|
|
join_dependency = JoinDependency.new(
|
|
|
|
relation.klass, association_joins, join_list
|
|
|
|
)
|
|
|
|
|
|
|
|
join_nodes.each do |join|
|
|
|
|
join_dependency.alias_tracker.aliases[join.left.name.downcase] = 1
|
|
|
|
end
|
|
|
|
|
2014-08-02 17:36:23 +00:00
|
|
|
join_dependency # ActiveRecord::Associations::JoinDependency
|
2014-08-01 06:42:22 +00:00
|
|
|
end
|
|
|
|
|
2014-08-02 17:36:23 +00:00
|
|
|
# ActiveRecord method
|
|
|
|
def build_or_find_association(name, parent = @base, klass = nil)
|
|
|
|
found_association = @join_dependency.join_associations
|
|
|
|
.detect do |assoc|
|
|
|
|
assoc.reflection.name == name &&
|
|
|
|
assoc.parent == parent &&
|
|
|
|
(!klass || assoc.reflection.klass == klass)
|
2014-08-01 06:42:22 +00:00
|
|
|
end
|
2014-08-02 17:36:23 +00:00
|
|
|
unless found_association
|
|
|
|
@join_dependency.send(
|
|
|
|
:build,
|
|
|
|
Polyamorous::Join.new(name, @join_type, klass),
|
|
|
|
parent
|
|
|
|
)
|
|
|
|
found_association = @join_dependency.join_associations.last
|
|
|
|
# Leverage the stashed association functionality in AR
|
|
|
|
@object = @object.joins(found_association)
|
2014-08-01 06:42:22 +00:00
|
|
|
end
|
2014-08-02 17:36:23 +00:00
|
|
|
found_association
|
2014-08-01 06:42:22 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|