DEV Community

Konstantin Stanmeyer
Konstantin Stanmeyer

Posted on

Rails Run-Through

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
Enter fullscreen mode Exit fullscreen mode

These validations do the following, in order:

  1. Checks to see if your post has a title
  2. Validates that the content exists, as well as it has at least a length of 50
  3. Checks the post's category, making sure the value isn't empty, and includes either Ruby or Rails
  4. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then, we can populate it with some code:

class StudentTeacherSerializer < ActiveModel::Serializer
  attributes :name

  has_many :students
end
Enter fullscreen mode Exit fullscreen mode

Then, if we go to the SHOW method for Student we can add it:

def show
  render json: find_student, serializer: StudentTeacherSerializer
end
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Minimizing Code

soon

Top comments (0)