پس از مدتی دست کشیدن از بلاگ نوشتن، به دنیای نویسندگی فنی برگشتم و قراره که امروز با هم یاد بگیریم چطور یک Rest API رو با روبی آن ریلز بسازیم. چرا روبی آن ریلز؟ دو تا دلیل داره. یکی این که چون من بلدمش و دلیل دوم این که منبع فارسی خوب براش به شدت کمه (کما این که برنامهنویس خوب ریلز کم نداریم در ایران).
در این پست قراره چیا یاد بگیریم؟ اول این که Rest چیه. بعدش با ریلز یه پروژه جدید ایجاد میکنیم، بعدش هم یک سناریوی ریزی که در این قسمت تا بخش خوبیش رو پیش میبریم. در قسمت بعدی هم پروژه رو تموم میکنیم و میریم که داشته باشیم فرانتند رو 🙂
تعریف Rest
واژه REST مخفف Representational State Transfer و به معنای «انتقال بازنمودی حالت» است. یعنی چی؟ یعنی ساده و مختصر و مفیدش اینه که شما هرچی که دارید رو بدون تغییر در حالاتش، به کاربر منتقل میکنید. یک تعریف بهترش هم اینه که همه چی به شکل یک لینک اینترنتی و خروجی قابل فهم برای مرورگر (و البته بهتر بگم، پروتکل HTTP) دربیاد.
رست، مزایای خاص خودش هم داره. اما بزرگترین مزیتش اینه که همهجا یکیه! یعنی اینطوری نیست که روی وبسایت من یه شکل باشه و روی یک وبسایت دیگه یه شکل دیگه. همهجا میتونید با دانشی که از HTTP و ابزارهایی که برای ارسال و دریافت درخواست HTTP دارید، باهاش کار کنید. ابزاری که در این پست باهاش کار میکنیم چیزی نیست جز curl دوست داشتنی. یک ابزار خطفرمانی در یونیکس که سالیان ساله برای هر بده بستانی با HTTP داره استفاده میشه.
نصب و راهاندازی روبی، ریلز و نود جی اس!
نصب کردن این موارد حقیقتا چیزی نیست که بخوام در این مطلب بهشون اشاره کنم، فقط یک سرنخ کوتاه میدم و شما خودتون دنبالش کنید.
همونطور که از اسم فرمورک مورد نظر پیداست، یک چارچوب توسعه روی زبان روبی محسوب میشه. حالا چطوری روبی رو نصب کنیم؟ یک راه نسبتا بدی هست که از مخازن توزیع یا brew نصب کنیم، یک راه بهتر هم استفاده از RVM میتونه باشه. شخصا همیشه از RVM استفاده میکنم، و راضی هم هستم. چون بهتون اجازه میده که چندین و چند نسخه روبی نصب داشته باشید.
برای نصب RVM از وبسایتش (لینک) استفاده کنید. این ابزار روی لینوکس و مک به راحتی نصب میشه. برای نصب روی ویندوز هم من از همین ابزار به کمک WSL استفاده کرده بودم و نتیجه، رضایت بخش بود. بعد از نصب این بزرگوار مطابق راهنماهاش، یک نسخه روبی (پیشنهاد من ۲.۷.۲ که این مطلب با استناد به استانداردهاش نوشته شده) نصب کنید. بعد از نصب روبی، این دو دستور رو اجرا کنید که ریلز و متعلقات نصب بشن:
gem install bundle gem install rails
نصب ریلز یکمی طولانیه. میتونید این زمان رو صرف نوشیدن یک فنجان قهوه کنید 🙂 این طولانی بودن هم ربط زیادی به اینترنت نداره، چون روی بستههای خاصی مثل sass یکم درجا میزنه چون نیاز داره یه چیزایی رو کامپایل کنه.
بعد از این، شما با مراجعه به وبسایت نود جی اس (لینک) و yarn (لینک) میتونید اینها رو هم متناسب با سیستمعامل خودتون نصب کنید. حالا همه چی آمادهست که پروژهمون رو بزنیم 🙂
آغاز پروژه
سناریو
خب بیاید فرض کنیم که یک شرکتی قراره یه سرویس وبلاگدهی مشابه مدیوم یا ویرگول ارائه کنه. در حال حاضر هیچی ازمون نخواسته جز :
- امکان ارسال پست از طریق API
- امکان ارسال نظر روی پست از طریق API
و خب در حال حاضر احراز هویت و … براشون مهم نیست چون میخوان ببینن که MVP کار میکنه یا نه؟! و ما هم از خداخواسته قبول کردیم این رو براشون پیاده کنیم. اما این همه ماجرا نیست. این دو مورد، باید یک سری ویژگی هم داشته باشن. این ویژگیها رو طراح محصول باید درآورده باشه. فرض کنیم که طراح محصول اینا رو درآورده و فعلا برای «پست» به ما یک سری ویژگی داده :
- عنوان یا title : یک رشته کرکتری
- عنوان کوتاه یا slug : یک رشته کرکتری که با – از هم جدا میشه، یک رشته رندم هم تهش میچسبه که منحصر به فرد بودنش رو تعیین کنه (چسبیدن رشته رندم رو از فرانت هندل کنید)
- بدنه یا body : متن اصلی.
خب، با این ویژگیها از ما میخوان که پست رو پیاده کنیم تا ویژگیهای کامنت رو بهمون بدن 🙂
ساخت پروژه جدید
ساخت پروژه جدید در ریلز بسیار سادهست. شما فقط کافیه که در پوشهای (مثلا من روی کامپیوتر یه پوشه دارم به اسم playground که پروژههای شخصیم توشن) که میخواید پروژه اونجا باشه، این دستور رو اجرا کنید:
rails new api-tutorial --api
اینجا چی کار میکنیم؟ rails که مشخصه، داره rails رو فراخوانی میکنه. new میگه یک پروژه جدید میخوام. قسمت بعدی، اسم پروژه و طبیعتا اسم پوشه پروژهست. در نهایت بخش مهم همون api عه. این داره به ریلز میگه که viewها و کلا مسخرهبازیای فرانتی رو بعدا و جدا انجام میدم. فعلا مسخرهبازی بکندی میخوام فقط 😁
حالا با اجرای دستور زیر، میریم به داخل پوشه پروژهمون:
cd api-tutorial
ساخت مدل جدید برای پست
همونطور که از مطلب MVC (لینک) میدونیم، مدل تعیین میکنه که یک موجودیت در دیتابیس به چه شکل قرار بگیره. خب، الان کاری که ما باید بکنیم چیه؟ اینه که دستور زیر رو اجرا کنیم و بگیم که چی میخوایم:
rails generate model Post title:string slug:string body:text
خب، در اینجا دقیقا همون چیزی که طراح محصول خواسته رو داریم پیادهسازی میکنیم. خب، الان در پوشه:
app/models
یک فایل به اسم post.rb ساخته شده که توش تقریبا هیچی نیست. و فعلا به اون فایل، کاری نداریم (تو قسمت دوم در مورد این فایل صحبت خواهیم کرد).
الان فقط لازمه که به دیتابیس بفهمونیم که ما یک جدول جدید برای پستها نیاز داریم. برای اون کار، دستور زیر رو اجرا میکنیم:
rails db:migrate
این کار برای ما جدول و روابطش رو میسازه.
ساخت کنترلر برای پست
خب، ما نیاز داریم که عملیات CRUD روی پست انجام بدیم. چرا؟
- هر پست نیاز داره ایجاد بشه. پستی که ایجاد نشه پستیه که قطعا ایجاد نشده 😐
- هر پست نیازمند اینه که به کاربر نمایش داده شه. قرار نیست مخزنالاسرار بسازیم که!
- هر پست نیاز داره که ویرایش بشه. فرض کنید یه جا تو پست نوشتیم «خورش کرفس بهترین غذای دنیاست». خب معلومه که باید «خورش کرفس» رو به «قرمه سبزی» تغییر بدیم.
- و هر پست باید قابل حذف شدن باشه. در مورد حذف نرم و سخت بعدا صحبت خواهیم کرد. اما فرض رو روی destroy یا همون «حذف سخت» میذاریم. چون طراح محصول هنوز اطلاعات بیشتری بهمون نداده.
خب، الان که این رو یاد گرفتیم چی کار میکنیم؟ این دستور رو اجرا میکنیم:
rails generate controller api/v1/post index show create update destroy
بعدش فایل :
app/controllers/api/v1/post_controller.rb
رو باز کنید. با چنین صحنهای روبرو خواهید شد:
class Api::V1::PostController < ApplicationController def index end def show end def create end def update end def destroy end end
خب اول از new شروع میکنیم! از حرف C ! اما قبلش باید یک تغییر کوچکی در این فایل بدیم :
class Api::V1::PostController < ApplicationController def index end def show end def new end def update end def destroy end private def post_params params.require(:post).permit(:title, :slug, :body) end end
متد post_params چی کار میکنه؟ این متد میاد فقط مواردی که نیازن رو validate میکنه و نمیذاره پارامتر بیشتر و یا اشتباهی به endpoint مربوطه پاس بدیم. خب، الان متد new رو به این شکل تغییر میدیم:
def new @post = Post.new(post_params) if @post.save render json: {:status => "success", :content => @post} else render error: {:status => "fail"} end end
خب یک مزیت دیگر استفاده از post_params هم همینه، بدون دردسر میتونیم همه پارامترها رو پاس بدیم به متدهامون. حالا، بهتر اینه که این متد رو تست هم بکنیم نه؟ پس این دستور رو وارد میکنیم:
rails server
این دستور، سرور ریلز رو ران میکنه و به ما اجازه میده که برنامه خودمون رو آزمایش کنیم.
اما الان بهمون ارور میده. پس چی کار کنیم؟ هیچی. این فایل :
config/routes.rb
رو باز میکنیم و به این شکل درش میاریم:
Rails.application.routes.draw do namespace :api do namespace :v1 do resources :post end end # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end
و حالا دوباره تست میکنیم. چطوری؟ به این شکل:
curl -X POST -H 'Content-Type: application/json' -i http://localhost:3000/api/v1/post --data '{ "title":"Hello, World", "slug":"hello-world-abcd1234", "body":"Hello, world! this is a simple test for my app" }'
و خروجیای که میده به این شکله:
{ "status": "success", "content": { "id": 1, "title": "Hello, World", "slug": "hello-world-abcd1234", "body": "Hello, world! this is a simple test for my app", "created_at": "2021-03-28T17:44:16.608Z", "updated_at": "2021-03-28T17:44:16.608Z" } }
حالا وقتشه که بریم سراغ R یعنی Retrieve/Restore. یعنی نمایش پستها و پست به صورت تکی. کافیه index و show رو یکم دستخوش تغییر کنیم:
def index @posts = Post.all render json: @posts end def show @post = Post.find(params[:id]) render json: @post end
و الان با ارسال چنین دستوری:
curl -X GET -i http://localhost:3000/api/v1/post/1
میتونیم پستهامون رو ببینیم.
حالا بیایم برای ویرایش هم چارهای بیاندیشیم 🙂 پس متد آپدیت هم به این شکل بهروز میکنیم:
def update @post = Post.find(params[:id]) if @post @post.update(post_params) end end
خب، اینجا چرا else نگذاشتم؟ چون اگر پست موجود نباشه (موجودیتش با if چک میشه) خود ریلز به ما ارور مناسب (معمولا ۴۰۴) رو نشون میده.
من در سیستم خودم، یک پست با آیدی ۲ دارم که قرار بود آپدیت بشه. الان اون رو با این دستور آپدیت میکنم:
curl -X PUT -H 'Content-Type: application/json' -i http://localhost:3000/api/v1/post/2 --data '{ "body":"updated" }'
حواستون باشه که در چنین موردی، ممکنه به شما استتوس ۲۰۴ داده بشه. نترسید چون آپدیت انجام شده.
و در نهایت، بیاید یک پست حذف کنیم 🙂
برای حذف پست، کافیه که شما متد destroy رو به این شکل دربیارید :
def destroy @post = Post.find(params[:id]) if @post @post.destroy render json: {:status => "success", :post_id => @post.id } end end
در این متد ما گفتیم که پست رو پیدا کن و نابودش کن. تهش هم گفتیم که انجام شد و یک پیام موفقیت هم به کاربر برگردوندیم.
این هم درخواست مربوطه:
curl -X DELETE -i http://localhost:3000/api/v1/post/3
و این هم جوابی که سرور به ما برمیگردونه:
{ "status": "success", "post_id": 3 }
خب، در نهایت فایل post_controller.rb ما به این شکل در اومده:
class Api::V1::PostController < ApplicationController def index @posts = Post.all render json: @posts end def show @post = Post.find(params[:id]) render json: @post end def create @post = Post.new(post_params) if @post.save render json: {:status => "success", :content => @post} else render error: {:status => "fail"} end end def update @post = Post.find(params[:id]) if @post @post.update(post_params) end end def destroy @post = Post.find(params[:id]) if @post @post.destroy render json: {:status => "success", :post_id => @post.id } end end private def post_params params.require(:post).permit(:title, :slug, :body) end end
کل چیزی که در این قسمت نوشتم، برای درک عملکرد API کافیه. از شما هم میخوام که همین سناریو رو برای خودتون، یک دور به صورت کاملا دستی پیاده کنید و سعی کنید درش خلاقیت به خرج بدید. تجربیاتتون هم در بخش نظرات با من به اشتراک بذارید 🙂
قسمت بعدی، چی یاد میگیریم؟
در قسمت بعدی، کاری میکنیم کارستان. یاد میگیریم که در مدلها روابطشون رو تعیین کنیم. بعد مدلی میسازیم که به این مدل وابستهست. در نهایت هم به همین شکل، کنترلرش رو میسازیم و پروژه رو تحویل کارفرمای عصبانی میدیم 🙂
از این که وقت گذاشتید و این پست رو خوندید، واقعا ممنونم. هر یک ویویی که این پستها میخوره، برای من شدیدا ارزشمنده.