بقلم بوزيدار باتسوف
نماذج القدوة مهمة.
- الضابط Alex J. Murphy / RoboCop
نصيحة | يمكنك العثور على نسخة جميلة من هذا الدليل مع الكثير من التنقل المحسّن على https://rails.rubystyle.guide. |
الهدف من هذا الدليل هو تقديم مجموعة من أفضل الممارسات ووصفات الأسلوب لتطوير Ruby on Rails. إنه دليل تكميلي لدليل أسلوب برمجة روبي الموجود بالفعل والموجه من قبل المجتمع.
يوصي دليل أسلوب Rails هذا بأفضل الممارسات حتى يتمكن مبرمجو Rails في العالم الحقيقي من كتابة تعليمات برمجية يمكن صيانتها بواسطة مبرمجي Rails الآخرين في العالم الحقيقي. يتم استخدام دليل الأسلوب الذي يعكس الاستخدام في العالم الحقيقي، ودليل الأسلوب الذي يتمسك بالمثل الأعلى الذي تم رفضه من قبل الأشخاص، ومن المفترض أن يساعد في المخاطرة بعدم التعود على الإطلاق - بغض النظر عن مدى جودته.
يتم فصل الدليل إلى عدة أقسام من القواعد ذات الصلة. لقد حاولت إضافة الأساس المنطقي وراء القواعد (إذا تم حذفه فقد افترضت أنه واضح جدًا).
لم أتوصل إلى جميع القواعد من العدم - فهي تعتمد في الغالب على مسيرتي المهنية الواسعة كمهندس برمجيات محترف، وملاحظات واقتراحات من أعضاء مجتمع ريلز والعديد من موارد برمجة ريلز المرموقة.
ملحوظة | بعض النصائح هنا تنطبق فقط على الإصدارات الحديثة من Rails. |
يمكنك إنشاء نسخة PDF من هذا الدليل باستخدام AsciiDoctor PDF، ونسخة HTML باستخدام AsciiDoctor باستخدام الأوامر التالية:
# Generates README.pdf
asciidoctor-pdf -a allow-uri-read README.adoc
# Generates README.html
asciidoctor README.adoc
نصيحة | قم بتثبيت الجوهرة gem install rouge |
تتوفر ترجمات الدليل باللغات التالية:
اليابانية
الروسية
نصيحة | يحتوي RuboCop، وهو محلل أكواد ثابتة (inter) ومنسق، على امتداد rubocop-rails ، استنادًا إلى دليل النمط هذا. |
ضع رمز التهيئة المخصص في config/initializers
. يتم تنفيذ التعليمات البرمجية الموجودة في أدوات التهيئة عند بدء تشغيل التطبيق.
احتفظ برمز التهيئة لكل جوهرة في ملف منفصل يحمل نفس اسم الجوهرة، على سبيل المثال carrierwave.rb
و active_admin.rb
وما إلى ذلك.
اضبط إعدادات بيئة التطوير والاختبار والإنتاج وفقًا لذلك (في الملفات المقابلة ضمن config/environments/
)
وضع علامة على الأصول الإضافية للتجميع المسبق (إن وجدت):
# config/environments/production.rb
# Precompile additional assets (application.js, application.css,
#and all non-JS/CSS are already added)
config . assets . precompile += %w( rails_admin/rails_admin.css rails_admin/rails_admin.js )
احتفظ بالتكوين الذي ينطبق على جميع البيئات في ملف config/application.rb
.
عند الترقية إلى إصدار أحدث من Rails، سيظل إعداد تكوين التطبيق الخاص بك على الإصدار السابق. للاستفادة من أحدث ممارسات Rails الموصى بها، يجب أن يتطابق الإعداد config.load_defaults
مع إصدار Rails الخاص بك.
# good
config . load_defaults 6.1
تجنب إنشاء تكوينات بيئة إضافية غير الإعدادات الافتراضية development
test
production
. إذا كنت بحاجة إلى بيئة شبيهة بالإنتاج مثل التدريج، فاستخدم متغيرات البيئة لخيارات التكوين.
احتفظ بأي تكوين إضافي في ملفات YAML ضمن دليل config/
.
نظرًا لأنه يمكن تحميل ملفات تكوين Rails 4.2 YAML بسهولة باستخدام التابع config_for
الجديد:
Rails :: Application . config_for ( :yaml_file )
عندما تحتاج إلى إضافة المزيد من الإجراءات إلى مورد RESTful (هل تحتاج إليها حقًا على الإطلاق؟) استخدم مسارات member
collection
.
# bad
get 'subscriptions/:id/unsubscribe'
resources :subscriptions
# good
resources :subscriptions do
get 'unsubscribe' , on : :member
end
# bad
get 'photos/search'
resources :photos
# good
resources :photos do
get 'search' , on : :collection
end
إذا كنت بحاجة إلى تحديد مسارات member/collection
متعددة، فاستخدم صيغة الكتلة البديلة.
resources :subscriptions do
member do
get 'unsubscribe'
# more routes
end
end
resources :photos do
collection do
get 'search'
# more routes
end
end
استخدم المسارات المتداخلة للتعبير بشكل أفضل عن العلاقة بين نماذج Active Record.
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post
end
# routes.rb
resources :posts do
resources :comments
end
إذا كنت بحاجة إلى تداخل المسارات بعمق يزيد عن مستوى واحد، فاستخدم الخيار shallow: true
. سيؤدي هذا إلى توفير المستخدم من posts/1/comments/5/versions/7/edit
كما يوفر لك من مساعدي عناوين URL الطويلة edit_post_comment_version
.
resources :posts , shallow : true do
resources :comments do
resources :versions
end
end
استخدم المسارات ذات مساحة الاسم لتجميع الإجراءات ذات الصلة.
namespace :admin do
# Directs /admin/products/* to Admin::ProductsController
# (app/controllers/admin/products_controller.rb)
resources :products
end
لا تستخدم مطلقًا مسار وحدة التحكم البرية القديمة. سيجعل هذا المسار جميع الإجراءات في كل وحدة تحكم قابلة للوصول عبر طلبات GET.
# very bad
match ':controller(/:action(/:id(.:format)))'
لا تستخدم match
لتحديد أي مسارات ما لم تكن هناك حاجة لتعيين أنواع طلبات متعددة بين [:get, :post, :patch, :put, :delete]
لإجراء واحد باستخدام خيار :via
.
أبقِ وحدات التحكم نحيفة - يجب أن تقوم فقط باسترداد البيانات الخاصة بطبقة العرض ويجب ألا تحتوي على أي منطق أعمال (يجب أن يكون كل منطق الأعمال موجودًا بشكل طبيعي في النموذج).
يجب أن يستدعي كل إجراء تحكم (من الناحية المثالية) طريقة واحدة فقط بخلاف البحث الأولي أو الجديد.
قلل عدد متغيرات المثيلات التي تم تمريرها بين وحدة التحكم والعرض.
يجب أن تكون إجراءات وحدة التحكم المحددة في خيار عامل تصفية الإجراءات في النطاق المعجمي. عامل تصفية الإجراءات المحدد لإجراء موروث يجعل من الصعب فهم نطاق تأثيره على هذا الإجراء.
# bad
class UsersController < ApplicationController
before_action :require_login , only : :export
end
# good
class UsersController < ApplicationController
before_action :require_login , only : :export
def export
end
end
تفضل استخدام قالب بدلاً من العرض المضمن.
# very bad
class ProductsController < ApplicationController
def index
render inline : "<% products.each do |p| %><p><%= p.name %></p><% end %>" , type : :erb
end
end
# good
## app/views/products/index.html.erb
<%= render partial : 'product' , collection : products %>
## app/views/products/_product.html.erb
< p ><%= product . name %>< /p>
<p><%= product.price %></p >
## app/controllers/products_controller.rb
class ProductsController < ApplicationController
def index
render :index
end
end
تفضل render plain:
على render text:
.
# bad - sets MIME type to `text/html`
...
render text : 'Ruby!'
...
# bad - requires explicit MIME type declaration
...
render text : 'Ruby!' , content_type : 'text/plain'
...
# good - short and precise
...
render plain : 'Ruby!'
...
تفضل الرموز المقابلة لرموز حالة HTTP الرقمية. إنها ذات معنى ولا تبدو مثل أرقام "سحرية" لرموز حالة HTTP الأقل شهرة.
# bad
...
render status : 403
...
# good
...
render status : :forbidden
...
تقديم فئات نماذج غير Active Record بحرية.
قم بتسمية النماذج بأسماء ذات معنى (لكن قصيرة) بدون اختصارات.
إذا كنت بحاجة إلى كائنات تدعم السلوك المشابه لـ ActiveRecord (مثل عمليات التحقق من الصحة) بدون وظيفة قاعدة البيانات، فاستخدم ActiveModel::Model
.
class Message
include ActiveModel :: Model
attr_accessor :name , :email , :content , :priority
validates :name , presence : true
validates :email , format : { with : / A [-a-z0-9_+ . ]+ @ ([-a-z0-9]+ . )+[a-z0-9]{2,4} z /i }
validates :content , length : { maximum : 500 }
end
بدءًا من الإصدار 6.1 من Rails، يمكنك أيضًا توسيع واجهة برمجة تطبيقات السمات من ActiveRecord باستخدام ActiveModel::Attributes
.
class Message
include ActiveModel :: Model
include ActiveModel :: Attributes
attribute :name , :string
attribute :email , :string
attribute :content , :string
attribute :priority , :integer
validates :name , presence : true
validates :email , format : { with : / A [-a-z0-9_+ . ]+ @ ([-a-z0-9]+ . )+[a-z0-9]{2,4} z /i }
validates :content , length : { maximum : 500 }
end
ما لم يكن لها بعض المعنى في مجال الأعمال، فلا تضع أساليب في نموذجك تعمل فقط على تنسيق بياناتك (مثل إنشاء تعليمات برمجية بتنسيق HTML). من المرجح أن يتم استدعاء هذه الأساليب من طبقة العرض فقط، لذا فإن مكانها في المساعدين. احتفظ بنماذجك لمنطق الأعمال واستمرارية البيانات فقط.
تجنب تغيير إعدادات Active Record الافتراضية (أسماء الجداول والمفتاح الأساسي وما إلى ذلك) إلا إذا كان لديك سبب وجيه للغاية (مثل قاعدة بيانات ليست تحت سيطرتك).
# bad - don't do this if you can modify the schema
class Transaction < ApplicationRecord
self . table_name = 'order'
...
end
ignored_columns
تجنب تعيين ignored_columns
. قد يحل محل المهام السابقة وهذا خطأ دائمًا تقريبًا. يُفضل الإلحاق بالقائمة بدلاً من ذلك.
class Transaction < ApplicationRecord
# bad - it may overwrite previous assignments
self . ignored_columns = %i[ legacy ]
# good - the value is appended to the list
self . ignored_columns += %i[ legacy ]
...
end
تفضل استخدام صيغة التجزئة لـ enum
. يجعل المصفوفة قيم قاعدة البيانات ضمنية وأي إدراج/إزالة/إعادة ترتيب للقيم في المنتصف سيؤدي على الأرجح إلى كسر التعليمات البرمجية.
class Transaction < ApplicationRecord
# bad - implicit values - ordering matters
enum type : %i[ credit debit ]
# good - explicit values - ordering does not matter
enum type : {
credit : 0 ,
debit : 1
}
end
قم بتجميع أساليب نمط الماكرو ( has_many
، validates
، إلخ) في بداية تعريف الفئة.
class User < ApplicationRecord
# keep the default scope first (if any)
default_scope { where ( active : true ) }
# constants come up next
COLORS = %w( red green blue )
# afterwards we put attr related macros
attr_accessor :formatted_date_of_birth
attr_accessible :login , :first_name , :last_name , :email , :password
# Rails 4+ enums after attr macros
enum role : { user : 0 , moderator : 1 , admin : 2 }
# followed by association macros
belongs_to :country
has_many :authentications , dependent : :destroy
# and validation macros
validates :email , presence : true
validates :username , presence : true
validates :username , uniqueness : { case_sensitive : false }
validates :username , format : { with : / A [A-Za-z][A-Za-z0-9._-]{2,19} z / }
validates :password , format : { with : / A S {8,128} z / , allow_nil : true }
# next we have callbacks
before_save :cook
before_save :update_username_lower
# other macros (like devise's) should be placed after the callbacks
...
end
has_many :through
تفضل has_many :through
has_and_belongs_to_many
. يتيح استخدام has_many :through
سمات وعمليات تحقق إضافية في نموذج الانضمام.
# not so good - using has_and_belongs_to_many
class User < ApplicationRecord
has_and_belongs_to_many :groups
end
class Group < ApplicationRecord
has_and_belongs_to_many :users
end
# preferred way - using has_many :through
class User < ApplicationRecord
has_many :memberships
has_many :groups , through : :memberships
end
class Membership < ApplicationRecord
belongs_to :user
belongs_to :group
end
class Group < ApplicationRecord
has_many :memberships
has_many :users , through : :memberships
end
تفضل self[:attribute]
على read_attribute(:attribute)
.
# bad
def amount
read_attribute ( :amount ) * 100
end
# good
def amount
self [ :amount ] * 100
end
تفضل self[:attribute] = value
على write_attribute(:attribute, value)
.
# bad
def amount
write_attribute ( :amount , 100 )
end
# good
def amount
self [ :amount ] = 100
end
استخدم دائمًا عمليات التحقق من الصحة "النمط الجديد".
# bad
validates_presence_of :email
validates_length_of :email , maximum : 100
# good
validates :email , presence : true , length : { maximum : 100 }
عند تسمية طرق التحقق المخصصة، التزم بالقواعد البسيطة:
validate :method_name
يقرأ مثل بيان طبيعي
يوضح اسم الطريقة ما يتحقق منه
يمكن التعرف على هذه الطريقة باعتبارها طريقة التحقق من الصحة من خلال اسمها، وليست طريقة أصلية
# good
validate :expiration_date_cannot_be_in_the_past
validate :discount_cannot_be_greater_than_total_value
validate :ensure_same_topic_is_chosen
# also good - explicit prefix
validate :validate_birthday_in_past
validate :validate_sufficient_quantity
validate :must_have_owner_with_no_other_items
validate :must_have_shipping_units
# bad
validate :birthday_in_past
validate :owner_has_no_other_items
لتسهيل قراءة عمليات التحقق من الصحة، لا تقم بإدراج سمات متعددة لكل عملية تحقق.
# bad
validates :email , :password , presence : true
validates :email , length : { maximum : 100 }
# good
validates :email , presence : true , length : { maximum : 100 }
validates :password , presence : true
عند استخدام التحقق المخصص أكثر من مرة أو أن التحقق من الصحة عبارة عن تعيين تعبير عادي، قم بإنشاء ملف مدقق مخصص.
# bad
class Person
validates :email , format : { with : / A ([^@ s ]+)@((?:[-a-z0-9]+ . )+[a-z]{2,}) z /i }
end
# good
class EmailValidator < ActiveModel :: EachValidator
def validate_each ( record , attribute , value )
record . errors [ attribute ] << ( options [ :message ] || 'is not a valid email' ) unless value =~ / A ([^@ s ]+)@((?:[-a-z0-9]+ . )+[a-z]{2,}) z /i
end
end
class Person
validates :email , email : true
end
احتفظ بأدوات التحقق المخصصة ضمن app/validators
.
فكر في استخراج أدوات التحقق المخصصة إلى جوهرة مشتركة إذا كنت تحتفظ بالعديد من التطبيقات ذات الصلة أو كانت أدوات التحقق عامة بدرجة كافية.
استخدم النطاقات المسماة بحرية.
class User < ApplicationRecord
scope :active , -> { where ( active : true ) }
scope :inactive , -> { where ( active : false ) }
scope :with_orders , -> { joins ( :orders ) . select ( 'distinct(users.id)' ) }
end
عندما يصبح النطاق المسمى المحدد باستخدام lambda والمعلمات معقدًا للغاية، فمن الأفضل إنشاء أسلوب فئة بدلاً من ذلك يخدم نفس الغرض من النطاق المسمى ويعيد كائن ActiveRecord::Relation
. يمكن القول أنه يمكنك تحديد نطاقات أبسط مثل هذا.
class User < ApplicationRecord
def self . with_orders
joins ( :orders ) . select ( 'distinct(users.id)' )
end
end
ترتيب إعلانات رد الاتصال بالترتيب الذي سيتم تنفيذه به. كمرجع، راجع عمليات الاسترجاعات المتاحة.
# bad
class Person
after_commit :after_commit_callback
before_validation :before_validation_callback
end
# good
class Person
before_validation :before_validation_callback
after_commit :after_commit_callback
end
احذر من سلوك الطرق التالية. إنهم لا يقومون بعمليات التحقق من صحة النموذج ويمكن أن يفسدوا حالة النموذج بسهولة.
# bad
Article . first . decrement! ( :view_count )
DiscussionBoard . decrement_counter ( :post_count , 5 )
Article . first . increment! ( :view_count )
DiscussionBoard . increment_counter ( :post_count , 5 )
person . toggle :active
product . touch
Billing . update_all ( "category = 'authorized', author = 'David'" )
user . update_attribute ( :website , 'example.com' )
user . update_columns ( last_request_at : Time . current )
Post . update_counters 5 , comment_count : - 1 , action_count : 1
# good
user . update_attributes ( website : 'example.com' )
استخدم عناوين URL سهلة الاستخدام. أظهر بعض السمات الوصفية للنموذج في عنوان URL بدلاً من id
الخاص به. هناك أكثر من طريقة لتحقيق ذلك.
to_param
للنموذج يتم استخدام هذه الطريقة بواسطة Rails لإنشاء عنوان URL للكائن. يُرجع التنفيذ الافتراضي id
السجل كسلسلة. يمكن تجاوزه ليشمل سمة أخرى يمكن للإنسان قراءتها.
class Person
def to_param
" #{ id } #{ name } " . parameterize
end
end
من أجل تحويل هذا إلى قيمة صديقة لعنوان URL، يجب استدعاء parameterize
على السلسلة. يجب أن يكون id
الكائن في البداية حتى يمكن العثور عليه من خلال طريقة find
الخاصة بـ Active Record.
friendly_id
جوهرة فهو يسمح بإنشاء عناوين URL يمكن قراءتها بواسطة الإنسان باستخدام بعض السمات الوصفية للنموذج بدلاً من id
الخاص به.
class Person
extend FriendlyId
friendly_id :name , use : :slugged
end
تحقق من وثائق الأحجار الكريمة لمزيد من المعلومات حول استخدامها.
find_each
استخدم find_each
للتكرار على مجموعة من كائنات AR. يعد التكرار عبر مجموعة من السجلات من قاعدة البيانات (باستخدام التابع all
، على سبيل المثال) غير فعال للغاية لأنه سيحاول إنشاء مثيل لجميع الكائنات مرة واحدة. في هذه الحالة، تسمح لك طرق المعالجة المجمعة بالعمل مع السجلات على دفعات، مما يقلل بشكل كبير من استهلاك الذاكرة.
# bad
Person . all . each do | person |
person . do_awesome_stuff
end
Person . where ( 'age > 21' ) . each do |