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
383 def construct_join_sql(key_relation, num)
384   join_sql = ""
385   connection = klass.connection
386   key = key_relation.to_s.singularize.to_sym
387 
388   key_table = definition.reflection_by_name(klass, key).table_name
389   value_table = klass.table_name.to_s
390 
391   value_table_fk_key, key_table_pk = reflection_keys(definition.reflection_by_name(klass, key))
392 
393   main_reflection = definition.reflection_by_name(definition.klass, relation)
394   if main_reflection
395     main_table = definition.klass.table_name
396     main_table_pk, value_table_fk_main = reflection_keys(definition.reflection_by_name(definition.klass, relation))
397 
398     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})"
399     value_table = " #{value_table}_#{num}"
400   end
401   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}) "
402 
403   return join_sql
404 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
415 def construct_simple_join_sql(num)
416   connection = klass.connection
417   key_value_table = klass.table_name
418 
419   main_table = definition.klass.table_name
420   main_table_pk, value_table_fk_main = reflection_keys(definition.reflection_by_name(definition.klass, relation))
421 
422   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)})"
423   return join_sql
424 end
reflection_conditions(reflection) click to toggle source
    # File lib/scoped_search/query_builder.rb
434 def reflection_conditions(reflection)
435   return unless reflection
436   conditions = reflection.options[:conditions]
437   conditions ||= "#{reflection.options[:source]}_type = '#{reflection.options[:source_type]}'" if reflection.options[:source] && reflection.options[:source_type]
438   conditions ||= "#{reflection.try(:foreign_type)} = '#{reflection.klass}'" if  reflection.options[:polymorphic]
439   " AND #{conditions}" if conditions
440 end
reflection_keys(reflection) click to toggle source
    # File lib/scoped_search/query_builder.rb
426 def reflection_keys(reflection)
427   pk = reflection.klass.primary_key
428   fk = reflection.options[:foreign_key]
429   # activerecord prior to 3.1 doesn't respond to foreign_key method and hold the key name in the reflection primary key
430   fk ||= reflection.respond_to?(:foreign_key) ? reflection.foreign_key : reflection.primary_key_name
431   reflection.macro == :belongs_to ? [fk, pk] : [pk, fk]
432 end
to_ext_method_sql(key, operator, value) { |:include, content| ... } click to toggle source
    # File lib/scoped_search/query_builder.rb
442 def to_ext_method_sql(key, operator, value, &block)
443   raise ScopedSearch::QueryNotSupported, "'#{definition.klass}' doesn't respond to '#{ext_method}'" unless definition.klass.respond_to?(ext_method)
444   begin
445     conditions = definition.klass.send(ext_method.to_sym, key, operator, value)
446   rescue StandardError => e
447     raise ScopedSearch::QueryNotSupported, "external method '#{ext_method}' failed with error: #{e}"
448   end
449   raise ScopedSearch::QueryNotSupported, "external method '#{ext_method}' should return hash" unless conditions.kind_of?(Hash)
450   sql = ''
451   conditions.map do |notification, content|
452     case notification
453       when :include then yield(:include, content)
454       when :joins then yield(:joins, content)
455       when :conditions then sql = content
456       when :parameter then content.map{|c| yield(:parameter, c)}
457     end
458   end
459   return sql
460 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
353 def to_sql(operator = nil, &block) # :yields: finder_option_type, value
354   num = rand(1000000)
355   connection = klass.connection
356   if key_relation
357     yield(:joins, construct_join_sql(key_relation, num) )
358     yield(:keycondition, "#{key_klass.table_name}_#{num}.#{connection.quote_column_name(key_field.to_s)} = ?")
359     klass_table_name = relation ? "#{klass.table_name}_#{num}" : klass.table_name
360     return "#{connection.quote_table_name(klass_table_name)}.#{connection.quote_column_name(field.to_s)}"
361   elsif key_field
362     yield(:joins, construct_simple_join_sql(num))
363     yield(:keycondition, "#{key_klass.table_name}_#{num}.#{connection.quote_column_name(key_field.to_s)} = ?")
364     klass_table_name = relation ? "#{klass.table_name}_#{num}" : klass.table_name
365     return "#{connection.quote_table_name(klass_table_name)}.#{connection.quote_column_name(field.to_s)}"
366   elsif relation
367     yield(:include, relation)
368   end
369   column_name = connection.quote_table_name(klass.table_name.to_s) + "." + connection.quote_column_name(field.to_s)
370   column_name = "(#{column_name} >> #{offset*word_size} & #{2**word_size - 1})" if offset
371   column_name
372 end