Module: DataTablesController::ClassMethods

Defined in:
lib/data_tables.rb

Instance Method Summary collapse

Instance Method Details

#datatables_source(action, model, *attrs) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/data_tables.rb', line 9

def datatables_source(action, model,  *attrs)
  modelCls = Kernel.const_get(model.to_s.split("_").collect(&:capitalize).join)
  modelAttrs = nil
  if modelCls < Ohm::Model
    if Gem.loaded_specs['ohm'].version == Gem::Version.create('0.1.5')
      modelAttrs = Hash[*modelCls.new.attributes.collect { |v| [v.to_s, nil] }.flatten]
    else
      modelAttrs = {}
    end
  else
    modelAttrs = modelCls.new.attributes
  end
  columns = []
  modelAttrs.each_key { |k| columns << k }

  options = {}
  attrs.each do |option|
    option.each { |k,v| options[k] = v }
  end

  # override columns
  columns = options_to_columns(options) if options[:columns]

  # define columns so they are accessible from the helper
  define_columns(modelCls, columns, action)

  # define method that returns the data for the table
  define_datatables_action(self, action, modelCls, columns, options)
end

#define_datatables_action(controller, action, modelCls, columns, options = {}) ⇒ Object

named_scope is a combination table that include everything shown in UI. except is the codition used for Ohm’s except method, it should be key-value format, such as [[‘name’, ‘bluesocket’],].



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/data_tables.rb', line 42

def define_datatables_action(controller, action, modelCls, columns, options = {})
  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]
  order_sort = options[:order_sort]

  #
  # ------- 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

      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
      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})"

        retried = 0
        if Tire.index(elastic_index_name){exists?}.response.code != 404
          begin
            controller_instance = self
            results = Tire.search(elastic_index_name) do
              query do
                boolean do
                  if search_condition && retried < 2
                    must { match :_all, search_condition, type: 'phrase_prefix' }
                  else
                    must { all }
                  end

                  except.each do |expt|
                    must_not { match expt[0].to_sym, expt[1].to_s }
                  end
                end
              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 do
                boolean do
                  must { all }
                  except.each do |expt|
                    must_not { match expt[0].to_sym, expt[1].to_s }
                  end
                end
              end
              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
          logger.debug "Index #{elastic_index_name} does not exists yet in ES."
        end
      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)
        total_display_records = objects.size
        if Gem.loaded_specs['ohm'].version == Gem::Version.create('0.1.5')
          objects = objects.sort(:by => columns[sort_column][:name].to_sym,
                                 :order => "ALPHA " + params[:sSortDir_0].capitalize,
                                 :start => params[:iDisplayStart].to_i,
                                 :limit => params[:iDisplayLength].to_i)
        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
  # ------- /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

    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 "*** (datatables:#{__LINE__}) Using ElasticSearch for #{modelCls.name}"
        objects =  []

        condstr = nil
        starttime_str = nil
        endtime_str = nil
        unless params[:sSearch].blank?
          sort_column_id = params[:iSortCol_0].to_i
          sort_column = columns[sort_column_id]
          if sort_column && sort_column.has_key?(:attribute)
            condstr = params[:sSearch].gsub(/_/, '\\\\_').gsub(/%/, '\\\\%')
          end
        end
        unless params[:sStarttime].blank? || params[:sEndtime].blank?
          sort_column_id = params[:iSortCol_0].to_i
          sort_column = columns[sort_column_id]
          if sort_column && sort_column.has_key?(:attribute)
            starttime = params[:sStarttime]
            endtime = params[:sEndtime]
          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'

        begin
          query = Proc.new do
            query do
              boolean do
                if condstr
                  must { match :_all, condstr, type: 'phrase_prefix' }          
                else
                  must { all }
                end
                except.each do |expt|
                  must_not { match expt[0].to_sym, expt[1].to_s }
                end
              end
            end
            filter(:range, created_at: {gte: starttime,lte: endtime}) unless starttime.blank? || endtime.blank?
            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"

      define_method action.to_sym do
        condition_local = ''
        condition = ''

        unless params[:sSearch].blank?
          sort_column_id = params[:iSortCol_0].to_i
          sort_column = columns[sort_column_id]
          condstr = params[:sSearch].strip.gsub(/%/, '%%').gsub(/'/,"''")

          search_columns = options[:columns].map{|e| e.class == Symbol ? e : e[:attribute] }.compact
          condition = search_columns.map do |column_name|
            " ((text(#{column_name}) ILIKE '%#{condstr}%')) "
          end.compact.join(" OR ")
          condition = "(#{condition})" unless condition.blank?
        end
        unless params[:sStarttime].blank? || params[:sEndtime].blank?
          sort_column_id = params[:iSortCol_0].to_i
          sort_column = columns[sort_column_id]
          if sort_column && sort_column.has_key?(:attribute)
            starttime = params[:sStarttime]
            endtime = params[:sEndtime]
          end
          condition = condition + "AND" unless condition.blank?
          condition = condition + "(last_seen BETWEEN  '#{starttime}' AND '#{endtime}') " 
        end
        condition_local = "(#{condition})" unless condition.blank?

        # 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
        else
          if add_search_option == true
            if conditions != []
              conditions.pop
              add_search_option = false
            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 ")
        end
        sort_column = params[:iSortCol_0].to_i
        current_page = (params[:iDisplayStart].to_i/params[:iDisplayLength].to_i rescue 0)+1
        order = if order_sort.respond_to?(:call)
                  order_sort.call(columns[sort_column][:name], params[:sSortDir_0])
                else
                  "#{columns[sort_column][:name]} #{params[:sSortDir_0]}"
                end

        if named_scope
            objects = modelCls.send(named_scope, *args).paginate(:page => current_page,
                                        :order => "#{order}",
                                        :conditions => conditions.join(" AND "),
                                        :per_page => params[:iDisplayLength])
        else
            objects = modelCls.paginate(:page => current_page,
                                        :order => order,
                                        :conditions => conditions.join(" AND "),
                                        :per_page => params[:iDisplayLength])
        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
        #
        # ------- /Postgres ----------- #
        #
      end
    end
  end
end