Writing your own ORM in Ruby24 Aug 2019
In this post, we’re going to write a basic ORM (Object-Relational Mapper) in Ruby. If you don’t know what an ORM is, or never used one before, I recommend reading this article before. Our ORM will implement the Active Record pattern. This post assumes you know about Ruby, Gems, Bundler and SQL.
Our ORM, let’s call it LiteRecord , will be very basic and will only support SQLite3, but will be good enough for you to understand how an ORM works under the cover and from there, you can build a more powerful one.
We will package our code into a gem, to make it easy to install in other projects. Go ahead and run:
$ bundle gem lite_record
We now have a folder (lite_record) where we’ll put our ORM code.
sqlite3 in your terminal, to make sure you have SQLite3 installed on your machine. If you don’t see a prompt, then you’ll need to install SQLite3 first (See how to here). Now, let’s install the SQLite3 gem to be able to use SQLite3 from our ruby code. Open the Gemspec file (lite_record.gemspec) inside the lite_record folder, and add this:
bundle install to install the Gem. We will need a database sample for testing our work, during development. In yout terminal (from the lite_record folder), run:
$ sqlite3 $ .open test.db; $ create users(id integer primary key, name varchar(15), email varchar(30));
You just created a database (test.db) that contains one table (users).
Now that we’re all setup, let’s get to the fun part.
LiteRecord needs to know which database it will work with. Open
lib/lite_record.rb and add the following code in there:
In this code, we add a method (configure) to the LiteRecord module. That method receives a path to a SQLite3 database, and then creates and store a connection to that database. That will be very useful to us, later. This is an example of configuration:
The Base model
lib/lite_record/, create a file and name it
base.rb. In this file, we will create the base model class LiteRecord::Base. This class will provides the methods and attributes needed by a typical model to interact with a specific table. Every model classes in a project, will have to extend LiteRecord::Base.
In this class, we create the constant DB, which contains the connection instance created in the configuration step. Also notice, the attr_accessor: table line.O On this line, we allow the model class to specify its mapping table name. This is an example of a model using LiteRecord:
We want a method that takes a hash of values and create a new record in the database. The hash keys will be the table columns names, and the values will be the new record values. Update lib/lite_record./base.rb, with the following code
The table_columns method uses the SQLite3 table_info pragma, to retrieve and return the table columns list without the id column.
The convert method takes a value and transform it into a suitable format for SQL. If the parameter is a string, it puts it inside quotes and if it’s nil, it replaces it will null.
We execute a SQL insert statement to create the record, and the use SELECT last_insert_row_id to retrieve the newly created record id.
At the end we return a new instance of the class with the columns values as attributes.
We want to retrieve a record (as an object) from the database with its id. Let’s update lib/lite_record./base.rb, with a find class method at line 20
It queries the database with the given id and instantiate a new model object with the query results (which will be a hash).
We want to know the total number of records in the table. Let’s update lib/lite_record./base.rb, with a count class method at line 24.
The count method simply runs a SELECT count query and returns the result (which will be an integer).
We have a model object, that exist in the database or not. When calling the save method, a new record should be created in the database if it didn’t exist, otherwise the existing record should be updated with the new values. Let’s update the base class, with a save method at line 58.
First notice the  and = methods. These methods make it easy to read and write the object attributes hash values.
The save methods first check if the model object has an id attributes. If it doesn’t have one, that means the record hasn’t been created yet, thus we call the create class method, otherwise we execute a UPDATE table query to update the record with the same id.
We have a model object that represents an existing record in the database, and we want to delete that record. Let’s add a destroy method inside our base class, at line 66.
In this method, we directly run a DELETE FROM query in the database to delete the record with the same id as the object id attribute.
Now we have a very basic ORM, unless it’s not usable in a project yet. Open the Gemspec file of our Gem and follow the TODO comments to update the metadata informations such as the Gem description, name…
Now, in your terminal run:
$ gem build lite_record.gemspec $ gem install lite_record-0.1.0.gem $ gem push
The full code for this ORM, is on Github. Click here.
Thanks for reading and don’t hesitate to provide (good or bad) feedbacks.