phoenix snippets

How to fix "the response was already sent" error in Elixir

Tagged phoenix, plug, elixir  Languages elixir

If you get an “(Plug.Conn.AlreadySentError) the response was already sent” error:

Server: localhost:4000 (http)
Request: POST /api/v1/xxx
** (exit) an exception was raised:
    ** (Plug.Conn.AlreadySentError) the response was already sent
        (plug) lib/plug/conn.ex:332: Plug.Conn.put_status/2

Verify that you have not defined the action plug more than once:

plug :action

Multi-tenancy in Ecto and Phoenix

Tagged elixir, multi-tenancy, postgres, phoenix, ecto  Languages bash, elixir

Creating a new tentant

A new tenant requires a namespace, which is a schema in Postgres, and a prefix in Ecto:

$ psql -U postgres database_x
> create schema aktagon; 

Querying data

import Ecto.Query
email = "christian@aktagon.com"
q = from(m in User, where: m.email == ^email)
Repo.all(%{q | prefix: "aktagon"})

Documentation: https://hexdocs.pm/ecto/Ecto.Query.html#module-query-prefix

Inserting data

Repo.insert(
  Ecto.put_meta(
   %User{ email: "christian@aktagon.com" },
   prefix: "aktagon"
  )
)

Migrations

$ mix ecto.migrate --prefix "aktagon"

Notes

  • (KeyError) key :__meta__ not found

I got this error when passing a changeset to Ecto.put_meta instead of a User struct.

Pagination with Phoenix and Ecto

Tagged ecto, elixir, pagination, phoenix  Languages elixir

Tested with Ecto 2.2 and Phoenix 1.3.

This module allows you to perform pagination with Phoenix and Ecto:

defmodule Pagination do
  import Ecto.Query
  alias Snippets.Repo
  #
  # ## Example
  #
  #    Snippets.Snippet
  #    |> order_by(desc: :inserted_at)
  #    |> Pagination.page(0, per_page: 10)
  #
  def page(query, page, per_page: per_page) when is_nil(page) do
    page(query, 0, per_page: per_page)
  end

  def page(query, page, per_page: per_page) when is_binary(page) do
    page = String.to_integer(page)
    page(query, page, per_page: per_page)
  end

  def page(query, page, per_page: per_page) do
    count = per_page + 1
    result = query
             |> limit(^count)
             |> offset(^(page*per_page))
             |> Repo.all
    has_next = (length(result) == count)
    has_prev = page > 0
    total_count = Repo.one(from t in subquery(query), select: count("*"))
    page = %{
      has_next: has_next,
      has_prev: has_prev,
      prev_page: page - 1,
      next_page: page + 1,
      page: page,
      first: page*per_page+1,
      last: Enum.min([page+1*per_page, total_count]),
      count: total_count,
      list: Enum.slice(result, 0, count-1)
    }
  end
end

Usage:

Snippets.Snippet
|> order_by(desc: :inserted_at)
|> Pagination.page(0, per_page: 10)

View helper

defmodule PaginationHelpers do
  import Phoenix.HTML
  import Phoenix.HTML.Form
  import Phoenix.HTML.Link
  import Phoenix.HTML.Tag

  def pagination_text(list) do
    ~e"""
    Displaying <%= list.first %>-<%= list.last %> of <%= list.count %>
    """
  end

  def pagination_links(conn, list, route) do
    content_tag :div, class: "pagination" do
      children = []
      if list.has_prev do
        children = children ++ link "Previous", to: route.(conn, :index, page: list.prev_page), class: "btn btn-secondary col-md-1"
      end
      if list.has_next do
        children = children ++ link "Next", to: route.(conn, :index, page: list.next_page), class: "btn btn-secondary col-md-1"
      end
      children
    end
  end
end

Usage:

<%= pagination_text(@snippets) %>
<%= pagination_links(@conn, @snippets, &snippet_path/3) %>