A few weeks at Alchemy Camp

Tagged: elixir

Hi there!

I'm back with a post with some thoughts on Elixir & Phoenix after using it for a few weeks. It's been really fun. I'm using Elixir & Phoenix to build Galley (previous post here).

Syntax

Pipes!

I love pipes in Elixir. They are fun to write, easy to read, and nice to debug. I love thinking of data moving through a pipe, being transformed piece by piece, until it has become something new.

  defp attach_selected_hero_to_uploads(uploaded_images, form_params) do
    uploaded_images
      |> Enum.with_index()
      |> IO.inspect(label: "before map <<<")
      |> Enum.map(fn {v, i} ->
        {parsed_int, _} = Integer.parse(Map.get(form_params, "hero_image", "0"))
        is_hero = if i == parsed_int, do: true, else: false
        # we have to handle for existing struct images and new maps
        # when setting the images that will be the hero.
        case v do
          %Galley.Recipes.Recipe.Image{} ->
            Map.from_struct(%{v | is_hero: is_hero})
          v when  is_binary(v) ->
            %{"url" => v, "is_hero" => is_hero}
          v when is_map(v) ->
            Map.put(v, "is_hero", is_hero)
        end

      |> IO.inspect(label: "after map >>>")
  end)

Here's a bit of code from Galley where I'm attaching a form value to a list of uploaded_images. I can transform the data, and insert some inspect calls that allow me to look at the differing values as they are transformed.

Little Things

There are some other syntax niceties.

# grab the head and tail of a list
[first | last] = [1, 2, 3 ,4]
# first => 1
# last => [2, 3, 4]

# easily update a map of struct
x = %{:a_field => "Spiders from Mars", :b_field => "HunkyDory"}
%{x | a_field: "Heroes"}
# => %{a_field: "Heroes", b_field: "hunky_dory"}

Pattern Matching

I'm still not using pattern matching in all the places I could be using. One part of it that I find a little hard to adjust to (but I do like) is the ability to name multiple functions with the same name but with different parameters and then let Elixir dispatch (am I using dispatch correctly?) to the correct function based on the input. I like this because it makes each unit of a function more specific and less likely to need a batch of if/cond control flow. It does make navigating the file a little bit slower for me to navigate as I'm so used to navigating by function names and not parameters.

Ecto is Amazing!

I'm still wrapping my head around how much ground Ecto covers. One thing I like about Ecto is that I'm able to look at the "api" of the data structure of a changeset to see how people think about validating data before it goes into a database. I've ended up hand-rolling a (much worse) validation / "casting" / error-reporting system for front-end code.

There are a lot of auxiliary Ecto functions that I don't know about yet and have yet to learn.

LiveView + TailWind

So far I'm quite happy with Liveview and Tailwind as a great combo to get basic interactivity into a page with decent styling. I added dark-mode across every part of the site in less than an hour or so.

I'm somewhat familiar with LiveView at this point but my code is pretty messy. It took me a bit of time to find out how to incorporate a user's identity into a liveview, as well as learning the API around using @assigns.

New Language Blinders

Now and then I get caught by a piece of Elixir/Phoenix that nearly derails-me-by-curiousity:

  • defmodule is a a macro? What does that expand to?

  • Wait, what's the difference between alias, use, and import again?

  • What's different between a struct and a map?

And so on. For the most part, I don't get too distracted and have been keeping my head down on Galley .

How's Galley going?

Things are moving along nicely. I don't have a ton to update, though. I've mostly been working on implementing uploading images - which has been a bit tricky. A recipe will be limited to 4 uploaded images, and of these 4 images, only one can be a "featured" image which will be used to display as a thumbnail and at the top of the recipe page.

Beyond that, things got a bit hairy as the user needs to be able to remove images they've uploaded, upload additional images, and change the featured image. I have most of that built, except for deleting images as well as hooking image uploads to an external storage solution like S3

What's left? There's quite a bit:

  • hook up storing images to S3

  • Add a few more tweaks to the "create recipe page"

  • learn about and implement a tagging system for recipes

  • Setup a User Settings page for changing password/email etc.

  • Setup an Admin user who can reset passwords, ban / kick etc.

  • Setup the "confirm email" system.

That's ... not too bad! After one month of working on Galley, I've gotten this much done:

  • Scaffold the project and add user auth

  • Get familiar with phoenix / liveview

  • Add the "create recipe view" with a working form + file upload

  • Add the "show single recipe" view with a similar layout to Ari's Garden

  • Setup "recipe index" view, with a grid layout and DB-based search.

I've learned a lot, and I think things will get faster as I move about the system/framework more and more. I'm a bit more confident about DB operations, modifying tables and doing migrations, and understanding the shape of the data when interacting with LiveView.

That's all for now!

o/

WT