Skip to content

Instantly share code, notes, and snippets.

@fernandes
Last active November 21, 2021 22:00
Show Gist options
  • Save fernandes/31c56b5aa1841643958031dc24c6a86b to your computer and use it in GitHub Desktop.
Save fernandes/31c56b5aa1841643958031dc24c6a86b to your computer and use it in GitHub Desktop.
[Crystal Lang] Lucky Framework + Shrine File Uploader
crystal: 0.34.0
dependencies:
lucky:
github: luckyframework/lucky
version: ~> 0.21
shrine:
github: jetrockets/shrine.cr
version: ~> 0.8
require "shrine"
Shrine.configure do |config|
config.storages["cache"] = Shrine::Storage::FileSystem.new("uploads", prefix: "cache")
config.storages["store"] = Shrine::Storage::FileSystem.new("uploads")
end
# db/migrations/20200614172421_create_file_imports.cr
class CreateFileImports::V20200614172421 < Avram::Migrator::Migration::V1
def migrate
# Learn about migrations at: https://luckyframework.org/guides/database/migrations
create table_for(FileImport) do
primary_key id : Int64
add_timestamps
add asset_data : JSON::Any?
add_belongs_to post : Post, on_delete: :cascade
end
end
def rollback
drop table_for(FileImport)
end
end
# src/shards.cr
# (at the bottom)
require "shrine"
# given our `post` example, param_key will be `post`
file = params.nested_file?(:param_key)["image_attribute"]?
if file
# file.name
# "param_key:image_attribute"
# file.tempfile
#<File:/var/folders/41/g51hx5q90xqgk7wz9qv6738c0000gn/T/.3uQb3Bparam_key:image_attribute (closed)>
SaveFileImport.create!(upload: file, post_id: post.id)
end
# src/models/file_import.cr
class FileImport < BaseModel
table do
column asset_data : JSON::Any?
belongs_to post : Post
# Now `commentable` could be a `photo` or `video`
polymorphic imageable, associations: [:post]
end
def blob_path
if data = asset_data
"/imports/#{data["id"].to_s}"
else
""
end
end
end
# src/models/post.cr
class Post < BaseModel
table do
# ..other fields..
has_many images : FileImport
end
end
# src/operations/save_file_import.cr
class FileImport::AssetUploader < Shrine
def generate_location(io : IO | UploadedFile, metadata, context, **options)
name = super(io, metadata, **options)
File.join("imports", name)
end
end
class SaveFileImport < FileImport::SaveOperation
permit_columns asset_data
needs upload : Lucky::UploadedFile
@asset_changed : Bool? = nil
property :asset_changed
before_save do
return true unless upload
return true if asset_changed == false
# do we need to create this new IO?
upload_io = IO::Memory.new(File.read(upload.path))
asset_data.value = convert_shrine_uploader(FileImport::AssetUploader.cache(upload_io))
@asset_changed = true
end
after_save move_to_store
def move_to_store(file_import : FileImport)
if asset_changed == true && asset_data.value
# do we need to create this new IO?
upload_io = IO::Memory.new(File.read(upload.path))
asset_data.value = convert_shrine_uploader(FileImport::AssetUploader.store(upload_io, move: true, context: { model: file_import }))
@asset_changed = false
save!
end
true
end
def convert_shrine_uploader(uploader : Shrine::UploadedFile) : JSON::Any
JSON.parse(uploader.data.to_json)
end
end
# in some post show page
post.images.each do |image|
if image && (asset_data = image.asset_data)
img src: image.blob_path
else
text "No Image"
end
end
# in some post update (with form)
def render_post_form(op)
form_for Post::Update.with(@post.id), enctype: "multipart/form-data" do
# some fields...
file_input(op.image_attribute, attrs: [:required]
# ...some fields
submit "Update", data_disable_with: "Updating..."
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment