I try to export a CSV table based on parameters in URL in Rails 4.
The URL parameters look like this "classes?date=2018-08-01" and depending on the URL date the view changes a displayed table with data for given month. I want to export these data. However, the Export button downloads always only data for the current month. What am I doing wrong?
I added a simplified code, not the exact copy. So if you need to see anything more from the code, write a comment.
Controller:
def index
#date = if params[:date].present?
Time.zone.parse(params[:date])
else
Time.zone.now
#classes = Class.includes(:teacher).on_month(#date.to_date)
respond_to do |format|
format.html
format.csv { send_data #classes.to_csv }
end
end
View:
= link_to 'Export', classes_path(#classes, format: :csv)
Model:
def self.to_csv
column_names = %w{ Teacher Class }
CSV.generate(headers: true) do |csv|
csv << column_names
all.each do |class|
csv << [class.teacher.name, class.class_name]
end
end
end
In your link to export you should include the date instead of the classes since in your controller if params[:date] is missing then you are using the current date.
= link_to 'Export', classes_path(date: #date, format: :csv)
Related
I am working on to allow download an excel file with the below code:
login = Etc.getlogin
#dataFile = "C:/rails/#{login}data.csv"
csv1=CSV.open(#dataFile, 'w') do |csv|
$data.each do |eachrow|
csv << [eachrow.name+"#gmail.com"]
end
end
send_file(#dataFile, :filename => "#{login}data", :type => "application/csv")
Using the above code, I am able to create a file and write the data.
Instead of this, how do i write the data in csv and get downloaded into users machine instead of saving in local/server.
What you can do is generate a string with the CSV library, using CSV::generate instead of CSV::open.
Controller:
class DataController < ApplicationController
def download
respond_to do |format|
format.csv { send_csv_download }
end
end
private
def send_csv_download
string = CSV.generate do |csv|
#data.each { |row| csv << ["#{row.name}#gmail.com"] }
end
send_data string, filename: 'foo.csv', type: :csv
end
end
config/routes.rb:
get '/download', to: 'data#download'
View:
<%= link_to 'Download CSV', download_path(format: :csv) %>
Note: Obviously, I have no idea where you get your #data from, since it isn't specified in your question.
AcitveAdmin provides a link at the bottom of the index page to download all resources in multiple formats, and one of the formats is CSV. It is gonna take all the resources, put them in a CSV file, and give that back to us. In my situation, I have a condition that there are certain resources that can be downloaded via CSV, like:
User.downloadables
But I'm unable to figure it out how to give this set instead of User.all to CSV format?
I have looked into documentation, and it doesn't say much except changing the layout of a CSV file.
Apparently, I couldn't find a way to pass a specific set of records to CSV file, so I wrote the custom controller method for it in ActiveAdmin, and that's how I accomplished it:
app/models/user.rb:
scope :downloadables, -> { where(status: :downloadable) }
def self.to_csv
downloadable_users = User.downloadables
CSV.generate do |csv|
csv << column_names
downloadable_users.each do |user|
csv << user.attributes.values_at(*column_names)
end
end
end
app/admin/user.rb:
First, I generated a collection action that will let us download CSV file:
collection_action :download_csv do
redirect_to action: :download_csv
end
Now, to have a link on index page through which one would be able to download the records in CSV file:
action_item only: :index do
link_to "Download CSV", download_csv_admin_users_path
end
And finally to have a controller method that will actually call the method from model, and do the rest of the magic:
controller do
def download_csv
respond_to do |format|
format.html { send_data User.to_csv, filename: "users-#{Data.today}.csv" }
end
end
end
And that's it. It is working.
You can also customize the csv export as well for active admin.
ActiveAdmin.register Post do
csv force_quotes: true, col_sep: ';', column_names: false do
column :title
column(:author) { |post| post.author.full_name }
end
end
Cheers
I am trying to generate a CSV output with data from my database. I would like to provide these data to a third party, so I picture I would give to someone a URL (website.com/api_data/cars) and by accessing this URL the person would be able to work with it - I think I want to access the URL and then to see there (in the action) data displayed and separated by , or ;.
But how to do that?
So far, I am trying following approach:
csv_string = CSV.generate do |csv|
cols = ["column one", "column two", "column three"]
csv << cols
csv << ["A", "B", "C"]
#filename = "data-#{Time.now.to_date.to_s}.csv"
end
send_data(csv_string, :type => 'text/csv; charset=utf-8; header=present', :filename => #filename)
This is in the controller generate_data and action csv_cars.
When I run this action (webste.com/generate_data/csv_cars), it will automatically pop up a window to download the file.
But how to write the CSV content to the action? So when I open the URL, I'll see there written the content from the database?
I know this is an old thread but I came across it in my search so in case someone else does the same, here's my answer and what worked for me.
I think bhanu had a good way of going about it but I did change something. Instead of doing #cars within the respond_to, I just called send_data Cars.to_csv since, as Rob stated, it was made as a class method. It worked beautifully for me.
class Car < ActiveRecord::Base
def self.to_csv(make)
attributes = %w{id name price} #customize columns here
cars = Car.where(maker_name: make)
CSV.generate(headers: true) do |csv|
csv << attributes
cars.each do |car|
csv << attributes.map{ |attr| car.send(attr) }
end
end
end
end
And then in the controller
class CarsController < ApplicationController
def index
send_data Cars.to_csv('Chevy'), filename: "cars-#{Date.today}.csv"
end
end
I understand that this will be called when you go to cars/index but you can put that into any method, if statement or anything you want and just have it called whenever you would like from there. You can also have arguments, as I did above with make, and query for certain fields. It was definitely a lot easier than I thought it was going to be. Hope this helped someone.
You need to do something like this.
def csv_cars
headers = ['column one', 'column two', 'column three']
csv_data = CSV.generate(headers: true) do |csv|
csv << headers
csv << ["A", "B", "C"]
end
send_data csv_data, filename: "data-#{Date.today.to_s}.csv", disposition: :attachment
end
define a to_csv method in your model as shown below
class Car < ActiveRecord::Base
def self.to_csv
attributes = %w{id name price} #customize columns here
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |car|
csv << attributes.map{ |attr| car.send(attr) }
end
end
end
end
Later in your controller
class CarsController < ApplicationController
def index
#cars = Car.all
respond_to do |format|
format.html
format.csv { send_data #cars.to_csv, filename: "cars-#{Date.today}.csv" }
end
end
end
I'm not quite sure what I am overlooking when attempting to allow csv download on my Game model and I'm getting a little lost.
On the profile show page I render an index like list of games associated with that user, i.e. their game schedule.
The Profiles Controller-
def show
#user = User.find_by_profile_name(params[:id])
if #user
#listings = #user.listings
#games = #user.games
respond_to do |format|
format.html
format.csv {send_data #games.to_csv}
end
return
render action: :show
else
render file: "public/404", status: 404, formats: [:html]
end
end
Then in the game.rb I define the method to_csv
def self.to_csv
CSV.generate do |csv|
csv << column_names
all.each do |item|
csv << item.attributes.values_at(*column_name)
end
end
end
And on the profile show page to download the expected csv game schedule
<%= link_to "Download my Schedule", profile_path(format: 'csv')%>
I believe this might be my issue lies, but that doesn't quite explain what I get in my csv which is just a game object
file-
Here is my routes.rb
resources :games
match 'friendships/:friend_id' => 'user_friendships#new', :as => :new_friendship
match 'dashboard' => 'dashboard#show', :as => :dashboard
root to: "profiles#index"
get '/players', to: 'profiles#index', as:'players'
get '/players', to: 'profiles#index', as:'users'
get '/:id', to: "profiles#show", as: 'profile'
The file should be formatted with the column names (location, opponent, time, etc) as the header line and the corresponding lines with their respective values for each instance associated to a user.
I think the to_csv method inside game should be re-declared as -
passed in the games array which needed to be converted.
the param passed to values_at is column_names not column_name.
def self.to_csv(games)
CSV.generate do |csv|
csv << column_names
games.each do |item|
csv << item.attributes.values_at(*column_names)
end
end
end
and in the controller, the code should be:
def show
#user = User.find_by_profile_name(params[:id])
if #user
#listings = #user.listings
#games = #user.games
respond_to do |format|
format.html
format.csv {send_data Game.to_csv(#games)}
end
return
render action: :show
else
render file: "public/404", status: 404, formats: [:html]
end
end
otherwise, you will output all the games no matter which user you are using.
Although cenjongh's answer isn't wrong, let me elaborate on it.
The syntax Game.to_csv(#games) goes against Ruby's/Rails' object oriented approach for me.
Since the CSV generation code in your case is totally model independent (you don't make any assumptions of column names, etc.) you could feed this method any array of models, i.e. Game.to_csv(#shampoos) which would still work but wouldn't read very well.
Since Rails scopes the all method according to the criteria attached to the ActiveRelation object, using it in your class method wouldn't result in an output of all the games.
Assuming you're using at least Rails 3.0 the line #games = #user.games would give you an ActiveRelation object, not an array, meaning you can call #games.to_csv (or to make it even clearer #user.games.to_csv) directly, which reads what it is, namely converting a list of games, that belong to a user into CSV.
Oh, and I guess this is just testing code, but the return shouldn't be there. And the render statement should go into the block of format.html.
This is more of an architecture/functional question for Rails. I have a search function which sends the criteria to the model where the query resides. The search works. Now I have a CSV export link <%= link_to "CSV", contacts_path(format: "csv") %> in my view file which points to localhost/books.csv.
The export didn't work without my search parameters (so localhost/book.csv?book_name=foo works as expected). What I do in send_data is I pass the #books object to the .to_csv function inside my model, and it becomes nil without passing the parameter also. Pls see code below.
My controller:
def index
#books = Book.search(params[:search])
respond_to do |format|
format.html
format.csv { send_data Book.to_csv(#books) }
end
My model:
def self.search(criteria)
find(:all, :conditions => ['book_name LIKE ?', "%#{criteria}%"])
end
def self.to_csv(search_results)
CSV.generate do |csv|
csv << column_names
search_results.each do |contact|
csv << contact.attributes.values_at(*column_names)
end
end
end
I like to understand why. The current setup seems to be making another request to the server in order to generate the CSV file, and that's why it requires the parameters in localhost/books.csv request. Is this correct?
Now, if instead I put the query inside the controller like below, the CSV request works as expected (so I just click the link and receive the file).
def index
#books = Book.find(:all, :conditions => ['book_name LIKE ?', "%#{criteria}%"]) respond_to do |format|
format.html
format.csv { send_data Book.to_csv(#books) }
end
I love to keep the query inside the model for the sake of organization, so would be awesome if you guys can point me to the right direction. Thanks!
I would suggest that you change your link to something like this:
<%= link_to "CSV", contacts_path(params.merge(format: "csv")) %>
This will pass down the current search parameters plus the new option for the format to be CSV. Then you can continue to keep the search method inside the model in the way that you had originally written it.