funsies

Use your database with childlike enthusiasm. Its more than fun– it’s funsies!

A fun, type-safe ORM for Gleam! (Work in progress, expect big changes! ALSO ASK QUESTIONS IF YOU WANT/NEED/ARE INTERESTED IN ANY OF IT!)

Now with a CLI! Now, have your schemas automatically generate types and decoders!

These docs are a bit out of date. Will soon be updated.

About

Funsies is a work in progress.

My goal with funsies was to create an easy-to-use, friendly ORM for this magical language we call Gleam.

I think that Gleam has a ton of potential for the web (with erlang and javascript targets). But, to date the DB landscape in Gleam is wonderful, but a bit bare-bones.

There are a few great projects (like squirrel) for using raw SQL, and some libraries for generating queries. There were not, however, many libraries for real, full-featured ORMs.

That’s what funsies is for!

Funsies has awesome code-gen abilities, and type-safety, and is (in my opinion) fun to use.

Usage (with CLI)

Funsies works based on Schemas. These schemas are used all the time when using funsies.

In the pre-CLI days, you would have to use the provided functions to generate the types and decoders.

This came with a couple of challenges:

Now, we have a CLI to manage the hard parts! All you do is create a src/schema folder. In that folder, you would create a file along the lines of {schema_name}.gleam and inside you would make a public function like this:

pub fn {schema_name} {
{schema}
}

Where schema is the schema you would normally put in your code, and the schema name is the same thing that you put in your file name.

Now, run gleam run -m funsies and it will magically generate a new folder (src/funs) with files for each and every one of your Schemas!

Usage

funsies is designed to be used with the gleam_pgo package for connecting to a postgres database. Currently, it only supports postgres database. Eventually I plan for it to support many DBs.

To use funsies, clone the repo, add it to your project, and read through the tutorial below.

Tutorial

First, set up a database object with the gleam_pgo package.

  let db =
    pgo.connect(
      pgo.Config(
        ..pgo.default_config(),
        host: "localhost",
        database: "your_db_name",
        user: "db_username",
        port: 5432,
        password: option.Some("db_password"),
        pool_size: 15,
      ),
    )

Now, the fun begins!

When using funsies, you will be working with Tables. Tables are made using the schema module, which provides a DSL for building gleam structs that fully represent the table in the database, and are used all over the ORM.

To create a table, use syntax like the following:

let user_table =
schema.create_table("table_name")
|> schema.add_serial_column("id") /// This is a column that autoincrements on insert.
|> schema.add_string_column("name", 255) // this represents text
|> schema.add_int_column("age") // this represents an integer
|> schema.add_bool_column("is_active") //this represents a boolean!

This table object is POWERFUL. It is used to generate types, to perform queries, etc.

To use this table, we can first perform a migration to create the table in our database. We need to generate the SQL, and then run it (this part I eventually want to make more seamless. Add a PR if you want to help!)

let user_table_sql = schema.generate_create_table_sql(user_table)
user_table_sql
|> pgo.execute(db, [], dynamic.dynamic)

Now, we have a table in our database!

The next step is to generate some types and decoders. This, as mentioned earlier, uses the table value!

Decoders and types are generated like this:

import db/decoder
decoder.generate_row_type(user_table)
decoder.generate_decoder_code(user_table)

Then the magic happens! Automatically, you will have a type and decoder generated for you! They will be in the src/schema directory, under files named after the table. The type will be named {table_name}Row{columns}, and the decoder will be named {table_name}_decoder_{columns}. (ALSO WORKING ON THIS TO MAKE NICER NAMES)

Now, we can import our generated types and use them to create an ORM.

import db/orm
import schema/users6
import schema/users6_decoder

let user_decoder = users6_decoder.users6_decoder_idnameisactive()

let user_orm =
  orm.orm(users6.Users6RowIdNameIsactive, user_table, db, user_decoder) // this is the ORM! The IdNameIsactive is the columns of the table we made earlier. This is so we can have many different types/decoders if we want to select only some columns!

Now the ORM can be used to perform queries!

let fetched_user_result = user_orm.by_id(2)

io.debug(fetched_user_result)  // This will be serialized into the types you generated earlier! No more messing around with `dynamic` types!

let all_users_result = user_orm.get_all()

io.debug(all_users_result)

let new_user_values = [pgo.text("Asher"), pgo.int(15), pgo.bool(True)] // no ID since auto-increments!

let insert_result = user_orm.insert(new_user_values)

io.debug(insert_result)

Magical, right?

Finally, we can use the yum DSL to build more complex queries.

import db/query/yum

let query =
  yum.new("users6")
  |> yum.select(["name", "is_active"])
  |> yum.where("is_active = TRUE")
  |> yum.to_sql()

This will return a string SQL query. To use it, you can generate new types using only the columns you are returning and have it decode into that decoder! Or just use the dynamic type if you want :)

Contributing

PLEASE CONTRIBUTE! I am new to Gleam, and if there are any improvements, docs, tests, new features, improvements of current features, or anything else you want to contribute, PLEASE make a PR! I would LOVE the help.

THANKS SO MUCH!

Search Document