module Basepack::Utils

Public Class Methods

detect_models() click to toggle source
# File lib/basepack/utils.rb, line 10
def self.detect_models
  #([Rails.application] + Rails::Engine::Railties.engines).map do |app|
  ([Rails.application] + Rails::Engine.subclasses.collect(&:instance)).map do |app|
    app.paths['app/models'].to_a.map do |load_path|
      Dir.glob(app.root.join(load_path)).map do |load_dir|
        Dir.glob(load_dir + "/**/*.rb").map do |filename|
          # app/models/module/class.rb => module/class.rb => module/class => Module::Class
          filename.reverse.chomp("#{app.root.join(load_dir)}/".reverse).reverse.chomp('.rb').camelize
        end
      end
    end
  end.flatten
end
field_sortable_columns(field) click to toggle source
# File lib/basepack/utils.rb, line 185
def self.field_sortable_columns(field)
  sort_reverse = field.sort_reverse
  Array.wrap(field.sortable).map do |sort|
    if sort == true
      # use field for sorting
      [ field.name.to_s, sort_reverse ]
    elsif sort == false
      # asked field is not sortable
      nil
    elsif (sort.is_a?(String) || sort.is_a?(Symbol)) and sort.to_s.include?('.')
      # just provide sortable, don't do anything smart
      [ sort.to_s, sort_reverse ]
    elsif sort.is_a?(Hash)
      # just join sortable hash, don't do anything smart
      [ "#{sort.keys.first}_#{sort.values.first}", sort_reverse ]
    else
      if sort.is_a? Array
        sort, order = sort
      else
        order = sort_reverse
      end
      [
        field.association? ? "#{field.associated_model_config.abstract_model.table_name}_#{sort}" : sort.to_s,
        order
      ]
    end
  end.compact
end
filter(scope, params, config, options = {}) click to toggle source
# File lib/basepack/utils.rb, line 30
def self.filter(scope, params, config, options = {})
  auth_object = options[:auth_object]
  filter = params[:f].is_a?(Hash) ? params[:f] : {}
  custom_filters = []
  error = nil

  if params[:ql].present?
    begin
      filter = filter.merge(FilterQL.new(options[:filterql_options]).parse(params[:ql]))
    rescue FilterQL::ParseError => e
      error = e.message
    end
  end

  # default sorting
  #
  if filter[:s].blank? and config.query.sort_by
    sort_by = config.query.sort_by.to_sym
    if field = config.query.fields.find {|f| f.name == sort_by }
      columns = field_sortable_columns(field)
      if columns.present?
        filter = filter.merge(s: Hash[columns.map.with_index do |c, i|
          [ i.to_s, { name: c[0], dir: c[1] ^ config.query.sort_reverse? ? 'desc' : 'asc' } ]
        end])
      end
    end
  end

  # custom filters
  #
  filter.each do |k, v|
    if k == "c"
      # {"c"=>{"28054"=>{"a"=>{"0"=>{"name"=>"tag"}}, "p"=>"cont", "v"=>{"0"=>{"value"=>"test"}}}}}
      #
      v.each_value do |cond|
        f = [cond["a"]["0"]["name"], cond["p"] || 'eq', cond["v"].try(:[], "0").try(:[], "value")]
        method = cond["p"] ? "filter_#{f[0]}_#{f[1]}" : "filter_#{f[0]}"
        if scope.klass.respond_to? method
          scope = scope.klass.send(method, scope, f[2], auth_object)
          custom_filters << f
        end
      end
    elsif k =~ /\A(.*)_((?:(?:does_)?not_)?[a-z]+)\z/
      atr = $1
      predicate = $2
      method = "filter_#{k}".sub(/_eq\z/, '')
      if scope.klass.respond_to? method
        scope = scope.klass.send(method, scope, v, auth_object)
        custom_filters << [atr, predicate, v]
      end
    end
  end

  resource_filter = scope.ransack(filter, search_key: :f, auth_object: auth_object)
  resource_filter.errors[:base] = [error] if error
  resource_filter.custom_filters = custom_filters
  scope = resource_filter.result(distinct: true)
  [resource_filter, scope]
end
model_config(resource_class) click to toggle source
# File lib/basepack/utils.rb, line 3
def self.model_config(resource_class)
  model_config = RailsAdmin.config(resource_class)
  raise ArgumentError.new("Model #{resource_class.inspect} not known by RailsAdmin") unless model_config
  raise ArgumentError.new("Model #{resource_class.inspect} excluded from RailsAdmin") if model_config.excluded?
  model_config
end
paginate(scope, params) click to toggle source
# File lib/basepack/utils.rb, line 90
def self.paginate(scope, params)
  scope = scope.page(params[:page].presence || 1)
  scope = scope.per(params[:per].presence) if params[:per].presence
  scope
end
query(scope, params, config) click to toggle source
# File lib/basepack/utils.rb, line 96
def self.query(scope, params, config)
  query = params[:query]

  if query.present?
    or_arel = []
    model = config.abstract_model.model
    object = nil
    dquery = query.downcase

    if model.respond_to?(:default_query)
      return scope.merge(model.send(:default_query, query))
    end

    config.query.fields.select(&:queryable?).map do |field|
      field.searchable_columns.each do |column_infos|
        column = model.arel_table[column_infos[:column][/([^.]+)$/, 1].to_sym]

        case column_infos[:type]
        when :integer
          begin
            or_arel << column.eq(Integer(query))
          rescue ArgumentError
          end
        when :decimal, :float
          begin
            or_arel << column.eq(Float(query))
          rescue ArgumentError
          end
        when :string, :text
          or_arel << column.matches("%#{query}%")
        when :boolean
          if Ransack::Constants::TRUE_VALUES.include? query
            or_arel << column.eq(true)
          elsif Ransack::Constants::FALSE_VALUES.include? query
            or_arel << column.eq(false)
          end
        when :date
          begin
            or_arel << column.eq(Time.zone.parse(query).to_date)
          rescue ArgumentError, NoMethodError
          end
        when :datetime, :timestamp
          begin
            date = Time.zone.parse(query).to_datetime
            if date.midnight - date == 0
              or_arel << column.gteq(date).and(column.lt(date.advance(:days => 1)))
            elsif date.sec_fraction == 0
              if date.sec == 0
                or_arel << column.gteq(date).and(column.lt(date.advance(:minutes => 1)))
              else
                or_arel << column.gteq(date).and(column.lt(date.advance(:seconds => 1)))
              end
            else
              or_arel << column.eq(date)
            end
          rescue ArgumentError, NoMethodError
          end
        when :enum
          values = nil
          if model.respond_to? :enumerized_attributes
            # enumerize
            if enum_attrs = model.enumerized_attributes[column.name]
              values = enum_attrs.values.map do |e_val|
                e_val.value if e_val == query or e_val.value == query or e_val.text.downcase.include?(dquery)
              end.compact
            end
          else
            enum_method = "#{column.name}_enum"
            if model.respond_to?(enum_method)
              options = model.send(enum_method)
            else
              object ||= model.new
              options = object.try(enum_method)
            end
            values = options.map do |o|
              o[1] if o[0].to_s.downcase.include?(dquery) or o[1].to_s.downcase.include?(dquery)
            end.compact if options
          end
          or_arel << column.in(values) if values.present?
        end
      end
    end

    scope = or_arel.present? ? scope.where(or_arel.map {|a| a.to_sql}.join(' OR ')) : scope.where("0") # none
  end

  scope
end
query_from_params(scope, params, options = {}) click to toggle source
# File lib/basepack/utils.rb, line 24
def self.query_from_params(scope, params, options = {})
  config = options[:config] || model_config(scope.klass)
  scope = paginate(query(scope, params, config), params)
  filter(scope, params, config, options)
end
translate(resource, action, subaction = '') click to toggle source
# File lib/basepack/utils.rb, line 214
def self.translate(resource, action, subaction = '')
  resource_class = (Class === resource) ? resource : resource.class
  model_name = resource_class.model_name.singular
  lookups = []
  lookups << :"basepack.forms.#{model_name}.#{action}.#{subaction}"
  lookups << :"basepack.actions.#{action}.#{subaction}"
  lookups << :"admin.actions.#{action}.#{subaction}"

  I18n.t(lookups.shift, default: lookups).presence
end