Skip to main content
14 Rails 5.2 Features:

How to Create an Active Storage Previewer

Episode 236 · March 27, 2018

Learn how to create a file previewer with Active Storage in Rails 5.2+ to generate preview images of application files. In this example, we'll be converting Powerpoint presentations to png image previews.

Rails 5.2 ActiveStorage


Transcripts

What's up guys? This episode we're diving into and advance feature in Active Storage where we're going to be creating a new previewer, so previewers are classes that can process your uploaded files and create a preview image for them, and built in into rails by default there are three and they handle PDF's and videos, and that's all they really do right now, so we're going to create one that treats presentations as previewable, and so we'll extract a preview image from presentations like powerpoints, keynotes, any other kind of presentations format that our tool understands, and the tool that we're using is LibreOffice, this is an open office suite, it's open source, basically it is what we'll use on the command line to convert our presentations into an image, and we're going to do this on the server side so that we don't have to load their GUI, and we'll just all run in the command line, and so that's what you need to look for, if you're trying to build one of these yourself you need something that runs in the command line, and just to point out here, we're not gonna dive into this in much detail, but if you want to see any of the previewers built into rails, activestorage/lib/active_storage/previewer where those live inside the repository, and you can take a look at how these are built, I just want to point out two things here that are important, you need a class method called Accept, so that this returns a boolean, yes or no, will we process the file that was just uploaded, you need to check for the correct content type and weather or not your required executable is installed, and then this preview method which is actually where the real work happens, you download the original file, and then you convert it to an image and then you upload that image. That's all you have to implement to build your own previewer, let's dive into building our own and we'll get into the details of how this works.

Let's create a new rails app, I'm going to use my rails app template so that we have devise and bootstrap and some things installed already, but all you really need is a rails 5.2 application that has ActiveStorage installed in it, so we're going to create this application, and then run rails active_storage:install once it's done.

Now that that's done, we'll run rails active_storage:install to get that installed, and then we'll run rails db:migrate to create our migrations and all of that. Now we can get started with adding our uploadable files. What I'm going to do is run rails g scaffold, we're going to have SlideDeck as our model, let's call it SlideDeck, we'll have a name and we'll have an upload that we'll create with ActiveStorage, so we'll run rails db:migrate to create that, let's go to our routes, we'll have our SlideDecks up there, move that down and then we can have root to: 'slide_decks#index', our SlideDeck model can

app/models/slide_deck.rb

class SlideDeck < ApplicationRecord
  has_one_attached :presentation
end

that will be the file that gets uploaded with it, and then in our form, our SlideDecks are going to be to be able to do a file upload, so we'll change this to a file field of presentation, and change the label on it, and then our SlideDecks controller will need to permit the presentation here as well, so we'll have presentation, and once we have that, we can go load up our rails server and create a new SlideDeck, so we have the ability to create and upload files, so I'm just going to upload a PowerPoint X file, and this doesn't do anything right now, there's no display for it or anything, so we can go to our show action, and we can add a section here maybe for the preview, so we'll add

<dl class="dl-horizontal">
  <dt>Name:</dt>
  <dd><%= @slide_deck.name %></dd>

  <dt>Preview:</dt>
  <dd><%= image_tag @slide_deck.presentation.preview(resize: "400x400") %></dd>
</dl>

That's going to give us some sort of error, and the reason we get this error is because it's unpreviewable, so if you try to call preview and there isn't a matching previewer, it will throw an exception, and so that's what's happening here, so we need to define our previewer that matches this content type and does all of that. Now one of the things that we can do is we can look at ActiveStorage::Blob.last, and we can look at the content type for this, so we can say ActiveStorage::Blob.last.content_type, so this content type describes your .pptx files that come from PowerPoint, but we can also support keynotes, we can support the older .ppt files, and other types of content types for presentation, so you want to google and see what your content types are or try uploading your own, and seeing what the application content types are, but we're going to specifically implement this one and then go check some more later on, so we'll just copy this to the clipboard and have access to it in just a minute as we create our preview, so to create our previewer, we want to create a folder inside app, and I'm going to call it previewers just out of convenience, and then we can edit app/preview/presentation_previewer.rb inside here we can

app/previewers/presentation_previewer.rb

class PresentationPreviewer < ActiveStorage::Previewer
  class << self
    def accept?(blob)
      blob.content_type == "application/vnd.openxmlformats-officedocument.presentationml.presentation" && libreoffice_exists?
    end

    def libreoffice_path
      ActiveStorage.paths[:libreoffice] || "libreoffice"
    end

    def libreoffice_exists?
      return @libreoffice_exists unless @libreoffice_exists.nil?

      system libreoffice_path, "--version", out: File::NULL, err: File::NULL

      @libreoffice_exists = $?.exitstatus == 0
    end
  end

  def preview
    download_blob_to_tempfile do |input|
      draw_first_page_from input do |output|
        yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
      end
    end
  end

  private

    def draw_first_page_from(file, &block)
      directory = File.dirname(file.path)
      basename  = File.basename(file.path, ".*")
      png_file  = File.join(directory, "#{basename}.png")

      system self.class.libreoffice_path, "--headless", "--invisible", "--convert-to", "png", "--outdir", directory, file.path
      draw "cat", png_file, &block

      File.delete(png_file)
    end
end

Last but not least, we need to create config/initializers/active_storage.rb, and we want to add

Rails.application.config.active_storage.previewers << PresentationPreviewers

That's going to make sure it gets added to the list and checked every single time, and lastly, we can optionally add in

Rails.application.config.active_storage.paths[:libreoffice] = ""

and you can define the LibreOffice executable location in here and now on the Mac, the application path is actually this one, so it's in /Applications/LibreOffece.app/Contents/MacOS/soffice not libre office, for whatever reason, so I'm going to paste that in here, and our presentation previewer should probably default to say libreoffice, if that's the one that's going to be used on like ubuntu, and we don't want probably the soffice version of that for the Mac or we could actually check the operating system inside this method and choose the default, but we should always default if this is defined in the paths options, we should use that one because the user has configured that one themselves, and overridden the default. We should actually fall back to one of these other ones that is more sane and your version of your previewer can do whatever you want here, just kind of make sure that it defaults to the executable names so that in case it's in the path, then it will be automatically used. That is all we're going to do there to change our config, and if we restart our rails application we should be able to go back to our app and refresh the page and run through all this code and see the previewed image.

If we refresh the page, and we have a broken image in this case, and what we can do is take a look at our logs to see what went wrong, now in our logs, we can see a Stack trace and that is part of the request to grab this ActiveStorage route and so it grabs that and it begins to run our previewer and then it crashes because it has run our previewer, it's inserted a new blob for our image.png version of the content type, and all of that is good, all of our code ran successfully, except when it got to the resize, we didn't have the minimagick gem installed in our application, so this is all we need to do, we need to add minimagick to our gemfile and we'll be good to go, we can refresh the page and it should work this time.

If you ran into any errors, maybe you made a typo, you called the wrong function or something, then you will see in a very similar stack trace whenever that image does not get displayed correctly, you take a look at your rails logs and figure out exactly what went wrong, now I've installed minimagick and we can see that our image preview has worked successfully and now we can see that that works with these powerpoints, which is really cool. Last but not least, if you ever wanted your previewers to support multiple content types, you can actually grab your content types and put them in a constant inside of your class and then you can just list out the ones that you want to support here, so I'll make a note , this is for pptx files, there's actually one called "application/x-ole-storage" which is the old ppt files, but unfortunately, this one actually would accidentally trigger, if you had, I believe an old XLS like spreadsheet and maybe old doc files, now this one of course I'm going to leave commented out because it's too generic and actually it's probably a bug that is overriding the original content type, which is more descriptive usually says Excel or PowerPoint in it, and you should be able to grab that out, but unfortunately, that is how it is right now, and so there's probably a bug that gets fixed in the mime types wherever that is being called inside ActiveStorage that gets cleaned up, so that is it for this episode, unfortunately keynote files from the Mac can't really be open by a LibreOffice, and most of the recommendations are just to export as PowerPoint files and open nodes. You can make changes to this file, you're probably going to need to restart your rails server so that it gets reloaded correctly, but that is all that there is to it, it's really straightforward to build your own previewer and I hope that we see a lot more of these, we should be able to package them up as a gem and then have them automatically included into your previewers just with a rail tie, so if you want to see an episode on creating the ActiveStorage previewer or gem, let me know in the comments below and we can get to that in the future episode, until then I will talk to you guys in the next one. Peace

Transcript written by Miguel

Discussion


Fallback
Another great video! Would love to see you create a gem for this on screen. 

Fallback
I would love to see you create a gem for this on screen.

Another thing I have been curious about is how you prep for each episode.

Perhaps you could do a meta-episode, where you show the episode and then after you show the prep that goes into that episode also? 

Just so that not are we learning, but we are learning new ways to learn by watching you prep to teach, if that makes sense?

Fallback
Hi a great video yes ! 💪🏽
I currently also trying to figure out some solutions to make more sexy the edit and create view with active storage (minimagick included as GEM) . 
I was working for long time with attachinary and it was sexy to display thumbnail inside create and edit view . 
Do you have any advice about these template create and edit with active storage and minimagick ? thanks again

Login or create an account to join the conversation.