如何在Ruby on Rails后端生成Chart.js图表并作为邮件附件发送
Absolutely! You can render Chart.js charts server-side in Rails, save them as images, and attach them to emails—here are a couple of reliable approaches to pull this off:
Approach 1: Use Puppeteer (Headless Chrome)
Since Chart.js relies on a browser's JavaScript engine, using a headless browser like Chrome (via Puppeteer) is the most flexible way to render charts server-side. Here's how to set it up:
Step 1: Add Dependencies
First, add the puppeteer-ruby gem to your Gemfile:
gem 'puppeteer-ruby'
Run bundle install, then make sure your server has Chrome/Chromium installed. For Ubuntu-based servers, install it with:
sudo apt-get install chromium-browser
Step 2: Create a Chart HTML Template
Make an ERB template that includes Chart.js and your chart configuration. For example, app/views/charts/email_chart.html.erb:
<!DOCTYPE html> <html> <head> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0"></script> </head> <body> <canvas id="myChart" width="800" height="400"></canvas> <script> const ctx = document.getElementById('myChart').getContext('2d'); new Chart(ctx, { type: '<%= @chart_type %>', data: <%= @chart_data.to_json %>, options: <%= @chart_options.to_json %> }); </script> </body> </html>
Step 3: Write a Service to Generate the Image
Create a service class to render the template, load it in Puppeteer, and take a screenshot:
# app/services/chart_generator_service.rb require 'puppeteer' class ChartGeneratorService def self.generate(chart_type:, chart_data:, chart_options: {}) # Render the ERB template to HTML html = ApplicationController.render( template: 'charts/email_chart', locals: { chart_type: chart_type, chart_data: chart_data, chart_options: chart_options } ) # Create a temporary file to store the image temp_file = Tempfile.new(['chart', '.png']) temp_file.close # Launch headless Chrome and take a screenshot Puppeteer.launch(headless: :new) do |browser| page = browser.new_page page.set_content(html) # Wait a moment for Chart.js to finish rendering (adjust timeout as needed) page.wait_for_timeout(1500) # Capture the canvas area as a PNG page.screenshot(path: temp_file.path, clip: { x: 0, y: 0, width: 800, height: 400 }) end temp_file end end
Step 4: Attach the Image to an Email
Use the service in your mailer to generate the chart and attach it:
# app/mailers/chart_mailer.rb class ChartMailer < ApplicationMailer def send_chart(recipient, chart_type, chart_data, chart_options = {}) # Generate the chart image chart_file = ChartGeneratorService.generate( chart_type: chart_type, chart_data: chart_data, chart_options: chart_options ) # Attach the file to the email attachments['analytics_chart.png'] = File.read(chart_file.path) # Send the email mail(to: recipient, subject: 'Your Requested Chart') do |format| format.html { render plain: 'Check out your chart attached below!' } end # Clean up the temporary file chart_file.unlink end end
Approach 2: Use a Third-Party Chart API (No Browser Required)
If you don't want to manage a headless browser setup, use a free API to render Chart.js charts directly to images. This is great for simple use cases:
# Example service using a chart API class ChartApiGeneratorService def self.generate(chart_config) encoded_config = URI.encode_www_form_component(chart_config.to_json) image_url = "https://quickchart.io/chart?c=#{encoded_config}" temp_file = Tempfile.new(['chart', '.png']) temp_file.binmode temp_file.write(URI.open(image_url).read) temp_file.close temp_file end end # Usage in mailer chart_config = { type: 'line', data: { labels: ['Jan', 'Feb', 'Mar'], datasets: [{ data: [10, 25, 15] }] } } chart_file = ChartApiGeneratorService.generate(chart_config)
Note: This relies on an external service, so avoid sending sensitive data via this method, and check the API's rate limits.
Key Considerations
- Background Processing: Generating images can be slow—use a background job (like Sidekiq) to offload this work instead of running it in a web request.
- Server Deployment: For Heroku, add the Chromium buildpack and set the env var
PUPPETEER_EXECUTABLE_PATH=/app/.apt/usr/bin/chromium-browser. - Performance: Adjust the Puppeteer wait timeout based on how complex your charts are—you can also listen for Chart.js's
animationCompleteevent instead of using a fixed delay for more reliability.
内容的提问来源于stack exchange,提问作者Lorenz

