Rails - Export DB to CSV not reading from translation file - ruby-on-rails

I've seen a number of useful suggestions here but so far haven't been able to fix my problem. I'm creating a redmine plugin (ruby on rails) and I have a button that exports the db into a CSV file. That part works just fine.
My problem is that the column names in the csv file are not being translated don't seem to be changed by a call to human_attribute_name. Can anybody point out where i've gone wrong and help me correct it?
I mostly followed this tutorial: http://railscasts.com/episodes/362-exporting-csv-and-excel?view=asciicast
app/models/person.rb
def self.to_csv(options={})
CSV.generate(options) do |csv|
csv << User.attribute_names.map {|c| User.human_attribute_name(c)}
# Could I instead use:
# csv << User.human_attribute_names
User.all.each do |user|
csv << user.attributes.values
end
end
end
config/locales/en.yml
en:
activerecord:
attributes:
user:
col1: "Column 1"
col2: "Column 2"

Found this solution which seems to work:
app/models/user.rb
def self.to_csv(options={})
CSV.generate(options) do |csv|
#cols = User.attribute_names #[1..-1] to remove first column from output
csv << #cols.map { |c| I18t.t c, scope: [:activerecord, :attributes, :user] }
User.all.each do |user|
csv << user.attributes.value_at(*#cols)
end
end
end

Related

Create timestamped labels on csv files (ruby code)

I am running a transaction download script through Ruby. I was wondering if it is possible to label each .csv it creates with the current date/time the script was run. Below is the end of the script.
CSV.open("transaction_report.csv", "w") do |csv|
csv << header_row
search_results.each do |transaction|
transaction_details_row = header_row.map{ |attribute| transaction.send(attribute) }
csv << transaction_details_row
end
end
Like this?
CSV.open("transaction_report-#{Time.now}.csv", "w") do |csv|
csv << header_row
search_results.each do |transaction|
transaction_details_row = header_row.map{ |attribute| transaction.send(attribute) }
csv << transaction_details_row
end
end
This just appends the time of generation to the file name. For example:
"transaction_report-#{Time.now}.csv"
# => "transaction_report-2019-10-10 16:09:07 +0100.csv"
If you want to avoid spaces in the file name, you can sub these out like so:
"transaction_report-#{Time.now.to_s.gsub(/\s/, '-')}.csv"
# => "transaction_report-2019-10-10-16:09:40-+0100.csv"
Is that what you're after? It sounds right based on the question, though happy to update if you're able to correct me :)

Is there any optimized way to export CSV of more than 100K record using Rails?

I have 200k locations in my database. So I want to export all the locations into CSV format. While doing this it is taking too much time to download. What is the best way to optimize code in rails?
In controller:
def index
all_locations = Location.all
respond_to do |format|
format.csv { send_data all_locations.to_csv, filename: "locations-#{Date.today}.csv" }
end
end
In model
def self.to_csv
attributes = %w{id city address}
CSV.generate(headers: true) do |csv|
csv << ['Id', 'City', 'Address']
all.each do |location|
csv << attributes.map{ |attr| location.send(attr) }
end
end
end
I ran your code with some adjustments with my own data. I made the following changes, and using benchmarking I came to a 7x increase.
Your model:
def self.to_csv
attributes = %w{id city address}
CSV.generate(headers: true) do |csv|
csv << ['Id', 'City', 'Address']
all.pluck(attributes).each { |data| csv << data }
end
end
By using pluck you only get the data you want, and then you push all that data into the csv array.
if you are using Postgresql then you can use this in application_record.rb
def self.to_csv_copy(attrs="*", header=[])
rc = connection.raw_connection
rv = header.empty? ? [] : ["#{header.join(',')}\n"]
sql = self.all.select(attrs).to_sql
rc.copy_data("copy (#{sql}) to stdout with csv") do
# rubocop:disable AssignmentInCondition
while line = rc.get_copy_data
rv << line
end
end
rv.join
end
and then do
Location.to_csv_copy(%w{id city address}, ['Id', 'City', 'Address'])
It is even faster than the above solution.

When CSV.generate, generate empty field without ""

Ruby 2.2, Ruby on Rails 4.2
I'm genarating some CSV data in Ruby on Rails, and want empty fields to be empty, like ,, not like ,"", .
I wrote codes like below:
somethings_cotroller.rb
def get_data
respond_to do |format|
format.html
format.csv do
#data = SheetRepository.accounts_data
send_data render_to_string, type: :csv
end
end
end
somethings/get_data.csv.ruby
require 'csv'
csv_str = CSV.generate do |csv|
csv << [1,260,37335,'','','','','','']
...
end
And this generates CSV file like this.
get_data.csv
1,260,37335,"","","","","",""
I want CSV data like below.
1,260,37335,,,,,,
It seems like Ruby adds "" automatically.
How can I do this??
In order to get CSV to output an empty column, you need to tell it that nothing is in the column. An empty string, in ruby, is still something, you'll need to replace those empty strings with nil in order to get the output you want:
csv_str = CSV.generate do |csv|
csv << [1,260,37335,'','','','','',''].map do |col|
col.respond_to?(:empty?) && col.empty? ? nil : col
end
end
# => 1,260,37335,,,,,,
In rails you can clean that up by making use of presence, though this will blank out false as well:
csv_str = CSV.generate do |csv|
csv << [1,260,37335,'',false, nil,'','',''].map(&:presence)
end
# => 1,260,37335,,,,,,
The CSV documentation shows an option that you can use for this case. There are not examples but you can guess what it does.
The only consideration is, you need to send an array of Strings, otherwise, you will get a NoMethodError
csv_str = CSV.generate(write_empty_value: nil) do |csv|
csv << [1,260,37335,'','','','','','', false, ' ', nil].map(&:to_s)
end
=> "1,260,37335,,,,,,,false, ,\n"
The benefit of this solution is, you preserve the false.
I resolved by myself!
in somethings_controller.rb
send_data render_to_string.gsub("\"\"",""), type: :csv

Rails - Passing an Active Record relation into a CSV

I'm trying to filter through all venues, and add any without an external_join_url to the CSV I've created.
require 'csv'
namespace :create_dump do
desc "Erase and fill database"
task :venue => :environment do
#venues = Venue.where(external_join_url: nil)
CSV.open("venues_without_join_url.csv", "w") do |csv|
csv << ["Venue Title"]
#venues.each do |venue|
csv << venue.title
end
end
end
end
When I attempt to do so, I get the error:
NoMethodError: undefined method `map' for "5 Pancras Square":String
I get this means I'm trying to map a string, but can't see what part I'm creating the string. I've tried different ways of assigning #venues (creating an array and shovelling into it) to no avail.
csv << expects an array of strings that's meant to be the fields of a row in the csv file
require 'csv'
namespace :create_dump do
desc 'Erase and fill database'
task venue: :environment do
#venues = Venue.where(external_join_url: nil)
CSV.open('venues_without_join_url.csv', 'w') do |csv|
csv << ['Venue Title']
#venues.each do |venue|
csv << [venue.title]
end
end
end
end

Rails CSV Export - Adding custom field

I'm building an ecommerce app in Rails. I have a method to export the Order model with shipping details. The below method works as-is but I want to add a column into the export from another model.
My order model has a product_id which joins with the product table that has the product name. I want to add the product name. So something like order.product.name. How would I go about adding that into the below?
Here is my method in my order.rb:
def self.to_csv(orders)
wanted_columns = [:id, :shipname, :shipaddress, :shipcity, :shipstate, :shipzip]
CSV.generate do |csv|
csv << wanted_columns
orders.each do |order|
csv << order.attributes.values_at(*wanted_columns)
end
end
end
You can simply add it to your line, like so:
def self.to_csv(orders)
wanted_columns = [:id, :shipname, :shipaddress, :shipcity, :shipstate, :shipzip]
CSV.generate do |csv|
csv << wanted_columns.insert(-1, :product_name)
orders.each do |order|
csv << order.attributes.values_at(*wanted_columns).insert(-1, order.product.name)
end
end
end
The insert method inserts the given values before the element with the given index.
Negative indices count backwards from the end of the array, where -1 is the last element.
It returns the resulting array.
To simplify, in this case, this:
wanted_columns = [:id, :shipname, :shipaddress, :shipcity, :shipstate, :shipzip]
CSV.generate do |csv|
csv << wanted_columns.insert(-1, :product_name)
end
Should behave exactly the same as:
CSV.generate do |csv|
csv << [:id, :shipname, :shipaddress, :shipcity, :shipstate, :shipzip, :product_name]
end
The same principal applies to the array generated by:
order.attributes.values_at(*wanted_columns)
If this is not working, try the simplified example, and inspect the array for correctness prior to adding it to the array. You may additionally simplify:
orders.each do |order|
csv << order.attributes.values_at(*wanted_columns).insert(-1, order.product.name)
end
to:
csv << orders.first.attributes.values_at(*wanted_columns).insert(-1, orders.first.product.name)
For purposes of troubleshooting...

Resources