Skip to content

Releases: coast-framework/coast

Changing the name and the version

26 Feb 17:40
Compare
Choose a tag to compare

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 the app 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 the db namespace and there's a macro defq 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 a defm macro or similar that defines the 5 basic db functions: list, find, insert, update, and delete 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

25 Feb 18:35
Compare
Choose a tag to compare

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

08 Feb 17:45
Compare
Choose a tag to compare

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

24 Jan 06:19
Compare
Choose a tag to compare

There was a problem when running lein run from a freshly generated coast project. That doesn't happen anymore.

Winter came and it was mild

09 Jan 07:37
Compare
Choose a tag to compare

Fixed an issue where error-page wasn't called as a function

Winter is coming

09 Jan 07:36
Compare
Choose a tag to compare

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!