Creating images from JSON in Rails
In a recent project I was asked to dynamically generate images that would show the events scheduled in a room. The event data is stored in an external system which I could get access to through its API.
A little googling turned up IMGKit, which wraps around the wkhtmltoimage program.
I created a tiny Rails app that only had one route /room/[room_id]/events. And I limited the events conttroller to only one action, :index. Then I defined one view /events/index.jpg.erb
<% events.each do |event| %>
<%= event.description %>
<% end %>
Within the event#index action the creation of the image was handled with
def index
...
kit = IMGKit.new(render_to_string, width: 480, height: 800, :quality => 100)
...
respond_to do |format|
format.jpg do
send_data(kit.to_jpg, :type => "image/jpeg", :disposition => 'inline')
end
end
end
That’s stuff you can learn in the IMGKit readme, though. Nothing too exciting there. But I ran into some further problems that might be of interest.
First: render_to_string
renders only the action’s template code, it
won’t use the controller’s layout file, or the application.html.erb
file. Even if you pass the right flag:
render_to_string(:layout => true)
I couldn’t get that to work at all. So if you have CSS files or HTML
structure that you need in your view, you’ll have to put it inside the
action’s erb file. So, my code in /app/views/events/index.jpg.erb
file
ended up looking like:
<!DOCTYPE html>
<html>
<head>
<title>Amxtest</title>
<style type='text/css'>
html {
...
}
/* etc */
</head>
<body>
...
</body>
</html>
Also note that linking to files using image_tag
or stylesheet_tag
won’t work the way you expect. Relative links don’t seem to work at all,
with or without Rails.root.
I solved this by setting environment-specific IMG_ROOT constant. So for
development it’s set to http://localhost:3000/
and then when I need to
show an image in my view, I save it to the /public/images directory and
link to it with:
<%= image_tag("#{IMG_ROOT}/images/image_name.jpg") %>
An ugly hack, but it solved my problem.
This linking problem also means you can’t easily include a CSS file in your view. You could probably fix it the same way I fixed images, but IMGKit offers this code that you can use inside the controller:
kit = IMGKit.new(render_to_string, width: 480, height: 800, :quality => 100)
css = File.open("#{Rails.root}/app/assets/stylesheets/images.css")
kit.stylesheets << css
Lastly, the creation of images can take a few seconds, especially if you’re pulling data from a 3rd party service. So you’ll probably want to look at caching. That’s a whole other topic, though.