Decisive
DSL for rendering and streaming CSVs in Rails apps
Usage
Example usage:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
include ActionController::Live # required to stream; decisive will fall back to rendering without it
def index
@users = User.all
end
end
# app/views/users/index.csv.decisive
csv @users, filename: "users-#{Time.zone.now.strftime("%Y_%m_%d")}.csv" do
column "Email" # omitted accessor field gets inferred: user.email
column "Full name", :name # explicit accessor field: user.name
column "Signed up" do |user| # accepts a block for doing something special
user.created_at.to_date
end
end
Then visit /users.csv to stream a file named "users-2010_01_01.csv" with the following contents:
Full name | Signed up | |
---|---|---|
frodo@example.com | Frodo Baggins | 2002-06-19 |
sam@example.com | Samwise Gamgee | 2008-10-13 |
Non-streaming usage for non-deterministic headers:
Sometimes, we don't know exactly what the headers will be until we've iterated through every record.
For example, lets say that the Frodo record has a #faqs attribute of { "Riddles?" => "Yes" }
, while Sam's is { "Hero?" => "Frodo" }
.
In this case, you can pass stream: false
to #csv, and the method will yield each record to the block:
# app/views/users/index.csv.decisive
csv @users, filename: "users-#{Time.zone.now.strftime("%Y_%m_%d")}.csv", stream: false do |user|
column "Email"
column "Full name"
user.faqs.favorite_questions_and_answers.each do |question, answer|
column question, answer
end
column "Signed up", user.created_at.to_date # we have access to the user record directly
end
Visiting /users.csv will render a file named "users-2010_01_01.csv" with the following contents:
Full name | Riddles? | Hero? | Signed up | |
---|---|---|---|---|
frodo@example.com | Frodo Baggins | Yes | 2002-06-19 | |
sam@example.com | Samwise Gamgee | Frodo | 2008-10-13 |
Debugging
Errors in your decisive template will often be swallowed while streaming is enabled, resulting in only some of the csv being rendered, without any explanation. You can temporarily switch decisive into non-streaming mode to see these errors:
# decisive template
csv @records, filename: "report.csv", stream: false do
...
end
# controller
class ReportController < ApplicationController
# include ActionController::Live
...
end
Gotchas
Sometimes the streaming templates will hang in test mode. This could be because action_controller/test_case
has been loaded, which monkeypatches AC::Live in a way that seems to breaks things. If you are running into this, try this workaround (cucumber example):
# features/support/fix_ac_live.rb
# requiring cucumber/rails requires rails/test_help which requires action_controller/test_case which redefines AC::Live#new_controller_thread to be single threaded, which breaks our downloads
# set it back to original method definition
module ActionController
module Live
silence_redefinition_of_method :new_controller_thread
def new_controller_thread # :nodoc:
Thread.new {
t2 = Thread.current
t2.abort_on_exception = true
yield
}
end
end
end
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/botandrose/decisive.
License
The gem is available as open source under the terms of the MIT License.