Exception Handling و Validations in Rails و نحوه نمایش خطاها به کاربران.

در این مقاله به بررسی موارد استثنا و اعتبارسنجی در Rails خواهیم پرداخت. رسیدگی و اعتبارسنجی استثناها برای آن بسیار مهم است هر برنامه های وب، از جمله آنهایی که با Rails ساخته شده اند. به این ترتیب میتوانیم پیامهای خطایی را نمایش دهیم که نه تنها برای ما بهعنوان توسعهدهنده، بلکه برای کاربران برنامههایمان نیز مفید است. چگونه متوجه می شوید که نام کاربری یا رمز عبور اشتباهی را در یک برنامه بدون هیچ گونه اعتبارسنجی یا رسیدگی به خطا وارد کرده اید؟
ابتدا، در صورت نیاز، یک تجدید (یا مقدمه) در مورد استثناها. در غیر این صورت، به بخش اعتبار سنجی بروید!
قبلاً استثناهایی را در عمل دیده اید. به عنوان مثال، اگر تا به حال در کد خود اشتباه تایپی داشته باشید که باعث از کار افتادن برنامه شما با «NoMethodError» یا «SyntaxError» شود، یک استثنا را مشاهده می کنید:
putss 'Hello World'
# => NoMethodError: undefined method 'putss' for main:Object
# => Did you mean? puts
یک استثنا نشان دهنده یک شرط خطا در یک برنامه است. استثناها نحوه برخورد روبی با رویدادهای غیرمنتظره است و مکانیسمی برای توقف اجرای یک برنامه ارائه می کنند. هنگامی که یک استثنا مطرح می شود، برنامه شما شروع به خاموش شدن می کند. اگر هیچ چیز این فرآیند را متوقف نکند، برنامه شما در نهایت متوقف می شود و یک پیام خطا چاپ می کند.
رسیدگی به استثنا
برنامههای کرشکننده بدون مشکل هستند. به طور معمول می خواهیم از خراب شدن برنامه جلوگیری کنیم و به خطا واکنش نشان دهیم. به این کار “دست زدن” می گویند (همچنین به عنوان “نجات” یا “گرفتن” یک استثنا شناخته می شود.
نحو اصلی چیزی شبیه به این است:
#Imagine we write a function that would be called when an exception is raised.
def handle_exception
puts "Got an exception, but I'm handling it!"
end
begin
# Any exceptions in here...
rescue
# ...will trigger this block of code
handle_exception
end
# => "Got an exception, but I'm handling it!"
در این مثال، یک استثنا مطرح شده است، اما برنامه به دلیل “رستگاری” از کار نمی افتد. به جای خراب شدن، روبی کد را در قسمت اجرا می کند rescue
بلوک، که یک پیام را چاپ می کند. این خیلی خوب است، اما تمام این کارها این است که بدون اینکه به ما بگوید مشکلی پیش آمده است چی اشتباه رفت
هر گونه اطلاعات در مورد آنچه اشتباه رخ داده است در داخل یک قرار می گیرد شی استثنا.
اشیاء استثنایی
اشیاء استثنا (به عنوان مثال SyntaxError یا ArgumentError) اشیاء Ruby معمولی هستند (زیر کلاس های کلاس Exception). آنها حاوی اطلاعاتی در مورد “چه اشتباهی رخ داد” برای استثنایی هستند که نجات یافتند.
برای به دست آوردن یک شی استثنا، rescue
نحو کمی متفاوت است. در این مثال، ما از یک کلاس استثنایی ZeroDivisionError نجات خواهیم داد.
#Rescues all errors, and assigns the exception object to an `error` variable
rescue => error
#Rescues only ZeroDivisionError and assigns the exception object to an `error` variable
rescue ZeroDivisionError => error
در مثال دوم بالا، ZeroDivisionError
کلاس شی در است error
متغیر،ZeroDivisionError
خود زیر مجموعه ای از StandardError
. استانداردترین انواع خطا در روبی مانند ArgumentError
و NameError
زیر کلاس های هستند StandardError
. در حقیقت، StandardError
استثنای پیش فرض برای روبی است زمانی که Exception
زیر کلاس به صراحت نامگذاری نشده است.
begin
do_something
rescue => error
# This is the same as rescuing StandardError
# and all of its subclasses
end
حالا.. بیایید به عقب برگردیم و نگاهی دقیق تر به شی استثنا برای بیاندازیم ZeroDivisionError
begin
# Any exceptions in here...
1 / 0
rescue ZeroDivisionError => error
puts "Exception Class: #{error.class}"
puts "Exception Message: #{error.message}"
end
# Exception Class: ZeroDivisionError
# Exception Message: divided by 0
اکثر موارد استثنایی Ruby حاوی پیامی هستند که جزئیات اشتباه را نشان می دهد.
ایجاد استثناهای خودتان
شما همچنین می توانید استثناهای خود را فعال کنید! فرآیند افزایش استثناهای خود را … خوب … “افزایش” می گویند. شما این کار را با تماس با raise
روش.
وقتی استثناهای خود را مطرح می کنید، شما انتخاب می کنید که از کدام نوع استثنا استفاده کنید. همچنین می توانید پیام خطای خود را تنظیم کنید.
begin
#raises an ArgumentError with the message "You screwed up!"
raise ArgumentError.new("You screwed up!")
rescue ArgumentError => error
puts error.message
end
# => "You screwed up!"
ایجاد استثناهای سفارشی
استثناهای داخلی روبی عالی هستند، اما هنوز از نظر دامنه محدود هستند و نمیتوانند هر مورد استفاده ممکن را پوشش دهند.
فرض کنید در حال ساختن یک برنامه وب هستید و می خواهید زمانی که یک کاربر سعی می کند به بخشی از سایت دسترسی پیدا کند که نباید به آن دسترسی داشته باشد، استثنایی ایجاد کنید؟ هیچ یک از استثناهای داخلی Ruby واقعاً مناسب نیستند، بنابراین بهترین شرط شما ایجاد یک استثنا سفارشی است.
برای ایجاد یک استثنا سفارشی، به سادگی یک کلاس جدید ایجاد کنید که از آن ارث می برد StandardError
.
class PermissionDeniedError < StandardError
def initialize(message)
# Call the parent's(StandardError) constructor to set the message
super(message)
end
end
# Then, when the user tries to do something they don't
# have permission to do, you might do something like this:
raise PermissionDeniedError.new("Permission Denied!!!")
سلسله مراتب طبقات استثنایی
ما با ایجاد یک زیر کلاس از یک استثنا سفارشی ایجاد کردیم StandardError
، که خود زیر مجموعه ای از Exception
.
اگر به سلسله مراتب کلاس ها نگاه کنید، خواهید دید که همه چیز زیر کلاس است Exception
.
نگران نباشید، لازم نیست همه اینها را به خاطر بسپارید. اما این مهم است که به دلیل بسیار خوبی با سلسله مراتب طبقاتی به طور کلی آشنا هستید.
نجات خطاهای یک کلاس خاص، خطاهای زیر کلاس های آن را نیز نجات می دهد.
وقتی StandardError را نجات می دهید، نه تنها استثنائات را نجات می دهید StandardError
بلکه فرزندانش نیز. اگر به نمودار نگاه کنید، خواهید دید که مقدار زیادی وجود دارد: ArgumentError، NameError، TypeError و غیره.
بنابراین، اگر قرار بود نجات دهید Exception
، شما می توانید تک تک استثناها را نجات دهید، که یک بد اندیشه. این کار را نکن!
روبی برای چیزهایی غیر از خطا از استثناها استفاده می کند، بنابراین نجات همه خطاها باعث می شود که برنامه شما به روش های عجیبی خراب شود. همیشه نجات خاص استثناها. در صورت شک، استفاده کنید StandardError
.
اکنون که درک درستی از استثناها داریم، بیایید نگاه کنیم اعتبارسنجی ها و چگونه همه چیز به هم مرتبط است
بسیاری از کارهایی که ما در زندگی روزمره خود انجام می دهیم اکنون می توانند به صورت آنلاین انجام شوند. چه خرید، بانکداری، رزرو شام و غیره. اما در قلب همه این فرآیندها چیست؟ داده ها، این همه داده است.
بنابراین، برای اطمینان از اینکه همه چیز به خوبی اجرا می شود، یکپارچگی داده ها باید تضمین شود. این توسط انجام می شود اعتبار سنجی داده هایی که قبل از ذخیره در پایگاه داده ارائه شده است.
همانطور که می دانید Rails بر اساس معماری MVC ساخته شده است. را م، که هست مترلایه odel، به مدیریت نحوه ایجاد و ذخیره اشیاء در پایگاه داده می پردازد. در Rails، این لایه به طور پیشفرض توسط Active Record اجرا میشود، بنابراین، Active Record با انجام وظیفه حیاتی اعتبارسنجی دادهها سروکار دارد.
اعتبارسنجی ها صرفاً مجموعه ای از قوانین هستند که بر اساس برخی معیارها معتبر بودن داده ها را تعیین می کنند. هر شی مدل شامل یک خطاها مجموعه. در مدل های معتبر، این مجموعه فاقد خطاست و خالی است. وقتی قوانین اعتبارسنجی را روی یک مدل خاص اعلام میکنید و آن مدل نمیتواند تصویب شود، یک استثنا مطرح خواهد شد و مجموعه خطاها حاوی خطاهایی مطابق با قوانینی است که شما تنظیم کرده اید، و این مدل معتبر نخواهد بود. یک مثال ساده بررسی این است که آیا یک فرم آنلاین تمام داده های لازم را قبل از ارسال دارد یا خیر. اگر قسمتی خالی بماند، پیغام خطا به کاربر نمایش داده می شود تا بتواند آن را پر کند.
اعتبار سنجی های ساده
بیایید به دو روش اعتبارسنجی اساسی نگاه کنیم.
اعتبارسنجی وجود و/یا عدم وجود صفات
این اعتبارسنجی وجود (یا عدم وجود) یک ویژگی را بررسی می کند. در مثال زیر، ما در حال بررسی هستیم که ببینیم آیا username
ویژگی نه تنها موجود است، بلکه منحصر به فرد است. این به شما کمک می کند تا از نام کاربری تکراری در برنامه خود جلوگیری کنید. شما اینها را با presence
و uniqueness
کمک کنندگان اعتبارسنجی به ترتیب با استفاده از validates
کلمه کلیدی و به دنبال آن نام ویژگی:
class User < ApplicationRecord
#validates presence and uniqueness of a username upon
#creation
validates :username, presence: true, uniqueness: true
end
اعتبارسنجی طول کاراکتر در یک ویژگی
برای یک password
ویژگی، ما میتوانیم بررسی کنیم که آیا رمز عبوری که میخواهیم تنظیم کنیم، الزامات مشخص شده ما را برآورده میکند یا خیر. در این مورد، ما مطمئن می شویم که حداقل طول آن 8 کاراکتر با کمک کننده طول باشد:
class User < ApplicationRecord
validates :username, presence: true, uniqueness: true
#validates minimum length of 8 characters
validates :password, length: { minimum: 8 }
end
کار با خطاهای اعتبارسنجی
پس چگونه با خطای اعتبارسنجی برخورد می کنید؟ همانطور که قبلا ذکر شد، زمانی که اعتبار سنجی ناموفق باشد، یک استثنا مطرح می شود و یک errors
مجموعه تولید می شود.
بیایید ببینیم وقتی می خواهیم یک حساب کاربری با رمز عبور کمتر از 8 کاراکتر ایجاد کنیم چه اتفاقی می افتد:
class User < ApplicationRecord
validates :username, presence: true, uniqueness: true
validates :password, length: { minimum: 8 }
end
user = User.create(username: admin, password: "Welcome")
pp user
# => #<User:0x00007fd0cca748e8
# id: nil,
# username: "admin",
# password: "Welcome",
# created_at: nil,
# updated_at: nil>
چیزی را که به نظر می رسد یک برمی گردیم User
به عنوان مثال، اما با بررسی دقیق تر می بینیم که id
ویژگی است nil
، به معنی User
نمونه در پایگاه داده ذخیره نشد. دانستن آن خوب است، اما باید ببینیم چرا دقیقاً این رکورد نامعتبر است. برای ایجاد یک استثنا و دیدن جزئیات بیشتر در مورد خطا، باید یک bang(!) را به ما اضافه کنیم create
روش:
class User < ApplicationRecord
validates :username, presence: true, uniqueness: true
validates :password, length: { minimum: 8 }
end
user = User.create!(username: admin, password: "Welcome")
# => ActiveRecord::RecordInvalid (Validation failed: Password is # too short (minimum is 8 characters))
در اینجا ما یک را دریافت می کنیم RecordInvalid
استثنا، یکی از بسیاری از زیر کلاس های استثنا ActiveRecordError
، که خود زیر مجموعه ای از StandardError
. لیست استثناهای Active Record را اینجا ببینید.
RecordInvalid
زمانی افزایش می یابد که یک رکورد به دلیل اعتبار سنجی ناموفق در پایگاه داده ذخیره نشود. در این مورد رمز عبور ما خیلی کوتاه بود.
یادتان هست اشاره کردم که مجموعه ای از خطاها نیز ایجاد می شود؟ در اینجا نحوه دسترسی به آن آمده است:
class User < ApplicationRecord
validates :username, presence: true, uniqueness: true
validates :password, length: { minimum: 8 }
end
user = User.create!(username: nil, password: "Welcome")
# => ActiveRecord::RecordInvalid (Validation failed: Username can't be blank, Password is too short (minimum is 8 characters))
#Accesses an array of error messages from the `errors` object.
user.errors.full_messages
# => ["Username can't be blank", "Password is too short (minimum is 8 characters)"]
داشتن آرایهای از خطاهای اعتبارسنجی به راحتی قابل دسترسی است زیرا میتوانیم آنها را به frontend ارتباط دهیم.
برمی گردیم به خودمان User
مدل، بیایید a اضافه کنیم create
روش ایجاد یک حساب کاربری جدید
def create
#remember to append a "!" to your create method
user = User.create!(user_params)
session[:user_id] = user.id
render json: user, status: :created
end
private
def user_params
params.permit(:username, :password,
:password_confirmation)
end
اکنون در صورت تأیید ناموفق، در این مورد یک نام کاربری خالی و/یا یک رمز عبور کوتاه، a RecordInvalid
استثنا مطرح خواهد شد. اکنون میتوانید منطق شرطی را برای نمایش خطاها به صفحه اصلی اضافه کنید، اجازه دهید ما را بهروزرسانی کنیم create
روش:
def create
begin
user = User.create!(user_params)
session[:user_id] = user.id
render json: user, status: :created
rescue ActiveRecord::RecordInvalid => exception
render json: {errors:
exception.record.errors.full_messages}, status:
:unprocessable_entity
end
end
در بلوک نجات خود، استثنا را به عنوان آرگومان عبور می دهیم و به مجموعه خطاهای خود که در exception.record.errors.full_messages
. سپس آن را با فرمت JSON زیبا می بندیم تا بتوان با کد وضعیت HTTP به فرانت اند ارسال شود. 422
موجودیت غیر قابل پردازش با استفاده از status:
روش. شما همچنین می توانید فقط تایپ کنید status: 422
، اما این قابل خواندن تر است.
آیا راه آسان تری وجود ندارد؟
می توانید ببینید که چگونه این می تواند سریع تکرار شود، به خصوص اگر با چندین مدل و کنترلر کار می کنید. شما باید همین را داشته باشید begin/rescue
مسدود کردن در همه create
اقدامات در سراسر کنترلرهای شما فقط برای رسیدگی به این یک استثنا. خوشبختانه راهی برای انتزاع بخشی از این منطق و آسان کردن زندگی شما وجود دارد.
در Rails متوجه خواهید شد که هر کنترل کننده از آن ارث می برد ApplicationController
، و بنابراین تمام روش های خود را به ارث می برد. ما می توانیم با حرکت منطقی برای مدیریت a از این مزیت استفاده کنیم RecordInvalid
استثنا تا ApplicationController
، اما این بار این کار را کمی متفاوت انجام خواهیم داد:
class ApplicationController < ActionController::API
#rescue_from takes an exception class, and an exception
#handler method
rescue_from ActiveRecord::RecordInvalid, with:
:unprocessable_entity_response
private
def unprocessable_entity_response(exception)
render json: {errors:
exception.record.errors.full_messages}, status:
:unprocessable_entity
end
end
به جای یک بلوک start/rescue، از متد rescue_from استفاده می کنیم که یک کلاس استثنا و یک کنترل کننده استثنا مشخص شده توسط یک دنباله را می گیرد. with:
گزینه ای حاوی نام روش کنترل کننده استثنا. در این مورد ما یک متد کلاس خصوصی به نام ایجاد کردیم unprocessable_entity_response
. بنابراین، هنگامی که استثنای مشخص شده مطرح می شود، rescue_from
شی استثنا را به کنترل کننده استثنا که در with:
گزینه.
در قسمت جلویی …
نمایش خطاهای اعتبارسنجی در فرانت اند به سادگی دریافت آنها از طریق یک درخواست واکشی و استفاده از رندر شرطی برای نمایش آنها به کاربر در صورت بروز خطای اعتبارسنجی است.
این نمونه ای از فرم ثبت نام حساب است که قبلاً با استفاده از React ساخته بودم:
function SignupForm() {
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const [passwordConfirmation, setPasswordConfirmation] =
useState("")
//Any validation errors that are returned will be stored
in state...
const [errors, setErrors] = useState([])
async function handleSubmit(e) {
e.preventDefault();
const res = await fetch("/api/signup", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username,
password,
password_confirmation: passwordConfirmation
})
})
if (res.ok) {
const user = await res.json()
setUser(user)
} else {
//if the fetch request fails and returns an error
object, the errors will be stored in state using
setErrors
const err = await res.json()
setErrors(err.errors)
}
}
در دستور بازگشت، میتوانید از رندر شرطی برای نمایش خطاها در صورت وجود آنها استفاده کنید errors
آرایه در حالت:
return (
{errors.map((err) => <Alert key={err} severity="error">{err}</Alert>)}
)
نتیجه:
https://guides.rubyonrails.org/active_record_validations.html
http://www.railsstatuscodes.com/
https://www.rubydoc.info/docs/rails/4.1.7/ActiveRecord/ActiveRecordError
https://ruby-doc.org/core-2.5.1/Exception.html