class ActiveRecordDataTables

Public Class Methods

new(args) click to toggle source
# File lib/active-record-data-tables.rb, line 4
def initialize(args)
  @args = args
  @params = @args[:params]
  @filter_params = @args[:filter_params]
  @force_filter_for = @args[:force_filter_for]
  @dts = @params[:dataTables]
  @columns = @args[:columns]

  @model = @args[:model]
  raise "No ':model' was given." unless @model
  @model_method_name = StringCases.camel_to_snake(@model.name)

  @joins = {}
  @sort_columns = [:title, :name]
  @executed = false
end

Public Instance Methods

execute(args = {}) click to toggle source
# File lib/active-record-data-tables.rb, line 21
def execute(args = {})
  @executed = true
  @query = args[:search] || @model

  filter unless args[:skip_filter]
  sorting
  limit

  return @query
end
json(final_query = nil) click to toggle source
# File lib/active-record-data-tables.rb, line 32
def json(final_query = nil)
  unless final_query
    execute unless @executed
    final_query = @query
  end

  dis_rec_count = final_query.limit(nil).count
  dis_rec_count = dis_rec_count.length if dis_rec_count.is_a?(Hash)

  res = {
    :sEcho => @dts[:sEcho] ? @dts[:sEcho].to_i : 1,
    :iTotalRecords => @model.count,
    :iTotalDisplayRecords => dis_rec_count,
    :aaData => []
  }
end

Private Instance Methods

escape(str) click to toggle source

Escapes the given string to be used in a SQL statement.

# File lib/active-record-data-tables.rb, line 154
def escape(str)
  return ActiveRecord::Base.connection.quote_string(str)
end
escape_col(name) click to toggle source

Escapes the given name as a column and checks for SQL-injections.

# File lib/active-record-data-tables.rb, line 159
def escape_col(name)
  raise "Possible SQL injection hack: '#{name}'." unless name.to_s.match(/\A[A-z\d_]+\Z/)
  return name
end
filter() click to toggle source

Checks the given parameters for filters and manipulates the query based on it.

# File lib/active-record-data-tables.rb, line 52
def filter
  @filter_params.each do |key, val|
    # Strip empty values from the array if the given value is an array.
    val.select!{ |val| !val.to_s.empty? } if val.is_a?(Array)

    # Convert key if it starts with a column name.
    key = key.slice(@model_method_name.length + 1, key.length) if key.start_with?("#{@model_method_name}_")

    if @force_filter_for && @force_filter_for.include?(key)
      ret = @args[:filter].call(:key => key, :val => val, :query => @query)
      @query = ret if ret
    elsif @model.column_names.include?(key)
      if val.is_a?(Array) && val.empty?
        # Ignore.
      else
        @query = @query.where(key => val)
      end
    elsif match = key.to_s.match(/^(.+)_like$/) and @model.column_names.include?(match[1])
      next if val.blank?
      table = @model.arel_table
      
      val.to_s.strip.split(/\s+/).each do |str|
        @query = @query.where(table[match[1].to_sym].matches("%#{escape(str)}%"))
      end
    elsif @args[:filter]
      ret = @args[:filter].call(:key => key, :val => val, :query => @query)
      @query = ret if ret
    else
      raise "Dont know what to do regarding filter with key: '#{key}'."
    end
  end
end
limit() click to toggle source

Checks the given parameters for limits and manipulates the query based on it.

# File lib/active-record-data-tables.rb, line 86
def limit
  raise "'iDisplayStart' was not given? #{@dts}" unless @dts.key?("iDisplayStart")
  raise "'iDisplayEnd' was not given? #{@dts}" unless @dts.key?("iDisplayLength")

  disp_start = @dts["iDisplayStart"].to_i
  disp_length = @dts["iDisplayLength"].to_i

  @query = @query.page((disp_start / disp_length) + 1).per(disp_length)
end
sorting() click to toggle source

Checks the given parameters for sorting and manipulates the query based on it.

# File lib/active-record-data-tables.rb, line 97
def sorting
  sort_no = 0
  sorts = []

  loop do
    sorted = false
    name_col = "iSortCol_#{sort_no}"
    name_mode = "sSortDir_#{sort_no}"
    sort_col = @dts[name_col]
    break if !sort_col

    col_name = @columns[sort_col.to_i]
    next if !col_name

    if @dts[name_mode] == "desc"
      sort_mode = "DESC"
    else
      sort_mode = "ASC"
    end

    if match = col_name.to_s.match(/^(.+)_id$/)
      method_name = match[1]
      sub_model_name = StringCases.snake_to_camel(col_name.slice(0, col_name.length - 3))

      if Kernel.const_defined?(sub_model_name)
        sub_model_const = Kernel.const_get(sub_model_name)
        unless @joins.key?(method_name)
          @query = @query.includes(method_name)
          @joins[method_name] = true
        end

        @sort_columns.each do |sort_col_name|
          if sub_model_const.column_names.include?(sort_col_name.to_s)
            sorts << "`#{sub_model_const.table_name}`.`#{escape_col(sort_col_name)}` #{sort_mode}"
            sorted = true
            break
          end
        end
      end
    end

    if @model.column_names.include?(col_name.to_s)
      sorts << "`#{@model.table_name}`.`#{escape_col(col_name)}` #{sort_mode}"
    elsif @args[:sort]
      res = @args[:sort].call(:key => col_name, :sort_mode => sort_mode, :query => @query)
      @query = res if res
    else
      raise "Unknown sort-column: '#{col_name}'."
    end

    sort_no += 1
  end

  @query = @query.order(sorts.join(", "))
end