Phoenix v1.3.0-rc.0 released

Elixir does not care at all about how and where you place your files. And afaik Phoenix is only doing inside contexts to keep related files close together. Do what feels natural.

4 Likes

This is true from implementation perspective, but if you change the standard in Phoenix, people will start adopting it everywhere. This is how it usually works. So the right question to ask is why is that change introduced and is the idea transferable to whole ecosystem? I believe you guys have some pretty good reasons to do it this way, but since it’s new, it would be useful to talk about those reasons.

You’re doing amazing work with the framework and I believe Phoenix is kind of driving the whole ecosystem(along with other important projects, like Nerves). That means Phoenix is a poster boy of Elixir and Phoenix conventions will spread, even if you do not intend it too.

4 Likes

It is not new. Phoenix projects already do not follow the path conventions on entries such as web/controllers/user_controller.ex (which would have required its module to be named MyApp.Web.Controllers.UserController).

Mix itself keeps Mix.Tasks.Escript.Build in lib/mix/tasks/escript.build.ex instead of lib/mix/tasks/escript/build.ex. We do it so you can quickly glance into lib/mix/tasks and see all available tasks.

Not caring about the directory structure has been an Elixir feature from before 1.0. Different projects have leveraged it to provide better code organization under circumstances they find it worth.

In this case, we find that colocating the context file with other files that belong to the same context is going to help exploration. Contrast how it works now:

    + accounts
      - accounts.ex
      - foo.ex
      - bar.ex
    + backoffice
    + blog
    + payments
    + sales
    + web

with the old way

    + accounts
      - foo.ex
      - bar.ex
    + backoffice
    + blog
    + payments
    + sales
    + web
    - accounts.ex
    ...
    - sales.ex

We find the first easier to explore if the files are all in one place. Chris even had to “cheat” in his presentation by making those files visually together while most tree viewers wouldn’t put them side by side. And yes, the idea is transferable to the whole ecosystem.

Therefore it is totally fine if you follow Phoenix conventions elsewhere, especially if you believe they lead to a better code structure. And if you want to try other approaches than the ones mentioned above, then discuss it with your team and go ahead with your changes if you are in agreement.

18 Likes

Thank you for that extensive explanation!

The remarks about better discovery ring true, especially as the number of contexts will probably rise quite a bit during application lifecycle. It looks like a good idea when you put it like this.

2 Likes

Will lib/myapp/.* be hot reloaded?

1 Like

Just tried out the generators with a small project. One thing that I was confused by, was prefixing the table names with the bounded context name. I have the following migration which was created by: mix phx.gen.html Builder Repository repositories url:string ssg:string

defmodule Staticrail.Repo.Migrations.CreateStaticrail.Builder.Repository do
  use Ecto.Migration

  def change do
    create table(:builder_repositories) do
      add :url, :string
      add :ssg, :string

      timestamps()
    end

  end
end

I wasn’t expecting the table names to be prefixed as these are the concrete version of the entities. We could have different views of these entities through different bounded contexts. I am not completely sure if it is a bug or if the table was intentionally named this way. The fix for this for me was simple, I just removed the :builder prefix in the migration. Not sure how others think about this.

3 Likes

Is there a way to use phx.gen.html in umbrella projects?

1 Like

(post withdrawn by author, will be automatically deleted in 24 hours unless flagged)

1 Like

Yes.

It is intentionally named this way, as mentioned in mix help phx.gen.html, to drive the point home they belong to a particular context. You can customize it from the generators with the --table switch as well.

You need to run it from the _web app in your umbrella. However it will put the context in the wrong place, there is an open issue to fix that.

4 Likes

I like the old way much more but I think I will put all new files inside Services directory and keep old style or rename accounts.ex to service.ex, from my point of view accounts/accounts.ex should stay outside the accounts folder, you can consider folders as namespaces.

lib/my_app
├── accounts
│   ├── user.ex
│   └── accounts.ex
├── sales
2 Likes
alexandrubagu@local:~/devel$ mix phx.new micro --umbrella --database mysql
alexandrubagu@local:~/devel/micro_umbrella/apps$ cd micr_umbrella/apps/micro_web/
alexandrubagu@local:~/devel/micro_umbrella/apps/micro_web$ mix phx.gen.html Accounts User users name:string
2 Likes

Yeah I did that, but as the contexts don’t go into the “non _web application”, I thought there might be another way.

There’s now an issue: https://github.com/phoenixframework/phoenix/issues/2139

2 Likes

It looks like generating a schema with a reference should be fixed in master but I’m still getting an error. Am I being derpy?

$ mix phx.gen.schema Blog.Post blog_posts title user_id:references:blog_users                                                                                                                                                         
* creating lib/hello_world/blog/post.ex                                                                                                                                                                                               
* creating priv/repo/migrations/20170303160218_create_blog_post.exs                                                                                                                                                                   
                                                                                                                                                                                                                                      
Remember to update your repository by running migrations:                                                                                                                                                                             
                                                                                                                                                                                                                                      
    $ mix ecto.migrate                                                                                                                                                                                                                
                                                                                                                                                                                                                                      
$ mix ecto.migrate                                                                                                                                                                                                                    
Compiling 1 file (.ex)                                                                                                                                                                                                                
Generated hello_world app                                                                                                                                                                                                             
                                                                                                                                                                                                                                      
08:02:23.215 [info]  == Running HelloWorld.Repo.Migrations.CreateHelloWorld.Blog.Post.change/0 forward                                                                                                                                
                                                                                                                                                                                                                                      
08:02:23.215 [info]  create table blog_posts                                                                                                                                                                                          
                                                                                                                                                                                                                                      
08:02:23.221 [info]  create index blog_posts_user_index                                                                                                                                                                               
** (Postgrex.Error) ERROR 42703 (undefined_column): column "user" does not exist                                                                                                                                                      
    (ecto) lib/ecto/adapters/sql.ex:195: Ecto.Adapters.SQL.query!/5                                                                                                                                                                   
    (ecto) lib/ecto/adapters/postgres.ex:86: anonymous fn/4 in Ecto.Adapters.Postgres.execute_ddl/3                                                                                                                                   
    (elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3                                                                                                                                                                    
    (ecto) lib/ecto/adapters/postgres.ex:86: Ecto.Adapters.Postgres.execute_ddl/3                                                                                                                                                     
    (ecto) lib/ecto/migration/runner.ex:98: anonymous fn/2 in Ecto.Migration.Runner.flush/0                                                                                                                                           
    (elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3                                                                                                                                                                    
    (ecto) lib/ecto/migration/runner.ex:96: Ecto.Migration.Runner.flush/0                                                                                                                                                             
    (stdlib) timer.erl:181: :timer.tc/2                                                                                                                                                                                               
    (ecto) lib/ecto/migration/runner.ex:27: Ecto.Migration.Runner.run/6                                                                                                                                                               
    (ecto) lib/ecto/migrator.ex:121: Ecto.Migrator.attempt/6                                                                                                                                                                          
    (ecto) lib/ecto/migrator.ex:71: anonymous fn/4 in Ecto.Migrator.do_up/4                                                                                                                                                           
    (ecto) lib/ecto/adapters/sql.ex:615: anonymous fn/3 in Ecto.Adapters.SQL.do_transaction/3                                                                                                                                         
    (db_connection) lib/db_connection.ex:1274: DBConnection.transaction_run/4                                                                                                                                                         
    (db_connection) lib/db_connection.ex:1198: DBConnection.run_begin/3                                                                                                                                                               
    (db_connection) lib/db_connection.ex:789: DBConnection.transaction/3                                                                                                                                                              
    (ecto) lib/ecto/migrator.ex:244: anonymous fn/4 in Ecto.Migrator.migrate/4                                                                                                                                                        
    (elixir) lib/enum.ex:1229: Enum."-map/2-lists^map/1-0-"/2                                                                                                                                                                         
    (ecto) lib/mix/tasks/ecto.migrate.ex:84: anonymous fn/4 in Mix.Tasks.Ecto.Migrate.run/2                                                                                                                                           
    (elixir) lib/enum.ex:645: Enum."-each/2-lists^foreach/1-0-"/2                                                                                                                                                                     
    (elixir) lib/enum.ex:645: Enum.each/2                                                                                                                                                                                             
                                                                                                                                                                                                                                      
$
1 Like

You were not. :slight_smile: It should be fixed in master now.

3 Likes

Just wait until the final release :grin:

3 Likes

Can someone remind me if Programming Phoenix book will be a free upgrade for 1.3?

4 Likes

Haha yeah!

Just broken the 100-like mark :003:

I think one of the reasons people are so thrilled about this release because it shows just how self-assured Phoenix is and how it does not feel the need to ‘take over’ your entire app (as many other frameworks do). Instead it is doing everything it can to complement, support and strengthen Elixir. This kind of cohesion is just awesome and I think it really resonates with people :slight_smile:

In my experience I have found that Pragprog offer free upgrades unless it is a major rewrite of the book (usually in accordance with a major new version of the language/framework). But even that isn’t always strictly true (and is just a guess) - when I got Programming Ruby 1.9 they gave me PR2.0 free, when I got Agile Web Dev with Rails 3, they gave AWDWR 4 free. I don’t think I have ever paid for an upgrade tbh and I think this is one of the reasons why they sell so many more books than other publishers - many of us buy them knowing we want to read them at some point, rather than immediately, and are happy in the knowledge that we’ll probably get the latest version when we are ready for it (within reason).

Having said that, this is just a guess based on my experience - if in doubt email Pragprog.

4 Likes

An RC can be treated as a final release, only bugs will be fixed :slight_smile:

2 Likes

Still, it is not recommended to use a RC in production, while you can a final release.

3 Likes

I wanted to provide some feedback on the changes in Phoenix 1.3.

First, the organization for a new umbrella app is really nice. Moving my models into a separate OTP app that the web app references is something I pieced together in 1.2 with examples posted on this forum. (https://github.com/wojtekmach/acme_bank)

Grouping up functional areas for the models is probably a good idea. It happens naturally over the course of a larger application, and it’s a solid DDD approach, so I am for that.

Where I am having trouble is the top level files we will be maintaining, like accounts.ex and accounts_test.exs.

  1. Many functions
    These files are going to become unwieldy very quickly: 3 models is 21 functions and that’s just getting started.

  2. That are very similar
    These files have repeated functionality that is extremely similar across models. Accounts.list_users, Accounts.list_roles, Accounts.list_permissions. My thought is that I should get Accounts.User.list / get! / update / etc for free through a used module. I am trying to weigh that against being too OO, but I think there is precedence with user Genserver and providing callbacks to supply arguments the common functionality will use. A function that provides a changeset function and struct/map type to a Model module is what comes to mind.

  3. Changesets aren’t by their schemas
    I am finding the natural flow for me is to look at my schema while writing my changeset logic, and even though I can open it in another tab, it feels like it should be in the same file.

I like the 1.2 idea of testing models separate from repo side effects. I don’t want to lock into existing ways, but I think some of the new changes are going to result in more code the end developers will have to write and maintain.

I realize I am free to do as I please, and that a lot of thought went into these changes. Overall the changes are great, but maybe the above can help make things a little smoother. Thanks.

4 Likes