Skip to content

Instantly share code, notes, and snippets.

@napcs
Created October 15, 2015 13:59
Show Gist options
  • Save napcs/62b999a714d3c64026f8 to your computer and use it in GitHub Desktop.
Save napcs/62b999a714d3c64026f8 to your computer and use it in GitHub Desktop.

Some things I have encountered when working with Ecto in Phoenix projects.

This may sound like a lot of complaining, but I am listing things I keep running into that, on their own, don't amount to much, but together, frustrate me.

This may also expose things that I don't understand about Ecto and FP. And I ask that instead of telling me I'm wrong, you consider why I may have come to these conclusions. There's a chance others will as well.

Ultimately, the choice of how Ecto works is up to those that maintain it. Software is full of opinions. But here are the main things that keep coming up for me. And in my opinion, I find them uncomfortable.

  1. It's included by default now.

    • When I generate a new project, Ecto is included in my mix.exs file and is brought in as a dependency. A default repository is created and configured to use PG. I understand that there's a way to opt out, but running mix phoenix.new with no arguments does not indicate how. (it's --no-ecto.)
    • Suggested solution: add help for the mix phoenix.new command indicating options.
  2. The steps to use a different database adapter other than pg are unclear. I can figure it out, but I have to remember each time.

    • Suggested solution: mix phoenix.new myapp --db=sqlite could set up the appropriate config, database adapter, etc.
  3. Somewhat of a tangent but after setup, I am instructed to mix ecto.create without knowing what it does. When I run it, I have to wait for everything to compile before I can see a message. The installation script runs mix deps.get but does not run mix deps.compile for me. ** Suggested Solution** Run mix deps.compile when creating the app if I choose to install deps.

  4. mix ecto.create uses the given configuration with a postgres user and postgres password. The machines I've tried this on don't have this user or password configured.

    • Suggested solution when instructing how to set up Ecto, specify that these connection details should be changed.
  5. mix phoenix.gen.model generates a model nicely. But it assumes all fields are required.

    • Suggested solution support alternative syntax for specifying required fields, or place all fields as optional.
  6. In general the recommended process for working with a record is tedious.

    alias MyApp.User
    alias MyApp.Repo
    changeset = User.changeset %User{}, %{:name => "Homer"}
    Repo.insert changeset
    

    That's a lot of steps. I can certainly shorten it by using Repo.insert with a model, but then my casts and validations are skipped. I want to just do

     ~~~
     alias MyApp.User
     User.create(%{:name => "Homer"}
     ~~~
    

    and be done. And I know I can. I think changesets are nice but it feels wrong to expose all of that to controllers. I feel strongly that changesets are a detail that shouldn't be exposed directly. And thus as I build apps I am writing code to do that.

  7. mix phoenix.gen.html creates a controller. but I have to add the route manually * suggested solution Add it for me. The argument that it's better to do things manually is undermined by including Ecto and phoenix.ecto from the beginning. The generator already assumes a ton about what I'm doing, including how I want my controllers and HTML configured. and not adding the route for me means I'm stuck - I cannot run additional mix tasks because it won't compile.

  • unique_constraint fails silently when no unique constraint exists in the database. Again, I understand why, bit it's another frustration when validates_uniqueness_of works in Rails and is emulated elsewhere. Checking for the existence of a record before attempting insertion is one extra DB hit when not necessary. But the way you do it now sends the entire record to the DB only to be rejected. select count(field) from table where field = value is less data than sending a whole blog post to the db only to be rejected because of a duplicate value.

Additionally, now I am adding logic to both my database and my codebase. And I have seen that logic get out of sync so many times in my career. * Suggested solution Give me a validates_uniqueness as an option. It can select count(field) from table where field == value. Let me decide to take the hit. I understand that validations are fired inside of the changeset method rather than at the time the repo receives the changeset so you're limited. * OR give me a way to specify it when I generate the model so it can create the constraint in the model and the database 8. changeset.valid? returns true if unique constraints are not met. I understand why, but it's another frustration. * suggested solution None. I can't think of anything good here other than looking at the db constraints somehow. 9. This is feedback from some end users Error messages seem to be ordered as validations first, then constraints. So if I have an error in an email field, I'm not informed that the username is taken until I fix the error with the email. The documentation makes this clear but it ends up not being as friendly.
* suggested solution None. 9. Ecto.Adapters.SQL.query requires a Repo, a statement, and params, even if I don't need them. * Suggested solution Define Ecto.Adapters.SQL.query/2 10. Writing queries is awkward but seems to be getting better with composable queries. But I think more abstraction should be encouraged. * MyRepo.get_by(Post, title: "My post") seems good, but more work than Post.get_by(title: "My post"). Abstractions are good. Think about this - if I change the name of my app or my repository, I've to change it everywhere in the application. One of the core principles of programming is abstraction. It's not just for OOP. The more things I have to pass around in my application, the more things I have to change when there's an API change. If MyRepo.get_by changes, or the syntax for the finders changes, it's all over my app.

And I think that's the major issue I have with Ecto. I am bleeding details about how to work with it all over my code. I have Repo calls everywhere. Anywhere I need to work with data, I need the model and the repo. We all know from working with Rails that it's a good idea to insulate yourself from the framework.

Once again, let me reiterate: I understand the design choices made. But I feel they're not for me. It's become clear that those who support the way things are with Ecto are doing so out of a belief that more verbosity is better because of flexibility. However, when I look at the rest of Phoenix and how well abstracted so much of it is, Ecto is a splash of very cold water. It feels very bolted-on. And to be honest, it is. When I started working with Phoenix, it wasn't there. It kinda shows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment