بایگانی برچسب: 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

مزایا و معایب معماری 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

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

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

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

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

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

مفهوم تازه : CORS

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

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

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

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

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

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

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

Share

امنیت حسابهای اجتماعی

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

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

فرض کنیم ما یک پسورد میسازیم، پسورد ما موقعی که داریم وارد میکنیم یک چیزی مثل MySecurePassword@123456 عه. این پسورد تقریبا تمام فاکتورهای یک پسورد امن رو داره. ولی اگر به صورت Plain Text بخوایم ذخیره‌ش کنیم این که جای خود، یک میلیون کارکتر دیگر هم داشته باشه احتمال لو رفتنش بسیار بالاست. (داخل پرانتز عرض کنم خدمت آقای علیرضا شیرازی، مدیریت محترم بلاگفا که تا چند سال پیش هم همینطوری پسورد ها رو نگهداری میکرد، جای این که در توییتر و … ملت رو بخاطر مواخذه کردنتون بلاک کنید، پاسخ میدادید که چرا اینطور بود! چون همون موقع هم بسیاری از اسکریپت های مدیریت محتوا پسوردها رو ماسک می‌کردن!).
حالا بخوایم MySecurePassword@123456 رو در دیتابیسی ذخیره کنیم باید چه کنیم؟ باید از «هش» و الگوریتم های مربوطه استفاده کنیم. مثلا الگوریتم MD5 (لینک جهت مطالعه). حالا این عبارت نسبتا سخت رو من با کمک ابزارهایی که دم دستم بود به MD5 هش میکنم و نتیجه میشه :

23b7618b2a23e171e6cda057a9736423

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

کد   
post '/welcome' do
 begin 
  params[:user][:password] == params[:user][:password_again]
  user = User.create(:username => params[:user][:username], :password => Digest::MD5.hexdigest(params[:user][:password]))
  redirect to("/welcome")
 rescue
  redirect to("/signup_error")
 end
end

و این یعنی ذخیره ایمن پسورد در یک دیتابیس (در اینجا از دیتابیس های مونگو استفاده کردم). و حالا اگر اون قسمتی که مربوط به هش کردن پسورد نمیشد رو، در کد قرار میدادم چه میشد؟ وقتی قرار بود Query بزنم مثلا به یوزر mamad و پسوردش 1125 بود، اینطور بر میگردوند :

کد   
{
"username":"mamad", 
"password":"1125"
}

و مثلا اگر شما قرار بود با API ای که من برای عضوگیری سرویس نوشتم کار کنید (مثلا لاگین کردن به وسیله سرویس من در اپلیکیشن، بازی یا سیستم حضور غیاب و … )، پسوردها رو به سادگی میتونستید ببینید. اما چیزی که الان بر میگرده اینه :

کد   
{"username":"mamad","password":"c21002f464c5fc5bee3b98ced83963b8"}

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

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

 

Share

پروژه گاه‌شمار

خب، من در گروه های تلگرامی این پیام رو میذاشتم :

دوستی مدتی پیش (که خیلی هم ازش نگذشته ) یک چالش رو بعنوان تمرین برنامه نویسی پیشنهاد داد، و اون هم نوشتن یک API بود برای دریافت ساعت و تاریخ.
من هم وارد این چالش شدم و یک API با روبی و سیناترا نوشتم، روی گیتهاب هم قرارش دادم ولی الان دپلویش کردم که اگر شما هم خواستید بتونید ازش استفاده کنید :
https://gahshomar-api.herokuapp.com/

خب، این هم پروژه ای بود که محسن بعنوان تمرین خواست که ما یه API بنویسیم، من هم این رو با Sinatra نوشتم و الان دپلوی شده و در دسترسه! امیدوارم ازش لذت ببرید 🙂

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

یک فنجان قهوه با طعم جاوا اسکریپت و روبی لطفا!

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

سلام دنیا

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

کد   
alert "Hello, World!"

این کد، یک پنجره پیام باز میکنه و پیام Hello World رو به کاربر نشون میده. بعد از اجرای دستور :

coffee --compile ./path/to/coffee/file

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

کد   
alert("Hello, World!");

حالا قطعه کد پیچیده تری رو بررسی میکنیم.

تابع فاکتوریل

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

کد   
a = (x) -> 
    if(x == 0)
       return 1
    else
       return x * a(x-1)

و این کد، همین تابع در جاوااسکریپت :

کد   
var a;
 
a = function(x) {
  if (x === 0) {
    return 1;
  } else {
    return x * a(x - 1);
  }
};

اجرای دو کد، با یک عدد خاص (مثلا ۴) ، یک نتیجه رو به ما بر میگردونه، چرا که کافی صرفا یک رابط نوشته شده با روبی برای جاوااسکریپت به حساب میاد.

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

موفق و پیروز باشید.

Share