The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Direct answer: load the records your document needs, combine them into a prepared data object, render PDF bytes with a generator such as Prawn (or render an HTML view with Wicked PDF), and return the result with Rails send_data. Use send_file only when a PDF already exists on disk. Keeping data loading separate from document layout makes a report assembled from several Active Record models easier to test and maintain.
Choose the PDF generation approach first
Your layout and deployment environment determine the right implementation. Prawn writes the document through Ruby drawing and text APIs. Wicked PDF renders an HTML view through the external wkhtmltopdf executable. Neither is universally best; check your Rails version, host, styling requirements and expected workload before locking dependencies.
| Requirement | Approach | Important trade-off |
|---|---|---|
| Layout can be expressed with PDF text and drawing primitives | Prawn | Direct Ruby PDF authoring; it is not an HTML/CSS view. Review the versioned Prawn documentation and lock the tested gem version. |
| An existing HTML template and CSS should drive the document | Wicked PDF | Familiar view-based authoring, but you must install and support wkhtmltopdf and configure assets for a process running outside Rails. |
| Generated bytes are held in memory | send_data |
Streams the generated response directly to the client. |
| A PDF already exists on disk | send_file |
Streams a file path; use a controlled, application-owned path. |
Rails documents that controllers provide both methods for streaming responses in its Action Controller Advanced Topics guide. The Rails 6.1 guide also demonstrates generating a Prawn PDF and passing the resulting string to send_data; verify examples against the Rails version your application actually runs.
Model the report as one prepared input
Do not make the PDF class query five unrelated models while it is drawing pages. Fetch the root record and associations in the controller or an application service, then pass a hash or value object containing everything the document needs. This avoids hidden database calls during rendering and gives you one place to enforce authorization and consistency.
#1 Best Overall
Example data loader
class ReportData
def self.load(id)
report = Report.includes(:customer, :line_items, :payments).find(id)
{
report: report,
customer: report.customer,
line_items: report.line_items,
payments: report.payments,
generated_at: Time.current
}
end
end
Use includes (or an equivalent preload strategy) for associations used in the document to avoid an N+1 query pattern. Authorize the report before loading related data, and decide whether all sections must represent one transactionally consistent snapshot. For large reports, consider a background job and a stored result rather than holding a long request open.
Generate a PDF with Prawn and send_data
Prawn is suitable when the report can be laid out with Ruby APIs. The following controller keeps retrieval, generation and HTTP response responsibilities distinct. ReportPdf is application code, not a Rails class.
class ReportsController < ApplicationController
def show
report_data = ReportData.load(params[:id])
pdf_bytes = ReportPdf.new(report_data).render
send_data pdf_bytes,
filename: "report-#{params[:id]}.pdf",
type: "application/pdf",
disposition: "attachment"
end
end
class ReportPdf
def initialize(data)
@report = data.fetch(:report)
@customer = data.fetch(:customer)
@line_items = data.fetch(:line_items)
@payments = data.fetch(:payments)
@generated_at = data.fetch(:generated_at)
end
def render
Prawn::Document.new do |pdf|
pdf.text "Report #{@report.id}", size: 22, style: :bold
pdf.move_down 8
pdf.text "Customer: #{@customer.name}"
pdf.text "Generated: #{@generated_at.iso8601}"
pdf.move_down 16
pdf.text "Line items", size: 16, style: :bold
rows = [["Description", "Quantity", "Amount"]] + @line_items.map do |item|
[item.description.to_s, item.quantity.to_s, format("%.2f", item.amount)]
end
pdf.table(rows, header: true, width: pdf.bounds.width)
pdf.move_down 16
pdf.text "Payments", size: 16, style: :bold
@payments.each do |payment|
pdf.text "#{payment.paid_at}: #{format('%.2f', payment.amount)}"
end
end.render
end
end
Install Prawn in the application and consult its versioned manual for tables, page breaks, fonts, images and repeatable headers. Escape or normalize user-provided text as appropriate for the API you use. Add tests that assert the response content type, filename and that the returned bytes begin with a valid PDF signature; integration tests should also exercise page overflow and empty associations.
Free tools Windows power users keep installed
One-click scans. No signup required.
Controller details that matter
- MIME type: use
application/pdf(thetype:option shown above). - Download versus inline display:
disposition: "attachment"prompts a download; useinlinewhen browser preview is desired. - Filenames: derive names from trusted identifiers and sanitize user-controlled text.
- Authorization: perform the same policy checks as the HTML report endpoint; generating a PDF must not bypass them.
- Memory:
send_dataholds generated bytes until the response is sent. Large documents may need asynchronous generation and object storage.
Use Wicked PDF when HTML is the natural source
Wicked PDF lets you render a Rails view as a PDF, but it wraps the external wkhtmltopdf executable. Install the gem and a compatible executable for your deployment, then verify the exact compatibility guidance in the project README.
Rank #2
class ReportsController < ApplicationController
def show
@report_data = ReportData.load(params[:id])
render pdf: "report-#{params[:id]}",
template: "reports/show",
formats: [:html]
end
end
Create app/views/reports/show.html.erb with the combined customer, line-item and payment data. The PDF process runs outside Rails, so relative asset paths that work in a normal browser may fail. Configure Wicked PDF’s asset handling, use absolute references where required, and test fonts, images, stylesheets and JavaScript in the same container or host that serves production PDFs. A missing executable, inaccessible asset or unsupported CSS rule commonly produces a blank or partially styled document.
When to avoid this path
- Use Prawn when you cannot install or maintain an external renderer.
- Use Wicked PDF when reproducing an existing HTML layout is more valuable than direct PDF control.
- Do not assume browser-perfect CSS support; renderer behavior depends on the installed executable and its options.
Keep multi-model data correct and efficient
Prevent inconsistent reads
Load all required records under the authorization context. If a report combines mutable invoices, payments and customer details, decide whether a transaction or a persisted report snapshot is needed. Otherwise, one model may reflect a later state than another by the time rendering finishes.
Control query count and ordering
Preload associations, select only columns needed for very large datasets, and apply an explicit ordering before passing collections to the generator. Never rely on database default order for a customer-facing document.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Handle empty and large collections
Render an explicit “No payments recorded” or equivalent message for empty associations. For thousands of rows, paginate or summarize, insert page breaks deliberately, and move work to a background job. A request timeout is not fixed by changing the response MIME type.
Rank #3
Make output deterministic
Pass a fixed report timestamp, locale, currency and timezone into the generator. Pin gem and executable versions in deployment, and compare generated PDFs in integration tests after dependency upgrades.
Common failures and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser downloads HTML or shows a Rails error page | An exception occurred before PDF bytes were returned, or the response was rendered as HTML. | Inspect server logs, ensure the generator returns bytes, and set type: "application/pdf". |
uninitialized constant Prawn |
The gem is absent or not loaded. | Add the dependency, run bundle installation in the deployment environment, and require it according to the selected version’s instructions. |
Wicked PDF cannot find wkhtmltopdf |
The executable is not installed or is outside the process PATH. | Install a compatible binary, configure its path, and test it inside the production image. |
| Styles or images disappear | The external renderer cannot resolve Rails-relative assets. | Configure Wicked PDF asset helpers or absolute URLs, and verify permissions and network access from the renderer process. |
| Report is slow or times out | N+1 queries, huge collections, expensive images or synchronous rendering. | Preload associations, reduce data, optimize assets and generate large reports asynchronously. |
| Rows appear in a different order on each run | No explicit ordering was applied. | Order the relation before placing it in the prepared data object. |
Response and deployment checklist
- Authorize the requested root record.
- Load all participating models with an intentional preload and ordering strategy.
- Build a single report-data object and pass it to Prawn or an HTML view.
- Pin and test the Prawn gem or Wicked PDF plus
wkhtmltopdfversions used in production. - Verify fonts, images, page breaks, empty states, locale, timezone and currency.
- Return generated bytes with
send_data, or usesend_filefor a trusted existing path. - For large jobs, persist the result and expose a controlled download endpoint.
Or skip the browser setup
If your goal is to capture a public report page as an image or PDF rather than generate a semantic Rails PDF, ScreenshotNeo provides a one-request API and an MCP server for AI agents. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; those cleanup steps can be disabled individually. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/reports/123 -o report.webp
See the ScreenshotNeo API documentation for PDF capture, authentication and options such as full-page lazy-image loading, CSS-selector element capture, device and retina settings, custom CSS or JavaScript, click and wait conditions, request blocking, cookies, headers, timezone, geolocation, resizing, caching, signed links, asynchronous webhooks, bulk capture and usage reporting. Its MCP tools are take_screenshot, get_page_info and capture_pdf.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Sign up for ScreenshotNeo free.
Frequently Asked Questions
Should PDF generation live in the controller?
A small report can use a private controller method, but a dedicated generator or service is easier to test when several models and layout rules are involved.
Can I use both Prawn and Wicked PDF in one Rails application?
Yes. Choose per document, but keep dependency versions, executable availability and rendering tests explicit for each path.
Does send_data save the PDF automatically?
No. It sends generated bytes in the HTTP response. Persist the bytes separately if users need a later download without regenerating the document.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchThe Bottom Line
Prepare one authorized, consistent data object from all required models, render it with Prawn or Wicked PDF according to your layout and deployment constraints, and return the result with send_data. Validate the complete stack in the same environment that will generate production documents.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

