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

  #
  # ------- 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
      sort_column = 1 if sort_column == 0
      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})"

        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
          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 "*** 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"

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