-
Notifications
You must be signed in to change notification settings - Fork 58
[1337] Add user and equipment CSV export #1340
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
module CsvExport | ||
require 'csv' | ||
require 'zip' | ||
|
||
PROTECTED_COLS = %w(id encrypted_password reset_password_token | ||
reset_password_sent_at) | ||
|
||
# generates a csv from the given model data | ||
# columns is optional; defaults to all columns except protected | ||
def generate_csv(data, columns = []) | ||
columns = data.first.attributes.keys if columns.empty? | ||
|
||
PROTECTED_COLS.each { |col| columns.delete(col) } | ||
|
||
CSV.generate(headers: true) do |csv| | ||
csv << columns | ||
|
||
data.each do |o| | ||
csv << columns.map do |attr| | ||
s = o.send(attr) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole thing is great, really nice job 😄 |
||
s.is_a?(ActiveRecord::Base) ? s.name : s | ||
end | ||
end | ||
end | ||
end | ||
|
||
# generates a zip file containing multiple CSVs | ||
# expects tables to be an array of arrays with the following format: | ||
# [[objects, columns], ...] | ||
# where columns is optional; defaults to all columns except protected | ||
def generate_zip(tables) | ||
# create the CSVs | ||
csvs = tables.map { |model| generate_csv(*model) } | ||
|
||
Zip::OutputStream.write_buffer do |stream| | ||
csvs.each_with_index do |csv, i| | ||
model_name = tables[i].first.first.class.name | ||
stream.put_next_entry "#{model_name}_#{Time.zone.now.to_s(:number)}.csv" | ||
stream.write csv | ||
end | ||
end.string | ||
end | ||
|
||
# downloads a csv of the given model table | ||
# NOTE: this method depends on ActionController | ||
def download_csv(data, columns, filename) | ||
send_data(generate_csv(data, columns), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, this isn't so much a problem but I was a bit confused about where |
||
filename: "#{filename}_#{Time.zone.now.to_s(:number)}.csv") | ||
end | ||
|
||
# downloads a zip file containing multiple CSVs | ||
# expects tables to be an array of arrays with the following format: | ||
# [[objects, columns], ...] | ||
# where columns is optional; defaults to all columns except protected | ||
# NOTE: this method depends on ActionController | ||
def download_zip(tables, filename) | ||
send_data(generate_zip(tables), type: 'application/zip', | ||
filename: "#{filename}.zip") | ||
end | ||
|
||
# NOTE: this method depends on ActionController | ||
def download_equipment_data | ||
categories = [Category.all, %w(name max_per_user max_checkout_length | ||
max_renewal_times max_renewal_length | ||
renewal_days_before_due sort_order)] | ||
models = [EquipmentModel.all, %w(category name description late_fee | ||
replacement_fee max_per_user | ||
max_renewal_length)] | ||
items = [EquipmentItem.all, %w(equipment_model name serial)] | ||
download_zip([categories, models, items], | ||
"EquipmentData_#{Time.zone.now.to_s(:number)}") | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
require 'spec_helper' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great specs, this is really nicely done! |
||
include CsvExport | ||
|
||
describe CsvExport do | ||
before(:all) { FactoryGirl.create(:app_config) } | ||
|
||
MODELS = [:user, :category, :equipment_model, :equipment_item] | ||
PROTECTED_COLS = %w(id encrypted_password reset_password_token | ||
reset_password_sent_at) | ||
|
||
shared_examples 'builds a csv' do |model| | ||
let(:csv) do | ||
generate_csv(FactoryGirl.build_list(model, 5)).split("\n") | ||
end | ||
|
||
it 'has the appropriate length' do | ||
expect(csv.size).to eq(6) | ||
end | ||
|
||
it 'has the appropriate columns' do | ||
expect(csv.first.split(',')).to eq( | ||
FactoryGirl.build(model).attributes.keys - PROTECTED_COLS) | ||
end | ||
|
||
it "doesn't include protected columns" do | ||
PROTECTED_COLS.each do |col| | ||
expect(csv.first.split(',')).not_to include(col) | ||
end | ||
end | ||
|
||
it 'limits columns appropriately' do | ||
cols = FactoryGirl.build(model).attributes.keys.sample(4) | ||
cols.delete 'id' if cols.include? 'id' | ||
csv = generate_csv(FactoryGirl.build_list(model, 5), cols).split("\n") | ||
expect(csv.first.split(',')).to eq(cols) | ||
end | ||
end | ||
|
||
MODELS.each { |m| it_behaves_like 'builds a csv', m } | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add this link to the equipment items index as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure!