|
|
|
@ -11,15 +11,18 @@ mix phx.routes | grep thing_path
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
#+results:
|
|
|
|
|
: thing_path GET /things/:id/into PokaIjoWeb.ThingController :insert
|
|
|
|
|
: thing_path GET /things/:id/move PokaIjoWeb.ThingController :move
|
|
|
|
|
: thing_path GET /things PokaIjoWeb.ThingController :index
|
|
|
|
|
: thing_path GET /things/:id/edit PokaIjoWeb.ThingController :edit
|
|
|
|
|
: thing_path GET /things/new PokaIjoWeb.ThingController :new
|
|
|
|
|
: thing_path GET /things/:id PokaIjoWeb.ThingController :show
|
|
|
|
|
: thing_path POST /things PokaIjoWeb.ThingController :create
|
|
|
|
|
: thing_path PATCH /things/:id PokaIjoWeb.ThingController :update
|
|
|
|
|
: thing_path DELETE /things/:id PokaIjoWeb.ThingController :delete
|
|
|
|
|
#+begin_example
|
|
|
|
|
thing_path GET /things/:id/into PokaIjoWeb.ThingController :insert
|
|
|
|
|
thing_path GET /things/:id/move PokaIjoWeb.ThingController :move
|
|
|
|
|
thing_path POST /things/:id/move PokaIjoWeb.ThingController :do_move
|
|
|
|
|
thing_path GET /things PokaIjoWeb.ThingController :index
|
|
|
|
|
thing_path GET /things/:id/edit PokaIjoWeb.ThingController :edit
|
|
|
|
|
thing_path GET /things/new PokaIjoWeb.ThingController :new
|
|
|
|
|
thing_path GET /things/:id PokaIjoWeb.ThingController :show
|
|
|
|
|
thing_path POST /things PokaIjoWeb.ThingController :create
|
|
|
|
|
thing_path PATCH /things/:id PokaIjoWeb.ThingController :update
|
|
|
|
|
thing_path DELETE /things/:id PokaIjoWeb.ThingController :delete
|
|
|
|
|
#+end_example
|
|
|
|
|
|
|
|
|
|
** =:index= lists all Things
|
|
|
|
|
|
|
|
|
@ -187,7 +190,7 @@ end
|
|
|
|
|
|
|
|
|
|
def insert(conn, %{"id" => id}) do
|
|
|
|
|
container = Thing.get_by(id: id)
|
|
|
|
|
changeset = Thing.new(%{"container_id": container.id})
|
|
|
|
|
changeset = Thing.new(%{"container_id" => container.id})
|
|
|
|
|
|
|
|
|
|
conn
|
|
|
|
|
|> put_view(PokaIjoWeb.ThingView)
|
|
|
|
@ -454,7 +457,7 @@ end
|
|
|
|
|
*** Controller Tests
|
|
|
|
|
|
|
|
|
|
#+begin_src elixir :tangle test/poka_ijo_web/controllers/thing_controller_test.exs
|
|
|
|
|
defmodule WebControllersThingShowTest do
|
|
|
|
|
defmodule WebControllersThingUpdateTest do
|
|
|
|
|
use PokaIjoWeb.ConnCase
|
|
|
|
|
|
|
|
|
|
test "assigns are assigned", %{conn: conn} do
|
|
|
|
@ -477,8 +480,184 @@ I'm not sure how to do this yet. I am thinking that this will be the arbitrary p
|
|
|
|
|
The =move= action is maybe misnamed -- it's not doing the movement just rendering a form where the user will be able to.
|
|
|
|
|
|
|
|
|
|
#+begin_src elixir :noweb-ref controller-move
|
|
|
|
|
def move(conn, _) do
|
|
|
|
|
conn |> redirect(to: Routes.thing_path(conn, :index))
|
|
|
|
|
def move(conn, %{"id" => id, "filter_str" => filter_str}) do
|
|
|
|
|
_move(conn, id, filter_str)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def move(conn, %{"id" => id}) do
|
|
|
|
|
_move(conn, id)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp _move(conn, id, filter_str \\ nil) do
|
|
|
|
|
{int_id, _} = Integer.parse(id)
|
|
|
|
|
things =
|
|
|
|
|
cond do
|
|
|
|
|
is_binary(filter_str) -> Thing.search_name("%#{filter_str}%")
|
|
|
|
|
true -> Thing.all()
|
|
|
|
|
end |> Enum.filter(&(&1.id != int_id))
|
|
|
|
|
|
|
|
|
|
thing_to_move = Thing.get_by(id: id)
|
|
|
|
|
|
|
|
|
|
conn
|
|
|
|
|
|> put_view(PokaIjoWeb.ThingView)
|
|
|
|
|
|> render(:move,
|
|
|
|
|
things: things,
|
|
|
|
|
subject: thing_to_move,
|
|
|
|
|
filter: filter_str,
|
|
|
|
|
page_title: "Poka Ijo: Moving #{thing_to_move.name}"
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def do_move(conn, %{"id" => id, "new_parent" => new_parent}) do
|
|
|
|
|
t = Thing.get_by(id: id)
|
|
|
|
|
parent = Thing.get_by(id: new_parent)
|
|
|
|
|
changeset = Thing.changeset(t, %{container_id: parent.id})
|
|
|
|
|
|
|
|
|
|
case Thing.update(changeset) do
|
|
|
|
|
{:error, changeset} ->
|
|
|
|
|
Logger.info("Changeset save failed: #{inspect(changeset.errors)}")
|
|
|
|
|
|
|
|
|
|
conn
|
|
|
|
|
|> put_flash(:alert, "Changeset save failed: #{inspect(changeset.errors)}")
|
|
|
|
|
|> put_view(PokaIjoWeb.ThingView)
|
|
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
|
conn
|
|
|
|
|
|> put_flash(:success, "#{t.name} moved!")
|
|
|
|
|
end
|
|
|
|
|
|> redirect(to: Routes.thing_path(conn, :show, t))
|
|
|
|
|
end
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
*** Templates
|
|
|
|
|
|
|
|
|
|
This relies on [[file:../javascript.org][§JavaScript]] to work; =Phoenix.HTML.Link.button= requires javascript for the =move here= actions to work. Eventually I'll move that to be a Phoenix LiveView module, which requires javascript in different ways. I don't really care about that here; this is probably going to be running on my phone, in Firefox. Now, for the "regular user" frontend, it will be key that the thing can behave without JavaScript but this code is fine, I think.
|
|
|
|
|
|
|
|
|
|
#+begin_src web :tangle lib/poka_ijo_web/templates/thing/move.html.eex :mkdirp yes
|
|
|
|
|
<div class="grid-x grid-padding-x">
|
|
|
|
|
<div class="small-12 large-8 cell">
|
|
|
|
|
<h2>Move <%= @subject.name %> to:</h2>
|
|
|
|
|
|
|
|
|
|
<%= form_for @conn, Routes.thing_path(@conn, :move, @subject), [method: "get"], fn f -> %>
|
|
|
|
|
<div class="input-group">
|
|
|
|
|
<span class="input-group-label">Name Filter:</span>
|
|
|
|
|
<%= text_input f, :filter_str, value: assigns[:filter], class: "input-group-field" %>
|
|
|
|
|
<div class="input-field-button">
|
|
|
|
|
<%= submit "filter", class: "button secondary" %>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<% end %>
|
|
|
|
|
|
|
|
|
|
<%= for thing <- @things do %>
|
|
|
|
|
<div class="callout grid-x">
|
|
|
|
|
<div class="small-8 medium-10 cell">
|
|
|
|
|
<%= thing.name %>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="small-4 medium-2 cell">
|
|
|
|
|
<%= button "move here", method: :post,
|
|
|
|
|
to: Routes.thing_path(@conn, :do_move, @subject, new_parent: thing.id),
|
|
|
|
|
class: "button primary expanded" %>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<% end %>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="small-12 large-4 cell">
|
|
|
|
|
<iframe src="https://www.youtube.com/embed/VFZNvj-HfBU" frameborder="0" allow="picture-in-picture" allowfullscreen></iframe>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
*** Controller Tests
|
|
|
|
|
|
|
|
|
|
Tests for the =:move= action are pretty simple, we're mostly just setting up the string filtering. The list of places to move to must of course not include the object itself. This also does *nothing* to prevent cycles in the object graph! Probably want to care about that at some point.
|
|
|
|
|
|
|
|
|
|
I have a helper which I embed that'll create the objects:
|
|
|
|
|
|
|
|
|
|
#+begin_src elixir :noweb-ref move-test-harnesses
|
|
|
|
|
defp insert do
|
|
|
|
|
{:ok, container} =
|
|
|
|
|
%{name: "container", description: "hi"}
|
|
|
|
|
|> PokaIjo.Thing.new()
|
|
|
|
|
|> PokaIjo.Repo.insert()
|
|
|
|
|
|
|
|
|
|
{:ok, cur_container} =
|
|
|
|
|
%{name: "current container", description: "hi"}
|
|
|
|
|
|> PokaIjo.Thing.new()
|
|
|
|
|
|> PokaIjo.Repo.insert()
|
|
|
|
|
|
|
|
|
|
{:ok, t} =
|
|
|
|
|
%{name: "test", description: "hi", container_id: cur_container.id}
|
|
|
|
|
|> PokaIjo.Thing.new()
|
|
|
|
|
|> PokaIjo.Repo.insert()
|
|
|
|
|
|
|
|
|
|
{:ok, candidate_1} =
|
|
|
|
|
%{name: "candidate 1", description: "hi"}
|
|
|
|
|
|> PokaIjo.Thing.new()
|
|
|
|
|
|> PokaIjo.Repo.insert()
|
|
|
|
|
|
|
|
|
|
{:ok, candidate_2} =
|
|
|
|
|
%{name: "candidate 2", description: "hi"}
|
|
|
|
|
|> PokaIjo.Thing.new()
|
|
|
|
|
|> PokaIjo.Repo.insert()
|
|
|
|
|
|
|
|
|
|
%{subject: t, container: container, all: [t, container, candidate_1, candidate_2, cur_container]}
|
|
|
|
|
end
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
#+begin_src elixir :tangle test/poka_ijo_web/controllers/thing_controller_test.exs :noweb yes
|
|
|
|
|
defmodule WebControllersThingMoveTest do
|
|
|
|
|
use PokaIjoWeb.ConnCase
|
|
|
|
|
alias WebControllersThingMoveTest
|
|
|
|
|
|
|
|
|
|
<<move-test-harnesses>>
|
|
|
|
|
|
|
|
|
|
test ":move @things doesn't include subject", %{conn: conn} do
|
|
|
|
|
%{subject: t, all: all} = insert()
|
|
|
|
|
conn = get(conn, "/things/#{t.id}/move")
|
|
|
|
|
|
|
|
|
|
assert conn.assigns[:things] != nil
|
|
|
|
|
assert Enum.count(conn.assigns[:things]) == Enum.count(all) - 1
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test ":move filter_str works", %{conn: conn} do
|
|
|
|
|
%{subject: t} = insert()
|
|
|
|
|
conn = get(conn, "/things/#{t.id}/move?filter_str=ca")
|
|
|
|
|
|
|
|
|
|
assert conn.assigns[:things] != nil
|
|
|
|
|
assert Enum.count(conn.assigns[:things]) == 2
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test ":move assigns are assigned (no filter)", %{conn: conn} do
|
|
|
|
|
%{subject: t} = insert()
|
|
|
|
|
conn = get(conn, "/things/#{t.id}/move")
|
|
|
|
|
|
|
|
|
|
assert conn.assigns != nil
|
|
|
|
|
assert conn.assigns[:subject] != nil
|
|
|
|
|
assert conn.assigns[:subject].id == t.id
|
|
|
|
|
assert conn.assigns[:page_title] =~ "Moving"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
the =:do_move= behavior is also pretty easy to test.
|
|
|
|
|
|
|
|
|
|
#+begin_src elixir :tangle test/poka_ijo_web/controllers/thing_controller_test.exs :noweb yes
|
|
|
|
|
defmodule WebControllersThingDoMoveTest do
|
|
|
|
|
use PokaIjoWeb.ConnCase
|
|
|
|
|
alias PokaIjo.Thing
|
|
|
|
|
alias WebControllersThingMoveTest
|
|
|
|
|
|
|
|
|
|
<<move-test-harnesses>>
|
|
|
|
|
|
|
|
|
|
test ":do_move happy path works", %{conn: conn} do
|
|
|
|
|
%{subject: t, container: container} = insert()
|
|
|
|
|
conn = post(conn, "/things/#{t.id}/move?new_parent=#{container.id}")
|
|
|
|
|
|
|
|
|
|
assert conn.status == 302
|
|
|
|
|
assert Thing.get_by(id: t.id).container_id == container.id
|
|
|
|
|
assert get_flash(conn, :success) =~ "moved!"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|