Hello, are there any examples of mature Dream projects out there?
I’m working on a little toy project to learn OCAML and getting to the point where it’s time to factors some of the logic out of main.ml and I’m wondering how folks tend to structure their projects. I have a bin/main.ml and a lib/importer, but my main.ml is starting to get a little cumbersome.
I worked in Django and their workflow is usually separate folders for models, views, templates, and forms.
I’m also pondering an eio refactor, most of my logic right now is using lwt except for my importer and it seems like it would be nice to move everything to eio.
That’s depends of the number of pages… I have a 6 pages Dream project. Then I have put all pages in the same folder with some pages names prefixed with adm_. I guess that if you have 20+ or 50+ pages, the prefix trick won’t suffice and directories would be needed (this add some complexity. I find OCaml/dune not as easily to manage than a Java project when you add directories).
With my tiny project, I use the route function and have some pages associated with a given directory (`Adm’), but you can have more directeries… here, Dream is nice and allow me to insert a filter about the authentication.
You can have multiple *.ml file in your bin directory just ensure they are in the dune file and are open(import) correctly to avoid circular dependency.
I have (include_subdirs qualified) in lib/dune, this is the closest thing to python’s package/module lookup logic, for example, lib/template/layout.ml will be seemed as module Template.Layout inside lib/, and module LIB_NAME.Template.Layout outside lib/ (e.g. in bin/main.ml) , the LIB_NAME is what you set in lib/dune. BTW, my bin/main.ml is almost always simply a line of let () = LIB_NAME.Cli.main ().
Is it best to put all the DB functions in one .ml file, and all routing in one.ml file etc.? or would it make sense to put all the functions from a single view in one file?
It’s not implemented with dream but you may be interested in the description of the code layout of the webapp here for schema entities (second list of bullets).
In particular this layered structure solves most of the recursive module dependencies problems you easily run into when you represent you relational databases in OCaml. Because foreign keys (pointers) naturally lead to recursive datatypes when you navigate relations.
This is precisely what I was looking for and it is very helpful. This is my first time hearing about htmlact, also, and I am interested in your approach there. Thank you!
rel looks super intriguing as well! This is a particularly interesting concept to me.
Automatic schema change computation via schema diffing.
I’m excited to go through the papers you cite here: index (rel.index)
I have waffled constantly between liking ORMs and raw SQL. I’m currently using Caqti for my database interface, and it’s been really good, but I must say I don’t love the ergonomics of a query failing silently if you get a type wrong. That seems at odds with the whole general approach.
For various reasons, I’ve settled on the POOH stack (PostgreSQL, OCAML, OpenBSD, htmx) but your approach seems very carefully considered, and you’ve clearly spent a lot more time thinking about this than I have
This shows an organizational pattern called ‘nested routes’ (lifted from Remix and Ember.js) that works pretty well with htmx. The dream-html library itself has a few niceties like a fairly large and complete set of HTML and htmx combinators, plus type-safe routes made with OCaml’s format strings, and form decoding. I’m using this in a side project and it all works pretty well: [ANN] Cmarkit 0.4.0 - CommonMark parser and renderer for OCaml - #9 by yawaramin
Note, htmlact (unreleased) is really just my own derivative of htmx see here for some hints.
This is again something that I stole from random readings on the web (it seems I didn’t record a precise reference in my notes but I remember these ideas being described in a few blog posts by different authors written when the web was still a nice place for humans :–). It’s super cool when you are developing and refining your initial data model. Once you have real data however, it helps but there’s no shortcut, the diffing provides you with the SQL for the schema structural changes but unless you changes are trivial proper hand written data migration procedures may still be needed.
Thank you, this is very helpful. I’m currently struggling through Modules and structuring an OCAML project for the first time, I really like the way you’ve organized routes here. This seems like a very effective way to organize both routes and template fragments.
I have been using and enjoying the hmtx features of Dream-html, thank you very much for that. I think I will also have to dig a little deeper into that ‘vary’ function you use to manage template fragments.
Your Zettel workflow/app looks pretty interesting, also! Did you use htmx to implement the search feature at all?
Good to hear! I have a search feature using SQLite’s FTS5 engine. I initially also had an ‘active search’ (search as you type) feature but took it out for the time being while I was redoing the app’s UI/UX. Planning to put it back in later. Next thing I want to implement is some kind of pagination for larger results pages.
What I like is using Caqti with ppx_rapper. The idea is to anotate the SQL query with types and ppx_rapper generate a function with the right types which are enforced by OCaml.
Oh, thank you for linking this! I had read this earlier and I had the misconception which you referenced, which was that hx-boost was primarily to keep navbar elements (or whatever) on a page to simulate SPA performance.
My current workflow is that I have a view which loads a page, and then does a hx-request on page load to load the content, which is stored in a partial. Then I have a on-scroll effect which loads the next page through that same partial when you hit the bottom of the page. But if I’m reading your article correctly, it seems as though I should be able to combine those into one endpoint, so then the logic would just become: if it’s a boosted link, as shown through the HTML header, I can just return the partial; if it’s a regular request I can display the full page. Am I on the right track here? I love this workflow and I’ve always ended up with a glut of ‘partial’ templates when I build with htmx.
I was looking at ppx_rapper and it looks pretty nice! It was a little much for me as I was just getting started with both the framework and the language, and I kind of struggle to figure out what ppx is doing behind the scenes. I’m still working to figure out what happens behind the scenes with the lwt ppx. It seems like, however, you would still run into the problem where if your declared types in the SQL query mismatch your SQL table, it fails silently. Does that sound right? That’s not a deal breaker by any means, it is simply slightly irritating to my utopian “everything should be typed” OCD
let get = Dream_html.get Routes.todo (fun req id ->
let todo = Repo.find id in
let rendered = render ~todo in
vary req
~fragment:(fun () -> respond (null [rendered; Page.title_tag todo.desc]))
(fun () ->
respond
(Page.render ~title_str:todo.desc
(Todos.render ~todos:(Repo.list ()) rendered))))
The fragment argument is what to render if it’s a partial request. The final positional argument is what to render if it’s a full page request. The full page render is structured basically like this: page(todos(todo)).