chrisjowen

chrisjowen

Am I bastardizing Phoenix?

Hi All,

So I have just started a new project and I wanted to avoid some of the issues I had before, so I started going crazy with macros etc. Now I have something that half of me really likes, and the other half thinks Im doing things the wrong way. I’d like some feedback on whether my approaches are sensible or a complete discrace to the elixir community :).

Ok so the first issue I had was that I had occasionally ended up passing in the wrong value for functions like:

some_func(some_id, _some_other_id)

In this case I had a few small time wasting issues where I had these args in the wrong order. I had tests around this but sometimes with defdelgate in the mix etc I somehow still passed the wrong vars. In this case I really missed types, and I wished I had taken my input and cast it to a struct as early as possible.

Another issue I had is that I was taking in and returning snake cased json, which isn’t the convention in javascript.

Finally, my authorization was a mess of logic and I started to think about moving all this to one place such as described here: Authorization Considerations For Phoenix Contexts - DockYard

What I wanted to end up with is something that simplifies:

  1. Taking controller params input in and convert them a DTO struct
  2. Run validations against the input and bomb out if its invalid
  3. Use use this DTO to check user authorization
  4. If all well run the controller logic

In some ways I have achieved all of these but in other ways I might have gone a bit Javary to do so.

Here’s what my controller looks like now:

defmodule LunchFuWeb.AuthController do
  use LunchFuWeb.BaseController
  alias LunchFu.Dto
  alias Lunchfu.Accounts

  @bind request: Dto.LoginRequest
  def login(conn, _, request: login_request) do
    with {:ok, user} <- Accounts.login(login_request) do
      json(conn, user)
     end
  end

  @bind request: Dto.UpdateCredentialsRequest
  @can_handle :update_user, :request, LunchFu.DefaultUserGloves
  def update_user(conn, _, request: update_user_request) do
    with {:ok, user} <- Accounts.update_user(update_user_request) do
      json(conn, user)
     end
  end

end

The input mapping uses a small library to define how input should be mapped, and it is defined something like this:

defmodule CamelCaseKeyResolver do
  def resolve(key) do
    [h | t] =
      key
      |> Atom.to_string()
      |> Macro.camelize()
      |> String.split("", trim: true)

    String.downcase(h) <> Enum.join(t)
  end
end

defmodule LunchFu.Dto.LoginRequest do
  use ExMapper.DefMapping

  defstruct [:username, :password, :hashed_password]

  defmapping do
     keys &CamelCaseKeyResolver.resolve/1
     override :hashed_password, key: "password", value: &hash_password_input/1
  end

  def validate(request) do
    request
    |> Justify.validate_required(:username)
    |> Justify.validate_required(:password)
  end

  def hash_password_input(val) do
    # TODO: Hash password
    "SOMEHASHED_PASSWORD_EXAMPLE"
  end

end

The permissions are handled by a small dsl to define permissions that looks like this:

defmodule LunchFu.DefaultUserGloves do
  use Rubbergloves, wearer: LunchFu.User

  # Hardcoded rules checked first
  phase :pre_checks do
    can_handle!(%LunchFu.User{role: :admin}, _any_action) # Admin can do anything
    can_handle!(%LunchFu.User{name: "Christopher Owen"}, :update_user, request=%DTO.UpdateCredentialsRequest{}) # Hardcoded that I can update users
    cannot_handle!(_anyone, _any_action) # everyone else CANNOT do anything
  end
  
  # If rejected check database to see if I have explicit permissions
  phase :registery_check do
    can_handle?(user, action, conditions) do
      Repo.one(from r in PermissionsRegistory, where r.user_id == ^user.id and r.action == ^action and r.conditions == ^conditions) != nil
    end  
  end
  
end

So the good part to this is that I have a very clean controller, and the mapping/validation/auth logic is all separated. The bad parts are that I have a bunch of magic happening, and also I feel slightly disgusted in myself for using attributes a base controller as it feels very java like.

What are you thoughts on all of this?

Chris

First Post!

chrisjowen

chrisjowen

Forgot to mention that also the reason I am responding with json(conn, schema) is because I also have other magic to map encode the schemas in a standard way always converting back to camelCase without using the phoinex views approach.

Most Liked

dimitarvp

dimitarvp

Looks alright to me. Phoenix is not strongly opinionated on how do you achieve your app’s goals. It only has a handful of conventions.

Everything that helps you maintain your code better is a win! If that’s the case then your code and concept are good. :slight_smile:

chrisjowen

chrisjowen

Thanks for the response, I was beginning to think it was the worst approach ever :). I’m still playing with all of this, but I’ll be pushing the code up to GitHub - chrisjowen/rubbergloves: A series of macros, and utilities to help handling your elixir tonics. · GitHub if anyone else finds this even slightly useful.

Where Next?

Popular in Discussions Top

pillaiindu
In django there is a cache framework backed by memcached. Rails also puts a lot of emphasis on caching, and even the idea of russian-doll...
New
rms.mrcs
A couple of days ago I was discussing with a friend about different approaches to write microservices. He said that if he was going to w...
New
pdgonzalez872
If this has been asked here before, please point me to where it was asked as I didn’t find it when I searched the forum. Maybe a mailing ...
New
chulkilee
Here are the list of HTTP client libraries/wrappers, and some thoughts on HTTP client in general. I’d like to hear from others how they w...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
eteeselink
Hi all, In the last days, two things happened: A blog post titled “They might never tell you it’s broken” made the rounds. It’s about ...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New

Other popular topics Top

jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31586 112
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New

We're in Beta

About us Mission Statement