Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Sample ActiveRecord model that uses paperclip to upload a csv report to s3
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
# 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
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
# 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
# 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
#admin/users.rb
ActiveAdmin.register User do
enable_reports
end
@varunlalan
Copy link

varunlalan commented Feb 10, 2015

Awesome 👍

One question. How can records be sorted?

@shimaalynks
Copy link

shimaalynks commented Mar 23, 2017

That helped me alot, Many thanks.

@ashishprajapati
Copy link

ashishprajapati commented Nov 1, 2020

Brilliant Work @stereoscott! It made my day. Many Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment