Fix typo.

Fix typo.

defmodule Phoenix.HTML.Form do
  @moduledoc ~S"""
  Helpers related to producing HTML forms.

  The functions in this module can be used in three
  distinct scenario:

    * with model data - when information to populate
      the form comes from a model

    * with connection data - when a form is created based
      on the information in the connection (aka `Plug.Conn`)

    * without form data - when the functions are used directly,
      outside of a form

  We will explore all three scenarios below.

  ## With model data

  The entry point for defining forms in Phoenix is with
  the `form_for/4` function. For this example, we will
  use `Ecto.Changeset`, which integrate nicely with Phoenix
  forms via the `phoenix_ecto` package.

  Imagine you have the following action in your controller:

      def new(conn, _params) do
        changeset = User.changeset(%User{})
        render conn, "new.html", changeset: changeset
      end

  where `User.changeset/2` is defined as follows:

      def changeset(user, params \\ nil) do
        cast(user, params)
      end

  Now a `@changeset` assign is available in views which we
  can pass to the form:

      <%= form_for @changeset, user_path(@conn, :create), fn f -> %>
        <label>
          Name: <%= text_input f, :name %>
        </label>

        <label>
          Age: <%= select f, :age, 18..100 %>
        </label>

        <%= submit "Submit" %>
      <% end %>

  `form_for/4` receives the `Ecto.Changeset` and converts it
  to a form, which is passed to the function as the argument
  `f`. All the remaining functions in this module receive
  the form and automatically generate the input fields, often
  by extracting information from the given changeset. For example,
  if the user had a default value for age set, it will
  automatically show up as selected in the form.

  ## With connection data

  `form_for/4` expects as first argument any data structure that
  implements the `Phoenix.HTML.FormData` protocol. By default,
  Phoenix implements this protocol for `Plug.Conn`, allowing us
  to create forms based only on connection information.

  This is useful when you are creating forms that are not backed
  by any kind of model data, like a search form.

      <%= form_for @conn, search_path(@conn, :new), [name: :search], fn f -> %>
        <%= text_input f, :for %>
        <%= submit "Search" %>
      <% end %>

  ## Without form data

  Sometimes we may want to generate a `text_input/3` or any other
  tag outside of a form. The functions in this module also support
  such usage by simply passing an atom as first argument instead
  of the form.

      <%= text_input :user, :name, value: "This is a prepopulated value" %>

  """

  alias Phoenix.HTML.Form
  import Phoenix.HTML
  import Phoenix.HTML.Tag

  @doc """
  Defines the Phoenix.HTML.Form struct.

  Its fields are:

    * `:source` - the data structure given to `form_for/4` that
      implements the form data protocol

    * `:name` - the name to be used when generating input fields

    * `:model` - the model used to lookup field data

    * `:params` - the parameters associated to this form in case
      they were sent as part of a previous request

    * `:hidden` - a keyword list of fields that are required for
      submitting the form behind the scenes as hidden inputs. This
      information will be used by upcoming nested forms

    * `:options` - a copy of the options given when creating the
      form via `form_for/4` without any form data specific key

    * `:errors` - a keyword list of errors that associated with
      the form

    * `:validations` - a keyword list of validations for the given
      inputs
  """
  defstruct source: nil, name: nil, model: %{}, hidden: [], params: %{},
            errors: [], validations: [], options: []

  @type t :: %Form{source: term, name: String.t, model: %{atom => term},
                   params: %{binary => term}, hidden: Keyword.t, options: Keyword.t,
                   errors: Keyword.t, validations: Keyword.t}

  @doc """
  Converts an attribute/form field into its humanize version.

      iex> humanize(:username)
      "Username"
      iex> humanize(:created_at)
      "Created at"
      iex> humanize("user_id")
      "User"

  """
  def humanize(atom) when is_atom(atom),
    do: humanize(Atom.to_string(atom))
  def humanize(bin) when is_binary(bin) do
    bin =
      if String.ends_with?(bin, "_id") do
        binary_part(bin, 0, byte_size(bin) - 3)
      else
        bin
      end

    bin |> String.replace("_", " ") |> String.capitalize
  end

  @doc """
  Generates a form tag with a form builder.

  See the module documentation for examples of using this function.

  ## Options

    * `:name` - the name to be used in the form. May be inflected
      if a model is available

    * `:method` - the HTTP method. If the method is not "get" nor "post",
      an input tag with name `_method` is generated along-side the form tag.
      Defaults to "post".

    * `:multipart` - when true, sets enctype to "multipart/form-data".
      Required when uploading files

    * `:csrf_token` - for "post" requests, the form tag will automatically
      include an input tag with name `_csrf_token`. When set to false, this
      is disabled

    * `:enforce_utf8` - when false, does not enforce utf8

  See `Phoenix.HTML.Tag.form_tag/2` for more information on the
  options above.
  """
  @spec form_for(Phoenix.HTML.FormData.t, String.t,
                 Keyword.t, (t -> Phoenix.HTML.unsafe)) :: Phoenix.HTML.safe
  def form_for(form_data, action, options \\ [], fun) when is_function(fun, 1) do
    form = Phoenix.HTML.FormData.to_form(form_data, options)
    html_escape [form_tag(action, form.options), fun.(form), raw("</form>")]
  end

  ## Form helpers

  @doc """
  Generates a text input.

  The form should either be a `Phoenix.HTML.Form` emitted
  by `form_for` or an atom.

  All given options are forwarded to the underlying input,
  default values are provided for id, name and value if
  possible.

  ## Examples

      # Assuming form contains a User model
      text_input(form, :name)
      #=> <input id="user_name" name="user[name]" type="text" value="">

      text_input(:user, :name)
      #=> <input id="user_name" name="user[name]" type="text" value="">

  """
  def text_input(form, field, opts \\ []) do
    generic_input(:text, form, field, opts)
  end

  @doc """
  Generates a hidden input.

  See `text_input/3` for example and docs.
  """
  def hidden_input(form, field, opts \\ []) do
    generic_input(:hidden, form, field, opts)
  end

  @doc """
  Generates an email input.

  See `text_input/3` for example and docs.
  """
  def email_input(form, field, opts \\ []) do
    generic_input(:email, form, field, opts)
  end

  @doc """
  Generates a number input.

  See `text_input/3` for example and docs.
  """
  def number_input(form, field, opts \\ []) do
    generic_input(:number, form, field, opts)
  end

  @doc """
  Generates a password input.

  See `text_input/3` for example and docs.
  """
  def password_input(form, field, opts \\ []) do
    generic_input(:password, form, field, opts)
  end

  @doc """
  Generates an url input.

  See `text_input/3` for example and docs.
  """
  def url_input(form, field, opts \\ []) do
    generic_input(:url, form, field, opts)
  end

  @doc """
  Generates a search input.

  See `text_input/3` for example and docs.
  """
  def search_input(form, field, opts \\ []) do
    generic_input(:search, form, field, opts)
  end

  @doc """
  Generates a telephone input.

  See `text_input/3` for example and docs.
  """
  def telephone_input(form, field, opts \\ []) do
    generic_input(:tel, form, field, opts)
  end

  @doc """
  Generates a range input.

  See `text_input/3` for example and docs.
  """
  def range_input(form, field, opts \\ []) do
    generic_input(:range, form, field, opts)
  end

  defp generic_input(type, form, field, opts) when is_atom(field) and is_list(opts) do
    opts =
      opts
      |> Keyword.put_new(:type, type)
      |> Keyword.put_new(:id, id_from(form, field))
      |> Keyword.put_new(:name, name_from(form, field))
      |> Keyword.put_new(:value, value_from(form, field))
    tag(:input, opts)
  end

  @doc """
  Generates a textarea input.

  All given options are forwarded to the underlying input,
  default values are provided for id, name and textarea
  content if possible.

  ## Examples

      # Assuming form contains a User model
      textarea(form, :description)
      #=> <textarea id="user_description" name="user[description]"></textarea>

  ## New lines

  Notice the generated textarea includes a new line after
  the opening tag. This is because the HTML spec says new
  lines after tags must be ignored and all major browser
  implementations do that.

  So in order to avoid new lines provided by the user
  from being ignored when the form is resubmitted, we
  automatically add a new line before the text area
  value.
  """
  def textarea(form, field, opts \\ []) do
    opts =
      opts
      |> Keyword.put_new(:id, id_from(form, field))
      |> Keyword.put_new(:name, name_from(form, field))

    {value, opts} = Keyword.pop(opts, :value, value_from(form, field) || "")
    content_tag(:textarea, html_escape(["\n", value]), opts)
  end

  @doc """
  Generates a file input.

  It requires the given form to be configured with `multipart: true`
  when invoking `form_for/4`, otherwise it fails with `ArgumentError`.

  See `text_input/3` for example and docs.
  """
  def file_input(form, field, opts \\ []) do
    if match?(%Form{}, form) and !form.options[:multipart] do
      raise ArgumentError, "file_input/3 requires the enclosing form_for/4 " <>
                           "to be configured with multipart: true"
    end

    opts =
      opts
      |> Keyword.put_new(:type, :file)
      |> Keyword.put_new(:id, id_from(form, field))
      |> Keyword.put_new(:name, name_from(form, field))

    tag(:input, opts)
  end

  @doc """
  Generates a submit input to send the form.

  All options are forwarded to the underlying input tag.

  ## Examples

      submit "Submit"
      #=> <input type="submit" value="Submit">

  """
  def submit(value, opts \\ []) do
    opts =
      opts
      |> Keyword.put_new(:type, "submit")
      |> Keyword.put_new(:value, value)
    tag(:input, opts)
  end

  @doc """
  Generates a radio button.

  Invoke this function for each possible value you to be
  sent to the server.

  ## Examples

      # Assuming form contains a User model
      radio_button(form, :role, "admin")
      #=> <input id="user_role_admin" name="user[role]" type="radio" value="admin">

  ## Options

  All options are simply forwarded to the underlying HTML tag.
  """
  def radio_button(form, field, value, opts \\ []) do
    value = html_escape(value)

    opts =
      opts
      |> Keyword.put_new(:type, "radio")
      |> Keyword.put_new(:id, id_from(form, field) <> "_" <> elem(value, 1))
      |> Keyword.put_new(:name, name_from(form, field))

    if value == html_escape(value_from(form, field)) do
      opts = Keyword.put_new(opts, :checked, true)
    end

    tag(:input, [value: value] ++ opts)
  end

  @doc """
  Generates a checkbox.

  This function is useful for sending boolean values to the server.

  ## Examples

      # Assuming form contains a User model
      checkbox(form, :famous)
      #=> <input name="user[famous]" type="hidden" value="false">
          <input checked="checked" id="user_famous" name="user[famous]"> type="checkbox" value="true")

  ## Options

    * `:checked_value` - the value to be sent when the checkbox is checked.
      Defaults to "true"

    * `:unchecked_value` - the value to be sent then the checkbox is unchecked,
      Defaults to "false"

    * `:value` - the value used to check if a checkbox is checked or unchecked.
      The default value is extracted from the model if a model is available

  All other options are forwarded to the underlying HTML tag.

  ## Hidden fields

  Because an unchecked checkbox is not sent to the server, Phoenix
  automatically generates a hidden field with the unchecked_value
  *before* the checkbox field to ensure the `unchecked_value` is sent
  when the checkbox is not marked.
  """
  def checkbox(form, field, opts \\ []) do
    opts =
      opts
      |> Keyword.put_new(:type, "checkbox")
      |> Keyword.put_new(:id, id_from(form, field))
      |> Keyword.put_new(:name, name_from(form, field))

    {value, opts}           = Keyword.pop(opts, :value, value_from(form, field))
    {checked_value, opts}   = Keyword.pop(opts, :checked_value, true)
    {unchecked_value, opts} = Keyword.pop(opts, :unchecked_value, false)

    # We html escape all values to be sure we are comparing
    # apples to apples. After all we may have true in the model
    # but "true" in the params and both need to match.
    value           = html_escape(value)
    checked_value   = html_escape(checked_value)
    unchecked_value = html_escape(unchecked_value)

    if value == checked_value do
      opts = Keyword.put_new(opts, :checked, true)
    end

    html_escape [tag(:input, name: Keyword.get(opts, :name), type: "hidden", value: unchecked_value),
                 tag(:input, [value: checked_value] ++ opts)]
  end

  @doc """
  Generates a select tag with the given `values`.

  Values are expected to be an Enumerable containing two-item tuples
  (like maps and keyword lists) or any Enumerable where the element
  will be used both as key and value for the generated select.

  ## Examples

      # Assuming form contains a User model
      select(form, :age, 0..120)
      #=> <select id="user_age" name="user[age]">
          <option value="0">0</option>
          ...
          <option value="120">120</option>
          </select>

      select(form, :role, ["Admin": "admin", "User": "user"])
      #=> <select id="user_role" name="user[role]">
          <option value="admin">Admin</option>
          <option value="user">User</option>
          </select>

      select(form, :role, ["Admin": "admin", "User": "user"], prompt: "Choose your role")
      #=> <select id="user_role" name="user[role]">
          <option value="">Choose your role</option>
          <option value="admin">Admin</option>
          <option value="user">User</option>
          </select>

  ## Options

    * `:prompt` - an option to include at the top of the options with
      the given prompt text

    * `:value` - the value used to select a given option.
      The default value is extracted from the model if a model is available

    * `:default` - the default value to use when none was given in
      `:value` and none was available in the model

  All other options are forwarded to the underlying HTML tag.
  """
  def select(form, field, values, opts \\ []) do
    {default, opts} = Keyword.pop(opts, :default)
    {value, opts}   = Keyword.pop(opts, :value, value_from(form, field) || default)

    {options, opts} = case Keyword.pop(opts, :prompt) do
      {nil, opts}    -> {raw(""), opts}
      {prompt, opts} -> {content_tag(:option, prompt, value: ""), opts}
    end

    opts =
      opts
      |> Keyword.put_new(:id, id_from(form, field))
      |> Keyword.put_new(:name, name_from(form, field))

    options = options_for_select(values, options, html_escape(value))
    content_tag(:select, options, opts)
  end

  defp options_for_select(values, options, value) do
    Enum.reduce values, options, fn
      {option_key, option_value}, acc ->
        option_key   = html_escape(option_key)
        option_value = html_escape(option_value)
        option(option_key, option_value, value, acc)
      option, acc ->
        option = html_escape(option)
        option(option, option, value, acc)
    end
  end

  defp option(option_key, option_value, value, acc) do
    opts = [value: option_value, selected: value == option_value]
    html_escape [acc|content_tag(:option, option_key, opts)]
  end

  ## Datetime

  @doc ~S"""
  Generates select tags for datetime.

  ## Examples

      # Assuming form contains a User model
      datetime_select form, :born_at
      #=> <select id="user_born_at_year" name="user[born_at][year]">...</select> /
          <select id="user_born_at_month" name="user[born_at][month]">...</select> /
          <select id="user_born_at_day" name="user[born_at][day]">...</select> —
          <select id="user_born_at_hour" name="user[born_at][hour]">...</select> :
          <select id="user_born_at_min" name="user[born_at][min]">...</select>

  If you want to include the seconds field (hidden by default), pass `sec: []`:

      # Assuming form contains a User model
      datetime_select form, :born_at, sec: []

  If you want to configure the years range:

      # Assuming form contains a User model
      datetime_select form, :born_at, year: [options: 1900..2100]

  You are also able to configure `:month`, `:day`, `:hour`, `:min` and
  `:sec`. All options given to those keys will be forwarded to the
  underlying select. See `select/4` for more information.

  ## Options

    * `:value` - the value used to select a given option.
      The default value is extracted from the model if a model is available

    * `:default` - the default value to use when none was given in
      `:value` and none was available in the model

    * `:year`, `:month`, `:day`, `:hour`, `:min`, `:sec` - options passed
      to the underlying select. See `select/4` for more information.
      The available values can be given in `:options`.

    * `:builder` - specify how the select can be build. It must be a function
      that receives a builder that should be invoked with the select name
      and a set of options. See builder below for more information.

  ## Builder

  The generated datetime_select can be customized at will by providing a
  builder option. Here is an example from EEx:

      <%= datetime_select form, :born_at, builder: fn b -> %>
        Date: <%= b.(:day, []) %> / <%= b.(:month, []) %> / <%= b.(:hour, []) %>
        Time: <%= b.(:hour, []) %> : <%= b.(:min, []) %>
      <% end %>

  Although we have passed empty lists as options (they are required), you
  could pass any option there and it would be given to the underlying select
  input.

  In practice, we recommend you to create your own helper with your default
  builder:

      def my_datetime_select(form, field, opts \\ []) do
        builder = fn b ->
          ~e"\""
          Date: <%= b.(:day, []) %> / <%= b.(:month, []) %> / <%= b.(:hour, []) %>
          Time: <%= b.(:hour, []) %> : <%= b.(:min, []) %>
          "\""
        end

        datetime_select(form, field, [builder: builder] ++ opts)
      end

  Then you are able to use your own datetime_select throughout your whole
  application.

  ## Supported date values

  The following values are supported as date:

    * a map containing the `year`, `month` and `day` keys (either as strings or atoms)
    * a tuple with three elements: `{year, month, day}`
    * `nil`

  ## Supported time values

  The following values are supported as time:

    * a map containing the `hour` and `min` keys and an optional `sec` key (either as strings or atoms)
    * a tuple with three elements: `{hour, min, sec}`
    * a tuple with four elements: `{hour, min, sec, usec}`
    * `nil`

  """
  def datetime_select(form, field, opts \\ []) do
    value = Keyword.get(opts, :value, value_from(form, field) || Keyword.get(opts, :default))

    builder =
      Keyword.get(opts, :builder) || fn b ->
        date = date_builder(b, opts)
        time = time_builder(b, opts)
        html_escape [date, raw(" — "), time]
      end

    builder.(datetime_builder(form, field, date_value(value), time_value(value), opts))
  end

  @doc """
  Generates select tags for date.

  Check `datetime_select/3` for more information on options and supported values.
  """
  def date_select(form, field, opts \\ []) do
    value   = Keyword.get(opts, :value, value_from(form, field) || Keyword.get(opts, :default))
    builder = Keyword.get(opts, :builder) || &date_builder(&1, opts)
    builder.(datetime_builder(form, field, date_value(value), nil, opts))
  end

  defp date_builder(b, _opts) do
    html_escape [b.(:year, []), raw(" / "), b.(:month, []), raw(" / "), b.(:day, [])]
  end

  defp date_value(%{"year" => year, "month" => month, "day" => day}),
    do: %{year: year, month: month, day: day}
  defp date_value(%{year: year, month: month, day: day}),
    do: %{year: year, month: month, day: day}

  defp date_value({{year, month, day}, _}),
    do: %{year: year, month: month, day: day}
  defp date_value({year, month, day}),
    do: %{year: year, month: month, day: day}

  defp date_value(nil),
    do: %{year: nil, month: nil, day: nil}
  defp date_value(other),
    do: raise(ArgumentError, "unrecognized date #{inspect other}")

  @doc """
  Generates select tags for time.

  Check `datetime_select/3` for more information on options and supported values.
  """
  def time_select(form, field, opts \\ []) do
    value   = Keyword.get(opts, :value, value_from(form, field) || Keyword.get(opts, :default))
    builder = Keyword.get(opts, :builder) || &time_builder(&1, opts)
    builder.(datetime_builder(form, field, nil, time_value(value), opts))
  end

  defp time_builder(b, opts) do
    time = html_escape [b.(:hour, []), raw(" : "), b.(:min, [])]

    if Keyword.get(opts, :sec) do
      html_escape [time, raw(" : "), b.(:sec, [])]
    else
      time
    end
  end

  defp time_value(%{"hour" => hour, "min" => min} = map),
    do: %{hour: hour, min: min, sec: Map.get(map, "sec", 0)}
  defp time_value(%{hour: hour, min: min} = map),
    do: %{hour: hour, min: min, sec: Map.get(map, :sec, 0)}

  defp time_value({_, {hour, min, sec, _msec}}),
    do: %{hour: hour, min: min, sec: sec}
  defp time_value({hour, min, sec, _mseg}),
    do: %{hour: hour, min: min, sec: sec}
  defp time_value({_, {hour, min, sec}}),
    do: %{hour: hour, min: min, sec: sec}
  defp time_value({hour, min, sec}),
    do: %{hour: hour, min: min, sec: sec}

  defp time_value(nil),
    do: %{hour: nil, min: nil, sec: nil}
  defp time_value(other),
    do: raise(ArgumentError, "unrecognized time #{inspect other}")

  @months [
    {"January", "1"},
    {"February", "2"},
    {"March", "3"},
    {"April", "4"},
    {"May", "5"},
    {"June", "6"},
    {"July", "7"},
    {"August", "8"},
    {"September", "9"},
    {"October", "10"},
    {"November", "11"},
    {"December", "12"},
  ]

  map = &Enum.map(&1, fn i ->
    i = Integer.to_string(i)
    {String.rjust(i, 2, ?0), i}
  end)

  @days   map.(1..31)
  @hours  map.(0..23)
  @minsec map.(0..59)

  defp datetime_builder(form, field, date, time, parent) do
    id   = Keyword.get(parent, :id, id_from(form, field))
    name = Keyword.get(parent, :name, name_from(form, field))

    fn
      :year, opts when date != nil ->
        {year, _, _}  = :erlang.date()
        {value, opts} = datetime_options(:year, year-5..year+5, id, name, parent, date, opts)
        select(:datetime, :year, value, opts)
      :month, opts when date != nil ->
        {value, opts} = datetime_options(:month, @months, id, name, parent, date, opts)
        select(:datetime, :month, value, opts)
      :day, opts when date != nil ->
        {value, opts} = datetime_options(:day, @days, id, name, parent, date, opts)
        select(:datetime, :day, value, opts)
      :hour, opts when time != nil ->
        {value, opts} = datetime_options(:hour, @hours, id, name, parent, time, opts)
        select(:datetime, :hour, value, opts)
      :min, opts when time != nil ->
        {value, opts} = datetime_options(:min, @minsec, id, name, parent, time, opts)
        select(:datetime, :min, value, opts)
      :sec, opts when time != nil ->
        {value, opts} = datetime_options(:sec, @minsec, id, name, parent, time, opts)
        select(:datetime, :sec, value, opts)
    end
  end

  defp datetime_options(type, values, id, name, parent, datetime, opts) do
    opts = Keyword.merge Keyword.get(parent, type, []), opts
    suff = Atom.to_string(type)

    {value, opts} = Keyword.pop(opts, :options, values)

    {value,
      opts
      |> Keyword.put_new(:id, id <> "_" <> suff)
      |> Keyword.put_new(:name, name <> "[" <> suff <> "]")
      |> Keyword.put_new(:value, Map.get(datetime, type))}
  end

  @doc """
  Generates a label tag.

  The form should either be a `Phoenix.HTML.Form` emitted
  by `form_for` or an atom.

  All given options are forwarded to the underlying tag.
  A default value is provided for `for` attribute but can
  be overriden if you pass a value to the `for` option.

  ## Examples

      # Assuming form contains a User model
      label(form, :name, "Name")
      #=> <label for="user_name">Name</label>

      label(:user, :email, "Email")
      #=> <label for="user_email">Email</label>
  """
  def label(form, field, text, opts \\ []) do
    opts = Keyword.put_new(opts, :for, id_from(form, field))
    content_tag(:label, text, opts)
  end

  ## Helpers

  defp value_from(%{model: model, params: params}, field) do
    case Map.fetch(params, Atom.to_string(field)) do
      {:ok, value} -> value
      :error -> Map.get(model, field)
    end
  end

  defp value_from(name, _field) when is_atom(name),
    do: nil

  defp id_from(%{name: name}, field),
    do: "#{name}_#{field}"
  defp id_from(name, field) when is_atom(name),
    do: "#{name}_#{field}"

  defp name_from(%{name: name}, field),
    do: "#{name}[#{field}]"
  defp name_from(name, field) when is_atom(name),
    do: "#{name}[#{field}]"
end

defmodule Phoenix.HTML.Form do
  @moduledoc ~S"""
  Helpers related to producing HTML forms.

  The functions in this module can be used in three
  distinct scenario:

    * with model data - when information to populate
      the form comes from a model

    * with connection data - when a form is created based
      on the information in the connection (aka `Plug.Conn`)

    * without form data - when the functions are used directly,
      outside of a form

  We will explore all three scenarios below.

  ## With model data

  The entry point for defining forms in Phoenix is with
  the `form_for/4` function. For this example, we will
  use `Ecto.Changeset`, which integrate nicely with Phoenix
  forms via the `phoenix_ecto` package.

  Imagine you have the following action in your controller:

      def new(conn, _params) do
        changeset = User.changeset(%User{})
        render conn, "new.html", changeset: changeset
      end

  where `User.changeset/2` is defined as follows:

      def changeset(user, params \\ nil) do
        cast(user, params)
      end

  Now a `@changeset` assign is available in views which we
  can pass to the form:

      <%= form_for @changeset, user_path(@conn, :create), fn f -> %>
        <label>
          Name: <%= text_input f, :name %>
        </label>

        <label>
          Age: <%= select f, :age, 18..100 %>
        </label>

        <%= submit "Submit" %>
      <% end %>

  `form_for/4` receives the `Ecto.Changeset` and converts it
  to a form, which is passed to the function as the argument
  `f`. All the remaining functions in this module receive
  the form and automatically generate the input fields, often
  by extracting information from the given changeset. For example,
  if the user had a default value for age set, it will
  automatically show up as selected in the form.

  ## With connection data

  `form_for/4` expects as first argument any data structure that
  implements the `Phoenix.HTML.FormData` protocol. By default,
  Phoenix implements this protocol for `Plug.Conn`, allowing us
  to create forms based only on connection information.

  This is useful when you are creating forms that are not backed
  by any kind of model data, like a search form.

      <%= form_for @conn, search_path(@conn, :new), [name: :search], fn f -> %>
        <%= text_input f, :for %>
        <%= submit "Search" %>
      <% end %>

  ## Without form data

  Sometimes we may want to generate a `text_input/3` or any other
  tag outside of a form. The functions in this module also support
  such usage by simply passing an atom as first argument instead
  of the form.

      <%= text_input :user, :name, value: "This is a prepopulated value" %>

  """

  alias Phoenix.HTML.Form
  import Phoenix.HTML
  import Phoenix.HTML.Tag

  @doc """
  Defines the Phoenix.HTML.Form struct.

  Its fields are:

    * `:source` - the data structure given to `form_for/4` that
      implements the form data protocol

    * `:name` - the name to be used when generating input fields

    * `:model` - the model used to lookup field data

    * `:params` - the parameters associated to this form in case
      they were sent as part of a previous request

    * `:hidden` - a keyword list of fields that are required for
      submitting the form behind the scenes as hidden inputs. This
      information will be used by upcoming nested forms

    * `:options` - a copy of the options given when creating the
      form via `form_for/4` without any form data specific key

    * `:errors` - a keyword list of errors that associated with
      the form

    * `:validations` - a keyword list of validations for the given
      inputs
  """
  defstruct source: nil, name: nil, model: %{}, hidden: [], params: %{},
            errors: [], validations: [], options: []

  @type t :: %Form{source: term, name: String.t, model: %{atom => term},
                   params: %{binary => term}, hidden: Keyword.t, options: Keyword.t,
                   errors: Keyword.t, validations: Keyword.t}

  @doc """
  Converts an attribute/form field into its humanize version.

      iex> humanize(:username)
      "Username"
      iex> humanize(:created_at)
      "Created at"
      iex> humanize("user_id")
      "User"

  """
  def humanize(atom) when is_atom(atom),
    do: humanize(Atom.to_string(atom))
  def humanize(bin) when is_binary(bin) do
    bin =
      if String.ends_with?(bin, "_id") do
        binary_part(bin, 0, byte_size(bin) - 3)
      else
        bin
      end

    bin |> String.replace("_", " ") |> String.capitalize
  end

  @doc """
  Generates a form tag with a form builder.

  See the module documentation for examples of using this function.

  ## Options

    * `:name` - the name to be used in the form. May be inflected
      if a model is available

    * `:method` - the HTTP method. If the method is not "get" nor "post",
      an input tag with name `_method` is generated along-side the form tag.
      Defaults to "post".

    * `:multipart` - when true, sets enctype to "multipart/form-data".
      Required when uploading files

    * `:csrf_token` - for "post" requests, the form tag will automatically
      include an input tag with name `_csrf_token`. When set to false, this
      is disabled

    * `:enforce_utf8` - when false, does not enforce utf8

  See `Phoenix.HTML.Tag.form_tag/2` for more information on the
  options above.
  """
  @spec form_for(Phoenix.HTML.FormData.t, String.t,
                 Keyword.t, (t -> Phoenix.HTML.unsafe)) :: Phoenix.HTML.safe
  def form_for(form_data, action, options \\ [], fun) when is_function(fun, 1) do
    form = Phoenix.HTML.FormData.to_form(form_data, options)
    html_escape [form_tag(action, form.options), fun.(form), raw("</form>")]
  end

  ## Form helpers

  @doc """
  Generates a text input.

  The form should either be a `Phoenix.HTML.Form` emitted
  by `form_for` or an atom.

  All given options are forwarded to the underlying input,
  default values are provided for id, name and value if
  possible.

  ## Examples

      # Assuming form contains a User model
      text_input(form, :name)
      #=> <input id="user_name" name="user[name]" type="text" value="">

      text_input(:user, :name)
      #=> <input id="user_name" name="user[name]" type="text" value="">

  """
  def text_input(form, field, opts \\ []) do
    generic_input(:text, form, field, opts)
  end

  @doc """
  Generates a hidden input.

  See `text_input/3` for example and docs.
  """
  def hidden_input(form, field, opts \\ []) do
    generic_input(:hidden, form, field, opts)
  end

  @doc """
  Generates an email input.

  See `text_input/3` for example and docs.
  """
  def email_input(form, field, opts \\ []) do
    generic_input(:email, form, field, opts)
  end

  @doc """
  Generates a number input.

  See `text_input/3` for example and docs.
  """
  def number_input(form, field, opts \\ []) do
    generic_input(:number, form, field, opts)
  end

  @doc """
  Generates a password input.

  See `text_input/3` for example and docs.
  """
  def password_input(form, field, opts \\ []) do
    generic_input(:password, form, field, opts)
  end

  @doc """
  Generates an url input.

  See `text_input/3` for example and docs.
  """
  def url_input(form, field, opts \\ []) do
    generic_input(:url, form, field, opts)
  end

  @doc """
  Generates a search input.

  See `text_input/3` for example and docs.
  """
  def search_input(form, field, opts \\ []) do
    generic_input(:search, form, field, opts)
  end

  @doc """
  Generates a telephone input.

  See `text_input/3` for example and docs.
  """
  def telephone_input(form, field, opts \\ []) do
    generic_input(:tel, form, field, opts)
  end

  @doc """
  Generates a range input.

  See `text_input/3` for example and docs.
  """
  def range_input(form, field, opts \\ []) do
    generic_input(:range, form, field, opts)
  end

  defp generic_input(type, form, field, opts) when is_atom(field) and is_list(opts) do
    opts =
      opts
      |> Keyword.put_new(:type, type)
      |> Keyword.put_new(:id, id_from(form, field))
      |> Keyword.put_new(:name, name_from(form, field))
      |> Keyword.put_new(:value, value_from(form, field))
    tag(:input, opts)
  end

  @doc """
  Generates a textarea input.

  All given options are forwarded to the underlying input,
  default values are provided for id, name and textarea
  content if possible.

  ## Examples

      # Assuming form contains a User model
      textarea(form, :description)
      #=> <textarea id="user_description" name="user[description]"></textarea>

  ## New lines

  Notice the generated textarea includes a new line after
  the opening tag. This is because the HTML spec says new
  lines after tags must be ignored and all major browser
  implementations do that.

  So in order to avoid new lines provided by the user
  from being ignored when the form is resubmitted, we
  automatically add a new line before the text area
  value.
  """
  def textarea(form, field, opts \\ []) do
    opts =
      opts
      |> Keyword.put_new(:id, id_from(form, field))
      |> Keyword.put_new(:name, name_from(form, field))

    {value, opts} = Keyword.pop(opts, :value, value_from(form, field) || "")
    content_tag(:textarea, html_escape(["\n", value]), opts)
  end

  @doc """
  Generates a file input.

  It requires the given form to be configured with `multipart: true`
  when invokig `form_for/4`, otherwise it fails with `ArgumentError`.

  See `text_input/3` for example and docs.
  """
  def file_input(form, field, opts \\ []) do
    if match?(%Form{}, form) and !form.options[:multipart] do
      raise ArgumentError, "file_input/3 requires the enclosing form_for/4 " <>
                           "to be configured with multipart: true"
    end

    opts =
      opts
      |> Keyword.put_new(:type, :file)
      |> Keyword.put_new(:id, id_from(form, field))
      |> Keyword.put_new(:name, name_from(form, field))

    tag(:input, opts)
  end

  @doc """
  Generates a submit input to send the form.

  All options are forwarded to the underlying input tag.

  ## Examples

      submit "Submit"
      #=> <input type="submit" value="Submit">

  """
  def submit(value, opts \\ []) do
    opts =
      opts
      |> Keyword.put_new(:type, "submit")
      |> Keyword.put_new(:value, value)
    tag(:input, opts)
  end

  @doc """
  Generates a radio button.

  Invoke this function for each possible value you to be
  sent to the server.

  ## Examples

      # Assuming form contains a User model
      radio_button(form, :role, "admin")
      #=> <input id="user_role_admin" name="user[role]" type="radio" value="admin">

  ## Options

  All options are simply forwarded to the underlying HTML tag.
  """
  def radio_button(form, field, value, opts \\ []) do
    value = html_escape(value)

    opts =
      opts
      |> Keyword.put_new(:type, "radio")
      |> Keyword.put_new(:id, id_from(form, field) <> "_" <> elem(value, 1))
      |> Keyword.put_new(:name, name_from(form, field))

    if value == html_escape(value_from(form, field)) do
      opts = Keyword.put_new(opts, :checked, true)
    end

    tag(:input, [value: value] ++ opts)
  end

  @doc """
  Generates a checkbox.

  This function is useful for sending boolean values to the server.

  ## Examples

      # Assuming form contains a User model
      checkbox(form, :famous)
      #=> <input name="user[famous]" type="hidden" value="false">
          <input checked="checked" id="user_famous" name="user[famous]"> type="checkbox" value="true")

  ## Options

    * `:checked_value` - the value to be sent when the checkbox is checked.
      Defaults to "true"

    * `:unchecked_value` - the value to be sent then the checkbox is unchecked,
      Defaults to "false"

    * `:value` - the value used to check if a checkbox is checked or unchecked.
      The default value is extracted from the model if a model is available

  All other options are forwarded to the underlying HTML tag.

  ## Hidden fields

  Because an unchecked checkbox is not sent to the server, Phoenix
  automatically generates a hidden field with the unchecked_value
  *before* the checkbox field to ensure the `unchecked_value` is sent
  when the checkbox is not marked.
  """
  def checkbox(form, field, opts \\ []) do
    opts =
      opts
      |> Keyword.put_new(:type, "checkbox")
      |> Keyword.put_new(:id, id_from(form, field))
      |> Keyword.put_new(:name, name_from(form, field))

    {value, opts}           = Keyword.pop(opts, :value, value_from(form, field))
    {checked_value, opts}   = Keyword.pop(opts, :checked_value, true)
    {unchecked_value, opts} = Keyword.pop(opts, :unchecked_value, false)

    # We html escape all values to be sure we are comparing
    # apples to apples. After all we may have true in the model
    # but "true" in the params and both need to match.
    value           = html_escape(value)
    checked_value   = html_escape(checked_value)
    unchecked_value = html_escape(unchecked_value)

    if value == checked_value do
      opts = Keyword.put_new(opts, :checked, true)
    end

    html_escape [tag(:input, name: Keyword.get(opts, :name), type: "hidden", value: unchecked_value),
                 tag(:input, [value: checked_value] ++ opts)]
  end

  @doc """
  Generates a select tag with the given `values`.

  Values are expected to be an Enumerable containing two-item tuples
  (like maps and keyword lists) or any Enumerable where the element
  will be used both as key and value for the generated select.

  ## Examples

      # Assuming form contains a User model
      select(form, :age, 0..120)
      #=> <select id="user_age" name="user[age]">
          <option value="0">0</option>
          ...
          <option value="120">120</option>
          </select>

      select(form, :role, ["Admin": "admin", "User": "user"])
      #=> <select id="user_role" name="user[role]">
          <option value="admin">Admin</option>
          <option value="user">User</option>
          </select>

      select(form, :role, ["Admin": "admin", "User": "user"], prompt: "Choose your role")
      #=> <select id="user_role" name="user[role]">
          <option value="">Choose your role</option>
          <option value="admin">Admin</option>
          <option value="user">User</option>
          </select>

  ## Options

    * `:prompt` - an option to include at the top of the options with
      the given prompt text

    * `:value` - the value used to select a given option.
      The default value is extracted from the model if a model is available

    * `:default` - the default value to use when none was given in
      `:value` and none was available in the model

  All other options are forwarded to the underlying HTML tag.
  """
  def select(form, field, values, opts \\ []) do
    {default, opts} = Keyword.pop(opts, :default)
    {value, opts}   = Keyword.pop(opts, :value, value_from(form, field) || default)

    {options, opts} = case Keyword.pop(opts, :prompt) do
      {nil, opts}    -> {raw(""), opts}
      {prompt, opts} -> {content_tag(:option, prompt, value: ""), opts}
    end

    opts =
      opts
      |> Keyword.put_new(:id, id_from(form, field))
      |> Keyword.put_new(:name, name_from(form, field))

    options = options_for_select(values, options, html_escape(value))
    content_tag(:select, options, opts)
  end

  defp options_for_select(values, options, value) do
    Enum.reduce values, options, fn
      {option_key, option_value}, acc ->
        option_key   = html_escape(option_key)
        option_value = html_escape(option_value)
        option(option_key, option_value, value, acc)
      option, acc ->
        option = html_escape(option)
        option(option, option, value, acc)
    end
  end

  defp option(option_key, option_value, value, acc) do
    opts = [value: option_value, selected: value == option_value]
    html_escape [acc|content_tag(:option, option_key, opts)]
  end

  ## Datetime

  @doc ~S"""
  Generates select tags for datetime.

  ## Examples

      # Assuming form contains a User model
      datetime_select form, :born_at
      #=> <select id="user_born_at_year" name="user[born_at][year]">...</select> /
          <select id="user_born_at_month" name="user[born_at][month]">...</select> /
          <select id="user_born_at_day" name="user[born_at][day]">...</select> —
          <select id="user_born_at_hour" name="user[born_at][hour]">...</select> :
          <select id="user_born_at_min" name="user[born_at][min]">...</select>

  If you want to include the seconds field (hidden by default), pass `sec: []`:

      # Assuming form contains a User model
      datetime_select form, :born_at, sec: []

  If you want to configure the years range:

      # Assuming form contains a User model
      datetime_select form, :born_at, year: [options: 1900..2100]

  You are also able to configure `:month`, `:day`, `:hour`, `:min` and
  `:sec`. All options given to those keys will be forwarded to the
  underlying select. See `select/4` for more information.

  ## Options

    * `:value` - the value used to select a given option.
      The default value is extracted from the model if a model is available

    * `:default` - the default value to use when none was given in
      `:value` and none was available in the model

    * `:year`, `:month`, `:day`, `:hour`, `:min`, `:sec` - options passed
      to the underlying select. See `select/4` for more information.
      The available values can be given in `:options`.

    * `:builder` - specify how the select can be build. It must be a function
      that receives a builder that should be invoked with the select name
      and a set of options. See builder below for more information.

  ## Builder

  The generated datetime_select can be customized at will by providing a
  builder option. Here is an example from EEx:

      <%= datetime_select form, :born_at, builder: fn b -> %>
        Date: <%= b.(:day, []) %> / <%= b.(:month, []) %> / <%= b.(:hour, []) %>
        Time: <%= b.(:hour, []) %> : <%= b.(:min, []) %>
      <% end %>

  Although we have passed empty lists as options (they are required), you
  could pass any option there and it would be given to the underlying select
  input.

  In practice, we recommend you to create your own helper with your default
  builder:

      def my_datetime_select(form, field, opts \\ []) do
        builder = fn b ->
          ~e"\""
          Date: <%= b.(:day, []) %> / <%= b.(:month, []) %> / <%= b.(:hour, []) %>
          Time: <%= b.(:hour, []) %> : <%= b.(:min, []) %>
          "\""
        end

        datetime_select(form, field, [builder: builder] ++ opts)
      end

  Then you are able to use your own datetime_select throughout your whole
  application.

  ## Supported date values

  The following values are supported as date:

    * a map containing the `year`, `month` and `day` keys (either as strings or atoms)
    * a tuple with three elements: `{year, month, day}`
    * `nil`

  ## Supported time values

  The following values are supported as time:

    * a map containing the `hour` and `min` keys and an optional `sec` key (either as strings or atoms)
    * a tuple with three elements: `{hour, min, sec}`
    * a tuple with four elements: `{hour, min, sec, usec}`
    * `nil`

  """
  def datetime_select(form, field, opts \\ []) do
    value = Keyword.get(opts, :value, value_from(form, field) || Keyword.get(opts, :default))

    builder =
      Keyword.get(opts, :builder) || fn b ->
        date = date_builder(b, opts)
        time = time_builder(b, opts)
        html_escape [date, raw(" — "), time]
      end

    builder.(datetime_builder(form, field, date_value(value), time_value(value), opts))
  end

  @doc """
  Generates select tags for date.

  Check `datetime_select/3` for more information on options and supported values.
  """
  def date_select(form, field, opts \\ []) do
    value   = Keyword.get(opts, :value, value_from(form, field) || Keyword.get(opts, :default))
    builder = Keyword.get(opts, :builder) || &date_builder(&1, opts)
    builder.(datetime_builder(form, field, date_value(value), nil, opts))
  end

  defp date_builder(b, _opts) do
    html_escape [b.(:year, []), raw(" / "), b.(:month, []), raw(" / "), b.(:day, [])]
  end

  defp date_value(%{"year" => year, "month" => month, "day" => day}),
    do: %{year: year, month: month, day: day}
  defp date_value(%{year: year, month: month, day: day}),
    do: %{year: year, month: month, day: day}

  defp date_value({{year, month, day}, _}),
    do: %{year: year, month: month, day: day}
  defp date_value({year, month, day}),
    do: %{year: year, month: month, day: day}

  defp date_value(nil),
    do: %{year: nil, month: nil, day: nil}
  defp date_value(other),
    do: raise(ArgumentError, "unrecognized date #{inspect other}")

  @doc """
  Generates select tags for time.

  Check `datetime_select/3` for more information on options and supported values.
  """
  def time_select(form, field, opts \\ []) do
    value   = Keyword.get(opts, :value, value_from(form, field) || Keyword.get(opts, :default))
    builder = Keyword.get(opts, :builder) || &time_builder(&1, opts)
    builder.(datetime_builder(form, field, nil, time_value(value), opts))
  end

  defp time_builder(b, opts) do
    time = html_escape [b.(:hour, []), raw(" : "), b.(:min, [])]

    if Keyword.get(opts, :sec) do
      html_escape [time, raw(" : "), b.(:sec, [])]
    else
      time
    end
  end

  defp time_value(%{"hour" => hour, "min" => min} = map),
    do: %{hour: hour, min: min, sec: Map.get(map, "sec", 0)}
  defp time_value(%{hour: hour, min: min} = map),
    do: %{hour: hour, min: min, sec: Map.get(map, :sec, 0)}

  defp time_value({_, {hour, min, sec, _msec}}),
    do: %{hour: hour, min: min, sec: sec}
  defp time_value({hour, min, sec, _mseg}),
    do: %{hour: hour, min: min, sec: sec}
  defp time_value({_, {hour, min, sec}}),
    do: %{hour: hour, min: min, sec: sec}
  defp time_value({hour, min, sec}),
    do: %{hour: hour, min: min, sec: sec}

  defp time_value(nil),
    do: %{hour: nil, min: nil, sec: nil}
  defp time_value(other),
    do: raise(ArgumentError, "unrecognized time #{inspect other}")

  @months [
    {"January", "1"},
    {"February", "2"},
    {"March", "3"},
    {"April", "4"},
    {"May", "5"},
    {"June", "6"},
    {"July", "7"},
    {"August", "8"},
    {"September", "9"},
    {"October", "10"},
    {"November", "11"},
    {"December", "12"},
  ]

  map = &Enum.map(&1, fn i ->
    i = Integer.to_string(i)
    {String.rjust(i, 2, ?0), i}
  end)

  @days   map.(1..31)
  @hours  map.(0..23)
  @minsec map.(0..59)

  defp datetime_builder(form, field, date, time, parent) do
    id   = Keyword.get(parent, :id, id_from(form, field))
    name = Keyword.get(parent, :name, name_from(form, field))

    fn
      :year, opts when date != nil ->
        {year, _, _}  = :erlang.date()
        {value, opts} = datetime_options(:year, year-5..year+5, id, name, parent, date, opts)
        select(:datetime, :year, value, opts)
      :month, opts when date != nil ->
        {value, opts} = datetime_options(:month, @months, id, name, parent, date, opts)
        select(:datetime, :month, value, opts)
      :day, opts when date != nil ->
        {value, opts} = datetime_options(:day, @days, id, name, parent, date, opts)
        select(:datetime, :day, value, opts)
      :hour, opts when time != nil ->
        {value, opts} = datetime_options(:hour, @hours, id, name, parent, time, opts)
        select(:datetime, :hour, value, opts)
      :min, opts when time != nil ->
        {value, opts} = datetime_options(:min, @minsec, id, name, parent, time, opts)
        select(:datetime, :min, value, opts)
      :sec, opts when time != nil ->
        {value, opts} = datetime_options(:sec, @minsec, id, name, parent, time, opts)
        select(:datetime, :sec, value, opts)
    end
  end

  defp datetime_options(type, values, id, name, parent, datetime, opts) do
    opts = Keyword.merge Keyword.get(parent, type, []), opts
    suff = Atom.to_string(type)

    {value, opts} = Keyword.pop(opts, :options, values)

    {value,
      opts
      |> Keyword.put_new(:id, id <> "_" <> suff)
      |> Keyword.put_new(:name, name <> "[" <> suff <> "]")
      |> Keyword.put_new(:value, Map.get(datetime, type))}
  end

  @doc """
  Generates a label tag.

  The form should either be a `Phoenix.HTML.Form` emitted
  by `form_for` or an atom.

  All given options are forwarded to the underlying tag.
  A default value is provided for `for` attribute but can
  be overriden if you pass a value to the `for` option.

  ## Examples

      # Assuming form contains a User model
      label(form, :name, "Name")
      #=> <label for="user_name">Name</label>

      label(:user, :email, "Email")
      #=> <label for="user_email">Email</label>
  """
  def label(form, field, text, opts \\ []) do
    opts = Keyword.put_new(opts, :for, id_from(form, field))
    content_tag(:label, text, opts)
  end

  ## Helpers

  defp value_from(%{model: model, params: params}, field) do
    case Map.fetch(params, Atom.to_string(field)) do
      {:ok, value} -> value
      :error -> Map.get(model, field)
    end
  end

  defp value_from(name, _field) when is_atom(name),
    do: nil

  defp id_from(%{name: name}, field),
    do: "#{name}_#{field}"
  defp id_from(name, field) when is_atom(name),
    do: "#{name}_#{field}"

  defp name_from(%{name: name}, field),
    do: "#{name}[#{field}]"
  defp name_from(name, field) when is_atom(name),
    do: "#{name}[#{field}]"
end


warn for unsupported constraints

warn for unsupported constraints

defmodule Sqlite.Ecto.Error do
  defexception [:message, :sqlite]

  def message(%__MODULE__{sqlite: {type, msg}}), do: "#{type}: #{msg}"
  def message(%__MODULE__{sqlite: msg}), do: "UNKNOWN: #{inspect msg}"
  def message(%__MODULE__{message: msg}), do: msg

  def exception({type, msg}), do: %__MODULE__{sqlite: {type, to_string(msg)}}
  def exception(msg), do: %__MODULE__{sqlite: msg}

  require Logger

  def to_constraints(%__MODULE__{sqlite: {:constraint, "UNIQUE constraint failed: " <> _}}) do
    Logger.warn "unique_constraint/3 is unsupported by SQLite"
    []
  end
  def to_constraints(%__MODULE__{sqlite: {:constraint, "FOREIGN KEY constraint failed"}}) do
    Logger.warn "foreign_key_constraint/3 is unsupported by SQLite"
    []
  end
  def to_constraints(_), do: []
end

defmodule Sqlite.Ecto.Error do
  defexception [:message, :sqlite]

  def message(%__MODULE__{sqlite: {type, msg}}), do: "#{type}: #{msg}"
  def message(%__MODULE__{sqlite: msg}), do: "UNKNOWN: #{inspect msg}"
  def message(%__MODULE__{message: msg}), do: msg

  def exception({type, msg}), do: %__MODULE__{sqlite: {type, to_string(msg)}}
  def exception(msg), do: %__MODULE__{sqlite: msg}

  def to_constraints(_), do: []
end


Clean up body code

Clean up body code

defmodule Tesla.Adapter.Ibrowse do
  def start do
    :ibrowse.start
  end

  def call(env) do
    opts = []

    if target = env.opts[:respond_to] do

      gatherer = spawn_link fn -> gather_response(env, target, {:ok, nil, nil, nil}) end

      opts = opts ++ [stream_to: gatherer]
      {:ibrowse_req_id, id} = send_req(env, opts)
      {:ok, id}
    else
      format_response(env, send_req(env, opts))
    end
  end

  defp format_response(env, res) do
    {:ok, status, headers, body} = res

    {status, _} = Integer.parse(to_string(status))
    headers     = Enum.into(headers, %{})

    %{env | status:   status,
            headers:  headers,
            body:     body}
  end

  defp send_req(env, opts) do
    body = env.body || []
    :ibrowse.send_req(
      env.url |> to_char_list,
      Enum.into(env.headers, []),
      env.method,
      body,
      opts
    )
  end

  defp gather_response(env, target, res) do
    {:ok, _status, _headers, _body} = res

    receive do
      {:ibrowse_async_headers, _, status, headers} ->
        gather_response(env, target, {:ok, status, headers, _body})

      {:ibrowse_async_response, _, body} ->
        body = if _body do
          _body <> body
        else
          body
        end

        gather_response(env, target, {:ok, _status, _headers, body})

      {:ibrowse_async_response_end, _} ->
        response = format_response(env, res)
        send target, {:tesla_response, response}
    end
  end
end

defmodule Tesla.Adapter.Ibrowse do
  def start do
    :ibrowse.start
  end

  def call(env) do
    opts = []

    if target = env.opts[:respond_to] do

      gatherer = spawn_link fn -> gather_response(env, target, {:ok, nil, nil, nil}) end

      opts = opts ++ [stream_to: gatherer]
      {:ibrowse_req_id, id} = send_req(env, opts)
      {:ok, id}
    else
      format_response(env, send_req(env, opts))
    end
  end

  defp format_response(env, res) do
    {:ok, status, headers, body} = res

    {status, _} = Integer.parse(to_string(status))
    headers     = Enum.into(headers, %{})

    %{env | status:   status,
            headers:  headers,
            body:     body}
  end

  defp send_req(env, opts) do
    :ibrowse.send_req(
      env.url |> to_char_list,
      Enum.into(env.headers, []),
      env.method,
      env.body || [],
      opts
    )
  end

  defp gather_response(env, target, res) do
    {:ok, _status, _headers, _body} = res

    receive do
      {:ibrowse_async_headers, _, status, headers} ->
        gather_response(env, target, {:ok, status, headers, _body})

      {:ibrowse_async_response, _, body} ->
        body = if _body do
          _body <> body
        else
          body
        end

        gather_response(env, target, {:ok, _status, _headers, body})

      {:ibrowse_async_response_end, _} ->
        response = format_response(env, res)
        send target, {:tesla_response, response}
    end
  end
end


tesla/middleware/json: allow DecodeJson,EncodeJson to use a different coder

defmodule Tesla.Middleware.DecodeJson do
  def call(env, run, opts \\ []) do
    decode = opts[:decode] || &JSX.decode/1

    env = run.(env)

    content_type = to_string(env.headers['Content-Type'])

    if String.starts_with?(content_type, "application/json") && (is_binary(env.body) || is_list(env.body)) do
      {:ok, body} = decode.(to_string(env.body))

      %{env | body: body}
    else
      env
    end
  end
end

defmodule Tesla.Middleware.EncodeJson do
  def call(env, run, opts \\ []) do
    encode = opts[:encode] || &JSX.encode/1

    if env.body do
      {:ok, body} = encode.(env.body)

      headers = %{'Content-Type' => 'application/json'}
      env = %{env | body: body}

      Tesla.Middleware.Headers.call(env, run, headers)
    else
      run.(env)
    end
  end
end

defmodule Tesla.Middleware.DecodeJson do
  def call(env, run, []) do
    env = run.(env)

    content_type = to_string(env.headers['Content-Type'])

    if String.starts_with?(content_type, "application/json") && (is_binary(env.body) || is_list(env.body)) do
      {:ok, body} = JSX.decode(to_string(env.body))

      %{env | body: body}
    else
      env
    end
  end
end

defmodule Tesla.Middleware.EncodeJson do
  def call(env, run, []) do

    if env.body do
      {:ok, body} = JSX.encode(env.body)

      headers = %{'Content-Type' => 'application/json'}
      env = %{env | body: body}

      Tesla.Middleware.Headers.call(env, run, headers)
    else
      run.(env)
    end
  end
end


templates updated

templates updated

defmodule #{projectNamespace}.Main do

  use Weber.Controller

  layout false
  def action([]) do
    {:render, [project: "#{projectName}"], []}
  end

end

defmodule #{projectNamespace}.Main do

  def action([]) do
    {:render, [project: "#{projectName}"], []}
  end

end


Fix DirtyWorker doing patch requests

Fix DirtyWorker doing patch requests

defmodule FarmbotExt.API.DirtyWorker do
  @moduledoc "Handles uploading/downloading of data from the API."
  alias FarmbotCore.Asset.{Private, Repo}

  alias FarmbotExt.{API, API.DirtyWorker}
  import API.View, only: [render: 2]

  require Logger
  use GenServer
  @timeout 10000

  @doc false
  def child_spec(module) when is_atom(module) do
    %{
      id: {DirtyWorker, module},
      start: {__MODULE__, :start_link, [[module: module, timeout: @timeout]]},
      type: :worker,
      restart: :permanent,
      shutdown: 500
    }
  end

  @doc "Start an instance of a DirtyWorker"
  def start_link(args) do
    GenServer.start_link(__MODULE__, args)
  end

  @impl GenServer
  def init(args) do
    # Logger.disable(self())
    module = Keyword.fetch!(args, :module)
    timeout = Keyword.get(args, :timeout, @timeout)
    {:ok, %{module: module, timeout: timeout}, timeout}
  end

  @impl GenServer
  def handle_info(:timeout, %{module: module} = state) do
    dirty = Private.list_dirty(module)
    local = Private.list_local(module)
    {:noreply, state, {:continue, Enum.uniq(dirty ++ local)}}
  end

  @impl GenServer
  def handle_continue([], state) do
    {:noreply, state, state.timeout}
  end

  def handle_continue([dirty | rest], %{module: module} = state) do
    Logger.info("[#{module} #{dirty.local_id} #{inspect(self())}] Handling dirty data")

    case http_request(dirty, state) do
      # Valid data
      {:ok, %{status: s, body: body}} when s > 199 and s < 300 ->
        Logger.debug(
          "[#{module} #{dirty.local_id} #{inspect(self())}] HTTP request complete: #{s} ok"
        )

        dirty |> module.changeset(body) |> handle_changeset(rest, state)

      # Invalid data
      {:ok, %{status: s, body: %{} = body}} when s > 399 and s < 500 ->
        Logger.debug(
          "[#{module} #{dirty.local_id} #{inspect(self())}] HTTP request complete: #{s} error+body"
        )

        changeset = module.changeset(dirty)

        Enum.reduce(body, changeset, fn {key, val}, changeset ->
          Ecto.Changeset.add_error(changeset, key, val)
        end)
        |> handle_changeset(rest, state)

      # Invalid data, but the API didn't say why
      {:ok, %{status: s, body: _body}} when s > 399 and s < 500 ->
        Logger.debug(
          "[#{module} #{dirty.local_id} #{inspect(self())}] HTTP request complete: #{s} error"
        )

        module.changeset(dirty)
        |> Map.put(:valid?, false)
        |> handle_changeset(rest, state)

      # HTTP Error. (500, network error, timeout etc.)
      error ->
        Logger.error(
          "[#{module} #{dirty.local_id} #{inspect(self())}] HTTP Error: #{state.module} #{
            inspect(error)
          }"
        )

        {:noreply, state, @timeout}
    end
  end

  # If the changeset was valid, update the record.
  def handle_changeset(%{valid?: true} = changeset, rest, state) do
    Logger.info("Successfully synced: #{state.module}")

    Repo.update!(changeset)
    |> Private.mark_clean!()

    {:noreply, state, {:continue, rest}}
  end

  # If the changeset was invalid, delete the record.
  # TODO(Connor) - Update the dirty field here, upload to rollbar?
  def handle_changeset(%{valid?: false, data: data} = changeset, rest, state) do
    message =
      Enum.map(changeset.errors, fn
        {key, {msg, _meta}} when is_binary(key) -> "\t#{key}: #{msg}"
        {key, msg} when is_binary(key) -> "\t#{key}: #{msg}"
      end)
      |> Enum.join("\n")

    Logger.error("Failed to sync: #{state.module} \n #{message}")
    _ = Repo.delete!(data)
    {:noreply, state, {:continue, rest}}
  end

  defp http_request(%{id: nil} = dirty, state) do
    Logger.debug("#{state.module} clean request (post)")
    path = state.module.path()
    data = render(state.module, dirty)
    API.post(API.client(), path, data)
  end

  defp http_request(dirty, state) do
    Logger.debug("#{state.module} dirty request (patch)")
    # IO.inspect(dirty, label: "PATCH")
    path = Path.join(state.module.path(), to_string(dirty.id))
    data = render(state.module, dirty)
    API.patch(API.client(), path, data)
  end
end

defmodule FarmbotExt.API.DirtyWorker do
  @moduledoc "Handles uploading/downloading of data from the API."
  alias FarmbotCore.Asset.{Private, Repo}

  alias FarmbotExt.{API, API.DirtyWorker}
  import API.View, only: [render: 2]

  require Logger
  use GenServer
  @timeout 10000

  @doc false
  def child_spec(module) when is_atom(module) do
    %{
      id: {DirtyWorker, module},
      start: {__MODULE__, :start_link, [[module: module, timeout: @timeout]]},
      type: :worker,
      restart: :permanent,
      shutdown: 500
    }
  end

  @doc "Start an instance of a DirtyWorker"
  def start_link(args) do
    GenServer.start_link(__MODULE__, args)
  end

  @impl GenServer
  def init(args) do
    # Logger.disable(self())
    module = Keyword.fetch!(args, :module)
    timeout = Keyword.get(args, :timeout, @timeout)
    {:ok, %{module: module, timeout: timeout}, timeout}
  end

  @impl GenServer
  def handle_info(:timeout, %{module: module} = state) do
    dirty = Private.list_dirty(module)
    local = Private.list_local(module)
    {:noreply, state, {:continue, Enum.uniq(dirty ++ local)}}
  end

  @impl GenServer
  def handle_continue([], state) do
    {:noreply, state, state.timeout}
  end

  def handle_continue([dirty | rest], %{module: module} = state) do
    Logger.info("[#{module} #{dirty.local_id} #{inspect(self())}] Handling dirty data")

    case http_request(dirty, state) do
      # Valid data
      {:ok, %{status: s, body: body}} when s > 199 and s < 300 ->
        Logger.debug(
          "[#{module} #{dirty.local_id} #{inspect(self())}] HTTP request complete: #{s} ok"
        )

        dirty |> module.changeset(body) |> handle_changeset(rest, state)

      # Invalid data
      {:ok, %{status: s, body: %{} = body}} when s > 399 and s < 500 ->
        Logger.debug(
          "[#{module} #{dirty.local_id} #{inspect(self())}] HTTP request complete: #{s} error+body"
        )

        changeset = module.changeset(dirty)

        Enum.reduce(body, changeset, fn {key, val}, changeset ->
          Ecto.Changeset.add_error(changeset, key, val)
        end)
        |> handle_changeset(rest, state)

      # Invalid data, but the API didn't say why
      {:ok, %{status: s, body: _body}} when s > 399 and s < 500 ->
        Logger.debug(
          "[#{module} #{dirty.local_id} #{inspect(self())}] HTTP request complete: #{s} error"
        )

        module.changeset(dirty)
        |> Map.put(:valid?, false)
        |> handle_changeset(rest, state)

      # HTTP Error. (500, network error, timeout etc.)
      error ->
        Logger.error(
          "[#{module} #{dirty.local_id} #{inspect(self())}] HTTP Error: #{state.module} #{
            inspect(error)
          }"
        )

        {:noreply, state, @timeout}
    end
  end

  # If the changeset was valid, update the record.
  def handle_changeset(%{valid?: true} = changeset, rest, state) do
    Logger.info("Successfully synced: #{state.module}")

    Repo.update!(changeset)
    |> Private.mark_clean!()

    {:noreply, state, {:continue, rest}}
  end

  # If the changeset was invalid, delete the record.
  # TODO(Connor) - Update the dirty field here, upload to rollbar?
  def handle_changeset(%{valid?: false, data: data} = changeset, rest, state) do
    message =
      Enum.map(changeset.errors, fn
        {key, {msg, _meta}} when is_binary(key) -> "\t#{key}: #{msg}"
        {key, msg} when is_binary(key) -> "\t#{key}: #{msg}"
      end)
      |> Enum.join("\n")

    Logger.error("Failed to sync: #{state.module} \n #{message}")
    _ = Repo.delete!(data)
    {:noreply, state, {:continue, rest}}
  end

  defp http_request(%{id: nil} = dirty, state) do
    Logger.debug("#{state.module} clean request (post)")
    path = state.module.path()
    data = render(state.module, dirty)
    API.post(API.client(), path, data)
  end

  defp http_request(dirty, state) do
    Logger.debug("#{state.module} dirty request (patch)")
    path = state.module.path()
    data = render(state.module, dirty)
    API.patch(API.client(), path, data)
  end
end


shutup compiler

shutup compiler

defprotocol Yodado.Definition.Rule do
  def matches?(definition, state)
  def json(definition)
end

defmodule Yodado.Definition do

  alias Yodado.Definition.Rule, as: Rule

  defrecord All, conditions: []
  defimpl Rule, for: All do
    def matches?(All[conditions: conditions], state) do
      conditions |> Enum.all?(&Rule.matches?(&1, state))
    end

    def json(All[conditions: conditions]) do
      [operand: "all", conditions: Enum.map(conditions, &Rule.json/1)]
    end
  end

  defrecord Any, conditions: []
  defimpl Rule, for: Any do
    def matches?(Any[conditions: conditions], state) do
      conditions |> Enum.any?(&Rule.matches?(&1, state))
    end

    def json(Any[conditions: conditions]) do
      [operand: "any", conditions: Enum.map(conditions, &Rule.json/1)]
    end
  end

  defrecord IncludedIn, param_name: nil, value: []
  defimpl Rule, for: IncludedIn do
    def matches?(IncludedIn[param_name: param_name, value: value], state) do
      actual_value = state[param_name]
      Enum.member?(value, actual_value)
    end

    def json(IncludedIn[param_name: param_name, value: value]) do
      [operand: "included_in", param_name: param_name, value: value]
    end
  end

  defrecord Is, param_name: nil, value: "true"
  defimpl Rule, for: Is do
    def matches?(Is[param_name: param_name, value: value], state) do
      state[param_name] == value
    end

    def json(Is[param_name: param_name, value: value]) do
      [operand: "is", param_name: param_name, value: value]
    end
  end

  defimpl Rule, for: Atom do
    def matches?(bool, _state) when is_boolean(bool), do: bool
    def json(bool) when is_boolean(bool), do: [operand: "boolean", value: bool]
  end

  defimpl Rule, for: Function do
    def matches?(f, state), do: f.(state)
    def json(_f), do: raise "not implemented"
  end

  def from_json(definition) do
    operand = Keyword.fetch!(definition, :operand)
    from_json(operand, definition)
  end

  defp from_json("any", definition) do
    conditions = Keyword.fetch!(definition, :conditions)
    Any[conditions: Enum.map(conditions, &from_json/1)]
  end

  defp from_json("all", definition) do
    conditions = Keyword.fetch!(definition, :conditions)
    All[conditions: Enum.map(conditions, &from_json/1)]
  end

  defp from_json("included_in", definition) do
    param_name = Keyword.fetch!(definition, :param_name)
    value = Keyword.fetch!(definition, :value)
    true = is_list(value) # validate we've got a list
    IncludedIn[param_name: param_name, value: value]
  end

  defp from_json("is", definition) do
    param_name = Keyword.fetch!(definition, :param_name)
    value = Keyword.fetch!(definition, :value)
    Is[param_name: param_name, value: value]
  end
end

defprotocol Yodado.Definition.Rule do
  def matches?(definition, state)
  def json(definition)
end

defmodule Yodado.Definition do

  alias Yodado.Definition.Rule, as: Rule

  defrecord All, conditions: []
  defimpl Rule, for: All do
    def matches?(All[conditions: conditions], state) do
      conditions |> Enum.all?(&Rule.matches?(&1, state))
    end

    def json(All[conditions: conditions]) do
      [operand: "all", conditions: Enum.map(conditions, &Rule.json/1)]
    end
  end

  defrecord Any, conditions: []
  defimpl Rule, for: Any do
    def matches?(Any[conditions: conditions], state) do
      conditions |> Enum.any?(&Rule.matches?(&1, state))
    end

    def json(Any[conditions: conditions]) do
      [operand: "any", conditions: Enum.map(conditions, &Rule.json/1)]
    end
  end

  defrecord IncludedIn, param_name: nil, value: []
  defimpl Rule, for: IncludedIn do
    def matches?(IncludedIn[param_name: param_name, value: value], state) do
      actual_value = state[param_name]
      Enum.member?(value, actual_value)
    end

    def json(IncludedIn[param_name: param_name, value: value]) do
      [operand: "included_in", param_name: param_name, value: value]
    end
  end

  defrecord Is, param_name: nil, value: "true"
  defimpl Rule, for: Is do
    def matches?(Is[param_name: param_name, value: value], state) do
      state[param_name] == value
    end

    def json(Is[param_name: param_name, value: value]) do
      [operand: "is", param_name: param_name, value: value]
    end
  end

  defimpl Rule, for: Atom do
    def matches?(bool, _state) when is_boolean(bool), do: bool
    def json(bool) when is_boolean(bool), do: [operand: "boolean", value: bool]
  end

  defimpl Rule, for: Function do
    def matches?(f, state), do: f.(state)
  end

  def from_json(definition) do
    operand = Keyword.fetch!(definition, :operand)
    from_json(operand, definition)
  end

  defp from_json("any", definition) do
    conditions = Keyword.fetch!(definition, :conditions)
    Any[conditions: Enum.map(conditions, &from_json/1)]
  end

  defp from_json("all", definition) do
    conditions = Keyword.fetch!(definition, :conditions)
    All[conditions: Enum.map(conditions, &from_json/1)]
  end

  defp from_json("included_in", definition) do
    param_name = Keyword.fetch!(definition, :param_name)
    value = Keyword.fetch!(definition, :value)
    true = is_list(value) # validate we've got a list
    IncludedIn[param_name: param_name, value: value]
  end

  defp from_json("is", definition) do
    param_name = Keyword.fetch!(definition, :param_name)
    value = Keyword.fetch!(definition, :value)
    Is[param_name: param_name, value: value]
  end
end


[gherkin] WIP: Use the local messages in gherkin-elixir

This make the CI build to use the local cucumber-messages version
instead of the released version, but this change cannot remaing when
releasing gherkin-elixir.

defmodule CucumberGherkin.MixProject do
  use Mix.Project

  @vsn "16.0.0"
  @github "https://github.com/cucumber/cucumber/tree/master/gherkin/elixir"
  @name "CucumberGherkin"

  def project do
    [
      app: :cucumber_gherkin,
      version: @vsn,
      name: @name,
      description: description(),
      package: package(),
      elixir: "~> 1.10",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      {:elixir_uuid, "~> 1.2"},
      {:cucumber_messages, "~> 13.2", path: "../../messages/elixir"},
      {:ex_doc, "~> 0.23", only: :dev, runtime: false}
    ]
  end

  defp description() do
    "Elixir Gherkin parser."
  end

  defp package() do
    [
      licenses: ["MIT"],
      source_url: @github,
      links: %{"GitHub" => @github}
    ]
  end
end

defmodule CucumberGherkin.MixProject do
  use Mix.Project

  @vsn "16.0.0"
  @github "https://github.com/cucumber/cucumber/tree/master/gherkin/elixir"
  @name "CucumberGherkin"

  def project do
    [
      app: :cucumber_gherkin,
      version: @vsn,
      name: @name,
      description: description(),
      package: package(),
      elixir: "~> 1.10",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      {:elixir_uuid, "~> 1.2"},
      {:cucumber_messages, "~> 13.2"},
      {:ex_doc, "~> 0.23", only: :dev, runtime: false}
    ]
  end

  defp description() do
    "Elixir Gherkin parser."
  end

  defp package() do
    [
      licenses: ["MIT"],
      source_url: @github,
      links: %{"GitHub" => @github}
    ]
  end
end


latest: stories score > -1

latest: stories score > -1

defmodule News.StoryController do
  use News.Web, :controller

  alias News.Story

  plug :scrub_params, "story" when action in [:create, :update]
  plug News.Plug.Authenticate, [anon: true] when action in [:new, :create]
  plug News.Plug.Authenticate, [flags: ~w(admin)] when action in [:edit, :update, :delete]

  # --- LISTINGS
  def frontpage(conn, _params) do
    stories = Repo.all from story in Story,
      left_join: user in assoc(story, :user),
      left_join: comments in assoc(story, :comments),
      left_join: taggings in assoc(story, :taggings),
      left_join: tag in assoc(taggings, :tag),
      left_join: v in News.Vote, on: v.votable_id == story.id and v.votable_type == "story",
      order_by: fragment("round(cast(log(greatest(abs(?), 1)) * sign(?) + (cast(extract(epoch from ?) as integer) - 1134028003) / 45000.0 as numeric), 7) DESC", story.score, story.score, story.inserted_at),
      where: story.score > 0,
      preload: [user: user, tags: tag, comments: comments, votes: v],
      select: story

    conn
      |> render("frontpage.html", stories: stories)
  end

  def by_domain(conn, %{"domain" => domain}) do
    domain = String.downcase(domain)
    stories = Repo.all from story in Story,
      left_join: user in assoc(story, :user),
      left_join: comments in assoc(story, :comments),
      left_join: taggings in assoc(story, :taggings),
      left_join: tag in assoc(taggings, :tag),
      left_join: v in News.Vote, on: v.votable_id == story.id and v.votable_type == "story",
      order_by: fragment("round(cast(log(greatest(abs(?), 1)) * sign(?) + (cast(extract(epoch from ?) as integer) - 1134028003) / 45000.0 as numeric), 7) DESC", story.score, story.score, story.inserted_at),
      where: story.score > 0 and fragment("?->'domain'", story.attrs) == ^domain,
      preload: [user: user, tags: tag, comments: comments, votes: v],
      select: story

    if Enum.empty?(stories) do
      error_not_found(conn)
    else
      conn
        |> assign(:title, "domain: #{domain}")
        |> render("frontpage.html", stories: stories)
      end
  end

  def latest(conn, _params) do
    stories = Repo.all from story in Story,
      left_join: user in assoc(story, :user),
      left_join: comments in assoc(story, :comments),
      left_join: taggings in assoc(story, :taggings),
      left_join: tag in assoc(taggings, :tag),
      left_join: v in News.Vote, on: v.votable_id == story.id and v.votable_type == "story",
      order_by: [desc: story.inserted_at],
      where: story.score > -1,
      preload: [user: user, tags: tag, comments: comments, votes: v],
      select: story
    conn
      |> assign(:title, "latest")
      |> render("frontpage.html", stories: stories)
  end

  # ---- NEW / CREATE
  def new(conn, %{"type" => type}) when type in ["link","text"] do
    changeset = Story.changeset(%Story{})
    render(conn, "new.html", changeset: changeset, type: type)
  end
  def new(conn, %{"type" => _}), do: error_not_found(conn)
  def new(conn, _), do: redirect(conn, to: submit_path(conn, :new, "link"))

  def create(conn, %{"story" => story_params=%{"type" => type}}) when type in ["text", "link"] do
    new_story = %Story{build(conn.assigns.current_user, :stories) | ip: conn.remote_ip}
    changeset = Story.changeset(new_story, story_params)

    if changeset.valid? do
      story = changeset
        |> Repo.insert!
        |> Story.after_repo_insert(:create, conn)

      if story_params["submit_to"] do
        tags = String.split(story_params["submit_to"], ~r{[\W]})
          |> Enum.reject(fn(t) -> t == "" end)
          |> Enum.uniq
        for tag <- tags, do: News.Tag.submit_story(tag, story)
      end

      conn
      |> redirect(to: Story.url(story))
    else
      render(conn, "new.html", changeset: changeset, type: type)
    end
  end

  # /s/:hash/:slug
  def show(conn, %{"hash" => hash, "slug" => slug}) do
    id = Story.decode_id(hash)
    if id do
      show(conn, %{"id" => id, "slug" => slug})
    else
      error_not_found(conn)
    end
  end
  # /s/:hash
  def show(conn, %{"hash" => hash}), do: show(conn, %{"hash" => hash, "slug" => "no"})
  # /stories/:id/:slug
  def show(conn, _params=%{"id" => id, "slug" => slug}) do
    story = Repo.get! from(story in Story,
                            left_join: user in assoc(story, :user),
                            left_join: comments in assoc(story, :comments),
                            left_join: taggings in assoc(story, :taggings),
                            left_join: tag in assoc(taggings, :tag),
                            left_join: v in News.Vote, on: v.votable_id == story.id and v.votable_type == "story",
                            left_join: cv in News.Vote, on: v.votable_id == comments.id and v.votable_type == "comment",
                            preload: [user: user, tags: tag, votes: v, comments: [:user, :comment]],
                            order_by: fragment("round(cast(log(greatest(abs(?), 1)) * sign(?) + (cast(extract(epoch from ?) as integer) - 1134028003) / 45000.0 as numeric), 7) DESC", comments.score, comments.score, comments.inserted_at),
                          ), id
    comments = Enum.group_by(story.comments, fn(comment) -> comment.comment_id end)
    if Story.slug(story) == slug do
      Logger.debug "Story: #{inspect story}"
      conn
        |> assign(:canonical, Story.url(story))
        |> assign(:meta_keywords, Enum.join(Enum.map(story.tags, fn(tag) -> tag.name end), ", "))
        |> assign(:title, story.title)
        |> render("show.html", story: story, comments: comments)
    else
      redirect(conn, to: Story.url(story))
    end
  end
  # /stories/:id
  def show(conn, %{"id" => id}), do: show(conn, %{"id" => id, "slug" => "no"})

  def edit(conn, %{"id" => id}) do
    story = Repo.get!(Story, id)
    changeset = Story.changeset(story)
    render(conn, "edit.html", story: story, changeset: changeset)
  end

  def update(conn, %{"id" => id, "story" => story_params}) do
    story = Repo.get!(Story, id)
    changeset = Story.changeset(story, story_params)

    if changeset.valid? do
      Repo.update!(changeset)

      conn
      |> put_flash(:info, "Story updated successfully.")
      |> redirect(to: story_path(conn, :index))
    else
      render(conn, "edit.html", story: story, changeset: changeset)
    end
  end

  def delete(conn, %{"id" => id}) do
    story = Repo.get!(Story, id)
    Repo.delete!(story)

    conn
    |> put_flash(:info, "Story deleted successfully.")
    |> redirect(to: story_path(conn, :index))
  end
end

defmodule News.StoryController do
  use News.Web, :controller

  alias News.Story

  plug :scrub_params, "story" when action in [:create, :update]
  plug News.Plug.Authenticate, [anon: true] when action in [:new, :create]
  plug News.Plug.Authenticate, [flags: ~w(admin)] when action in [:edit, :update, :delete]

  # --- LISTINGS
  def frontpage(conn, _params) do
    stories = Repo.all from story in Story,
      left_join: user in assoc(story, :user),
      left_join: comments in assoc(story, :comments),
      left_join: taggings in assoc(story, :taggings),
      left_join: tag in assoc(taggings, :tag),
      left_join: v in News.Vote, on: v.votable_id == story.id and v.votable_type == "story",
      order_by: fragment("round(cast(log(greatest(abs(?), 1)) * sign(?) + (cast(extract(epoch from ?) as integer) - 1134028003) / 45000.0 as numeric), 7) DESC", story.score, story.score, story.inserted_at),
      where: story.score > 0,
      preload: [user: user, tags: tag, comments: comments, votes: v],
      select: story

    conn
      |> render("frontpage.html", stories: stories)
  end

  def by_domain(conn, %{"domain" => domain}) do
    domain = String.downcase(domain)
    stories = Repo.all from story in Story,
      left_join: user in assoc(story, :user),
      left_join: comments in assoc(story, :comments),
      left_join: taggings in assoc(story, :taggings),
      left_join: tag in assoc(taggings, :tag),
      left_join: v in News.Vote, on: v.votable_id == story.id and v.votable_type == "story",
      order_by: fragment("round(cast(log(greatest(abs(?), 1)) * sign(?) + (cast(extract(epoch from ?) as integer) - 1134028003) / 45000.0 as numeric), 7) DESC", story.score, story.score, story.inserted_at),
      where: story.score > 0 and fragment("?->'domain'", story.attrs) == ^domain,
      preload: [user: user, tags: tag, comments: comments, votes: v],
      select: story

    if Enum.empty?(stories) do
      error_not_found(conn)
    else
      conn
        |> assign(:title, "domain: #{domain}")
        |> render("frontpage.html", stories: stories)
      end
  end

  def latest(conn, _params) do
    stories = Repo.all from story in Story,
      left_join: user in assoc(story, :user),
      left_join: comments in assoc(story, :comments),
      left_join: taggings in assoc(story, :taggings),
      left_join: tag in assoc(taggings, :tag),
      left_join: v in News.Vote, on: v.votable_id == story.id and v.votable_type == "story",
      order_by: [desc: story.inserted_at],
      preload: [user: user, tags: tag, comments: comments, votes: v],
      select: story
    conn
      |> assign(:title, "latest")
      |> render("frontpage.html", stories: stories)
  end

  # ---- NEW / CREATE
  def new(conn, %{"type" => type}) when type in ["link","text"] do
    changeset = Story.changeset(%Story{})
    render(conn, "new.html", changeset: changeset, type: type)
  end
  def new(conn, %{"type" => _}), do: error_not_found(conn)
  def new(conn, _), do: redirect(conn, to: submit_path(conn, :new, "link"))

  def create(conn, %{"story" => story_params=%{"type" => type}}) when type in ["text", "link"] do
    new_story = %Story{build(conn.assigns.current_user, :stories) | ip: conn.remote_ip}
    changeset = Story.changeset(new_story, story_params)

    if changeset.valid? do
      story = changeset
        |> Repo.insert!
        |> Story.after_repo_insert(:create, conn)

      if story_params["submit_to"] do
        tags = String.split(story_params["submit_to"], ~r{[\W]})
          |> Enum.reject(fn(t) -> t == "" end)
          |> Enum.uniq
        for tag <- tags, do: News.Tag.submit_story(tag, story)
      end

      conn
      |> redirect(to: Story.url(story))
    else
      render(conn, "new.html", changeset: changeset, type: type)
    end
  end

  # /s/:hash/:slug
  def show(conn, %{"hash" => hash, "slug" => slug}) do
    id = Story.decode_id(hash)
    if id do
      show(conn, %{"id" => id, "slug" => slug})
    else
      error_not_found(conn)
    end
  end
  # /s/:hash
  def show(conn, %{"hash" => hash}), do: show(conn, %{"hash" => hash, "slug" => "no"})
  # /stories/:id/:slug
  def show(conn, _params=%{"id" => id, "slug" => slug}) do
    story = Repo.get! from(story in Story,
                            left_join: user in assoc(story, :user),
                            left_join: comments in assoc(story, :comments),
                            left_join: taggings in assoc(story, :taggings),
                            left_join: tag in assoc(taggings, :tag),
                            left_join: v in News.Vote, on: v.votable_id == story.id and v.votable_type == "story",
                            left_join: cv in News.Vote, on: v.votable_id == comments.id and v.votable_type == "comment",
                            preload: [user: user, tags: tag, votes: v, comments: [:user, :comment]],
                            order_by: fragment("round(cast(log(greatest(abs(?), 1)) * sign(?) + (cast(extract(epoch from ?) as integer) - 1134028003) / 45000.0 as numeric), 7) DESC", comments.score, comments.score, comments.inserted_at),
                          ), id
    comments = Enum.group_by(story.comments, fn(comment) -> comment.comment_id end)
    if Story.slug(story) == slug do
      Logger.debug "Story: #{inspect story}"
      conn
        |> assign(:canonical, Story.url(story))
        |> assign(:meta_keywords, Enum.join(Enum.map(story.tags, fn(tag) -> tag.name end), ", "))
        |> assign(:title, story.title)
        |> render("show.html", story: story, comments: comments)
    else
      redirect(conn, to: Story.url(story))
    end
  end
  # /stories/:id
  def show(conn, %{"id" => id}), do: show(conn, %{"id" => id, "slug" => "no"})

  def edit(conn, %{"id" => id}) do
    story = Repo.get!(Story, id)
    changeset = Story.changeset(story)
    render(conn, "edit.html", story: story, changeset: changeset)
  end

  def update(conn, %{"id" => id, "story" => story_params}) do
    story = Repo.get!(Story, id)
    changeset = Story.changeset(story, story_params)

    if changeset.valid? do
      Repo.update!(changeset)

      conn
      |> put_flash(:info, "Story updated successfully.")
      |> redirect(to: story_path(conn, :index))
    else
      render(conn, "edit.html", story: story, changeset: changeset)
    end
  end

  def delete(conn, %{"id" => id}) do
    story = Repo.get!(Story, id)
    Repo.delete!(story)

    conn
    |> put_flash(:info, "Story deleted successfully.")
    |> redirect(to: story_path(conn, :index))
  end
end