Last Updated: January 09, 2021
·
4.191K
· ianwhitney

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

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.

1 Response
Add your response

For the image_tag, I recommend using:
<%= image_tag(image_url("image_name.jpg")) %>
We get the correct url and asset_pipeline goodness (fingerprinting, etc). Don't forget to use "app/assets/images", instead of "public/images".

over 1 year ago ·