Active Admin Enhanced

Hi! This is Rob, and I work on Kayac for the server-side engineering team. We’re going to add audio and image previews to our Active Admin views so we can see previews of all our media content!

Project repo: https://github.com/Rob117/active-admin-uploads

Why

I was recently working on a project where I needed a portable solution for enabling image and audio previews in active admin. The admins needed to be able to click on any given piece of audio to hear it, or an image preview to see it full size. They also needed this information available in the show, index, and edit views. This is the code that I distilled to perform that function. The full walkthrough is at the bottom.

Note: This code is not very Safari friendly, so I strongly recommend Chrome or Firefox for working with active admin (and also in general).

The Code

Module EnhancedUploader
# We grab the form object, then try to get the specific object we want to
  # input to replace. If that object exists, we show it as a hint.
  def input_audio(f, property)
    f.input property, hint: audio_content(f.object, property)
  end

  def input_image(f, property)
    f.input property, hint: image_content(f.object, property)
  end

# We simply grab an object and check to see if our property exists on it.
# If it does, display the object
  def audio_content(object, property)
    if object.persisted? && object.respond_to?(property)
      audio_tag object.send(property), controls: true
    else
      content_tag(:span, 'No Current Audio')
    end
  end

  def image_content(object, property)
    if object.persisted? && object.respond_to?(property)
      image_tag object.send(property), height: 150
    else
      content_tag(:span, 'No Current Image')
    end
  end
end

Inputs Explanation

First, we’ll start with the input form options:

  def input_audio(f, property)
    f.input property, hint: audio_content(f.object, property)
  end

  def input_image(f, property)
    f.input property, hint: image_content(f.object, property)
  end

So, in order to understand what this code does, you have to understand what a hint is in active admin. When you edit something in active admin, it allows you to give some context as to what that object might be. You typically specify that with the hint: param. For example, you could write some text next to a field called ‘credit card’ on an input form that says ‘application only accepts visa.’, and it would show that next to the input field.

Let us assume we have a user profile picture that we want to display a preview of when we edit it on a form. We also have a audio sample of the user saying ‘hello’ that we would like to be playable on the form. The code for the input image and audio would look something like this:

  # f is a form
  form do |f|
    extend EnhancedUploader
    input_image f, :profile_picture
    input_audio f, :hello_audio
  end

Please check the section “The Use” for a minimal, guided, working example.

Audio and Image Content methods

Next, we’ll explain what the following audio and image rendering code does:

  def audio_content(object, property)
    if object.persisted? && object.respond_to?(property)
      audio_tag object.send(property), controls: true
    else
      content_tag(:span, 'No Current Audio')
    end
  end

  def image_content(object, property)
    if object.persisted? && object.respond_to?(property)
      image_tag object.send(property), height: 150
    else
      content_tag(:span, 'No Current Image')
    end
  end

This code is much easier to explain. In each method, we accept an object. First, we check that it is persisted (saved to the DB), because if it isn’t there’s nothing to show or render - we cannot render data that we do not have.

Next, we check to make sure that the object can actually respond to the method that we want. It wouldn’t be a good idea to try to render something that the object doesn’t have! For example, before we try to display a profile_photo, we should make sure that the model actually has a profile_photo field that we can show.

Lastly, we simply render an html5-compliant image link or audio player with controls. The code in the show section would look something like this:

  show do
    extend EnhancedUploader
    attributes_table do
      row :profile_photo do |row_object|
        image_content row_object, :profile_photo
      end
      row :hello_audio do |row_object|
        audio_content row_object, :hello_audio
      end
    end
  end

Walkthrough

Laying Foundation

In your terminal

rails new demo
rails g model User name:string location:string
rails db:create db:migrate

Now we add active admin and devise.

In your gemfile, add the lines

gem 'activeadmin'
gem 'devise'

In the console:

bundle
rails generate devise:install
rails g active_admin:install
rails db:migrate db:seed
rails s

Go to localhost:3000/admin, login with username: admin@example.com password: password

create file at app/admin/user.rb, and paste in this content:

ActiveAdmin.register User do
  permit_params :name, :location
  # Show the essential data in the index
  index do
    selectable_column # we can select columns for batch actions
    column :id
    column :name
    column :location
    column :created_at
    column :updated_at
    actions
  end

  # When you click on the show for any individual item, this data is rendered
  show do
    attributes_table do # display the following attributes
      row :id
      row :name
      row :location
      row :created_at
      row :updated_at
    end
  end
  # When you click on edit, this form is rendered
  form do |f|
    f.semantic_errors
    f.inputs do
      f.input :name
      f.input :location
    end
    f.actions
  end
end

Go to http://localhost:3000/admin/users and confirm that we can create, delete, and edit our basic users.

Adding CarrierWave

Time to add an image uploader and an audio uploader. First, let’s include our library that will manage data display for us. Move the following file to lib/enhanced_uploader

https://github.com/Rob117/active-admin-uploads/blob/master/lib/enhanced_uploader.rb

Add the following to your gemfile:

gem 'carrierwave', '~> 1.0'

In the terminal, type:

bundle

make two uploaders, one for profile_photo and one for hello_audio:

rails g uploader profile_photo
rails g uploader hello_audio

Add both uploaders to the user model (terminal):

rails g migration add_profile_photo_to_users profile_photo:string
rails g migration add_hello_audio_to_users hello_audio:string
rails db:migrate

Open app/models/user.rb file and mount uploaders

class User < ApplicationRecord
  mount_uploader :profile_photo, ProfilePhotoUploader
  mount_uploader :hello_audio,   HelloAudioUploader
end

Replace app/admin/user.rb with the following (we aren’t showing the enhanced uploader quite yet):

ActiveAdmin.register User do
  permit_params :name, :location, :profile_photo, :hello_audio
  # Show the essential data in the index
  index do
    selectable_column
    column :id
    column :name
    column :location
    column :profile_photo
    column :hello_audio
    column :created_at
    column :updated_at
    actions
  end

  # When you click on the show for any individual item, this data is rendered
  show do
    attributes_table do
      row :id
      row :name
      row :location
      row :profile_photo
      row :hello_audio
      row :created_at
      row :updated_at
    end
  end
  # When you click on edit, this form is rendered
  form do |f|
    f.semantic_errors
    f.inputs do
      f.input :name
      f.input :location
      f.inputs :profile_photo
      f.inputs :hello_audio
    end
    f.actions
  end
end

We just added the fields. Restart your server and access your users page. Create a user if you need to. Notice that on index, show, and edit that the fields for profile_photo and hello_audio are blank. This is to be expected - we haven’t uploaded anything yet!

Upload a small image and a small audio clip.

Notice that after we’ve uploaded it you can only view the path names and not anything inside the file.

To include our ‘library’, we only need make one small change, in config/application.rb:

require_relative 'boot'
require 'rails/all'
Bundler.require(*Rails.groups)

module Blogly
  class Application < Rails::Application
    config.autoload_paths += %W(#{config.root}/lib) # Add this line!
  end
end

Now, simply change your admin/user.rb file to this final code:

ActiveAdmin.register User do
  permit_params :name, :location, :profile_photo, :hello_audio
  # Show the essential data in the index
  index do
    extend EnhancedUploader # include uploader
    selectable_column
    column :id
    column :name
    column :location
    column :profile_photo do |row_object|
      image_content row_object, :profile_photo
    end
    column :hello_audio do |row_object|
      audio_content row_object, :hello_audio
    end
    column :created_at
    column :updated_at
    actions
  end

  # When you click on the show for any individual item, this data is rendered
  show do
    extend EnhancedUploader # include uploader
    attributes_table do
      row :id
      row :name
      row :location
      row :profile_photo do |item|
        image_content item, :profile_photo
      end
      row :hello_audio do |item|
        audio_content item, :hello_audio
      end
      row :created_at
      row :updated_at
    end
  end
  # When you click on edit, this form is rendered
  form do |f|
    extend EnhancedUploader # include uploader
    f.semantic_errors
    f.inputs do
      f.input :name
      f.input :location
      input_image f, :profile_photo
      input_audio f, :hello_audio
    end
    f.actions
  end
end

Refresh your server, and head to the users page. Now you should see previews of the audio and image that you uploaded on each index, show, and edit page! Success!

Extra - Careers!

Do you speak Japanese to a high conversational level (~N2) and want to work with us? Click the link below to send us a message!

www.kayac.com

Related Artices (Japanese)

techblog.kayac.com

techblog.kayac.com