fix test to be date agnostic

fix test to be date agnostic

defmodule Slax.GithubCommands.Test do
  use Slax.ModelCase, async: true
  import Mox
  @subject Slax.Commands.GithubCommands

  describe "format_issues/1" do
    test "when there is an 'in progress' label" do
      assert @subject.format_issues([
               %{
                 "title" => "title",
                 "updated_at" => "2019-04-18T14:08:35Z",
                 "labels" => [%{"name" => "in progress"}]
               }
             ]) =~ "Last Updated at"
    end

    test "when there is not an 'in progress' label" do
      assert @subject.format_issues([
               %{
                 "title" => "title",
                 "updated_at" => "2019-04-18T14:08:35Z",
                 "labels" => [%{"name" => "back log"}]
               }
             ]) =~ ":snail: *Issues In Progress for"
    end
  end

  describe "format_results/1" do
    setup :setup_no_errors

    def setup_no_errors(context) do
      {:ok,
       context
       |> Map.put(:starting_result, %{
         project_name: true,
         github_repo: true,
         slack_channel: true,
         errors: %{},
         success: %{
           project_name: "project_name",
           github_repo: "org_name/project_name",
           slack_channel: "slack_channel"
         }
       })}
    end

    test "when there are no errors for any steps", %{starting_result: starting_result} do
      result = @subject.format_results(starting_result)

      assert result ==
               "Project Name: project_name\nGithub: org_name/project_name\nGithub Teams: \nSlack: slack_channel\nLintron: \nBoard Checker: \nReusable Stories: "
    end

    test "when there are errors for certain steps" do
      starting_result = %{
        project_name: true,
        github_repo: true,
        errors: %{
          slack_channel: "slack_channel error"
        },
        success: %{
          project_name: "project_name",
          github_repo: "org_name/project_name"
        }
      }

      result = @subject.format_results(starting_result)

      assert result ==
               "Project Name: project_name\nGithub: org_name/project_name\nGithub Teams: \nSlack: slack_channel error\nLintron: \nBoard Checker: \nReusable Stories: "
    end
  end

  describe "parse_project_name/2" do
    def setup_starting_result(context) do
      {:ok,
       context
       |> Map.put(:starting_result, %{errors: %{}, success: %{}})}
    end

    setup :setup_starting_result

    test "invalid characters", %{starting_result: starting_result} do
      with result <- @subject.parse_project_name(starting_result, "hi&") do
        assert Map.get(result, :errors) == %{project_name: "Invalid Project Name"}
        assert Map.get(result, :project_name) == nil
      end
    end

    test "too many characters", %{starting_result: starting_result} do
      with result <-
             @subject.parse_project_name(
               starting_result,
               "thisshouldbemorethantwentytwocharacters"
             ) do
        assert Map.get(result, :errors) == %{project_name: "Invalid Project Name"}
        assert Map.get(result, :project_name) == nil
      end
    end

    test "valid_input", %{starting_result: starting_result} do
      with result <- @subject.parse_project_name(starting_result, "valid_repo_name") do
        assert Map.get(result, :errors) == %{}
        assert Map.get(result, :success) == %{project_name: "Project Name Parsed"}
        assert Map.get(result, :project_name) == "valid_repo_name"
      end
    end
  end

  def setup_starting_result_with_name(context) do
    {:ok,
     context
     |> Map.put(:starting_result, %{errors: %{}, success: %{}})
     |> put_in([:starting_result, :success, :project_name], "Project Name Parsed")
     |> put_in([:starting_result, :project_name], "valid_project_name")
     |> put_in([:starting_result, :github_repo], "repo_url")
     |> put_in([:starting_result, :success, :github_repo], "Github Repo Found or Created: <repo_url>")
    }
  end

  def successful_tree_request(_, _, _) do
    {:ok,
     %HTTPoison.Response{
       status_code: 200,
       body: ~s<{
             "tree": [
               {
                 "path": "story_path1.md",
                 "sha": "test_sha",
                 "type": "blob"
               },
               {
                 "path": "story_path3.md",
                 "sha": "test_sha",
                 "type": "blob"
               }

             ]
           }>
     }}
  end

  def successful_blob_request(_, _, _) do
    {:ok,
     %HTTPoison.Response{
       status_code: 200,
       body: ~s<{
           "content": "LS0tCnRpdGxlOiB0ZXN0X3RpdGxlCi0tLQp0ZXN0IGNvbnRlbnQK"
         }>
     }}
  end

  def successful_issue_request(_, _, _, _) do
    {:ok,
     %HTTPoison.Response{
       status_code: 200,
       body: ~s<{
         "content": ["1"]
       }>
     }}
  end

  describe "create_reusable_stories/5" do
    setup [:setup_starting_result_with_name]

    test "when all requests are successful", %{starting_result: starting_result} do
      expect(Slax.HttpMock, :get, 1, &successful_tree_request/3)
      expect(Slax.HttpMock, :get, 2, &successful_blob_request/3)
      expect(Slax.HttpMock, :post, 3, &successful_issue_request/4)

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result[:errors] == %{}
      assert result[:project_name] == "valid_project_name"
      assert result[:github_repo] == "repo_url"
      assert result[:reusable_stories] == true
      assert result[:success] == %{
        project_name: "Project Name Parsed",
        reusable_stories: "Reuseable Stories Created",
        github_repo: "Github Repo Found or Created: <repo_url>"
      }
    end

    def failing_request(_, _, _) do
      {:ok,
       %HTTPoison.Response{
         status_code: 400,
         body: ~s<{
           "message": "you done goofed"
         }>
       }}
    end

    test "when the starting results dont have a github repo" do
      starting_result =
        %{
          project_name: "valid_project_name",
          errors: %{},
          success: %{
            project_name: "Project Name Parsed"
          }
        }

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result == starting_result
    end

    def failing_request(w, x, y, _), do: failing_request(w, x, y)

    test "when fetching the project tree fails", %{starting_result: starting_result} do
      expect(Slax.HttpMock, :get, 1, &failing_request/3)

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result[:errors] == %{reusable_stories: "you done goofed"}
      assert result[:project_name] == "valid_project_name"
      assert result[:github_repo] == "repo_url"
      assert result[:reusable_stories] == nil
      assert result[:success] == %{
        project_name: "Project Name Parsed",
        github_repo: "Github Repo Found or Created: <repo_url>"
      }
    end

    test "when fetching a blob fails", %{starting_result: starting_result} do
      expect(Slax.HttpMock, :get, 1, &successful_tree_request/3)
      expect(Slax.HttpMock, :get, 2, &failing_request/3)

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result[:errors] == %{
        reusable_stories:
        "story_path1.md: you done goofed\nstory_path3.md: you done goofed"
      }
      assert result[:project_name] == "valid_project_name"
      assert result[:github_repo] == "repo_url"
      assert result[:reusable_stories] == nil
      assert result[:success] == %{
        project_name: "Project Name Parsed",
        github_repo: "Github Repo Found or Created: <repo_url>"
      }

    end

    test "when posting issues fails", %{starting_result: starting_result} do
      expect(Slax.HttpMock, :get, 1, &successful_tree_request/3)
      expect(Slax.HttpMock, :get, 2, &successful_blob_request/3)
      expect(Slax.HttpMock, :post, 3, &failing_request/4)

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result[:errors] == %{
        reusable_stories:
        "story_path1.md: you done goofed\nstory_path3.md: you done goofed"
      }
      assert result[:project_name] == "valid_project_name"
      assert result[:github_repo] == "repo_url"
      assert result[:reusable_stories] == nil
      assert result[:success] == %{
        project_name: "Project Name Parsed",
        github_repo: "Github Repo Found or Created: <repo_url>"
      }

    end

    def invalid_blob_request(_, _, _) do
      {:ok,
       %HTTPoison.Response{
         status_code: 200,
         body: ~s<{
           "content": "invalidblob"
         }>
       }}
    end

    test "when decoding a blob fails", %{starting_result: starting_result} do
      expect(Slax.HttpMock, :get, 1, &successful_tree_request/3)
      expect(Slax.HttpMock, :get, 2, &invalid_blob_request/3)
      expect(Slax.HttpMock, :post, 3, &successful_issue_request/4)

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result[:errors] == %{
        reusable_stories:
        "story_path1.md: Unable to parse content\nstory_path3.md: Unable to parse content"
      }
      assert result[:project_name] == "valid_project_name"
      assert result[:github_repo] == "repo_url"
      assert result[:reusable_stories] == nil
      assert result[:success] == %{
        project_name: "Project Name Parsed",
        github_repo: "Github Repo Found or Created: <repo_url>"
      }
    end

    def invalid_frontmatter_request(_, _, _) do
      {:ok,
       %HTTPoison.Response{
         status_code: 200,
         body: ~s<{
           "content": "LS0KaW52YWxpZCBmcm9udG1hdHRlcgpfCm9vcHMK"
         }>
       }}
    end

    test "when parsing issue frontmatter fails", %{starting_result: starting_result} do
      expect(Slax.HttpMock, :get, 1, &successful_tree_request/3)
      expect(Slax.HttpMock, :get, 2, &invalid_frontmatter_request/3)
      expect(Slax.HttpMock, :post, 3, &successful_issue_request/4)

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result[:errors] == %{
        reusable_stories:
        "story_path1.md: invalid_front_matter\nstory_path3.md: invalid_front_matter"
      }
      assert result[:project_name] == "valid_project_name"
      assert result[:github_repo] == "repo_url"
      assert result[:reusable_stories] == nil
      assert result[:success] == %{
        project_name: "Project Name Parsed",
        github_repo: "Github Repo Found or Created: <repo_url>"
      }
    end
  end
end

defmodule Slax.GithubCommands.Test do
  use Slax.ModelCase, async: true
  import Mox
  @subject Slax.Commands.GithubCommands

  describe "format_issues/1" do
    test "when there is an 'in progress' label" do
      assert @subject.format_issues([
               %{
                 "title" => "title",
                 "updated_at" => "2019-04-18T14:08:35Z",
                 "labels" => [%{"name" => "in progress"}]
               }
             ]) =~ "Last Updated at"
    end

    test "when there is not an 'in progress' label" do
      assert @subject.format_issues([
               %{
                 "title" => "title",
                 "updated_at" => "2019-04-18T14:08:35Z",
                 "labels" => [%{"name" => "back log"}]
               }
             ]) =~ ":snail: *Issues In Progress for 5/18* :snail: \n"
    end
  end

  describe "format_results/1" do
    setup :setup_no_errors

    def setup_no_errors(context) do
      {:ok,
       context
       |> Map.put(:starting_result, %{
         project_name: true,
         github_repo: true,
         slack_channel: true,
         errors: %{},
         success: %{
           project_name: "project_name",
           github_repo: "org_name/project_name",
           slack_channel: "slack_channel"
         }
       })}
    end

    test "when there are no errors for any steps", %{starting_result: starting_result} do
      result = @subject.format_results(starting_result)

      assert result ==
               "Project Name: project_name\nGithub: org_name/project_name\nGithub Teams: \nSlack: slack_channel\nLintron: \nBoard Checker: \nReusable Stories: "
    end

    test "when there are errors for certain steps" do
      starting_result = %{
        project_name: true,
        github_repo: true,
        errors: %{
          slack_channel: "slack_channel error"
        },
        success: %{
          project_name: "project_name",
          github_repo: "org_name/project_name"
        }
      }

      result = @subject.format_results(starting_result)

      assert result ==
               "Project Name: project_name\nGithub: org_name/project_name\nGithub Teams: \nSlack: slack_channel error\nLintron: \nBoard Checker: \nReusable Stories: "
    end
  end

  describe "parse_project_name/2" do
    def setup_starting_result(context) do
      {:ok,
       context
       |> Map.put(:starting_result, %{errors: %{}, success: %{}})}
    end

    setup :setup_starting_result

    test "invalid characters", %{starting_result: starting_result} do
      with result <- @subject.parse_project_name(starting_result, "hi&") do
        assert Map.get(result, :errors) == %{project_name: "Invalid Project Name"}
        assert Map.get(result, :project_name) == nil
      end
    end

    test "too many characters", %{starting_result: starting_result} do
      with result <-
             @subject.parse_project_name(
               starting_result,
               "thisshouldbemorethantwentytwocharacters"
             ) do
        assert Map.get(result, :errors) == %{project_name: "Invalid Project Name"}
        assert Map.get(result, :project_name) == nil
      end
    end

    test "valid_input", %{starting_result: starting_result} do
      with result <- @subject.parse_project_name(starting_result, "valid_repo_name") do
        assert Map.get(result, :errors) == %{}
        assert Map.get(result, :success) == %{project_name: "Project Name Parsed"}
        assert Map.get(result, :project_name) == "valid_repo_name"
      end
    end
  end

  def setup_starting_result_with_name(context) do
    {:ok,
     context
     |> Map.put(:starting_result, %{errors: %{}, success: %{}})
     |> put_in([:starting_result, :success, :project_name], "Project Name Parsed")
     |> put_in([:starting_result, :project_name], "valid_project_name")
     |> put_in([:starting_result, :github_repo], "repo_url")
     |> put_in([:starting_result, :success, :github_repo], "Github Repo Found or Created: <repo_url>")
    }
  end

  def successful_tree_request(_, _, _) do
    {:ok,
     %HTTPoison.Response{
       status_code: 200,
       body: ~s<{
             "tree": [
               {
                 "path": "story_path1.md",
                 "sha": "test_sha",
                 "type": "blob"
               },
               {
                 "path": "story_path3.md",
                 "sha": "test_sha",
                 "type": "blob"
               }

             ]
           }>
     }}
  end

  def successful_blob_request(_, _, _) do
    {:ok,
     %HTTPoison.Response{
       status_code: 200,
       body: ~s<{
           "content": "LS0tCnRpdGxlOiB0ZXN0X3RpdGxlCi0tLQp0ZXN0IGNvbnRlbnQK"
         }>
     }}
  end

  def successful_issue_request(_, _, _, _) do
    {:ok,
     %HTTPoison.Response{
       status_code: 200,
       body: ~s<{
         "content": ["1"]
       }>
     }}
  end

  describe "create_reusable_stories/5" do
    setup [:setup_starting_result_with_name]

    test "when all requests are successful", %{starting_result: starting_result} do
      expect(Slax.HttpMock, :get, 1, &successful_tree_request/3)
      expect(Slax.HttpMock, :get, 2, &successful_blob_request/3)
      expect(Slax.HttpMock, :post, 3, &successful_issue_request/4)

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result[:errors] == %{}
      assert result[:project_name] == "valid_project_name"
      assert result[:github_repo] == "repo_url"
      assert result[:reusable_stories] == true
      assert result[:success] == %{
        project_name: "Project Name Parsed",
        reusable_stories: "Reuseable Stories Created",
        github_repo: "Github Repo Found or Created: <repo_url>"
      }
    end

    def failing_request(_, _, _) do
      {:ok,
       %HTTPoison.Response{
         status_code: 400,
         body: ~s<{
           "message": "you done goofed"
         }>
       }}
    end

    test "when the starting results dont have a github repo" do
      starting_result =
        %{
          project_name: "valid_project_name",
          errors: %{},
          success: %{
            project_name: "Project Name Parsed"
          }
        }

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result == starting_result
    end

    def failing_request(w, x, y, _), do: failing_request(w, x, y)

    test "when fetching the project tree fails", %{starting_result: starting_result} do
      expect(Slax.HttpMock, :get, 1, &failing_request/3)

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result[:errors] == %{reusable_stories: "you done goofed"}
      assert result[:project_name] == "valid_project_name"
      assert result[:github_repo] == "repo_url"
      assert result[:reusable_stories] == nil
      assert result[:success] == %{
        project_name: "Project Name Parsed",
        github_repo: "Github Repo Found or Created: <repo_url>"
      }
    end

    test "when fetching a blob fails", %{starting_result: starting_result} do
      expect(Slax.HttpMock, :get, 1, &successful_tree_request/3)
      expect(Slax.HttpMock, :get, 2, &failing_request/3)

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result[:errors] == %{
        reusable_stories:
        "story_path1.md: you done goofed\nstory_path3.md: you done goofed"
      }
      assert result[:project_name] == "valid_project_name"
      assert result[:github_repo] == "repo_url"
      assert result[:reusable_stories] == nil
      assert result[:success] == %{
        project_name: "Project Name Parsed",
        github_repo: "Github Repo Found or Created: <repo_url>"
      }

    end

    test "when posting issues fails", %{starting_result: starting_result} do
      expect(Slax.HttpMock, :get, 1, &successful_tree_request/3)
      expect(Slax.HttpMock, :get, 2, &successful_blob_request/3)
      expect(Slax.HttpMock, :post, 3, &failing_request/4)

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result[:errors] == %{
        reusable_stories:
        "story_path1.md: you done goofed\nstory_path3.md: you done goofed"
      }
      assert result[:project_name] == "valid_project_name"
      assert result[:github_repo] == "repo_url"
      assert result[:reusable_stories] == nil
      assert result[:success] == %{
        project_name: "Project Name Parsed",
        github_repo: "Github Repo Found or Created: <repo_url>"
      }

    end

    def invalid_blob_request(_, _, _) do
      {:ok,
       %HTTPoison.Response{
         status_code: 200,
         body: ~s<{
           "content": "invalidblob"
         }>
       }}
    end

    test "when decoding a blob fails", %{starting_result: starting_result} do
      expect(Slax.HttpMock, :get, 1, &successful_tree_request/3)
      expect(Slax.HttpMock, :get, 2, &invalid_blob_request/3)
      expect(Slax.HttpMock, :post, 3, &successful_issue_request/4)

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result[:errors] == %{
        reusable_stories:
        "story_path1.md: Unable to parse content\nstory_path3.md: Unable to parse content"
      }
      assert result[:project_name] == "valid_project_name"
      assert result[:github_repo] == "repo_url"
      assert result[:reusable_stories] == nil
      assert result[:success] == %{
        project_name: "Project Name Parsed",
        github_repo: "Github Repo Found or Created: <repo_url>"
      }
    end

    def invalid_frontmatter_request(_, _, _) do
      {:ok,
       %HTTPoison.Response{
         status_code: 200,
         body: ~s<{
           "content": "LS0KaW52YWxpZCBmcm9udG1hdHRlcgpfCm9vcHMK"
         }>
       }}
    end

    test "when parsing issue frontmatter fails", %{starting_result: starting_result} do
      expect(Slax.HttpMock, :get, 1, &successful_tree_request/3)
      expect(Slax.HttpMock, :get, 2, &invalid_frontmatter_request/3)
      expect(Slax.HttpMock, :post, 3, &successful_issue_request/4)

      result =
        @subject.create_reusable_stories(
          starting_result,
          "access_token",
          "test_org",
          "story_repo",
          path1: "story_path1",
          path3: "story_path3"
        )

      assert result[:errors] == %{
        reusable_stories:
        "story_path1.md: invalid_front_matter\nstory_path3.md: invalid_front_matter"
      }
      assert result[:project_name] == "valid_project_name"
      assert result[:github_repo] == "repo_url"
      assert result[:reusable_stories] == nil
      assert result[:success] == %{
        project_name: "Project Name Parsed",
        github_repo: "Github Repo Found or Created: <repo_url>"
      }
    end
  end
end


Baby steps in elixir project

Baby steps in elixir project

defmodule Leftpad.Controllers.Main do
  use Sugar.Controller

  def index(conn, []) do
      str = conn.params["str"] || ""
      ch  = conn.params["ch"] || ""
      len = conn.params["len"] || 0

      padded = String.rjust(str, String.to_integer(len), ?a)

      json conn, %{str: padded}
  end
end

defmodule Leftpad.Controllers.Main do
  use Sugar.Controller

  def index(conn, []) do
      str = conn.params[:str] || ""
      ch  = conn.params[:chk] || ""
      len = conn.params[:len] || 0

      json conn, %{str: String.rjust(str, len, ch)}
  end
end


Remove action plug from API Controller

Remove action plug from API Controller

This was cargo coding apparently only applicable to older versions of
Phoenix: newer code does this automatically, so having this in was
causing "duplicate render"-type errors.

defmodule FourOhFourFinderApp.Api.PageController do
  use FourOhFourFinderApp.Web, :controller

  def fetch(conn, params) do
    page_info = FourOhFourFinderApp.PageAnalyzer.analyze(params["url"])
    render conn, page: page_info
  end
end

defmodule FourOhFourFinderApp.Api.PageController do
  use FourOhFourFinderApp.Web, :controller

  plug :action

  def fetch(conn, params) do
    page_info = FourOhFourFinderApp.PageAnalyzer.analyze(params["url"])
    render conn, page: page_info
  end
end


Fixing resource_review storing, and score calculation

defmodule Survey.ResourceController do
  use Survey.Web, :controller

  require Logger
  alias Survey.Resource
  import Prelude
  alias Survey.Resource
  alias Survey.ResourceTag
  alias Survey.Repo

  plug :action

  def add(conn, params) do
    if params["f"] do
      Logger.info("Saving new resource")
      save_to_db(conn, params["f"])
      Survey.Grade.submit_grade(conn, "add_resource", 1.0)
    end

    already = Resource.user_submitted_no(conn.assigns.user.id)
    if already > 0 do
      conn = put_flash(conn, :info,
        "Thank you for submitting #{already} #{resource_word(already)}. Your participation has already been graded. You are welcome to submit more resources, or move on to other parts of the course.")
    end

    sig = conn.assigns.user.sig_id
    tags = ResourceTag.get_tags(sig)

    conn
    |> put_layout("minimal.html")
    |> render "resource.html", tags: tags
  end

  def resource_word(cnt) when cnt > 1, do: "resources"
  def resource_word(cnt), do: "resource"

  #----------------------------------------

  def tag_cloud(conn, params) do
    sig = conn.assigns.user.sig_id
    if params["all"], do: sig = nil
    tagfreq = Resource.tag_freq(sig)
    conn
    |> put_layout("minimal.html")
    |> render "tag_cloud.html", tagfreq: tagfreq
  end

  def list(conn, params) do
    sig = conn.assigns.user.sig_id
    if params["tag"] do
      tag = params["tag"]
      resources = Resource.resource_list(sig, params["tag"])
    else
      tag = nil
      resources = Resource.resource_list(sig)
    end

    conn
    |> put_layout("minimal.html")
    |> render "list.html", resources: resources, tag: tag
  end
  #----------------------------------------

  def review(conn, params) do
    if params["list"] do
      conn = put_session(conn, :review_redirect, true)
      redirect = :list
    else
      redirect = :next
      conn = delete_session(conn, :review_redirect)
    end
    user = conn.assigns.user
    already = Resource.user_reviewed_no(conn.assigns.user.id)
    if already > 0 do
      conn = put_flash(conn, :info,
        "Thank you for reviewing #{already} #{resource_word(already)}. You are welcome to review more resources, or move on to other parts of the course.")
    end

    if params["id"] do
      id = String.to_integer(params["id"])
      Resource.update_seen(user, id)
    else
      id = Resource.get_random(conn.assigns.user)
    end

    if !id do
      html conn, "Sorry, we could not find any new resources for you to review. Try back in a little while."
    else
      sig = conn.assigns.user.sig_id
      tags = ResourceTag.get_tags(sig)
      resource = Resource.get_resource(id)
      if !resource do
        html conn, "Resource with that ID not found"
      else

        rtype = if resource.generic do
          "generic"
        else
          "discipline-specific"
        end

        conn
        |> put_layout("minimal.html")
        |> render "review.html", tags: tags, resource: resource,
          resourcetype: rtype, redirect: redirect
      end
    end
  end

  def review_submit(conn, params) do
    user = conn.assigns.user
    resource = Repo.get(Resource, params["resource_id"])
    form = params["f"]
    |> Enum.map(fn {k, v} -> {k, String.strip(v)} end)
    |> Enum.into(%{})

    # COMMENTS
    comments = resource.comments || []
    if string_param_exists(form["comment"]) do
      newcom = %{nick: user.nick, user_id: user.id, text: form["comment"],
        date: Ecto.DateTime.local}
      comments = [ newcom | comments ]
    end

    # DESCRIPTION
    description = resource.description
    old_desc = resource.old_desc
    if string_param_exists(form["description"]) &&
      String.strip(form["description"]) != String.strip(resource.description) do
      description = String.strip(form["description"])

      cur = %{description: form["description"], user_id: user.id,
        date: Ecto.DateTime.local}

      # either append, or create old_desc with orig as first entry
      if old_desc do
        old_desc = [ cur | old_desc ]
      else
        orig = %{description: resource.description,
          user_id: resource.user_id, date: resource.inserted_at}
        old_desc = [cur, orig]
      end
    end

    # SCORE
    score = resource.score || 0.0
    old_score = resource.old_score || []
    if form["rating"] != "" do
      {newscore, _} = Float.parse(form["rating"])
      new_old_score = %{ "user" => user.id, "score" => newscore,
        "date" => Ecto.DateTime.local }
      old_score = [ new_old_score | old_score ]
      score_sum = old_score
      |> Enum.map(fn x -> x["score"] end)
      |> Enum.sum
      score = score_sum / length(old_score)
    end

    # TAGS
    tags = resource.tags
    if !(resource.generic || !form["tags"] || form["tags"] == "") do
      old_tags = resource.old_tags
      raw_tags = String.split(form["tags"], "|")
      new_tags = set_difference(raw_tags, tags)
      if !Enum.empty?(new_tags) do

        tags = raw_tags
        cur = %{tags: new_tags, user_id: user.id,
          date: Ecto.DateTime.local}

        # either append, or create old_desc with orig as first entry
        if old_tags do
          old_tags = [ cur | old_tags ]
        else
          orig = %{tags: resource.tags,
            user_id: resource.user_id, date: resource.inserted_at}
          old_tags = [cur, orig]
        end
      end
    end

    # INSERT DB
    %{ resource |
      tags: tags, old_tags: old_tags, score: score, old_score: old_score,
      description: description, old_desc: old_desc, comments: comments}
    |> Repo.update!

    redir_url = if get_session(conn, :review_redirect) do
      "/resource/list"
    else
      "/resource/review"
    end

    conn
    |> ParamSession.redirect redir_url
  end
  #----------------------------------------

  def set(x) when is_list(x) do
    Enum.into(x, HashSet.new)
  end

  def different_as_set?(x, y) when is_list(x) and is_list(y) do
    !Enum.empty?(set_difference(x, y))
  end

  def set_difference(x, y) when is_list(x) and is_list(y) do
    Set.difference(set(x), set(y))
    |> Enum.to_list
  end

  def preview(conn, params) do
    tags = ResourceTag.get_tags(2)
    conn
    |> put_layout("minimal.html")
    |> render "resource.html", tags: tags
  end

  def report(conn, _) do
    resources = Resource.get_all_by_sigs
    conn
    |> put_layout("minimal.html")
    |> render "report.html", resources: resources
  end
  #----------------------------------------

  def save_to_db(conn, params) do
    if !Resource.find_url(params["url"], conn.assigns.user.sig_id) do
      resource = params
      |> Map.update("generic", false, fn x -> x == "true" end)
      |> Map.put("user_id", conn.assigns.user.id)
      |> Map.put("sig_id", conn.assigns.user.sig_id)
      |> atomify_map
      |> proc_tags

      struct(Resource, resource)
      |> Repo.insert!

      ResourceTag.update_tags(conn.assigns.user.sig_id, resource.tags)
    else
      Logger.warn("Tried inserting resource with same URL twice")
    end
  end

  defp proc_tags(%{tags: tags} = h), do: %{h | tags: String.split(tags, "|") }
  defp proc_tags(x), do: x

  def check_url(conn, params) do
    sig = conn.assigns.user.sig_id
    url = params["url"] |> String.strip
    if id = Resource.find_url(url, sig) do
      json conn, %{result: "exists", id: id}
    else
      json conn, %{result: "success"}
    end
  end

  defp bad_status(s) do
    s |> Integer.to_string |> String.starts_with?("4")
  end

  defp string_param_exists(s) do
    s && String.strip(s) != ""
  end
end

defmodule Survey.ResourceController do
  use Survey.Web, :controller

  require Logger
  alias Survey.Resource
  import Prelude
  alias Survey.Resource
  alias Survey.ResourceTag
  alias Survey.Repo

  plug :action

  def add(conn, params) do
    if params["f"] do
      Logger.info("Saving new resource")
      save_to_db(conn, params["f"])
      Survey.Grade.submit_grade(conn, "add_resource", 1.0)
    end

    already = Resource.user_submitted_no(conn.assigns.user.id)
    if already > 0 do
      conn = put_flash(conn, :info,
        "Thank you for submitting #{already} #{resource_word(already)}. Your participation has already been graded. You are welcome to submit more resources, or move on to other parts of the course.")
    end

    sig = conn.assigns.user.sig_id
    tags = ResourceTag.get_tags(sig)

    conn
    |> put_layout("minimal.html")
    |> render "resource.html", tags: tags
  end

  def resource_word(cnt) when cnt > 1, do: "resources"
  def resource_word(cnt), do: "resource"

  #----------------------------------------

  def tag_cloud(conn, params) do
    sig = conn.assigns.user.sig_id
    if params["all"], do: sig = nil
    tagfreq = Resource.tag_freq(sig)
    conn
    |> put_layout("minimal.html")
    |> render "tag_cloud.html", tagfreq: tagfreq
  end

  def list(conn, params) do
    sig = conn.assigns.user.sig_id
    if params["tag"] do
      tag = params["tag"]
      resources = Resource.resource_list(sig, params["tag"])
    else
      tag = nil
      resources = Resource.resource_list(sig)
    end

    conn
    |> put_layout("minimal.html")
    |> render "list.html", resources: resources, tag: tag
  end
  #----------------------------------------

  def review(conn, params) do
    if params["list"] do
      conn = put_session(conn, :review_redirect, true)
      redirect = :list
    else
      redirect = :next
      conn = delete_session(conn, :review_redirect)
    end
    user = conn.assigns.user
    already = Resource.user_reviewed_no(conn.assigns.user.id)
    if already > 0 do
      conn = put_flash(conn, :info,
        "Thank you for reviewing #{already} #{resource_word(already)}. You are welcome to review more resources, or move on to other parts of the course.")
    end

    if params["id"] do
      id = String.to_integer(params["id"])
      Resource.update_seen(user, id)
    else
      id = Resource.get_random(conn.assigns.user)
    end

    if !id do
      html conn, "Sorry, we could not find any new resources for you to review. Try back in a little while."
    else
      sig = conn.assigns.user.sig_id
      tags = ResourceTag.get_tags(sig)
      resource = Resource.get_resource(id)
      if !resource do
        html conn, "Resource with that ID not found"
      else

        rtype = if resource.generic do
          "generic"
        else
          "discipline-specific"
        end

        conn
        |> put_layout("minimal.html")
        |> render "review.html", tags: tags, resource: resource,
          resourcetype: rtype, redirect: redirect
      end
    end
  end

  def review_submit(conn, params) do
    user = conn.assigns.user
    resource = Repo.get(Resource, params["resource_id"])
    form = params["f"]

    # COMMENTS
    comments = resource.comments || []
    if form["comment"] do
      newcom = %{nick: user.nick, user_id: user.id, text: form["comment"],
        date: Ecto.DateTime.local}
      comments = [ newcom | comments ]
    end

    # DESCRIPTION
    description = resource.description
    old_desc = resource.old_desc
    if form["description"] &&
      String.strip(form["description"]) != String.strip(resource.description) do
      description = String.strip(form["description"])

      cur = %{description: form["description"], user_id: user.id,
        date: Ecto.DateTime.local}

      # either append, or create old_desc with orig as first entry
      if old_desc do
        old_desc = [ cur | old_desc ]
      else
        orig = %{description: resource.description,
          user_id: resource.user_id, date: resource.inserted_at}
        old_desc = [cur, orig]
      end
    end

    # SCORE
    score = resource.score || 0.0
    old_score = resource.old_score || []
    if form["rating"] != "" do
      {newscore, _} = Float.parse(form["rating"])
      new_old_score = %{ user: user.id, score: newscore,
        date: Ecto.DateTime.local }
      old_score = [ new_old_score | old_score ]
      scorelen = length(old_score) * 1.0
      score = ((score * (scorelen - 1)) + newscore / scorelen)
    end

    # TAGS
    tags = resource.tags
    if !(resource.generic || !form["tags"] || form["tags"] == "") do
      old_tags = resource.old_tags
      raw_tags = String.split(form["tags"], "|")
      new_tags = set_difference(raw_tags, tags)
      if !Enum.empty?(new_tags) do

        tags = raw_tags
        cur = %{tags: new_tags, user_id: user.id,
          date: Ecto.DateTime.local}

        # either append, or create old_desc with orig as first entry
        if old_tags do
          old_tags = [ cur | old_tags ]
        else
          orig = %{tags: resource.tags,
            user_id: resource.user_id, date: resource.inserted_at}
          old_tags = [cur, orig]
        end
      end
    end

    # INSERT DB
    %{ resource |
      tags: tags, old_tags: old_tags, score: score, old_score: old_score,
      description: description, old_desc: old_desc, comments: comments}
    |> Repo.update!

    redir_url = if get_session(conn, :review_redirect) do
      "/resource/list"
    else
      "/resource/review"
    end

    conn
    |> ParamSession.redirect redir_url
  end
  #----------------------------------------

  def set(x) when is_list(x) do
    Enum.into(x, HashSet.new)
  end

  def different_as_set?(x, y) when is_list(x) and is_list(y) do
    !Enum.empty?(set_difference(x, y))
  end

  def set_difference(x, y) when is_list(x) and is_list(y) do
    Set.difference(set(x), set(y))
    |> Enum.to_list
  end

  def preview(conn, params) do
    tags = ResourceTag.get_tags(2)
    conn
    |> put_layout("minimal.html")
    |> render "resource.html", tags: tags
  end

  def report(conn, _) do
    resources = Resource.get_all_by_sigs
    conn
    |> put_layout("minimal.html")
    |> render "report.html", resources: resources
  end
  #----------------------------------------

  def save_to_db(conn, params) do
    if !Resource.find_url(params["url"], conn.assigns.user.sig_id) do
      resource = params
      |> Map.update("generic", false, fn x -> x == "true" end)
      |> Map.put("user_id", conn.assigns.user.id)
      |> Map.put("sig_id", conn.assigns.user.sig_id)
      |> atomify_map
      |> proc_tags

      struct(Resource, resource)
      |> Repo.insert!

      ResourceTag.update_tags(conn.assigns.user.sig_id, resource.tags)
    else
      Logger.warn("Tried inserting resource with same URL twice")
    end
  end

  defp proc_tags(%{tags: tags} = h), do: %{h | tags: String.split(tags, "|") }
  defp proc_tags(x), do: x

  def check_url(conn, params) do
    sig = conn.assigns.user.sig_id
    url = params["url"] |> String.strip
    if id = Resource.find_url(url, sig) do
      json conn, %{result: "exists", id: id}
    else
      json conn, %{result: "success"}
    end
  end

  defp bad_status(s) do
    s |> Integer.to_string |> String.starts_with?("4")
  end
end


build /p/ for plaintext

build /p/ for plaintext

defmodule Hermit.Web do
  use Plug.Router

  plug :match
  plug :dispatch

  get "/" do
    send_resp(conn, 200, "TODO: some kind of listing thing.")
  end

  # Plain text
  get "/p/:pipe_id" do
    Hermit.Plumber.add_pipe_listener(pipe_id, self())

    conn
    |> put_resp_header("content-type", "text/plain")
    |> send_chunked(200)
    |> send_replay(pipe_id, :plain)
    |> listen_loop(:plain)
  end

  get "/v/:pipe_id" do
    send_file(conn, 200, "./web/index.html")
  end

  get "/sse/:pipe_id" do
    # Register our process as a pipe listener
    Hermit.Plumber.add_pipe_listener(pipe_id, self())

    conn
    |> put_resp_header("content-type", "text/event-stream")
    |> send_chunked(200)
    |> send_replay(pipe_id, :sse)
    |> listen_loop(:sse)
  end

  defp send_replay(conn, pipe_id, format) do
    # FIXME: this feels like a really bad separation of concerns
    Agent.get(Hermit.Plumber, fn _state ->
      pipe_id
      |> Hermit.Plumber.get_pipe_file
      |> File.stream!([], 2048)
      |> Enum.map(&(format_chunk(&1, format)))
      |> Enum.into(conn)
    end)
  end

  defp format_chunk(bytes, format) do
    case format do
      :sse ->
        encoded = Base.encode64(bytes)
        "data: #{encoded}\n\n"
      :plain ->
        bytes
    end
  end

  defp write_chunk(conn, msg) do
    {:ok, conn} = chunk(conn, msg)
    conn
  end

  defp listen_loop(conn, format) do
    receive do
      {:pipe_activity, msg} ->
        conn
        |> write_chunk(msg |> format_chunk(format))
        |> listen_loop(format)
    end
  end

  match _ do
    send_resp(conn, 404, "oops")
  end
end

defmodule Hermit.Web do
  use Plug.Router

  plug :match
  plug :dispatch

  get "/" do
    send_resp(conn, 200, "TODO: some kind of listing thing.")
  end

  get "/v/:pipe_id" do
    send_file(conn, 200, "./web/index.html")
  end

  get "/sse/:pipe_id" do
    # Register our process as a pipe listener
    Hermit.Plumber.add_pipe_listener(pipe_id, self())

    conn
    |> put_resp_header("content-type", "text/event-stream")
    |> send_chunked(200)
    |> send_replay(pipe_id)
    |> listen_loop()
  end

  defp send_replay(conn, pipe_id) do
    # FIXME: this feels like a really bad separation of concerns
    Agent.get(Hermit.Plumber, fn _state ->
      pipe_id
      |> Hermit.Plumber.get_pipe_file
      |> File.stream!([], 2048)
      |> Enum.map(&format_chunk/1)
      |> Enum.into(conn)
    end)
  end

  defp format_chunk(bytes) do
    encoded = Base.encode64(bytes)
    "data: #{encoded}\n\n"
  end

  defp write_chunk(conn, msg) do
    {:ok, conn} = chunk(conn, msg)
    conn
  end

  defp listen_loop(conn) do
    receive do
      {:pipe_activity, msg} ->
        conn
        |> write_chunk(msg |> format_chunk())
        |> listen_loop()
    end
  end

  match _ do
    send_resp(conn, 404, "oops")
  end
end


improved performance of Integer.undigits/2

defmodule Integer do
  @moduledoc """
  Functions for working with integers.
  """

  import Bitwise

  @doc """
  Determines if an integer is odd.

  Returns `true` if `n` is an odd number, otherwise `false`.

  Allowed in guard clauses.
  """
  defmacro is_odd(n) do
    quote do: (unquote(n) &&& 1) == 1
  end

  @doc """
  Determines if an integer is even.

  Returns `true` if `n` is an even number, otherwise `false`.

  Allowed in guard clauses.
  """
  defmacro is_even(n) do
    quote do: (unquote(n) &&& 1) == 0
  end

  @doc """
  Returns the ordered digits for the given non-negative integer.

  An optional base value may be provided representing the radix for the returned
  digits.

  ## Examples

      iex> Integer.digits(101)
      [1, 0, 1]

      iex> Integer.digits(58127, 2)
      [1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]

  """
  @spec digits(non_neg_integer, non_neg_integer) :: [non_neg_integer]
  def digits(n, base \\ 10) when is_integer(n)    and n >= 0
                            and  is_integer(base) and base >= 2 do
    do_digits(n, base, [])
  end

  defp do_digits(0, _base, []),  do: [0]
  defp do_digits(0, _base, acc), do: acc
  defp do_digits(n, base, acc)  do
    do_digits div(n,base), base, [rem(n,base) | acc]
  end

  @doc """
  Returns the integer represented by the ordered digits.

  An optional base value may be provided representing the radix for the digits.

   ## Examples

       iex> Integer.undigits([1, 0, 1])
       101

       iex> Integer.undigits([1, 4], 16)
       20
  """
  @spec undigits([integer], integer) :: integer
  def undigits(digits, base \\ 10) when is_integer(base) do
    do_undigits(digits, base, 0)
  end

  defp do_undigits([], _base, acc), do: acc
  defp do_undigits([digit | tail], base, acc) do
    do_undigits(tail, base, acc * base + digit)
  end

  @doc """
  Converts a binary to an integer.

  If successful, returns a tuple of the form `{integer, remainder_of_binary}`.
  Otherwise `:error`.

  ## Examples

      iex> Integer.parse("34")
      {34,""}

      iex> Integer.parse("34.5")
      {34,".5"}

      iex> Integer.parse("three")
      :error

  """
  @spec parse(binary) :: {integer, binary} | :error
  def parse(<< ?-, bin :: binary >>) do
    case do_parse(bin) do
      :error -> :error
      {number, remainder} -> {-number, remainder}
    end
  end

  def parse(<< ?+, bin :: binary >>) do
    do_parse(bin)
  end

  def parse(bin) when is_binary(bin) do
    do_parse(bin)
  end

  defp do_parse(<< char, bin :: binary >>) when char in ?0..?9, do: do_parse(bin, char - ?0)
  defp do_parse(_), do: :error

  defp do_parse(<< char, rest :: binary >>, acc) when char in ?0..?9 do
    do_parse rest, 10 * acc + (char - ?0)
  end

  defp do_parse(bitstring, acc) do
    {acc, bitstring}
  end

  @doc """
  Returns a binary which corresponds to the text representation
  of `some_integer`.

  Inlined by the compiler.

  ## Examples

      iex> Integer.to_string(123)
      "123"

  """
  @spec to_string(integer) :: String.t
  def to_string(some_integer) do
    :erlang.integer_to_binary(some_integer)
  end

  @doc """
  Returns a binary which corresponds to the text representation
  of `some_integer` in base `base`.

  Inlined by the compiler.

  ## Examples

      iex> Integer.to_string(100, 16)
      "64"

  """
  @spec to_string(integer, 2..36) :: String.t
  def to_string(some_integer, base) do
    :erlang.integer_to_binary(some_integer, base)
  end

  @doc """
  Returns a char list which corresponds to the text representation of the given integer.

  Inlined by the compiler.

  ## Examples

      iex> Integer.to_char_list(7)
      '7'

  """
  @spec to_char_list(integer) :: char_list
  def to_char_list(number) do
    :erlang.integer_to_list(number)
  end

  @doc """
  Returns a char list which corresponds to the text representation of the
  given integer in the given base.

  Inlined by the compiler.

  ## Examples

      iex> Integer.to_char_list(1023, 16)
      '3FF'

  """
  @spec to_char_list(integer, 2..36) :: char_list
  def to_char_list(number, base) do
    :erlang.integer_to_list(number, base)
  end
end

defmodule Integer do
  @moduledoc """
  Functions for working with integers.
  """

  import Bitwise

  @doc """
  Determines if an integer is odd.

  Returns `true` if `n` is an odd number, otherwise `false`.

  Allowed in guard clauses.
  """
  defmacro is_odd(n) do
    quote do: (unquote(n) &&& 1) == 1
  end

  @doc """
  Determines if an integer is even.

  Returns `true` if `n` is an even number, otherwise `false`.

  Allowed in guard clauses.
  """
  defmacro is_even(n) do
    quote do: (unquote(n) &&& 1) == 0
  end

  @doc """
  Returns the ordered digits for the given non-negative integer.

  An optional base value may be provided representing the radix for the returned
  digits.

  ## Examples

      iex> Integer.digits(101)
      [1, 0, 1]

      iex> Integer.digits(58127, 2)
      [1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]

  """
  @spec digits(non_neg_integer, non_neg_integer) :: [non_neg_integer]
  def digits(n, base \\ 10) when is_integer(n)    and n >= 0
                            and  is_integer(base) and base >= 2 do
    do_digits(n, base, [])
  end

  defp do_digits(0, _base, []),  do: [0]
  defp do_digits(0, _base, acc), do: acc
  defp do_digits(n, base, acc)  do
    do_digits div(n,base), base, [rem(n,base) | acc]
  end

  @doc """
  Returns the integer represented by the ordered digits.

  An optional base value may be provided representing the radix for the digits.

   ## Examples

       iex> Integer.undigits([1, 0, 1])
       101

       iex> Integer.undigits([1, 4], 16)
       20
  """
  @spec undigits([integer], integer) :: integer
  def undigits(digits, base \\ 10) when is_integer(base) do
    do_undigits(:lists.reverse(digits), base, 1)
  end

  defp do_undigits([], _base, _acc), do: 0
  defp do_undigits([digit | tail], base, acc) do
    digit * acc + do_undigits(tail, base, acc * base)
  end

  @doc """
  Converts a binary to an integer.

  If successful, returns a tuple of the form `{integer, remainder_of_binary}`.
  Otherwise `:error`.

  ## Examples

      iex> Integer.parse("34")
      {34,""}

      iex> Integer.parse("34.5")
      {34,".5"}

      iex> Integer.parse("three")
      :error

  """
  @spec parse(binary) :: {integer, binary} | :error
  def parse(<< ?-, bin :: binary >>) do
    case do_parse(bin) do
      :error -> :error
      {number, remainder} -> {-number, remainder}
    end
  end

  def parse(<< ?+, bin :: binary >>) do
    do_parse(bin)
  end

  def parse(bin) when is_binary(bin) do
    do_parse(bin)
  end

  defp do_parse(<< char, bin :: binary >>) when char in ?0..?9, do: do_parse(bin, char - ?0)
  defp do_parse(_), do: :error

  defp do_parse(<< char, rest :: binary >>, acc) when char in ?0..?9 do
    do_parse rest, 10 * acc + (char - ?0)
  end

  defp do_parse(bitstring, acc) do
    {acc, bitstring}
  end

  @doc """
  Returns a binary which corresponds to the text representation
  of `some_integer`.

  Inlined by the compiler.

  ## Examples

      iex> Integer.to_string(123)
      "123"

  """
  @spec to_string(integer) :: String.t
  def to_string(some_integer) do
    :erlang.integer_to_binary(some_integer)
  end

  @doc """
  Returns a binary which corresponds to the text representation
  of `some_integer` in base `base`.

  Inlined by the compiler.

  ## Examples

      iex> Integer.to_string(100, 16)
      "64"

  """
  @spec to_string(integer, 2..36) :: String.t
  def to_string(some_integer, base) do
    :erlang.integer_to_binary(some_integer, base)
  end

  @doc """
  Returns a char list which corresponds to the text representation of the given integer.

  Inlined by the compiler.

  ## Examples

      iex> Integer.to_char_list(7)
      '7'

  """
  @spec to_char_list(integer) :: char_list
  def to_char_list(number) do
    :erlang.integer_to_list(number)
  end

  @doc """
  Returns a char list which corresponds to the text representation of the
  given integer in the given base.

  Inlined by the compiler.

  ## Examples

      iex> Integer.to_char_list(1023, 16)
      '3FF'

  """
  @spec to_char_list(integer, 2..36) :: char_list
  def to_char_list(number, base) do
    :erlang.integer_to_list(number, base)
  end
end


aggregations

aggregations

defmodule ESx.Model.Response do
  defstruct [
    :took, :timed_out, :shards, :hits, :total, :max_score,
    :aggregations, :suggestions, :__schema__, :__model__, results: [], records: [],
  ]

  @type t :: %__MODULE__{}

  use ESx.Model.Response.Ecto  # TODO: tobe abstraction

  def parse(_model, _schema, {:ok, %{"error" => _} = rsp}), do: rsp
  def parse(model, schema, {:ok, %{} = rsp}) do
    %__MODULE__{
      __model__: model,
      __schema__: schema,
      hits: rsp["hits"]["hits"],
      total: rsp["hits"]["total"],
      max_score: rsp["hits"]["max_score"],
      took: rsp["took"],
      timed_out: rsp["timed_out"],
      shards: rsp["_shards"],
      aggregations: rsp["aggregations"] || rsp["facets"],
    }
  end

  def results(%{__model__: model, __schema__: schema} = search) do
    rsp = ESx.Model.Search.execute search
    parse model, schema, rsp
  end

  def aggregations(_st) do
    # Aggregations.new(response['aggregations'])
  end

  def suggestions(_st) do
    # Suggestions.new(response['suggest'])
  end

  defimpl Enumerable, for: ESx.Model.Response do

    def count(%ESx.Model.Response{total: total}), do: total

    def member?(%ESx.Model.Response{records: records}, value)
    when length(records) > 0 do
      value in records
    end
    def member?(%ESx.Model.Response{hits: hits}, value) do
      value in hits
    end

    def reduce(%ESx.Model.Response{records: records}, acc, fun)
    when length(records) > 0 do
      Enumerable.reduce(records, acc, fun)
    end
    def reduce(%ESx.Model.Response{hits: hits}, acc, fun) do
      Enumerable.reduce(hits, acc, fun)
    end
  end

end

defmodule ESx.Model.Response do
  defstruct [
    :took, :timed_out, :shards, :hits, :total, :max_score,
    :aggregations, :suggestions, :__schema__, :__model__, results: [], records: [],
  ]

  @type t :: %__MODULE__{}

  use ESx.Model.Response.Ecto  # TODO: tobe abstraction

  def parse(_model, _schema, {:ok, %{"error" => _} = rsp}), do: rsp
  def parse(model, schema, {:ok, %{} = rsp}) do
    %__MODULE__{
      __model__: model,
      __schema__: schema,
      hits: rsp["hits"]["hits"],
      total: rsp["hits"]["total"],
      max_score: rsp["hits"]["max_score"],
      took: rsp["took"],
      timed_out: rsp["timed_out"],
      shards: rsp["_shards"],
    }
  end

  def results(%{__model__: model, __schema__: schema} = search) do
    rsp = ESx.Model.Search.execute search
    ESx.Model.Response.parse model, schema, rsp
  end

  def aggregations(_st) do
    # Aggregations.new(response['aggregations'])
  end

  def suggestions(_st) do
    # Suggestions.new(response['suggest'])
  end

  defimpl Enumerable, for: ESx.Model.Response do

    def count(%ESx.Model.Response{total: total}), do: total

    def member?(%ESx.Model.Response{records: records}, value)
    when length(records) > 0 do
      value in records
    end
    def member?(%ESx.Model.Response{hits: hits}, value) do
      value in hits
    end

    def reduce(%ESx.Model.Response{records: records}, acc, fun)
    when length(records) > 0 do
      Enumerable.reduce(records, acc, fun)
    end
    def reduce(%ESx.Model.Response{hits: hits}, acc, fun) do
      Enumerable.reduce(hits, acc, fun)
    end
  end

end


Improve docs for assertions

Improve docs for assertions

defexception ExUnit.AssertionError, message: "assertion failed"

defmodule ExUnit.Assertions do
  @moduledoc """
  This module contains a set of assertions functions that are
  imported by default into your test cases.

  In general, a developer will want to use the general
  `assert` macro in tests. The macro tries to be smart
  and provide good reporting whenever there is a failure.
  For example, `assert some_fun() == 10` will fail (assuming
  `some_fun()` returns 13):

      Expected 10 to be equal to 13

  This module also provides other small convenient functions
  like `assert_match`, `assert_member` and `assert_raise` to
  easily handle other common cases as, respectively, asserting
  if two terms match, asserting if an item belongs to a list or
  if a function raises an exception.
  """

  @doc """
  Asserts the `expected` value is true.

  `assert` in general tries to be smart and provide a good
  reporting whenever there is a failure. For example,
  `assert 10 > 15` is going to fail with a message:

      Expected 10 to be more than 15

  ## Examples

      assert true

  """
  defmacro assert(expected) do
    translate_assertion(expected)
  end

  @doc """
  Asserts the `expected` value is true.
  If it fails, raises the given message.

  ## Examples

      assert false, "it will never be true"

  """
  def assert(expected, message) when is_binary(message) do
    unless expected, do:
      raise ExUnit.AssertionError, message: message
    true
  end

  ## START HELPERS

  defmacrop negation?(op) do
    quote do: (var!(op) == :! or var!(op) == :not)
  end

  defp translate_assertion({ :==, _, [left, right] }) do
    { expected, actual } = guess_expected_and_actual(left, right)
    assert_operator :==, expected, actual, "equal to (==)"
  end

  defp translate_assertion({ :<, _, [left, right] }) do
    assert_operator :<, left, right, "less than"
  end

  defp translate_assertion({ :>, _, [left, right] }) do
    assert_operator :>, left, right, "more than"
  end

  defp translate_assertion({ :<=, _, [left, right] }) do
    assert_operator :<=, left, right, "less than or equal to"
  end

  defp translate_assertion({ :>=, _, [left, right] }) do
    assert_operator :>=, left, right, "more than or equal to"
  end

  defp translate_assertion({ :===, _, [left, right] }) do
    { expected, actual } = guess_expected_and_actual(left, right)
    assert_operator :===, expected, actual, "equal to (===)"
  end

  defp translate_assertion({ :!==, _, [left, right] }) do
    { expected, actual } = guess_expected_and_actual(left, right)
    assert_operator :!==, expected, actual, "not equal to (!==)"
  end

  defp translate_assertion({ :!=, _, [left, right] }) do
    { expected, actual } = guess_expected_and_actual(left, right)
    assert_operator :!=, expected, actual, "not equal to (!=)"
  end

  defp translate_assertion({ :access, _, [container, base] }) do
    quote do
      container = unquote(container)
      base = unquote(base)
      assert(container[base], "Expected #{inspect base} to access #{inspect container}")
    end
  end

  defp translate_assertion({ op, _, [{ :access, _, [container, base] }] }) when negation?(op) do
    quote do
      container = unquote(container)
      base = unquote(base)
      assert(!container[base], "Expected #{inspect base} to not access #{inspect container}")
    end
  end

  defp translate_assertion(expected) do
    quote do
      value = unquote(expected)
      assert value, "Expected #{inspect value} to be true"
    end
  end

  defp guess_expected_and_actual(left, right) do
    case right do
    match: { fun, i, _ } when is_integer(i) and (fun != :<<>> or fun != :{})
      { left, right }
    else:
      { right, left }
    end
  end

  defp assert_operator(operator, expected, actual, text) do
    quote do
      left  = unquote(expected)
      right = unquote(actual)
      assert unquote(operator).(left, right),
        "Expected #{inspect left} to be #{unquote(text)} #{inspect right}"
    end
  end

  ## END HELPERS

  @doc """
  Asserts the `expected` value matches `received`. This relies
  on Elixir's pattern match instead of simply comparing terms.

  ## Examples

      assert_match { 1, _, 3 }, { 1, 2, 3 }

  """
  defmacro assert_match(expected, received) do
    quote do
      try do
        unquote(expected) = unquote(received)
        true
      rescue: x in [MatchError]
        raise ExUnit.AssertionError, message: x.message
      end
    end
  end

  @doc """
  Asserts the value is a member of the given enumerable.
  Used to check if an item belongs to a list.

  ## Examples

      assert_member "foo", ["foo", "bar"]

  """
  def assert_member(base, container, message // nil) do
    message = message || "Expected #{inspect container} to include #{inspect base}"
    assert(Enum.find(container, &1 == base), message)
  end

  @doc """
  Asserts the `exception` is raised during `function` execution with the expected message.

  ## Examples

      assert_raise ArithmeticError, "bad argument in arithmetic expression", fn ->
        1 + "test"
      end
  """
  def assert_raise(exception, expected_message, function) do
    error = assert_raise(exception, function)
    assert expected_message == error.message
  end

  @doc """
  Asserts the `exception` is raised during `function` execution.

  ## Examples

      assert_raise ArithmeticError, fn ->
        1 + "test"
      end

  """
  def assert_raise(exception, function) do
    function.()
    flunk "Expected #{inspect exception} exception but nothing was raised"
  rescue: error in [exception]
    error
  rescue: error
    name = error.__record__(:name)

    if name == ExUnit.AssertionError do
      raise(error)
    else:
      flunk "Expected exception #{inspect exception}, got #{inspect name}"
    end
  end

  @doc """
  Asserts the `enum` collection is empty.

  ## Examples

      assert_empty []
      assert_empty [1, 2]

  """
  def assert_empty(enum, message // nil) do
    message = message || "Expected #{inspect enum} to be empty"
    assert Enum.empty?(enum), message
  end

  @doc """
  Asserts the `value` is nil.

  """
  def assert_nil(value, message // nil) do
    message = message || "Expected #{inspect value} to be nil"
    assert value == nil, message
  end

  @doc """
  Asserts the `expected` and `received` are within `delta`.

  ## Examples

      assert_in_delta 1.1, 1.5, 0.2
      assert_in_delta 10, 15, 4

  """
  def assert_in_delta(expected, received, delta, message // nil) do
    diff = abs(expected - received)
    message = message ||
      "Expected |#{inspect expected} - #{inspect received}| (#{inspect diff}) to be < #{inspect delta}"
    assert diff < delta, message
  end

  @doc """
  Asserts the throw `expected` during `function` execution.

  ## Examples

      assert_throw 1, fn ->
        throw 1
      end

  """
  def assert_throw(expected, function) do
    assert_catch(:throw, expected, function)
  end

  @doc """
  Asserts the exit `expected` during `function` execution.

  ## Examples

      assert_exit 1, fn ->
        exit 1
      end

  """
  def assert_exit(expected, function) do
    assert_catch(:exit, expected, function)
  end

  @doc """
  Asserts the error `expected` during `function` execution.

  ## Examples

      assert_error :function_clause, fn ->
        List.flatten(1)
      end

  """
  def assert_error(expected, function) do
    assert_catch(:error, expected, function)
  end

  defp assert_catch(expected_type, expected_value, function) do
    function.()
    flunk "Expected #{expected_type} #{inspect expected_value}, got nothing"
  catch: ^expected_type, ^expected_value
    expected_value
  catch: ^expected_type, actual_value
    flunk "Expected #{expected_type} #{inspect expected_value}, got #{inspect actual_value}"
  end

  @doc """
  Asserts the `not_expected` value is false.

  ## Examples

      refute false

  """
  def refute(not_expected, message // nil) do
    message = message || "Expected #{inspect not_expected} to be false"
    not assert(!not_expected, message)
  end

  @doc """
  Assets the `expected` value does not match `received`. This uses
  Elixir's pattern matching instead of simply comparing terms.

  ## Examples

      refute_match { 1, _, 3 }, { 1, 2, 3 }

  """
  defmacro refute_match(expected, received) do
    quote do
      try do
        unquote(expected) = unquote(received)
        flunk "Unexpected right side #{inspect unquote(received)} match"
      rescue: x in [MatchError]
        true
      end
    end
  end

  @doc """
  Asserts the `enum` collection is not empty.

  ## Examples

      refute_empty []
      refute_empty [1, 2]

  """
  def refute_empty(enum, message // nil) do
    message = message || "Expected #{inspect enum} to not be empty"
    refute Enum.empty?(enum), message
  end

  @doc """
  Asserts the `value` is not nil.
  """
  def refute_nil(value, message // nil) do
    message = message || "Expected #{inspect value} to not be nil"
    refute value == nil, message
  end

  @doc """
  Asserts the `expected` and `received` are not within `delta`.

  ## Examples

      refute_in_delta 1.1, 1.2, 0.2
      refute_in_delta 10, 11, 2

  """
  def refute_in_delta(expected, received, delta, message // nil) do
    diff = abs(expected - received)
    message = message ||
      "Expected |#{inspect expected} - #{inspect received}| (#{inspect diff}) to not be < #{inspect delta}"
    refute diff < delta, message
  end

  @doc """
  Asserts the value is not a member of the given enumerable.
  Used to check if an item belongs to a list.

  ## Examples

      refute_member "baz", ["foo", "bar"]

  """
  def refute_member(base, container, message // nil) do
    message = message || "Expected #{inspect container} to not include #{inspect base}"
    refute(Enum.find(container, &1 == base), message)
  end

  @doc """
  Fails with a message.

  ## Examples

      flunk "This should raise an error"

  """
  def flunk(message // "Epic Fail!") do
    assert false, message
  end
end

defexception ExUnit.AssertionError, message: "assertion failed"

defmodule ExUnit.Assertions do
  @doc """
  Asserts the `expected` value is true.

  `assert` in general tries to be smart and provide a good
  reporting whenever there is a failure. For example,
  `assert 10 > 15` is going to fail with a message:

      Expected 10 to be more than 15

  ## Examples

      assert true

  """
  defmacro assert(expected) do
    translate_assertion(expected)
  end

  @doc """
  Asserts the `expected` value is true.
  If it fails, raises the given message.

  ## Examples

      assert false, "it will never be true"

  """
  def assert(expected, message) when is_binary(message) do
    unless expected, do:
      raise ExUnit.AssertionError, message: message
    true
  end

  ## START HELPERS

  defmacrop negation?(op) do
    quote do: (var!(op) == :! or var!(op) == :not)
  end

  defp translate_assertion({ :==, _, [left, right] }) do
    { expected, actual } = guess_expected_and_actual(left, right)
    assert_operator :==, expected, actual, "equal to (==)"
  end

  defp translate_assertion({ :<, _, [left, right] }) do
    assert_operator :<, left, right, "less than"
  end

  defp translate_assertion({ :>, _, [left, right] }) do
    assert_operator :>, left, right, "more than"
  end

  defp translate_assertion({ :<=, _, [left, right] }) do
    assert_operator :<=, left, right, "less than or equal to"
  end

  defp translate_assertion({ :>=, _, [left, right] }) do
    assert_operator :>=, left, right, "more than or equal to"
  end

  defp translate_assertion({ :===, _, [left, right] }) do
    { expected, actual } = guess_expected_and_actual(left, right)
    assert_operator :===, expected, actual, "equal to (===)"
  end

  defp translate_assertion({ :!==, _, [left, right] }) do
    { expected, actual } = guess_expected_and_actual(left, right)
    assert_operator :!==, expected, actual, "not equal to (!==)"
  end

  defp translate_assertion({ :!=, _, [left, right] }) do
    { expected, actual } = guess_expected_and_actual(left, right)
    assert_operator :!=, expected, actual, "not equal to (!=)"
  end

  defp translate_assertion({ :access, _, [container, base] }) do
    quote do
      container = unquote(container)
      base = unquote(base)
      assert(container[base], "Expected #{inspect base} to access #{inspect container}")
    end
  end

  defp translate_assertion({ op, _, [{ :access, _, [container, base] }] }) when negation?(op) do
    quote do
      container = unquote(container)
      base = unquote(base)
      assert(!container[base], "Expected #{inspect base} to not access #{inspect container}")
    end
  end

  defp translate_assertion(expected) do
    quote do
      value = unquote(expected)
      assert value, "Expected #{inspect value} to be true"
    end
  end

  defp guess_expected_and_actual(left, right) do
    case right do
    match: { fun, i, _ } when is_integer(i) and (fun != :<<>> or fun != :{})
      { left, right }
    else:
      { right, left }
    end
  end

  defp assert_operator(operator, expected, actual, text) do
    quote do
      left  = unquote(expected)
      right = unquote(actual)
      assert unquote(operator).(left, right),
        "Expected #{inspect left} to be #{unquote(text)} #{inspect right}"
    end
  end

  ## END HELPERS

  @doc """
  Asserts the `expected` value matches `received`. This relies
  on Elixir's pattern match instead of simply comparing terms.

  ## Examples

      assert_match { 1, _, 3 }, { 1, 2, 3 }

  """
  defmacro assert_match(expected, received) do
    quote do
      try do
        unquote(expected) = unquote(received)
        true
      rescue: x in [MatchError]
        raise ExUnit.AssertionError, message: x.message
      end
    end
  end

  @doc """
  Asserts the value is a member of the given enumerable.
  Used to check if an item belongs to a list.

  ## Examples

      assert_member "foo", ["foo", "bar"]

  """
  def assert_member(base, container, message // nil) do
    message = message || "Expected #{inspect container} to include #{inspect base}"
    assert(Enum.find(container, &1 == base), message)
  end

  @doc """
  Asserts the `exception` is raised during `function` execution with the expected message.

  ## Examples

      assert_raise ArithmeticError, "bad argument in arithmetic expression", fn ->
        1 + "test"
      end
  """
  def assert_raise(exception, expected_message, function) do
    error = assert_raise(exception, function)
    assert expected_message == error.message
  end

  @doc """
  Asserts the `exception` is raised during `function` execution.

  ## Examples

      assert_raise ArithmeticError, fn ->
        1 + "test"
      end

  """
  def assert_raise(exception, function) do
    function.()
    flunk "Expected #{inspect exception} exception but nothing was raised"
  rescue: error in [exception]
    error
  rescue: error
    name = error.__record__(:name)

    if name == ExUnit.AssertionError do
      raise(error)
    else:
      flunk "Expected exception #{inspect exception}, got #{inspect name}"
    end
  end

  @doc """
  Asserts the `enum` collection is empty.

  ## Examples

      assert_empty []
      assert_empty [1, 2]

  """
  def assert_empty(enum, message // nil) do
    message = message || "Expected #{inspect enum} to be empty"
    assert Enum.empty?(enum), message
  end

  @doc """
  Asserts the `value` is nil.

  """
  def assert_nil(value, message // nil) do
    message = message || "Expected #{inspect value} to be nil"
    assert value == nil, message
  end

  @doc """
  Asserts the `expected` and `received` are within `delta`.

  ## Examples

      assert_in_delta 1.1, 1.5, 0.2
      assert_in_delta 10, 15, 4

  """
  def assert_in_delta(expected, received, delta, message // nil) do
    diff = abs(expected - received)
    message = message ||
      "Expected |#{inspect expected} - #{inspect received}| (#{inspect diff}) to be < #{inspect delta}"
    assert diff < delta, message
  end

  @doc """
  Asserts the throw `expected` during `function` execution.

  ## Examples

      assert_throw 1, fn ->
        throw 1
      end

  """
  def assert_throw(expected, function) do
    assert_catch(:throw, expected, function)
  end

  @doc """
  Asserts the exit `expected` during `function` execution.

  ## Examples

      assert_exit 1, fn ->
        exit 1
      end

  """
  def assert_exit(expected, function) do
    assert_catch(:exit, expected, function)
  end

  @doc """
  Asserts the error `expected` during `function` execution.

  ## Examples

      assert_error :function_clause, fn ->
        List.flatten(1)
      end

  """
  def assert_error(expected, function) do
    assert_catch(:error, expected, function)
  end

  defp assert_catch(expected_type, expected_value, function) do
    function.()
    flunk "Expected #{expected_type} #{inspect expected_value}, got nothing"
  catch: ^expected_type, ^expected_value
    expected_value
  catch: ^expected_type, actual_value
    flunk "Expected #{expected_type} #{inspect expected_value}, got #{inspect actual_value}"
  end

  @doc """
  Asserts the `not_expected` value is false.

  ## Examples

      refute false

  """
  def refute(not_expected, message // nil) do
    message = message || "Expected #{inspect not_expected} to be false"
    not assert(!not_expected, message)
  end

  @doc """
  Assets the `expected` value does not match `received`. This uses
  Elixir's pattern matching instead of simply comparing terms.

  ## Examples

      refute_match { 1, _, 3 }, { 1, 2, 3 }

  """
  defmacro refute_match(expected, received) do
    quote do
      try do
        unquote(expected) = unquote(received)
        flunk "Unexpected right side #{inspect unquote(received)} match"
      rescue: x in [MatchError]
        true
      end
    end
  end

  @doc """
  Asserts the `enum` collection is not empty.

  ## Examples

      refute_empty []
      refute_empty [1, 2]

  """
  def refute_empty(enum, message // nil) do
    message = message || "Expected #{inspect enum} to not be empty"
    refute Enum.empty?(enum), message
  end

  @doc """
  Asserts the `value` is not nil.
  """
  def refute_nil(value, message // nil) do
    message = message || "Expected #{inspect value} to not be nil"
    refute value == nil, message
  end

  @doc """
  Asserts the `expected` and `received` are not within `delta`.

  ## Examples

      refute_in_delta 1.1, 1.2, 0.2
      refute_in_delta 10, 11, 2

  """
  def refute_in_delta(expected, received, delta, message // nil) do
    diff = abs(expected - received)
    message = message ||
      "Expected |#{inspect expected} - #{inspect received}| (#{inspect diff}) to not be < #{inspect delta}"
    refute diff < delta, message
  end

  @doc """
  Asserts the value is not a member of the given enumerable.
  Used to check if an item belongs to a list.

  ## Examples

      refute_member "baz", ["foo", "bar"]

  """
  def refute_member(base, container, message // nil) do
    message = message || "Expected #{inspect container} to not include #{inspect base}"
    refute(Enum.find(container, &1 == base), message)
  end

  @doc """
  Fails with a message.

  ## Examples

      flunk "This should raise an error"

  """
  def flunk(message // "Epic Fail!") do
    assert false, message
  end
end


Reword function with default arg example

defmodule Functions do
  use Koans

  def greet(name) do
    "Hello, #{name}!"
  end

  koan "Functions map arguments to outputs" do
    assert greet("World") == ___
  end

  def multiply(a, b), do: a * b
  koan "Single line functions are cool, but mind the comma and the colon!" do
    assert 6 == multiply(2, ___)
  end

  def first(foo, bar), do: "#{foo} and #{bar}"
  def first(foo), do: "Only #{foo}"

  koan "Functions with the same name are distinguished by the number of arguments they take" do
    assert first("One", "Two") == ___
    assert first("One") == ___
  end

  def repeat_again(message, times \\ 5) do
    String.duplicate(message, times)
  end

  koan "Functions can have default argument values" do
    assert repeat_again("Hello ") == ___
    assert repeat_again("Hello ", 2) == ___
  end

  def sum_up(thing) when is_list(thing), do: :entire_list
  def sum_up(_thing), do: :single_thing

  koan "Functions can be picky and apply only to certain types" do
    assert sum_up([1 ,2 ,3]) == ___
    assert sum_up(1) == ___
  end

  def bigger(a,b) when a > b, do: "#{a} is bigger than #{b}"
  def bigger(a,b) when a <= b, do: "#{a} is not bigger than #{b}"

  koan "Intricate guards are possible, but be mindful of the reader" do
    assert bigger(10, 5) == ___
    assert bigger(4, 27) == ___
  end

  def the_length(0), do: "It was zero"
  def the_length(number), do: "The length was #{number}"

  koan "For those individual one-offs, you can even guard on the arguments themselves" do
    assert the_length(0) == ___
    assert the_length(5) == ___
  end

  koan "Little anonymous functions are common, and called with a dot" do
    multiply = fn (a,b) -> a * b end
    assert multiply.(2,3) == ___
  end

  koan "You can even go shorter, by using &(..) and positional arguments" do
    multiply = &(&1 * &2)
    assert multiply.(2,3) == ___
  end

  def times_five_and_then(number, fun), do: fun.(number*5)
  def square(number), do: number * number

  koan "You can pass functions around as arguments. Place an '&' before the name and state the arity" do
    assert times_five_and_then(2, &square/1) == ___
  end

  koan "Functions can be combined elegantly with the pipe operator" do
    result = "full-name"
    |> String.split("-")
    |> Enum.map(&(String.capitalize(&1)))
    |> Enum.join(" ")

    assert result == ___
  end
end

defmodule Functions do
  use Koans

  def greet(name) do
    "Hello, #{name}!"
  end

  koan "Functions map arguments to outputs" do
    assert greet("World") == ___
  end

  def multiply(a, b), do: a * b
  koan "Single line functions are cool, but mind the comma and the colon!" do
    assert 6 == multiply(2, ___)
  end

  def first(foo, bar), do: "#{foo} and #{bar}"
  def first(foo), do: "Only #{foo}"

  koan "Functions with the same name are distinguished by the number of arguments they take" do
    assert first("One", "Two") == ___
    assert first("One") == ___
  end

  def repeat_again(message, times \\ 5) do
    String.duplicate(message, times)
  end

  koan "Not all arguments are always needed" do
    assert repeat_again("Hello ") == ___
    assert repeat_again("Hello ", 2) == ___
  end

  def sum_up(thing) when is_list(thing), do: :entire_list
  def sum_up(_thing), do: :single_thing

  koan "Functions can be picky and apply only to certain types" do
    assert sum_up([1 ,2 ,3]) == ___
    assert sum_up(1) == ___
  end

  def bigger(a,b) when a > b, do: "#{a} is bigger than #{b}"
  def bigger(a,b) when a <= b, do: "#{a} is not bigger than #{b}"

  koan "Intricate guards are possible, but be mindful of the reader" do
    assert bigger(10, 5) == ___
    assert bigger(4, 27) == ___
  end

  def the_length(0), do: "It was zero"
  def the_length(number), do: "The length was #{number}"

  koan "For those individual one-offs, you can even guard on the arguments themselves" do
    assert the_length(0) == ___
    assert the_length(5) == ___
  end

  koan "Little anonymous functions are common, and called with a dot" do
    multiply = fn (a,b) -> a * b end
    assert multiply.(2,3) == ___
  end

  koan "You can even go shorter, by using &(..) and positional arguments" do
    multiply = &(&1 * &2)
    assert multiply.(2,3) == ___
  end

  def times_five_and_then(number, fun), do: fun.(number*5)
  def square(number), do: number * number

  koan "You can pass functions around as arguments. Place an '&' before the name and state the arity" do
    assert times_five_and_then(2, &square/1) == ___
  end

  koan "Functions can be combined elegantly with the pipe operator" do
    result = "full-name"
    |> String.split("-")
    |> Enum.map(&(String.capitalize(&1)))
    |> Enum.join(" ")

    assert result == ___
  end
end


adjust error and docs to reflect configurable payload_limit

defmodule Poxa.EventHandler do
  @moduledoc """
  This module contains Cowboy HTTP handler callbacks to request on /apps/:app_id/events

  More info on Cowboy HTTP handler at: http://ninenines.eu/docs/en/cowboy/HEAD/guide/http_handlers

  More info on Pusher events at: http://pusher.com/docs/rest_api
  """
  alias Poxa.Authentication
  alias Poxa.PusherEvent
  alias Poxa.Event
  require Logger

  @doc false
  def init(_transport, _req, _opts), do: {:upgrade, :protocol, :cowboy_rest}

  def allowed_methods(req, state), do: {["POST"], req, state}

  @invalid_event_json JSX.encode!(%{error: "Event must have channel(s), name, and data"})

  @doc """
  If the body is not JSON or not a valid PusherEvent this function will
  guarantee that 400 will be returned
  """
  def malformed_request(req, state) do
    {:ok, body, req} = :cowboy_req.body(req)
    case JSX.decode(body) do
      {:ok, data} ->
        case PusherEvent.build(data) do
          {:ok, event} -> {false, req, %{body: body, event: event}}
          _ ->
            req = :cowboy_req.set_resp_body(@invalid_event_json, req)
            {true, req, state}
        end
      _ ->
        req = :cowboy_req.set_resp_body(@invalid_event_json, req)
        {true, req, state}
    end
  end

  @authentication_error_json JSX.encode!(%{error: "Authentication error"})

  @doc """
  More info http://pusher.com/docs/rest_api#authentication
  """
  def is_authorized(req, %{body: body} = state) do
    {qs_vals, req} = :cowboy_req.qs_vals(req)
    {method, req} = :cowboy_req.method(req)
    {path, req} = :cowboy_req.path(req)
    authorized = Authentication.check(method, path, body, qs_vals)
    req = if authorized do
            req
          else
            :cowboy_req.set_resp_body(@authentication_error_json, req)
          end
    {authorized, req, state}
  end

  @doc """
  The event data can't be greater than payload_limit which defaults to 10KB
  """
  def valid_entity_length(req, %{event: event} = state) do
    {:ok, payload_limit} = Application.fetch_env(:poxa, :payload_limit)
    valid = byte_size(event.data) <= payload_limit
    req = if valid do
            req
          else
            :cowboy_req.set_resp_body(invalid_data_size_json(payload_limit), req)
          end
    {valid, req, state}
  end

  @doc false
  def content_types_accepted(req, state) do
    {[{{"application", "json", :*}, :post}], req, state}
  end

  @doc false
  def content_types_provided(req, state) do
    {[{{"application", "json", []}, :undefined}], req, state}
  end

  @doc false
  def post(req, %{event: event}) do
    PusherEvent.publish(event)
    Event.notify(:api_message, %{channels: event.channels, name: event.name, socket_id: nil})
    req = :cowboy_req.set_resp_body("{}", req)
    {true, req, nil}
  end

  @doc false
  defp invalid_data_size_json(payload_limit) do
    JSX.encode!(%{error: "Data key must be smaller than #{payload_limit}B"})
  end
end

defmodule Poxa.EventHandler do
  @moduledoc """
  This module contains Cowboy HTTP handler callbacks to request on /apps/:app_id/events

  More info on Cowboy HTTP handler at: http://ninenines.eu/docs/en/cowboy/HEAD/guide/http_handlers

  More info on Pusher events at: http://pusher.com/docs/rest_api
  """
  alias Poxa.Authentication
  alias Poxa.PusherEvent
  alias Poxa.Event
  require Logger

  @doc false
  def init(_transport, _req, _opts), do: {:upgrade, :protocol, :cowboy_rest}

  def allowed_methods(req, state), do: {["POST"], req, state}

  @invalid_event_json JSX.encode!(%{error: "Event must have channel(s), name, and data"})

  @doc """
  If the body is not JSON or not a valid PusherEvent this function will
  guarantee that 400 will be returned
  """
  def malformed_request(req, state) do
    {:ok, body, req} = :cowboy_req.body(req)
    case JSX.decode(body) do
      {:ok, data} ->
        case PusherEvent.build(data) do
          {:ok, event} -> {false, req, %{body: body, event: event}}
          _ ->
            req = :cowboy_req.set_resp_body(@invalid_event_json, req)
            {true, req, state}
        end
      _ ->
        req = :cowboy_req.set_resp_body(@invalid_event_json, req)
        {true, req, state}
    end
  end

  @authentication_error_json JSX.encode!(%{error: "Authentication error"})

  @doc """
  More info http://pusher.com/docs/rest_api#authentication
  """
  def is_authorized(req, %{body: body} = state) do
    {qs_vals, req} = :cowboy_req.qs_vals(req)
    {method, req} = :cowboy_req.method(req)
    {path, req} = :cowboy_req.path(req)
    authorized = Authentication.check(method, path, body, qs_vals)
    req = if authorized do
            req
          else
            :cowboy_req.set_resp_body(@authentication_error_json, req)
          end
    {authorized, req, state}
  end

  @invalid_data_size_json JSX.encode!(%{error: "Data key must be smaller than 10KB"})

  @doc """
  The event data can't be greater than 10KB
  """
  def valid_entity_length(req, %{event: event} = state) do
    {:ok, payload_limit} = Application.fetch_env(:poxa, :payload_limit)
    valid = byte_size(event.data) <= payload_limit
    req = if valid do
            req
          else
            :cowboy_req.set_resp_body(@invalid_data_size_json, req)
          end
    {valid, req, state}
  end

  @doc false
  def content_types_accepted(req, state) do
    {[{{"application", "json", :*}, :post}], req, state}
  end

  @doc false
  def content_types_provided(req, state) do
    {[{{"application", "json", []}, :undefined}], req, state}
  end

  @doc false
  def post(req, %{event: event}) do
    PusherEvent.publish(event)
    Event.notify(:api_message, %{channels: event.channels, name: event.name, socket_id: nil})
    req = :cowboy_req.set_resp_body("{}", req)
    {true, req, nil}
  end
end


Fix fib example.

Fix fib example.

% A tail call optimized fibonacci.
module Math
  def fibonacci(n)
    fibonacci(n, 1, 0)
  end

  def fibonacci(0, _, result)
    result
  end

  def fibonacci(n, next, result)
    fibonacci(n - 1, next + result, next)
  end
end

Math.fibonacci(0)   % => 0
Math.fibonacci(1)   % => 1
Math.fibonacci(3)   % => 2
Math.fibonacci(10)  % => 55

% A tail call optimized fibonacci.
module Math
  def fibonacci(n)
    fibonacci(n, 1, 0)
  end

  def fibonacci(0, _, result)
    result
  end

  def fibonacci(n, next, result)
    fibonacci(n - 1, next + result, next)
  end
end

Math.fibonacci(0)   % => 0
Math.fibonacci(1)   % => 1
Math.fibonacci(3)   % => 2
Math.fibonacci(1000000)  % => 55


char list -> string in example

char list -> string in example

#!/usr/bin/elixir -pa _build/dev/lib/elixlsx/ebin/

require Elixlsx

alias Elixlsx.Sheet
alias Elixlsx.Workbook

sheet1 = Sheet.with_name("First")
# Set cell B2 to the string "Hi". :)
         |> Sheet.set_cell("B2", "Hi")
# Optionally, set font properties:
         |> Sheet.set_cell("B3", "Hello World", bold: true, underline: true, color: "#ffaa00")
# Set background color
         |> Sheet.set_cell("B4", "Background color \\o/", bg_color: "#ffff00")
# Number formatting can be applied like this:
         |> Sheet.set_cell("A1", 123.4, num_format: "0.00")
# Two date formats are accepted, erlang's :calendar format and UNIX timestamps.
# the datetime: true parameter automatically applies conversion to Excels internal format.
         |> Sheet.set_cell("A2", {{2015, 11, 30}, {21, 20, 38}}, datetime: true)
         |> Sheet.set_cell("A3", 1448882362, datetime: true)
# datetime: true ouputs date and time, yyyymmdd limits the output to just the date
         |> Sheet.set_cell("A4", 1448882362, yyyymmdd: true)
# make some room in the first column, otherwise the date will only show up as ###
         |> Sheet.set_col_width("A", 18.0)
# Cell borders
         |> Sheet.set_cell("A5", "Double border", border: [bottom: [style: :double, color: "#cc3311"]])
# Formula
         |> Sheet.set_cell("E1", 1.2, num_format: "0.00")
         |> Sheet.set_cell("E2", 2, num_format: "0.00")
         |> Sheet.set_cell("E3", 3.5, num_format: "0.00")
         |> Sheet.set_cell("E4", 4, num_format: "0.00")
         |> Sheet.set_cell("E5", 5, num_format: "0.00")
 # Show `value` if there is no calculated value (example: Mac OS preview):
         |> Sheet.set_cell("E6", {:formula, "SUM(E1:E5)", value: 15.70}, num_format: "0.00", bold: true)
         |> Sheet.set_cell("F1", {:formula, "NOW()"}, num_format: "yyyy-mm-dd hh:MM:ss")
         |> Sheet.set_col_width("F", 18.0)

workbook = %Workbook{sheets: [sheet1]}

# it is also possible to add a custom "created" date to workbook, otherwise,
# the current date is used.

workbook = %Workbook{workbook | datetime: "2015-12-01T13:40:59Z"}

# It is also possible to create a sheet as a list of rows:
sheet2 = %Sheet{name: "Third", rows: [[1,2,3,4,5],
                                       [1,2],
                                       ["increased row height"],
                                       ["hello", "world"]]}
         |> Sheet.set_row_height(3, 40)

workbook = Workbook.append_sheet(workbook, sheet2)

# For the list of rows approach, cells with properties can be encoded by using a
# list with the value at the head and the properties in the tail:
sheet3 = %Sheet{name: "Second", rows:
  [[1,2,3],
   [4,5,6, ["goat", bold: true]],
   [["Bold", bold: true], ["Italic", italic: true], ["Underline", underline: true], ["Strike!", strike: true],
    ["Large", size: 22]],
   # wrap_text makes text wrap, but it does not increase the row height
   # (see row_heights below).
   [["This is a cell with quite a bit of text.", wrap_text: true],
   # make some vertical alignment
    ["Top", align_vertical: :top],
    # also set font name
    ["Middle", align_vertical: :center, font: "Courier New"],
    ["Bottom", align_vertical: :bottom]],
# Unicode should work as well:
   [["Müłti", bold: true, italic: true, underline: true, strike: true]],
# Change horizontal alignment
   [["left", align_horizontal: :left], ["right", align_horizontal: :right],
    ["center", align_horizontal: :center], ["justify", align_horizontal: :justify],
    ["general", align_horizontal: :general], ["fill", align_horizontal: :fill]]
  ],
  row_heights: %{4 => 60}}

# Insert sheet3 as the second sheet:
workbook = Workbook.insert_sheet(workbook, sheet3, 1)

# It is possible to merge blocks of cells. The top-left cells' value will be retained
# in the block, all others will be dropped.
sheet4 = %Sheet{name: "Merged Cells",
                rows: List.duplicate(["A", "B", "C", "D", "E"], 5),
                merge_cells: [{"A1", "A3"}, {"C1", "E1"}, {"C3", "E5"}]}

Workbook.append_sheet(workbook, sheet4)
|> Elixlsx.write_to("empty.xlsx")

#!/usr/bin/elixir -pa _build/dev/lib/elixlsx/ebin/

require Elixlsx

alias Elixlsx.Sheet
alias Elixlsx.Workbook

sheet1 = Sheet.with_name("First")
# Set cell B2 to the string "Hi". :)
         |> Sheet.set_cell("B2", "Hi")
# Optionally, set font properties:
         |> Sheet.set_cell("B3", "Hello World", bold: true, underline: true, color: "#ffaa00")
# Set background color
         |> Sheet.set_cell("B4", "Background color \\o/", bg_color: "#ffff00")
# Number formatting can be applied like this:
         |> Sheet.set_cell("A1", 123.4, num_format: "0.00")
# Two date formats are accepted, erlang's :calendar format and UNIX timestamps.
# the datetime: true parameter automatically applies conversion to Excels internal format.
         |> Sheet.set_cell("A2", {{2015, 11, 30}, {21, 20, 38}}, datetime: true)
         |> Sheet.set_cell("A3", 1448882362, datetime: true)
# datetime: true ouputs date and time, yyyymmdd limits the output to just the date
         |> Sheet.set_cell("A4", 1448882362, yyyymmdd: true)
# make some room in the first column, otherwise the date will only show up as ###
         |> Sheet.set_col_width("A", 18.0)
# Cell borders
         |> Sheet.set_cell("A5", "Double border", border: [bottom: [style: :double, color: "#cc3311"]])
# Formula
         |> Sheet.set_cell("E1", 1.2, num_format: "0.00")
         |> Sheet.set_cell("E2", 2, num_format: "0.00")
         |> Sheet.set_cell("E3", 3.5, num_format: "0.00")
         |> Sheet.set_cell("E4", 4, num_format: "0.00")
         |> Sheet.set_cell("E5", 5, num_format: "0.00")
 # Show `value` if there is no calculated value (example: Mac OS preview):
         |> Sheet.set_cell("E6", {:formula, "SUM(E1:E5)", value: 15.70}, num_format: "0.00", bold: true)
         |> Sheet.set_cell("F1", {:formula, "NOW()"}, num_format: "yyyy-mm-dd hh:MM:ss")
         |> Sheet.set_col_width("F", 18.0)

workbook = %Workbook{sheets: [sheet1]}

# it is also possible to add a custom "created" date to workbook, otherwise,
# the current date is used.

workbook = %Workbook{workbook | datetime: "2015-12-01T13:40:59Z"}

# It is also possible to create a sheet as a list of rows:
sheet2 = %Sheet{name: 'Third', rows: [[1,2,3,4,5],
                                       [1,2],
                                       ["increased row height"],
                                       ["hello", "world"]]}
         |> Sheet.set_row_height(3, 40)

workbook = Workbook.append_sheet(workbook, sheet2)

# For the list of rows approach, cells with properties can be encoded by using a
# list with the value at the head and the properties in the tail:
sheet3 = %Sheet{name: "Second", rows:
  [[1,2,3],
   [4,5,6, ["goat", bold: true]],
   [["Bold", bold: true], ["Italic", italic: true], ["Underline", underline: true], ["Strike!", strike: true],
    ["Large", size: 22]],
   # wrap_text makes text wrap, but it does not increase the row height
   # (see row_heights below).
   [["This is a cell with quite a bit of text.", wrap_text: true],
   # make some vertical alignment
    ["Top", align_vertical: :top],
    # also set font name
    ["Middle", align_vertical: :center, font: "Courier New"],
    ["Bottom", align_vertical: :bottom]],
# Unicode should work as well:
   [["Müłti", bold: true, italic: true, underline: true, strike: true]],
# Change horizontal alignment
   [["left", align_horizontal: :left], ["right", align_horizontal: :right],
    ["center", align_horizontal: :center], ["justify", align_horizontal: :justify],
    ["general", align_horizontal: :general], ["fill", align_horizontal: :fill]]
  ],
  row_heights: %{4 => 60}}

# Insert sheet3 as the second sheet:
workbook = Workbook.insert_sheet(workbook, sheet3, 1)

# It is possible to merge blocks of cells. The top-left cells' value will be retained
# in the block, all others will be dropped.
sheet4 = %Sheet{name: "Merged Cells",
                rows: List.duplicate(["A", "B", "C", "D", "E"], 5),
                merge_cells: [{"A1", "A3"}, {"C1", "E1"}, {"C3", "E5"}]}

Workbook.append_sheet(workbook, sheet4)
|> Elixlsx.write_to("empty.xlsx")


Add docs to assertions

Add docs to assertions

defexception ExUnit::AssertionError, message: "assertion failed"

defmodule ExUnit::Assertions do
  # Asserts the `expected` value matches `received`. Differently
  # from `assert_equal`, `assert_match` uses underscore and
  # therefore allows a developer to match against a specific
  # part of the `received` structure.
  #
  # ## Examples
  #
  #     assert_match { 1, _, 3 }, { 1, 2, 3 }
  #
  defmacro assert_match(expected, received) do
    escaped = ExUnit::Escaper.escape(expected)
    quote do
      value = unquote(received)
      assert match?(unquote(expected), value),
        "Expected #{inspect value} to match #{inspect unquote(escaped)}"
    end
  end

  # FIXME: Make this works with double quoted strings
  # Asserts the `base` value is included in `container`.
  #
  # ## Examples
  #
  #     assert_include 'foo', 'foobar'
  #
  def assert_included(base, container) do
    assert_included(base, container, "Expected #{inspect container} to include #{inspect base}")
  end

  def assert_included(base, container, message) do
    assert(Erlang.string.str(container, base) != 0, message)
  end

  # Asserts the `expected` value is equal to `received`.
  #
  # ## Examples
  #
  #     assert_equal 0, 0
  #
  def assert_equal(expected, received) do
    assert_equal(expected, received, "Expected #{inspect received} to be equal to #{inspect expected}")
  end

  def assert_equal(expected, received, message) do
    assert(expected == received, message)
  end

  # Asserts the `not_expected` value is false.
  #
  # ## Examples
  #
  #     refute false
  #
  def refute(not_expected) do
    refute(not_expected, "Expected #{inspect not_expected} to be false")
  end

  def refute(not_expected, message) do
    not assert(!not_expected, message)
  end

  # Asserts the `expected` value is true.
  #
  # ## Examples
  #
  #     assert true
  #
  def assert(expected) do
    assert(expected, "Expected #{inspect expected} to be true")
  end

  def assert(expected, message) when is_binary(message) do
    unless expected, do:
      raise ExUnit::AssertionError, message: message
    true
  end
end

defexception ExUnit::AssertionError, message: "assertion failed"

defmodule ExUnit::Assertions do
  # Asserts the `expected` value matches `received`. Differently
  # from `assert_equal`, `assert_match` uses underscore and
  # therefore allows a developer to match against a specific
  # part of the `received` structure.
  #
  # ## Examples
  #
  #     assert_match { 1, _, 3 }, { 1, 2, 3 }
  #
  defmacro assert_match(expected, received) do
    escaped = ExUnit::Escaper.escape(expected)
    quote do
      value = unquote(received)
      assert match?(unquote(expected), value),
        "Expected #{inspect value} to match #{inspect unquote(escaped)}"
    end
  end

  # FIXME: Make this works with double quoted strings
  def assert_included(base, container) do
    assert_included(base, container, "Expected #{inspect container} to include #{inspect base}")
  end

  def assert_included(base, container, message) do
    assert(Erlang.string.str(container, base) != 0, message)
  end

  def assert_equal(expected, received) do
    assert_equal(expected, received, "Expected #{inspect received} to be equal to #{inspect expected}")
  end

  def assert_equal(expected, received, message) do
    assert(expected == received, message)
  end

  def refute(not_expected) do
    refute(not_expected, "Expected #{inspect not_expected} to be false")
  end

  def refute(not_expected, message) do
    not assert(!not_expected, message)
  end

  def assert(expected) do
    assert(expected, "Expected #{inspect expected} to be true")
  end

  def assert(expected, message) when is_binary(message) do
    unless expected, do:
      raise ExUnit::AssertionError, message: message
    true
  end
end


Revert

Revert