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