Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save amiramke/8853890 to your computer and use it in GitHub Desktop.

Select an option

Save amiramke/8853890 to your computer and use it in GitHub Desktop.

Revisions

  1. @stereoscott stereoscott created this gist Jan 12, 2014.
    26 changes: 26 additions & 0 deletions active_admin_extensions.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,26 @@
    module ActiveAdmin

    module Reports
    module DSL
    def enable_reports
    action_item only: :index do
    link_to("Download", {action: :report, params: params}, {method: :post, data: { confirm: "Are you sure you want to generate this report?"}})
    end

    collection_action :report, method: :post do
    report = Report.create({
    name: resource_class.to_s.pluralize.titleize,
    model_name: resource_class.to_s,
    admin_user: current_admin_user,
    params: params['q']
    })
    Report.delay.generate(report.id)
    redirect_to(admin_report_path(report.id), {notice: "Report queued for processing!"})
    end
    end
    end
    end

    ActiveAdmin::ResourceDSL.send :include, ActiveAdmin::Reports::DSL
    end

    67 changes: 67 additions & 0 deletions csv_builder.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,67 @@
    # CSVBuilder stores CSV configuration
    # Extracted from ActiveAdmin
    #
    # Usage example:
    #
    # csv_builder = CSVBuilder.new
    # csv_builder.column :id
    # csv_builder.column("Name") { |resource| resource.full_name }
    #
    # csv_builder = CSVBuilder.new :col_sep => ";"
    # csv_builder.column :id
    #
    #
    class CSVBuilder

    # Return a default CSVBuilder for a resource
    # The CSVBuilder's columns would be Id followed by this
    # resource's content columns
    def self.default_for_resource(resource)
    new(resource: resource).tap do |csv_builder|
    csv_builder.column(:id)
    resource.content_columns.each do |content_column|
    csv_builder.column(content_column.name.to_sym)
    end
    end
    end

    def self.call_method_or_proc_on(receiver, *args)
    options = { :exec => true }.merge(args.extract_options!)

    symbol_or_proc = args.shift

    case symbol_or_proc
    when Symbol, String
    receiver.send(symbol_or_proc.to_sym, *args)
    when Proc
    if options[:exec]
    instance_exec(receiver, *args, &symbol_or_proc)
    else
    symbol_or_proc.call(receiver, *args)
    end
    end
    end

    attr_reader :columns, :options

    def initialize(options={}, &block)
    @resource = options.delete(:resource)
    @columns, @options = [], options
    instance_exec &block if block_given?
    end

    # Add a column
    def column(name, &block)
    @columns << Column.new(name, @resource, block)
    end

    class Column
    attr_reader :name, :data

    def initialize(name, resource = nil, block = nil)
    @name = name.is_a?(Symbol) && resource.present? ? resource.human_attribute_name(name) : name.to_s.humanize
    @data = block || name.to_sym
    end
    end
    end

    88 changes: 88 additions & 0 deletions report.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,88 @@
    class Report < ActiveRecord::Base
    belongs_to :admin_user

    # see https://github.com/thoughtbot/paperclip/wiki/Encryption-on-amazon-s3
    has_attached_file :report, s3_permissions: :private,
    s3_headers: { "x-amz-server-side-encryption" => "AES256" },
    path: "private/reports/:filename"

    # fog_host contains the protocol, but expiring url appends http to it so we have to remove that
    def download_url
    url = report.try(:expiring_url, Time.now + 60.seconds)
    url.gsub!('http://https://', 'https://') if url
    end

    def generate
    start!
    path = build_csv
    finish!
    self.report = File.open(path)
    uploaded!
    end

    def start!
    self.started_at = Time.now
    save!
    end

    def finish!
    self.finished_at = Time.now
    save!
    end

    def uploaded!
    self.uploaded_at = Time.now
    save!
    end

    def uploaded?
    uploaded_at?
    end

    def status
    if uploaded_at?
    :available
    elsif finished_at?
    :finished
    elsif started_at?
    :started
    else
    :pending
    end
    end

    private

    def build_csv
    model = model_name.classify.constantize
    path = "#{Rails.root.to_s}/tmp/#{filename}"

    csv_builder = model.csv_builder
    columns = csv_builder.columns

    record_count = 0
    CSV.open(path, "wb") do |csv|
    csv << columns.map(&:name)
    model.report_scope.search(params).result.find_each do |resource|
    csv << columns.map do |column|
    CSVBuilder.call_method_or_proc_on resource, column.data
    end
    record_count += 1
    end
    end

    self.record_count = record_count
    path
    end

    def filename
    [model_name.try(:downcase), created_at.try(:to_i), id].join("_") + ".csv"
    end

    class << self
    def generate(report_id)
    find(report_id).generate
    end
    end

    end
    26 changes: 26 additions & 0 deletions reportable.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,26 @@
    # a mixin for your models to add default report behavior
    module Reportable
    extend ActiveSupport::Concern

    def to_csv
    self.class.csv_builder.columns.map do |column|
    CSVBuilder.call_method_or_proc_on self, column.data
    end
    end

    module ClassMethods

    def csv_builder
    CSVBuilder.default_for_resource(self)
    end

    def csv_columns
    csv_builder.columns.map(&:name)
    end

    def report_scope
    all
    end

    end
    end
    26 changes: 26 additions & 0 deletions user.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,26 @@
    # how to define the csv export inside of the model
    class User < ActiveRecord::Base
    include Reportable

    # override some of the reportable methods

    class << self

    def report_scope
    # defaults to 'all' but you could do a custom scope like this...
    joins(:addresses)
    end

    def csv_builder
    csv = CSVBuilder.new({ resource: self }) do
    column :id
    column :user_id
    column :first_name
    column :last_name
    column("Address") { |user| user.primary_address.try(:to_str) }
    column :created_at
    end
    end
    end

    end
    5 changes: 5 additions & 0 deletions users.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,5 @@
    #admin/users.rb
    ActiveAdmin.register User do
    enable_reports
    end