module ScopedSearch::QueryBuilder::Field

This module gets included into the Field class to add SQL generation.

Public Instance Methods

construct_join_sql(key_relation, num) click to toggle source

This method construct join statement for a key value table It assume the following table structure

+----------+  +---------+ +--------+
| main     |  | value   | | key    |
| main_pk  |  | main_fk | |        |
|          |  | key_fk  | | key_pk |
+----------+  +---------+ +--------+

uniq name for the joins are needed in case that there is more than one condition on different keys in the same query.

    # File lib/scoped_search/query_builder.rb
382 def construct_join_sql(key_relation, num)
383   join_sql = ""
384   connection = klass.connection
385   key = key_relation.to_s.singularize.to_sym
386 
387   key_table = definition.reflection_by_name(klass, key).table_name
388   value_table = klass.table_name.to_s
389 
390   value_table_fk_key, key_table_pk = reflection_keys(definition.reflection_by_name(klass, key))
391 
392   main_reflection = definition.reflection_by_name(definition.klass, relation)
393   if main_reflection
394     main_table = definition.klass.table_name
395     main_table_pk, value_table_fk_main = reflection_keys(definition.reflection_by_name(definition.klass, relation))
396 
397     join_sql = "\n  INNER JOIN #{connection.quote_table_name(value_table)} #{value_table}_#{num} ON (#{main_table}.#{main_table_pk} = #{value_table}_#{num}.#{value_table_fk_main})"
398     value_table = " #{value_table}_#{num}"
399   end
400   join_sql += "\n INNER JOIN #{connection.quote_table_name(key_table)} #{key_table}_#{num} ON (#{key_table}_#{num}.#{key_table_pk} = #{value_table}.#{value_table_fk_key}) "
401 
402   return join_sql
403 end
construct_simple_join_sql(num) click to toggle source

This method construct join statement for a key value table It assume the following table structure

+----------+  +---------+
| main     |  | key     |
| main_pk  |  | value   |
|          |  | main_fk |
+----------+  +---------+

uniq name for the joins are needed in case that there is more than one condition on different keys in the same query.

    # File lib/scoped_search/query_builder.rb
414 def construct_simple_join_sql(num)
415   connection = klass.connection
416   key_value_table = klass.table_name
417 
418   main_table = definition.klass.table_name
419   main_table_pk, value_table_fk_main = reflection_keys(definition.reflection_by_name(definition.klass, relation))
420 
421   join_sql = "\n  INNER JOIN #{connection.quote_table_name(key_value_table)} #{key_value_table}_#{num} ON (#{connection.quote_table_name(main_table)}.#{connection.quote_column_name(main_table_pk)} = #{key_value_table}_#{num}.#{connection.quote_column_name(value_table_fk_main)})"
422   return join_sql
423 end
reflection_conditions(reflection) click to toggle source
    # File lib/scoped_search/query_builder.rb
432 def reflection_conditions(reflection)
433   return unless reflection
434   conditions = reflection.options[:conditions]
435   conditions ||= "#{reflection.options[:source]}_type = '#{reflection.options[:source_type]}'" if reflection.options[:source] && reflection.options[:source_type]
436   conditions ||= "#{reflection.try(:foreign_type)} = '#{reflection.klass}'" if  reflection.options[:polymorphic]
437   " AND #{conditions}" if conditions
438 end
reflection_keys(reflection) click to toggle source
    # File lib/scoped_search/query_builder.rb
425 def reflection_keys(reflection)
426   pk = reflection.klass.primary_key
427   fk = reflection.options[:foreign_key]
428   fk ||= reflection.foreign_key
429   reflection.macro == :belongs_to ? [fk, pk] : [pk, fk]
430 end
to_ext_method_sql(key, operator, value) { |:include, content| ... } click to toggle source
    # File lib/scoped_search/query_builder.rb
440 def to_ext_method_sql(key, operator, value, &block)
441   raise ScopedSearch::QueryNotSupported, "'#{definition.klass}' doesn't respond to '#{ext_method}'" unless definition.klass.respond_to?(ext_method)
442   begin
443     conditions = definition.klass.send(ext_method.to_sym, key, operator, value)
444   rescue StandardError => e
445     raise ScopedSearch::QueryNotSupported, "external method '#{ext_method}' failed with error: #{e}"
446   end
447   raise ScopedSearch::QueryNotSupported, "external method '#{ext_method}' should return hash" unless conditions.kind_of?(Hash)
448   sql = ''
449   conditions.map do |notification, content|
450     case notification
451       when :include then yield(:include, content)
452       when :joins then yield(:joins, content)
453       when :conditions then sql = content
454       when :parameter then content.map{|c| yield(:parameter, c)}
455     end
456   end
457   return sql
458 end
to_sql(operator = nil) { |finder_option_type, value| ... } click to toggle source

Return an SQL representation for this field. Also make sure that the relation which includes the search field is included in the SQL query.

This function may yield an :include that should be used in the ActiveRecord::Base#find call, to make sure that the field is available for the SQL query.

    # File lib/scoped_search/query_builder.rb
352 def to_sql(operator = nil, &block) # :yields: finder_option_type, value
353   num = rand(1000000)
354   connection = klass.connection
355   if key_relation
356     yield(:joins, construct_join_sql(key_relation, num) )
357     yield(:keycondition, "#{key_klass.table_name}_#{num}.#{connection.quote_column_name(key_field.to_s)} = ?")
358     klass_table_name = relation ? "#{klass.table_name}_#{num}" : klass.table_name
359     return "#{connection.quote_table_name(klass_table_name)}.#{connection.quote_column_name(field.to_s)}"
360   elsif key_field
361     yield(:joins, construct_simple_join_sql(num))
362     yield(:keycondition, "#{key_klass.table_name}_#{num}.#{connection.quote_column_name(key_field.to_s)} = ?")
363     klass_table_name = relation ? "#{klass.table_name}_#{num}" : klass.table_name
364     return "#{connection.quote_table_name(klass_table_name)}.#{connection.quote_column_name(field.to_s)}"
365   elsif relation
366     yield(:include, relation)
367   end
368   column_name = connection.quote_table_name(klass.table_name.to_s) + "." + connection.quote_column_name(field.to_s)
369   column_name = "(#{column_name} >> #{offset*word_size} & #{2**word_size - 1})" if offset
370   column_name
371 end