神刀安全网

Rails File Uploading You Can Believe in with Shrine

Rails File Uploading You Can Believe in with Shrine

In my previous article we learned how we to enable file upload inRails using Refile. Today we will look at another file uploading gem – Shrine . Shrine is a another toolkit for file uploads in Ruby applications.

In its introductory blog post , the author of Shrine, Janko Marohnić, indicates that Shrine is heavily influenced by both Refile and CarrierWave. Shrine makes use of a plugin system for everything, shipping with tons of plugins , some of which I will make use of today.

We will be using Shrine to build a Rails application that stores images and books. Yeah books…you might want to show your friends your latest collection of books. I hope you love to read as much as I do. Let’s build this thing and put it out for the world to see.

ImageMagick Installation

For Shrine to work you need ImageMagick installed. Depending on your operating system, use one of the steps below.

Mac Users:

brew install imagemagick 

Ubuntu Users:

sudo apt-get install imagemagick 

If neither of the above works for you, meaning you are on Windows or something else, check the installations page on the ImageMagick site. There are binary releases for Windows 7 and above.

Rails Application Generation

Run the command to create your new application:

rails new book-showcase 

We are going to be using a single controller

rails generate controller Books 

Set up the corresponding routes:

config/routes.rb

  Rails.application.routes.draw do   resources :books    root to: "books#index" end 

Integrate Shrine

Before we proceed to creating a model, let’s pull Shrine into the Rails app. Open up your Gemfile and add the Shrine gem and it’s dependencies.

Gemfile

  # Shrine Dependencies gem 'fastimage' gem 'image_processing' gem 'mini_magick' gem 'shrine' 
  • fastimage is used to extract the image dimensions.
  • image_processing includes some helpers for using ImageMagick
  • mini_magick is a low-memory replacement for RMagick, a Ruby wrapper around ImageMagick

Run

bundle install 

Now let’s create our Book model:

rails generate model Book 

Open your migration and make it look like this:

xxx_create_books.rb

  class CreateBooks < ActiveRecord::Migration   def change     create_table :books do |t|       t.string :name       t.text :image_data        t.timestamps null: false     end   end end 

Run the migration

rake db:migrate 

We need to configure the model with Shrine functionality. Create a new file in your model folder, call it image_uploader.rb :

touch app/models/image_uploader.rb 

Paste the following inside the file you just created:

app/models/image uploader.rb_

class ImageUploader < Shrine   include ImageProcessing::MiniMagick    plugin :activerecord   plugin :determine_mime_type   plugin :logging, logger: Rails.logger   plugin :remove_attachment   plugin :store_dimensions   plugin :validation_helpers   plugin :versions, names: [:original, :thumb]    Attacher.validate do     validate_max_size 2.megabytes, message: 'is too large (max is 2 MB)'     validate_mime_type_inclusion ['image/jpg', 'image/jpeg', 'image/png', 'image/gif']   end    def process(io, context)     case context[:phase]     when :store       thumb = resize_to_limit!(io.download, 200, 200)       { original: io, thumb: thumb }     end   end end 

As mentioned, Shrine provides us with different plugins for different functions. Here are the plugins included in our uploader and their functions:

  • :activerecord : This extends the “attachment” interface with support for ActiveRecord. Whenever an “attachment” module is included, additional callbacks are added to the model.
  • :determine_mime_type : This stores the actual MIME type of the uploaded file.
  • :logging : The logging plugin logs any storing/processing/deleting that is performed. By passing in Rails.logger to the :logger option, we change the logger to be useful in our Rails application.
  • :remove_attachment : The remove_attachment plugin allows you to delete attachments through checkboxes on the web form.
  • :store_dimensions : This plugin extracts and stores dimensions of the uploaded image.
  • :validation_helpers : This provides helper methods for validating attached files. Take a look at the following Attacher block:

app/models/image uploader.rb_

Attacher.validate do   validate_max_size 2.megabytes, message: 'is too large (max is 2 MB)'   validate_mime_type_inclusion ['image/jpg', 'image/jpeg', 'image/png', 'image/gif'] end 

The validators are pretty straightforward. Here, we are constraining the size to 2MB or less and the acceptable file types to jpg , png , and gif .

  • :versions : The versions plugin enables your uploader to deal with versions of an image. To generate versions, you simply return a hash of versions like we did in our uploader.

app/models/image uploader.rb_

plugin :versions, names: [:original, :thumb] 

Now, in the process method, handle any version (other than :original ) and return it. As you can see, we’re doing this for the store phase, meaning, when the file is being stored:

...  def process(io, context)   case context[:phase]   when :store     thumb = resize_to_limit!(io.download, 200, 200)     { original: io, thumb: thumb }   end end 

Setup your BooksController :

app/controllers/books controller.rb_

class BooksController < ApplicationController   before_action :set_book, only: [:show, :edit, :update, :destroy]    def index     @books = Book.all   end    def show   end    def new     @book = Book.new   end    def edit   end    def create     @book = Book.new(book_params)      if @book.save       redirect_to @book, notice: 'book was successfully created.'     else       render :new     end   end    def update     if @book.update(book_params)       redirect_to @book, notice: 'book was successfully updated.'     else       render :edit     end   end    def destroy     @book.destroy     redirect_to books_url, notice: 'book was successfully destroyed.'   end    private    def set_book     @book = Book.find(params[:id])   end    def book_params     params.require(:book).permit(:name, :image, :remove_image)   end end 

You need to create an initializer for Shrine:

touch config/initializers/shrine.rb 

Paste in the following:

config/initializers/shrine.rb

require "shrine" require "shrine/storage/file_system" require "image_processing/mini_magick"  Shrine.storages = {   cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),   store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"), } 

Shrine ships with two (2) default storages: FileSystem and S3 .

FileSystem

FileSystem storage deals with uploads to the file system of the application itself. It gets initialized with a base folder and a prefix , like we have above. The prefix is a directory relative to the base folder where files will be stored, and it gets included in the URL.

S3

S3 storage is responsible for uploads to the Amazon Web Service (AWS) S3 service. To use S3 storage, you will need the aws-sdk gem in your Gemfile . Do not forget to bundle install .

Now go over to config/initilazers/shrine.rb to set things up for S3 storage.

config/initializers/shrine.rb

require "shrine" require "shrine/storage/s3"  s3_options = {   access_key_id:     ENV.fetch("S3_ACCESS_KEY_ID"),   secret_access_key: ENV.fetch("S3_SECRET_ACCESS_KEY"),   region:            ENV.fetch("S3_REGION"),   bucket:            ENV.fetch("S3_BUCKET"), }  Shrine.storages = {   cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),   store: Shrine::Storage::S3.new(prefix: "store", **s3_options), } 

At first we require shrine and shrine s3 . S3 prefix option is passed the name of the bucket where our files will be uploaded. In the example we have above, there will be two (2) buckets; one for store , the other for cache . The next option, s3-options , gives us access to AWS. You will need to get your AWS access key and secret access key and put them into the environment variables.

Now let’s work on the views. The view to hold our form :

app/views/books/ form.html.erb_

<%= form_for(@book) do |f| %>   <%= render "error_messages", target: @book %>    <div class="field">     <%= f.label :name %><br>     <%= f.text_field :name %>   </div>    <div class="field">     <%= f.label :image %><br>     <%= f.file_field :image %>   </div>   <%- if @book.image_data? %>     <div class="field">       <%= image_tag @book.image_url(:thumb) %>     </div>     <div class="field">       Remove attachment: <%= f.check_box :remove_image %>     </div>   <%- end %>   <div class="actions">     <%= f.submit %>   </div> <% end %> 

I want us to separate the error messages, that is a new habit I have recently developed. Create a folder in your views named application . Create a partial calledj _error_messages.html.erb and paste in the error messages:

app/views/application/ error messages.html.erb

<% if target.errors.any? %>     <div id="error_explanation">       <h2><%= pluralize(@book.errors.count, "error") %> prohibited this book from being saved:</h2>        <ul>       <% @book.errors.full_messages.each do |message| %>         <li><%= message %></li>       <% end %>       </ul>     </div> <% end %> 

The form is really simple. Just use the form.file_field :image to render the appropriate field.

Let’s work on the show page. At this point, when we try to submit the form we’ll get an Action Controller: Exception caught message because of the missing show template.

This is how the view code for your show page should look.

app/views/books/show.html.erb

<p id="notice"><%= notice %></p>  <div>   <strong>Name:</strong>   <%= @book.name %> </div> <div>   <strong>Image:</strong>   <%= image_tag @book.image_url(:original) %> </div>  <%= link_to 'Edit', edit_book_path(@book) %> | <%= link_to 'Back', books_path %> 

Let’s setup our Edit page too:

app/views/books/html.erb

<h1>Editing Book</h1>  <%= render 'form' %>  <%= link_to 'Show', @book %> | <%= link_to 'Back', books_path %> 

And the index page:

app/views/books/index.html.erb

<p id="notice"><%= notice %></p>  <h1>Listing Books</h1>  <table>   <thead>     <tr>       <th>Name</th>       <th>Image</th>       <th colspan="3"></th>     </tr>   </thead>    <tbody>     <% @books.each do |book| %>       <tr>         <td><%= book.name %></td>         <td><%= image_tag book.image_url(:thumb) %></td>         <td><%= link_to 'Show', book %></td>         <td><%= link_to 'Edit', edit_book_path(book) %></td>         <td><%= link_to 'Destroy', book, method: :delete, data: { confirm: 'Are you sure?' } %></td>       </tr>     <% end %>   </tbody> </table>  <br>  <%= link_to 'New Book', new_book_path %> 

Versions

In your Rails application, you may want to store a thumbnails (or other versions) alongside the original image. For that you need to load the versions plugin, which I mentioned above. We did that already in the image_uploader.rb file. Now, just have the #process method return a Hash of versions:

app/views/models/image uploader.rb_

class ImageUploader < Shrine   include ImageProcessing::MiniMagick    plugin :activerecord   plugin :determine_mime_type   plugin :logging, logger: Rails.logger   plugin :remove_attachment   plugin :store_dimensions   plugin :validation_helpers   plugin :versions, names: [:original, :large, :medium, :small, :thumb]    Attacher.validate do     validate_max_size 2.megabytes, message: 'is too large (max is 2 MB)'     validate_mime_type_inclusion ['image/jpg', 'image/jpeg', 'image/png', 'image/gif']   end    def process(io, context)     if context[:phase] == :store       size_700 = resize_to_limit!(io.download, 700, 700)       size_500 = resize_to_limit(size_700,    500, 500)       size_300 = resize_to_limit(size_500,    300, 300)       thumb = resize_to_limit(size_300, 200, 200)        { original: io, large: size_700, medium: size_500, small: size_300, thumb: thumb }     end   end end 

You can pick which of the versions you want to render in the view, just like we did with the view for index . Open your index view and change the rendered image to the small version.

app/views/books/index.html.erb

<p id="notice"><%= notice %></p>  <h1>Listing Books</h1>  <table>   <thead>     <tr>       <th>Name</th>       <th>Image</th>       <th colspan="3"></th>     </tr>   </thead>    <tbody>     <% @books.each do |book| %>       <tr>         <td><%= book.name %></td>         <td><%= image_tag book.image_url(:small) %></td>         <td><%= link_to 'Show', book %></td>         <td><%= link_to 'Edit', edit_book_path(book) %></td>         <td><%= link_to 'Destroy', book, method: :delete, data: { confirm: 'Are you sure?' } %></td>       </tr>     <% end %>   </tbody> </table>  <br>  <%= link_to 'New Book', new_book_path %> 

Conclusion

In this article we learned how to enable image uploading in our Rails application using Shrine. You can checkout Shrine’s page on Github if you want to try out some exciting features not mentioned here. Shrine has some great support for putting file uploads into background jobs or direct uploads, just to name a couple of features we didn’t cover today. Also, Shrine is not just for Rails, you can use it with any Ruby application.

Thanks for reading and see you soon!

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Rails File Uploading You Can Believe in with Shrine

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮