Skip to content

Instantly share code, notes, and snippets.

@chrismccord
Last active August 11, 2020 04:21
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save chrismccord/557ef22e2a03a7992624 to your computer and use it in GitHub Desktop.
Save chrismccord/557ef22e2a03a7992624 to your computer and use it in GitHub Desktop.

Phoenix 1.0.x to 1.1.0 upgrade instructions

Deps

Update your phoenix, phoenix_html deps, and if using ecto, update your phoenix_ecto dep

# mix.exs
def deps do
  [{:phoenix, "~> 1.1"},
   {:phoenix_ecto, "~> 2.0"},
   {:phoenix_html, "~> 2.3"},
   ...]
end

Now run $ mix deps.update phoenix phoenix_html phoenix_ecto

View / Template Changes

The @inner assign has been removed in favor of explicit rendering with render/3 and the new @view_module and view_template assigns.

In your web/templates/layout/app.html.eex (and other layouts), replace:

<%= @inner %>

with:

<%= render @view_module, @view_template, assigns %>

Ecto Changes

Ecto 1.1 has renamed Ecto.Model to Ecto.Schema and moved many Model functions to the Ecto module. Update your web/web.ex blocks:

def model do
  quote
-   use Ecto.Model
+   use Ecto.Schema

+   import Ecto
    import Ecto.Changeset
    ...  
  end 
end

def controller do
  quote
    ...
    alias MyApp.Repo
+   import Ecto
    import Ecto.Query, only: [from: 1, from: 2]
    ...  
  end 
end

def channel do
  quote
    ...
    alias MyApp.Repo
+   import Ecto
    import Ecto.Query, only: [from: 1, from: 2]
    ...  
  end 
end

Gettext (optional)

Gettext has been added for internationalization and localization support. See the Gettext docs for full details.

To add Gettext support to your application first add :gettext to your deps in mix.exs:

def deps do
  [...,
   {:gettext, "~> 0.9"},
   ...]
end

Next, in mix.exs, add the :gettext compiler to your project and :gettext to your :applications list in application list:

def project do
  [...,
-  compilers: [:phoenix] ++ Mix.compilers,
+  compilers: [:phoenix, :gettext] ++ Mix.compilers,
  ...]
end

def application do
  [mod: {MyApp, []},
-  applications: [:phoenix, :phoenix_html, :cowboy, :logger,
-                 :phoenix_ecto, :postgrex]]
+  applications: [:phoenix, :phoenix_html, :cowboy, :logger, :gettext
+                 :phoenix_ecto, :postgrex]]
end

Next, run $ mix deps.get

Now, create a gettext.ex file at web/gettext.ex with the following contents:

defmodule MyApp.Gettext do
  @moduledoc """
  A module providing Internationalization with a gettext-based API.

  By using [Gettext](http://hexdocs.pm/gettext),
  your module gains a set of macros for translations, for example:

      import MyApp.Gettext

      # Simple translation
      gettext "Here is the string to translate"

      # Plural translation
      ngettext "Here is the string to translate",
               "Here are the strings to translate",
               3

      # Domain-based translation
      dgettext "errors", "Here is the error message to translate"

  See the [Gettext Docs](http://hexdocs.pm/gettext) for detailed usage.
  """
  use Gettext, otp_app: :my_app
end

Replace MyApp with your app module and :my_app with your otp app.

Next run the following commands from your app root to add necessary gettext supporting files:

$ mkdir -p priv/gettext/en/LC_MESSAGES
$ curl https://raw.githubusercontent.com/phoenixframework/phoenix/277eb7dd03366b336458ffe8dbf637c133b595f0/installer/templates/new/priv/gettext/en/LC_MESSAGES/errors.po > priv/gettext/en/LC_MESSAGES/errors.po
$ curl https://raw.githubusercontent.com/phoenixframework/phoenix/277eb7dd03366b336458ffe8dbf637c133b595f0/installer/templates/new/priv/gettext/errors.pot > priv/gettext/errors.pot

Next, create a web/views/error_helpers.ex and add these contents:

defmodule MyApp.ErrorHelpers do
  @moduledoc """
  Conveniences for translating and building error messages.
  """
  use Phoenix.HTML

  @doc """
  Generates tag for inlined form input errors.
  """
  def error_tag(form, field) do
    if error = form.errors[field] do
      content_tag :span, translate_error(error), class: "help-block"
    end
  end

  @doc """
  Translates an error message using gettext.
  """
  def translate_error({msg, opts}) do
    # Because error messages were defined within Ecto, we must
    # call the Gettext module passing our Gettext backend. We
    # also use the "errors" domain as translations are placed
    # in the errors.po file. On your own code and templates,
    # this could be written simply as:
    #
    #     dngettext "errors", "1 file", "%{count} files", count
    #
    Gettext.dngettext(MyApp.Gettext, "errors", msg, msg, opts[:count], opts)
  end

  def translate_error(msg) do
    Gettext.dgettext(MyApp.Gettext, "errors", msg)
  end
end

And now you can import Gettext and MyApp.ErrorHelpers into your web.ex view block:

      import MyApp.Router.Helpers
+     import MyApp.ErrorHelpers
+     import MyApp.Gettext

ChangesetView changes

Changests are no longer encoded to errors when encoding to JSON. The changeset errors should be rendered explicitly. Update your web/views/changeset_view.ex (if it exists) with the following code:

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

  @doc """
  Traverses and translates changeset errors.

  See `Ecto.Changeset.traverse_errors/2` and
  `MyApp.ErrorHelpers.translate_error/1` for more details.
  """
  def translate_errors(changeset) do
    Ecto.Changeset.traverse_errors(changeset, &translate_error/1)
  end

  def render("error.json", %{changeset: changeset}) do
    # When encoded, the changeset returns its errors
    # as a JSON object. So we just pass it forward.
    %{errors: translate_errors(changeset)}
  end
end

If you are not using Gettext with the ErrorHelpers module, add this function to your ChangesetView:

def translate_error({msg, opts}) do
  String.replace(msg, "%{count}", to_string(opts[:count]))
end
def translate_error(msg), do: msg

Channels JavaScript Client changes

after hooks have been replaced by a timeout option on push, and a receive("timeout", callback) hook.

// 1.0.x
channel.push("new_message", {body: "hi!"})
       .receive("ok", resp => console.log(resp) )
       .after(5000, () => console.log("times! up"))

channel.push("new_message", {body: "hi!"})
       .receive("ok", resp => console.log(resp) )
       .after(12000, () => console.log("times! up"))

// 1.1.0
// timeout default to 5000
channel.push("new_message", {body: "hi!"}) 
       .receive("ok", resp => console.log(resp) )
       .receive("timeout", () => console.log("times! up"))

// custom timeout as optional 3rd arg
channel.push("new_message", {body: "hi!"}, 12000)
       .receive("ok", resp => console.log(resp) )
       .receive("timeout", () => console.log("times! up"))
@riverrun
Copy link

Related to the Ecto upgrades, Ecto.Model.build is now Ecto.build_assoc, so after making the above changes to web/web.ex, any calls to build should be replaced with build_assoc.

@almassapargali
Copy link

Hello, thanks for this, maybe this document should also mention this commit?

@notdevinclark
Copy link

One thing that I ran into with the upgrade was my form errors breaking, especially if you are following along with the Programming Phoenix book. In the book, it mentions using this for form errors:

  <%= if f.errors != [] do %>
    <div class="alert alert-danger">
      <p>
        Oops, somthing went wrong! Please check the errors below:
      </p>
      <ul>
        <%= for {attr, message} <- f.errors do %>
          <li>
            <%= humanize(attr) %> <%= message %>
          </li>
        <% end %>
      </ul>
    </div>
  <% end %>

The book hasn't been updated yet since it is still in beta, but I wanted to try out the newest version, on so doing, I got this error:

no function clause matching in Phoenix.HTML.Safe.Tuple.to_iodata/1

This is because the new update, whether using gettext or just a ChangesetView module, adds the translate_errors function, which you need to use to wrap your error messages, like so:

  <%= if f.errors != [] do %>
    <div class="alert alert-danger">
      <p>
        Oops, somthing went wrong! Please check the errors below:
      </p>
      <ul>
        <%= for {attr, message} <- f.errors do %>
          <li>
-            <%= humanize(attr) %> <%= message %>
+            <%= humanize(attr) %> <%= translate_errors(message) %>
          </li>
        <% end %>
      </ul>
    </div>
  <% end %>

Just thought I'd mention it since it took me forever to figure out what was wrong and I wanted to use the newest version while going through the book. Hope this helps someone out.

@jimbeaudoin
Copy link

@notdevinclark Thank you for this. A little typo, it is translate_error without an s at the end.

@bartekupartek
Copy link

after update I can't run server, it seems that not all features are moved to Ecto.

== Compilation error on file web/models/user.ex ==
** (CompileError) web/models/user.ex:4: undefined function before_insert/1
    (stdlib) erl_eval.erl:669: :erl_eval.do_apply/6
    (elixir) lib/kernel/parallel_compiler.ex:100: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/8```

@tomwilsn
Copy link

@bartekupartek Check the Ecto 1.1 release notes (http://blog.plataformatec.com.br/2015/12/ecto-v1-1-released-and-ecto-v2-0-plans/), specifically the section titled "Goodbye callbacks"

@henrik
Copy link

henrik commented Dec 21, 2015

The diff above isn't quite accurate: web/web.ex doesn't indicate that a bunch of import Ecto.Model should also be removed (replaced by import Ecto).

I also had import Ecto.Model in test/support/model_case.ex and some other support files. I grepped for Ecto.Model and replaced every import Ecto.Model with import Ecto.

If you use ex_machina: I got errors like "** (UndefinedFunctionError) undefined function: Ecto.Association.loaded?/1" when running the tests. Went away by upgrading ex_machina from 0.5.0 to 0.6.0.

@the-guitarman
Copy link

I moved my project from phoenix 1.0.3 to 1.1 and I added gettext. Now I can't start my server:

==> gettext
could not compile dependency :gettext, "mix compile" failed. You can recompile this dependency with "mix deps.compile gettext", update it with "mix deps.update gettext" or clean it with "mix deps.clean gettext"
** (UndefinedFunctionError) undefined function: :yecc.file/2 (module :yecc is not available)
:yecc.file('src/gettext_po_parser.yrl', [parserfile: 'src/gettext_po_parser.erl', report: true])
(mix) lib/mix/compilers/erlang.ex:84: anonymous fn/3 in Mix.Compilers.Erlang.compile/3
...

UPDATE: I removed gettext and it works again.

@bansalakhil
Copy link

On ubuntu machine installing erlang-parsetools solved the issue

@TigerWolf
Copy link

When upgrading, it appears that my web.ex was missing:

      import Ecto.Changeset
      import Ecto.Query, only: [from: 1, from: 2]

This caused problems (cast/4 undefined function). It was only through creating a new project I was able to see the differences and add these missing lines.

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