Lucky, an experimental new web framework by thoughtbot

Paul Smith

Ruby and Elixir make it fun to write beautiful code, but I still see bugs in production that could have been caught with a better type system.

Rails is productive, but I quickly run into speed issues that require view caching.

Then there’s Elm. It’s beautiful. Fun. But only available on the front-end.

Where’s my Holy Grail!

Enter Lucky, a new web framework and ORM written in Crystal

lucky logo

The goal: catch bugs early, forget about most performance issues, and spend more time on code instead of debugging and writing tests.

Lucky is an experimental framework built to achieve these goals. It uses Crystal — a beautiful, fast, and type safe language with a syntax unabashedly inspired by Ruby.

Lucky leverages the type system and meta programming in Crystal to help you create web applications quickly, while maintaining performance and catching subtle bugs that you’d normally miss.

Yeah yeah yeah, show me how it works already!

In this post we’ll be talking about the ORM, but sign up at luckyframework.org to hear about new posts, guides, and future releases.

The other day I reviewed some code that had a bug but neither I nor the author saw the bug. We wanted to reformat fax numbers to remove the US country code at the beginning.

def formatted_fax_number
  # fax_number is a method generated by ActiveRecord
  fax_number.gsub("+1", "")
end

We deployed to production and got our favorite error: Undefined method gsub on Nil…great.

With Lucky this bug would have been caught at compile time:

# Define a model
class Facility < BaseModel
  table :facilities do
    field fax_number : String? # Adding ? Makes this nilable
  end

  def formatted_fax_number
    fax_number.gsub("+1", "")
  end
end

When we declare field fax_number : String? We are saying that the fax number might be nil. So when we try to call gsub on it the compiler will helpfully tell us: Method gsub does not exist for types (String | Nil)

Instead we can change it to this:

def formatted_fax_number
  fax_number.try { |number| number.gsub("+1", "") }
end

Bug removed!

Flexible type safe queries

So how do we query the database with Lucky?

# First set up a model
class User < BaseModel
  table :users do
    field name : String
    field age : Int32
  end
end

When we define a model with table a User::BaseQuery class is added. Let’s see how we can use that:

class UserQuery < User::BaseQuery
end

user_query = UserQuery.new
user_query.name("Paul") # Query for users whose name is "Paul"
user_query.age.gt(30) # Query for users whose age is greater than 30

Because name is a method, you don’t have to worry about renaming the column or having a typo in your query. It will fail to compile if you do.

You also will get a compile time error if you accidentally pass something to it that might be nil or that is not the correct type for the field.

Type specific query methods

You’ll get methods specific to the type of the column:

# Strings get things like `lower` and `ilike`
UserQuery.new.name.lower.ilike("pa%") # Will find users with a name like "Paul"

This makes for incredibly flexible, type safe querying. That means less time debugging and less time writing tests since you can be confident your queries will work.

Scopes with plain old Crystal methods

It’s easy to extend queries by using regular Crystal methods

class UserQuery < User::BaseQuery
  def adults
    age.gte(18)
  end

  def search(query)
    name.lower.ilike("#{query.downcase}")
  end
end

UserQuery.new.adults.search("Paul")

Everything is type safe, and the methods are regular Crystal methods. This makes it easy to understand and easy to extend with modules or classes.

What’s next for Lucky

More blog posts and guides are coming next so you can start playing around with Lucky on your own projects. Soon we’ll build some internal projects in Lucky to help flesh out corner cases and areas where Lucky can improve.

Sign up for updates on new posts, guides, and Lucky releases

Sign up at luckyframework.org. We’ll let you know about new blog posts, new release and when we have the guides section up so you can start playing with Lucky on your own.