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
330 def construct_join_sql(key_relation, num)
331   join_sql = ""
332   connection = klass.connection
333   key = key_relation.to_s.singularize.to_sym
334 
335   key_table = definition.reflection_by_name(klass, key).table_name
336   value_table = klass.table_name.to_s
337 
338   value_table_fk_key, key_table_pk = reflection_keys(definition.reflection_by_name(klass, key))
339 
340   main_reflection = definition.reflection_by_name(definition.klass, relation)
341   if main_reflection
342     main_table = definition.klass.table_name
343     main_table_pk, value_table_fk_main = reflection_keys(definition.reflection_by_name(definition.klass, relation))
344 
345     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})"
346     value_table = " #{value_table}_#{num}"
347   end
348   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}) "
349 
350   return join_sql
351 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
362 def construct_simple_join_sql(num)
363   connection = klass.connection
364   key_value_table = klass.table_name
365 
366   main_table = definition.klass.table_name
367   main_table_pk, value_table_fk_main = reflection_keys(definition.reflection_by_name(definition.klass, relation))
368 
369   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)})"
370   return join_sql
371 end
reflection_conditions(reflection) click to toggle source
    # File lib/scoped_search/query_builder.rb
381 def reflection_conditions(reflection)
382   return unless reflection
383   conditions = reflection.options[:conditions]
384   conditions ||= "#{reflection.options[:source]}_type = '#{reflection.options[:source_type]}'" if reflection.options[:source] && reflection.options[:source_type]
385   conditions ||= "#{reflection.try(:foreign_type)} = '#{reflection.klass}'" if  reflection.options[:polymorphic]
386   " AND #{conditions}" if conditions
387 end
reflection_keys(reflection) click to toggle source
    # File lib/scoped_search/query_builder.rb
373 def reflection_keys(reflection)
374   pk = reflection.klass.primary_key
375   fk = reflection.options[:foreign_key]
376   # activerecord prior to 3.1 doesn't respond to foreign_key method and hold the key name in the reflection primary key
377   fk ||= reflection.respond_to?(:foreign_key) ? reflection.foreign_key : reflection.primary_key_name
378   reflection.macro == :belongs_to ? [fk, pk] : [pk, fk]
379 end
to_ext_method_sql(key, operator, value) { |:include, content| ... } click to toggle source
    # File lib/scoped_search/query_builder.rb
389 def to_ext_method_sql(key, operator, value, &block)
390   raise ScopedSearch::QueryNotSupported, "'#{definition.klass}' doesn't respond to '#{ext_method}'" unless definition.klass.respond_to?(ext_method)
391   conditions = definition.klass.send(ext_method.to_sym,key, operator, value) rescue {}
392   raise ScopedSearch::QueryNotSupported, "external method '#{ext_method}' should return hash" unless conditions.kind_of?(Hash)
393   sql = ''
394   conditions.map do |notification, content|
395     case notification
396       when :include then yield(:include, content)
397       when :joins then yield(:joins, content)
398       when :conditions then sql = content
399       when :parameter then content.map{|c| yield(:parameter, c)}
400     end
401   end
402   return sql
403 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
300 def to_sql(operator = nil, &block) # :yields: finder_option_type, value
301   num = rand(1000000)
302   connection = klass.connection
303   if key_relation
304     yield(:joins, construct_join_sql(key_relation, num) )
305     yield(:keycondition, "#{key_klass.table_name}_#{num}.#{connection.quote_column_name(key_field.to_s)} = ?")
306     klass_table_name = relation ? "#{klass.table_name}_#{num}" : klass.table_name
307     return "#{connection.quote_table_name(klass_table_name)}.#{connection.quote_column_name(field.to_s)}"
308   elsif key_field
309     yield(:joins, construct_simple_join_sql(num))
310     yield(:keycondition, "#{key_klass.table_name}_#{num}.#{connection.quote_column_name(key_field.to_s)} = ?")
311     klass_table_name = relation ? "#{klass.table_name}_#{num}" : klass.table_name
312     return "#{connection.quote_table_name(klass_table_name)}.#{connection.quote_column_name(field.to_s)}"
313   elsif relation
314     yield(:include, relation)
315   end
316   column_name = connection.quote_table_name(klass.table_name.to_s) + "." + connection.quote_column_name(field.to_s)
317   column_name = "(#{column_name} >> #{offset*word_size} & #{2**word_size - 1})" if offset
318   column_name
319 end