The Railway Pattern

You cannot view this unit as you're not logged in yet. Go to Account to login.

11 Comments

  1. Ali ELBaitam on November 25, 2020 at 6:53 am

    Hi,
    In the Instructions section, it is stated that the award_points/2 should “Reject user’s who’s age is < 16". I thought a validate_at_least_age/1:

    def validate_at_least_age(%User{age: age} = user), when age < 16 do

    should work. But in the test module, validate_at_least_age has arity 2; with a second integer argument. Is it meant to make the function more generic and can validate against any cutoff age? or what is the purpose of the second integer argument?

    Thanks

    • Mark Ericksen on November 25, 2020 at 6:58 am

      If you look at the solution, I hope it helps make it clearer. The idea with the /2 arity is so the age cutoff to compare it to can be specified. So I could use the same function to say validate_at_least_age(user, 16) or validate_at_least_age(user, 21).

      So you were very close! Perhaps I need to review the text/instructions to make it more clear.

      • Ali ELBaitam on November 25, 2020 at 9:25 am

        Thanks for the quick replies Mark. I look at the solutions after I make the tests pass. So, you made the function more generic to apply for any cutoff age.

        • Mark Ericksen on November 25, 2020 at 10:09 am

          Another tip is to look more closely at the code for the failing test. Can you tell how the API is intended to be used from it? If so, that helps determine what it wants and how to write it. I often use this outside-in approach to help design code. “This is how the code would work if it existed.” This helps flip the perspective from “how do I make it work?” to focus on designing the API with “how do I want to use this?”

  2. Krishnan Kannan on October 30, 2021 at 10:06 am

    Mark, thanks for a great exercise – learned a lot. Another point for discussion is – in this Railway pattern, do we assume these functions are called in certain sequence? For e.g, if I call the blacklist check before validating active user, the ultimate business solution is the same but the given tests and solution doesn’t seem to work if we change the sequence of the functions. I am of the opinion that these functions can be called in any order and don’t assume any ordering. Any thoughts on that?

    • Mark Ericksen on October 31, 2021 at 5:18 am

      In general, you should assume that the order of the functions used in a pipeline is important. A pipeline expresses a sequential set of operations where the order matters. Like in the example for the section, you can’t mix_dry_ingredients/1 before you gather_ingredients/1. But you could switch around the order of mix_dry_ingredients/1 and mix_wet_ingredients/1. If you defined the pipeline process following a cookbook’s set of instructions, then it would have a flow like this.

      With your specific question for the Practice Exercise “Award Points”, it’s true that the “filtering” steps of 1-3 could be performed in any order and not alter the outcome. Keep in mind that in a real-life system some of those checks might be more intensive or require performing more work. What I mean is that checking the active flag is faster or more efficient than checking the inclusion of the name in a list. If one of those filter steps said something like “validates the user has purchased at least $45 worth or product”, then you’d want to do that check after you’ve done the simple quick filters. That way you don’t spend time doing database queries and calculations when you could have quickly eliminated it earlier.

      When a business requirement describes a sequential process, the Railway Pattern lets you elegantly express that happy-path flow in an easy to read and follow way.

      So in the “Award Points” practice exercise, no, the order of the checks is not important. For pipelines in general, you should assume that the order has significance and is important.

      Good question!

      • Krishnan Kannan on November 2, 2021 at 3:39 pm

        Thanks Mark for elaborating on this with examples – I understand it better now. Will keep that in mind when I use this pattern.

  3. Maksym Kosenko on April 24, 2022 at 10:47 am

    Hey, I noticed that there’s lack of “)” in Life.Cooking
    {:ok, Map.put(data, :ingredients, found_ingredients} should be {:ok, Map.put(data, :ingredients, found_ingredients)}
    and the same is for :equipment . It’s an errata. isn’t it?!

    • Mark Ericksen on April 24, 2022 at 2:22 pm

      Good catch! Fixed! Thanks for letting me know.

  4. Zac B on April 28, 2023 at 10:23 pm

    By inverting your test condition (check for negatives, not positives) you can simplify your pipeline a bit:

    def validate_at_least_age({:ok, %User{age: age}}, required_age) when age < required_age, do: {:error, "User age is below the cutoff"}
    def validate_at_least_age(passthrough, _), do: passthrough

    This eliminates one additional function and, personally, I find it more clear. Wondering if you have an opinion (or community guideline) on taking this approach.

    • Mark Ericksen on April 29, 2023 at 8:38 am

      Yup! That works great too!

Leave a Comment

You must be logged in to post a comment.