Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
83: Active Record Validations and Callbacks
A few years ago, I was working on a SaaS project where we let admins create custom subscription tiers. One afternoon, I noticed our revenue for the day was... negative. It turned out a bug in the admin panel allowed a "-50.00" value to be submitted for the monthly price. Because we had no server-side validations on the SubscriptionPlan model, Active Record just happily saved that negative number into the database. For three hours, we were literally paying people to use our software. That's the hard way of learning that client-side validation is a courtesy, but server-side validation is a necessity.
Keeping the Garbage Out of Your Database
Validations are your last line of defense. They ensure that only "clean" data actually hits your disk. In Active Record, validations happen before the data is saved to the database. If any validation fails, the record is marked as invalid, and the save method returns false without ever executing the SQL INSERT or UPDATE.
You'll mostly use the validates helper. It's a Swiss Army knife that handles the most common scenarios. For example, if we were fixing that subscription bug, we'd do this:
class SubscriptionPlan < ApplicationRecord
validates :name, presence: true, uniqueness: true
validates :price, numericality: { greater_than_or_equal_to: 0 }
validates :sku, length: { is: 8 }
end
In this snippet, presence: true ensures the name isn't nil or an empty string. uniqueness: true checks the database to make sure no other plan has that name. I personally find numericality to be one of the most important validations—it prevents the "negative price" nightmare I mentioned earlier. If you need something more complex, you can write a custom validation method. Just define a method and use validate :method_name (singular) to call it.
Hooking into the Object Lifecycle
While validations check if data is correct, callbacks allow you to trigger logic at specific moments in an object's life. Think of them as "event listeners" for your model. You might want to hash a password before saving a user, or send a welcome email immediately after a record is created.
Active Record provides a variety of hooks. The most common ones are before_validation, before_save, after_create, and after_commit. Here is how you might use them to keep your data consistent:
class User < ApplicationRecord
before_validation :normalize_email
after_create :send_welcome_email
private
def normalize_email
self.email = email.downcase.strip if email.present?
end
def send_welcome_email
UserMailer.welcome(self).deliver_later
end
end
Notice that I used before_validation for the email. This is a pro tip: if you're modifying a field that needs to be validated, do it before the validation step. If you normalize the email before_save, you might be validating a string with trailing spaces and then saving a trimmed version, which can lead to weird edge cases.
The Danger of Over-Automating
I have to give you a warning here: callbacks are a double-edged sword. It's tempting to put all your business logic in after_save or after_create, but this is a fast track to "Callback Hell." When your models become bloated with callbacks, testing becomes a nightmare because simply creating a test object triggers a chain reaction of emails, API calls, and database updates.
A good rule of thumb is that callbacks should be used for internal state consistency—like calculating a total price based on line items. If the action involves an external service (like sending an email or hitting a Stripe API), consider moving that logic into a Service Object. Keep your models lean; your future self will thank you when you're trying to run a migration or a seed script without accidentally emailing 5,000 users.
📋 Practical Task
Exercise: Implementing a User Profile Sanitizer and Credit Balance Guard
You are building a credit-based system for a freelance marketplace. You need to ensure that user data is clean and that their credit balance never drops below zero.
Requirements:
- Create or modify a
Usermodel. - Validations:
- The
usernamemust be present, unique, and at least 3 characters long. - The
credit_balancemust be a number and cannot be less than 0.
- The
- Callbacks:
- Implement a
before_validationcallback that removes any leading or trailing whitespace from theusername. - Implement an
after_createcallback that logs a message to the console saying "New user created: [username]".
- Implement a
Testing your work: Try to save a user with a negative balance or a duplicate username. Verify that user.save returns false and that the user.errors object contains the expected messages. Then, save a user with a username like `" ruby_dev "` and verify that it is saved as `"ruby_dev"` in the database.
There are no comments for now.