Since I ended up using a csv method and not a json method I thought it might be a good Idea to change the question name and remove the part with the json if other people might have the same problem someday.
Update: I've tried using this as a way to export data.
In my controller I have the following:
def profile
#user = User.find(params[:id])
#user_posts = #user.posts.order('created_at DESC')
respond_to do |format|
format.html
format.csv { send_data #user.csv, filename: "userinfo-#{Date.today}.csv" }
end
end
In my view :
<p><%= link_to("Generate Report", user_path(#user, format: :csv), class: "btn btn-success",) %></p>
In my user.rb :
def csv
CSV.generate do |csv|
csv << column_names
all.each do |item|
csv << item.attributes.values_at(*column_names)
end
end
end
Update 2: Managed to fix it myself, I was using a wrong csv method in my Controller. Replacing it with the following :
def csv
CSV.generate do |csv|
csv << %w{ user_id user_username user_email }
csv << [ self.id, self.username, self.email]
end
end
works like a charm.
Best regards
I want to recover data in two different tables and put the data on excel but I have a little bug if someone has an idea. Thank you
class ExportController < ApplicationController
def index
#attachments= Attachment.all
#projects= Project.all
respond_to do |format|
format.html
format.csv {send_data #attachments.to_csv}
format.xls {send_data #attachments.to_csv(col_sep: "\t") and send_data #projects.to_csv(col_sep: "\t")}
end
end
end
route.rb
get '/export', to: 'export#index'
model..
class Export < ActiveRecord::Base
def self.to_csv(options = {})
CSV.generate(options) do |csv|
csv << column_names
all.each do |table|
csv << table.attributes.values_at(*column_names)
end
end
end
end
view
<h1>Exportation</h1>
<%= link_to 'Download as .xlsx', export_path(format: :xls) %>
model (project.rb)
class Project < ActiveRecord::Base
def self.to_csv(options = {})
CSV.generate(options) do |csv|
csv << column_names
all.each do |project|
csv << project.attributes.values_at(*column_names)
end
end
end
end
model (attachment.rb)
class Attachment < ActiveRecord::Base
def self.to_csv(options = {})
CSV.generate(options) do |csv|
csv << column_names
all.each do |table|
csv << table.attributes.values_at(*column_names)
end
end
end
end
error image
You can't respond twice one request. An option would be:
format.xls {send_data #attachments.to_csv(col_sep: "\t") + #projects.to_csv(col_sep: "\t") }
With this, you'll respond with one file containing attachments and projects.
I need your help! I Have a simple database that I would like to download as a csv file.
Here is The Database:
class CreateTeams < ActiveRecord::Migration[5.1]
def change
create_table :teams do |t|
t.integer :name
t.integer :match
t.string :scout
t.timestamps
end
end
end
I would like to have a button on the program that I'm making to download the database, probably in the index.html.erb.
Here is my Teams controller:
class TeamController < ApplicationController
def index
#teams = Team.all
end
def new
#team = Team.new
end
def create
#team = Team.new(team_params)
respond_to do |format|
if #team.save
format.js
redirect_to '/team/index'
else
format.json { render json: #team.errors.messages, status: :unprocessable_entity }
end
end
end
private
def team_params
params.require(:team).permit(:name, :scout, :match)
end
end
I need your help please!
You can read the model attributes and push it to a csv string.
def index
csv_string = CSV.generate do |csv|
# header
csv << Team.attribute_names
Team.all.each do |team|
csv << team.attributes.values
end
end
respond_to do |format|
format.csv { render csv: csv_string, filename: 'teams.csv' }
end
end
Or you can also use this ruby gem comma, which does exactly the same thing as what you need. Add the gem in your Gemfile, install and you can use it like:
def index
respond_to do |format|
format.csv { render csv: Team.limit(50) }
end
end
Put this in your model:
def self.to_csv
attributes = %w{name match scout}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |team|
csv << team.attributes.values_at(*attributes)
end
end
end
Then in your view (add index action in controller):
<%= link_to "CSV", teams_path(format: "csv") %>
Then in your controller add under
#teams = Team.all
format.csv { send_data #teams.to_csv, filename: "Teams-#{Date.today}.csv"
And you should be good to go. You may need to add the index action to your routes if you didn't use "resources"
I found a way to do this, but thanks to all the people that posted an answer!
First, you put this in your rails model:
def self.to_csv
attributes = %w{number match scout}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |team|
csv << team.attributes.values_at(*attributes)
end
end
end
Then you put This in your routes:
resources :teams
Then you put this in your Teams controller index action:
def index
#team = Team.all
respond_to do |format|
format.html
format.csv {send_data #team.to_csv}
end
end
and in your application.rb add
require 'csv'
If you want to view the code, go to 'localhost:3000/teams.csv' and it will download it as a csv file and it will open in Microsoft Excel(if you have it as your default).
I am trying to download some model data from a search page to a csv file. Here is the relevant code:
config/application.rb
require 'csv'
controller
def find_by_registration_date
#registration_date = params[:registration_date]
#registrations = Registration.where("DATE(created_at) = ? ", #registration_date)
respond_to do |format|
format.html
format.csv { send_data Registrations.to_csv(#registrations) }
end
end
model
def self.to_csv(options = {})
CSV.generate(options) do |csv|
csv << column_names
all.each do |registration|
csv << registration.attributes.values_at(*column_names)
end
end
end
EDIT: When I try this I get the error:
TypeError in RegistrationsController#find_by_registration_date
can't convert ActiveRecord::Relation into Hash
Rails.root: /home/johnmlocklear/railsApps/nso
Application Trace | Framework Trace | Full Trace
app/models/registration.rb:43:in `to_csv'
app/controllers/registrations_controller.rb:139:in `block (2 levels) in find_by_registration_date'
app/controllers/registrations_controller.rb:137:in `find_by_registration_date'
EDIT: This is so odd. If I do something like:
def find_by_registration_date
#registrations = Registration.order :first_name
respond_to do |format|
format.html
format.csv { send_data #registrations.to_csv }
end
end
...then it works as expected. I'm assuming that one of these methods is returning an array, and another is returning a hash?
Here is what I ended up with...
app/views/registrations/find_by_registration_date.html.erb
<%= link_to 'Export CSV', csv_path(registration_date: #registration_date) %>
routes.rb
match 'csv' => 'registrations#export_csv', :as => :csv
app/controllers/registrations_controller.rb
def export_csv
#registration_date = params[:registration_date]
#registrations = Registration.where("DATE(created_at) = ? ", #registration_date)
csv = CSV.generate do |csv|
csv << ["last_name", "id"]
#registrations.each do |r|
csv << [r.last_name, r.student_id]
end
end
send_data(csv, :type => 'test/csv', :filename => 'add_hoc.csv')
end
Goes against the 'skinny controller/fat model' paradigm, but works for now.
First, the desired result
I have User and Item models. I'd like to build a JSON response that looks like this:
{
"user":
{"username":"Bob!","foo":"whatever","bar":"hello!"},
"items": [
{"id":1, "name":"one", "zim":"planet", "gir":"earth"},
{"id":2, "name":"two", "zim":"planet", "gir":"mars"}
]
}
However, my User and Item model have more attributes than just those. I found a way to get this to work, but beware, it's not pretty... Please help...
Update
The next section contains the original question. The last section shows the new solution.
My hacks
home_controller.rb
class HomeController < ApplicationController
def observe
respond_to do |format|
format.js { render :json => Observation.new(current_user, #items).to_json }
end
end
end
observation.rb
# NOTE: this is not a subclass of ActiveRecord::Base
# this class just serves as a container to aggregate all "observable" objects
class Observation
attr_accessor :user, :items
def initialize(user, items)
self.user = user
self.items = items
end
# The JSON needs to be decoded before it's sent to the `to_json` method in the home_controller otherwise the JSON will be escaped...
# What a mess!
def to_json
{
:user => ActiveSupport::JSON.decode(user.to_json(:only => :username, :methods => [:foo, :bar])),
:items => ActiveSupport::JSON.decode(auctions.to_json(:only => [:id, :name], :methods => [:zim, :gir]))
}
end
end
Look Ma! No more hacks!
Override as_json instead
The ActiveRecord::Serialization#as_json docs are pretty sparse. Here's the brief:
as_json(options = nil)
[show source]
For more information on to_json vs as_json, see the accepted answer for Overriding to_json in Rails 2.3.5
The code sans hacks
user.rb
class User < ActiveRecord::Base
def as_json(options)
options = { :only => [:username], :methods => [:foo, :bar] }.merge(options)
super(options)
end
end
item.rb
class Item < ActiveRecord::Base
def as_json(options)
options = { :only => [:id, name], :methods => [:zim, :gir] }.merge(options)
super(options)
end
end
home_controller.rb
class HomeController < ApplicationController
def observe
#items = Items.find(...)
respond_to do |format|
format.js do
render :json => {
:user => current_user || {},
:items => #items
}
end
end
end
end
EDITED to use as_json instead of to_json. See How to override to_json in Rails? for a detailed explanation. I think this is the best answer.
You can render the JSON you want in the controller without the need for the helper model.
def observe
respond_to do |format|
format.js do
render :json => {
:user => current_user.as_json(:only => [:username], :methods => [:foo, :bar]),
:items => #items.collect{ |i| i.as_json(:only => [:id, :name], :methods => [:zim, :gir]) }
}
end
end
end
Make sure ActiveRecord::Base.include_root_in_json is set to false or else you'll get a 'user' attribute inside of 'user'. Unfortunately, it looks like Arrays do not pass options down to each element, so the collect is necessary.
Incase anyone is looking for an alternative solution for this, this is how I solved this in Rails 4.2:
def observe
#item = some_item
#user = some_user
respond_to do |format|
format.js do
serialized_item = ItemSerializer.new(#item).attributes
serialized_user = UserSerializer.new(#user).attributes
render :json => {
:item => serialized_item,
:user => serialized_user
}
end
end
end
This returns the serialized version of both objects as JSON, accessible via response.user and response.item.
There are a lot of new Gems for building JSON now, for this case the most suitable I have found is Jsonify:
https://github.com/bsiggelkow/jsonify
https://github.com/bsiggelkow/jsonify-rails
This allows you to build up the mix of attributes and arrays from your models.
Working answer #2 To avoid the issue of your json being "escaped", build up the data structure by hand, then call to_json on it once. It can get a little wordy, but you can do it all in the controller, or abstract it out to the individual models as to_hash or something.
def observe
respond_to do |format|
format.js do
render :json => {
:user => {:username => current_user.username, :foo => current_user.foo, :bar => current_user.bar},
:items => #items.collect{ |i| {:id => i.id, :name => i.name, :zim => i.zim, :gir => i.gir} }
}
end
end
end