Releases: coast-framework/coast
Changing the name and the version
Why .alpha
Why coast.alpha instead of a semver version bump?
Now, why did I need such a drastic change to coast? Backwards incompatibility is the worst. I always hate it when I update something and it's backwards incompatible, meaning names have changed or been removed altogether. It's a horrible experience. I also hate that once you add something to the public api in your library and you love the name and don't want to just abandon it altogether, you have two options, don't do the things you really want to do, or do them slowly over time. Unfortunately for me and probably for you, I'm not that patient, I usually get all my motivation in one go and try to knock it out. Which is what I did here (and then I subsequently fixed the bugs I found up to version 0.1.4, oops).
What's new
-
Less dependencies
I had a few libraries I had been writing just for coast, so I decided to bring them into the fold and just make them files in the coast namespace instead of totally separate libraries. This cut down on duplicated code across each library (map-vals
anyone?) and the.sql
parsing code for migrations and queries is essentially the same now, which is great, less code! -
More vertical integration
Along with fewer dependencies, since the code is all in one folder, the integration between the underlying parts of coast is better, a more concrete example is the not found page, once part of the routing code which was always kind of weird to me, it now is sent into theapp
function at the top and can be called when a query doesn't have any results, of course I got rid of that, but that was the thinking anyway. -
One uniform api for sql reads and writes
After typing out my 100th(db/query :posts {} :posts/where {})
with oksql I decided I hated it and changed directions back to copying yesql wholesale. I took a slightly different approach like always and I decided to add one more namespace to the model layer (model as in mvc, and in rails-style mvc) to make schema changes on the write side not suck so much. The readme talks more about it, but it boils down to using two different sql files, one that is not meant to be customized*.db.sql
, and another that is meant to be. Both of the files are meant to be referenced in thedb
namespace and there's a macrodefq
that calls def for you so it looks more like a regular function definition. Since the sql code is just meant to be overwritten on schema changes, I may change this in the future to just have adefm
macro or similar that defines the 5 basic db functions:list
,find
,insert
,update
, anddelete
and then you can override them in your.sql
file when it's time to add some joins or subqueries or what have you.
That's pretty much it, I'll try to talk more about future changes and why I made them more frequently, and even though I consider this coast 2.0, it's really more like the previous version was me just slapping libraries together to get something out the door and this version is the first real version I'm actually proud of, bugs and all.
Handle 404s from missing db results
I added query!
which throws a not found exception so you don't have to manually do that in your controller, just add ! to query and that will be taken care of.
Better exceptions
After trying a few different things I've settled on something in between, it's not quite there yet but it's an improvement over "catch all exceptions and return a tuple"
The current way of handling errors in controllers has remain unchanged, I've decided to actually never change anything because otherwise my own code using coast will break, like I experienced back when I naively changed the database-url
to db-spec-or-url
instead of just adding, I renamed. So you can still do this with try!
(defn fresh [request]
(views.items/fresh request))
(def validations
[[:required [:name] "can't be blank"]])
(defn insert [request]
(as-> (get request :params) %
(coast/validate % validations)
(db/insert :items %)
(assoc request :item %)))
(defn create [request]
(let [[response error] (-> request
insert
coast/try!)]
(if (nil? error)
(coast/redirect "/items" "Item created successfully")
(fresh (assoc response :error error)))))
Which, while it works, it catches every single error, database not connected for whatever reason? It's going to show that to the user, not really what they care to see, in that case it would be better in dev to see prone errors and better in prod to just shut the whole thing down and show a 500 page. So with that mind, I introduce two functions that work together, inspired by several different clojure libraries, but uniquely coast-y. throw+
throws an exception like normal, except it's with ex-data
with {:coast-ex true}
and try+
looks for that exception type, if not it just keeps going up the chain to your existing catch-all middleware. So much better. Oh, it also returns a map of errors with an :error
key, so in this case, coast/validate
now uses throw+
so on a validation error it returns:
{:error "Validation has failed"
:name "Name can't be blank"}
Here's an example of how throw+
(in coast/validate
) and try+
work together to only catch coast exceptions and throw error maps instead of EVERY exception and just returning a string.
(defn fresh [request]
(views.items/fresh request))
(def validations
[[:required [:name] "can't be blank"]])
(defn insert [request]
(as-> (get request :params) %
(coast/validate % validations)
(db/insert :items %)
(assoc request :item %)))
(defn create [request]
(let [[response errors] (-> request
insert
coast/try+)]
(if (nil? error)
(coast/redirect "/items" "Item created successfully")
(fresh (assoc response :error error)))))
🐛fix
Winter came and it was mild
Fixed an issue where error-page
wasn't called as a function
Winter is coming
There were a few deps it turns out that weren't needed:
- selmer
- cheshire
- reload
So I axed them and replaced them with the only functionality I needed. In the case of reload, I used ring.middleware.reload.
I also changed the docs to reflect the new reality of requiring coast/wrap-coast-defaults
. I'm starting to think about splitting up the middleware into separate pieces, but I'm not quite sure.
Also added a prod exception handling middleware, just pass in :error-page
and viola instead of leaking exceptions in prod when an error happens, you'll get your custom error page instead!