Rediscovering ActiveRecord
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Part Number | 28 | |
Number of Parts | 89 | |
Author | ||
License | CC Attribution - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/31551 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
00:00
Software developerMedical imagingTouchscreenRow (database)Computer animation
00:25
QuicksortGroup actionUsabilityMultiplication signDatabaseRow (database)Social classEndliche ModelltheorieComplex (psychology)Process (computing)Network topologyPressureTable (information)Physical systemEvent horizonQuery languageOrder (biology)CodeComputer animation
01:38
Core dumpRight angleMedical imagingFunctional (mathematics)Computer fileQuery languageLine (geometry)Bounded variationCASE <Informatik>Connected spaceTemporal logicForm (programming)Order (biology)Row (database)EmailEndliche ModelltheorieDependent and independent variablesComputer animation
02:41
Casting (performing arts)State of matterDatabasePhysical systemWebsiteData conversionPosition operatorAuthorizationEndliche ModelltheorieACIDSource codeData typeCodeMultiplication signStability theoryCartesian coordinate systemIntegerType theoryObject (grammar)Insertion lossString (computer science)InformationBootstrap aggregatingNatural numberAttribute grammarInternet forumDependent and independent variablesNumberoutputConnected spaceDefault (computer science)Field (computer science)DistanceReal numberVideo gameDigital photographyGame controllerRow (database)Graph (mathematics)System callAreaService (economics)Universe (mathematics)Content (media)Sound effectTable (information)Key (cryptography)Reduction of orderInternetworkingVotingLatent heatTouchscreenCompass (drafting)Uniform resource locatorControl flowError messageStructural loadMathematicsQuery languageQuicksortRoundness (object)Disk read-and-write headBitCache (computing)Inheritance (object-oriented programming)Serial portSocial classMereologyHash functionVideo game consoleData storage deviceForm (programming)Computer animation
10:07
Point (geometry)Query languageInformationRow (database)Cache (computing)Limit (category theory)Right angleObject (grammar)Line (geometry)MereologyStatement (computer science)Block (periodic table)Level (video gaming)Latent heatNumberKeyboard shortcutField (computer science)PlanningAttribute grammarDatabaseFree variables and bound variablesGroup actionData structureIntegerOrder (biology)Different (Kate Ryan album)CausalityIntegrated development environmentComputer filePhysical lawAreaLattice (group)1 (number)Shift operatorAutocovarianceParallel portCASE <Informatik>QuicksortSolitary confinementComputer-assisted translationVector spaceGame controllerEmailNichtlineares GleichungssystemEndliche ModelltheorieForm (programming)BitDependent and independent variablesComputer animation
14:48
Point (geometry)Endliche ModelltheorieQuery languageStudent's t-testRankingComputer animation
15:13
Attribute grammarType theoryLevel (video gaming)Keyboard shortcutCasting (performing arts)Array data structureOrder (biology)Limit (category theory)ResultantNumberHash functionQuery languageSource codeRow (database)LogicArithmetic meanString (computer science)Object (grammar)Endliche ModelltheorieDatabaseInstance (computer science)Multiplication signTransformation (genetics)Social classSystem callCodeFree variables and bound variablesPattern languageVariable (mathematics)Connected spaceDot productFlagStatement (computer science)Hand fanControl flowMoment (mathematics)Connectivity (graph theory)Cartesian coordinate systemPoint (geometry)QuicksortSoftware testingHierarchyComputer fileMappingForm (programming)Sphere1 (number)SoftwareInformationBit rateGroup actionAnalogyInternetworkingStudent's t-testSquare numberPhysical systemReal numberTimestampComputer animation
22:00
Query languageDifferent (Kate Ryan album)CodeBeta functionMultiplication signFlagResultantLine (geometry)QuicksortInheritance (object-oriented programming)BlogSocial classReverse engineeringCore dumpRow (database)MereologyComputer programmingNumberGroup actionAreaSoftware frameworkPoint (geometry)Endliche ModelltheorieOnline helpInformationFinite differenceMoment (mathematics)Computer animation
24:48
Computer fileMobile appLibrary (computing)Group actionCodeLambda calculusRight angleGenderArithmetic meanHydraulic jumpOrder (biology)Row (database)Standard deviationMetropolitan area networkBit rateNamespaceComputer animation
27:02
Computer animation
Transcript: English(auto-generated)
00:02
My name is Mario Chavez. I work for a development shop. And what I'm going to talk today is
00:21
about rediscovering active record. We are all familiar with active record. When I came to Rails eight years ago, the first thing that I hear from the people is what was that active record was magic. We just create or define a class. And that's pretty much what we
00:47
needed in order to make it work professionally about two years ago. But over time, I just find out that actually there is no magic in active record. Active record is doing pretty
01:02
cool things for us. It's hiding a lot of the complexity that implies to have a model be connected to a table in the database. But at the end, there is no magic at all. And what I'm trying to do here is just to do some exploration and go to the main
01:27
actions that active record is doing for us in order to transform a query into an object. So, let's start with that. Probably we are all familiar with this line of code. We are
01:44
basically just using a generator in order to create a model. What we are going to end up is something like this. Again, I can run this query. Where I'm asking active record to go to
02:06
the database and pull one of the records with the ID. And what we see below is the actual response that we may be expecting. In this case, I have an ID, I have a name and email address. And obviously, we're going to have to create it. But the question here is how?
02:30
How can active record come from this into being able to execute my query? What is it doing underneath? So, the first thing that we need to understand is that active record needs to do
02:48
some kind of introspection. The first thing that active record needs to perform is to ask the database what is the table schema. Once we have this table schema, we can do a lot of
03:05
things. So, the first thing that we load a model in our application and we try to do any kind of operation like a query. The first thing that is going to happen is that
03:21
active record is going to try to load this schema. Basically, what it's doing, it's calling this method. If we fire up the console and we just configure some model and call these attribute types, what we are going to find out is that this method is going to fire
03:42
something inside of the active record code which is called schema. No, we cannot build a Rails application. This method is delegating the call to the actual database connection to ask what is the schema for this specific model. Here, the connection is
04:03
playing on the schema cache. So, it means that every time or at least the first time that we try to use a model, active record is going to go in there, pull the table schema, make a copy of that, cache it, and it's a little bit ready for us for the next calls to these
04:23
models. So, it means that only the first time for each model, we are going to make this kind of call. Also, this means that the connection is keeping the cache. It contains all the attributes, all the schema for this specific model.
04:44
And we can see, for example, in this piece of code, we are asking the hash to give us the schema for a specific table. If there is nothing in there, that's where the connection actually issue a query to the database. It will depend on each database how the query is
05:04
going to look like. But, at the end, we're going to get all the information that we need to understand what is the schema for our model. So, what we are getting, if we call directly
05:21
the schema cache column hash, for example, for the table user, we're going to get this specific hash. We have the keys are going to be the columns of the table. And value is going to be an object that is going to have a lot of information. But probably some of the most
05:41
important piece of data that we are getting here is the attribute type. We can see here that the database is an integer. But also, we're going to have, like, default values and any other important information that we can use inside of ActiveRecord. Now that we have this understanding, the load the schema method also needs to
06:07
give us a little bit more information. Basically, what we are going to need is a cast type. And a cast type is something that will allow us to convert whatever the database
06:26
is telling us that is going to be the data type that we are storing in the database, and what the data type is going to become on the Ruby side. Obviously, for integer, probably this is pretty simple. I mean, if we have a number, we're going to have
06:42
an integer in the database, probably we're going to have an integer on the Ruby side. But for things like, for example, time stamps, this is going to be a different story. We're going to need something that will help us to make the cast between the original data type into a Ruby type. What is happening inside of ActiveRecord is that it does implement
07:06
this active model type value. This is a base class that helps us to define how ActiveRecord is going to make this cast in between database types into Ruby types.
07:22
If we inspect the ActiveRecord source code, we're going to find out that we have active model type integer, active model type string, active model type date, and some other objects that will help us to create this cast. And this is something new in Rails 5.
07:44
Well, actually, it is implemented in Rails 4.2. But the API is private. It's not available for us. But in Rails 5, we can actually create our own types. Like, for example, imagine that we have a field in the database where we are storing an edge store data in there. And then we want to
08:05
pull the data into a Rails application. Instead of getting a hash, we wanted to convert that into some sort of object. And this object doesn't really need to be an active model object. It can be pretty much everything. Or probably we can convert that into
08:22
an struct or something like that. So we can actually define our own data types for Rails. And the way that we can do that is we need to implement at least three methods from this type of class. The first one is the serialize. This is the method that we are gonna tell
08:42
Rails or ActiveRecord how we are gonna transform whatever we are getting from the database into a Ruby object. The second one is cast. This one is being used for user input. For example, we have an input field. And this input field can accept a number.
09:07
When the form posts that information back to Rails, we need a way to get the number. So it looks like the final type that is going to be stored in the database.
09:21
And finally, we need to implement the serialize method. The serialize method is gonna dictate how the Ruby type data is gonna be transformed into the database type. So as I said, this is part of the attributes API.
09:41
So at this point, now that we were able to pull the database schema. Now, I just want to do one more thing, which will hopefully be of interest. Because it's still pretty boring. It's just serving a static string. I want to look at that. What's that ENV that's being passed?
10:00
If we call these attributes types, we can see here the response. So look at that. So what now? We know that we can understand the schema. But we can still get to this point where we can actually run the query. The next thing that ActiveRecord needs to perform is to create a query for us.
10:28
ActiveRecord is smart enough to try to cache query objects for us. Let me explain why. When we execute a query, for example, this dot find and the number,
10:44
what it's gonna do, it's gonna try to find if that statement has been executed before. If it is, obviously, we're gonna just pull the query. And we're gonna perform whatever action it needs to be done. But if not, then our find method is gonna be converted into a query.
11:06
So we can see here in the block, that's the query. That's how the find number is being converted into a query. But there is something here that we need to pay attention.
11:23
Is that in the where clause, instead of passing the ID of the object that I'm looking for, Rails or ActiveRecord is doing something. And we're gonna see what it means in a minute. So we don't have the query.
11:42
It means that we're gonna have to create a new one. If we go to the statement cache file, we're gonna find this method. And this is the part that takes care of creating the query for us. The first thing that we are gonna need is to convert the dot where
12:01
into our AST. And that's what the first line of the create method is doing. This block.com is actually executing the where clause that we see before. And this where clause is being converted into this RL3,
12:24
where we have this node, which is quality. We have the attribute, which is the attribute ID. And then on the right side, we have something called bind param. This is kind of a place holder. Where we're gonna replace that with the ID of the object that we are looking for.
12:42
And actually, if we see the other structure that we have on the left, we see at the end that we have a block which reference to the ID. But instead of saying that it's an integer or displaying the value, what it's actually saying is that this is a substitute.
13:01
This is something that we are going to replace at some point. The next part that the create method is doing here is a bind map. The bind map is basically setting up what are the params that we are gonna pass to the query.
13:21
The first one on the right is the value for the limit clause. Because we are doing dot find, and we are actually asking for just one record. And the one on the left is the definition just for the query attribute. In this case, it's the ID field.
13:40
The next thing that's gonna happen here is that we're gonna create this query builder of the RL AST. And this is what actually is gonna construct the SQL for us. In this case, it is using the specific database adapter
14:02
in order to create the right query for the right database. So in my case, I was using Postgres, if we were using MySQL or something else, the query probably will be a little bit different. But here, what we can see in the query that this query builder is creating for us is that the ID and the limit has these question marks,
14:23
which means that those are placeholders that we are going to replace at some point. And doing this will allow us also to cache the query on the Rails side, but also we are gonna keep the chance on the database, to cache the query, the execution plan for the query in the database.
14:45
So yes, you made up with me up to this point. Well, the model knows what is the schema. We know that we needed to build a query.
15:03
We have the query, so what is next? At this point, we are ready to execute the query. What is going to happen is that now that we have the statement, we're gonna be able to call this method execute.
15:23
And the method execute, as you can see, one of the patterns is the actual ID that we are trying to, for the record that we are trying to pull from the database. And well, we also pass in the connection. The execute method will do a couple things here.
15:41
The first step, it's gonna actually perform the binds. Do you remember that in the previous steps, we have a binder, and we have two attributes in there. The first one was the limit clause, and the second one was for the ID. But at that point, the ID was marked as a substitute.
16:02
At this point, we are actually replacing the value with the real ID of the record that we are trying to pull here. Also, a Rails engine. Then what we are asking in this step is to actually give us the SQL statement,
16:21
the string that we are gonna send to the database. We see that we still have the placeholders. What are Rails engines then? And once that we are ready, what ActiveRecord is gonna do is just execute this find by SQL, where we are actually going to pass the SQL query, the bind values, which are the values that at the end
16:43
are gonna be sent to the database for a query, and what we are gonna get is, at some point, is a result set. This result set, it's gonna be something like this. It's just an object that has the columns and the rows.
17:04
And for the rows, it's an array of arrays. So on the last step, what we are gonna perform is, we are gonna execute a map of this result set, and the instantiate method is the one that is gonna create the instances for each one of the records that we are pulling off the database.
17:31
So the instantiate method does a few things. The first thing is that it needs to allocate the object, like actually create the object off the user model,
17:42
and then it needs to initialize the attributes. And this is an important step. Actually, the code for initializing the attributes is the one that you are seeing here. The first thing that ActiveRecord needs to perform
18:01
is to define all the accessors and the helper methods for the attributes. Like, for example, this is the first time that we use this model, so it doesn't have like a dot name or dot email, and this is where those methods are gonna be defined.
18:22
Inside of the model, there is a flag that says if all the attributes have been initialized or not. If not, then there is something that runs and creates all these methods and all the query methods also. The logic for this is, if you want to look into the source codes, is the attribute methods.
18:41
Actually, the logic is very dense, and it needs a talk by its own, because ActiveRecord is performing a lot of work in there. But anyhow, now that we have all the attributes, the next thing that we need to set for the model
19:00
is to create this lazy attribute hash. This lazy attribute hash is, every time that we call like user.attributes, actually we are delegating that call into this class. In this class, it's the one that knows what is the data that our model is containing or that our object is holding on.
19:25
Fine, don't worry about it. If we inspect this lazy attribute hash, we're gonna find out that it has two main variables here. The first one is the types. Do you remember at some point, I told you that once that we get the schema,
19:40
we need a cast type, and this cast type is the way that would allow us to convert the data from the database into a Ruby type. So here we have this hash, where we can see each of the attributes, what is the cast type that is gonna be used, and also we have the values. The values is where we are actually holding the data as it comes from the database.
20:01
There is no transformation here. There is nothing that we haven't made to the data yet. It's just the raw data that we get from the database. The name of this class, lazy attribute hash,
20:21
it has a meaning because let's say that our model has 10 attributes, and we do user.find one, and we get the model. So if we wanted to cast all the data for the 10 attributes,
20:41
just as soon as we get the data, obviously we're gonna spend time over there just doing the casting in order to prepare the data to be used by us. But for example, what lazy attribute hash is doing is that it is delaying that. If we just do user.find, and number one,
21:01
we're gonna get the model, but no casting is going to be performed until we do something like user.name, and we try to pull the name, then that's when the lazy attribute hash is gonna perform the casting. This is the same for the rest of the attributes.
21:21
So once that we do something like user.id, as you can see, there is another variable inside of the lazy attribute hash, which is the delegate hash. And this one is actually containing the value that it comes as a raw value from the database, and the final value as it was casted.
21:43
For example, if this were the example of the timestamp, we're gonna see on the value before cast that it was just in the string, and the value will be the actual Ruby type that represents the things. Things go wrong because they during. So yes, it's working lazy.
22:02
So finally, after going through all these hoops, ActiveRecord was able to perform the query that I asked for, and I got the expected result. So as you can see, there is a lot of work. The dash dash mountable flag at the end of the first line
22:22
turns it into an engine. Go from just class user to actually have an ActiveRecord perform something for us. So what we learn here, the first thing again is that there is no magic.
22:41
What ActiveRecord is doing actually a very cool thing. Obviously, what I showed you here were mostly the highlights, but following the code is not that hard. This is actually pretty easy to just pick up one of the query
23:00
and try to follow up whatever ActiveRecord is doing underneath. Another thing that I learned when I was doing this is that ActiveRecord is gonna try to cache and be as lazy as possible. Every time that we work with the models, if there is a work that it doesn't need to be performed
23:20
just in that parent directory, ActiveRecord is gonna just put in a hole and then do something else until we actually require that. And if there is a chance for ActiveRecord to actually cache whatever we are doing into the different operations, then ActiveRecord is gonna try to cache that information for us.
23:42
As I said, following the code is not that hard. We don't really need to go deep into understanding how all the things work in the framework, but obviously we need to have some sort of curiosity
24:04
with the tweaks that we use every day just to get a great idea on what is going on, how things work. I got interested in this because I was writing a blog post
24:21
probably two weeks before the first beta one of Rails 5 hits. So I was going through all the different pull requests on GitHub and I found out a lot of things that were happening inside of ActiveRecord and that's how I just started off with the curiosity
24:42
to see what is happening inside of ActiveRecord every time that we run a query. So the first thing that you'll notice thank you of course it is a gem we do have our gem stack
25:03
so this is kind of like the I don't know if you have any questions oh and sorry one more thing to mention about that
25:25
gem spec kind of important oh yes if there is any other resources other than just going directly to the code if you want to learn or to understand what is going with
25:42
ActiveRecord sure there is there is an IRC there are google groups also going directly to to the github and we're going to be working with the gem spec as we go on if you look in lib
26:05
back and everything's going to be named which is why you have back there everything in this engine is going to be name-spaced and and we'll see how that plays um and before there's an Rails engine that's that's the thing that tells Rails like hey treat this as an engine
26:27
perhaps that would be which you'll see not on a real location but on your engine right we have our lambda no sorry I never been in that in that specific uh situation
26:43
when uh these requests it be yes sorry level like like my own so we're going to work with the file a lot in the app