Phoenix v1.3.0-rc.0 released

As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure, first class umbrella project support, and scaffolding that re-enforces phoenix as a web-interface to your greater Elixir application. We have also included a new action_fallback feature in Phoenix.Controller that allows you to translate common datastructures in your domain to valid responses. In practice, this cleans up your controller code and gives you a single plug to handle otherwise duplicated code paths. It is particularly nice for JSON API controllers.

For those interested in a detailed overview of the changes and design decisions, check out my LonestarElixir keynote that just went live: https://www.youtube.com/watch?v=tMO28ar0lW8

To use the new phx.new project generator, you can install the archive with the following command:

$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez

As always, we have an upgrade guide with detailed instructions for migrating from 1.2.x projects: https://gist.github.com/chrismccord/71ab10d433c98b714b75c886eff17357

1.3.0 is a backwards compatible release, so upgrading can be as easy as bumping your :phoenix dep in mix.exs to “~> 1.3”. For those wanting to adopt the new conventions, the upgrade guides will take you step-by-step. Before you upgrade, it’s worth watching the keynote or exploring the design decisions outlined below.

Phoenix 1.3 – Design With Intent

The new project and code generators take the lessons learned from the last two years and push folks towards better design decisions as they’re learning. Changes to new projects include moving web inside lib to avoid any special directories, as well as the introduction of the new Web namespace convention to further signal that your Phoenix related web modules are the web interface into your greater Elixir application. Along with new project structure, comes new phx.gen.html and phx.html.json generators that adopt these goals of isolating your web interface from your domain.

Contexts

When you generate a HTML or JSON resource with phx.gen.html|json, Phoenix will generate code inside a Context, which is simply a well-named module, with well-named functions representing an API boundary to part of your application domain.

For example, to generate a “user” resource we’d run:

$ mix phx.gen.html Accounts User users email:string:unique

Notice how “Accounts” is a new required first parameter. This is the context module where your code will live that carries out the business logic of user accounts in your application. Here’s a peek at part of the code that’s generated:

# lib/my_app/web/controllers/user_controller.ex
defmodule MyApp.Web.UserController do     
  ...
  alias MyApp.Accounts
  
  def index(conn, _params) do
    users = Accounts.list_users()
    render(conn, "index.html", users: users)
  end

  def create(conn, %{"user" => user_params}) do
    case Accounts.create_user(user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "user created successfully.")
        |> redirect(to: user_path(conn, :show, user))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end
  ...
end


# lib/my_app/accounts/accounts.ex

defmodule MyApp.Accounts do
  @moduledoc """
  The boundary for the Accounts system.
  """
  alias MyApp.Accounts.User
  
  def list_users do
    Repo.all(User)
  end
  
  def create_user(attrs \\ %{}) do
    %User{}
    |> user_changeset(attrs)
    |> Repo.insert()
  end
  ...
end

You will also have an Ecto schema generated inside lib/my_app/accounts/user.ex. Notice how our controller calls into an API boundary to create or fetch users in the system. We are still using Ecto, but the database and application logic are separated. Our web-interface doesn’t need to know the details of the storage or DB representatin of our User. The Accounts module could internally store users in an agent, ETS, or elsewhere and our controller code remains untouched.

By asking users to think about the boundaries of their APIs, we end up with more maintainable, well structured code. Additionally, we can get a glimpse of what the application does and its feature-set just be exploring the application directory structure:

lib/my_app
├── accounts
│   ├── user.ex
│   └── accounts.ex
├── sales
│   ├── ticket.ex
│   ├── manager.ex
│   └── sales.ex
├── repo.ex
└── web
    ├── channels
    ├── controllers
    ├── templates
    └── views

With just a glance at the directory structure, we can see this application has a User Accounts system, as well as sales system. We can also infer that there is a natural boundary between these systems thru the sales.ex and accounts.ex modules. We can gain this insight without seeing a single line of code. Contrast that to the previous web/models, which did not reveal any relationship between files, and mostly reflected your database structure, providing no insight on how they actually related to your domain.

We are excited about these changes and their long-term payoff in maintainability. We also feel they’ll lead to sharable, isolated libraries that the whole community can take advantage of – inside and outside of Phoenix related projects.

If you have issues upgrading, please find us on #elixir-lang irc or slack and we’ll get things sorted out!

I would also like to give a special thank you to @wojtekmach for his help getting the new generators ready for prime-time. <3

Happy coding! :hatched_chick::fire:

-Chris

Full changelog:

1.3.0-rc.0 (2017-03-01)

  • Enhancements

    • [Generator] Add new phx.new, phx.new.web, phx.new.ecto
      project generators with improved application structure and support for
      umbrella applications
    • [Generator] Add new phx.gen.html and phx.gen.json resource
      generators with improved isolation of API boundaries
    • [Controller] Add current_path and current_url to generate a
      connection’s path and url
    • [Controller] Introduce action_fallback to registers a plug to
      call as a fallback to the controller action
    • [Controller] Wrap exceptions at controller to maintain connection
      state
    • [Channel] Add ability to configure channel event logging with
      :log_join and :log_handle_in options
    • [Channel] Warn on unhandled handle_info/2 messages
    • [Channel] Channels now distinguish from graceful exits and
      application restarts, allowing clients to enter error mode and
      reconnected after cold deploys.
    • [Router] document match support for matching on any http method
      with the special :* argument
    • [ConnTest] Add redirected_params/1 to return the named params
      matched in the router for the redirected URL
  • Deprecations

    • [Generator] All phoenix.* mix tasks have been deprecated in
      favor of new phx.* tasks
  • JavaScript client enhancements

    • Add ability to pass encode and decode functions to socket
      constructor for custom encoding and decoding of outgoing and incoming
      messages.
    • Detect heartbeat timeouts on client to handle ungraceful
      connection loss for faster socket error detection
    • Add support for AMD/RequireJS
157 Likes

Thank you to everyone who worked on this project!

7 Likes

Thanks for the post Chris. I really love the separation of the application logic and business logic and have been trying to implement it based off your talk from Elixir Conf but it is really helpful to have this post as a walkthrough. Thanks for all of your hard work!

4 Likes

Screaming Architecture :slight_smile: https://8thlight.com/blog/uncle-bob/2011/09/30/Screaming-Architecture.html

6 Likes

Great news :tada: Thanks to all Phoenix devs!

2 Likes

Is there a mistake in directory structure in the post - files context.ex are nested in directories?

2 Likes

@dgamidov seems like they’re on the same level to me.

Thanks Pheonix devs! I’m pretty excited about this one.

1 Like

Yay!! Brilliant!! Thanks for all the hard work all!

In case it’s helpful, I’ve also updated https://www.phoenixdiff.org/ to show the diffs for 1.3.0-rc.0

6 Likes

@aptinio in the post?

Directories accounts and sales are on the same level, but accounts.ex is nested in accounts:

lib/my_app
├── accounts
│   ├── user.ex
│   └── accounts.ex
├── sales

And in keynote blog and sales and blog.ex are on the same level.

1 Like

Looks really promising, can’t wait for final release. :slight_smile:

1 Like

Oh, right. The context file is nested in the post. I was looking at the screenshot.

2 Likes

Hm, should this work?

$ mix phx.gen.schema Blog.User blog_users name > /dev/null                                                                                                                                                                            
$ mix ecto.migrate > /dev/null                                                                                                                                                                                                        
$ mix phx.gen.schema Blog.Post blog_posts title user_id:references:users                                                                                                                                                              
** (Mix) Unknown type `users` given to generator                                                                                                                                                                                      
$ mix phx.gen.schema Blog.Post blog_posts title user_id:references:blog_users                                                                                                                                                         
** (Mix) Unknown type `blog_users` given to generator
$
1 Like

Hi, I can’t get deps after mix phx.new MyApp. Anyone can help?

$ mix deps.get
Running dependency resolution...

Failed to use "phoenix" because
  mix.exs specifies ~> 1.3.0-rc

** (Mix) Hex dependency resolution failed, relax the version requirements of your dependencies or unlock them (by using mix deps.update or mix deps.unlock). If you are unable to resolve the conflicts you can try overriding with {:dependency, "~> 1.0", override: true}
1 Like

Have you tried the suggested solutions?

1 Like

Yes, I tried. But had same error report.

1 Like

We have decided to nest them after the talk was given. So the post is correct, the talk is slightly out of date.

7 Likes

Can you please run mix hex.info and let us know what it returns? Can you also include your mix.exs file?

2 Likes

Yes, it was supposed to work. Can you please do a bug report on the issues tracker? Thank you!

2 Likes
$ mix hex.info
Hex:    0.15.0
Elixir: 1.4.0
OTP:    19.1

Built with: Elixir 1.3.4 and OTP 18.3.4.4

I did’t change mix.exs file after running mix phx.new three.

defmodule Three.Mixfile do
  use Mix.Project

  def project do
    [app: :three,
     version: "0.0.1",
     elixir: "~> 1.4",
     elixirc_paths: elixirc_paths(Mix.env),
     compilers: [:phoenix, :gettext] ++ Mix.compilers,
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     aliases: aliases(),
     deps: deps()]
  end

  # Configuration for the OTP application.
  #
  # Type `mix help compile.app` for more information.
  def application do
    [mod: {Three.Application, []},
     extra_applications: [:logger]]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_),     do: ["lib"]

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [{:phoenix, "~> 1.3.0-rc"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.2"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     {:cowboy, "~> 1.0"}]
  end

  # Aliases are shortcuts or tasks specific to the current project.
  # For example, to create, migrate and run the seeds file at once:
  #
  #     $ mix ecto.setup
  #
  # See the documentation for `Mix` for more info on aliases.
  defp aliases do
    ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
     "ecto.reset": ["ecto.drop", "ecto.setup"],
     "test": ["ecto.create --quiet", "ecto.migrate", "test"]]
  end
end
2 Likes