Lucky Logo

# Setup

For handling file uploads, we will use Shrine.cr.

Start by adding the shrine shard to your shard.yml and run shards install.

dependencies:
  shrine:
    github: jetrockets/shrine.cr

Next you will require the shard in src/shards.cr

# src/shards.cr

# Require your shards here
require "avram"
require "lucky"
require "carbon"
require "authentic"
require "shrine"

Lastly, we can create a config file for configuring where our uploaded files will be stored. Create a new file in confg/shrine.cr

# config/shrine.cr
Shrine.configure do |config|
  config.storages["cache"] = Storage::FileSystem.new("uploads", prefix: "cache")
  config.storages["store"] = Storage::FileSystem.new("uploads")
end

# SaveOperation setup

The file_attribute is used in your save operation to specify the name of the param attribute that will contain the file.

class SaveUser < User::SaveOperation
  permit_columns name
  file_attribute :profile_picture

  before_save do
    profile_picture.value.try { |pic| upload_pic(pic) }
  end

  private def upload_pic(pic)
    result = Shrine.upload(File.read(pic.tempfile.path), "store", metadata: { "filename" => pic.filename })
    profile_picture_path.value = result.id
  end
end

# Action setup

Your action code will look standard with no additional code needed.

class Users::Create < BrowserAction
  post "/users" do
    SaveUser.create(params) do |op, user|
      if user
        redirect to: Users::Show.with(user.id)
      else
        html Users::NewPage, op: op
      end
    end
  end
end

# Page setup

The two main items to take note of is the form_for uses the multipart: true option to properly set the enctype, and the use of the file_input.

class Users::NewPage < MainLayout
  needs op : SaveUser

  def content
    form_for Users::Create, multipart: true do
      mount Shared::Field, op.name
      mount Shared::Field, op.profile_picture, &.file_input
    end
  end
end
See a problem? Have an idea for improvement? Edit this page on GitHub