مدتی پیش، پروژههای مختلفی مثل مارال یا جبیر رو با هدف انتشار و ساخت یک مدل زبانی بزرگ با همون LLM (مخفف Large Language Model) شروع کرده بودم اما بحث این مدلها و شاید همزمان شدن انتشار اینها با نسخههای جدیدی از پلتفرمهای مانی و آتلیه، کمی باعث شده بود که از هدف اصلی دور بشیم.
در همین مدت، مدل ۸ میلیارد پارامتری هرمز منتشر شد که خب یک بازخورد بسیار خوب از جامعه فارسیزبان تونست بگیره. مدل هرمز، از طریق وبسایت هاگینگفیس، کاملا در دسترس شماست و میتونید ازش استفاده کنید.
اما هرمز شد بخشی از یک پروژه بزرگتر، چرا که کمی دقت به بزرگان این حوزه، نشون از این بود که تقریبا همه شرکتهای خوب و موفق در این حوزه، به جای این که «یک مدل» منتشر کنند «یک خانواده مدل» منتشر کردند که خب باید از این قضیه تا حدی الگوگیری میشد.
تصمیم به ساخت یک خانواده از مدلها
از تولیدکنندگان بزرگ و تجاری مدلهای جنریتیو که بگذریم، بسیاری از شرکتهایی که مدلهای اوپن سورس تولید میکنن و نامداران این عرصه هم هستند (مثل Alibaba Cloud, DeepSeek, Mistral و حتی Meta) عموما به یک عدد مدل کفایت نمیکنند.
مدلهایی که این شرکتها تولید میکنند عموما در یک «خانواده» قرار داره و این خانواده هم بر اساس تعداد پارامتر، توانایی استنتاج (یا همون Reasoning) توانایی بینایی ماشین (یا همون vision) و …، تعیین میشن. برای مثال یکی از مدلهای معروف این حوزه که LLaMA نام داره و توسط شرکت متا ساخته شده، معمولا در یک نمونه کوچک (۷ یا ۸ میلیارد پارامتری)، یک نمونه متوسط (۱۱ یا ۱۳ پارامتری) و نمونههای بزرگ (۷۰ میلیارد پارامتر و بیشتر) تولید میشه.
اما خب یک مورد دیگری هم که به چشمم خورد، کاری بود که DeepSeek با R1 کرده بود. در واقع اومده بودن مدلهای کوچکتر (از یک و نیم میلیارد تا هفتاد میلیارد پارامتر) رو با روش Distillation درست کرده بودند.
در واقع مدلهایی مثل LLaMA, Qwen, Mistral و … رو با دادههایی که از مدل دیپسیک ۶۷۱ میلیارد پارامتری به دست آورده بودند، مجدد آموزش دادند که در اختیار افراد بیشتری قرار بگیره.
همین موضوع، باعث شد که به این فکر بیفتیم که در سال ۱۴۰۴ به جای این که هفتهای یک LLM ریلیز کنیم 😁 یک خونواده خوب از LLMها برای تمام فصول ریلیز کنیم که باز هم از DeepSeek V3 و ترین کردن QLoRA و مرج کردن روی اون شروع شد.
اسم Xei از کجا میاد؟
پیش از این که بخواهیم در مورد خود مدلها و روش اجراشون صحبت کنیم، کمی در مورد اسم توضیح بدم.
ریاضیدانان ایرانی مثل خوارزمی، موقعی که معادلات خاصی رو حل میکردند از عبارت «شیء» بعنوان مجهول استفاده میکردند. وقتی اروپاییها آثار این دانشمندان رو به زبانهای خودشون ترجمه کردند، درک کردند که این «شیء» در واقع مجهوله و به جای این که Object (یا چیزی معادلش) ترجمهش کنند، برای حفظ حالت مجهولش از عبارت xei استفاده کردند که بعدا شد xای که در معادلات مختلف استفاده میکنیم.
یکی از دلایل این اسم، اینه که هم تلفظش برای داخلیها راحته هم خارجیها و هم یک بکگراند جالب ایرانی داره.
اما حالا مدلها چی هستند؟ چرا انقدر این خونواده از مدلها مهم بود؟
اهمیت خانواده مدل Xei
یکی از دلایل اصلی ساخته شدن Xei این بود که این مدلها بتونن هم روی دستگاههای کاربر نهایی مثل من و شما اجرا شن هم روی زیرساختهای بزرگ و صنعتی.
در واقع هم تعدادی مدل On Device داشته باشیم و هم تعداد زیادی مدل برای استفاده Enterprise و به همین خاطر ۷ تا مدل در این خونواده، قرار گرفته که در ادامه بررسی میکنیم.
مدلهای Xei
مدل ۰.۱ میلیارد پارامتری، مبتنی بر لاماست و صرفا زبان انگلیسی میفهمه و میتونه در کارهایی مثل کدنویسی به شما کمک کنه.
مدل ۰.۵ میلیارد پارامتری، مبتنی بر Qwen ساخته شده. با این که از دیتای چندزبانی درش استفاده شده ولی بهترین عملکرد رو روی انگلیسی داره و همچنان برای کارهایی مثل کدنویسی و نوشتن ایمیل، مناسبه.
مدل ۲ میلیارد پارامتری که مبتنی بر Gemma 2 ساخته شده و محمد شجاعی عزیز زحمت ساختش رو کشیده، اولین مدلیه که به خوبی فارسی رو درک میکنه و میتونه به زبان فارسی به شما پاسخهای درست بده.
مدل ۸ میلیارد پارامتری که در واقع همون هرمز قدیمی خودمونه و مبتنی بر Command-R از Cohere ساخته شده.
مدل ۳۲ میلیارد پارامتری که باز هم مبتنی بر Command-R ساخته شده و نتایج بهتر و دقیقتری میتونه تولید کنه.
مدل ۱۰۰ میلیارد پارامتری که باز هم مبتنی بر Command-R ساخته شده 😁
و در نهایت مدل ۶۷۱ میلیارد پارامتری که مبتنی بر DeepSeek V3 ساخته شده و از معماری MoE بهره میبره.
و خب همونطوری که میبینید، تا مدل ۸ میلیارد پارامتری به سادگی روی اکثر رایانههای شخصی حتی بدون کارت گرافیک NVIDIA قابل اجراست ولی نمونه ۳۲ و ۱۰۰ و ۶۷۱ نیاز به منابع بیشتری دارند که در ادامه به اونها هم میپردازیم.
چطوری به Xei دسترسی پیدا کنیم؟
اگر میخواهید مستقیما به سمت مدل ۶۷۱ میلیارد پارامتری بریم، کافیه که به این سرویس برید، یک حساب کاربری بسازید و شروع به چت کنید.
ولی اگر دوست دارید که این مدل رو روی سیستم شخصی خودتون اجرا کنید، میتونید از کتابخونه Ollama نسخه مناسب رو دانلود کنید (با کارت ۲۰۵۰ تا مدل ۳۲ میلیاردی قابل اجراست، گرچه بهترین نتیجه مربوط به همون ۸ میلیاردیه).
در آموزشهای بعدی، نحوه راهاندازی و کار کردن با Ollama رو هم قرار خواهم داد که ببینید چطور میشه به سادگی یک سری مدل خوب هوش مصنوعی رو روی کامپیوتر شخصی، اجرا کرد.
جمعبندی و سخن آخر
در حال حاضر، پروژه Xei بعنوان یکی از پرچمداران مجموعه مانی که تحت برند Aqua Regia فعالیت میکنه قراره مدتها آخرین و مهمترین پروژه ما باشه. از همین رو، پست بلاگ مربوط بهش هم زود نوشته شد تا این که بتونیم روی اون مانور لازم رو بدیم.
اما کل داستان این نیست و به زودی با سورپرایزهای جدیدتری، در خدمت شما خواهیم بود. امیدوارم تا اون موقع با Xei کارهای خفنی کرده باشید 😎
پس از مدت طولانی، با یک پست دیگر برگشتم و این بار قراره با هم احراز هویت JWT رو در ریلز بررسی کنیم. اول از همه، لازمه بدونیم JWT چیه؟ چرا نیازش داریم؟ اصلا چرا احراز هویت و هزاران چرای دیگر که احتمالا الان در سر شما هستند. بعدش یک پروژه خیلی کوچولو ایجاد میکنیم و با هم براش احراز هویت رو پیادهسازی میکنیم 🙂
احراز هویت JWT چیه و چرا بهش نیاز داریم؟
اول از همه این سوال بنیادیتر رو پاسخ بدیم که «چرا احراز هویت نیاز داریم؟» و بعد بریم سراغ احراز هویت JWT که قراره در این مطلب در موردش مفصل حرف بزنیم.
ما به احراز هویت نیاز داریم چون همیشه بخشی از دادههای ما، خصوصی هستند. گذشته از اون، احراز هویت میتونه اجازه CRUD رو به شما بده، نه؟ فکر کنید اپی دارید که هرکسی میتونه روش بخونه و بنویسه. ممکنه خوندن چیزی باشه که برای «همه» مناسب باشه اما «نوشتن» اینطور نیست. بخصوص که نوشتن خودش به قسمتهایی مثل حذف و بروزرسانی هم شکسته شده.
پس ما به احراز هویت نیاز داریم که هر ننهقمری (😂) نتونه از API ما استفاده کنه. بلکه کاربرانی که ثبتنام کردند و دسترسی درستی به سرویس دارند، بتونن استفاده کنن. این قضیه در API های تجاری (یا Business facing) خودشون رو بیشتر و بهتر نشون میدن.
حالا سوال مهمتر …
احراز هویت JWT چیه؟
در اپهای قدیمی (مثل همین وردپرس)، احراز هویت توسط cookie ها انجام میشه. به چه صورتی؟ به این صورت که وقتی نام کاربری و گذرواژه وارد میکنیم، نرمافزار فضایی رو در مرورگر به خودش اختصاص میده و یک سری اطلاعات رو با خودش جابجا میکنه. اما در ReST API ها این قضیه رو نداریم. در واقع نمیتونیم به کوکیها اعتماد کنیم. پس چه میکنیم؟ اینجا لازمه جز یوزرنیم(که معمولا میتونه عمومی باشه) و پسورد (که میتونه راحت لو بره) میاییم و یک «توکن» هم تعریف میکنیم. این توکن، میتونه ثابت یا متغیر باشه. یعنی چی؟ یعنی میتونه به ازای هربار ورود تغییر کنه، میتونه سر زمان مشخصی هم منقضی بشه.
حالا توکن چیه؟ توکن به صورت کلی، در کازینوها معادل پولیه که شما در بازیها قرار میدید، در واقع مجوز حضور شما در اون کازینو، کلاب و … است. حتی به رمزارزهایی که بر مبنای دیگر رمزارزها ساخته میشن هم سکه نمیگن بلکه میگن توکن. شما فرض کنید که مثلا ۱۰۰ دلار میدید و پنج تا سکه با آرم اون کازینوی خاص دریافت میکنید. اگر در بازی برنده بشید یا ببازید، باید توکنهاتون رو تحویل بدید یا بگیرید.
حالا در احراز هویت JWT هم، ما به ازای کاربرمون یک توکن در نظر میگیریم. این توکن، معمولا یک رشته طولانیه که انسان نمیتونه بخوندش. نتیجتا خیلی از اطلاعات ما به صورت ایمنتر میتونن رد و بدل بشن (طبیعیه که مواردی مثل SSL داشتن و الگوریتمهایی که در ساخت توکن داشتیم هم مهمن). ضمن این که نامکاربری، ایمیل، رمزعبور و .. هم به همین سادگیا نمیتونن خونده بشن.
پس ما میآییم و یک دیتابیسی از توکنها در کنار دیتابیسی از یوزرها میسازیم (البته درستترش، جدوله!) و به ازای هر یوزر معمولا دوتا توکن در اون دیتابیس قرار میدیم. یکیش رو بهش میگیم «توکن دسترسی» یا Access Token و یکی رو میگیم «توکن بازنشانی» یا Refresh Token. توکن دسترسی، معمولا یک تاریخ انقضایی داره و بعد از اون با استفاده از توکن بازنشانی، میتونیم یکی جدید بگیریم. اما در آموزش امروز، صرفا میخوایم توکن دسترسی رو به دست بیاریم.
احراز هویت 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
خب پس چی میمونه که انجام ندادیم؟ یک سری آزمایش ساده 🙂
ساخت کاربر
خب الان کافیه بعد اجرای سرور ریلز (مطابق آموزشهای قبلی)، این دستور رو اجرا کنیم:
که اینها سرایند (Header) های ما هستند. در این قسمت، هرچی جلوی Authorization قرار داره توکن ماست. و میتونیم ازش استفاده کنیم.
بخش بعدی هم اینه :
{"message":"You are logged in."}
که صرفا به ما میگه ورودمون موفقیتآمیز بوده.
فکر کنم این مطلب، آخرین مطلبی بود که در مورد بیسیکهای روبیآنریلز مینوشتم. احتمالا در آینده نهچندان دور، همه اینها رو با هم به یک سری آموزش ویدئویی تبدیل کنم و از طریق آپارات یا یوتوب منتشرشون کنم.
طبیعتا یک قسمتهایی از آموزش در این مطلب پوشش داده نشده، سعی میکنم در آینده یک یا دو پست تکمیلی هم ارائه کنم که همه این قضایا به خوبی پوشش داده بشه (یا این که کلا در بخش ویدئویی در خدمتتون باشم).
به صورت کلی، دوست دارم بدونم نظر شما در مورد این تیپ آموزشها چیه؟ آیا ادامهشون بدم یا خیر؟ و این که آیا پایهش هستید که بحث فرانتند رو هم شروع کنیم یا روی همین بکند باقی بمونیم و اول یه پروژه کامل رو بکندش رو بزنیم و بعد بریم سراغ فرانت؟ 🙂
در آخر هم بابت وقتی که گذاشتید و مطلب رو خوندید ازتون متشکرم.
در پست قبلی با هم یک 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 اشاره میکنه.
در مطالب بعدی، میخواهیم بریم سراغ یک سری مفهوم و پیادهسازی دیگر. احتمال قوی هم بریم سراغ فرانتند و ببینیم که در دنیای فرانتند چه خبره. پس منتظر باشید که فصل جدیدی از مطالب فنی در راهه 🙂
در آخر، از شما بابت وقتی که برای خوندن این مطلب گذاشتید هم کمال تشکر رو دارم.
پس از مدتی دست کشیدن از بلاگ نوشتن، به دنیای نویسندگی فنی برگشتم و قراره که امروز با هم یاد بگیریم چطور یک 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 چک میشه) خود ریلز به ما ارور مناسب (معمولا ۴۰۴) رو نشون میده.
من در سیستم خودم، یک پست با آیدی ۲ دارم که قرار بود آپدیت بشه. الان اون رو با این دستور آپدیت میکنم:
خب، در نهایت فایل 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 کافیه. از شما هم میخوام که همین سناریو رو برای خودتون، یک دور به صورت کاملا دستی پیاده کنید و سعی کنید درش خلاقیت به خرج بدید. تجربیاتتون هم در بخش نظرات با من به اشتراک بذارید 🙂
قسمت بعدی، چی یاد میگیریم؟
در قسمت بعدی، کاری میکنیم کارستان. یاد میگیریم که در مدلها روابطشون رو تعیین کنیم. بعد مدلی میسازیم که به این مدل وابستهست. در نهایت هم به همین شکل، کنترلرش رو میسازیم و پروژه رو تحویل کارفرمای عصبانی میدیم 🙂
از این که وقت گذاشتید و این پست رو خوندید، واقعا ممنونم. هر یک ویویی که این پستها میخوره، برای من شدیدا ارزشمنده.
در پست قبلی، بررسی کردیم که اصلا 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
و خب وارد جزییات نمیشم، میتونیم جزییات مدل رو در فایلهای مربوطه بررسی کنیم.
بعد برای این مدل به این شکل میتونیم کنترلر بسازیم:
البته لازم به ذکره که باید دقت داشته باشید همیشه کنترلرها از جنس resource و CRUD نیستن و این دو راهکار، در واقع برای وقتی خوبن که شما نیازمند CRUD باشید و نخواهید هزینه ساخت کنترلر «از بیخ» رو متحمل بشید.
جمعبندی مطالب
در دو مطلب، هم تعاریف مرتبط با MVC رو یاد گرفتیم، هم یه نیمچه اپ 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 در پایتون.
حالا اگر با مرورگر به localhost:3000/posts بریم این صحنه زشت رو میبینیم 😁
و اگر روی لینکها کلیک کنیم، وارد پستها میشیم:
البته یادتون باشه slug معمولا برای url کاربرد داره ولی چون اینجا قصد آموزش ریلز نبود، از این ماجرا صرف نظر کردیم.
خب، حالا بعد یک مطلب طولانی، میدونیم که چی به چیه؛ بیاید با هم جمعبندی کنیم.
جمعبندی مطلب
اول از همه در این مطلب به یک تعریفی برای MVC رسیدیم. در واقع، کار ما بعنوان یک مهندس نرمافزار، تبدیل مفاهیم و موجودیتهای یک بیزنس و روابطشون با هم به یک سری مدل، کنترلر و نحوه نمایششون به کاربر در فرم ویو خواهد بود. حالا که یک تعریفی از MVC داریم، به نظرم بد نیست که بیایم هر مورد هم یک بار دیگر، بررسی کنیم.
مدل : مدلها، تعاریف انتزاعی از موجودیتها، ویژگیها و روابط بینشون هستند. به عبارتی، شکلی که ما قراره داده خام رو پردازش کنیم، در مدل تعیین میشه. کمی راحتترش کنم؟ مدل در واقع ساختار دیتابیس ماست.
کنترلر : کنترلر وظیفه ساخت یک نمونه از مدل، استخراج داده از دیتابیس، بروزرسانی داده در دیتابیس و حذف یک رکورد از دیتابیس رو عهده داره. این هم اگر بخوام راحت توصیف کنم، میگم ابزاری که باهاش دیتابیس رو به شکل سادهتری کنترل میکنیم.
ویو : ویو، نتیجه یک کوئری رو به صورت قابل خواندن توسط انسان، به ما نمایش میده. نتیجه کوئری معمولا به صورت یک جدول در دیتابیس (در واقع یک یا چند سطرش) یا به صورت JSON برمیگرده که توسط انسان قابل خوندن نیست. ما در ویو (و البته اغلب فرانت) این اتفاق رو رقم میزنیم.
البته موارد دیگری هم هستند که دیگه واقعا از حوصله این مطلب خارج شدند. دلیل عمدهش؟ طولانی شدن بیش از حد این مطلب. وگرنه دوست داشتم از معایب و مزایای MVC هم حرف بزنم. یک اتفاق بامزه هم این وسط افتاد که من حتی مجبور شدم لپتاپ رو ریبوت کنم و شانس آوردم که دو سه ساعت فکر و نوشتن، نپریده بود 😂
در پایان، از همه شمایی که این مطلب رو خوندید تشکر میکنم که وقت گذاشتید بابت خوندن مطلب. ضمنا، این اتفاقات در فرمورکی که شما استفاده میکنید چطوری رقم میخوره؟ در نظرات منتظرتونم 🙂
مدتی پیش، خواستم یک نمونه شخصی از ماستودون بسازم. موقع نصب، ارورهای عجیب و غریب دریافت کردم و خب شروع کردم به جست و جو پیرامون این که چرا اینطوریه و خیلیها واضحا بیان کردند که «مشکل از تصویر داکر 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 رو رد کردند و امیدم رو به طور کلی به این پروژه برای اضافه کردن این دیتابیس، از دست دادم.
یک توصیه نهایی بخوام به دوستان توسعهدهنده کنم اینه که : سعی کنید حداقل در زمینه دیتابیس، وابستگی رو به حداقل خودش برسونید. اول از همه با این فرض که ممکنه هرلحظه شما به هردلیلی بخواید سیستم رو عوض کنید. پس هرقدر وابستگی کمرنگتر باشه، بیشتر به نفع خودتونه.
مورد بعدی هم اینه که اگر نفر بعدی به هر دلیلی خواست با سیستم متفاوتی پروژه رو اجرا کنه، انقدر سختش نباشه. بهرحال، همه قرار نیست همه چیز رو اونطوری که من و شما دوست داریم، استفاده کنن.
مصادف با عید قربان امسال، پروژه API ترجمه فارسی قرآن کریم رو منتشر کردم. این پروژه، پروژه سختی برای من نبود اما چیزای جالبی ازش یاد گرفتم که خب بد نیست در موردش مطلبی بنویسم. اما قبل از اون، لازمه یک چیزی رو توضیح بدم …
چه شد که این پروژه رو شروع کردم؟
حقیقتش شروع این پروژه به این دلیل بود که شدیدا دنبال ابزار مشابهی میگشتم. با این که بعد از این که این پروژه رو دپلوی کردم، یک دوستی در توییتر به من پیشنهاد استفاده از این ابزار رو داد؛ اما بهرحال من ابزار رو توسعه داده بودم و دپلوی هم کرده بودم.
خلاصه که، شروع این پروژه برای من جالب بود. چرا که پروژه به خودی خود سخت نبود اما وسطاش، دقیقا جایی که داشتم فرانتند براش میساختم، چالشی شد. به همین خاطر تصمیم گرفتم که این مطلب رو بنویسم و توضیح بدم که چرا این چالش برای من پیش اومد 🙂
مفهوم تازه : CORS
خب، وقتی که ما بکند و فرانتند پروژه رو از هم جدا میکنیم، در واقع داریم دوتا origin مختلف رو به هم وصل میکنیم. طبق استانداردهای وب؛ اصولا چنین کاری در مرورگرها؛ بخاطر مسائل امنیتی مجاز دونسته نشده. به همین خاطر، ما مفهوم تازهای به اسم «چند ریشهای» یا Cross-Origin رو خواهیم داشت.
حقیقتا وقتی فرانتند رو با جاوااسکریپت مینوشتم؛ دیدم که یک سری ارور بهم میده و توی اون ارورها بهم میگه که بکند من، برای «اشتراک گذاری منابع چندریشهای» یا Cross-Origin Resource Sharing تهیه نشده. دوست داشتم از خود کد جاوااسکریپتم درستش کنم اما چون قرار بود روی هاست همین وبلاگ قرار بگیره و نه سرور مجزایی؛ ترجیح دادم که به جای node.js از وانیلا جی اس استفاده کنم (توضیح : وانیلا اصطلاحا به نرم افزار یا ابزار دست نخورده گفته میشه) و خب راهکاری براش پیدا نکردم (شاید هم چون درست دنبالش نگشتم) ولی از اونجایی که بکند رو با روبی و سیناترا نوشته بودم، براش راهکار پیدا کردم و این راهکار رو در یک ویرگول به اشتراک گذاشتم (لینک).
همین یک کار کوچک، باعث شد که من مفهوم کورز و صد البته نحوه کانفیگ درستش در بکند رو یاد بگیرم، از این جهت هم واقعا خوشحالم چرا که اکثر توسعه دهنده های فرانت، مدام از این قضیه که در بکند این مشکل حل نمیشه، نالانند ((:
چیزی که سالها عقبش انداخته بودم : زرین پال!
تقریبا از وقتی که فهمیدم اینترنت چیه، دنبال راهی برای کسب درآمد ازش بودم. فکر کنم دوم سوم دبیرستان بودم که سایت زرین پال به وجود اومد و اون موقع (چون به سن قانونی نرسیده بودم) نمیتونستم ازش استفاده کنم؛ یا این که باید از کسی میخواستم که قبول کنه به اسمش اکانت باز کنم (مثلا پدر یا مادرم).
از وقتی به سن قانونی رسیدم هم هرروز میخواستم برم و اکانت زرین پال درست کنم تا ازش برای حمایت و فروش و … روی اینترنت استفاده کنم. خلاصه که بالاخره همزمان با ریلیز این پروژه، این اتفاق هم افتاد ((: تازه دقیقتر بخواهیم حساب کنیم، برای این مساله هم مجبور شدم کارت بانکیم رو تمدید کنم و هم برای کارت هوشمند ملی اقدام کنم!
به طور خلاصه، این پروژه پروژه جالبی برام بود. هم چالشاش هم ساختارش و هم اتفاقات پیرامونش. ضمن این که به خود پروژه هم ابتدای مطلب لینک دادم و امیدوارم بخونید و خوشتون بیاد. اگر هم از پروژه قراره استفادهای کنید، حمایت هم بکنید ممنون میشم (:
لیست پیوندی، یکی از ساختمان داده هایی هست که معمولا توی درس ساختمان داده درس داده میشه (البته در مورد کاربردش در زندگی واقعی چیزی نمیدونم، ممنون میشم بهم بگید) و خب معمولا سر کلاس، توی زبانهایی مثل C یا ++C درس میدنش. اما من داشتم توی نت میگشتم و به این مقاله رسیدم. که این مقاله، توضیح داده چطور میشه توی روبی این ساختمان داده رو پیاده کرد. خب به صورت ساده میریم سراغ پیاده سازی و کم کم پیچیدش میکنیم.
class Node
attr_accessor :node, :nextdef initialize(node)@node = node
endend
خب تا اینجا، عملکرد ساده لیست پیوندی رو داریم. همون node و next که next معمولا از جنس اشاره گره. خب ما قاعدتا یک متد دیگری هم نیاز داریم. متدی که نیاز داریم، متدیه که بهمون بگه چیا توی لیستمون ذخیره کردیم. اصولا یکی از مهم ترین متد هاییه که میتونیم توی این کلاس، اضافه کنیم. متد رو به این شکل مینویسیم :
بسیار خوب! حالا یک متدی مینویسیم که این لیست رو برای ما، برعکس کنه. گرچه چنین متدی نیاز نیست، اما چون توی روبی از این متد برای هش ها و لیست ها (آرایه ها) استفاده شده، بهتره ما هم به لینک لیستمون اضافش کنیم. خب یک متد هم به اسم Reverse ایجاد میکنیم به این شکل:
defself.reverse(node)return node if node.next.nil?
head, swap, node.next = node.next, node, nil
link = head.nextwhile link != nil
head.next = swap
swap = head
head = link
link = link.nextend
head.next = swap
head
end
حالا میتونیم با استفاده از این کلاس، از لیست های پیوندی استفاده کنیم. البته دقت کنید که ما در اینجا در مورد حذف و اضافه کردن Node ها حرفی نزدیم. بلکه صرفا نمایش و معکوس کردن لیست رو بررسی کردیم. امیدوارم کد به کمکتون اومده باشه :).
خب، اول از همه بگم که نمیدونستم چه عنوانی رو باید انتخاب میکردم، و طبق فرمول «احمقانه ترین ایده ممکن» این عنوان رو روی پست گذاشتم. بگذریم، توی این پست میخوام یه پدیده جدید و خیلی خوب رو معرفی کنم. میدونید که من مدت زیادی هست که روبی کار شدم ( و حتی پایگاهی برای توسعه دهندگان روبی ایجاد کردم) روبی زبان خیلی خوبیه ولی خب قاعدتا نمیشه به نیتیو کد تبدیلش کرد. همونطور که پایتون و … نمیشه نیتیو کد کامپایل کرد. اما با یه چیزی رو به رو شدم که دقیقا خود روبی بود، ولی نیتیو کد میشد. من که بهش سلام کردم، شما هم بهش سلام کنید. زبان برنامه نویسی کریستال یک زبان ruby – inspired هست که کاملا کامپایلری عمل میکنه. سینتکس کاملا سینتکس روبی هست و از این بابت خیالتون راحت باشه که اگر روبی بلدید، یاد گیری این زبان براتون چند دقیقه بیشتر زمان نخواهد برد. اجازه بدید چند مثال رو بررسی کنیم.
class Greeter
def initialize(name)@name = name
enddef say_hi
puts"Hello, I am #{@name}"endend
کد ها رو دیدید؟ خیلی خوب! اگر دوست دارید که این ها رو به نیتیو کد تبدیل کنید همین الان به وبسایت کریستال (که ابتدای پست لینک دادم) مراجعه کنید و شروع به خوندن داکیومنت هاش کنید. مطمئنا کوچکترین تفاوتی در ظاهر با روبی نداره و از این جهت، روبیست ها میتونن به سادگی یادش بگیرند.
وبلاگ شخصی محمدرضا حقیری، برنامهنویس، گیک و یک شخص خوشحال