I'm making an export to csv file functionality in a Ruby on Rails repo and I'm almost done. However, when I press the "Export all" button, I get the undefined method `export' for nil:NilClass
error. The log shows that format.csv { send_data @foos.export, filename: "foos-#{Date.today}.csv" }
went wrong. What am I missing please?
This is model
class Foo < ApplicationRecord
has_many :bars
def export
[id, name, foos.map(&:name).join(' ')]
end
end
This is part of controller
def index
@foos = Foo.all
end
def export
all = Foo.all
attributes = %w{name}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |foo|
csv << attributes.map{ |attr| foo.send(attr) }
end
respond_to do |format|
format.csv { send_data @foos.export, filename: "foos-#{Date.today}.csv" }
end
end
end
def name
"#{foo_id} #{name}"
end
This is View
<a href="/export.csv"><button >export all</button></a>
This is Routes
Rails.application.routes.draw do
resources :foos
get :export, controller: :foos
root "foos#index"
end
This is Rake (lib/tasks/export.rb)
namespace :export do
task foo: :environment do
file_name = 'exported_foo.csv'
csv_data = Foo.to_csv
File.write(file_name, csv_data)
end
end
CodePudding user response:
Start by creating a service object that takes a collection of records and returns CSV so that you can test the CSV generation in isolation:
# app/services/foo_export_service.rb
# Just a Plain Old Ruby Object that converts a collection of foos into CSV
class FooExportService
# The initializer gives us a good place to setup our service
# @param [Enumerable] foo - an array or collection of records
def initialize(foos)
@headers = %w{name} # the attributes you want to use
@foos = foos
end
# performs the actual work
# @return [String]
def perform
CSV.generate do |csv|
@foos.each do |foo|
csv << foo.serializable_hash.slice(@headers).values
end
end
end
# A convenient factory method which makes stubbing the
# service easier
# @param [Enumerable] foos - an array or collection of records
# @return [String]
def self.perform(foos)
new(foos).perform
end
end
# example usage
FooExportService.perform(Foo.all)
Not everything in a Rails application needs to be jammed into a model, view or controller. They already have enough responsiblities. This also lets you resuse the code for example in your rake task if you actually need it.
This simply iterates over the collection and uses Rails built in serialization features to turn the model instances into hashes that can be serialized as CSV. It also uses the fact that Hash#slice
also reorders the hash keys.
In your controller you then just use the service object:
class FoosController
def export
@foos = Foo.all
respond_to do |format|
format.csv do
send_data FooExportService.perform(@foos),
filename: "foos-#{Date.today}.csv"
end
end
end
end
You don't even really need a separate export
action in the first place. Just use MimeResponds to add CSV as an availble response format to the index:
class FoosController
def index
# GET /foos
# GET /foos.csv
@foos = Foo.all
respond_to do |format|
format.html
format.csv do
send_data FooExportService.perform(@foos),
filename: "foos-#{Date.today}.csv"
end
end
end
end
<%= link_to("Export as CSV", foos_path(format: :csv)) %>