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