How to set path where to save CSV file? - ruby-on-rails

I'm saving records into CSV using after_save_filer. I don't receive any error and also I can't find file, what was generated(if it exists).
Here is code what I'm using:
after_save :to_csv
def to_csv(options = {})
require 'csv'
CSV.open("C:/project/myfile.csv", "w") do |csv|
csv << self.class.column_names
csv << self.attributes.values_at(*column_names)
end
end
How can I check is after save filter has been run ? How I can set the path ?

To check if the after_save was run you can either put a debugger statement inside the to_csv method and see if it stops there when you save the model, or you could simply put a puts "I am in to_csv method inside the method and look for it in the console.

Related

How to add links to the individual records in the to_csv method in rails 5 so absolute urls are printed in the csv export?

I'm working on a project in rails and recently watched this great gorails.com episode on exporting data as CSV (https://gorails.com/episodes/export-to-csv). I can get it to work, but I really want to get the absolute url of each of the "tasks" so in the CSV export it would show in each row as a 4th column the link to the "task" eg: "localhost:3000/tasks/1" "localhost:3000/tasks/2" etc. Something like "task_url" as the column header
I haven't found any resource online that helps with this. Any thoughts on how to do this in the most efficient way? Thank you for the help! Here is the working code:
def self.to_csv
attributes = %w{id task_name task_description}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |task|
csv << attributes.map{ |attr| task.send(attr) }
end
end
end
And I would like to do something like this (emphasis added around task_url):
def self.to_csv
#need to declare what task_url is here
attributes = %w{id task_name task_description **task_url**}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |task|
csv << attributes.map{ |attr| task.send(attr) }
end
end
end
You could update your Task model with a #task_url instance method so that calling send on the task instances works. Then next implement the #task_url method with the necessary logic to the build and return the appropriate link for the individual task object. Something like:
class Task
include Rails.application.routes.url_helpers
...
def self.to_csv
attributes = %w{id task_name task_description task_link}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |task|
csv << attributes.map{ |attr| task.send(attr) }
end
end
end
def task_link
task_url(self)
end
end
In this example, the links are constructed using the rails routes helpers. And to access them, we'll need to the include the #url_helpers module since they are not available (by default) in the model layer.
Notice that in the example, the method is called #task_link; the assumption being that the route helper method is already named #task_url and so we avoid overriding it.

Upload a Tempfile to Carrierwave in Rails

I am trying to create a Tempfile using some user-inputted information in one of my models:
after_create :form_to_csv
def form_to_csv
temp_file = Tempfile.new('test_temp.csv', encoding: 'utf-8')
begin
self.input_data.split('\n').each do |line|
temp_file.write line
end
temp_file.rewind
self.build_input_data_file(file: Rails.root.join("temp_file.path}").open)
ensure
temp_file.close
temp_file.unlink
end
end
The form data is stored in the input_data field as text. I get to write my data to the Tempfile successfully but attaching it to the model is cumbersome - I keep trying to attach nil to my model. My main problem is that the file I generate has a path like:
/var/folders/jw/7pjlw_212qd3s4ddfj1zvnpr0000gn/T/test_temp.csv20170502-78762-1eh23ml
and it won't work with Rails.root.join and with the attachment method propose in CarrierWave docs here (Upload from a local file).
My mistake was that I tried to assign a .csv extension within the filename parameter of the Tempfile object. Instead I needed to pass an array like this:
def form_to_csv
temp_file = Tempfile.new(['test_temp', '.csv'], encoding: 'utf-8')
begin
self.input_data.split('\n').each do |line|
temp_file.write line
end
temp_file.rewind
self.build_input_data_file(file: Rails.root.join(temp_file.path).open)
ensure
temp_file.close
temp_file.unlink
end
end

Exporting CSV data from Rails

I'm working to export CSV data from Rails. I'm following the tutorial here: http://railscasts.com/episodes/362-exporting-csv-and-excel?view=asciicast
In my controller I have:
def show
# #company is being provided correctly.
#groups = #company.groups
render text: #groups.to_csv
end
In my group.rb model:
def self.to_csv
Rails.logger.info "Hello World"
CSV.generate do |csv|
csv << column_names
all.each do |product|
csv << product.attributes.values_at(*column_names)
end
end
end
The issue is the browser is outputting just the following:
#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#
The model method appears to be being ignored, even after a Rails restart. What's wrong here?
The to_csv is a class method. Meaning its meant to be called like Group.to_csv. You might want to change the method signature to something like Group.to_csv(groups) instead.
def self.to_csv(groups)
Rails.logger.info "Hello World"
CSV.generate do |csv|
csv << column_names
groups.each do |product|
csv << product.attributes.values_at(*column_names)
end
end
end
Then in show
def show
# #company is being provided correctly.
#groups = #company.groups
render text: Group.to_csv(#groups)
end
I also met this problem, which was already solved by this answer:
https://stackoverflow.com/a/17311372/3458781
Shortly, you have to restart your server. Hope it works for you.
A quick version for rails console that is not for prod but just to have access to the data in excel:
first do :
print TableName.first.attributes.map{|a,v| a}.join(",")
second do :
TableName.all.each{|t| print t.attributes.map{|a,v| a}.join(",") + "\n"}; nil
copy the result of this two call in a csv file

Undefined method 'column_names' when generating CSV in after_save_filter

I want to generate CSV file after user creates his account, so I created after_save filter in User Model, but I'm getting errors.
Here is my code:
after_save :to_csv
def to_csv(options = {})
require 'csv'
CSV.generate(options) do |csv|
csv << self.column_names//also tried User.column_names
csv << self.attributes.values_at(*column_names)
end
end
but when User is created I get error:
undefined local variable or method `column_names' for #<User:0x326f778>
app/models/user.rb:52:in `block in to_csv'
app/models/user.rb:50:in `to_csv'
app/controllers/users_controller.rb:27:in `create'
Why I'm getting this ? I'm using this railscast - http://railscasts.com/episodes/362-exporting-csv-and-excel.
column_names is a method on the class. You're calling it on the instance. If you really want column_names then use self.class.column_names.
Try self.attributes.keys which return columns
Am bit late int he game but here it is
self.attribute_names
does the job for me.

How do you make a method apply to a collection of ActiveRecord objects

Currently, if I want to apply a method to a group of ActiveRecord objects, I have to structure the call like so:
messages = Message.find(:all)
csv = Message.to_csv(messages)
How can I define the method so it's structured like so?
messages = Message.find(:all)
csv = messages.to_csv
This is the current model code:
require 'fastercsv'
class Message < ActiveRecord::Base
def Message.to_csv(messages)
FasterCSV.generate do |csv|
csv << ["from","to", "received"]
for m in messages
csv << [m.from,m.to,m.created_at]
end
end
end
end
The following will call to_csv on all instances included in the messages array.
messages = Message.find(:all)
csv = messages.map { |message| message.to_csv }
In Rails, in Ruby 1.9 or with Symbol#to_proc available through other means, you can also shorten it to:
csv = messages.map(&:to_csv)
The longer form is useful when you want to make a more complex operation:
csv = messages.map { |message|
if message.length < 1000
message.to_csv
else
"Too long"
end
}
Put this in a file in lib/. I would recommend calling it something like base_ext.rb
require 'fastercsv'
class ActiveRecord::Base
def self.to_csv(objects, skip_attributes=[])
FasterCSV.generate do |csv|
csv << attribute_names - skip_attributes
objects.each do |object|
csv << (attribute_names - skip_attributes).map { |a| "'#{object.attributes[a]}'" }.join(", ")
end
end
end
end
After that, go to config/environment.rb and put require 'base_ext' at the bottom of the file to include the new extension. Upon restarting your server you should have access to a to_csv method on all model classes and when you pass it an array of objects should generate a nice CSV format for you.
FasterCSV patches the Array class and adds a 'to_csv' method to it already, but it doesn't do what you want. You could overwrite it yourself by doing something like:
class Array
def to_csv(options = Hash.new)
collect { |item| item.to_csv }.join "\n"
end
end
Or something along those lines, but that's kind of crappy.
Honestly, it makes more sense the way you've done it as a class method on your model.
You could create a method on your Message class to do something along the lines of...
In your controller....
#csv_file = Message.send_all_to_csv
In your model...
require 'fastercsv'
class Message < ActiveRecord::Base
def send_all_to_csv
#messages = Find.all
FasterCSV.generate do |csv|
csv << ["from","to", "received"]
for message in #messages
csv << [message.from,message.to,message.created_at]
end
end
# do something with your csv object (return it to the controller
# or pass it on to another class method
end
end
You could define the method directly on the messages object itself, but if you do that, the method would only be available to that specific instance:
def messages.to_csv()
FasterCSV.generate do |csv|
csv << ["from", "to", "received"]
self.each { |m| csv << [m.from, m.to, m.created_at] }
end
end
Then you could call it like so:
messages.to_csv
I'm a Ruby newbie, so I'm not sure if this is idiomatic Ruby or not: that is, I'm not sure how common or accepted it is to define new methods directly on object instances, I only know it's possible ;-)
If it is isolated to one AR model I would add a to_custom_csv_array instance method
def to_custom_csv_array
[self.from,self.to,self.created_at]
end
then override find on the class
def self.find(*args)
collection = super
collection.extend(CustomToCSV) if collection.is_a?(Array)
end
and in CustomToCSV define the to_custom_csv to generate the csv
module CustomToCSV
def to_custom_csv
FasterCSV.generate do |csv|
csv << ["from","to", "received"]
csv << self.map {|obj| obj.to_custom_csv_array}
end
end
end
This is from memory but should work.
I know that it is a very old question but just thought to provide a feedback anyway.
check blog http://blog.zahiduzzaman.com/2013/07/adding-tocsv-method-to-active-record.html
just another way of achieving this

Resources