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:
- Where do I keep my schemas? Are they separate from the rest of my code?
- How do I create the types and decoders? Should I write a script? Just call a function? Or, worst of all, write it by hand?!
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!