High Leverage Rails
Introduction
Introduction to this course
Why use Ruby on Rails
Why use SQLite
Ruby on Rails + SQLite
Powering Your App with SQLite
Creating tables
Timestamps
Column types
Typeof
Ruby types
Creating table introduction
Creating table advanced
Inserting data
Updating data
Upserting data
Reading data
Virtual columns
Enums
Introduction to JSON
Indexing JSON
JSON table functions
Building a Modern Rails Application
Creating a new Rails application
Installing Solid Queue
Installing Solid Cache
Installing Solid Cable
Dockerfile
Application overview
Authentication
Base styles
Registration
Scaffolding posts
Polishing posts
Scaffolding comments
Polishing comments
Polishing models
Polishing controllers
Creating new post
Updating post
Reviewing MVP
Tagging posts
Custom tags
Friendly URLs
Full text search
Deploying & Operating Your App
Backups
Check Litestream locally
Verifying backups
Deployment options
Deploying with Hatchbox
Deployment constraints
Vertical scaling
Database access
Migrations
Locked video

Please purchase the course to watch this video.

Video thumbnail
Powering Your App with SQLite
Ruby types

Full Course

$
129
$179
USD, one-time fee
This course came at the perfect time. I’ve recently gotten back into Rails after an 18-year hiatus, and this was a perfect refresher and shows just how much you can accomplish with Rails right out of the box.
Garrett Winder
Garrett Winder

Move fast and fix things

Application monitoring that helps developers get it done.

Rails hosting made simple

Deploy apps to servers that you own and control.

Summary

Discover how Active Record deserializes SQLite data into Ruby classes for seamless data handling in Rails. Learn how BigDecimal ensures precise arithmetic, how date and time values are mapped, and how JSON columns are converted into Ruby types for data consistency and efficiency.

Video Transcript

We now know the 11 different column types that Active Record supports when defining tables, and we understand how SQLite stores those columns in its database file. We've also seen how Active Record serializes data from Ruby into SQLite.

In this video, I want to focus on how Active Record deserializes SQLite data back into Ruby classes, allowing us to work seamlessly with it in our Rails application.

Exploring Data Serialization in Active Record

Jumping back into our IRB playground, we have our posts table and Post model set up. Let's create an initial post instance and examine how different column types are stored.

The logs show the same behavior as before:

  • Binary data → Stored as raw binary blobs
  • Boolean values → Stored as integers (0 for false, 1 for true)
  • Strings & Text → Stored as TEXT
  • Integer, Float, and Decimal → Stored as numeric values
  • Datetime & Date → Stored as ISO 8601-formatted text strings

Checking Ruby Class Mappings for Different Column Types

To see how Active Record maps database values to Ruby objects, let’s inspect a few columns.

For example, a datetime column is represented in Ruby as a Time class. To make it easier to inspect different column types, I’ve set up a type_of method to check the Ruby class for each column.

def typeof(col) = @post.public_send(col).class
typeof 'binary'

Here's what we find:

  • Binary → String (handled as a binary string)
  • Boolean → TrueClass (or FalseClass for false values)
  • String & Text → String
  • Integer → Integer
  • Float → Float
  • Decimal → BigDecimal

Ruby has this distinction between approximate real number values and precise real number values. This is why I recommend to always use the decimal column, because you're going to get back large decimal case values.

Why BigDecimal is Preferred Over Float for Arithmetic

One key takeaway is the importance of using BigDecimal for decimal columns instead of Float.

Let's see why:

1.2 - 1.0
# => 0.19999999999999996 (unexpected behavior due to floating-point precision issues)

This is the kind of bug that you see when you're working with approximate float values.

Now, using BigDecimal:

BigDecimal("1.2") - BigDecimal("1.0")
# => 0.2e0 (precise and correct)

This allows our arithmetic to always give us predicable results. The decimal column always gets deserialized into a Ruby BigDecimal object. These BigDecimal objects allow us to have predictable results with basic arithmetic. That's why I always recommend using decimals.

How Active Record Handles Date and Time Values

Looking at date, time, and datetime columns, we see:

  • Date → Stored as a Ruby Date object
  • Time → Stored as a Ruby Time object
  • Datetime → Also stored as a Ruby Time object

Just as we saw with the serialization, there actually isn't a way in Ruby to simply have an object that represents clock time without any regard for when on a calendar something happened.

To store our time value, let's look at what our time value is:

@post.time
# => 2024-11-19 17:20:50.992821 UTC

We see that we have an actual month, day, and year. You might be wondering we do I see 2024-11-19 and not January 1st, 2000.

This is one small an edge case to be aware of. When you create a record with Active Record, you don't actually get the rehydrated values for what was put into the database. You just have the values that you put in.

If we reload the record, we make a read query to SQL and force our values to come back from what was put in the database.

@post.reload.time
# => 2000-01-01 17:20:50.992821 UTC

Now we see that indeed our date is 2000-01-01. You need to be aware that the serializaion and deserialization process that active record is using to bridge the gap between SQLite types can allow for these small gaps when you're working with a Ruby object that has a value that is actually different from the value that is being stored in the database.

It's worth reloading in cases where you want to be 100% certain that you are working with the data as it is represented in the database.

How Active Record Handles JSON Columns

Inspecting the JSON column, we see that it is deserialized into the appropriate Ruby type:

  • A JSON object (dictionary) → Ruby Hash
  • A JSON array → Ruby Array
  • A JSON string, boolean, or number → Converted to the matching Ruby type

Bridging the Gap Between SQLite and Ruby

Active Record provides seamless integration between SQLite storage types and Ruby classes, but it’s essential to be aware of edge cases like:

  • Reloading records to ensure accurate data reflection
  • Using BigDecimal for precision arithmetic
  • Understanding how Ruby handles time values

With these foundations set, we’re now ready to dive deeper into creating and updating records in Active Record, which we’ll cover in the next set of videos.