রেইলসে স্ক্যাফোল্ড জেনারেট করলে আপনি সাধারণ respond_to ব্লকগুলো দেখবেন:
def destroy @task.destroy respond_to do |format| format.html { redirect_to tasks_url, notice: 'Task was successfully destroyed.' } format.json { head :no_content } end end
তবে আপনার কিছু একশন, যেন index, সেগুলো নেই!
# GET /tasks # GET /tasks.json def index @tasks = Task.all end
এটা খারাপ। কেন? আপনি যদি /tasks.txt হিট করেন, এবং txt আপনার অ্যাপ্লিকেশন সাপোর্ট করে না, তবে আপনি ভুল এরর পাবেন:
ActionView::MissingTemplate (Missing template tasks/index, application/index with {:locale=>[:en], :formats=>[:text], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]}
এটা একদম সঠিক নয়। আপনাকে ক্লায়েন্টকে বলা উচিত যে সে এমন ফরম্যাট রিকোয়েস্ট করছে যেটা আপনি সাপোর্ট করেন না, না যেন আপনি সঠিক ফাইল খুঁজে পাচ্ছেন না।
যদি এটি UnknownFormat এরর হতো, আপনি একটি ভালো রেসপন্স কোড রিটার্ন করতে পারতেন। বদলে, এই এররগুলো অন্যান্য, অনরিলেটেড এররগুলোর সাথে মিক্স হয়ে যাবে, এবং সেগুলো হ্যান্ডল করা খুব কঠিন হবে।
আপনি আপনার index একশনে respond_to ব্লক যোগ করতে পারেন:
# GET /tasks # GET /tasks.json def index @tasks = Task.all respond_to do |format| format.html format.json end end
তब আপনি আশা অনুযায়ী এক্সেপশন এবং এরর কোড পাবেন:
Started GET "/tasks.txt" for 127.0.0.1 at 2014-11-03 22:05:12 -0800 Processing by TasksController#index as TEXT Completed 406 Not Acceptable in 21ms ActionController::UnknownFormat (ActionController::UnknownFormat): app/controllers/tasks_controller.rb:8:in `index'
খুব ভালো। তবে আপনার সব কন্ট্রোলারে respond_to ছড়িয়ে দেয়া পাগলামী। এটা অন-রেইলস-ইশ লাগে। DRY ভাঙে। এবং আপনাকে আপনার কন্ট্রোলার আসলে কী কাজ করছে সেটা থেকে ডিসট্র্যাক্ট করে।
আপনি এখনও ভুল ফরম্যাট সঠিকভাবে হ্যান্ডল করতে চান। তো কী করবেন?
একটি respond_to শর্টকাট
আপনি যদি আপনার অবজেক্ট রেন্ডার করার জন্য কিছু স্পেশাল না করেন, তবে আপনি একটি শর্টকাট নিতে পারেন। আপনি যদি লিখেন:
def index @tasks = Task.all respond_to :html, :json end
এটা index-এ পুরো respond_to ব্লক লেখার মতই কাজ করে। এটা আপনার একশন যেসব ফরম্যাট জানে সেগুলো রেইলসকে বলার একটি ছোট উপায়। এবং যদি ভিন্ন একশন ভিন্ন ফরম্যাট সাপোর্ট করে, তবে কম কোডে সেসব পার্থক্য হ্যান্ডল করার এটি একটি ভালো উপায়।
কন্ট্রোলার লেভেলে ফরম্যাট হ্যান্ডল করুন
সাধারণত, আপনার কন্ট্রোলারের প্রতিটি একশন একই ফরম্যাটের সাথে কাজ করে। যদি index json-এ রেসপন্ড করে, তবে new, create, এবং বাকি সবও করবে। তাই কত ভালো হতো যদি একটি respond_to থাকে যা পুরো কন্ট্রোলারকে এফেক্ট করে:
class TasksController < ApplicationController before_action :set_task, only: [:show, :edit, :update, :destroy] respond_to :html, :json # GET /tasks # GET /tasks.json def index @tasks = Task.all respond_with(@tasks) end
এটা আসলে কাজ করে:
Started GET "/tasks.txt" for 127.0.0.1 at 2014-11-03 22:17:37 -0800 Processing by TasksController#index as TEXT Completed 406 Not Acceptable in 7ms ActionController::UnknownFormat (ActionController::UnknownFormat): app/controllers/tasks_controller.rb:8:in `index'
ঠিক সেই ধরনের এরর যা আমরা আশা করছিলাম! এবং আপনাকে প্রতিটি একশন মেস করা লাগলো না।
কখনো কখনো আপনি মডেলের স্টেটের ওপর ভিত্তি করে ভিন্ন জিনিস করতে চাবেন। উদাহরণস্বরূপ, create-এ, আপনি রিডাইরেক্ট করবেন অথবা ফর্ম রি-রেন্ডার করবেন, মডেলটি ভ্যালিড কিনা তার ওপর নির্ভর করে।
রেইলস এটা হ্যান্ডল করতে পারে। কিন্তু আপনাকে এখনও বলে দিতে হবে কোন অবজেক্ট চেক করতে চান, respond_with দিয়ে। তাই এর বদলে:
def create @task = Task.new(task_params) respond_to do |format| if @task.save format.html { redirect_to @task, notice: 'Task was successfully created.' } format.json { render :show, status: :created, location: @task } else format.html { render :new } format.json { render json: @task.errors, status: :unprocessable_entity } end end end
আপনি লিখতে পারেন:
def create @task = Task.new(task_params) flash[:notice] = "Task was successfully created." if @task.save respond_with(@task) end
এভাবে, আপনি আপনার কোডকে আপনার রেসপন্ড করার ফরম্যাট থেকে আলাদা করলেন। আপনি রেইলসকে একবার বলতে পারেন আপনি কী ফরম্যাট হ্যান্ডল করতে চান। আপনাকে প্রতিটি একশনে সেগুলো রিপিট করতে হবে না।
রেসপন্ডার্স জেম
রেইলস ৪.২-এ একটা ক্যাচ আছে: respond_with আর ইনক্লুডেড নয়। তবে আপনি সেটা ফিরিয়ে পেতে পারেন responders জেম ইনস্টল করলে। এবং responders জেম আরো কিছু সুবিধা নিয়ে আসে।
আপনি respond_with-এ ফ্ল্যাশ মেসেজ সেট করতে পারেন responders :flash কন্ট্রোলারের শীর্ষে ইনক্লুড করলে:
class TasksController < ApplicationController responders :flash
সুবিধাজনকভাবে, আপনি এই ফ্ল্যাশ মেসেজের ডিফল্ট লোকেল ফাইলে সেট করতে পারেন।
অথবা, আপনার Gemfile-এ responders জেম থাকলে এবং আপনি রেইলস স্ক্যাফোল্ড জেনারেট করলে, জেনারেটর কন্ট্রোলার তৈরি করবে respond_with ব্যবহার করে respond_to-র বদলে:
class TasksController < ApplicationController before_action :set_task, only: [:show, :edit, :update, :destroy] respond_to :html, :json def index @tasks = Task.all respond_with(@tasks) end def show respond_with(@task) end # ...
এটা রেইলসের সাথে আসা স্ক্যাফোল্ড চেয়ে অনেক ক্লিনার।
এবং শেষে, আপনি যদি নির্দিষ্ট কন্ট্রোলার একশনের জন্য শুধু একটি ফরম্যাটে রেসপন্ড করতে চান, তবে আপনি একাধিকবার respond_to কল করতে পারেন:
class TasksController < ApplicationController respond_to :html respond_to :js, only: :create end
জেরোন وেইনকে কমেন্টে ধন্যবাদ সেটি টিপের জন্য!
respond_with নাকি respond_to?
আপনি যদি ভিন্ন ফরম্যাটের জন্য ভিন্ন ইনফরমেশন রিটার্ন করতে চান, আপনার কয়েকটি অপশন আছে। কন্ট্রোলার-লেভেল respond_to কম্বাইন করা respond_with ছোট কন্ট্রোলার পাওয়ার একটি দুর্দান্ত উপায়। কিন্তু এটা সবচেয়ে বেশি সাহায্য করে যখন আপনার সব কন্ট্রোলার একশন একই ফরম্যাটে রেসপন্ড করে, এবং রেইলস যেভাবে এক্সপেক্ট করে সেভাবে কাজ করে।
কখনো কখনো, আপনি চান কিছু একশন ভিন্নভাবে কাজ করে। এক-লাইনার respond_to সেটা হ্যান্ডল করার জন্য খুব ভালো।
আপনি যদি আরও কন্ট্রোল চান, ফুল respond_to ব্লক সহ ব্যবহার করুন, এবং আপনি প্রতিটি ফরম্যাট কীভাবে হ্যান্ডল করতে চান সেটা করে ফেলুন।
এই যেকোনো একটি দিয়ে, আপনি যেসব ফরম্যাট সাপোর্ট করেন না সেগুলোর রিকোয়েস্ট সঠিক এরর পাবে। এবং আপনার অ্যাপ্লিকেশন এবং এর ক্লায়েন্ট দুটোই অনেক কম বিভ্রান্ত থাকবে।