I'm a rails newbie. Could someone tell me if there is a quick and easy way to generate rtf documents for people to download using rails?
For example if I have "views/users/show.html.erb" the view normally outputs to html so I want to people to be able to download as an identical rtf document?
ruby-rtf is the gem you are looking for. There are some examples of rtf generation here
Add this to initializers/mime_types.rb:
Mime::Type.register "text/richtext", :rtf
Code to give you an idea:
document = RTF::Document.new(RTF::Font.new(RTF::Font::ROMAN, 'Times New Roman'))
document.paragraph do |p|
p << "This is the first sentence in the paragraph. "
p << "This is the second sentence in the paragraph. "
p << "And this is the third sentence in the paragraph."
end
send_file document, :type=>"text/richtext"
ruby-rtf - is rtf parsing
this one is for rtf generating - https://github.com/clbustos/rtf
I am doing this on Rails 4.2 and it seems to work so far. I have not tested this on the latest version of Rails but that is next. The gem has not been maintained recently so the verdict is still out on whether or not this will meet all my requirments.
First, Nazar is correct the link should be https://github.com/clbustos/rtf in order to create RTF files from a Rails controller vs parsing RTF files.
I tried the code provided in the answers but an issue exists with using send_file in this implementation. Since send_file is meant for sending a file given a path it doesn't work as shown. On the other hand send_data is used to send a data stream directly from the Rails app. So here is the code I used for creating an RTF document for download from the controller directly.
Basic Setup:
Gemfile:
gem 'rtf'
Install the gem.
config/initializers/mime_types.rb
Mime::Type.register "text/richtext", :rtf
In the "lib" Directory I created a special RTF generator (Putting the code in the controller is fine to generate an RTF document for testing but the RTF generation should be in a seperate file because code to create an RTF document can get long quickly and doesn't really belong in the controller):
lib/rtf_reporting.rb
class RtfReporting
require 'rtf'
def initialize
end
...
def self.get_rtf_document(reporting)
document = RTF::Document.new(RTF::Font.new(RTF::Font::ROMAN, 'Times New Roman'))
document.paragraph do |p|
p << "This is the first sentence in the paragraph. TESTING ID = #{reporting.id}"
p << "This is the second sentence in the paragraph. "
p << "And this is the third sentence in the paragraph."
end
return document.to_rtf
end
end
Controller:
app/controllers/reportings_controller.rb
class ReportingsController < ApplicationController
require 'rtf_reporting'
...
def show
...
respond_to do |format|
format.rtf do
send_data RtfReporting.get_rtf_document(#reporting), :type=>"text/richtext",
:filename => "your_file_name.rtf",
:disposition => 'attachment'
end
end
end
end
I hope this helps someone. The original answers in this post helped me out! In know the biggest difference between my answer and the other answers is send_file vs send_data which in its own right is a big deal but I also wanted to provide some insight on how I would organize my code given that there is no view file like there is for an HTML or PDF based solution.
Related
In my rails application I've created a business daily report. There is some non-trivial logic for showing it (all kind of customizable parameters that are used for filtering in the model, a controller that calls that model and some non-trivial view for it, for example, some of the columns are row-spanning over several rows).
Now I wish to send this report nightly (with fixed parameters), in addition to the user ability to generate a customize report in my web site. Of course, I wish not to re-write/duplicate my work, including the view.
My question is how can I call the controller action from my mailer so that it will be as if the page was requested by a user (without sending a get request as a browser, which I wish to avoid, of course)?
In answer to your question is if you are generating some sort of pdf report then go with using the wicke_pdf gem does exactly that generates pdfs. To send a report on a nightly basis the best thing for this is to implement some sort of cron job that runs at a particular time which you can do using the whenever gem. You can do something like:
schedule.rb
every :day, :at => '12:00am'
runner User.send_report
end
With this at hand you can see that you call the send_report method sits inside the User model class as shown below:
User.rb
class User < ActiveRecord::Base
def self.send_report
ReportMailer.report_pdf(#user).deliver
end
end
Inside send_report we call the mailer being ReportMailer which is the name of the class for our mailer and the method being report_pdf and pass in the user. BUT remember this is an example I have here I am not sure the exact specified information you want in a report.
Mailer
class ReportMailer< ActionMailer::Base
default :from => DEFAULT_FROM
def report_pdf(user)
#user = user
mail(:subject => "Overtime", :to => user.email) do |format|
format.text # renders report.text.erb for body of email
format.pdf do
attachments["report.pdf"] = WickedPdf.new.pdf_from_string(
render_to_string(:pdf => "report",:template => 'report/index.pdf.erb',
:layouts => "pdf.html"))
end
end
end
end
Inside the mailer there are a variety of things going on but the most important part is inside the format.pdf block that uses a variety of wicked_pdf methods (this is assuming that you are using wicked_pdf btw. Inside the block you create a new WickedPDF pdf object and render it to a string. Then provide it with the name of the report, the template and the layout. It is important that you create a template. This usually will where the report will be displaying from. The file type is a .pdf.erb this means that when this view or report is generated in the view the embedded ruby tags are being parsed in and the output is going to be a pdf format.
UserController
def report
#user = User.scoped
if params[:format] == 'pdf'
#Do some stuff here
User.send_report(#users)
end
respond_to do |format|
format.html
format.pdf do
render :pdf => "#{Date.today.strftime('%B')} Report",
:header => {:html => {:template => 'layouts/pdf.html.erb'}}
end
end
end
The key thing you asked that I picked up on.
how can I call the controller action from my mailer
In the controller simply collate a scope of Users, then check the format is a pdf, providing it is do some stuff. Then it will run the method send_report which I earlier highlighted in the user model class (Btw in your words this is the controller calling the model). Then inside the respond block for this there is a format.pdf so that you can generate the pdf. Once again note that you need a template for the core design of the pdf, which is similar to how rails generates an application.html.erb in the layouts. However here we have a pdf.html.erb defined. So that this can be called anywhere again in your application should you want to generate another pdf in your application somewhere else.
Think I've provided a substantial amount of information to set you off in the right direction.
I want the the table to be displayed in pdf format .
we are using
format.html
format.json{render json: #user}
etc for generating html ,json format
Similarly
I want to render the page as pdf .
so what steps are there if it is possible in rails???
Step one:
register PDF type for use in respond_to blocks
# config/mime_types.rb
Mime::Type.register "application/pdf", :pdf
Step two:
in your controller respond to PDF format request
respond_to do |format|
format.html
format.json{render json: #user}
format.pdf do
# ...
# here your PDF generating code
# ...
send_data(your_generated_pdf, filename: 'your_filename.pdf', type: 'application/pdf')
end
end
For generating PDF you can use pdfkit with wkthmltopdf, Prawn and some other. Refer to the respective documentations on their usage.
Starting from Rails 5:
Step three:
add gem 'responders' as Rails 5 changelog says: "Remove respond_to/respond_with placeholder methods, this functionality has been extracted to the responders gem"
Another thing is Prawn.
It seems more robust and support complex situations. So you may choose this to work with something sophisticated. Get the https://prawnpdf.org/manual.pdf here.
The API docs: https://prawnpdf.org/api-docs/2.3.0/
Thanks Glenn for pointing it out.
I think this might help [ https://github.com/mileszs/wicked_pdf ]
You will always be able to use like format.pdf as long as your route supports .:format part. So just use that to capture the request for pdf. Then render the pdf using wicked_pdf.
The basic usage is showing there as below
def controller_action
... ... ...
... ... ...
format.pdf do
render :pdf => "file_name"
end
end
Here is another article might help to understand the usage.
Finally its done with prawn and prawn-rails. Here are the details.
To make my system ready
# Add this to your Gemfile
gem 'prawn'
gem 'prawn_rails'
# run
bundle install
I hit the url http://localhost:3000/regions i.e. to hit the index of RegionsController and it shows the page as pdf format.
I have a Region Model with a single attribute name. Has two entries in regions table.
[
#<Region id: 1, name: "Region 1", ... >,
#<Region id: 2, name: "Region 2", ... >
]
My Controller Method:
def index
#regions = Region.all # Should return two region objects :)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #regions }
format.pdf # <---------- This will handle the pdf response
end
end
I have created a view file in views/regions/index.pdf.prawn. Anyway the view file name format is :action.pdf.prawn and the view file is containing
prawn_document() do |pdf|
#regions.each {|r| pdf.text r.name}
end
This will just output the name of Regions.
Thats it. You have your page in pdf format. Now just play with all other options provided by Prawn and Prawn-Rails. You can find some here - http://prawn-rails-demo.heroku.com/
Just thinking to write a series of blogs guiding how to use different pdf generation tool with rails 3.
Let me know if it solves your problem.
If you need to use templates, you can always use the combine_pdf gem by itself (it has minor editing capabilities, such as tables and text boxes) or together with any of the aforementioned solutions, such as Prawn.
I have a Posts table in my Rails 3.0.10 app. I want to give my users the option to export a particular Post record to CSV format, not all of them. And while my Post table has a lot of fields, I only want to export the title and the body.
After doing some searching apparently the best way to do this is through FasterCSV. And apparently it's already built in Ruby 1.9.2, which I'm using. Thing is pretty much all the tutorials are outdated (from Rails 1 or 2) and I have absolutely no idea how to accomplish this.
I've tried putting in my posts_controller.rb
def export_to_csv
#post = Post.find(params[:id])
csv_string = CSV.generate do |csv|
csv << [#post.title, #post.body]
end
# send it to the browsah
send_data csv_string,
:type => 'text/csv; charset=iso-8859-1; header=present',
:disposition => "attachment; filename=post.csv"
end
Which I THINK may be the right code, but I have no idea how to use it in my view. Ideally I want to have a link to export the CSV file, but I'm thinking it has to be done through a form_tag?
Would appreciate if someone could point me towards the right direction. Thanks.
After several hours of Googling and experimentation I found the answer.
1.) Install this gem: https://github.com/dasil003/csv_builder
2.) Add respond_to do |format| format.csv to the action you want to turn into a CSV (in my case, the def show part of the posts_controller
3.) Create a action.csv.csvbuilder file (in my case, show.csv.csvbuilder) and add the data you need (in my case, add csv << [#post.title, #post.body])
4.) Add a link to the CSV in the views.
i was trying for multiple image upload with dragonfly in rails3. i searched for some tutorials, but couldn't find any. i found a tutorial for multiple image upload with Carrierwave, but couldnt find luck with dragonfly .. any help please :)
Preface
Dragonfly itself can be used to manage media for your project in general, similar to paperclip. The question itself boils down to the multiple file upload within a rails application. The some tutorials on this topic available, which can easily be adapted to models using Dragonfly for storing specific files on them. I would suggest you look into those and try to adapt them for your project.
However, I can present a minimum example which i built for a rails 3.2 app currently in development, which isn't perfect (validation handling for example), but can give you some starting points.
Example
Just for reference, the essential idea is taken from here. This example is done with Rails 3.2.x.
Let's say you have a vacation database, where users may create trip reports on vacations they took. They may leave a small description, as well as some pictures.
Start out by building a simple ActiveRecord based model for the trips, lets just call it Trip for now:
class Trip < ActiveRecord::Base
has_many :trip_images
attr_accessible :description, :trip_images
end
As you can see, the model has trip images attached to it via a has_many association. Lets have a quick look at the TripImage model, which uses dragonfly for having the file stored in the content field:
class TripImage < ActiveRecord::Base
attr_accessible :content, :trip_id
belongs_to :trip_id
image_accessor :content
end
The trip image it self stores the file attachment. You may place any restrains within this model, e.g. file size or mime type.
Let's create a TripController which has a new and create action (you can generate this via scaffolding if you like, it is by far nothing fancy):
class TripController < ApplicationController
def new
#trip = Trip.new
end
def create
#trip = Trip.new(params[:template])
#create the images from the params
unless params[:images].nil?
params[:images].each do |image|
#trip.trip_images << TripImages.create(:content => image)
end
if #trip.save
[...]
end
end
Nothing special here, with the exception of creating the images from another entry than the params hash. this makes sense when looking at the the file upload field within the new.html.erb template file (or in the partial you use for the fields on the Trip model):
[...]
<%= f.file_field :trip_images, :name => 'images[]', :multiple => true %>
[...]
This should work for the moment, however, there are no limitations for the images on this right now. You can restrict the number of images on the server side via a custom validator on the Trip model:
class Trip < ActiveRecord::Base
has_many :trip_images
attr_accessible :description, :trip_images
validate :image_count_in_bounds, :on => :create
protected
def image_count_in_bounds
return if trip_images.blank?
errors.add("Only 10 images are allowed!") if trip_images.length > 10
end
end
I leave this up to you, but you could also use client side validations on the file field, the general idea would be to check the files upon changing the file field (in CoffeeScript):
jQuery ->
$('#file_field_id').change () ->
#disable the form
for file in this.files
#check each file
#enable the form
Summary
You can build a lot out of existing tutorials, as dragonfly does not behave that differently to other solutions when it comes to just to uploading files. However, if you'd like something fancier, I'd suggest jQuery Fileupload, as many others have before me.
Anyways, I hope I could provide some insight.
Preamble:
I investigated how to version an API and found several ways to do it. I decided to try peter williams' suggestion and created new vendor mime types to specify version and format. I could find no definitive write-up for doing this following "the rails way" so I pieced together info from several places. I was able to get it working, but there is some goofiness in the way the renderers handle Widget array vs Widget instance in respond_with.
Basic steps & problem:
I registered mime types and added renderers for version 1 in both xml and json to ApplicationController, the renderers call to_myproj_v1_xml and to_myproj_v1_json methods in the model. respond_with(#widget) works fine but respond_with(#widgets) throws an HTTP/1.1 500 Internal Server Error saying that the "Template is missing".
Workaround:
"Template is missing" means that no render was called and no matching template exists. by accident, I discovered that it is looking for a class method... so I came up with the code below which works but I'm not really happy with it. The goofiness is mostly in and related to xml = obj.to_myproj_v1_xml(obj) and the duplication in the model.
My question is - has anyone done anything similar in a slightly cleaner fashion?
-= updated code =-
config/initializers/mime_types.rb:
Mime::Type.register 'application/vnd.com.mydomain.myproj-v1+xml', :myproj_v1_xml
Mime::Type.register 'application/vnd.com.mydomain.myproj-v1+json', :myproj_v1_json
app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :authenticate
ActionController.add_renderer :myproj_v1_xml do |obj, options|
xml = obj.to_myproj_v1_xml
self.content_type ||= Mime::Type.lookup('application/vnd.com.mydomain.myproj-v1+xml')
self.response_body = xml
end
ActionController.add_renderer :myproj_v1_json do |obj, options|
json = obj.to_myproj_v1_json
self.content_type ||= Mime::Type.lookup('application/vnd.com.mydomain.myproj-v1+json')
self.response_body = json
end
end
app/models/widget.rb:
class Widget < ActiveRecord::Base
belongs_to :user
V1_FIELDS = [:version, :model, :description, :name, :id]
def to_myproj_v1_xml
self.to_xml(:only => V1_FIELDS)
end
def to_myproj_v1_json
self.to_json(:only => V1_FIELDS)
end
def as_myproj_v1_json
self.as_json(:only => V1_FIELDS)
end
end
app/controllers/widgets_controller.rb:
class WidgetsController < ApplicationController
respond_to :myproj_v1_xml, :myproj_v1_json
def index
#widgets = #user.widgets
respond_with(#widgets)
end
def create
#widget = #user.widgets.create(params[:widget])
respond_with(#widget)
end
def destroy
#widget = #user.widgets.find(params[:id])
respond_with(#widget.destroy)
end
def show
respond_with(#widget = #user.widgets.find(params[:id]))
end
...
end
config/initializers/monkey_array.rb
class Array
def to_myproj_v1_json(options = {})
a = []
self.each { |obj| a.push obj.as_myproj_v1_json }
a.to_json()
end
def to_myproj_v1_xml(options = {})
a = []
self.each { |obj| a.push obj.as_myproj_v1_json } # yes this is json instead of xml. as_json returns a hash
a.to_xml()
end
end
UPDATE:
Found another solution that feels better but still a little weird (I'm still not completely comfortable with monkey patches), probably ok though... basically moved building the response data from the class method to_myproj_v1_json to a monkey patch on Array. This way when there is an Array of Widgets, it calls the instance method as_myproj_v1_json on each Widget and returns the whole Array as desired format.
One note:
as_json has nothing to do with json format, just creates a hash. Add custom formatting to as_myproj_v1_json (or an as_json override if you aren't using custom mime types), then to_json will change a hash to a json string.
i have updated the code below to be what is currently used, so the original question may not make sense. if anyone wants the original question and code shown as was and fixed code in a response i can do that instead.
For the answer: see the question :-)
In short, there are different solutions, of which one is in the question above:
Monkey-patch Array to implement a method that will give the (old) v1 JSON back
I haven't seen this content type trick used anywhere in a Rails project before so this is new to me. The way I've typically seen it done is to define a route namespace (e.g. /api/v1/) which goes to a controller (say, Api::Version1Controller).
Also, I know you want to do things the "Rails way", and maybe this sounds crotchety coming from a guy who has been with Rails since 1.3, but the whole respond_with / respond_to stuff is rather magic to me. I didn't know that respond_to looks for a to_XXX method when it serializes objects, for instance (maybe I need to read up on that). Having to monkey-patch Array like that seems rather silly. Besides, for an API, formatting the model data is really the view's job, not the model's. I might look into something like rabl in this case. There's a good writeup about it here.