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
363 def construct_join_sql(key_relation, num)
364   join_sql = ""
365   connection = klass.connection
366   key = key_relation.to_s.singularize.to_sym
367 
368   key_table = definition.reflection_by_name(klass, key).table_name
369   value_table = klass.table_name.to_s
370 
371   value_table_fk_key, key_table_pk = reflection_keys(definition.reflection_by_name(klass, key))
372 
373   main_reflection = definition.reflection_by_name(definition.klass, relation)
374   if main_reflection
375     main_table = definition.klass.table_name
376     main_table_pk, value_table_fk_main = reflection_keys(definition.reflection_by_name(definition.klass, relation))
377 
378     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})"
379     value_table = " #{value_table}_#{num}"
380   end
381   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}) "
382 
383   return join_sql
384 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
395 def construct_simple_join_sql(num)
396   connection = klass.connection
397   key_value_table = klass.table_name
398 
399   main_table = definition.klass.table_name
400   main_table_pk, value_table_fk_main = reflection_keys(definition.reflection_by_name(definition.klass, relation))
401 
402   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)})"
403   return join_sql
404 end
reflection_conditions(reflection) click to toggle source
    # File lib/scoped_search/query_builder.rb
414 def reflection_conditions(reflection)
415   return unless reflection
416   conditions = reflection.options[:conditions]
417   conditions ||= "#{reflection.options[:source]}_type = '#{reflection.options[:source_type]}'" if reflection.options[:source] && reflection.options[:source_type]
418   conditions ||= "#{reflection.try(:foreign_type)} = '#{reflection.klass}'" if  reflection.options[:polymorphic]
419   " AND #{conditions}" if conditions
420 end
reflection_keys(reflection) click to toggle source
    # File lib/scoped_search/query_builder.rb
406 def reflection_keys(reflection)
407   pk = reflection.klass.primary_key
408   fk = reflection.options[:foreign_key]
409   # activerecord prior to 3.1 doesn't respond to foreign_key method and hold the key name in the reflection primary key
410   fk ||= reflection.respond_to?(:foreign_key) ? reflection.foreign_key : reflection.primary_key_name
411   reflection.macro == :belongs_to ? [fk, pk] : [pk, fk]
412 end
to_ext_method_sql(key, operator, value) { |:include, content| ... } click to toggle source
    # File lib/scoped_search/query_builder.rb
422 def to_ext_method_sql(key, operator, value, &block)
423   raise ScopedSearch::QueryNotSupported, "'#{definition.klass}' doesn't respond to '#{ext_method}'" unless definition.klass.respond_to?(ext_method)
424   begin
425     conditions = definition.klass.send(ext_method.to_sym, key, operator, value)
426   rescue StandardError => e
427     raise ScopedSearch::QueryNotSupported, "external method '#{ext_method}' failed with error: #{e}"
428   end
429   raise ScopedSearch::QueryNotSupported, "external method '#{ext_method}' should return hash" unless conditions.kind_of?(Hash)
430   sql = ''
431   conditions.map do |notification, content|
432     case notification
433       when :include then yield(:include, content)
434       when :joins then yield(:joins, content)
435       when :conditions then sql = content
436       when :parameter then content.map{|c| yield(:parameter, c)}
437     end
438   end
439   return sql
440 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
333 def to_sql(operator = nil, &block) # :yields: finder_option_type, value
334   num = rand(1000000)
335   connection = klass.connection
336   if key_relation
337     yield(:joins, construct_join_sql(key_relation, num) )
338     yield(:keycondition, "#{key_klass.table_name}_#{num}.#{connection.quote_column_name(key_field.to_s)} = ?")
339     klass_table_name = relation ? "#{klass.table_name}_#{num}" : klass.table_name
340     return "#{connection.quote_table_name(klass_table_name)}.#{connection.quote_column_name(field.to_s)}"
341   elsif key_field
342     yield(:joins, construct_simple_join_sql(num))
343     yield(:keycondition, "#{key_klass.table_name}_#{num}.#{connection.quote_column_name(key_field.to_s)} = ?")
344     klass_table_name = relation ? "#{klass.table_name}_#{num}" : klass.table_name
345     return "#{connection.quote_table_name(klass_table_name)}.#{connection.quote_column_name(field.to_s)}"
346   elsif relation
347     yield(:include, relation)
348   end
349   column_name = connection.quote_table_name(klass.table_name.to_s) + "." + connection.quote_column_name(field.to_s)
350   column_name = "(#{column_name} >> #{offset*word_size} & #{2**word_size - 1})" if offset
351   column_name
352 end