Application monitoring that helps developers get it done.
Deploy apps to servers that you own and control.
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.
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:
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:
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.
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.
Looking at date, time, and datetime columns, we see:
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.
Inspecting the JSON column, we see that it is deserialized into the appropriate Ruby type:
Active Record provides seamless integration between SQLite storage types and Ruby classes, but it’s essential to be aware of edge cases like:
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.