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
217: Push Notifications from a Ruby Backend
Push notifications are one of those features that seem simple on a diagram but can be a total headache in practice. You aren't just talking to a database; you're talking to a third-party gateway (like Firebase or Apple), which then talks to a device that might be offline or in airplane mode. For this lesson, we're going to build a "Price Drop Alert" system. When a product's price dips below a user's target, we'll fire off a notification.
Picking our gateway
I'm going to use Firebase Cloud Messaging (FCM) because it handles both Android and iOS, and it's the standard for most Ruby backends today. To keep this clean, we aren't going to use a massive wrapper gem that hides everything. Instead, we'll use faraday to make the HTTP requests. I prefer this because when the API changes—and they always do—you'll actually know how to debug the raw JSON request.
Structuring the notification payload
Before we touch the network, we need to figure out what we're sending. A push notification isn't just a string; it's a JSON object. We need a token (the unique ID of the user's device) and a notification object containing the title and body.
notification_payload = {
message: {
token: "user_device_token_abc123",
notification: {
title: "Price Drop Alert!",
body: "Those sneakers you wanted are now $89.99!"
},
data: {
product_id: "456",
discount_type: "flash_sale"
}
}
}
Note that I added a data hash. This is crucial. The notification part is what the OS shows in the tray, but the data part is what your app uses to actually navigate the user to the right product page when they tap the notification.
The mistake: The "Legacy" Trap
Here is where I usually trip up when returning to an FCM project after a few months. I started writing the request using the old "Server Key" method—just adding a header like Authorization: key=AAA.... I hit the endpoint and got a 401 Unauthorized.
I realized I was trying to use the Legacy HTTP protocol. Google has moved to the FCM HTTP v1 API, which requires OAuth2 tokens. You can't just use a static string anymore; you need a Service Account JSON key and a short-lived Bearer token. This is a common point of frustration, but it's much more secure.
Handling OAuth2 and the Request
To fix this, I'll use the googleauth gem to handle the token exchange. It's a lifesaver because it manages the token expiration and refreshing for you. Here is how I'd wrap this into a reusable service object.
require 'faraday'
require 'googleauth'
class PushNotificationService
FCM_URL = "https://fcm.googleapis.com/v1/projects/your-project-id/messages:send"
def self.send_alert(device_token, title, body, extra_data = {})
# Get the OAuth2 access token from the service account file
auth = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: File.open('service-account.json'),
scope: 'https://www.googleapis.com/auth/firebase.messaging'
)
auth.fetch_access_token!
conn = Faraday.new
response = conn.post(FCM_URL) do |req|
req.headers['Authorization'] = "Bearer #{auth.access_token}"
req.headers['Content-Type'] = 'application/json'
req.body = {
message: {
token: device_token,
notification: { title: title, body: body },
data: extra_data
}
}.to_json
end
response.success?
end
end
Wiring it into the business logic
Now we just need to call this when our price logic triggers. I wouldn't put this directly in the model—sending a network request during a database save is a recipe for a slow app. Instead, I'd wrap it in a background job. But for the sake of the example, here is the logic flow:
# Inside a PriceChecker service or Job
if product.current_price < user.target_price
PushNotificationService.send_alert(
user.fcm_token,
"Price Drop!",
"#{product.name} is now only #{product.current_price}!",
{ product_id: product.id }
)
end
By separating the PushNotificationService from the logic that decides when to notify, you can easily swap FCM for another provider (like OneSignal or AWS SNS) without rewriting your price-checking logic.
📋 Practical Task
Build a "New Message" Notification Trigger
Imagine you are building a chat application. You have a Message model and a User model. The User model has a fcm_token attribute.
Your task is to implement a method within a new class called ChatNotificationService that sends a push notification to the recipient of a message. The notification should:
- Include the sender's name in the title (e.g., "Message from Sarah").
- Include a snippet of the message text in the body.
- Pass the
message_idin thedatapayload so the app can open the specific conversation.
Assume the googleauth and faraday gems are already configured and you have access to a service-account.json file. Write the class and the method that accepts a message object as its argument.
There are no comments for now.