class ActiveList::Generator

Manage data query

Attributes

controller[RW]
controller_method_name[RW]
export_class[RW]
records_variable_name[RW]
table[RW]
view_method_name[RW]

Public Class Methods

new(*args) { |table| ... } click to toggle source
# File lib/active_list/generator.rb, line 5
def initialize(*args, &_block)
  options = args.extract_options!
  @controller = options[:controller]
  name = args.shift || @controller.controller_name.to_sym
  model = (options[:model] || name).to_s.classify.constantize
  @collection = options[:collection] || !!(model.name == @controller.controller_name.to_s.classify)
  @controller_method_name = "list#{'_' + name.to_s if name != @controller.controller_name.to_sym}"
  @view_method_name       = "_#{@controller.controller_name}_list_#{name}_tag"
  @records_variable_name  = "@#{name}"
  @table = ActiveList::Definition::Table.new(name, model, options)
  @export_class = options[:export_class]
  if block_given?
    yield @table
  else
    @table.load_default_columns
  end
  @parameters = { sort: :to_s, dir: :to_s }
  @parameters.merge!(page: :to_i, per_page: :to_i) if @table.paginate?
end

Public Instance Methods

collection?() click to toggle source
# File lib/active_list/generator.rb, line 25
def collection?
  @collection
end
controller_method_code() click to toggle source
# File lib/active_list/generator.rb, line 37
def controller_method_code
  code = "# encoding: utf-8\n"
  code << "def #{controller_method_name}\n"
  code << session_initialization_code.dig
  code << "  respond_to do |format|\n"
  code << "    format.html do\n"
  code << "      if request.xhr?\n"
  code << renderer.remote_update_code.dig(4)
  code << "      else\n"
  code << "        render(inline: '<%=#{view_method_name}-%>')\n" # , layout: action_has_layout?
  code << "      end\n"
  code << "    end\n"
  for format, exporter in ActiveList::Exporters.hash
    code << "    format.#{format} do\n"
    code << exporter.new(self).generate_file_code(format).dig(3)
    code << "    end\n"
  end
  code << "  end\n"
  # Save preferences of user
  if defined?(User) && User.instance_methods.include?(:preference)
    code << "  p = current_user.preference('list.#{view_method_name}', YAML::dump({}))\n"
    code << "  p.set! YAML::dump(#{var_name(:params)}.stringify_keys)\n"
  end
  code << "end\n"
  # code.split("\n").each_with_index{|l, x| puts((x+1).to_s.rjust(4)+": "+l)}
  unless ::Rails.env.production?
    file = ::Rails.root.join('tmp', 'code', 'active_list', 'controllers', controller.controller_path, controller_method_name + '.rb')
    FileUtils.mkdir_p(file.dirname)
    File.write(file, code)
  end
  code
end
exportable_query_code(options = {}) click to toggle source
# File lib/active_list/generator/finder.rb, line 67
def exportable_query_code(options = {})
  unless @table.options.keys.include?(:order)
    columns = @table.table_columns
    @table.options[:order] = (columns.any? ? columns.first.name.to_sym : { id: :desc })
  end

  class_name = "options[\"constant_name\"]&.constantize || #{@table.model.name}"
  class_name = "(controller_name != '#{class_name.tableize}' && !options[\"constant_name\"] ? controller_name.to_s.classify.constantize : #{class_name})" if collection?

  code = ''

  code << "query = #{class_name}.to_s\n"
  code << "query << #{scope_code.inspect}\n" if scope_code

  if select_code
    code << "select = #{select_code}\n"
    code << "query << \".select(\"\n"
    code << "query << select.inspect\n"
    code << "query << \")\"\n"
  end

  if from_code
    code << "from = #{from_code}\n"
    code << "query << \".from(\"\n"
    code << "query << from.inspect\n"
    code << "query << \")\"\n"
  end

  unless @table.options[:conditions].blank?
    code << "condition = #{conditions_code}\n"
    code << "query << \".where(\"\n"
    code << "query << condition.inspect\n"
    code << "query << \")\"\n"
  end

  code << "query << \".joins(#{@table.options[:joins].inspect})\"\n" unless @table.options[:joins].blank?

  unless includes_reflections.empty?
    expr = includes_reflections.inspect[1..-2]
    code << "query << \".includes(#{expr})\"\n"
    code << "query << \".references(#{expr})\"\n"
  end

  unless @table.options[:group].blank?
    code << "group = #{@table.options[:group].inspect}\n"
    code << "query << \".group(\"\n"
    code << "query << group.inspect\n"
    code << "query << \")\"\n"
  end

  code << "order = #{var_name(:order)}\n"
  code << "query << \".reorder(\"\n"
  code << "query << order.inspect\n"
  code << "query << \")\"\n"

  code.c
end
renderer() click to toggle source
# File lib/active_list/generator.rb, line 33
def renderer
  ActiveList::Renderers[@table.options[:renderer]].new(self)
end
select_data_code(options = {}) click to toggle source

Generate select code for the table taking all parameters in account

# File lib/active_list/generator/finder.rb, line 5
def select_data_code(options = {})
  paginate = (options.key?(:paginate) ? options[:paginate] : @table.paginate?)
  # Check order
  unless @table.options.keys.include?(:order)
    columns = @table.table_columns
    @table.options[:order] = (columns.any? ? columns.first.name.to_sym : { id: :desc })
  end

  class_name = "options[\"constant_name\"]&.constantize || #{@table.model.name}"
  class_name = "(controller_name != '#{class_name.tableize}' && !options[\"constant_name\"] ? controller_name.to_s.classify.constantize : #{class_name})" if collection?

  # Find data
  query_code = class_name.to_s
  query_code << scope_code if scope_code
  query_code << ".select(#{select_code})" if select_code
  query_code << ".from(#{from_code})" if from_code
  query_code << ".where(#{conditions_code})" unless @table.options[:conditions].blank?
  query_code << ".joins(#{@table.options[:joins].inspect})" unless @table.options[:joins].blank?
  unless includes_reflections.empty?
    expr = includes_reflections.inspect[1..-2]
    query_code << ".includes(#{expr})"
    query_code << ".references(#{expr})"
  end

  code = ''
  code << "#{query_code}\n"

  code << if @table.options[:count].present?
            "#{var_name(:count)} = #{query_code}.count(#{@table.options[:count].inspect})\n"
          else
            "#{var_name(:count)} = #{query_code}.count\n"
          end
  query_code << ".group(#{@table.options[:group].inspect})" unless @table.options[:group].blank?
  query_code << ".reorder(#{var_name(:order)})"

  if paginate
    code << "#{var_name(:limit)}  = (#{var_name(:params)}[:per_page] || 25).to_i\n"

    code << "if params[:page]\n"
    code << "  #{var_name(:page)} = (#{var_name(:params)}[:page] || 1).to_i\n"
    code << "elsif params['#{table.name}-id'] and #{var_name(:index)} = #{query_code}.pluck(:id).index(params['#{table.name}-id'].to_i)\n"
    # Find page of request element
    code << "  #{var_name(:page)} = (#{var_name(:index)}.to_f / #{var_name(:limit)}).floor + 1\n"
    code << "else\n"
    code << "  #{var_name(:page)} = 1\n"
    code << "end\n"
    code << "#{var_name(:page)}   = 1 if #{var_name(:page)} < 1\n"

    code << "#{var_name(:offset)} = (#{var_name(:page)} - 1) * #{var_name(:limit)}\n"
    code << "#{var_name(:last)}   = (#{var_name(:count)}.to_f / #{var_name(:limit)}).ceil.to_i\n"
    code << "#{var_name(:last)}   = 1 if #{var_name(:last)} < 1\n"

    code << "return #{view_method_name}(options.merge(page: 1)) if 1 > #{var_name(:page)}\n"
    code << "return #{view_method_name}(options.merge(page: #{var_name(:last)})) if #{var_name(:page)} > #{var_name(:last)}\n"
    query_code << ".offset(#{var_name(:offset)})"
    query_code << ".limit(#{var_name(:limit)})"
  end

  code << "#{records_variable_name} = #{query_code} || {}\n"
  code
end
session_initialization_code() click to toggle source
# File lib/active_list/generator.rb, line 85
def session_initialization_code
  code = "options = {} unless options.is_a? Hash\n"
  # For Rails 5
  code << "options.update(params.to_unsafe_h)\n"
  if defined?(User) && User.instance_methods.include?(:preference)
    code << "#{var_name(:params)} = YAML::load(current_user.preference('list.#{view_method_name}', YAML::dump({})).value).symbolize_keys\n"
    code << "#{var_name(:params)} = {} unless #{var_name(:params)}.is_a?(Hash)\n"
  else
    code << "#{var_name(:params)} = {}\n"
  end
  code << "#{var_name(:params)}.update(options.symbolize_keys)\n"
  code << "unless #{var_name(:params)}[:hidden_columns].is_a? Array\n"
  code << "  #{var_name(:params)}[:hidden_columns] = #{@table.hidden_columns.map(&:name).map(&:to_sym).inspect}\n"
  code << "end\n"
  for parameter, convertor in @parameters.sort { |a, b| a[0].to_s <=> b[0].to_s }
    # expr  = "options.delete('#{@table.name}_#{parameter}') || options.delete('#{parameter}') || #{var_name(:params)}[:#{parameter}]"
    # expr += " || #{@table.options[parameter]}" unless @table.options[parameter].blank?
    # code << "#{var_name(:params)}[:#{parameter}] = (#{expr}).#{convertor}\n"
    expr = "#{var_name(:params)}[:#{parameter}]"
    expr = "(#{expr} || #{@table.options[parameter]})" unless @table.options[parameter].blank?
    code << "#{var_name(:params)}[:#{parameter}] = #{expr}.#{convertor}\n"
  end
  code << "params[:redirect] ||= request.fullpath unless request.xhr?\n"

  # Order
  code << "#{var_name(:order)} = #{@table.options[:order] ? @table.options[:order].inspect : 'nil'}\n"
  code << "if #{var_name(:col)} = {" + @table.sortable_columns.collect { |c| "'#{c.sort_id}' => '#{c.sort_expression}'" }.join(', ') + "}[#{var_name(:params)}[:sort]]\n"
  code << "  #{var_name(:params)}[:dir] = 'asc' unless #{var_name(:params)}[:dir] == 'asc' or #{var_name(:params)}[:dir] == 'desc'\n"
  code << "  null_pos = 'first' if #{var_name(:params)}[:dir] == 'asc'\n"
  code << "  null_pos = 'last' if #{var_name(:params)}[:dir] == 'desc'\n"
  code << "  #{var_name(:order)} = #{var_name(:col)} + ' ' + #{var_name(:params)}[:dir] + ' NULLS ' + null_pos \n"
  code << "end\n"

  code
end
var_name(name) click to toggle source
# File lib/active_list/generator.rb, line 29
def var_name(name)
  "_#{name}"
end
view_method_code() click to toggle source
# File lib/active_list/generator.rb, line 70
def view_method_code
  code = "# encoding: utf-8\n"
  code << "def #{view_method_name}(options={}, &block)\n"
  code << session_initialization_code.dig
  code <<   "#{renderer.build_table_code}(options).dig\n"
  code << "end\n"
  # code.split("\n").each_with_index{|l, x| puts((x+1).to_s.rjust(4)+": "+l)}
  unless ::Rails.env.production?
    file = ::Rails.root.join('tmp', 'code', 'active_list', 'views', controller.controller_path, view_method_name + '.rb')
    FileUtils.mkdir_p(file.dirname)
    File.write(file, code)
  end
  code
end

Protected Instance Methods

conditions_code() click to toggle source

Generate the code from a conditions option

# File lib/active_list/generator/finder.rb, line 147
def conditions_code
  conditions = @table.options[:conditions]
  code = ''
  case conditions
  when Array
    case conditions[0]
    when String # SQL
      code << '[' + conditions.first.inspect
      code << conditions[1..-1].collect { |p| ', ' + sanitize_condition(p) }.join if conditions.size > 1
      code << ']'
    when Symbol # Method
      raise 'What?' # Amazingly explicit.
    # code << conditions.first.to_s + '('
    # code << conditions[1..-1].collect { |p| sanitize_condition(p) }.join(', ') if conditions.size > 1
    # code << ')'
    else
      raise ArgumentError, 'First element of an Array can only be String or Symbol.'
    end
  when Hash # SQL
    code << '{' + conditions.collect { |key, value| key.to_s + ': ' + sanitize_condition(value) }.join(',') + '}'
  when Symbol # Method
    code << conditions.to_s + '(options)'
  when CodeString
    code << '(' + conditions.gsub(/\s*\n\s*/, ';') + ')'
  when String
    code << conditions.inspect
  else
    raise ArgumentError, "Unsupported type for conditions: #{conditions.inspect}"
  end
  code
end
from_code() click to toggle source
# File lib/active_list/generator/finder.rb, line 179
def from_code
  return nil unless @table.options[:from]
  from = @table.options[:from]
  code = ''
  code << '(' + from.gsub(/\s*\n\s*/, ';') + ')'
  code
end
includes_reflections() click to toggle source

Compute includes Hash

# File lib/active_list/generator/finder.rb, line 128
def includes_reflections
  hash = []
  @table.columns.each do |column|
    hash << column.reflection.name if column.respond_to?(:reflection)
  end
  hash
end
sanitize_condition(value) click to toggle source
# File lib/active_list/generator/finder.rb, line 208
def sanitize_condition(value)
  # if value.is_a? Array
  #   # if value.size==1 and value[0].is_a? String
  #   #   value[0].to_s
  #   # else
  #   value.inspect
  #   # end
  # elsif value.is_a? CodeString
  #   value.inspect
  # elsif value.is_a? String
  #   '"' + value.gsub('"', '\"') + '"'
  # els
  if [Date, DateTime].include? value.class
    '"' + value.to_formatted_s(:db) + '"'
  elsif value.is_a? NilClass
    'nil'
  else
    value.inspect
  end
end
scope_code() click to toggle source
# File lib/active_list/generator/finder.rb, line 136
def scope_code
  return nil unless scopes = @table.options[:scope]
  scopes = [scopes].flatten
  code = ''
  scopes.each do |scope|
    code << ".#{scope}"
  end
  code
end
select_code() click to toggle source
# File lib/active_list/generator/finder.rb, line 187
def select_code
  return nil unless @table.options[:distinct] || @table.options[:select]
  code = ''
  code << 'DISTINCT ' if @table.options[:distinct]
  if @table.options[:select]
    # code << @table.options[:select].collect { |k, v| ", #{k[0].to_s + '.' + k[1].to_s} AS #{v}" }.join
    code << @table.options[:select].collect do |k, v|
      c = if k.is_a? Array
            k[0].to_s + '.' + k[1].to_s
          else
            k
          end
      c += " AS #{v}" unless v.blank?
      c
    end.join(', ')
  else
    code << "#{@table.model.table_name}.*"
  end
  ('"' + code + '"').c
end