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!

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.

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.

# 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!

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.

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.

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.

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 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.