یادداشت های مهاجرت از Rails به Django

آخرین امار بین جنگو و ریلز کمی پیشتر دنیای روبی/ریلز و پایتون/جنگو دو دنیای موازی و تقریبا به یک اندازه محبوب بود اما این محبوبیت کم کم به سمت جنگو و پایتون سوق پیدا کرد. هرچند این روزها هم با وجود محبوب تر بودن جنگو، دنیای ریلز هنوز پر ماجرا و داغ است. در این میان من نیز شانس این را داشتم که هم در تیم فنی قوی تخفیفان برای حدود دو سال با Ruby On Rails کار کنم و هم در تیم فوق العاده ی فرمالو و در کنار دوست خوبم حسن به مدت حدود ۹ ماه ( تا لحظه ی نوشتن این مقاله) با جنگو دست و پنچه نرم کنم. در ادامه میخواهم آنچه از تفاوت ها و شباهت های جنگو و ریلز برای خودم مهم بوده و یادداشت برداشته ام را، به صورت فنی و متخصر شرح دهم تا شاید برای کسانی که تجربه ی مشابهی مثل من خواهند داشت مفید باشد در نهایت هم تجربه ی خودم را از کار کردن با هر دوی این وب اپ ها با شما به اشتراک خواهم گذاشت.1- Ruby vs Python هر بار کسی در مورد تفاوت خود زبان پایتون و روبی از من سوالی می پرسد، به یاد حرف آنکل باب می افتم که معتقد این دو زبان تقریبا یکی هستند. حقیقتا اگر به طور کلی به این دو زبان نگاه کنیم شباهت های بسیار زیادی دیده می شود و به نظرم یک روبی کار با چند ساعت مطالعه با پایتون آشنا و آماده کار می شود اما شاید بزرگترین تفاوت برای من اول مسیله اجبار استفاده از پرانتز و دیگری مسیله مپ کردن بود. برای مثال این نمون کد زیر که قرار است تمام عناصر یک لیست را با حروف بزرگ بنویسم در روبی:[&quotDave&quot, &quothorse&quot, &quotFOO&quot].map(&:upcase)و در پایتون:[x.upper() for x in [&quotDave&quot, &quothorse&quot, &quotFOO&quot]]2- Rack vs WSGIبه نظرم درین مورد یادداشت به تنهایی گویا ماجرا هست. # WSGI def app(request): return { &quotstatus&quot: 200, &quotheaders&quot: {&quotcontent_type&quot: &quottext/plain&quot}, &quotbody&quot: &quotHello World&quot} # Rack app = proc do |env| [ 200, {'Content-Type' => 'text/plain'}, &quotHello World&quot ] end3- Framework Architectureریلز ساختار MVC دارد این در حالیست که جنگو از ساختار MVT پیروی می کند. بزرگترین تفاوت این دو ساختار تا آنجایی که من فهمیدم در بخش مدیریت تعامل بین مدل و تپملیت هاست. اما به طور کلی میتوان این تفاوت های ساختاری را در جنگو (سمت راست ) و ریلز (سمت چپ ) دید:routes.rb == urls.pyauthors_controller.rb == authors/views.pymodels/Author.rb == authors/models.py4- Commandsبه طور کلی دستور Rails را می توان معادل دستور Python manage.py در جنگو دانست برای مثال:Python manage.py == Rails Rails console == Python manage.py shell و همینطور از آنجایی که ریلز از مفهوم کلاس و آبجکت برای به اشتراک گذاشتن کد استفاده می کند ولی در مقابل آن جنگو از متود ها برای به اشتراک گذاشتن کد استفاده می کند در نتیجه:require == from * import * #Rails require 'render' #Django from django.shortcuts import render5- Forms vs form_for شاید یکی از برتری های جنگو نسبت به ریلز که البته در خیلی از جم های موجود برای ریلز رفع شده وجود مفهوم Form باشد. این مفهوم در واقع جدا سازی منطق رندر فرم از تمپلیت است. مثلا برای رسم یک فرم در ریلز از کد زیر در تمپلیت استفاده می شود:<%= form_for(@post) do |f| %> <%= @post.errors[:base].full_messages %> <%= f.text_field :title %> <%= @post.errors[:title].full_messages %> <%= f.text_area :body %> <%= @post.errors[:body].full_messages %> <% end %>این در حالیست که این منطق در جنگو به صورت یک کد جدا در نظر گرفته شده:# The form, with a custom validation class PostForm(forms.ModelForm): class Meta: model = Post fields = ['title', 'body'] def clean(self): cleaned_data = super().clean() body = cleaned_data.get(&quotbody&quot) title = cleaned_data.get(&quottags&quot) if not title in body: self.add_error(None, &quotBody must contain title&quot)که این مسیله باعث شده کد های تمپلیت در جنگو بسیار تمیز تر باشند. هر چند همانطور که گفتم جم های بسیاری این موشکل ریلز هرا حل کرده اند. برای مثال جم معروف و TrailBlazer را برای مطالعه بیشتر معرفی میکنم.6- Model Manager vs Model ActiveRecordشاید یکی از سخت ترین تغییرات برای من درک مفهموم Manager ها در جنگو بود. در واقع این مورد به حدی گسترده است که برای من امکان شرح کامل تمام موارد در این مقاله وجود ندارد. برای همین سعی می کنم به مواری که برخوردم و یادداشت های مرتبط با آن بپردازم.برای شروع یک مدل ساده در هر دو زبان را باهم ببینیم:# Django # app1/leads/models.py class Lead(models.Model): name = models.CharField(max_length=255) credit = models.models.DecimalField() email = models.EmailField() phone_number = models.CharField(max_length=255, blank=True) lead_owner = models.ForeignKey(LeadOwner) # Rails # models/lead.rb class Lead < ApplicationRecord validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } belongs_to :lead_owner # db/migrate/2018..._create_lead.rb class CreateLead < ActiveRecord::Migration[5.1] def change create_table :lead do |t| t.string :name f.number_field :credit t.string :email t.string :phone_number, null: false t.references :lead_owner, foreign_key: true end end endهمانطور که متوجه شدید مدل ها در جنگو شامل اطلاعات فیلد ها می شوند و برای ساختن میگرشن باید دستوری جداگانه اجرا شود و بعد از آن با دستوری دیگر میگرشن ها واقعا روی دیتا بیس اعمال می شوند این در حالیست که در ریلز این دو منطق جدا از هم هستند. هر چند شاید کمی گیج کننده باشند اما همین ویژگی میگرشن را در Rails بسیار راحت تر کرده چیزی که به نظر من در جنگو بسیار ضعیف تر و با مشکلات بیشتری رو به روست. برای مثال اگر بخواهیم یک دستور دیتا بیسی یا یک کد در هنگام اجرای میگرشن ها اجرا شود:#Django from django.db import migrations def forwards(apps, schema_editor): if schema_editor.connection.alias != 'default': return # Your migration code goes here even SQL << class Migration(migrations.Migration): dependencies = [ # Dependencies to other migrations ] operations = [ migrations.RunPython(forwards), ] #Rails class ExampleMigration < ActiveRecord::Migration def change # Your migration code goes here even SQL << endهمانطور که می بینید این کار در ریلز به مراتب راحت تر از جنگو است. حالا فرض کنیم میخواهیم آبکت آخر این مدل را فراخوانی کنیم یا روی آنها فیلتری انجام دهیم و در نهایت جمع یک ستون را حساب کنیم:#DjangoLead.objects.last() Lead.objects.filter(name__startswith=&quotFoo&quot) Model.objects.all().aggregate(Sum(&quotnum_field&quot))# RailsLead.last Lead.where(&quotname LIKE 'Foo%'&quot) Lead.sum(&quotnum_field&quot)7- Scopes vs Custom Model Managerفرض کنیم ما نیاز داریم یک متود جدید در مدل داشته باشیم که یک کویری را بر روی مدل ما اجرا می کند مثلا with_counts در جنگو باید برای این کار باید خطوط زیر به کد اضافه شود:from django.db import models class PollManager(models.Manager): def with_counts(self): from django.db import connection with connection.cursor() as cursor: cursor.execute(&quot&quot&quot SELECT p.id, p.question, p.poll_date, COUNT(*) FROM polls_opinionpoll p, polls_response r WHERE p.id = r.poll_id GROUP BY p.id, p.question, p.poll_date ORDER BY p.poll_date DESC&quot&quot&quot) result_list = [] for row in cursor.fetchall(): p = self.model(id=row[0], question=row[1], poll_date=row[2]) p.num_responses = row[3] result_list.append(p) return result_list class OpinionPoll(models.Model): ... objects = PollManager()ولی مورد مشابه در ریلز نه تنها تعداد خطوط کد کمتر است بلکه ریلز قابلیت chain شدن این دستور را به ما می دهد چزیی که در جنگو اوجود ندارد.scope :with_counts, -> () { find_by_sql(&quotSELECT p.id, p.question, p.poll_date, COUNT(*) FROM polls_opinionpoll p, polls_response r WHERE p.id = r.poll_id GROUP BY p.id, p.question, p.poll_date ORDER BY p.poll_date DESC&quot) }8- Mixin vs abstract class!یکی دیگر از موارد بارز احتلاق در جنگو عدم وجود interface یا ماژول بود. به طور کلی اگر شما message های مشترک بین چند entity دارید باید آن ها را به صورت abstract_class ایجاد کنید و هر آبجکت هم در نتیجه ی این کار می تواند از چند آبجکت پدر ارث بری کرده باشد. آبجکت ها به ترتیب از سمت چپ بر آبجکت اصلی ارث بری شده اعمال می شوند. این در حالیست که در بسیاری از دیگر زبان ها مفهوم هایی از قبیل inerface یا module وجود دارد که بدون اارث بری امکان به اشتراک گذاشتن message ها را فراهم میکند9- Testsشاید در ابتدا که هنوز با مفاهیم تست در پایتون آشنا نبودم به نظرم جنگو محیط مناسبی برای تست نیامد اما به مرور زمان متوجه اشتراک زیاد تست در هر ود محیط شدم. برای مثال مفاهیم کا کردن .و استفااده از فکتوری ها به جای استفاده مستقیم از DB و غیره.10 - Conclusionدر نهایت به نظر من که البته قطعا نظر کاملی نیست و خوحشال می شوم نظرات شما را در کامنت بخوانم، هر دوی این وب اپلیکشن ها خوبی و بدی های خودشان را دارند اما جنگو میتواند به راحتی در تله ی کد قابل اجرا و غیر قابل تغییر گرفتار شود. به نظرم ساختارهای مورد استفاده در جنگو بسیار قدیمی شده و در عین حال بدنه ی بزرگ جنگو اجازه ی تغییرات بنیادین مثل مواردی که در بستر ریلز مشاهده می کنیم را نمی دهد. به نظرم شاید پایتون با فلسک آینده ی بهتری داشته باشد هرچند که شاید کامیونیتی پشت جنگو مثل php همچنان علاقه مند به رشد این غول بزرگ باشند .به هر حال چه ریلز یا روبی و چه جنگو یا پایتون هر دو کامل ،قدرتمند و توانمند در اجرای پروژه های بزرگ هستند . همانطور که هم اکنون شرکت های بزرگی در حال استفاده از این وب اپلیکشین ها می باشند.راستی اگه دوست داشتین این مقاله رو به انگلیسی هم اینجا نوشتم، در ضمن اونجا چند تا مورد بیشتر در باره ی مثال شماره ۹ زدم که به نظرم میتونه خیلی مفید باشه. منابع:https://www.rubypigeon.com/posts/forms-comparing-django-to-rails/https://medium.com/@wasabigeek/from-django-to-rails-models-and-migrations-4fcbf89265a9https://medium.com/@carlosalmonte04/intro-to-django-for-rails-developers-1866dabe82c3https://medium.com/@yeraydiazdiaz/django-rails-cheat-sheet-50adf2441913https://news.ycombinator.com/item?id=2810424https://docs.djangoproject.com/en/dev/topics/db/managers/#modifying-initial-manager-querysetshttps://docs.djangoproject.com/en/3.0/topics/db/managers/https://docs.djangoproject.com/en/3.1/howto/writing-migrations/http://blog.cynthiakiser.com/blog/2014/01/06/django-for-rails-developers/https://api.rubyonrails.org/classes/ActiveRecord/Calculations.htmlhttps://sudoseed.wordpress.com/2017/06/30/web-framework-wars-django-vs-ruby-on-rails/