Konstantin Stanmeyer
Posted on October 18, 2022
VIEW Methods
soon
Relationships
soon
Validations
Validations live in the model, checks if one or many certain parameters are met, and returns the expected result if correct. Error handling is done in the case of a validation failing.
Here are some simple validations, including some ugly regex:
class Post < ApplicationRecord
validates :title, presence: true
validates :content, presence: true, length: { minimum: 50 }
validates :category, inclusion: { in: ['Ruby', 'Rails'] }
validates :capitalized?
def capitalized?
if title.split.first.match(/\A[A-Z]/)
errors.add(:title, "first letter must be capitalized")
end
end
end
These validations do the following, in order:
- Checks to see if your post has a title
- Validates that the content exists, as well as it has at least a length of 50
- Checks the post's category, making sure the value isn't empty, and includes either Ruby or Rails
- CUSTOM VALIDATION: after defining the method below the validations, this now checks to see if the title begins with a capitol letter
If any one of these validations is not met, an error will be thrown by ActiveRecord
, and the attempted task has failed.
Serializers
MAKE SURE SERIALIZER GEM IS INSTALLED
Customizes the json
returned for a single model. This functions like an includes
statement, where data associated with the object you're acting on gets returned along with it.
Built-in Serializers:
In this example for Students
, I will walk through some reasonable use-cases for serializers:
With this, whenever we send json for any instance of Student
, this serializer will innately apply to it. The key difference between this and custom options is passing a serializer in a fetch path; Built-in doesn't need to be written elsewhere besides the model, but custom serializers must. (example later for custom)
First, create the serializer for Students
:
rails g serializer students
Examples:
1) A student object contains these eight values:
create_table "students", force: :cascade do |t|
t.string "name"
t.integer "age"
t.string "race"
t.integer "grade"
t.string "best-friend"
t.integer "teacher_id"
end
Now, say we don't want all of those values, and instead only want the student
's name and age (we definitely don't need an id if this is a SHOW
path, unless we're searching by a value other than id). With a serializer, we can pass those two values as attributes
class StudentSerializer < ActiveModel::Serializer
attributes :name, :age
has_many :books
belongs_to :teacher
end
I also went a step further to include the relationships, which is another aspect of serializers, where they automatically, in pair with ActiveRecord
, find associated values with foreign keys. Here, in a SHOW
request for a specific student
, we are grabbing all books that have the student
's foreign key (owned by the student
), as well as the one teacher
the student
belongs to, and returning them in the JSON
response. A request as such would return something like this:
{
"name": "Frederick",
"age": 21,
"books": [
{
"title": "East of Eden",
"author": "John Steinbeck"
},
{
"title": "The House Of Mirth",
"author": "Edith Wharton"
}
],
"teacher": {
"name": "Mr. Teacher",
"class": "Math"
}
}
Here, though, the returned json
for Teacher
and Books
holds their entire object, what if we want to serialize it like we did with Student
? We can! Adding a serializer to either class, the same as we did for student
, will innately apply it to any instance of Teacher
or Books
, even when pulled with Student
again. Here is an example serializer for books:
class BookSerializer < ActiveModel::Serializer
attributes :title
belongs_to :student
end
Now, if we do that same request for a single student
, this will be returned:
{
"name": "Frederick",
"age": 21,
"books": [
{
"title": "East of Eden"
},
{
"title": "The House Of Mirth"
}
],
"teacher": {
"name": "Mr. Teacher",
"class": "Math"
}
}
Custom Serializers:
Say we want the same return from a student
, but we want the teacher
to only include their name. We could technically do it this way:
class TeacherSerializer < ActiveModel::Serializer
attributes :name
has_many :students
end
But this isn't what we want. When we call an SHOW
method on teacher
itself now, it will only return this:
{
"name": "Mr. Teacher"
}
But what if we wanted to return the whole teacher
object? What if we want to only have the teacher
return their name when we fetch a student
, but continue returning the whole object when we just want to fetch a teacher
? We can use a custom serializer, then pass is IN student
, where the relationship lives.
First we generate the serializer:
rails g serializer student_teacher
Then, we can populate it with some code:
class StudentTeacherSerializer < ActiveModel::Serializer
attributes :name
has_many :students
end
Then, if we go to the SHOW method for Student
we can add it:
def show
render json: find_student, serializer: StudentTeacherSerializer
end
Now, without a serializer for teacher
(no more TeacherSerializer
), when we call a SHOW
request on student
we get this:
{
"name": "Frederick",
"age": 21,
"books": [
{
"title": "East of Eden"
},
{
"title": "The House Of Mirth"
}
],
"teacher": {
"name": "Mr. Teacher"
}
}
WOW. Only the teacher
's name! Nice.
Error handling
Here, we will be using ActiveRecord
's error-catching, and setting two general errors to handle, along with object-specific json
returns, using the error object. There'll also be a custom error at the end.
First, we can look at what we need to error handle in the routes we've made for a model:
For SHOW
or UPDATE
, both of which are looking for instance of an object, we want to return an error when the search parameter doesn't match any existing instance. We can use ActiveRecord
's rescue
, or rescue_from
method to access the "record" value within the error object when it's thrown. Starting with the RecordNotFound
error, we can add handling at the top of the controller, and then define what's returned in our private methods:
class StudentsController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_response
#code...
def show
student = find_student
render json: student
end
private
def render_not_found_response
render json: { error: "Student not found" }, status: :not_found
end
#code...
end
Now
Then, there is handling a RecordInvalid
, which can be thrown when you are trying to pass an updated or new instance during a CREATE
or UPDATE
. Here is how that works:
def update
post = post.create!(post_params)
render json: post
end
Minimizing Code
soon
Posted on October 18, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024