بایگانی برچسب: s

احراز هویت JWT در روبی آن ریلز

پس از مدت طولانی، با یک پست دیگر برگشتم و این بار قراره با هم احراز هویت JWT رو در ریلز بررسی کنیم. اول از همه، لازمه بدونیم JWT چیه؟ چرا نیازش داریم؟ اصلا چرا احراز هویت و هزاران چرای دیگر که احتمالا الان در سر شما هستند. بعدش یک پروژه خیلی کوچولو ایجاد می‌کنیم و با هم براش احراز هویت رو پیاده‌سازی می‌کنیم 🙂

احراز هویت JWT چیه و چرا بهش نیاز داریم؟

اول از همه این سوال بنیادی‌تر رو پاسخ بدیم که «چرا احراز هویت نیاز داریم؟» و بعد بریم سراغ احراز هویت JWT که قراره در این مطلب در موردش مفصل حرف بزنیم.

ما به احراز هویت نیاز داریم چون همیشه بخشی از داده‌های ما، خصوصی هستند. گذشته از اون، احراز هویت می‌تونه اجازه CRUD رو به شما بده، نه؟ فکر کنید اپی دارید که هرکسی می‌تونه روش بخونه و بنویسه. ممکنه خوندن چیزی باشه که برای «همه» مناسب باشه اما «نوشتن» اینطور نیست. بخصوص که نوشتن خودش به قسمت‌هایی مثل حذف و بروزرسانی هم شکسته شده.

پس ما به احراز هویت نیاز داریم که هر ننه‌قمری (😂) نتونه از API ما استفاده کنه. بلکه کاربرانی که ثبت‌نام کردند و دسترسی درستی به سرویس دارند، بتونن استفاده کنن. این قضیه در API های تجاری (یا Business facing) خودشون رو بیشتر و بهتر نشون میدن.

حالا سوال مهم‌تر …

احراز هویت JWT چیه؟

در اپهای قدیمی (مثل همین وردپرس)، احراز هویت توسط cookie ها انجام میشه. به چه صورتی؟ به این صورت که وقتی نام کاربری و گذرواژه وارد می‌کنیم، نرم‌افزار فضایی رو در مرورگر به خودش اختصاص میده و یک سری اطلاعات رو با خودش جابجا می‌کنه. اما در ReST API ها این قضیه رو نداریم. در واقع نمی‌تونیم به کوکی‌ها اعتماد کنیم. پس چه می‌کنیم؟ اینجا لازمه جز یوزرنیم(که معمولا می‌تونه عمومی باشه) و پسورد (که می‌تونه راحت لو بره) میاییم و یک «توکن» هم تعریف می‌کنیم. این توکن، می‌تونه ثابت یا متغیر باشه. یعنی چی؟ یعنی می‌تونه به ازای هربار ورود تغییر کنه، می‌تونه سر زمان مشخصی هم منقضی بشه.

حالا توکن چیه؟ توکن به صورت کلی، در کازینوها معادل پولیه که شما در بازی‌ها قرار می‌دید، در واقع مجوز حضور شما در اون کازینو، کلاب و … است. حتی به رمزارزهایی که بر مبنای دیگر رمزارزها ساخته میشن هم سکه نمی‌گن بلکه میگن توکن. شما فرض کنید که مثلا ۱۰۰ دلار می‌دید و پنج تا سکه با آرم اون کازینوی خاص دریافت می‌کنید. اگر در بازی برنده بشید یا ببازید، باید توکن‌هاتون رو تحویل بدید یا بگیرید.

حالا در احراز هویت JWT هم، ما به ازای کاربرمون یک توکن در نظر می‌گیریم. این توکن، معمولا یک رشته طولانیه که انسان نمی‌تونه بخوندش. نتیجتا خیلی از اطلاعات ما به صورت ایمن‌تر می‌تونن رد و بدل بشن (طبیعیه که مواردی مثل SSL داشتن و الگوریتم‌هایی که در ساخت توکن داشتیم هم مهمن). ضمن این که نام‌کاربری، ایمیل، رمزعبور و .. هم به همین سادگیا نمی‌تونن خونده بشن.

پس ما می‌آییم و یک دیتابیسی از توکن‌ها در کنار دیتابیسی از یوزرها میسازیم (البته درست‌ترش، جدوله!) و به ازای هر یوزر معمولا دوتا توکن در اون دیتابیس قرار می‌دیم. یکیش رو بهش میگیم «توکن دسترسی» یا Access Token و یکی رو می‌گیم «توکن بازنشانی» یا Refresh Token. توکن دسترسی، معمولا یک تاریخ انقضایی داره و بعد از اون با استفاده از توکن بازنشانی، می‌تونیم یکی جدید بگیریم. اما در آموزش امروز، صرفا میخوایم توکن دسترسی رو به دست بیاریم.

شمایی از کار JWT
احراز هویت JWT به این شکل کار می‌کنه

خب، الان که تقریبا همه‌چی رو می‌دونیم، بریم برای پیاده‌سازی.

پیاده‌سازی یک اپلیکیشن ریلز با JWT

خب در قدم اول، باید یک اپ ایجاد کنیم. این اپ رو به این شکل ایجاد می‌کنیم:

rails new devise-jwt --api

خب توضیح واضحات:

  • قسمت rails که واضحا فراخوانی نرم‌افزار rails در ترمینال ماست.
  • قسمت new در خواست برای ایجاد یک اپ جدیده.
  • قسمت devise-jwt اسم پروژه ماست. حالا چرا؟ چون قراره از یک لایبرری با همین اسم استفاده کنیم. بنابراین، پروژه رو اینطوری اسم گذاشتیم.
  • در قسمت آخر هم، به ریلز گفتیم که ما تو رو بخاطر API هات دوست داریم. ویو نیاز نیست.

بعد از چند ثانیه (و بسته به سرعت اینترنت دقیقه) اپ ما ساخته می‌شه. بعد لازمه که مرحله مرحله کارهایی رو انجام بدیم.

نصب لایبرری‌های مورد نیاز

خب، اول از همه با ویرایشگر متنی مورد علاقمون فایل Gemfile رو باز می‌کنیم و این خطوط رو بهش اضافه می‌کنیم :
gem 'devise'
gem 'devise-jwt'
gem 'rack-cors'

بعد از این که این خطوط رو اضافه کردیم، دستور زیر رو اجرا می‌کنیم:

bundle

این دستور چه کار می‌کنه؟ میاد و تمام لایبرری‌های مورد نظر شما رو به صورت ایزوله در یک دایرکتوری، نصب می‌کنه. به این شکل شما می‌تونید به سادگی بدون رسیدن آسیب به باقی لایبرری‌های روبی نصب شده روی سرور یا حتی کامپیوتر خودتون، ایده‌هاتون رو تست کنید.

لازم به ذکره که بعد از اجرای این دستور فایل Gemfile.lock به‌روز میشه، این فایل حالا چه کار می‌کنه؟ این فایل حواسش به همه‌چی هست. در واقع، ورژن روبی، ورژن ریلز، لایبرری‌های مورد نیاز و ورژنینگشون و … رو همه رو این فایل داره کنترل می‌کنه.

بعد از انجام مراحل فوق، کافیه این دستور هم اجرا کنیم:

rails g devise:install

این دستور چه می‌کنه؟ این دستور هم برای ما فایلهای devise رو در جای درستش قرار می‌ده.

آشنایی با devise

برای احراز هویت در هر سیستمی ما دو راه داریم:

  • نوشتن سیستم احراز هویت از بیخ
  • استفاده از کتابخانه‌های موجود

در مورد روش «از بیخ»، ما معمولا این کار رو انجام نمی‌دیم. چرا؟ چون معمولا اونقدر خوب نیستیم که بتونیم امنیت سیستم رو به خوبی تامین کنیم. در مورد دوم، در هر فرمورک و زبانی، کتابخانه‌هایی ساخته شدند که کمک می‌کنن ما بتونیم با اضافه کردنشون به پروژه خودمون، بخش احراز هویت رو هندل کنیم. برای ریلز devise ساخته شده. این لایبرری، یک لایبرری مبتنی بر cookie برای احراز هویت وب‌اپ‌هاست.

بعد از همه‌گیر شدن ReST API ها، لایبرری devise-jwt هم نوشته شد. این لایبرری، ابزاریه که به من و شما کمک می‌کنه بتونیم احراز هویت JWT رو به پروژه‌مون اضافه کنیم. در واقع هر سه لایبرری که به پروژه اضافه کردیم، کارشون همینه که JWT رو برای ما راحت کنند.

هندل کردن CORS

در این مطلب قصد ندارم در مورد CORS حرف بزنم، چون قبل‌تر ازش حرف زدم (و می‌تونید اینجا بخونید). اینجا ما صرفا قصدمون اینه که بیاییم و این مشکل رو حل کنیم. چطوری؟ خب این فایل:

config/initializers/cors.rb

رو با ویرایشگر متنی مورد علاقه‌مون باز می‌کنیم، و محتواش رو به این شکل تغییر می‌دیم:

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'

    resource '*',
             headers: :any,
             methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

توجه کنید که این قسمت معمولا به صورت کامنت‌شده در کد هست، فقط کافیه آنکامنتش کنید و نیاز نیست که کلا این مورد رو کپی پیست کنید.

ساخت مدل و کنترلرهای مورد نیاز برای کاربر

یکی از خوبی‌های devise اینه که به شکل scaffold گونه‌ای، می‌تونه به ما کمک کنه که کاربر و سیستم کنترلش رو بسازیم. برای ساخت مدل کاربر فقط کافیه که این دستور رو اجرا کنیم:

rails g devise User

به این شکل می‌فهمه که باید یک مدل، مطابق مدل User ولی با مشخصات devise برامون بسازه. بعدش هم کافیه این دستور رو اجرا کنیم:

rails db:migrate

که جدولای مرتبط برامون در دیتابیس ساخته بشن.

حالا که خیالمون از بابت این قضایا راحت شد چی؟ هیچی. دو تا کنترلر هم می‌سازیم به این شکل:

rails g controller users/sessions
rails g controller users/registrations

بعد از این میشه گفت که کار ما اینجا تمام شده و باید بریم یه چیزایی رو ادیت کنیم 🙂

ویرایش مدل یوزر

بعد از این که کارهای بالا رو انجام دادیم، کافیه که بریم سراغ مدل یوزرمون و به این شکل ادیتش کنیم:

class User < ApplicationRecord

  devise :database_authenticatable,
         :jwt_authenticatable,
         :registerable,
         jwt_revocation_strategy: JwtDenylist
end

حالا این کار برای چیه؟ برای اینه که ما یک جدول دیگر به اسم JWT Deny List در نظر می‌گیریم و توکن‌های منقضی‌شده رو درونش قرار می‌دیم. به اون شکل وقتی توکنی منقضی بشه، می‌تونیم به کاربر خطا نشون بدیم یا از توسعه‌دهنده‌های فرانت تیم بخوایم که وقتی اون خطا رو دیدن، کاربر رو لاگ اوت کنن. خلاصه که راه برای رسیدن به نتیجه مطلوب زیاده. بگذریم، بعد از این، در پوشه مدلها لازمه که یک فایل به اسم jwt_denylist.rb ایجاد کنیم و این محتوا رو درونش قرار بدیم:

class JwtDenylist < ApplicationRecord
  include Devise::JWT::RevocationStrategies::Denylist

  self.table_name = 'jwt_denylist'
end

بعد نیاز داریم که برای این قضیه یک مایگرشن اضافه کنیم:

rails g migration CreateJwtDenylist

سپس، فایل مایگرشن که معمولا در آدرس:

db/migrate

قرار داره رو باز می‌کنیم و محتواش رو به این شکل تغییر می‌دیم:

class CreateJwtDenylist < ActiveRecord::Migration[6.1]
  def change
    create_table :jwt_denylist do |t|
      t.string :jti, null: false
      t.datetime :exp, null: false
    end
    add_index :jwt_denylist, :jti
  end
end

و بعد یک دور مایگرشن‌ها رو اجرا می‌کنیم:

rails db:migrate

تا اینجا مطلب طولانی شد؟ ایرادی نداره. بریم یک قهوه بزنیم به بدن و برگردیم 🙂

کنترلر Session

امیدوارم که کافئین به قدر کافی مودتون رو بالا آورده باشه 🙂 حالا وقتشه که بریم و کنترلر session رو درست کنیم. نیازی نیست واقعا کار خاصی کنیم. تنها کاری که نیازه بکنیم اینه که کنترلری که ساختیم رو باز کنیم و این موارد رو درش کپی کنیم:

class Users::SessionsController < Devise::SessionsController
  respond_to :json

  private

  def respond_with(resource, _opts = {})
    render json: { message: 'You are logged in.' }, status: :ok
  end

  def respond_to_on_destroy
    log_out_success && return if current_user

    log_out_failure
  end

  def log_out_success
    render json: { message: "You are logged out." }, status: :ok
  end

  def log_out_failure
    render json: { message: "Hmm nothing happened."}, status: :unauthorized
  end
end

نکته مهم، اگر هنگام ساخت کنترلر، جای users از چیز دیگری استفاده کردید باید Users رو در کد بالا به اون تغییر بدید. اگر هم کلا چیزی نذاشتید، کل قسمت Users:: رو ازش حذف کنید.

کنترلر Registration

خب عین همون بخش قبلی، شما کافیه کنترلر registrations رو باز کنید و این کد رو درونش کپی کنید:

class Users::RegistrationsController < Devise::RegistrationsController
  respond_to :json

  private

  def respond_with(resource, _opts = {})
    register_success && return if resource.persisted?

    register_failed
  end

  def register_success
    render json: { message: 'Signed up sucessfully.' }
  end

  def register_failed
    render json: { message: "Something went wrong." }
  end
end

تنظیمات نهایی devise

خب اول در ترمینال (یا cmd) این دستور رو اجرا کنید:

rake secret

و یک کد طولانی و مسخره بهتون میده 😁 اون رو در فایل:

config/initializers/devise.rb

در آخر فایل به این شکل کپی کنید:

config.jwt do |jwt|
  jwt.secret = rake_secret_output
end

نکته بسیار مهم اینجا چیه؟ این که حواستون باشه این صرفا یک پروژه تسته و برای محیط پروداکشن اصلا جالب نیست که سیکرت‌ها و توکن‌ها، هاردکد باشن. برای اون زمان می‌تونید از ENV استفاده کنید.

مسیرها

خب، الان که تقریبا همه‌چی آرومه و ما چقدر خوشحالیم، کافیه که بیاییم و فایل routes.rb رو هم به این شکل ویرایش کنیم:

Rails.application.routes.draw do
  devise_for :users,
             controllers: {
                 sessions: 'users/sessions',
                 registrations: 'users/registrations'
             }
end

خب پس چی‌ می‌مونه که انجام ندادیم؟ یک سری آزمایش ساده 🙂

ساخت کاربر

خب الان کافیه بعد اجرای سرور ریلز (مطابق آموزش‌های قبلی)، این دستور رو اجرا کنیم:

curl -X POST -H "Content-type: application/json" -d '{ "user": { "email":"test@test.com", "password":"12345678", "password_confirmation":"12345678"}}' http://localhost:3000/users

بعد از اجرای این دستور، یک شیء جی‌سون ساده به این شکل:

{"message":"Signed up sucessfully."}

به ما برمی‌گرده.

دریافت توکن

حالا چطور توکن دریافت کنیم؟ این هم ساده‌ست. فقط کافیه که این دستور رو اجرا کنیم:

curl -X POST -i -H "Content-type: application/json" -d '{"user":{"email":"test@test.com", "password":"12345678"}}' http://localhost:3000/users/sign_in

بعد در خروجی اول به ما چنین چیزی می‌ده:

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
Vary: Accept, Origin
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIyIiwic2NwIjoidXNlciIsImF1ZCI6bnVsbCwiaWF0IjoxNjIyMTAyOTUxLCJleHAiOjE2MjIxMDY1NTEsImp0aSI6IjBlZDk0YTFmLTgzYTEtNDM3Yy1hOTBkLWQyNTNjY2ZlZDE5YyJ9.NohABuynT-F2GqfCscGtSF6zzK0iAVUIlajSqmMgIMA
ETag: W/"36cb9f6def08029b9c6bff3c14a98017"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 0584c92c-5100-4acf-8a21-852faa1c0173
X-Runtime: 0.209443
Transfer-Encoding: chunked

که این‌ها سرایند (Header) های ما هستند. در این قسمت، هرچی جلوی Authorization قرار داره توکن ماست. و می‌تونیم ازش استفاده کنیم.

بخش بعدی هم اینه :

{"message":"You are logged in."}

که صرفا به ما می‌گه ورودمون موفقیت‌آمیز بوده.

فکر کنم این مطلب، آخرین مطلبی بود که در مورد بیسیک‌های روبی‌آن‌ریلز می‌نوشتم. احتمالا در آینده نه‌چندان دور، همه این‌ها رو با هم به یک سری آموزش ویدئویی تبدیل کنم و از طریق آپارات یا یوتوب منتشرشون کنم.

طبیعتا یک قسمت‌هایی از آموزش در این مطلب پوشش داده نشده، سعی می‌کنم در آینده یک یا دو پست تکمیلی هم ارائه کنم که همه این قضایا به خوبی پوشش داده بشه (یا این که کلا در بخش ویدئویی در خدمتتون باشم).

به صورت کلی، دوست دارم بدونم نظر شما در مورد این تیپ آموزش‌ها چیه؟ آیا ادامه‌شون بدم یا خیر؟ و این که آیا پایه‌ش هستید که بحث فرانتند رو هم شروع کنیم یا روی همین بکند باقی بمونیم و اول یه پروژه کامل رو بکندش رو بزنیم و بعد بریم سراغ فرانت؟ 🙂

در آخر هم بابت وقتی که گذاشتید و مطلب رو خوندید ازتون متشکرم.

Share

ساخت REST API در روبی آن ریلز – قسمت دوم

در پست قبلی با هم یک REST API نوشتیم که یک نمونه از مدل «پست» رو می‌تونست ایجاد کنه، بروز کنه، نمایش بده و نابود کنه.

در این پست هم قصد دارم که کار مشابهی کنم، منتها این بار تیم محصول به ما یک سناریوی جدید داده. از ما خواستند که این بار، کامنت هم به پست‌ها اضافه کنیم و بتونیم با استفاده از API برای هرپستی، یک کامنت هم ایجاد کنیم. پس وقت رو غنیمت می‌شماریم و میریم سروقت پروژه (پوشه‌ها و ساختار عینا مثل پست قبلیه و هیچ تغییری در اون رخ نداده).

ساخت مدل برای کامنت

بیاییم ببینیم تیم محصول برای ما چی طراحی کرده. این عزیزان در نظر دارند که هر کامنت صرفا یک «متن بدنه» داشته باشه و بیشتر از اون نیاز نداشتند. کار ما اینه که حالا مدلی طراحی کنیم که علاوه بر اون، به پست‌ها هم ربط داشته‌باشه. چطوری این کار رو می‌تونیم بکنیم؟

کافیه دستور زیر رو اجرا کنیم و مدلش رو بسازیم:

rails generate model Comment body:text post_id:integer

خب حالا میریم به پوشه :

app/models

و اول post.rb رو به این شکل ویرایش می‌کنیم:

class Post < ApplicationRecord
    has_many :comments 
end

و سپس comment.rb رو به این شکل ویرایش می‌کنیم:

class Comment < ApplicationRecord
    belongs_to :post 
end

حالا این خطوط چی میگن؟ ما در پایگاه داده چندین نوع رابطه داریم. توضیح این روابط به صورت مفصل باشه برای یک پست دیگه. اما اینجا بیاید در نظر بگیرید که طراحی محصول به شکلی بوده که «هر پست میتونه بی‌شمار کامنت داشته باشه و هر کامنت متعلق به فقط و تنها فقط یک پسته». این نوع رابطه اسمش هست «یک به چند» یا بهتر بگم «یک به خیلی» و به قول خارجی‌ها One to many.

حالا که از این قضیه خبر داریم و پست رو هم ساختیم تعلل نمی‌کنیم. می‌ریم سراغ ساخت کنترلر مربوطه. اینجا کنترلر به ما کمک می‌کنه که بتونیم به سادگی یک کامنت رو روی پست بسازیم و مدیریت کنیم. در مورد سناریوی کنترل کامنت هم این بار کمی ساده‌تر می‌گیریم. در ادامه این مورد رو با هم بررسی خواهیم کرد.

ساخت کنترلر کامنت

اول کنترلر کامنت رو به این شکل ایجاد می‌کنیم:

rails generate controller api/v1/comments

و سپس در فایل:

config/routes.rb

این تغییر ریز رو ایجاد می‌کنیم :

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :post do 
        resources :comments
      end
    end
  end
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

و بعد میریم سروقت کنترلر 🙂 اما قبل از اون بیاید یه چیزی رو بررسی کنیم. این روابط رو! این روابط چطوری تعیین شدند؟ و چرا مهمند. پس در ترمینالمون تایپ می‌کنیم:

rails c

این دستور، به ما یک «کنسول ریلز» میده. کنسول به ما کمک می‌کنه که ایده‌ها رو در یک لول قبل از مرورگر و درخواست‌های HTTP بررسی کنیم. در اسکرین‌شات زیر از ترمینال من، می‌بینید که چطوری یکی از پست‌ها رو تست کردم و دیدم که آیا روابطش با کامنت‌ها درسته یا خیر.

خب حالا بیاییم کنترلر رو بنویسیم. قبل از اون، من یک کامنت دستی در کنسول به این شکل می‌سازم:

c = Comment.new(:body => "This is a comment", :post_id => 1)
c.save

و کنسول رو می‌بندم. می‌ریم سراغ کنترلرمون. همونطور که گفتم اینجا یه سری چیزا نیستن. مثلا show اینجا نیازی نیست باشه ولی index نیاز هست. پس می‌ریم سراغ این که این موارد رو در کنترلر لحاظ کنیم. خب با توجه به این توضیحات، ما یک متد index به این شکل نیاز داریم:

def index
    @post = Post.find(params[:post_id])
    render json: @post.comments
end

همونطور که دیدید، اینجا تنها چیزی که نیازه بررسی بشه post_id ماست. درخواستی که به سمت سرور می‌فرستیم هم به این شکله:

curl -X GET -i http://localhost:3000/api/v1/post/1/comments

حالا وقتشه که بتونیم یک کامنت جدید ایجاد کنیم. برای ساخت کامنت جدید هم کافیه که به این شکل، متد create رو بنویسیم:

def create
        @comment = Comment.new(:body => params[:comment][:body], :post_id => params[:post_id])
        if @comment.save 
            render json: {:status => "success", :comment => @comment}
        end 
    end

و نمونه درخواستی که براش می‌فرستیم هم به این شکل:

curl -X POST -H 'Content-Type: application/json' -i http://localhost:3000/api/v1/post/1/comments --data '{
"body":"This is another comment"
}'

حالا یک سوال مهم ممکنه برای شما پیش بیاد و اون هم اینه که :

چرا پارامترهای ارسالی فرق دارند؟

دلیلش خیلی ساده‌ست. ریلز اول میاد پارامترهای درون URL رو مستقیم میخونه، بعد میاد سراغ Request Body که در واقع اگر سلسله مراتبی (مثل یک فایل YAML) بهش نگاه کنیم این شکلی میشه:

- post_id: 1
- comment:
    - body: "This is another comment"

در واقع برای خودش یک Resource space در نظر می‌گیره و body رو از اون میخونه. به همین خاطره که یکی زیرمجموعه comment و دیگری مستقیما post_id میشه.

باقی متدها چی؟

معمولا کامنت‌ها قابل ادیت و حذف و … نیستند. ما هم به جهت سادگی این ماجرا رو براشون پیاده‌سازی نمی‌کنیم تا بعدا چه پیش آید 🙂

جمع‌بندی دو قسمت اخیر

خب در این دوقسمت ما خیلی چیزا یاد گرفتیم که فهرست‌وار بررسی می‌کنیم :

  • چطور ریلز نصب کنیم.
  • چطور یک پروژه ریلز جدید ایجاد کنیم.
  • چطور یک منطق تجاری (Business Logic) رو درک کنیم
  • چطور مدل‌های مورد نظر رو بسازیم
  • چطور API بسازیم و تست کنیم.

این موارد بسیار مهمن و فکر کنم بعد خوندن این دو قسمت حداقل‌های ساخت یک API رو یاد گرفتید. بعد از این چه چیزهایی لازمه که یاد بگیریم؟ این دیگه بستگی به خودتون داره. شاید در موردش مطلبی بنویسم اما فکر کنم این آخرین مطلبیه که انقدر مستقیم داره به نوشتن و ساختن API اشاره می‌کنه.

در مطالب بعدی، میخواهیم بریم سراغ یک سری مفهوم و پیاده‌سازی دیگر. احتمال قوی هم بریم سراغ فرانتند و ببینیم که در دنیای فرانتند چه خبره. پس منتظر باشید که فصل جدیدی از مطالب فنی در راهه 🙂

در آخر، از شما بابت وقتی که برای خوندن این مطلب گذاشتید هم کمال تشکر رو دارم.

Share

ساخت یک Rest API در روبی آن ریلز – قسمت اول

پس از مدتی دست کشیدن از بلاگ نوشتن، به دنیای نویسندگی فنی برگشتم و قراره که امروز با هم یاد بگیریم چطور یک 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 کافیه. از شما هم میخوام که همین سناریو رو برای خودتون، یک دور به صورت کاملا دستی پیاده کنید و سعی کنید درش خلاقیت به خرج بدید. تجربیاتتون هم در بخش نظرات با من به اشتراک بذارید 🙂

قسمت بعدی، چی یاد می‌گیریم؟

در قسمت بعدی، کاری می‌کنیم کارستان. یاد می‌گیریم که در مدلها روابطشون رو تعیین کنیم. بعد مدلی می‌سازیم که به این مدل وابسته‌ست. در نهایت هم به همین شکل، کنترلرش رو می‌سازیم و پروژه رو تحویل کارفرمای عصبانی می‌دیم 🙂

از این که وقت گذاشتید و این پست رو خوندید، واقعا ممنونم. هر یک ویویی که این پست‌ها میخوره، برای من شدیدا ارزشمنده.

Share

مزایا و معایب معماری MVC در توسعه نرم‌افزار

در پست قبلی، بررسی کردیم که اصلا MVC چیه و به چه کار میاد و یکم هم مثال ازش در روبی آن ریلز دیدیم. در این پست، قصد دارم مزایا و معایبش رو بگم و بگم که روبی آن ریلز، چه راه‌حلی برای مشکلاتش پیشنهاد می‌کنه.

باز هم لازمه که سلب ادعا کنم که این مطلب مطلقا آموزش روبی آن ریلز نیست و شما اگر میخواید روبی آن ریلز یاد بگیرید، میتونید تا پست بعدی منتظر باشید 😁

مروری بر کلیت MVC

از پست قبلی، این دید رو داریم که MVC در واقع یک روش تبدیل کردن موجودیت‌های محصول به یک سری مدل، کنترل ارتباطاتشون با هم و با دنیای بیرون با یک سری کنترلر و نمایششون به صورت ویوئه (متفاوت‌تر از قبلی شد، نه؟ چون صرفا بیانم رو در تعریفش عوض کردم). اما حرفی از مزایای و معایبش نزدم.

عمده‌ترین دلیلم برای ادامه ندادن، این بود که مطلب قبلی زمان زیادی از من گرفت و حتی مجبور به ریبوت کردن لپتاپم میانه راه شدم. خود مطلب هم شدیدا طولانی بود و مطمئن بودم از یک حدی طولانی‌تر می‌شد، دیگه کسی اون رو نمیخوند.

حالا که تعاریف رو با هم بررسی کردیم، بریم سراغ اصل مطلب 🙂

مزایا و معایب معماری MVC

مزایا

  • تسریع فرایند پیاده‌سازی : همونطوری که در پست قبلی دیدید، اکثریت قریب به اتفاق چارچوب‌های توسعه که با این معماری کار می‌کنند، ابزارهایی برای تولید و تست موجودیت‌ها در اختیار شما میذارن. این به خودی خود موجب تسریع فرایند پیاده‌سازی میشه. در مورد توسعه هم این قضیه میتونه تا حدی صادق باشه، البته توسعه رو در بخش معایب بررسی می‌کنیم.
  • تسهیل فرایند کار گروهی : در یک بیزنس، معمولا بیش از یک نیرو روی محصول نهایی – چه در پیاده‌سازی چه در توسعه – کار می‌کنه. این معماری به توسعه‌دهنده‌ها کمک می‌کنه بهتر بتونن محصول رو درک کنن. بخصوص اگر قرار باشه هر شخص روی یه بخشی کار کنه و پیاده‌ش کنه.
  • تسهیل فرایند به‌روزرسانی : طبیعیه که به‌روزرسانی یک محصول، کار ساده‌ای نیست. اما یک فرض ساده رو در نظر بگیرید. مثلا من، یک فروشگاه اینترنتی دارم، قراره در نسخه جدید وبسایتم، بخشی هم افتتاح کنم که افراد بتونن لوازم کارکرده هم خرید و فروش کنن. اضافه کردن یک موجودیت جدید به نرم‌افزاری که MVC نوشته شده به شدت ساده‌تر میشه و من فقط لازمه مدل بخش «کارکرده‌» رو اضافه کنم و روابطش با باقی اجزا رو پیاده‌سازی کنم.
  • تسهیل فرایند کشف و رفع باگ : خب از اونجایی که هر موجودیتی، در این معماری به شکلی جدا از موجودیت دیگره، خیلی راحت میشه فهمید مشکل از کجاست. البته لازمه این رو بگم که در معایب هم قراره به این بخش اشاره کنیم و در گوشتون هم میگم که Rest API در این زمینه میتونه بهتر عمل کنه 🙂

خب ما ایرانیا یک ضرب‌المثل معروف داریم که میگه «گل بی‌عیب، خداست». پس این معماری و طبیعتا فرمورک‌هایی که با این معماری کار می‌کنند هم خالی از ایراد نیستند. در این بخش از معایب MVC می‌گم و البته بخشیش هم از معایبی که برای ریلز و لاراول گفته شده نقل می‌کنم.

در مورد بخشی که از ریلز و لاراوله، اسمی از فرمورک نمیارم چون در نظرم این معایب، معایب معماری هستند و نه فرمورک. در واقع اینطوری فرض کنیم که یک سلولی که مشکلی داشته تقسیم شده و همه سلول‌های جدید، همون مشکل رو دارند 😁

معایب

  • سختی درک معماری : این موضوع، میتونه برای برنامه‌نویس‌های تازه‌کارتر، کمی مشکل‌ساز باشه. توصیه می‌کنم قبل از این که لقمه بزرگ بردارید و سراغ ریلز، جنگو یا لاراول برید حتما اول با سیناترا، فلاسک یا لومن کار کنید. به این شکل فرمورک‌ها میتونن براتون به شدت قابل درک‌تر بشن.
  • خارج شدن شکل نرم‌افزار از کنترل توسعه‌دهنده‌(ها) : این مورد کمی پیچیده‌ست. فقط بذارید به این شکل بهتون بگم که شما مثلا یه سری واژه رو نمی‌دونید چطور مدیریت کنید. مثلا ممکنه اسم مدل مربوط به حساب بانکی رو Hesab, Bank یا Bank Account بذارید و نفر بعدی که روی کد شما کار می‌کنه، با این قضیه به مشکل بخوره. بهترین راه اینه که این موارد، قبل از پیاده‌سازی کشف و مستند بشن.
  • انعطاف پایین : طبیعیه که وقتی یک جعبه‌ابزار کامل دم دستتون باشه، دیگه سراغ ساخت ابزار نمی‌رید. اکثر فرمورک‌های MVC هم این مشکل رو دارند و اگر شما بخواهید بخشی هم خودتون پیاده کنید، به شدت به مشکل می‌خورید.
  • پرفرمنس‌تایم پایین : خب از اونجایی که اکثر این فرمورک‌ها، دارن همه چیز رو خودشون هندل می‌کنن، ممکنه هزینه بالایی برای اجرا داشته باشند. البته این هزینه ممکنه با توجه به سخت‌افزارهای امروزی زیاد هم بالا حساب نشه، اما چرا وقتی میشه با کمترین هزینه کاری کرد، هزینه رو بی‌خود و بی‌جهت ببریم بالا؟
  • رفع اشکال پرهزینه : درسته که فرایند کشف و رفع باگ رو گفتیم که آسونه، اما هزینه بالایی داره. حواسمون باید به این موضوع باشه که داریم با چیزی کار می‌کنیم که همه چیش به همه چیش ربط داره و قطع این ارتباط میتونه هزینه زیادی وارد کنه بهمون.
  • بزرگ شدن غیرقابل کنترل پروژه : خب وقتی که MVC کار می‌کنیم، عموما خیلی کم پیش میاد که بخواهیم موجودیت‌ها رو به صورت سرویس‌های مجزا توسعه بدیم. در واقع اینجا خبری از میکروسرویس و اینا نیست! همین باعث میشه بیزنس از یک حدی که بزرگتر بشه، کنترلش به شدت سخت شه. از همین رو، معمولا در کنار MVC یک معماری دیگری مثل rest هم استفاده میشه که این داستان‌ها رو نداشته باشیم.

حالا بیاید ببینیم چه راه‌حلایی برای این قضیه داریم؟ یک راه خیلی خوب اینه که وقتی مدلی ساخته میشه، کنترلر و ویوهای مربوطه هم اتوماتیک تولید بشن. بعدها میتونیم اون چیزی که نیاز نداریم رو از این چرخه حذف کنیم. خب چطوری؟ در این بخش، دو راه حل میارم. یکی از ریلز و دیگری لاراول (فکر کنم کسی انتظار لاراول رو در این قسمت نداشت).

راه حل Ruby on Rails

در ریلز یک مفهومی داریم به اسم scaffold. این مفهوم چی کار می‌کنه؟ میاد مدل، ویو و کنترلر یه موجودیت رو برای ما میسازه. برای زمان‌هایی که Rest API نداریم و میخواهیم صفر تا صد اپ رو خودمون بزنیم، یکی از بهترین انتخاب‌ها در ساخت و طراحی موجودیت‌ها همینه.

مثال پست بلاگ رو در نظر بگیرید. چطوری می‌تونیم به سادگی همه‌چیش رو هندل کنیم؟! ساده‌ست، کافیه این دستور رو تایپ کنیم:

rails generate scaffold Post title:string slug:string body:text

و این دستور به خودی خودش میاد و همه‌ چیزهایی که نیاز داشتیم رو میسازه. خوبی‌های این روش چیه؟

  • استاندارد بودن متدها در کنترلر (یعنی نیاز نیست دیگه زحمت مضاعف برای Routing بکشیم)
  • وجود viewهایی که پارامترهای درست ارسال و یا دریافت می‌کنن (کاهش هزینه کشف و رفع باگ در مراحل اولیه توسعه)

اما خب یک بدی بزرگ هم داره و اون اینه که خلاقیت شما رو میتونه کلا نابود کنه. برای اون هم راهکارهایی هست. مثل این که شما یه کنترلر مجزا بسازید و متدها و endpointهای مورد نظر خودتون رو اونجا تعریف کنید.

باقی موارد، همه مثل مطلب قبلیه و خب در مطالبی که در آینده نزدیک خواهم نوشت، حتما اشاره خواهم کرد به این موضوع که چه فعل و انفعالاتی در ریلز رخ میده برای ساخت این ماجراها 🙂

راه‌حل در لاراول

یک سلب ادعای کوچک ابتدای این بخش بکنم؟ عمده تجربه من با لاراول روی Rest API بوده و فرصتی نشده که MVC باهاش کار کنم. شاید هم هیچوقت MVC کار نکنم. اما این راه‌حل قطع به یقین برای MVC هم پاسخگوئه.

اول از همه یک مدل می‌سازیم:

php artisan make:model Post

و خب وارد جزییات نمی‌شم، میتونیم جزییات مدل رو در فایلهای مربوطه بررسی کنیم.

بعد برای این مدل به این شکل می‌تونیم کنترلر بسازیم:

php artisan make:controller --resource --model=Post

البته لازم به ذکره که باید دقت داشته باشید همیشه کنترلرها از جنس resource و CRUD نیستن و این دو راهکار، در واقع برای وقتی خوبن که شما نیازمند CRUD باشید و نخواهید هزینه ساخت کنترلر «از بیخ» رو متحمل بشید.

جمع‌بندی مطالب

در دو مطلب، هم تعاریف مرتبط با MVC رو یاد گرفتیم، هم یه نیم‌چه اپ MVC نوشتیم و هم مزایا و معایبش و راه‌حل‌های احتمالی برای معایب قضیه رو بررسی کردیم.

این مطالب، بیش از این که رنگ و بوی برنامه‌نویسی داشته باشند، مرتبط با مهندسی نرم‌افزار بودند و خوشحالم که در این زمینه هم محتوایی تولید کردم. فکر کنم این پست دیگه جمع‌بندی خاصی نیاز نداشته باشه. فقط خواستم بابت وقتی که برای خوندن این مطلب گذاشتید، ازتون تشکر کنم 🙂

Share

معماری MVC همراه با مثال در ریلز

احتمالا اگر با فرمورک‌هایی مثل جنگو، ریلز یا ASP کار کرده باشید، عبارت MVC رو به کرات مشاهده کردید. در این پست، قصد دارم تا در مورد این معماری توضیحاتی ارائه کنم. در هر بخش هم، یک مثالی از فرمورک روبی آن ریلز آوردم.

قبل از شروع بعنوان سلب ادعا عرض کنم که شما نیاز دارید که خودتون، روبی آن ریلز رو جداگانه یاد بگیرید و این مطلب، مطلقا آموزش روبی آن ریلز نیست.

معماری MVC یعنی چی؟

خب MVC یک مخففه برای Model, View و Controller. در این معماری، یک نرم‌افزار (یا بهتره بگیم ایده/بیزنس) رو به سه موجودیت مدل، ویو و کنترلر تقسیم می‌کنیم و هر قسمت ایده یا بیزنس رو به یکی از اینها تبدیل می‌کنیم. در واقع، فرمورکها اینجان که ما فرایند ساخت این قضایا رو سریع‌تر پیش ببریم.

پس، بهتره جای این که صرفا دنبال زیربغل مار بگردیم، هر موجودیت رو جدا تعریف کنیم و ببینیم تهش به چی می‌رسیم!

مدل – Model

مدل، همونطور که از اسمش پیداست؛ یک مفهوم انتزاعی از یک موجودیته. بذارید یک مثال ببینیم. بیاید یک پست وبلاگ رو بررسی کنیم.

پست وبلاگ چیا داره؟ پست وبلاگ در نظر من اینها رو حتما داره:

  • عنوان که به عنوان یک رشته متنی در نظر گرفته میشه.
  • عنوان کوتاه یا slug که این هم یک رشته متنی در نظر گرفته میشه.
  • متن بدنه یا body که «متن» در نظر گرفته میشه.

توجه کنید که text از نظر اکثر فرمورکها با string متفاوته. این تفاوت رو احتمالا در مستندات فرمورکی که باهاش کار می‌کنید میتونید پیدا کنید.

حالا این مدل رو چطوری میسازیم؟ در ریلز به این صورت می‌تونیم یک مدل بسازیم:

rails generate model Post title:string slug:string body:text

بعد از اون هم به این شکل مایگرشن‌ها رو اجرا می‌کنیم:

rails db:migrate

خب الان چی داریم؟!

الان چیزی نداریم جز یک مدل، که صرفا میگه «تعریف من از پست چیه». این تعریف کجا استفاده میشه؟ در دیتابیس. خب پس یعنی چی؟ بیاید یک تعریف کلی از مدل ارائه کنیم:

مدل عبارت است از تعاریف انتزاعی و ویژگی‌هاشون که در یک پایگاه داده ذخیره میشه.

خب، الان که می‌دونیم مدل چیه، چطور می‌تونیم یک نمونه ازش اجرا کنیم؟ برای نمونه در ریلز، ما اول کنسول ریلز رو باز می‌کنیم:

rails c

و سپس این دستور رو در کنسول وارد می‌کنیم:

Post.create(:title => "Hello, World", :slug => "hello-world", :body
 => "This is a first post made the wrongest way possible")

و به این شکل، ما یک پست ساختیم. اما آیا روش بهتری هم هست؟ بله هست!

کنترلرها – Controllers

طبیعتا وقتی به شما یک API داده میشه، از شما نمی‌خوان که با دستورات روبی پست بسازید، به‌روز کنید یا حذف کنید! چیزی که به شما میدن چنین چیزیه:

GET /api/v1/posts

و این باید لیست همه پست‌ها رو برگردونه. پس چطور این اتفاق می‌افته؟ با موجودیتی به اسم «کنترلر». کنترلر کارش چیه؟ کنترلر همونطور که از اسمش پیداست، میتونه کنترل کنه (یا بعبارتی چطور چشم‌بسته غیب بگیم؟). کنترلر وظیفه‌ش انجام عملیات CRUD روی مدله.

حالا CRUD چیه؟ این هم یه مفهومی در بطن MVC و Rest API عه که باید بدونید چی به چیه و چی کار می‌کنه. عبارت CRUD مخفف Create, Restore, Update و Destroy عه. بیاید با هم دقیق بررسی کنیم:

  • حرف C یا Create : یعنی کنترلر باید بتونه برای من، یک نمونه از مدلم رو بسازه.
  • حرف R یا Restore (که در بعضی منابع Retrieve هم گفتن) : یعنی کنترلر باید بتونه محتوای یک مدل یا همه مدلها رو به من نشان بده.
  • حرف U یا Update : یعنی کنترلر باید بتونه در وضعیت یک نمونه از مدل، تغییری ایجاد کنه.
  • حرف D یا Destroy : یعنی کنترلر باید بتونه یک نمونه از مدل رو حذف کنه.

شما همون پست بلاگ رو در نظر بگیرید. این پست ممکنه نیاز داشته باشه که ویرایش بشه، نیازمند اینه که گاهی حتی حذف کامل بشه و … . این کار رو با کنترلر انجام می‌دیم. حالا چطوری یک کنترلر می‌سازیم؟ به این شکل:

rails generate controller api/v1/posts

سپس درون فایل کنترلر، این دو تابع رو برای حرف R اضافه می‌کنیم:

def index
    @posts = Post.all 
    render json: @posts
end

def show 
    @post = Post.find(params[:id])
    render json: @post 
end

در یک تابع، قراره که کل پست‌ها رو ببینیم و در یکی، فقط اونی که با ID مورد نظر ما درخواست شده نمایش داده میشه. خب، الان کافیه با ابزاری مثل curl تست کنیم API مون رو.

 ~/playground/mvc-tutorial/ [master] curl http://localhost:3000/api/v1/posts/1
{"id":1,"title":"Hello, World","slug":"hello-world","body":"This is a first post made the wrongest way possible","created_at":"2021-03-23T16:19:41.950Z","updated_at":"2021-03-23T16:19:41.950Z"}

حالا جهت این که C هم ببینیم، اون هم به کنترلر مربوطه اضافه می‌کنیم، اما بقیه‌ش رو از مستندات ریلز می‌تونید یاد بگیرید 😁

پس این تابع رو به فایل کنترلرمون اضافه می‌کنیم:

def create 
        @post = Post.new(post_params)
        if @post.save 
            render json: {:post => @post, :status => success}
        else 
            render error: {:error => "Failed"}
        end 
end

این تابع چی کار می‌کنه؟ اول میاد از طریق یک تابع خصوصی به اسم post_params تعدادی از پارامترها مثل عنوان، عنوان کوتاه و متن بدنه رو دریافت می‌کنه، یک نمونه از مدل پست میسازه. اگر ذخیره‌سازی پست با موفقیت انجام شه، محتوای پست رو به ما نمایش میده.

با ابزار curl به این شکل می‌تونیم از این بخش کنترلر استفاده کنیم:

curl -X POST -H 'Content-Type: application/json' -i http://localhost:3000/api/v1/posts --data '{
 "title":"A post from a request",
 "slug":"a-post-from-a-request",
 "body":"This post has been create using an API call, to show you how awesome MVC can get!" 
}'

البته حواستون باشه در این مورد، حتما باید هدر Content-Type رو ارسال کنیم.

این هم نمونه جوابی که این تابع باید به ما بده:

{
  "post": {
    "id": 2,
    "title": "A post from a request",
    "slug": "a-post-from-a-request",
    "body": "This post has been create using an API call, to show you how awesome MVC can get!",
    "created_at": "2021-03-23T17:37:55.502Z",
    "updated_at": "2021-03-23T17:37:55.502Z"
  },
  "status": "success"
}

 

اما خب، چطوری این داده رو میتونیم همینجا ببینیم؟! بدون این که از curl و … استفاده کنیم؟

ویوها – View

در بخش پیش، کنترلرها رو با دیدی که برای API و … داریم ساختیم. برای استفاده از ویو، یک بار دیگه یه کنترلر میسازیم:

rails generate controller posts

در این کنترلر این دو تابع رو قرار می‌دیم:

def index
    @posts = Post.all 
end

def show 
    @post = Post.find(params[:id])
end

خب قرار نبود این قسمت اینجاش با کد زدن شروع شه اما خب نیاز بود درک کنیم که ویو دقیقا چیه. خب، بیایم با دو تعریف آشنا شیم.

قابل خواندن برای ماشین (Machine Readable) : این مفهوم، به معنای داده‌هاییه که صرفا برای کامپیوتر قابل درک و خواندن هستن و برای کاربر، خواندنشون مشکله. مثل خروجی JSON یا چیزایی که تو دیتابیس ذخیره می‌کنیم. وقتی از روی API داده‌ای می‌فرستیم، در واقع داده‌ها رو به صورت قابل خواندن برای ماشین می‌فرستیم و دریافت می‌کنیم.

قابل خواندن برای انسان (Human Readable) : این مفهوم، به معنای داده‌هاییه که برای انسان قابل خوندن هستند. مثل همین پستی که شما دارید میخونید. در واقع این پست در یک دیتابیس MySQL به شکل عجیب و غریب (برای انسان) ذخیره شده و وقتی شما روی لینکش کلیک می‌کنید، به شکل قابل خواندن برای انسان درمیاد.

اگر صرفا API داشته باشیم، کار ما MVC نیست، اما میتونیم ویو رو با استفاده از Front End داشته باشیم (که در آینده در مورد اون هم یک مطلب خواهم نوشت). اما میتونیم بیایم و برای حداقل همون حرف R یک ویو بسازیم (در حقیقت دو تا). چرا؟ چون بعضی وقتا ممکنه برنامه‌نویس فرانت نداشته باشیم، بعضی وقتا ممکنه حتی نیازی به فرانت نباشه و … .

برای ساخت ویو در ریلز، نیازی به اجرای دستوری نیست. در این فرمورک، وقتی کنترلر بسازید در پوشه:

app/views

یک پوشه به اسم کنترلر مربوطه ساخته میشه. پس در پوشه:

app/views/posts

دو فایل به اسم‌های index.html.erb و show.html.erb ایجاد می‌کنیم.

در فایل index.html.erb این موارد رو باید داشته باشیم:

<h1> Posts </h1>
<% for post in @posts %>
    <ul>
        <li>
            <a href="/posts/<%= post.id %>"> <%= post.title %></a>
        </li>
    </ul>
<% end %>

همونطور که می‌بینید، این کد دو تفاوت اساسی با HTML معمولی داره :

  • پسوندش یه ERB اضافه داره
  • کد روبی وسطش زده شده.

خب، این زبون، HTML نیست (بله، HTML هم زبانه، فقط «زبان برنامه‌نویسی» نیست!) بلکه زبان تمپلیتینگ خود روبیه. مثل blade در php یا Jinja در پایتون.

در show.html.erb هم این کد رو قرار می‌دیم:

<h1> <%= @post.title %> </h1> 
<h2> Slug: <%= @post.slug %> </h2 >
<h2> Body: </h2>
<p> 
    <%= @post.body %>
</p>

حالا اگر با مرورگر به localhost:3000/posts بریم این صحنه زشت رو می‌بینیم 😁

و اگر روی لینک‌ها کلیک کنیم، وارد پست‌ها می‌شیم:

البته یادتون باشه slug معمولا برای url کاربرد داره ولی چون اینجا قصد آموزش ریلز نبود، از این ماجرا صرف نظر کردیم.

خب، حالا بعد یک مطلب طولانی، می‌دونیم که چی به چیه؛ بیاید با هم جمع‌بندی کنیم.

جمع‌بندی مطلب

اول از همه در این مطلب به یک تعریفی برای MVC رسیدیم. در واقع، کار ما بعنوان یک مهندس نرم‌افزار، تبدیل مفاهیم و موجودیت‌های یک بیزنس و روابطشون با هم به یک سری مدل، کنترلر و نحوه نمایششون به کاربر در فرم ویو خواهد بود. حالا که یک تعریفی از MVC داریم، به نظرم بد نیست که بیایم هر مورد هم یک بار دیگر، بررسی کنیم.

  • مدل : مدلها، تعاریف انتزاعی از موجودیت‌ها، ویژگی‌ها و روابط بینشون هستند. به عبارتی، شکلی که ما قراره داده خام رو پردازش کنیم، در مدل تعیین میشه. کمی راحت‌ترش کنم؟ مدل در واقع ساختار دیتابیس ماست.
  • کنترلر : کنترلر وظیفه ساخت یک نمونه از مدل، استخراج داده از دیتابیس، بروزرسانی داده در دیتابیس و حذف یک رکورد از دیتابیس رو عهده داره. این هم اگر بخوام راحت توصیف کنم، میگم ابزاری که باهاش دیتابیس رو به شکل ساده‌تری کنترل می‌کنیم.
  • ویو : ویو، نتیجه یک کوئری رو به صورت قابل خواندن توسط انسان، به ما نمایش میده. نتیجه کوئری معمولا به صورت یک جدول در دیتابیس (در واقع یک یا چند سطرش) یا به صورت JSON برمی‌گرده که توسط انسان قابل خوندن نیست. ما در ویو (و البته اغلب فرانت) این اتفاق رو رقم می‌زنیم.

البته موارد دیگری هم هستند که دیگه واقعا از حوصله این مطلب خارج شدند. دلیل عمده‌ش؟ طولانی شدن بیش از حد این مطلب. وگرنه دوست داشتم از معایب و مزایای MVC هم حرف بزنم. یک اتفاق بامزه هم این وسط افتاد که من حتی مجبور شدم لپتاپ رو ریبوت کنم و شانس آوردم که دو سه ساعت فکر و نوشتن، نپریده بود 😂

در پایان، از همه شمایی که این مطلب رو خوندید تشکر می‌کنم که وقت گذاشتید بابت خوندن مطلب. ضمنا، این اتفاقات در فرمورکی که شما استفاده می‌کنید چطوری رقم میخوره؟ در نظرات منتظرتونم 🙂

Share

نگاهی کلی به کد منبع ماستودون

مدتی پیش، خواستم یک نمونه شخصی از ماستودون بسازم. موقع نصب، ارورهای عجیب و غریب دریافت کردم و خب شروع کردم به جست و جو پیرامون این که چرا اینطوریه و خیلی‌ها واضحا بیان کردند که «مشکل از تصویر داکر PostgresSQL بوده و باید روی اون تصویر کمی تغییر داده شه».

حقیقتا من از PGSQL استفاده نکردم و خیلی هم دلم نمیخواست وارد قضیه بشم و روی داکرفایل و داکر کامپوز و …، تغییر ایجاد کنم. به نظرم همون چیزی که خود توسعه‌دهنده پیشنهاد می‌کنه، معمولا بهترین حالتیه که داره با نرم‌افزار کار می‌کنه. اما یه ایده‌ای به سرم زد و اون هم این بود که چی میشه اگر پشتیبانی از MySQL رو به این بزرگوار اضافه کنم؟ نتیجه این شد که تصمیم گرفتم این مطلب رو بنویسم 😀

ماستودون چیه؟

ماستودون یک شبکه اجتماعی از نوع «فدریتد» (انگلیسی : Federated) محسوب میشه. به این شکل که وقتی شما روی سرور خودتون هم اون رو اجرا کنید، می‌تونید باقی سرورها رو ببینید و تعامل کنید.

مدتی میشه که خیلی از دوستانم، ماستودون رو بعنوان شبکه اجتماعی اصلی استفاده می‌کنند و خیلی کم‌تر به توییتر میان. شاید هم کلا توییتر نیان. یکی از دلایلش همینه که شما، صاحب داده‌های خودت هستی. این یک ایده خیلی خوبه که اگر واقعا حریم خصوصی و گردش اطلاعات براتون مهمه، به سمت ماستودون مهاجرت کنید.

نگاه به کد و چندکلمه‌ای صحبت پیرامون اون

خب از اینجا اصل ماجرا شروع میشه. کد ماستودون با فرمورک روبی آن ریلز (لینک) نوشته شده. این برای من خیلی خوشحال‌کننده بود چون خوندن و تغییر دادن اون کد رو آسون می‌کرد. اما این هم باید در نظر گرفت که کد، به شدت بزرگه و حتی برای حرفه‌ای‌های روبی آن ریلز هم خوندنش کمی دشواره. بهرحال می‌شه یکم حوصله به خرج داد و کد بزرگ رو درک کرد. این مشکل کد نیست. بلکه مشکل اون ساختار MVC محسوب میشه که بعد یه مدتی کد رو به شدت بزرگ می‌کنه و پیمایش و رفع اشکالش رو سخت.

در نگاه اول، کد واقعا ایراد خاصی (جز ایرادات مرسوم پروژه‌های مشابه) رو نداره. اما اولین ایراد جایی خودش رو نشون میده که شما چندین کتابخونه سیستمی اضافی نیاز دارید. برای من libicu و libidn و اینا بود. یکم جا خوردم که چرا باید این‌ها نصب باشه؟ اما با کمی تحقیق در موردش، فهمیدم که یک سری از کتابخونه‌های روبی هستند که وابسته به این بزرگوارهان. در واقع، هردوی این‌ها به شکلی برای پردازش متن استفاده شدند و خب این که پردازش متن، از طرف سیستم انجام بشه خیلی منطقیه. این موضوع فقط کمی باعث بزرگتر شدن پروژه شده. اما خوبی‌هاش، به شدت بیشتر از بدی‌هاشه.

مورد بعدی اروری بود که سر PGSQL گرفتم. خب حقیقتا انتظار این یکی رو داشتم. چون PGSQL و کتابخونه‌های سیستمیش، روی سیستم من نصب نیستند (البته شاید بعدها مجبور شم برای اشکال زدایی و کمک به همین ماستودون، نصبش کنم) و خب اروری که گرفتم دقیقا همین رو می‌گفت. وقتی مطمئن شدم که هیچ کتابخونه دیگری مشکلی نداره، دست به کار شدم که PGSQL رو با MySQL عوض کنم.

پروسه تغییر سیستم مدیریت پایگاه داده

خب معمولش اینه که اول، به پروژه می‌گیم کتابخونه‌های لازم رو نصب کنه. نیاز بود روی سیستم libmysqlclient-dev هم نصب کنم و کردم. بعدش در Gemfile، جم‌های مربوط به مدیریت MySQL رو اضافه کردم و جم‌های مربوط به PGSQL رو به دیدگاه تبدیل کردم و رفتم سراغ مراحل ساخت پروژه.

متاسفانه، اینجا کلی ارور گرفتم سر یک سری جم خاص! جم‌هایی که مرتبط با PGSQL هستند و شدیدا Hard Code شدن رو در کد منبع، شاهد بودیم.

قسمتیش رو دستی حذف کردم به امید این که کار کنه. اما موقع اجرای Migration ها، ارورهای جدیدی گرفتم. قسمت بزرگی از Migration ها رو چک کردم و دیدم که یه جاهایی حتی به صورت دستی دستورات PGSQL نوشته شده. حس می‌کردم که دیگه از اینجا جلوتر رفتن، یه جورایی مسخره کردن خودم باشه. حقیقتا بود، چون اصلا آشنا نبودم به این که اینجا چه اتفاقی داره می‌افته و خب بیخیال اضافه کردن پشتیبانی MySQL به این پروژه شدم.

جمع‌بندی

همونطوری که عرض کردم خدمت شما، بزرگترین اشکال این کد اینه که شدیدا وابسته به PGSQL عه. اگر اینقدر هاردکد نمی‌شد، شاید وضعیت طور دیگری بود و می‌شد پشتیبانی پایگاه‌های دیگر رو بهش اضافه کرد. متاسفانه در issue هایی که در پروژه بود هم دیدم که خیلی صریح پیشنهادات مرتبط با MySQL رو رد کردند و امیدم رو به طور کلی به این پروژه برای اضافه کردن این دیتابیس، از دست دادم.

یک توصیه نهایی بخوام به دوستان توسعه‌دهنده کنم اینه که : سعی کنید حداقل در زمینه دیتابیس، وابستگی رو به حداقل خودش برسونید. اول از همه با این فرض که ممکنه هرلحظه شما به هردلیلی بخواید سیستم رو عوض کنید. پس هرقدر وابستگی کمرنگ‌تر باشه، بیشتر به نفع خودتونه.

مورد بعدی هم اینه که اگر نفر بعدی به هر دلیلی خواست با سیستم متفاوتی پروژه رو اجرا کنه، انقدر سختش نباشه. بهرحال، همه قرار نیست همه چیز رو اونطوری که من و شما دوست داریم، استفاده کنن.

Share

ساختن یک API و چیزهایی که از آن یاد گرفتم

مصادف با عید قربان امسال، پروژه API ترجمه فارسی قرآن کریم رو منتشر کردم. این پروژه، پروژه سختی برای من نبود اما چیزای جالبی ازش یاد گرفتم که خب بد نیست در موردش مطلبی بنویسم. اما قبل از اون، لازمه یک چیزی رو توضیح بدم …

چه شد که این پروژه رو شروع کردم؟

حقیقتش شروع این پروژه به این دلیل بود که شدیدا دنبال ابزار مشابهی می‌گشتم. با این که بعد از این که این پروژه رو دپلوی کردم، یک دوستی در توییتر به من پیشنهاد استفاده از این ابزار رو داد؛ اما بهرحال من ابزار رو توسعه داده بودم و دپلوی هم کرده بودم.

خلاصه که، شروع این پروژه برای من جالب بود. چرا که پروژه به خودی خود سخت نبود اما وسطاش، دقیقا جایی که داشتم فرانتند براش میساختم، چالشی شد. به همین خاطر تصمیم گرفتم که این مطلب رو بنویسم و توضیح بدم که چرا این چالش برای من پیش اومد 🙂

مفهوم تازه : CORS

خب، وقتی که ما بکند و فرانتند پروژه رو از هم جدا می‌کنیم، در واقع داریم دوتا origin مختلف رو به هم وصل می‌کنیم. طبق استانداردهای وب؛ اصولا چنین کاری در مرورگرها؛ بخاطر مسائل امنیتی مجاز دونسته نشده. به همین خاطر، ما مفهوم تازه‌ای به اسم «چند ریشه‌ای» یا Cross-Origin رو خواهیم داشت.

حقیقتا وقتی فرانتند رو با جاوااسکریپت می‌نوشتم؛ دیدم که یک سری ارور بهم میده و توی اون ارورها بهم میگه که بکند من، برای «اشتراک گذاری منابع چندریشه‌ای» یا Cross-Origin Resource Sharing تهیه نشده. دوست داشتم از خود کد جاوااسکریپتم درستش کنم اما چون قرار بود روی هاست همین وبلاگ قرار بگیره و نه سرور مجزایی؛ ترجیح دادم که به جای node.js از وانیلا جی اس استفاده کنم (توضیح : وانیلا اصطلاحا به نرم افزار یا ابزار دست نخورده گفته میشه) و خب راهکاری براش پیدا نکردم (شاید هم چون درست دنبالش نگشتم) ولی از اونجایی که بکند رو با روبی و سیناترا نوشته بودم، براش راهکار پیدا کردم و این راهکار رو در یک ویرگول به اشتراک گذاشتم (لینک).

همین یک کار کوچک، باعث شد که من مفهوم کورز و صد البته نحوه کانفیگ درستش در بکند رو یاد بگیرم، از این جهت هم واقعا خوشحالم چرا که اکثر توسعه دهنده های فرانت، مدام از این قضیه که در بکند این مشکل حل نمیشه، نالانند ((:

چیزی که سالها عقبش انداخته بودم : زرین پال!

تقریبا از وقتی که فهمیدم اینترنت چیه، دنبال راهی برای کسب درآمد ازش بودم. فکر کنم دوم سوم دبیرستان بودم که سایت زرین پال به وجود اومد و اون موقع (چون به سن قانونی نرسیده بودم) نمی‌تونستم ازش استفاده کنم؛ یا این که باید از کسی میخواستم که قبول کنه به اسمش اکانت باز کنم (مثلا پدر یا مادرم).

از وقتی به سن قانونی رسیدم هم هرروز میخواستم برم و اکانت زرین پال درست کنم تا ازش برای حمایت و فروش و … روی اینترنت استفاده کنم. خلاصه که بالاخره همزمان با ریلیز این پروژه، این اتفاق هم افتاد ((: تازه دقیقتر بخواهیم حساب کنیم، برای این مساله هم مجبور شدم کارت بانکیم رو تمدید کنم و هم برای کارت هوشمند ملی اقدام کنم!

به طور خلاصه، این پروژه پروژه جالبی برام بود. هم چالشاش هم ساختارش و هم اتفاقات پیرامونش. ضمن این که به خود پروژه هم ابتدای مطلب لینک دادم و امیدوارم بخونید و خوشتون بیاد. اگر هم از پروژه قراره استفاده‌ای کنید، حمایت هم بکنید ممنون میشم (:

Share

پیاده سازی لیست پیوندی در روبی

لیست پیوندی، یکی از ساختمان داده هایی هست که معمولا توی درس ساختمان داده درس داده میشه (البته در مورد کاربردش در زندگی واقعی چیزی نمیدونم، ممنون میشم بهم بگید) و خب معمولا سر کلاس، توی زبانهایی مثل C یا ++C درس میدنش. اما من داشتم توی نت میگشتم و به این مقاله رسیدم. که این مقاله، توضیح داده چطور میشه توی روبی این ساختمان داده رو پیاده کرد. خب به صورت ساده میریم سراغ پیاده سازی و کم کم پیچیدش میکنیم.

کد   
class Node
 attr_accessor :node, :next
 
 def initialize(node)
  @node = node
 end
end

خب تا اینجا، عملکرد ساده لیست پیوندی رو داریم. همون node و next که next معمولا از جنس اشاره گره. خب ما قاعدتا یک متد دیگری هم نیاز داریم. متدی که نیاز داریم، متدیه که بهمون بگه چیا توی لیستمون ذخیره کردیم. اصولا یکی از مهم ترین متد هاییه که میتونیم توی این کلاس، اضافه کنیم. متد رو به این شکل مینویسیم :

کد   
def self.node_list(node, msg = nil)
    msg ||= ""
    return msg[0..-4] if node.nil?
    node_list(node.next, msg << "#{node.node} -> ")
  end

بسیار خوب! حالا یک متدی مینویسیم که این لیست رو برای ما، برعکس کنه. گرچه چنین متدی نیاز نیست، اما چون توی روبی از این متد برای هش ها و لیست ها (آرایه ها) استفاده شده، بهتره ما هم به لینک لیستمون اضافش کنیم. خب یک متد هم به اسم Reverse ایجاد میکنیم به این شکل:

کد   
def self.reverse(node)
    return node if node.next.nil?
 
    head, swap, node.next = node.next, node, nil
    link = head.next
 
    while link != nil
      head.next = swap
      swap = head
      head = link
      link = link.next
    end
 
    head.next = swap
    head
  end

حالا میتونیم با استفاده از این کلاس، از لیست های پیوندی استفاده کنیم. البته دقت کنید که ما در اینجا در مورد حذف و اضافه کردن Node ها حرفی نزدیم. بلکه صرفا نمایش و معکوس کردن لیست رو بررسی کردیم. امیدوارم کد به کمکتون اومده باشه :).

Share

رویای کریستالی برنامه نویسانه

خب، اول از همه بگم که نمیدونستم چه عنوانی رو باید انتخاب میکردم، و طبق فرمول «احمقانه ترین ایده ممکن» این عنوان رو روی پست گذاشتم. بگذریم، توی این پست میخوام یه پدیده جدید و خیلی خوب رو معرفی کنم. میدونید که من مدت زیادی هست که روبی کار شدم ( و حتی پایگاهی برای توسعه دهندگان روبی ایجاد کردم)  روبی زبان خیلی خوبیه ولی خب قاعدتا نمیشه به نیتیو کد تبدیلش کرد. همونطور که پایتون و … نمیشه نیتیو کد کامپایل کرد. اما با یه چیزی رو به رو شدم که دقیقا خود روبی بود، ولی نیتیو کد میشد. من که بهش سلام کردم، شما هم بهش سلام کنید. زبان برنامه نویسی کریستال یک زبان ruby – inspired هست که کاملا کامپایلری عمل میکنه. سینتکس کاملا سینتکس روبی هست و از این بابت خیالتون راحت باشه که اگر روبی بلدید، یاد گیری این زبان براتون چند دقیقه بیشتر زمان نخواهد برد. اجازه بدید چند مثال رو بررسی کنیم.

برنامه Hello World

کد   
puts "Hello, World"

 

چقدر فرق با روبی حس کردید؟ درسته! هیچ فرقی با روبی نداره. مثالهای بعدی هم هیچ فرقی ندارند.

برنامه شرطی (چک کردن سن قانونی)

کد   
if age >= 18
 puts "Legal"
else 
 puts "Not Legal"
end

 

استفاده از حلقه (برنامه چاپ اعداد ۰ تا ۱۰)

کد   
n = 0
while n <= 10
 puts n
 n += 1
end

 

برنامه شیء گرا (نوشتن کلاس Greeter )

کد   
class Greeter
 def initialize(name)
  @name = name
 end
 
 def say_hi
  puts "Hello, I am #{@name}"
 end
end

 

کد ها رو دیدید؟ خیلی خوب! اگر دوست دارید که این ها رو به نیتیو کد تبدیل کنید همین الان به وبسایت کریستال (که ابتدای پست لینک دادم) مراجعه کنید و شروع به خوندن داکیومنت هاش کنید. مطمئنا کوچکترین تفاوتی در ظاهر با روبی نداره و از این جهت، روبیست ها میتونن به سادگی یادش بگیرند.

Share

نوشتن یک Data Parser ساده

چندین پست قبل، در مورد نوشتن یک زبان برنامه سازی ، مطلبی نوشته بودم. در این مطلب قصد داریم یکی از بخشهای مهم هر زبان یعنی Parser رو بررسی کنیم. اگر یک جست و جوی ساده در اینترنت انجام دهید، حتما خواهید دید که اکثر آموزشهای ساخت زبان برنامه سازی، عملیات parse کردن داده رو با parser generator های موجود مثل bison و … انجام دادن. اما چرا اون رو خودمون ننویسیم؟

برای نوشتن یک Parser ساده تنها نیاز داریم به این که از Regex ها استفاده کنیم. البته، چیزی که در این مطلب نوشته میشه به هیچ وجه شما رو برنامه نویس نمی‌کنه، فقط ممکنه راه رو برای برنامه نویس شدن شما باز کنه.

خب، بریم سر اصل مطلب. میشه گفت تقریبا همه با زبان Lisp و Scheme و سینتکسشون آشنا میشن. این زبان ها سینتکس های جالبی دارن و از همه مهم تر این سینتکس ها نسبتا راحته، و در همان نتایج جست و جو، شما هزاران آموزش پیاده سازی Lisp و Scheme خواهید دید. یک عملیات ساده در Lisp و Scheme به این شکله :

کد   
(+ 1 2)

خب ساختار پرانتز مبنا، باعث میشه کار ما برای بازسازی عملیات های ساده در این زبان ها، بسیار بسیار ساده تر بشه. بیایم اول ببینیم که دیتا پارسر ما باید چه کنه؟

  1. باید این خط از برنامه بهش داده بشه
  2. باید اون رو تجزیه تحلیل کنه و انواع داده ای رو جدا کنه
  3. عملگر رو تشخیص بده
  4. عملیات رو انجام بده و خروجی مناسب رو بگردونه

خب، ما میخوایم نوع داده ایمون عدد باشه، حرف باشه، و بعد از اولین پرانتز، عملگر تعریف شه.

برای این کار، ابتدا دستورات زیر رو در نظر میگیریم :

کد   
( + 1 2 )
( display 'Hello')

خب، الان باید یه تابع کوچولو بنویسیم که نوع داده ای رو بهمون نشون بده. این تابع رو من توی روبی مینویسم و به این شکل در میاد :

کد   
def parse(str)
 str = str.split(' ')
 str[1] = str[1].to_sym
 for i in str
  if /[a-zA-Z]/ === i
   puts "#{i} is STRING"
  elsif /[0-9]/ === i
   puts "#{i} is NUMBER"
  elsif /[:a-z\+\-]/ === i
   puts "#{i} is OPERATOR"
  else
   puts "#{i} is not defined}
  end
end
end

ابتدا، تمام اعضای یک رشته که با فاصله از هم قرار گرفته اند را جدا کردیم (در واقع فاصله ای که در قطعه کد Scheme قرار داده شده، برای Parser ما مهم است) ، سپس با یک دستور شرطی نوع آن را بررسی کردیم. عضو شماره ۱ هر آرایه هم به یک Symbol تبدیل شده است (بعدها سمبل ها را بعنوان اوپراتور ها استفاده خواهیم نمود). بیاید این تابع را اجرا کنیم. آن هم با دو دستور داده شده.

نتیجه به این شکل خواهد بود :

Screenshot from 2015-08-21 19:16:40

همانگونه که شاهدید، پرانتز هارا نمیشناسد، display را هم به عنوان اوپراتور قبول ندارد، حال باید چه کرد؟ برای پرانتز ها باید دو شرط دیگر اضافه کنیم. پس برنامه ما به این شکل خواهد بود :

کد   
def parse(str)
 str = str.split(' ')
 str[1] = str[1].to_sym
 for i in str
  if /[a-zA-Z]/ === i
   puts "#{i} is STRING"
  elsif /[0-9]/ === i
   puts "#{i} is NUMBER"
  elsif /:[a-z\+\-]/ === i
   puts "#{i} is OPERATOR"
  elsif /\(/ === i
   puts "#{i} is LPAREN"
  elsif /\)/ === i 
   puts "#{i} is RPAREN"
  else
   puts "#{i} is not defined
  end
end
end

خب، کد جدید را تست میکنیم وبا تست کد جدید، شما شاهد نتیجه درست خواهید بود.

در این مطلب قصد آن بود که شما ببینید چگونه میتوان فهمید که زبان Lisp ، بررسی میکند که یک داده از چه نوعی است. برای پردازش دستورات درون یک repl هم کافیست تا با یک حلقه بی نهایت، دستوری مشابه دستور زیر بنویسید :

کد   
while true
 print "Lisp > "
 cmd = gets.chomp
 parse(cmd)
end

تبریک، شما یک دیتا پارسر نوشته اید. در مراحل بعدی، سعی میکنم تا توضیح دهم چگونه یک Lisp یا Scheme کوچک درست کنیم.

 

Share