Rails - Import csv file into nested route - ruby-on-rails

I have successfully gotten CSV files to be imported following the steps outlined in the Railscast 396 episode. However, now I'm trying to import a csv that is nested. I have seen other posts where people are trying to import both the parent and the child data but in this case I'm trying to only import the child data on the econ_report show view.
For example, I have econ_results that I'm trying to import that is nested under an econ_report. The rake file shows the following link.
import_econ_report_econ_results
When I try to load the show page for the econ_report I get the following error
No route matches {:action=>"import", :controller=>"econ_results", :id=>"1"} missing required keys: [:econ_report_id]
I have the following code in the econ_result.rd file:
def self.to_csv
CSV.generate do |csv|
csv << column_names
all.each do |sub|
csv << sub.attributes.values_at(*column_names)
end
end
end
def self.import(file)
CSV.foreach(file.path,headers: true) do |row|
EconResult.create! row.to_hash
end
end
Lastly, in the econ_results_controller I have the following code:
before_action :set_econ_report
def set_econ_report
#econ_report = EconReport.find(params[:econ_report_id])
end
def import
EconResult.import(params[:file])
end
def create
#econ_result = #econ_report.econ_results.create(econ_result_params)
redirect_to #econ_report
end
Is the designation of #econ_report causing the conflict in the controller?

You need to tell rails that the record you are passing is for the econ_report_id param. The default is that the route helper will use a passed record for the id param.
<%= link_to import_econ_report_econ_results_path(econ_report_id: #econ_report) %>
I would write a helper method since the name is ridiculously long.

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.

unknown attribute Rails on import CSV uninitialized constant

I am getting the error ActiveRecord::UnknownAttributeError in ProductsController#import, while trying to import a large csv file.
The migration is done and the table is created.
When i try to process the file I get,
unknown attribute 'CO_NO' for Product.
Extracted source (around line #14):
Model:
class Product < ActiveRecord::Base
attr_accessor :co_no, :parcel_id, :file_t, :asmt_yr
require 'csv'
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
product_hash = row.to_hash # exclude the price field
product = Product.where(id: product_hash["id"])
if product.count == 1
product.first.update_attributes(product_hash)
else
Product.create!(product_hash)
end # end if !product.nil?
end # end CSV.foreach
end # end self.import(file)
end # end class
controller:
classProductsController<ApplicationController
def index
#products=Product.all
end
def import
Product.import(params[:file])
redirect_toroot_url,notice:"Productsimported."
end
end
csv header:
co_no,parcel_id,file_t,asmnt_yr,bas_strt plus many more
27,"0000009500001010","R",2014
irb output:
Product.all
NameError: uninitialized constant Product
from (irb):2
from /Users/david/.rvm/rubies/ruby-2.1.3/bin/irb:11:in `<main>'
Just a hunch without trying out the code, but the error message you are showing...
unknown attribute 'CO_NO' for Product.
indicates an input attribute that is "CO_NO", but you've declared "co_no". Since Ruby is case sensitive for variable names is probably failing to find attribute "CO_NO" (as opposed to "co_no").
By adding quotes around the cvs header, I was able to get past the error.
"co_no","parcel_id","file_t","asmnt_yr","bas_strt"
Also, I discovered coming from Rails 3 to Rails 4 that attr_accessor is now handled in the controller. After these 2 tweaks the data imported correctly

Exporting CSV in Rails with a class method being called on an array of objects

I want to export a csv file from Rails and I'm following the Railscast on this topic (http://railscasts.com/episodes/362-exporting-csv-and-excel?view=asciicast).
In the Railscast, the controller (products_controller.rb) contains:
def index
#products = Product.order(:name)
respond_to do |format|
format.html
format.csv { send_data #products.to_csv }
end
end
The Product model (models/product.rb) contains:
def self.to_csv
CSV.generate do |csv|
csv << column_names
all.each do |product|
csv << product.attributes.values_at(*column_names)
end
end
end
As seen in this StackOverflow question (Exporting CSV data from Rails), to_csv is a class method that is being called on an array of class objects. I expected this to fail because to_csv is not being called on the Product class.
This works, but why does it work?
My question should have been "Why would sending the message, to_csv, to an instance of ActiveRecord::Relation cause a method on the Product class object to be invoked?"
In Rails, class methods automatically become available as "collection" methods; This is the reason why they are available to Relation objects (See this SO question/answer I missed earlier: how does this Ruby class method get invoked?).
You are indeed correct - you are sending the message to_csv to an instance of, initially ActiveRecord::Relation, which ultimately is transformed into Array.
Have you required csv? If so, sending to_csv to an instance of an Array will work.
The stdlib/csv library decorates Array - http://ruby-doc.org/stdlib-1.9.3/libdoc/csv/rdoc/Array.html

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

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