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]