lib/hydra_attribute/active_record/relation/query_methods.rb
module HydraAttribute
module ActiveRecord
module Relation
module QueryMethods
extend ActiveSupport::Concern
MULTI_VALUE_METHODS = [:hydra_joins_aliases, :hydra_select_values, :hydra_attributes]
included do
attr_writer *MULTI_VALUE_METHODS
MULTI_VALUE_METHODS.each do |value|
class_eval <<-EOS, __FILE__, __LINE__ + 1
def #{value}; @#{value} ||= [] end
EOS
end
alias_method_chain :where, :hydra_attribute
end
def where_with_hydra_attribute(opts, *rest)
return self if opts.blank?
if opts.is_a?(Hash)
opts.inject(self) do |relation, (name, value)|
if ::HydraAttribute::HydraAttribute.names_by_entity_type(klass.model_name).include?(name.to_s)
relation, name = relation.clone, name.to_s
relation.hydra_attributes << name
relation.hydra_joins_aliases << hydra_helper.ref_alias(name, value)
relation.joins_values += hydra_helper.build_joins(name, value)
relation.where_values += build_where(hydra_helper.where_options(name, value))
relation
else
relation.where_without_hydra_attribute(name => value)
end
end
else
where_without_hydra_attribute(opts, *rest)
end
end
def build_arel
# remove duplicate columns and add table prefix to all of them
@group_values = hydra_helper.quote_columns(@group_values.uniq.reject(&:blank?))
@order_values = hydra_helper.quote_columns(@order_values.uniq.reject(&:blank?))
# detect hydra attributes from select list
@hydra_select_values, @select_values = @select_values.partition { |value| ::HydraAttribute::HydraAttribute.names_by_entity_type(klass.model_name).include?(value.to_s) }
@hydra_select_values.map!(&:to_s)
@select_values.map!{ |value| hydra_helper.prepend_table_name(value) }
# attributes "id" and "hydra_set_id" are required for models which use HydraAttribute::ActiveRecord model
# but if calculation method is performed, obtained data from database aren't converted to models
# so these attributes should not be forcibly added to query
if !hydra_attribute_performs_calculation && (@hydra_select_values.any? or @select_values.any?)
@select_values << hydra_helper.prepend_table_name(klass.primary_key)
@select_values << hydra_helper.prepend_table_name('hydra_set_id')
end
# Add filter by hydra sets which have all these attributes
if hydra_attributes.any? or @hydra_select_values.any?
hydra_attribute_ids = (hydra_attributes | hydra_select_values).map { |name| hydra_helper.hydra_attribute_id(name) }
hydra_sets = ::HydraAttribute::HydraSet.all_by_entity_type(klass.model_name).select do |hydra_set|
hydra_attribute_ids.all? do |hydra_attribute_id|
hydra_set.has_hydra_attribute_id?(hydra_attribute_id)
end
end
@where_values << table[:hydra_set_id].in(hydra_sets.map(&:id)).or(table[:hydra_set_id].eq(nil))
end
super
end
class Helper
attr_reader :relation, :klass, :connection
def initialize(relation)
@relation, @klass, @connection = relation, relation.klass, relation.connection
end
def attr_eq_column?(attr, column)
attr, column = attr.to_s, column.to_s
[
column,
"#{klass.table_name}.#{column}",
"#{klass.table_name}.#{connection.quote_column_name(column)}",
"#{klass.quoted_table_name}.#{column}",
"#{klass.quoted_table_name}.#{connection.quote_column_name(column)}"
].include?(attr)
end
def prepend_table_name(column, table = klass.table_name)
case column
when String, Symbol
copy = column.to_s.strip
if copy =~ /^\w+$/
connection.quote_table_name(table) + '.' + connection.quote_column_name(copy)
else
column
end
else
column
end
end
def build_joins(name, value)
conn = klass.connection
quoted_alias = conn.quote_table_name(ref_alias(name, value))
[[
"#{join_type(value)} JOIN",
conn.quote_table_name(ref_table(name)),
'AS',
quoted_alias,
'ON',
"#{klass.quoted_table_name}.#{klass.quoted_primary_key}",
'=',
"#{quoted_alias}.#{conn.quote_column_name(:entity_id)}",
'AND',
"#{quoted_alias}.#{conn.quote_column_name(:hydra_attribute_id)}",
'=',
hydra_attribute_id(name)
].join(' ')]
end
def where_options(name, value)
{ref_alias(name, value) => {value: value}}
end
def ref_table(name)
hydra_attribute = hydra_attribute_by_name(name)
"hydra_#{hydra_attribute.backend_type}_#{klass.table_name}"
end
def ref_alias(name, value)
ref_table(name) + '_' + join_type(value).downcase + '_' + name
end
def join_type(value)
if value.nil? or (value.is_a?(Array) and value.include?(nil))
'LEFT'
else
'INNER'
end
end
def hydra_attribute_id(name)
hydra_attribute_by_name(name).id
end
def hydra_attribute_by_name(name)
::HydraAttribute::HydraAttribute.all_by_entity_type(klass.model_name).find do |hydra_attribute|
hydra_attribute.name == name.to_s
end
end
def quote_columns(columns)
columns.map do |column|
column = column.respond_to?(:to_sql) ? column.to_sql : column.to_s
if ::HydraAttribute::HydraAttribute.names_by_entity_type(klass.model_name).include?(column)
join_alias = ref_alias(column, 'inner') # alias for inner join
join_alias = ref_alias(column, nil) unless relation.hydra_joins_aliases.include?(join_alias) # alias for left join
relation.joins_values += build_joins(column, nil) unless relation.hydra_joins_aliases.include?(join_alias)
prepend_table_name('value', join_alias)
else
prepend_table_name(column)
end
end
end
end
private
def hydra_helper
@hydra_helper ||= Helper.new(self)
end
end
end
end
end