কম্পিউটার টিউটোরিয়াল

Shrine দিয়ে রেলসে সহজ ফাইল আপলোড: একটি আধুনিক, মডুলার সমাধান

কারrierWave, Paperclip, Dragonfly সহ অনেক ফাইল আপলোডিং জেম রয়েছে। প্রতিটির নিজস্ব বৈশিষ্ট্য আছে এবং সম্ভবত আপনি ইতিমধ্যে কমপক্ষে একটি ব্যবহার করেছেন।

আজ আমি জানাবো একটি tươngbrücke নতুন কিন্তু খুব দারুণ সমাধান—Shrine সম্পর্কে, যা জ্যাঙ্কো মারোহনিচ তৈরি করেছেন। অন্যান্য সময়ের জেমগুলোর বিপরীতে, এটি একটি মডুলার পদ্ধতি অনুসরণ করে, অর্থাৎ প্রতিটি ফিচার একটি মডিউল (বা Shrine-এর পরিভাষায় প্লাগইন) হিসেবে প্যাকেজ করা হয়েছে। বৈধতা চাই? একটি প্লাগইন যোগ করুন। ফাইল প্রসেসিং চাই? আবার একটি প্লাগইন যোগ করুন! আমি এই পদ্ধতিটি খুব পছন্দ করি কারণ এটি আপনাকে সহজে নিয়ন্ত্রণ দেয় যে কোন মডেলের জন্য কোন ফিচারগুলো উপলব্ধ থাকবে।

এই আর্টিকেলে আমি দেখাবো কীভাবে:

  • রেলস অ্যাপ্লিকেশনে Shrine ইন্টিগ্রেট করবেন
  • গ্লোবাল ও পার-আপলোডার কনফিগার করবেন
  • ফাইল আপলোড করার ক্ষমতা যোগ করবেন
  • ফাইল প্রসেস করবেন
  • বৈধতা নিয়ম যোগ করবেন
  • অতিরিক্ত মেটাডেটা সংরক্ষণ করবেন এবং আমাজন S3 ক্লাউড স্টোরেজ ব্যবহার করবেন

এই আর্টিকেলের সোর্স কোড GitHub-তে উপলব্ধ।
কার্যকর ডেমো এখানে পাওয়া যাবে।

Shrine ইন্টিগ্রেশন

শুরু করতে, ডিফল্ট টেস্ট স্যুট ছাড়া একটি নতুন রেলস অ্যাপ্লিকেশন তৈরি করুন:

rails new FileGuru -T

এই ডেমোতে আমি রেলস ৫ ব্যবহার করছি, তবে অধিকাংশ ধারণা সংস্করণ ৩ এবং ৪-এও প্রযোজ্য।

Gemfile-এ Shrine জেমটি যোগ করুন:

gem "shrine"

তারপর চালান:

bundle install

এখন আমাদের একটি মডেল দরকার, আমি এটি Photo নাম দিচ্ছি। Shrine সকল ফাইল-সম্পর্কিত তথ্য একটি বিশেষ টেক্সট কলামে সংরক্ষণ করে যার শেষে _data সাফিক্স থাকে। সংশ্লিষ্ট মাইগ্রেশন তৈরি ও প্রয়োগ করুন:

rails g model Photo title:string image_data:text rails db:migrate

পুরনো রেলস সংস্করণের জন্য কমান্ডটি হবে:

rake db:migrate

Shrine-এর কনফিগারেশন অপশনগুলো গ্লোবালি ও পার-মডেল দুভাবেই সেট করা যায়। গ্লোবাল সেটিংস ইনিশিয়ালাইজার ফাইলে করা হয়। সেখানে আমি প্রয়োজনীয় ফাইল ও প্লাগইনগুলো যুক্ত করব। প্লাগইনগুলো Shrine-এ ফাংশনালিটির টুকরো আলাদা মডিউল হিসেবে রেখে আপনাকে সকল ফিচারের পূর্ণ নিয়ন্ত্রণ দেয়। উদাহরণস্বরূপ, বৈধতা, ইমেজ প্রসেসিং, ক্যাশিং অ্যাট্যাচমেন্ট ইত্যাদির জন্য প্লাগইন রয়েছে।

ফিলহাল দুটি প্লাগইন যোগ করছি: একটি ActiveRecord সাপোর্টের জন্য এবং অন্যটি লগিং সেটআপের জন্য। এগুলো গ্লোবালি ইনক্লুড হবে। সাথে ফাইল সিস্টেম স্টোরেজও সেটআপ করছি:

config/initializers/shrine.rb

require "shrine"
require "shrine/storage/file_system"
Shrine.plugin :activerecord
Shrine.plugin :logging, logger: Rails.logger
Shrine.storages = {
  cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
  store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"),
}

লগার কনসোলে ডিবাগ তথ্য আউটপুট করবে—ফাইল প্রসেস করতে কত সময় লাগলো তা দেখাবে। এটি খুব কাজের।

2015-10-09T20:06:06.676Z #25602: STORE[cache] ImageUploader[:avatar] User[29543] 1 file (0.1s)
2015-10-09T20:06:06.854Z #25602: PROCESS[store]: ImageUploader[:avatar] User[29543] 1-3 files (0.22s)
2015-10-09T20:06:07.133Z #25602: DELETE[destroyed]: ImageUploader[:avatar] User[29543] 3 files (0.07s)

সকল আপলোড করা ফাইল public/uploads ডিরেক্টরিতে সংরক্ষিত হবে। গিটে এই ফাইলগুলো ট্র্যাক করা দরকার নেই, তাই এই ফোল্ডারটি বাদ দিন:

.gitignore

public/uploads

এখন একটি বিশেষ "আপলোডার" ক্লাস তৈরি করুন যেটি মডেল-নির্দিষ্ট সেটিংস ধারণ করবে। ফিলহাল এটি খালি থাকবে:

models/image_uploader.rb

class ImageUploader < Shrine
end

শেষে, এই ক্লাসটি Photo মডেলে ইনক্লুড করুন:

models/photo.rb

include ImageUploader[:image]

[:image] একটি ভার্চুয়াল অ্যাট্রিবিউট যোগ করে যা ফর্ম নির্মাণে ব্যবহার হবে। উপরের লাইনটি নিচের মতোেও লেখা যায়:

include ImageUploader.attachment(:image)
# অথবা
include ImageUploader::Attachment.new(:image)

বढ़িয়া! এখন মডেলটি Shrine-এর ফাংশনালিটির সাথে সজ্জিত। পরবর্তী ধাপে যাব।

কন্ট্রোলার, ভিউ এবং রাউট

এই ডেমোর জন্য ফটো ম্যানেজ করার একটি কন্ট্রোলার যথেষ্ট। index পেজটি রুট হিসেবে কাজ করবে:

pages_controller.rb

class PhotosController < ApplicationController
  def index
    @photos = Photo.all
  end
end

ভিউ:

views/photos/index.html.erb

<h1>Photos</h1>
<%= link_to 'Add Photo', new_photo_path %>
<%= render @photos %>

@photos অ্যারে রেন্ডার করার জন্য একটি পারশিয়াল দরকার:

views/photos/_photo.html.erb

<div>
  <% if photo.image_data? %>
    <%= image_tag photo.image_url %>
  <% end %>
  <p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p>
</div>

image_data? হলো ActiveRecord-এর একটি মেথড যা চেক করে রেকর্ডে ইমেজ আছে কিনা।
image_url হলো Shrine-এর মেথড যা মূল ইমেজের পাথ রিটার্ন করে। অবশ্য ছোট থাম্বনেইল দেখানো ভালো, সেই জন্য পরে ব্যবস্থা নেব।

সকল প্রয়োজনীয় রাউট যোগ করুন:

config/routes.rb

resources :photos, only: [:new, :create, :index, :edit, :update]
root 'photos#index'

ভূমিকা প্রস্তুত—এখন মূল কাজে যাব!

ফাইল আপলোড করা

এই সেকশনে ফাইল আপলোড করার ফাংশনালিটি যোগ করা দেখানো হলো। কন্ট্রোলার অ্যাকশনগুলো খুব সিম্পল:

photos_controller.rb

def new
  @photo = Photo.new
end

def create
  @photo = Photo.new(photo_params)
  if @photo.save
    flash[:success] = 'Photo added!'
    redirect_to photos_path
  else
    render 'new'
  end
end

স্ট্রং প্যারামিটারের জন্য একমাত্র সতর্কতা: image_data নয়, image ভার্চুয়াল অ্যাট্রিবিউটটি পারমিট করতে হবে।

photos_controller.rb

private
def photo_params
  params.require(:photo).permit(:title, :image)
end

new ভিউ তৈরি করুন:

views/photos/new.html.erb

<h1>Add photo</h1>
<%= render 'form' %>

ফর্মের পারশিয়ালও সহজ:

views/photos/_form.html.erb

<%= form_for @photo do |f| %>
  <%= render "shared/errors", object: @photo %>
  <%= f.label :title %>
  <%= f.text_field :title %>
  <%= f.label :image %>
  <%= f.file_field :image %>
  <%= f.submit %>
<% end %>

এখানেও image অ্যাট্রিবিউটই ব্যবহার করা হয়েছে, image_data নয়।

এরর দেখানোর জন্য আরেকটি পারশিয়াল:

views/shared/_errors.html.erb

<% if object.errors.any? %>
  <h3>The following errors were found:</h3>
  <ul>
    <% object.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <% end %>
  </ul>
<% end %>

এতটুকুই—এখন থেকে আপনি ইমেজ আপলোড করতে পারবেন।

বৈধতা (Validations)

ডেমো অ্যাপটি পুরো করার জন্য আরও কাজ বাকি। প্রধান সমস্যা: ব্যবহারকারী যেকোনো টাইপ ও সাইজের ফাইল আপলোড করতে পারবেন, যা ভালো নয়। তাই বৈধতা সাপোর্টের জন্য আরেকটি প্লাগইন যোগ করুন:

config/initializers/shrine.rb

Shrine.plugin :validation_helpers

ImageUploader-এ বৈধতা লজিক সেট করুন:

models/image_uploader.rb

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

শুধু ১ এমবির নিচে JPG এবং PNG ইমেজ আপলোডের অনুমতি দেওয়া হয়েছে। প্রয়োজনে নিয়মগুলো পরিবর্তন করুন।

MIME টাইপ

ডিফল্টলি Shrine ফাইলের MIME টাইপ Content-Type HTTP হেডার থেকে নিয়ে, যা ব্রাউজার পাস করে এবং শুধু ফাইল এক্সটেনশন বেসড হয়—সবসময় विश्वস্ত নয়। ফাইলের কন্টেন্ট বেসড MIME টাইপ নির্ধারণের জন্য determine_mime_type প্লাগইন ব্যবহার করুন। এটি আপলোডার ক্লাসে ইনক্লুড করছি কারণ অন্যান্য মডেলে এটি দরকার নাও হতে পারে:

models/image_uploader.rb

plugin :determine_mime_type

এই প্লাগইন ডিফল্টলি লিনাক্সের file ইউটিলিটি ব্যবহার করে।

আপলোড করা ইমেজ ক্যাশ করা

এখন ফর্মে ভুল ডেটা দিলে ইরর সহ ফর্ম রি-রেন্ডার হয়, কিন্তু আপলোড করা ইমেজটি হারিয়ে যায়—ব্যবহারকারী আবার সিলেক্ট করতে হয়। cached_attachment_data প্লাগইন দিয়ে সহজে সমাধান করা যায়:

models/image_uploader.rb

plugin :cached_attachment_data

এখন ফর্মে একটি হিডেন ফিল্ড যোগ করুন:

views/photos/_form.html.erb

<%= f.hidden_field :image, value: @photo.cached_image_data %>
<%= f.label :image %>
<%= f.file_field :image %>

ফটো এডিট করা

ইমেজ আপলোড হচ্ছে, কিন্তু এডিট করার ব্যবস্থা নেই। কন্ট্রোলারের অ্যাকশনগুলো:

photos_controller.rb

def edit
  @photo = Photo.find(params[:id])
end

def update
  @photo = Photo.find(params[:id])
  if @photo.update_attributes(photo_params)
    flash[:success] = 'Photo edited!'
    redirect_to photos_path
  else
    render 'edit'
  end
end

একই _form পারশিয়াল ব্যবহার হবে:

views/photos/edit.html.erb

<h1>Edit Photo</h1>
<%= render 'form' %>

এখনও যথেষ্ট নয়: ব্যবহারকারী আপলোড করা ইমেজ মুছে দিতে পারবেন না। সেজন্য remove_attachment প্লাগইন:

models/image_uploader.rb

plugin :remove_attachment

এটি :remove_image ভার্চুয়াল অ্যাট্রিবিউট ব্যবহার করে, তাই কন্ট্রোলারে পারমিট করুন:

photos_controller.rb

def photo_params
  params.require(:photo).permit(:title, :image, :remove_image)
end

রেকর্ডে অ্যাট্যাচমেন্ট থাকলে চেকবক্স দেখান:

views/photos/_form.html.erb

<% if @photo.image_data? %>
  Remove attachment: <%= f.check_box :remove_image %>
<% end %>

থাম্বনেইল ইমেজ জেনারেট করা

ফিলহাল মূল ইমেজ দেখানো হচ্ছে, যা প্রিভিউর জন্য ভালো নয়—বড় ফাইল ডাউনলোড করতে হয়। CSS width/height দিয়ে ছোট করলে ও ফাইল সাইজ কমে না। সার্ভার সাইডে আপলোডের সময় ছোট প্রিভিউ ইমেজ জেনারেট করা ভালো। এতে দুটি প্লাগইন এবং দুটি অতিরিক্ত জেম লাগবে।

প্রথমে জেমগুলো যোগ করুন:

gem "image_processing"
gem "mini_magick", ">= 4.3.5"

image_processing হলো Shrine-এর লেখকের তৈরি একটি জেম যা ইমেজ ম্যানিপুলেশনের হাই-লেভেল হেল্পার মেথড দেয়। এটি mini_magick-র উপর নির্ভরশীল, যা ImageMagick-এর রুবি র‍্যাপার। তাই সিস্টেমে ImageMagick ইনস্টল থাকতে হবে।

নতুন জেমগুলো ইনস্টল করুন:

bundle install

এখন প্লাগইনগুলো এবং তাদের ডিপেন্ডেন্সি ইনক্লুড করুন:

models/image_uploader.rb

require "image_processing/mini_magick"
class ImageUploader < Shrine
  include ImageProcessing::MiniMagick
  plugin :processing
  plugin :versions
  # অন্যান্য কোড...
end

processing প্লাগইন ইমেজ ম্যানিপুলেট করতে দেয় (রিসাইজ, রোটেট, ফরম্যাট কনভার্ট ইত্যাদি)। versions প্লাগইন একই ইমেজের বিভিন্ন ভ্যারিয়েন্ট রাখতে দেয়। এই ডেমোতে দুটি ভার্শন থাকবে: "original" এবং "thumb" (৩০০x৩০০-এ রিসাইজড)।

ইমেজ প্রসেস করে দুটি ভার্শন স্টোর করার কোড:

models/image_uploader.rb

class ImageUploader < Shrine
  process(:store) do |io, context|
    {
      original: io,
      thumb: resize_to_limit!(io.download, 300, 300)
    }
  end
end

resize_to_limit! হলো image_processing জেমের মেথড। এটি ইমেজকে ৩০০x৩০০-এ ছোট করে যদি সেটি বড় হয়, আর ছোট হয় তবে কিছুই করে না। এস্পেক্ট রেশিও রক্ষা করে।

ইমেজ দেখানোর সময় image_url মেথডে :original বা :thumb আর্গুমেন্ট দিন:

views/photos/_photo.html.erb

<div>
  <% if photo.image_data? %>
    <%= image_tag photo.image_url(:thumb) %>
  <% end %>
  <p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p>
</div>

ফর্মেও একইভাবে:

views/photos/_form.html.erb

<% if @photo.image_data? %>
  <%= image_tag @photo.image_url(:thumb) %>
  Remove attachment: <%= f.check_box :remove_image %>
<% end %>

আপলোড শেষে প্রসেসড ফাইলগুলো অটোমেটিক ডিলিট করার জন্য delete_raw প্লাগইন যোগ করুন:

models/image_uploader.rb

plugin :delete_raw

ইমেজের মেটাডেটা

ইমেজ রেন্ডার করার পাশাপাশি এর মেটাডেটা ও বের করা যায়। উদাহরণস্বরূপ, মূল ফটোর সাইজ এবং MIME টাইপ দেখান:

views/photos/_photo.html.erb

<div>
  <% if photo.image_data? %>
    <%= image_tag photo.image_url(:thumb) %>
    <p>
      Size <%= photo.image[:original].size %> bytes<br>
      MIME type <%= photo.image[:original].mime_type %><br>
    </p>
  <% end %>
  <p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p>
</div>

ডাইমেনশন (দৈর্ঘ্য-প্রস্থ) ডিফল্টলি স্টোর হয় না, কিন্তু store_dimensions প্লাগইন দিয়ে সম্ভব।

ইমেজের ডাইমেনশন

store_dimensions প্লাগইন fastimage জেমের উপর নির্ভরশীল। যোগ করুন:

gem 'fastimage'

তারপর:

bundle install

প্লাগইন ইনক্লুড করুন:

models/image_uploader.rb

plugin :store_dimensions

এখন width এবং height মেথড দিয়ে ডাইমেনশন দেখান:

views/photos/_photo.html.erb

<div>
  <% if photo.image_data? %>
    <%= image_tag photo.image_url(:thumb) %>
    <p>
      Size <%= photo.image[:original].size %> bytes<br>
      MIME type <%= photo.image[:original].mime_type %><br>
      Dimensions <%= "#{photo.image[:original].width}x#{photo.image[:original].height}" %>
    </p>
  <% end %>
  <p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p>
</div>

dimensions মেথডও আছে যা অ্যারে রিটার্ন করে (যেমন [500, 750])।

ক্লাউডে যাব (Amazon S3)

ডেভেলপাররা প্রায়শই ক্লাউড সার্ভিসে ফাইল হোস্ট করে। Shrine এ.amazon S3-তে আপলোডের সুযোগ দেয়।

প্রথমে দুটি জেম Gemfile-এ যোগ করুন:

gem "aws-sdk", "~> 2.1"
group :development do
  gem 'dotenv-rails'
end

aws-sdk S3 SDK-এর জন্য, dotenv-rails ডেভেলপমেন্টে এনভায়রনমেন্ট ভেরিয়েবল ম্যানেজ করতে।

bundle install

এখন S3 এক্সেসের জন্য কী পেয়ার প্রাপ্তি করুন। AWS Console-এ সাইন ইন করে Security Credentials > Users-এ যান। S3-তে ফাইল ম্যানিপুলেশনের পারমিশন সহ একটি ইউজার তৈরি করুন। পূর্ণ S3 এক্সেসের সিম্পল পলিসি:

{
  "Version": "2016-11-14",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": "*"
    }
  ]
}

তैयার ইউজারের কী পেয়ার ডাউনলোড করুন। রুট এক্সেস কী ব্যবহার করবেন না—ভয়াবহ অসুরক্ষিত।

S3 বাকেট তৈরি করুন এবং প্রজেক্ট রুটে কনফিগারেশন ফাইল যোগ করুন:

.env

S3_KEY=YOUR_KEY
S3_SECRET=YOUR_SECRET
S3_BUCKET=YOUR_BUCKET
S3_REGION=YOUR_REGION

কখনোই এই ফাইল পাবলিক করবেন না এবং গিট থেকে বাদ দিন:

.gitignore

.env

Shrine-এর গ্লোবাল কনফিগারেশন পরিবর্তন করে নতুন স্টোরেজ যোগ করুন:

config/initializers/shrine.rb

require "shrine"
require "shrine/storage/s3"
s3_options = {
  access_key_id: ENV['S3_KEY'],
  secret_access_key: ENV['S3_SECRET'],
  region: ENV['S3_REGION'],
  bucket: ENV['S3_BUCKET'],
}
Shrine.storages = {
  cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
  store: Shrine::Storage::S3.new(prefix: "store", **s3_options),
}

হয়ে গেলো! অ্যাপের অন্য কোনো অংশ পরিবর্তন ছাড়া এই নতুন স্টোরেজ টেস্ট করতে পারবেন। S3 থেকে কী সংক্রান্ত ইরর আসলে কী ও সিক্রেট সঠিক কপি হয়েছে কিনা চেক করুন (ট্রেইলিং স্পেস বা ইনভিজিবল ক্যারেক্টার না থাকলে ভালো)।

উপসংহার

এই আর্টিকেলটি এখানে সমাপ্ত হচ্ছে। আশা করি এখন আপনি Shrine ব্যবহারে অনেক আত্মবিশ্বাসী অনুভব করছেন এবং আপনার প্রজেক্টে এটি ব্যবহার করতে চাইছেন। আমরা এই জেমের অনেক ফিচার আলোচনা করেছি, কিন্তু এছাড়াও আরও আছে—যেমন ফাইলের সাথে অতিরিক্ত কনটেক্সট সংরক্ষণ, ডায়রেক্ট আপলোড মেকানিজম ইত্যাদি।

Shrine-এর ডকুমেন্টেশন ও অফিসিয়াল ওয়েবসাইট ব্রাউজ করুন যেখানে সব প্লাগইন বিস্তারিত বর্ণনা করা আছে। আরও প্রশ্ন থাকলে কমেন্ট করুন। ধন্যবাদ এবং শুভ কোডিং!

  1. Hotwire দিয়ে Rails অ্যাপস দ্রুত করুন: JavaScript ছাড়া সার্ভার-সাইড রেন্ডারিং

  2. Byebug এবং Pow দিয়ে রেলসে রিমোট ডিবাগিং: একটি স্টেপ-বাই-স্টেপ গাইড

  3. রুবি অন রেইলসে টেইলউইন্ড CSS যোগ করা: একটি প্র্যাকটিক্যাল ধাপে ধাপে গাইড

  4. Angular এবং Rails 5 ইন্টিগ্রেশন: একটি স্মুথ ফুল-স্ট্যাক পদ্ধতি