Comparing ActiveRecord and LuckyRecord
Tags: crystal, ruby, lucky, activerecord
Published: 2018-06-29 18:39:20

---
title: Comparing ActiveRecord and LuckyRecord
published: true
description: Comparison between ActiveRecord in Ruby and LuckyRecord in Crystal
tags: crystal, ruby, lucky, activerecord
---

TL;DR LuckyRecord was a great move, and is getting better all the time.

This isn't really a tutorial, but more just thoughts between how it feels to write code with ActiveRecord as my ORM VS using LuckyRecord as my ORM. They each use different patterns, so the thought process between the two are quite different. 

I'm using postgres, and I have a database already setup with a `users` table that looks sort of like:

```sql
CREATE TABLE actors(
  id SERIAL PRIMARY KEY,
  name character varying,
  slug character varying,
  created_at timestamp NOT NULL DEFAULT now(),
  updated_at timestamp NOT NULL DEFAULT now()
);
```

## Life with ActiveRecord


The gems needed are

```ruby
# Gemfile
source "https://rubygems.org/"

# This comes with rails
gem 'activerecord'
gem 'pg'
```

Then to break out all the bits in to a single file

```ruby
require "pg"
require "active_record" #c'mon rails, get your naming together >_<

ActiveRecord::Base.establish_connection(
  adapter: "postgresql",
  database: "my_database_name",
  username: "postgres",
  host: "localhost"
)

class Actor < ActiveRecord::Base
end
```

Now we can use our model to search and manipulate.

```ruby
person = Actor.find(1)
puts "They call me Funky #{actor.name.capitalize}"

person.name = "Chris Pratt"
person.save
```

We find a very specific record by an ID, and then we can manipulate that record. Great stuff... That is, unless there isn't a record returned from the database. With ActiveRecord's `find` method, it will raise an exception when the record isn't found. This example is a bit contrived, so in reality you might have something more like

```ruby
actor = Actor.find_by(slug: params[:slug])
puts "They call me Funky #{actor.name.capitalize}"
```

Where `params` being some input that comes from the user. Maybe you have the ID in your URL, but IDs in a URL can be leaky metadata, and not look as nice, especially for SEO reasons. So in my case I have slugs which are parameterized versions of the name like `chris-pratt`. This way the URL reads more like `/actors/chris-pratt` vs `/actors/145432523`.

With ActiveRecord's `find_by` method, it will return `nil` if no record is found. This would cause an issue in the above example. You'd see a `Undefined method name for NilClass` error.


```ruby
actor = Actor.find_by(slug: params[:slug])
if actor.present?
    puts "They call me Funky #{actor.name.capitalize}"
end
```

Ok, now it won't perform our action unless we have a record. Alternatively, we could use the convention `find_by!` to have it raise an exception if no record was found. In a legacy app, you may find a mix of these two conventions (like in one of my apps). 

We have taken the time to catch when the record exists or not, but one thing we missed is that with the old schema, it was `first_name` and `last_name`. At some point, it was merged in to just `name`, but that field was never set as being required, or had any validation on if it should be unique. I guess it is possible 2 actors could have the same name. Like Michael Jordan (Basketball player / Space Jam), and Michael B Jordan (Black Panther's cousin), or Kate Hudson (Goldie's daughter), and Kate Hudson (Katy Perry's real name).

In this case, we now are noticing sometimes our actors index page blows up with `Undefined method capitalize for NilClass`. That damn NilClass error again! This leaves our code riddled with `name.try(:capitalize)` and my favorite `name&.capitalize`. It works, but it's really starting to get messy catching these fires in bugsnag and deploying every day with some "nil catch fix".

Aside from all the bad data we have to deal with, we have some serious speed issues. We have an index page that lists all the actors. This list is sorted by the latest movie release. So the first actor on the page is staring in the most recent movie. We also have a lot of different sites that display different types of movies to the audience. Certain movies will be displayed on certain sites (Think kids movies on a kids site, comedy movies on a comedy site, etc...). This structure gets a bit confusing.

```sql
SELECT actors.*, movie_releases.latest_release_date FROM actors
INNER JOIN performances ON performances.actor_id = actors.id
INNER JOIN movies ON movies.id = performances.movie_id
INNER JOIN movie_releases ON movie_releases.releasable_id = movies.id
WHERE movie_releases.genre_site_id = ?
ORDER BY movie_releases.latest_release_date DESC
```

With ActiveRecord, when you have a gnarly query like this, it's going to generate an ActiveRecord object for each record it finds. This page for us loads in about 12 seconds before we add in a massive cache mechanism. We're using [Varnish](http://varnish-cache.org/) to handle this.There's a whole slew of other issues around this.

## Life with LuckyRecord

Ok, so LuckyRecord isn't written in Ruby, it's written in [Crystal](https://github.com/crystal-lang/crystal). Now, it's not really fair to compare Ruby to Crystal when it comes to speed. Crystal is compiled, and Ruby isn't. But since Crystal is compiled, we know that we get speed out of the box. This should fix our issue of long load times, and allow us to get rid of the heavy caching stuff. In turn, this should save us money.

Why LuckyRecord? If there's so many other frameworks and stuff, why not one of the others? Well, Lucky is written by [Paul Smith](https://medium.com/@paulcsmith) who happens to work for [ThoughtBot](https://thoughtbot.com/), and they are a pretty big part of the Rails community. That means I can at least trust the code to be pretty solid. Secondly, it wasn't our first choice. Our first choice we couldn't get booted. Docs were bad, and it felt too clunky. We built a proof of concept in a few different frameworks, and the Lucky app was the only one that got from A to B without us throwing a laptop against the wall. I'm sure if we had started that today, things might have turned out different since everything else has made so much progress. In any case, we're pretty happy with our choice, so it worked out.

Let's look at the setup above using LuckyRecord.

You'll first need to add these to your `shard.yml`

```yaml
dependencies:
  pg:
    github: will/crystal-pg
  # these come with lucky
  lucky_record:
    github: luckyframework/lucky_record
  lucky_inflector:
    github: luckyframework/lucky_inflector
```

And now to set it up.

```ruby
require "pg"
require "lucky_inflector"
require "lucky_record"

LuckyRecord::Repo.configure do
  settings.url = "postgres://postgres@localhost/my_database_name"
end

class Actor < LuckyRecord::Model
  table :users do
    column name : String?
    column slug : String?
  end
end

class ActorForm < Actor::BaseForm
  fillable name
end
```

First up you'll notice a few things. Lucky breaks each component in to smaller components (similar to rails). This means if you wanted to use LuckyRecord outside of Lucky, you're gonna need a few bits. Next we'll see that the schema is right there in the model. In Ruby, I remember using MongoDB one time, and having to setup the schema in the model. I really like that because the model is the first place I go for all my model needs. Lastly you'll notice there's an extra class here with the `ActorForm`.

### Forms

With ActiveRecord, your model connects to the DB, handles queries, business logic, and all kinds of stuff. Most rubyists will abstract out these patterns and add in Mutations, Serializers, and other patterns. This is great, except developers tend to be overly opinionated when it comes to patterns. This means that less and less you start to see these rails apps looking more and more different from each other.

Lucky sort of brings that approach back from classic rails since devs are breaking things out anyway, why not just do that from the beginning? The `ActorForm` is used for creating and updating your records. Imagine you have a form that the user is allowed to enter in data and create/update records. LuckyRecord gives you a safe way to allow which fields they can update, and a place to put all the logic around creating and updating records. This is nice because maybe when you create one record, you also need to create a few others. Now your model isn't responsible for the creating of other models. It's a form object that responsible for that form a user uses to do what it needs to do.

This also lets you create other forms like `AdminActorForm`, or whatever. Maybe you have users with elevated privileges that can do more things with records. It would look like this:

```ruby
actor = ActorForm.new
actor.name.value = "Chris Pratt"
actor.slug.value = "chris-pratt"
actor.save!
```

Of course you can do validations and all that fun stuff too. The main thing is that this is all handled outside of the model. 

Ok, so what about querying the model?

```ruby
query = Actor::BaseQuery.new.name("Chris Pratt")
query.map(&.name) #=> ["Chris Pratt"]
```

LuckyRecord gives you a few namespaced classes out of the box when you define your model. One for the Form object, and another one for the Query object. This allows you to store all of the different SQL queries you need. You can create a child class that has these all setup for you!

```ruby
class ActorQueries < Actor::BaseQuery
  # SELECT actors.* FROM actors WHERE actors.name = ? ORDER BY actors.name ASC
  # returns Array(String)
  def self.by_name(name : String)
    self.new.name(name).name.asc_order.map(&.name)
  end
  # or a chainable query method
  def by_name(_name : String)
    name(_name).name.asc_order
  end
end

ActorQueries.by_name("Chris Pratt")
```

The nice thing here from the crystal side is that if the compiler catches a spot where you might potentially pass nil to this method, it would throw a compile-time error. `No method overload matches by_name for ActorQueries with compile-time (String | Nil)`. Or at least I think that's how the error reads 😅. The basic idea is that the compiler says that the method signature only takes a String as an argument, but you have a spot in your code that could *potentially* be nil. 

The whole ideals around Lucky is that all of these error you normally see in production you'll see in development before your app even boots. The abstractions, and different use of objects even down to how Lucky has you create the HTML is all designed so things are just rock solid in production. 

Of course there's always going to be a few things that slip through, but it's a TON less than it would be in Ruby. 

## Recap Time

There's some pros and cons to this, so let's list them out (in no particular order).

1. Lucky catches compile-time errors so you have less issues in production
2. Crystal gives you speed from being compiled
3. LuckyRecord breaks out logic in to smaller components to make testing easier, and focusing on what matters
4. LuckyRecord ONLY supports postgres. You can use Lucky with a different ORM if you need MySQL, or something else.
5. LuckyRecord takes a small performance hit for how it builds queries. This may improve later, but it's not a huge deal. It's still about [12x faster than ActiveRecord](https://github.com/jwoertink/crystal_orm_test/#results).
6. Having all these compile-time errors slows development down a LOT. This isn't really a con because you have to pay a price either way. The choice is development, or production.
7. There's some features lacking in LuckyRecord currently, but as the whole crystal ecosystem is still new, you'll get this will anything you try to use. In fact, I've used all of the ORMs in crystal, and NONE of them do everything we needed for our app.
8. The [docs for Lucky](https://luckyframework.org/guides/overview/) are really good. And really pretty! (yes, this matters).
9. The [community for Lucky](https://gitter.im/luckyframework/Lobby) is also really great! Help is usually very quick.
10. I like tacos.

Thanks for reading!

(authors note: this article was written 9 different ways with different thought processes over a few days. If it's confusing, let me know so I can make it more coherent)