lib/data_tables.rb in data_tables-0.1.7 vs lib/data_tables.rb in data_tables-0.1.9
- old
+ new
@@ -1,6 +1,7 @@
require "data_tables/data_tables_helper"
+
module DataTablesController
def self.included(cls)
cls.extend(ClassMethods)
end
@@ -42,42 +43,107 @@
conditions = options[:conditions] || []
scope = options[:scope] || :domain
named_scope = options[:named_scope]
named_scope_args = options[:named_scope_args]
except = options[:except]
+ es_block = options[:es_block]
+ #
+ # ------- Ohm ----------- #
+ #
if modelCls < Ohm::Model
define_method action.to_sym do
+ logger.debug "[tire] (datatable:#{__LINE__}) #{action.to_sym} #{modelCls} < Ohm::Model"
+
if scope == :domain
domain = ActiveRecord::Base.connection.schema_search_path.to_s.split(",")[0]
return if domain.nil?
end
search_condition = params[:sSearch].blank? ? nil : params[:sSearch].to_s
- records = scope == :domain ? modelCls.find(:domain => domain) : modelCls.all
- if except
- except.each do |f|
- records = records.except(f[0].to_sym => f[1])
- end
- end
- total_records = records.size
+
sort_column = params[:iSortCol_0].to_i
sort_column = 1 if sort_column == 0
current_page = (params[:iDisplayStart].to_i/params[:iDisplayLength].to_i rescue 0) + 1
- objects = nil
- if search_condition.nil?
- if Gem.loaded_specs['ohm'].version == Gem::Version.create('0.1.5')
- objects = records.sort_by(columns[sort_column][:name].to_sym,
- :order => "ALPHA " + params[:sSortDir_0].capitalize,
- :start => params[:iDisplayStart].to_i,
- :limit => params[:iDisplayLength].to_i)
+ per_page = params[:iDisplayLength] || 10
+ per_page = per_page.to_i
+ sort_dir = params[:sSortDir_0] || 'desc'
+ column_name_sym = columns[sort_column][:name].to_sym
+
+ objects = []
+ total_display_records = 0
+ total_records = 0
+
+ if defined? Tire
+ #
+ # ----------- Elasticsearch/Tire for Ohm ----------- #
+ #
+ elastic_index_name = "#{Tire::Model::Search.index_prefix}#{modelCls.to_s.underscore}"
+ logger.debug "*** (datatable:#{__LINE__}) Using tire for search #{modelCls} (#{elastic_index_name})"
+
+ search_condition = elasticsearch_sanitation(search_condition, except)
+ just_excepts = except ? elasticsearch_sanitation(nil, except) : "*"
+ logger.debug "*** search_condition = #{search_condition}; sort by #{column_name_sym}:#{sort_dir}; domain=`#{domain.inspect}'"
+
+ retried = 0
+ if Tire.index(elastic_index_name){exists?}.response.code != 404
+ begin
+ controller_instance = self
+ results = Tire.search(elastic_index_name) do
+ # retry #2 exclude search terms (and sorting) from search query
+ if retried < 2
+ query { string search_condition }
+ else
+ query { string just_excepts }
+ end
+
+ # retry #1 exclude sorting from search query
+ sort{ by column_name_sym, sort_dir } if retried < 1
+
+ filter(:term, domain: domain) unless domain.blank?
+ if es_block.is_a?(Symbol)
+ controller_instance.send(es_block, self)
+ else
+ es_block.call(self) if es_block.respond_to?(:call)
+ end
+ from (current_page-1) * per_page
+ size per_page
+ end.results
+
+ objects = results.map{ |r| modelCls[r._id] }.compact
+ total_display_records = results.total
+
+
+ total_records = Tire.search(elastic_index_name, search_type: 'count') do
+ query { string just_excepts }
+ filter(:term, domain: domain) unless domain.blank?
+ es_block.call(self) if es_block.respond_to?(:call)
+ end.results.total
+ rescue Tire::Search::SearchRequestFailed => e
+ if retried < 2
+ retried += 1
+ logger.info "Will retry(#{retried}) again because #{e.inspect}"
+ retry
+ end
+ logger.info "*** ERROR: Tire::Search::SearchRequestFailed => #{e.inspect}"
+ end
else
- objects = records.sort_by(columns[sort_column][:name].to_sym,
- :order => "ALPHA " + params[:sSortDir_0].capitalize,
- :limit => [params[:iDisplayStart].to_i, params[:iDisplayLength].to_i])
+ logger.debug "Index #{elastic_index_name} does not exists yet in ES."
end
- total_display_records = total_records
else
+ #
+ # -------- Redis/Lunar search --------------- #
+ #
+ logger.debug "*** (datatable:#{__LINE__}) Using Redis/Lunar for search #{modelCls} (#{elastic_index_name})"
+ records = scope == :domain ? modelCls.find(:domain => domain) : modelCls.all
+ if except
+ except.each do |f|
+ records = records.except(f[0].to_sym => f[1])
+ end
+ end
+ total_records = records.size
+
+ logger.debug "*** (datatable:#{__LINE__}) NOT using tire for search"
options = {}
domain_id = domain.split("_")[1].to_i if scope == :domain
options[:domain] = domain_id .. domain_id if scope == :domain
options[:fuzzy] = {columns[sort_column][:name].to_sym => search_condition}
objects = Lunar.search(modelCls, options)
@@ -90,87 +156,165 @@
else
objects = objects.sort(:by => columns[sort_column][:name].to_sym,
:order => "ALPHA " + params[:sSortDir_0].capitalize,
:limit => [params[:iDisplayStart].to_i, params[:iDisplayLength].to_i])
end
+ # -------- Redis/Lunar search --------------- #
end
+
data = objects.collect do |instance|
columns.collect { |column| datatables_instance_get_value(instance, column) }
end
render :text => {:iTotalRecords => total_records,
:iTotalDisplayRecords => total_display_records,
:aaData => data,
:sEcho => params[:sEcho].to_i}.to_json
end
- else
+ # ------- /Ohm ----------- #
+ else # Non-ohm models
# add_search_option will determine whether the search text is empty or not
init_conditions = conditions.clone
add_search_option = false
- define_method action.to_sym do
- condition_local = ''
- unless params[:sSearch].blank?
- sort_column_id = params[:iSortCol_0].to_i
- sort_column_id = 1 if sort_column_id == 0
- sort_column = columns[sort_column_id]
- if sort_column && sort_column.has_key?(:attribute)
- condstr = params[:sSearch].gsub(/_/, '\\\\_').gsub(/%/, '\\\\%')
- condition_local = "(text(#{sort_column[:name]}) ILIKE '#{condstr}%')"
+
+ if modelCls.ancestors.any?{|ancestor| ancestor.name == "Tire::Model::Search"}
+ #
+ # ------- Elasticsearch ----------- #
+ #
+ define_method action.to_sym do
+ domain_name = ActiveRecord::Base.connection.schema_search_path.to_s.split(",")[0]
+ logger.debug "*** Using ElasticSearch for #{modelCls.name}"
+ objects = []
+
+ condstr = ""
+ unless params[:sSearch].blank?
+ sort_column_id = params[:iSortCol_0].to_i
+ sort_column_id = 1 if sort_column_id == 0
+ sort_column = columns[sort_column_id]
+ if sort_column && sort_column.has_key?(:attribute)
+ condstr = params[:sSearch].gsub(/_/, '\\\\_').gsub(/%/, '\\\\%')
+ end
end
+
+ sort_column = params[:iSortCol_0].to_i
+ current_page = (params[:iDisplayStart].to_i/params[:iDisplayLength].to_i rescue 0)+1
+ per_page = params[:iDisplayLength] || 10
+ column_name = columns[sort_column][:name] || 'message'
+ sort_dir = params[:sSortDir_0] || 'desc'
+
+ condstr = elasticsearch_sanitation(condstr, except)
+
+ begin
+ query = Proc.new do
+ query { string(condstr) }
+ filter(:term, domain: domain_name) unless domain_name.blank?
+ es_block.call(self) if es_block.respond_to?(:call)
+ end
+
+ results = modelCls.search(page: current_page,
+ per_page: per_page,
+ sort: "#{column_name}:#{sort_dir}",
+ &query)
+ objects = results.to_a
+ total_display_records = results.total
+ total_records = modelCls.search(search_type: 'count') do
+ filter(:term, domain: domain_name) unless domain_name.blank?
+ es_block.call(self) if es_block.respond_to?(:call)
+ end.total
+ rescue Tire::Search::SearchRequestFailed => e
+ logger.debug "[Tire::Search::SearchRequestFailed] #{e.inspect}\n#{e.backtrace.join("\n")}"
+ objects = []
+ total_display_records = 0
+ total_records = 0
+ end
+
+ data = objects.collect do |instance|
+ columns.collect { |column| datatables_instance_get_value(instance, column) }
+ end
+
+ render :text => {:iTotalRecords => total_records,
+ :iTotalDisplayRecords => total_display_records,
+ :aaData => data,
+ :sEcho => params[:sEcho].to_i}.to_json
end
+ # ------- /Elasticsearch ----------- #
+ else
+ #
+ # ------- Postgres ----------- #
+ #
+ logger.debug "(datatable) #{action.to_sym} #{modelCls} < ActiveRecord"
- # We just need one conditions string for search at a time. Every time we input
- # something else in the search bar we will pop the previous search condition
- # string and push the new string.
- if condition_local != ''
- if add_search_option == false
- conditions << condition_local
- add_search_option = true
- else
- if conditions != []
- conditions.pop
+ define_method action.to_sym do
+ condition_local = ''
+ unless params[:sSearch].blank?
+ sort_column_id = params[:iSortCol_0].to_i
+ sort_column_id = 1 if sort_column_id == 0
+ sort_column = columns[sort_column_id]
+ condstr = params[:sSearch].strip.gsub(/_/, '\\\\_').gsub(/%/, '\\\\%')
+
+ search_columns = options[:columns].map{|e| e.class == Symbol ? e : nil }.compact
+ condition_local = search_columns.map do |column_name|
+ " ((text(#{column_name}) ILIKE '%#{condstr}%')) "
+ end.compact.join(" OR ")
+ condition_local = " ( #{condition_local} ) " unless condition_local.blank?
+ end
+
+ # We just need one conditions string for search at a time. Every time we input
+ # something else in the search bar we will pop the previous search condition
+ # string and push the new string.
+ if condition_local != ''
+ if add_search_option == false
conditions << condition_local
+ add_search_option = true
+ else
+ if conditions != []
+ conditions.pop
+ conditions << condition_local
+ end
end
- end
- else
- if add_search_option == true
- if conditions != []
- conditions.pop
- add_search_option = false
+ else
+ if add_search_option == true
+ if conditions != []
+ conditions.pop
+ add_search_option = false
+ end
end
end
- end
- if named_scope
- args = named_scope_args ? Array(self.send(named_scope_args)) : []
- total_records = modelCls.send(named_scope, *args).count :conditions => init_conditions.join(" AND ")
- total_display_records = modelCls.send(named_scope, *args).count :conditions => conditions.join(" AND ")
- else
- total_records = modelCls.count :conditions => init_conditions.join(" AND ")
- total_display_records = modelCls.count :conditions => conditions.join(" AND ")
+ if named_scope
+ args = named_scope_args ? Array(self.send(named_scope_args)) : []
+ total_records = modelCls.send(named_scope, *args).count :conditions => init_conditions.join(" AND ")
+ total_display_records = modelCls.send(named_scope, *args).count :conditions => conditions.join(" AND ")
+ else
+ total_records = modelCls.count :conditions => init_conditions.join(" AND ")
+ total_display_records = modelCls.count :conditions => conditions.join(" AND ")
+ end
+ sort_column = params[:iSortCol_0].to_i
+ sort_column = 1 if sort_column == 0
+ current_page = (params[:iDisplayStart].to_i/params[:iDisplayLength].to_i rescue 0)+1
+ if named_scope
+ objects = modelCls.send(named_scope, *args).paginate(:page => current_page,
+ :order => "#{columns[sort_column][:name]} #{params[:sSortDir_0]}",
+ :conditions => conditions.join(" AND "),
+ :per_page => params[:iDisplayLength])
+ else
+ objects = modelCls.paginate(:page => current_page,
+ :order => "#{columns[sort_column][:name]} #{params[:sSortDir_0]}",
+ :conditions => conditions.join(" AND "),
+ :per_page => params[:iDisplayLength])
+ end
+ #logger.info("------conditions is #{conditions}")
+ data = objects.collect do |instance|
+ columns.collect { |column| datatables_instance_get_value(instance, column) }
+ end
+ render :text => {:iTotalRecords => total_records,
+ :iTotalDisplayRecords => total_display_records,
+ :aaData => data,
+ :sEcho => params[:sEcho].to_i}.to_json
+ #
+ # ------- /Postgres ----------- #
+ #
end
- sort_column = params[:iSortCol_0].to_i
- sort_column = 1 if sort_column == 0
- current_page = (params[:iDisplayStart].to_i/params[:iDisplayLength].to_i rescue 0)+1
- if named_scope
- objects = modelCls.send(named_scope, *args).paginate(:page => current_page,
- :order => "#{columns[sort_column][:name]} #{params[:sSortDir_0]}",
- :conditions => conditions.join(" AND "),
- :per_page => params[:iDisplayLength])
- else
- objects = modelCls.paginate(:page => current_page,
- :order => "#{columns[sort_column][:name]} #{params[:sSortDir_0]}",
- :conditions => conditions.join(" AND "),
- :per_page => params[:iDisplayLength])
- end
- #logger.info("------conditions is #{conditions}")
- data = objects.collect do |instance|
- columns.collect { |column| datatables_instance_get_value(instance, column) }
- end
- render :text => {:iTotalRecords => total_records,
- :iTotalDisplayRecords => total_display_records,
- :aaData => data,
- :sEcho => params[:sEcho].to_i}.to_json
end
end
end
private
@@ -182,11 +326,11 @@
columns = []
options[:columns].each do |column|
if column.kind_of? Symbol # a column from the database, we don't need to do anything
columns << {:name => column, :attribute => column}
elsif column.kind_of? Hash
- col_hash = { :name => column[:name], :special => column }
+ col_hash = { :name => column[:name], :special => column }
col_hash[:attribute] = column[:attribute] if column[:attribute]
columns << col_hash
end
end
columns
@@ -207,9 +351,21 @@
end
columnNames
end
end
+ end
+
+ def elasticsearch_sanitation(search_string, except)
+ logger.debug "*** elasticsearch_sanitation.before = `#{search_string}'"
+ search_string = '*' if search_string.blank?
+ search_string.strip!
+ numerical_search = (search_string.split.count > 1) ? "" : "OR *#{search_string.gsub(":","\\:")}*"
+ search_string = "(\"*#{search_string}*\" #{numerical_search}) " unless search_string =~ /(\*|\")/
+ exceptions = except.map { |f| "NOT #{f[0]}:\"#{f[1]}\""}.join(" AND ") if except
+ search_string += " AND " + exceptions if exceptions
+ logger.debug "*** elasticsearch_sanitation.after = `#{search_string}'"
+ search_string
end
# gets the value for a column and row
def datatables_instance_get_value(instance, column)
if column[:special]