About the talk
RailsConf 2019 - ActiveRecord, the Repository Pattern, and You by Craig Buchek
ActiveRecord is big. You just won't believe how vastly, hugely, mind-bogglingly big it is. It does a lot. Many would say it does too much. For me, the biggest issue is that it combines 2 distinct pieces of functionality — modeling the domain, and abstracting database access.
For years, I've dreamed of separating these 2, while still staying on Rails' golden path. I finally found a way to do that. The benefits are high: faster tests, simpler code, decoupling from the database, automatic schema migrations — without much downside. Let's take a look and see if it might be for you and your project.
All right. I had a minor tragedy. I lost my dongle for my clicker little bit late, but let's get started. Today. We're going to dig into active record in lower right into their has went to the slides if you want to follow along if you have breast feeding system presenter notes, and if I actually get some links and notes that I want cover in the talk my Twitter Handles in the lower left corner, feel free to tweet at me or about me and use the hashtag real count. Still will focus on two major issues with active record look at
some Alternatives, but then we'll talk about why you might not want to use those Alternatives and then we'll talk about a potential solution and a pattern that I think we should all be using. Who is who here uses active record? Okay, most of us who hasn't use active record. All right. I don't see any cancer that whose use a different Ruby worm. Couple hands. How about a non Rubio term in a different language? a few hands who hates active record Anyone else have a love-hate relationship like I do. Alright actually more won't hurt her that so active records of 800 pound gorilla. And now it's so
hard to get hired hand rails. You're going to be using active record. First I want to make sure that everyone knows what a norm is. I'm so groovy deals with object obviously and SQL databases deal with relations. It's actually something called relational algebra that they work with sounds pretty cool. I'm not sure there's any mathematical foundations for for nosql databases these two sides together in master tween objects in relations know that there was an impedance mismatch between the
two sides would were swelling one side and might not work. Well on the other side. So some straight data structures camping map of 1210 examples of this is a tree structure really easy to do a noob but there's several different ways to do it in a relational algebra and it's difficult to match between those two different ways. I gave it to a Rubicon 2015 where I do have more in-depth into what a norm is sort of found the essence by building 1 in 400 lines. So real that can record is
based on the active record pattern here is Martin father's definition. I'm not sure if he's the first one to come up with it, but he did document it in the patterns of Enterprise application architecture. Note to do list three separate things here wrapping a dove taste database table encapsulating database access and any anime logic now, you can argue that rapping in the capsulating are pretty much the same thing. But the main logic is clearly a separate concern having that and in there is an indication that we might be violating the single responsibility
principle SRP. So here's are you allowed to use a diagram or class diagram of the active record pattern? Note that there are three different kinds of things going on fine and save deal with persistent storage there above that line and below that line. We have named age and address the deal with the main logic. Biggest problem with active record I have that isn't it encourages bad engineering project have it's mostly cuz it violates the thing of responsibility principle that persistence and that to me magic. Separation of concerns is important
just like rails separate the MVC model view controller into separate concerns. We should probably be doing that with the model itself. And as your brother gets bigger active records flaws become more apparent. I find it in about 12 to 20 model classes. It starts to hurt. Where is if you're below that probably doesn't really matter to you. Active record is Big it's about 40% of rails and that size is another symptom of the single responsibility principle possibly being violated tries to do too much in one place and it translates as multiple
concerns. So I'm doing some stats to hear from reels 5.23 model with one field and that came into over a hundred instance methods and 600 class methods. For comparison object has 86 message itself with string and Ray have about 250 methods. I'm so the number of instances is pretty bad. You're not going to remember most of those for a lot of those and Grand some of those are Dynamic you're going to know that if it has a field is going to have a few things related that there's a lot of
things going on. The number of class methods is really bad. So class methods have several issues which Which I think I will covered later. All right. So the other thing I find super frustrating about active record is relationships or associations are defined in the model. Like it has many belongs to but attributes are defined in the database schema. I think that's a terrible abuse of the dry principle. Dry says there should be only one place to look for any piece of info. I feel like attributes and relations are similar kind of thing that you should look in one place for and the clue
is a half an hour to see them at the same time. Between those related things in different places seems really counter to the to the dragons. So you have to look into places for all the details about a model. This is a case where there's too much magic for me. I understand workarounds. There's a model annotations Jim. There is an atom package to show taco from the Cena button broken for me. So after me safety, I actually came out in 4.2 but it wasn't publicized into real five, but we have to use it in
hardly. Anyone. Does anyone here actually use annotations in their models and in active record. Decent, all right, that's good. Thank you. So I have to release a couple gems actually to to let you to find active record models before the battery safety. I was a thing one was called virtus active record. I didn't seem to stop by Uncle Bob architecture the last year's. A couple anyone lucky enough to have been there. All right, one other person I was there. I'm the person that asked him a question.
Can you show us some code? And he said you got to figure it out yourself. You can hear me on the video talk. It was only giving it at once at a Regional Conference. I'm not sure why maybe cuz he didn't give us details and I called him out. I don't know probably not affected by that and probably even earlier. I struggle to find a way to get rails to implement all those architectural suggestions my last project we use the interactive Jim to do the interactive
part exactly on the the chart. They're up there his diagram and that splits the rails controller from the business logic according to this talk. The fact is our app is a web app is sort of incidental and so we should have the business logic separate from that incidental delivery mechanism and interactive can get us that there's a pretty good interactive Jim and it works pretty well for that part, but I've never found a great answer for splitting into these in the database, which if you look there he's got in and he's on the right side and then an entity database are any of the
Gateway in the database and this is a quest that I'll be talking about today. So after almost ten years from that talk and Kabob wrote a book on the topic, clean architecture is a pretty good book. The details. Bob also has a Blog article called clean architecture. It's got a pretty good since thanks exclamation. Sarah the first stop of my quest is the sequel orm. I will not pronounce SQL to SQL cuz then I would get really confused. This is the biggest surprise when I did research for an earlier related talk and it's written by Jeremy Evans. Is he having to be here today? All
right. Now I don't see him. So he was winner of a ruby hero award a few years back sequel has tongue the plugins and they leverage lots of data base features, especially for postgres supports almost any SQL database you can think of and I really like the documentation. People have two different API since you can use its got the data set and the model apis. Kissing code used to set up the sequel for the next couple of slides not a lot going on here. Pretty much just creating a table. Simpson SQL syntax is really nice. Here's the data said
looking like for nothing the block lets you use bear calling names and a greater than that's pretty cool active record does not let you do. That although there is a squeal Jim that as that's where the feature the problem is. It doesn't stay synced up with active record as well as would like and I think I'm running a few other bugs than problems. Data sets are innumerable with each element as a hash light has two hash like object. You can see that being used in like 5. I haven't come across anything. That seagull can't do well which is pretty cool. Actually. It just doesn't fit the
pattern that I'm looking for. So here's a higher-level API the SQL model and it's using objects instead of just a hash like object and you probably be more likely to use this layer and rails Wii we like to use object-oriented. Like active record attributes of derive from the database schema, but also like active record relationships have to be specified manually and getting that thing that really frustrates me. I really like the sequel. I wish I could record was more like SQL actually the people
doesn't solve the problem trying to address. So next door man. My German journey is ROM the Ruby object mapper. This was originally meant to influence the data data mapper pattern of the data math Library when you use it. I'm so originally this is Dad about for 2 and 2013. They renamed it the wrong and 2014. They moved away from object relational mapping all together. So it's not really technically Norm it just map to Dad and not the object. Most of the work is done by Peter solica like ants try to pronounce it in his language piotr Peter. I
believe in English. It's similar and spirits and partly inspired by elixirs acto acto. All right. Good number. All right, so you guys might find this the little little more palatable than I do. So formally virtus a really nice attribute declaration Library. ROM is a bit complex to use. It has commands relations mappers and you have to buy into this completely different Paradigm in mindset. ROM developers are responsible for the dry RB Library switch will actually talk about a little more. It's
really good small independent low-level composable libraries some of the leaders of this movement towards functional programming and immutability and Ruby are are part of this dry RV and in the wrong team. I find them a bit focused on the load level details too much and I think that's why it takes him a long time to get their product out. But once it's out, it's really high-quality code. So romulation, this looks pretty straightforward and we've got we've got to a model class called user that empty and then we've got
a user's class which is a romulation and we Define the attributes there and schema block and then we have the association's in in a Cell Block of that kind of like that. That's nice and then I have to find a physically with the scope in there. We can also tell Ron to pull the schema from the database. We would replace the schema block with a schema infer true, but then we went to the all the attributes and it seems like this is the preferred way showing all the attributes in in the Declaration. Are saving object we start with the relation.
We we knew up a relation object. We passed that to a change that you know after this feels really familiar. It includes the create or an update. I don't know what happens if you get it wrong and it passes all the attributes of the hash. So, like I said, we're not really dealing with objects. You have to you know, do your object to hatch if you were dealing with the Ruby object and then we have to explicitly commit the changes. I think that might be nice. I'm not sure. So I found Ron to be really complex. Here's an overview diagram of of their architecture.
Honestly, I can't follow everything that's going on there. What's that sound that confusing? I really want to like rum, but I found it too complex and confusing. I couldn't actually get things set up right to run the code that I showed you in those previous examples. The last of my quest is the model are for Nami. Nami is a full web framework and I'll turn it into rails. I have like everything I've seen if if I had a choice, I'd probably choose to Tsunami instead of rails at this point just to try some side projects. So Hanami supports SQL actually be a sequel
memory and file adapters. It follows the data-driven design architecture. I'll talk about that a little bit more. So it has entities which are models without resistance or validations. It's got a repository which is mostly like a class methods in active record model classes. So things like for a rate update for sis to delete or all fine first and last for which is the Declaration of how to map between database records and object attributes. Here's the first part of a Konami model. So we inherit
from her mommy and daddy which surprisingly ads only four methods. At least the last time I looked at as ID the Eagles initialize and then a class method called attributes that what they were using their and that's all I had that's pretty cool initializing takes the cast of attributes. Does that all the in turkey and some cheese attributes and types come from they the dry types Library so that types colon colon ant types of colon colon string. Those are dry types. We could again let the model for the schema from the database like active record, but I don't think that's that common.
So persistent is done by the repository class know that things like we're in order here are private. We can only use them within that query there online 3. Queries are analogous analogous to the Scopes in active record and hear were using it. We knew up a an article from the article repository we created and then we we can go find it and find find by author. So I think an army is my favorite Ruby alarm if I had a choice then probably use it in rails, but it's not a very realistic option as far as I can
tell it requires everyone on your team to learn something new. It's just you that's not a big deal. If you have a team of I think if we have AT&T on my team probably not going to work also probably don't want to use it on a project that already has hundreds of models. It's been around for I think eight or ten years. So there's not much documentation on using it with rails. The other ones I talked about and really I don't consider using active record most of the time and they may or may not work with another warm. So an Ami
model implement the repository pattern and their problems or a pattern represents a collection of domain objects. And in a lot of ways we can create Creed we can't read the database is an in-memory collection. We can kind of abstract that even more than we do with active record. We do have something similar and active record at the end of that the class methods the create the where the find all and when he greatest scope that's basically also sort of the repository pattern. But again, it's stuck in that in in class methods and in those have some serious limitations at
least a procedural code instead of object-oriented code and offering indicates that you missed in history auction. It limits your polymorphism and it's hard to test and hard to refactor. There's a good article on climate that talks about all the problems with class methods and if you pull up the the talk here and presenter notes. There's a link to it. So here's the uml class diagram for the repository pattern note the arrows. The domain model is not dependent on anything. There's a clear separation of concerns the domain model class name of the business
logic repository class handle the persistence so we can actually end up with more than one for giving bottle. Maybe you weren't Jardin. Maybe you won't stop deleting things in a separate database. Maybe you want retry segmentation. Maybe you want in memory for assistance for tests that uses a different database back or maybe in memory backing. You might see this repository pattern with a third class which we did the map in class that handles the correlation between the database fields and the object attributes.
So I spent several years looking for way to have my cake and eat it too. I want to keep using active record, but I want to separate my domain model from the database persistence. So one Saturday morning, I was in bed staying in bed a little late and I was thinking about it again. Don't ask I don't I don't know why I need to think about those things in bed, but I came up with a solution that I thought I could work and they split out the active record record into several models. And I thought I know I can use those various models that active record uses and split them into the two sides. The
funny thing is I think I misremembered that I think it was action controller that got modularized for breaking into pieces and using the picking and choosing which piece has acted model did get pulled out of active record. I think of that time but I don't think they heard you can use separately wasn't quite as easy to make this work as I expected all the modules have a lot of interdependencies and there's no real documentation on how to use each module and what their dependencies look like. Sober turn out that the domain model. It's just most of active model. So I ended up calling that
active model entity when I originally called it active record in 2D and the positive side is most effective record. I'm still going to show the difference between using standard active record and using active record repository, which is the gem. I'm working on the fusion typical active record model. This should be pretty familiar to you. We have a couple blocks to as many we have a validation and we have a scope and then there's some field that we don't know about by looking at the code, unfortunately. Here's the same thing using active record depository
instead of stop laughing. I'm including a module. This is an interesting little thing that I pattern that I found the modules actually dynamically generated through the call to active record are active model entity method so we can pass parameters and I'll talk about that some more when I talk about the implementation. Let's see you tomorrow. We're mixing is the term energy comes from Eric Evans domain driven design an entity is an object that has an identity so we can have two items with the same attributes of a different IDs and those would
be considered different entities. But if we have to save two items of the same type that have the same ID those would be considered the same thing and there's actually something called an identity map and inactive record the other major difference between as we declare attributes hear the name and types, which fixes my second biggest gripe. We still have the blonds. Do we still have the mini what we don't have is the scope? And if we had any instance methods would it would have us here as well? So who's the repository for that same class and Candor including module
instead of so classy again, we can passing parameters we can pass them out of class or working with by default. I am taking leave the term use repository knocking the depository often and assuming it's user we can specify the database table name if it can't be derived specify a primary key and we can we can supposed to buy a mapping of database column names to entity attribute names. And the scope is on this suppository side cuz it's only deals with the entire collection. Not any individual object. So here's a typical
controller with rails and active record. No, we tell the user model to save itself online for and that will insurance policy if it failed to save. Here's the same thing with my active record repository. Jim only two lines of changed line for we explicitly test and see if the model is valid and and then deal with that and then like 5 weeks positive reinstate a model object instead of telling the model object to save itself is one caveat. If you got a neatness validation that can't be determined until you hit the database still if you do have that you're actually going to have to catch an
exception on on the save. So here's a bit of the implementation of the entity model. So I talked about that pattern the framer ice model pattern. Unfortunately, I think this is the simplest implementation possible. Basically, we create a list of models that may vary depending on what we passed. And then recreated module composed of those those models. So, you know, the the self-composed model module not important to understand more important to understand that we're taking several modules and were composing them together in this. Like I said, why
was the pass parameters? So as I said, we I previously called that active record entity, but we're not using anything from active record. So I change the name you see there. We're just including and extending active model modules. It's hard to say I'm so here's the repository side to get them using that same perimeter as module pattern on but haven't implemented anything on this site yet. This site is all active record plus some custom code. We're mostly making the custom code Mason mostly make sure that active record still works
despite the pieces I've taken away, but it still has used most of active record, but not quite all So we got some help about this be calling it back to record this method lets you do user calling colon repository. Save and then pass a user object. This one's a bit tricky. We have to create an active record model object temporarily to save and then we update the entity's ID. When we save to let us know that the entity has been persisted. This is an implementation of active record are active model persistent? Which I think is required to be an active model Citizen
and for reals to be able to deal with you. They're quite a few challenges quite a few more than I expected probably do the fact that I remember which things kept modularized for a while how to separate the modules. It turned out the inside is all a model and a repository side is all I can record. We're not subclassing active record face and that's her not to be really tricky. I spent hours trying to fix this directly uses that to figure some things out and includes info about the connection to the database and I also had till I can record that the repository class class. Currently
I'm fighting with active record relations. I'm getting an error doesn't seem to be related to the code. I added it makes it really Charter troubleshoot. So I still have a lot of work to do to make us usable. Please do not use this in production. I'm not going to use it in production. Not sure I'll even get to that point but it was fun and interesting to learn that maybe I can make it the main part of testing all the way the relations work like Cascade and deletions loading or at the load all the relations and not them. Object and saving in basically the opposite way
we can do automatic automatically great migrations because we have all the data we need in the model class. So I'll declare their I think I'm missing right now is is indexing actually had that option. If you're in the migrations go see Matt Jasinski stalking migrations, right after this team ain't and calling you mine and covers a lot of God churches with migrations. I plan to look at those couches. If I do get to the automating migrations, it's in the next time slot over in room SF. So I need some help from all of you. If you're interested, please
go start a repo on GitHub. So I know if people are interested more people are interested the more likely I am to complete the project. I'm easy to find on the internet or in person got the Weedmaps t-shirt on today. I made the repo in the stock easy to find and I have wings on everything on the last slide and the links kind of linked to each other. I'd like to thank you all for coming to watching especially like to thank my co-workers the who watch a preview is this talking providing some valuable feedback if you like listening to me, I do a podcast on a hydro call this afterlife
sporadically. I'm not always on but we have resurrected it and we are recording podcast again. A big thank you to my employer Weedmaps for sponsoring this talk about 20 of us here. Most of us have t-shirts on and we are hiring big time. Come see us that are both will have T-shirts. I'm not sure exactly which ones yet. The source of the presentation is it helping my presentations repo easy to find there's a link to the active record repository Jim. You can also find that on my GitHub page. I put it near the top and feel free to
Buy this talk
Access to all the recordings of the event
Buy this video
With ConferenceCast.tv, you get access to our library of the world's best conference talks.