Handling routes from an API in a type-safe way with Swift

I’m working on an application that has to handle some kind of routing directions coming from an API in the following form:

{
    "title": "Tap to go to your profile!",
    "destination": "profile"
}

These items would show in a form of timeline of events, that when tapped, would take you to specific parts of the application.

I was thinking about how I could leverage Swift’s type-safety to make sure the application validates the destinations for each of these items and make a more robust solution for this.

Immediately, enums came to mind. I can easily define an enumeration with raw values:

enum Destination: String {
    case profile
    case info
    case feed
}

But then, I looked closely at the API’s documentation, and noticed that some destinations could come with attached metadata:

{
    "title": "Tap to go to Oscar's profile!",
    "destination": "profile:859928508374784884"
}

As you can probably guess, tapping on the item above should take you to the profile screen for the user with id 859928508374784884. I thought of enums with associated values, but that wouldn’t cut it. Associated values in enumerations prevent me from inflating instances from raw values.

I decided to come up with my alternative for enumerations to handle this specific case so I could build destination instances in a type-safe manner directly from the JSON objects coming from the API.


First, I started with a structure. Since I’m interested in being able to convert a raw string to an instance of my type and viceversa, RawRepresentable is the protocol to adopt.

struct Destination: RawRepresentable {
    private let _identifier: String
    
    var rawValue: String {
        return _identifier
    }
    
    init?(rawValue: String) {
        _identifier = rawValue
    }
}

Now, I need to check whether the raw value passed to the initializer is a valid one. For that, I defined a string set that contained all the possible identifiers for a Destination, and check for that on the failable initializer from RawRepresentable.

struct Destination: RawRepresentable {
    // ...
    
    private static let options: Set<String> = [
        "info",
        "profile",
        "feed",
        "event"
    ]
    
    init?(rawValue: String) {
        guard Destination.options.contains(rawValue) else {
            return nil
        }
        
        _identifier = rawValue
    }
}

The only thing missing right now would be handling the attached metadata for the available destinations.

For that, I added a value property that would hold the destination’s metadata (if any), and a couple more checks to the the initializer:

struct Destination: RawRepresentable {
    // ...
    var value: String?
    
    public var rawValue: String {
        return _identifier + (value != nil ? ":(value!)" : "")
    }
    
    init?(rawValue: String) {
        // Split the raw value
        let ids = rawValue.split(separator: ":")
            
        // Check that the identifier exists
        guard let identifier = ids.first else {
            return nil
        }
            
        // Convert from String.SubSequence
        let id = String(identifier)
        
        // Make sure this identifier is valid
        guard Destination.options.contains(id) else {
            return nil
        }
            
        _identifier = id
        
        // If metadata exists, add that metadata to value
        if ids.count == 2 {
            value = String(ids[1])
        }
    }
}

Now, conform with Codable so that Destination can be parsed from and to raw data:

struct Destination: RawRepresentable {
    // ...
    enum DestinationError: Error {
        case unsupportedDestination
        case wrongFormat
    }
    
    enum CodingKeys: String, CodingKey {
        case _identifier = "destination"
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(_identifier, forKey: ._identifier)
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let rawValue = try container.decode(String.self, forKey: ._identifier)
        let ids = rawValue.split(separator: ":")
        
        guard let identifier = ids.first else {
            throw DestinationError.wrongFormat
        }
        
        let id = String(identifier)
        
        guard Destination.options.contains(id) else {
            throw DestinationError.unsupportedDestination
        }
        
        _identifier = id
        
        if ids.count == 2 {
            value = String(ids[1])
        }
    }
}

Unfortunately, I coulnd’t find an elegant way to extract the initialization checks to another method (because scope initialization, etc), so the initializer from RawRepresentable has to be basically duplicated on the one from Decodable.

One last step would be making Destination conform with Hashable:

struct Destination: RawRepresentable, Codable, Hashable {
    // ...
    var hashValue: Int {
        return _identifier.hashValue
    }
}

func ==(lhs: Destination, rhs: Destination) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

Note that hashValue takes only into account the identifier for the destination, not it’s attached value. That way, profile:859928508374784884 and a profile are considered equal, even though the first one has attached metadata.

This is super specific in this case, because I only care about the route and the metadata is treated as optional. However, in your own implementation, taking value into consideration for Hashable might make sense.

And that’s it. A quick hack would be to add static properties to Destination with prebuilt values so that they can be treated as enumeration cases:

struct Destination: RawRepresentable {
    static let info = Destination(rawValue: "info")!
    static let profile = Destination(rawValue: "profile")!
    static let feed = Destination(rawValue: "feed")!
    static let event = Destination(rawValue: "event")!
    
    static func profile(value: String) -> Destination? {
        return Destination.rawValue("profile:(value)")
    }
}

Here’s how all that comes together:

let json = ["destination": "profile:859928508374784884"]
let jsonData = try! JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)

let decoder = JSONDecoder()
let parsedDestination = try? decoder.decode(Destination.self, from: jsonData)

let simpleDestination = Destination.profile
let profileDestination = Destination.profile(value: "859928508374784884")!

parsedDestination == profileDestination // #=> true

And of course, you can switch on Destination values as well:

switch parsedDestination! {
case .profile: 
    router.navigateToProfile(parsedDestination)
    
default:
    // Handle other cases.
}

I thought this could make for a nice example on how to leverage Swift’s flexibility to help when the default data structures are not compatible with our use cases.

If you have any comments or idea, or I missed something, feel free to ping me on Twitter @Swanros.

Thanks for reading!

Available for work

I’m now available to take on new iOS and Elixir projects. I have experience working with distributed teams, fast-paced projects, and leading teams.

If you want to ship a 1.0, please do get in touch over at oscar@swanros.com and let’s talk about your needs.

You can learn more about my background and experience downloading my resume over here.

Also, I’d greatly appreciate if you shared this post with anyone you think may be interested in hiring an experienced iOS developer!

Thanks!

guards in Elixir 1.6

Elixir 1.6 has been released with a lot of nice additions to the language, but without a doubt, what I’m most pumped about is real guard support in the language.

Before 1.6, you could constraint a function like so:

def change_settings(%User{admin: is_admin}, settings) when is_admin == true do
    # Update the settings is only available for admins
end

This worked just fine, but now with real guard support, we can extract those checks to make reuse and composition easier!

defmodule User do
  defstruct [:karma, :friend_count]
end

defmodule Main do
  defmacro can_update_settings(karma, friend_count) do
    quote do
      unquote(karma)> 100 and unquote(friend_count) > 3
    end
  end

  def change_settings(user = %User{karma: karma, friend_count: count}, settings) when can_update_settings(karma, count) do
    IO.puts "Updating settings..."

    {:ok, user}
  end

  def add_member(user = %User{karma: karma, friend_count: count}, new_user) when can_update_settings(karma, count) do
    IO.puts "Adding member"

    {:ok, user}
  end

  def main do
    %User{karma: 101, friend_count: 4}
    |> change_settings(%{})
  end
end

Main.main

// => "Updating settings..."

You can skip the manual macro creation alltogether if you use defguard and defguardp, though. These are new functions in the Kernel module that just take your constraint and generate a macro suitable for use as a guard from it.

defguard can_update_settings(verified) when verified == true

I specially love the fact that now you can also use multiple guards with functions:

defmodule User do
  defstruct [:karma, :friend_count]
end

defmodule CommunityFactory do
  defguard has_enough_karma(karma) when karma > 100
  defguard has_invited_enough_friends(friend_count) when friend_count > 3

  def create_community(%User{karma: karma, friend_count: count}, com_name)
    when has_enough_karma(karma)
    when has_invited_enough_friends(count) do

    IO.puts "Creating community..."

    {:ok, %{name: com_name}}
  end

  def create_community(_user, com_name) do
    IO.puts "Cannot create #{com_name}. User still does not complete onboarding."

    {:error, nil}
  end

end

defmodule Main do
  def main do
    %User{karma: 101, friend_count: 5}
    |> CommunityFactory.create_community("My Community")
  end
end

Main.main

// => Creating community...

I use guards like crazy in Swift. I think they’re a great tool to structure your code. However, in Swift, guards can be used (and maybe abused) more as a fancy if/else dance, rather than a mean to express condition conformance.

For instance, in Swift, the only requirement for a guard is that it breaks the flow of the function:

guard user.isAdmin else { 
    print("User is not admin!")  # Compiling error, does not break function flow
}

// Update settings here

But literally nothing stops you from transferring the control over to another function!

guard user.isAdmin else {
    showErrorMessage(.notAdmin) // Give up control of the execution flow
    return
}

// Update settings here

Although this may seem harmless, it can cost some precious down the road when you’re trying to debug something that just follows a trail of guarded statements that keep on going forever.

With Elixir’s flavor of guards, this is explicitly not possible:

Not all expressions are allowed in guard clauses, but only a handful of them. This is a deliberate choice: only a predefined set of side-effect-free functions are allowed. This way, Elixir (and Erlang) can make sure that nothing bad happens while executing guards and no mutations happen anywhere.

Altough I love Swift, I do think that its guard does not entirely guard you from shooting yourself in the foot.


Small but thoughtful updates like this to the language really make me hopeful for the future of Elixir.

You can read everything about the new Elixir guards on the official documentation page and read the release announcement here.

Experimenting with inherited tables in Ecto

I’ve been working on a personal project with some mates for the past few months, and I’ve been mostly responsible for building our backend with Phoenix.

It’s come to a time where we need to implement some sort of "comments & likes" functionality for the app, so I set out to start thinknig how to go about it.

Note: this is just me experimenting, not trying to say that this is the right way to do it. I’m still learning about these things, so if you think I’m missing something, by all means, let me know at oscar@swanros.com

Note 2: the example code for this post is on GitHub.

When I started digging around, most of the answers I found about how to implement such functionality pointed to using table inheritance so that I could have a ActionableEntities table, and EntityComments and EntityLikes tables. Then, Posts, Photos, Events whould inherit from ActionableEntities gaining the ability of being commented or ‘liked’.

"Simple enough," I said to myself. Then I started digging. This is what I found.

Implementing table inheritance with Ecto

I couldn’t find concrete examples of how to do this, but did read the official Ecto documentation, though, and found that you can pass an :options parameter to specify extra attributes that you want your table to have, such as WITH, INHERITS or ON COMMIT. So, the migration looks like this:

def change do
    create table(:actionable_entities) do
        timestamps()
    end
    
    create table(:entity_comments) do
        add :content, :string
        add :entity_id, references(:actionable_entities)
        
        timestamps()
    end
    
    create table(:posts, options: "INHERITS (actionable_entities)") do
        add :content, :string
    end
    
    # photos and all other tables follow the same structure as the posts one.
end

Now the modules that’re going to be using each of these tables are defined as follows:

defmodule Inh.ActionableEntity do
  use Ecto.Schema

  schema "interactive_entities" do
    has_many :comments, Inh.EntityComments.Comment

    timestamps()
  end
end

defmodule Inh.EntityComments.Comment do
  use Ecto.Schema

  schema "entity_comments" do
    field :content, :string
    belongs_to :entity, Inh.ActionableEntity

    timestamps()
  end
end

defmodule Inh.Posts.Post do
  use Ecto.Schema

  schema "posts" do
    field :content, :string

    timestamps()
  end
end

"This ought to work right here," I thought to myself. And it does for the most part: I can create a post, and get a list of posts. I can even query for a Post‘s comments eventhough there are none on the database.

However, the following error on the database arises when trying to create a comment for a given post:

ERROR: insert or update on table "entity_comments" violates foreign key constraint "entity_comments_actionable_entity_id_fkey" DETAIL: Key (entity_id)=(1) is not present in table "actionable_entities".

The code used for trying to insert a new comment looks something along the lines of

def create_for_post(id, c_params) when is_integer(id) do
    Repo.get(ActionableEntity, id)
    |> Ecto.build_assoc(:comments, c_params)
    |> Repo.insert()
end

First get the post I want to comment, build the corresponding association and then insert it into the database. Here’s the problem, though:

indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children.

So, if I add a record to posts or to photos, the information from the inherited table bubbles up to teh parent table. But then, technically, the information is a post, not an actionable_entity. 🤔

Postgres is right to be telling me that there’s no ActionableEntity with the given id.

I could get more of a sense that something is really wrong here by creating a new photos table that too inherits from actionable_entity and adding some records to it:

At this point, there’s no data consistency as the database can’t distinguish between Photos and Posts.

One way to solve this is to create a trigger on the database to check every time we try to inset a new EntityComment for a Post, that the Post indeed exists on the database. To do this, the foreign key constraint needs to be removed from the database. So first, update the entity_comments migration:

create table(:entity_comments) do
  add :content, :string
  add :entity_id, :integer

  timestamps()
end

entity_id is now just a simple :integer, there’s not an explicit reference to the actionable_entities table.

Then, create the trigger:

execute """
  CREATE OR REPLACE FUNCTION internal_post_check() RETURNS TRIGGER AS $$
  BEGIN
    IF NOT EXISTS(SELECT 1 FROM posts WHERE id = new.entity_id) THEN
      RAISE EXCEPTION 'Post does not exist:  %', new.entity_id;
    END IF;
    RETURN new;
  END;
  $$ language plpgsql;
"""

execute """
  CREATE TRIGGER CheckEntityExists BEFORE INSERT OR UPDATE ON entity_comments
  FOR EACH ROW EXECUTE PROCEDURE internal_post_check();
"""

This trigger will run every time a new record wants to be inserted on the entity_comments table and will manually check if a post with the value on entity_id as id exists.

It may seem that the problem is now solved, but then again, what would happen when I have a Post and want to retrieve its comments? If I have Posts and Photos and potentially N number of "interactive entities" on my database, how would I be able to query just for those?

I solved this by adding a new type:String column on the actionable_entity table. Posts would have the value post in that column, photos would have the value photo, and so on for every type of actionable entity I eventually add to the database.

This way, now I can query for the comments of a specific photo with a given id:

def comments_for_post(id) when is_integer(id) do
    q = from entity in ActionableEntity,
        where: entity.id == ^id and entity.type == ^"post",
        left_join: comments in assoc(entity, :comments),
        preload: [comments: comments]
        
    post = Repo.one(q)
    post.comments
end

Final notes

Although this works it does require to bypass the database’s integrity checks to handle those on my own. This is very error prone and would require me to constantly run tests to verify that I’m not missing addig a new trigger for photos, events, or any other "actionable entity" that I want to add to my system.

Also, if I decide to add another kind of action to these entities, such as "likes" or "claps," I’d have add another set of checks for those too.

This is very soon becoming a maintainability nightmare.

I asked friend whose really experienced with backend development and he just sent me this link to a post called Three Reasons Why You Shouldn’t Use Single Table Inheritance. Read it.

In the end, this was just me trying to implement a solution that I thought would make for a good one, but it seems that the compromises that need to be made here are not worth it.

What I went with was the simplest approach: add to the entity_comments table the columns of the entities that I want to enable comments for:

id | content | post_id | photo_id | event_id | inserted_at | updated_at
-----------------------------------------------------------------------
1    Hey!      1                               -----         -----
2    nice!               1                     -----         -----
3    Good!     4                               -----         -----

As stated in schema.ex#L806:

Unless you have dozens of columns, this is simpler for the developer,
more DB friendly and more efficient in all aspects.

So there’s that! Thanks for reading.

Becoming a Better iOS Developer Through Tooling: My AltConf 2017 talk

The fine folks at Realm have now uploaded the remaining videos from AltConf 2017 — including the presentation that I gave there.

You can watch it a Realm Academy, here.

Presenting in the US for the first time was a really exciting experience for me — and something that I’m going to push myself to do more of. I really loved the rush of being there presenting in a foreign country on a foreign language.

I’m gonna call this one a victory — although I know there’s some stuff in the presentation that could’ve used a little more polishing, I’m quite happy with how everything turned out.

What’s nice about this, is that I can now watch myself with a calm head and take notes of things that I can improve for future conference talks in the US or around the world.

I’d really appreciate any constructive feedback you share with me about this presentation. Hit me up at oscar@swanros.com, or @Swanros on Twitter.

On iOS interview exercises

Today I saw this Tweet:

It spawned a great discussion on Twitter and thought I’d share my perspective on this.

Although this may seem like a dull exercise to ask an interviewee to do, in my experience I’ve found it to provide a great amount of insight into how the one doing it thinks about solving a problem.

By revieweing this exercise’s deliverable, I can know…

  • How they think about data modeling
    • Are the models structs or classes?
  • How they reason about data flow
    • Did they create a manager for the data layer?
    • Whose responsability is to download the data and parse it?
    • How should changes be bubbled up to the UI?
  • How they feel about using dependencies
    • Did they use Alamofire to consume the API?
    • Did they use a library to parse JSON?
  • How they approach API design
    • Are closures used intellengtly, or at all, where they would become handy?
    • What’s the “no-data” state, nil or []?

I could go on.

What’s best, is that many interesting conversations that provide even more useful information can be spun-off from any of those questions above.

In my experience, I’ve found this very exercise to have a different outcome every time I use it to interview someone.

Résumé

Oscar Eduardo Cisneros Larios, Sr. iOS Software Engineer

Download PDF version.

+52 312 171 0694 | oscar@swanros.com | @swanros

Oscar is a self-taught iOS Developer with more than 6 years of experience and a passion for education and turning hard technical problems into readable text that anyone can comprehend. Living in Colima, Mexico, Oscar has been working remotely for the past few years for various companies in the U.S., specifically in the San Francisco Bay Area, such as OneLogin, michelada.io, and CloudApp.

As an iOS Developer, Oscar focuses on the app’s core architecture, using his expertise to look into the future, and give the application in question the ability to be resilient to changes that may come down the road.

Skills

  • Swift and Objective-C programming for iOS and macOS.
  • Elixir programming, mainly used for web APIs and implementing tooling for iOS and macOS app deployment.
  • Framework-oriented architecture design.
  • Service design and implementation using Elixir/Phoneix.
  • UI/UX design using Sketch.

Passions

  • Teaching & Education. Oscar comes from a lineage of teachers that have marked the life of generations in his hometown. He learned from a young age the importance of being up to date on what it is that interest him, and the value of helping others building up knowledge and confidence. One of his goals is to make programming more accessible to Spanish-speaking audiences — to that end, he has done free workshops, written blog posts, and given talks in Spanish about iOS programming and industry best practices. aprendeios.com is his current endeavor in this area.
  • Music. Before settling for Systems Engineering as a major for college, Oscar was seriously considering pursuing a career on music. He learned to play piano at age 6, played guitar through elementary and middle school and settled for drums, which remain his favorite instrument. You can often see Oscar playing air drums or air guitar in while the code is compiling, or whenever a breakdown is about to happen. Oscar apologizes in advance if you’re with him at a public place and a band is playing — he’ll be watching them.
  • Technology. From very early age, he demonstrated a fascination for technologies and its different applications — from small, quirky utility clocks, to phones and software. One of the projects Oscar is the proudest, is an online community he built while on high school that was comprised of a news site and YouTube channel built around Apple news. SwanrosTech, as it was named, took off slowly but steadily, getting almost 10k daily visits to the webpage and more than a million views on YouTube, right before Oscar decided to shut the project down to focus on college.

Notable Experience

Sr. iOS Software Engineer, CloudApp. Oct 2016 — Current.
At CloudApp, Oscar worked on rebuilding the foundation for the CloudApp macOS application, with the goal that the iOS and macOS clients for the service shared a unified code base. One of his main focus was helping the Apple Platforms team to establish standards and best practices across working areas, such as code review, release process, and architecture. Oscar also worked closely with the backend team to create new micro-services with the goal of automating the team’s Continuous Integration and Continuous Delivery goals.

Sr. iOS Software Engineer, michelada.io Apr 2016 — Oct 2016
Oscar joined the michelada.io team to help with some short-term client projects that needed iOS components. Through constant communication and feedback, the michelada.io team was able to cater to company’s clients necessities. Oscar worked closely with the michelada.io team to help clients transform their ideas into working products on iOS devices, guiding them through the idea, design, and implementation processes.

Mobile Software Engineer, OneLogin Mar 2014 — Jul 2015
While working at OneLogin, Oscar rebuilt the iOS application that the company’s clients use to access their secure infrastructure and applications. He also helped design and implement OneLogin’s NAPPS architecture, which enables secure communication and authentication between native applications on both iOS and Android.

Other
Oscar started working professionally as an iOS/web developer in 2013 at a local marketing firm doing apps for local clients. Today, when time permits, Oscar also works with different clients and companies as an aide to help them revamp their mobile offering. These endeavors are often carried on on a per-contract basis. Most of the contract work that Oscar has carried out has to do with taking UI design to implementation and helping review architectural decisions.

Education

Oscar started college at Instituto Tecnológico de Colima with the intention of majoring in Systems Engineering. However, he soon realized that the academic and technical level of the institute’s offering was not what he was expecting, and was not going to help him accomplish his objectives.

Being a natural autodidact learner, Oscar has been focused on learning topics and mastering tools that actually matter to him ever since dropping out of college.

What Oscar looks for in a company

  • A platform to do work that impact people’s lives in a positive way.
  • Working on interesting problems related to his passions.
  • Working with people that are aligned with his vision.
  • For it to enable self growth, both on a personal and professional level.

Apple starts rejecting apps with “hot code push” features

This thread from Apple’s Developer Forums is making some waves on the internets today. Apparently, they’re now rejecting apps that include code that can modify your app after it’s released to the store.

Rollout.io seems to be the SDK that’s setting the alarm off, so far.

If you use Rollout.io, or another “push code after you release to the store” service, it’s time to start thinking about changing your strategy. (clears throat I told you).

From Rollout.io‘s FAQ:

Does Rollout comply to Apple’s Guidelines?

Yes. As per Apple’s official guidelines, Rollout.io does NOT alter binaries. Rollout uses JavascriptCore to add logic to your patches. For more details, check out how Rollout is compliant with App Store guidelines.

I wouldn’t build my business on such a fine line.

And also, please don’t act surprised when Apple start rejecting React Native apps down the road.

The moral of the story: stay away from JavaScript.

action_fallback and contexts in Phoenix 1.3 made my controllers tiny!

Yesterday I posted about how Phoenix 1.3 was pure love for API development. In that post, I mentioned that one of my favorites features in this new release is the action_fallback plug.

Today, I want to talk a little bit more about a killer combo I’ve uncovered: action_fallback + contexts.

I’m still amazed at how Phoenix 1.3 enables my controllers to be so tiny.



# Background
In my application, I have a `User` module that’s managed through an `Accounts` context:

def update(conn, %{"id" => id, "user" => user_params}) do
  user = Accounts.get_user(id)

  with {:ok, %User{} = user} <- Accounts.update_user(user, user_params), 
  do: render(conn, "show.json", user: user)
end

The default MyApp.Web.FallbackController that’s generated when you first create your app includes, amongst others, a call/2 definition that accepts an %Ecto.Changeset{} instance:

defmodule MyApp.Web.FallbackController do
  use MyApp.Web, :controller

  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
    conn
    |> put_status(:unprocessable_entity)
    |> render(MyApp.Web.ChangesetView, "error.json", changeset: changeset)
  end

  # 
end

This function will be called if, for instance, the update/2 method on UserController failed to pattern match the with statement. Repo.update/1 returns {:error, changeset} in case it fails. Since I didn’t define an else path, {:error, changeset} would be then forwarded to MyApp.Web.FallbackController.



# Custom Errors
I like to define custom errors for the domains for my app. The default behavior on `MyApp.Web.ChangesetView` will output something like this:

{
  errors: [
    "username has already been taken"
  ]
}

I modified MyApp.Web.ChangesetView to expose the error the way I wanted:

defmodule MyApp.Web.ChangesetView do
  use MyApp.Web, :view
  alias MyApp.ResponseWrapper

  def translate_errors(changeset) do
    case hd(changeset.errors) do
      {label, {"has already been taken", _}} ->
        "#{label}_taken"
      {:password, {"should be at most %{count} character(s)", _}} ->
        "password_too_long"
      {:password, {"should be at least %{count} character(s)", _}} ->
        "password_too_short"
      _ ->
        :something_went_wrong
    end
  end

  def render("error.json", %{changeset: changeset}) do
    ResponseWrapper.error translate_errors(changeset)
  end
end

In the translate_errors/1 function, I take the first error off the changeset, and then pattern match its content. I can add more patterns to match later depending on my needs. Now, MyApp.Web.ChangesetView outputs errors like this:

{
  status: "error",
  error: "username_taken"
}

{
  status: "error",
  error: "password_too_short"
}



# Benefits
What’s great about that, is that now I can basically reuse that same code for any other module that needs to be inserted in my database.

This lets me write code like this:

defmodule MyApp.Web.DeviceController do
  use MyApp.Web, :controller

  alias MyApp.{Authentication, Notifications}

  plug :scrub_params, "device" when action in [:create]
  plug Authentication
  action_fallback MyApp.Web.FallbackController

  def create(conn, %{"device" => device_params}) do
    with {:ok, user} <- Authentication.get_current_user(conn),
         {:ok, _dev} <- Notifications.add_device(device_params, user),
         do: :success
  end
end

My create/2 function in DeviceController is just 3 lines long. In those 3 lines, I:

  1. Get the current user.
  2. Insert a new device (with changeset validations) to the database.
  3. Passes :success to my the action_fallback plug.

The “happy path” response from my fallback looks like this:

{
  status: "success",
  data: {
    status: "success"
  }
}

If any of the guards in the with statement fails, guess what: my fallback controller’s got my back 😬.

For instance, say the Notifications.add_device/2 function fails to insert the new device into the database because it didn’t meet one of the changeset validations, such as the unique_constraint for device_token.

Notifications.add_device would then return {:error, changeset}, which would be forwarded to my fallback controller, and the JSON error would look like this:

{
  status: "error",
  error: "device_token_taken"
}

And I got that for free. 👆

Yay contexts! Yay action_fallback! 🎉

Phoenix 1.3 is pure love for API development

Phoenix 1.3-rc.0 was just released, and while it’s still not a final release, it already feels solid as h*ck.

I’ve been doing a personal project on the side for a while now, and it has a web API component, which I wrote in Phoenix 1.2. That was my first time using Phoenix on a serious project that I intend to ship.

With the release of Phoenix 1.3, and after I watched Chris McCord’s talk about the changes it brings to the table, I decided to rewrite my API from scratch with this new version.

And oh boy, is it great.

Here are my first-impressions.

New Folder Structure

It has been said a lot of times that Phoenix is "Rails for Elixir." With 1.3, the core team sure wants that notion to be as dead as the web/ folder and the model concept.

I’m not going to explain everything that Chris talked about on his keynote at Lonestar Elixir (you should seriously go watch it), but I think it’s worth saying that the change in the folder structure of a Phoenix project has bigger implications that just files moving from one place to another.

Now, there are concrete boundaries between your actual Elixir application, and the interface that Phoenix provides for it to communicate with the web.

Responsibility separation by design. I love this.

Thinking Ahead

Long gone are the mix phoenix.* commands. Say hi to mix phx.*. Less things to type. Love it.

Also, the generators will make you think in advance of what you want to do before you actually do it.

For instance, in Phoenix 1.2 you could do something like this:

$ mix phoenix.gen.json User users email:string

which then would generate the right migrations, and the model:

defmodule MyApp.User do
    use MyApp.Web, :model

    schema "users" do
        field :email, :string
    end
end

along with a (web) controller that would handle the CRUD tasks, with the logic for validation within each action. Over time, using this structure, is easy to end up with controller actions over hundred lines long.

Now, with Phoenix 1.3, you need to specify a context for each resource you create (don’t call them "models" anymore 🙄):

$ mix phx.gen.json Accounts User users email:string

In the command above, Accounts is the context module that’s going to be also generated, and is through this context that we’ll interact with our User module:

Accounts.get_user(2) #=> %User{email: oscar@swanros.com}
Accounts.create_user(params) #=> {:ok, new_user}

This is powerful, because now you can define clear boundaries for your application domains.

Say you have the concept of "contacts" in your application. Each user (User) has contacts (also User instances). This can get messy (as I’ve written before). But using this new notion of "contexts", we can have a ContactRelationship context and handle everything in a clean way:

user
|> ContactRelationships.relationship_with(another_user)
|> ContactRelationships.accept

user
|> ContactRelationships.get_contacts

user
|> ContactRelationships.request_contact(another_user)

This scenario would make our folder structure look something like this:

|- lib/
|---- my_app/
|-------- accounts/
|-------- contact_relationships/
|-------- web/

Defining concrete boundaries between responsibilities within your app makes you come up with better code over time.

action_fallback

So far, the additions to the framework are really nice and welcomed. However, I think my favorite one is the new action_fallback plug.

On Phoenix 1.2 and earlier, every controller needed to return a valid conn for every request. Otherwise, an exception would rise.

This is still true, but with the new action_fallback plug we can delegate that task to another controller whose sole purpose is to handle the cases when other controllers couldn’t provide a "successful" response to the request.

Let me explain… before, you wrote code similar to this in every controller action:

def create(conn, %{"user" => user_params}) do
  user = Repo.get_by(User, phone_number: user_params["phone_number"])

  cond do
    user && checkpw(user_params["password"], user.password_hash) ->
      case create_session(user) do
        {:ok, session} ->
          conn
            |> put_status(:created)
            |> render("show.json", %{session: session, user: user})

        _ ->
          conn
            |> put_status(:unauthorized)
            |> render("error.json")
      end

    true ->
      dummy_checkpw()
      conn
        |> put_status(:unauthorized)
        |> render("error.json")
  end
end

Is not that bad, truly. But this is still better with Phoenix 1.3:

action_fallback MyApp.Web.FallbackController

def create(conn, %{"user" => user_params}) do
  user = Accounts.get_user_by_phone(user_params["phone_number"])

  cond do
    user && checkpw(user_params["password"], user.hashed_password) ->
      with {:ok, %Session{} = session} <- Sessions.create_session(user) do
        conn
        |> put_status(:created)
        |> render("auth_success.json", user: user, session: session)
      end

    true ->
      {:error, :wrong_credentials}
  end
end

With Phoenix 1.3 our controller actions can be simplified to just care about the "happy path", and leave the rest to the fallback controller that we define using the action_fallback plug.

In this case, when the cond evaluates to true we return from the action {:error, :wrong_credentials}. Since this is not a valid connection, our fallback controller comes forward:

defmodule MyApp.Web.FallbackController do
  use MyApp.Web, :controller

  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
    conn
    |> put_status(:unprocessable_entity)
    |> render(MyApp.Web.ChangesetView, "error.json", changeset: changeset)
  end

  def call(conn, {:error, :unauthorized}) do
    conn
    |> put_status(:unauthorized)
    |> render(MyApp.Web.ErrorView, "auth_required.json")
  end

  def call(conn, {:error, :wrong_credentials}) do
    conn
    |> put_status(:unprocessable_entity)
    |> render(MyApp.Web.ErrorView, "wrong_credentials.json")
  end
end

By pattern matching, the fallback controller knows exactly what to send back as a response to the request.

This also means that we can have a centralized list of error codes that our API can expose to our clients, on our ErrorView module.

No more error definitions scattered around our codebase. ❤️

However, why stop with errors? If your API has "default" responses for actions, you can rely on the fallback controller too to display them:

def delete(conn, _params) do
  with {:ok, session} <- Authentication.get_session(conn),
       {:ok, deleted} <- Sessions.delete_session(session) do
    :success
  else _ ->
    {:error, :unauthorized}
  end
end

:success will be passed to the appropriate action on your fallback controller, and there goes your default response. 🎉

Final thoughts

Phoenix 1.3 is a great update.

Please keep in mind that the defaults 1.3 offers could be done with 1.2, too. But the fact that this is now the default, really speaks of how much attention to detail the Phoenix team is putting in.

They don’t want us to be using "The Phoenix Way™", but rather provide sensible defaults that enable us to create our own solutions in the best possible way.

Some points:

  • The new fallback_controller plug is awesome.
  • The fact that you need to think in terms of boundaries for your app, I think will eventually lead to better, more maintainable codebases.
  • Smarter code generation is 👌.
  • For APIs, the centralized list of available errors is really welcomed.

I love how productive I am with Phoenix when building APIs, and I’m really looking forward to how this already-great framework will evolve past 1.X.


edit: I also wrote about how action_fallback and contexts in Phoenix 1.3 made my controllers tiny.