Skip to content

Commit

Permalink
Merge pull request #42 from dwyl/code-examples
Browse files Browse the repository at this point in the history
More Code Examples from the Programming Phoenix book
  • Loading branch information
iteles authored Mar 6, 2017
2 parents dc763ca + 7d222ef commit 59cb5d2
Show file tree
Hide file tree
Showing 43 changed files with 663 additions and 103 deletions.
8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
language: elixir
elixir:
- 1.3.4
before_install: cd /home/travis/build/dwyl/learn-phoenix-framework/rumbl && pwd
cache:
directories:
- _build
- deps
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,19 @@ _trying_ to make it **go _faster_**<sup>1</sup> ...
***Or***... _get_ the [**_best_ car**](http://www.cnbc.com/2015/08/27/teslas-p85d-is-the-best-car-consumer-reports-has-ever-tested.html) built for speed, safety and environmental friendliness and ***stop wasting time*** on the past!

![model-s-photo](https://cloud.githubusercontent.com/assets/194400/22628333/e8a107ee-ebc9-11e6-9140-6be11cdddd87.jpg "Tesla Model S") <br />
<small>_**Note**: in the case of Phoenix,
you're getting a Tesla Model S P100D for the "price" of a Ford Fiesta!
<small>_**Note**: with Phoenix,
you're getting a [Tesla Model S P100D](https://www.tesla.com/en_GB/blog/new-tesla-model-s-now-quickest-production-car-world) for the "price" of a Ford Fiesta!
A **logical** choice, **right**_?
</small>

Just like there is an _entire industry_ involved in "_tuning_" distinctly
"_average_" cars (_that weren't made for high performance!_)
there's a similar one for "_optimsing_" slow web applications.
there's a similar one for "_optimizing_" slow web applications.
Organizations end up spending _way_ more time and money
(_"engineering" consultants and server resources_!)
trying to make their "_old tech_" scale or serve more (_concurrent users_),
than they would simply making smarter tech choices.
trying to make their "_old tech_" scale or serve more (_concurrent_) users,
than they would simply making smarter tech choices
(_and avoiding ["sunk cost bias"](http://www.lifehack.org/articles/communication/how-the-sunk-cost-fallacy-makes-you-act-stupid.html)_).

<sup>1: car mod fails:
[ridelust.com/30-custom-cars-from-around-the-world](http://www.ridelust.com/30-custom-cars-from-around-the-world)
Expand Down
23 changes: 23 additions & 0 deletions examples/basic-test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# run this test using the following command:
# elixir -e "ExUnit.start()" -r basic-test.exs

defmodule MyTest do
use ExUnit.Case, async: true

setup do
# run some tedious setup code
:ok
end

test "pass" do
assert true
end

test "fail" do
refute false
end
end


# thanks to @shouston3 in
# https://github.com/dwyl/learn-phoenix-framework/issues/34#issuecomment-280930167
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ defmodule Rumbl.VideoController do

plug :scrub_params, "video" when action in [:create, :update]


plug :load_categories when action in [:new, :create, :edit, :update]

defp load_categories(conn, _) do
Expand Down
3 changes: 3 additions & 0 deletions rumbl/config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ config :rumbl, Rumbl.Repo,
database: "rumbl_test",
hostname: "localhost",
pool: Ecto.Adapters.SQL.Sandbox

config :comeonin, :bcrypt_log_rounds, 4
config :comeonin, :pbkdf2_rounds, 1
28 changes: 28 additions & 0 deletions rumbl/lib/rumbl/permalink.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule Rumbl.Permalink do
@behaviour Ecto.Type

def type, do: :id

def cast(binary) when is_binary(binary) do
case Integer.parse(binary) do
{int, _} when int > 0 -> {:ok, int}
_ -> :error
end
end

def cast(integer) when is_integer(integer) do
{:ok, integer}
end

def cast(_) do
:error
end

def dump(integer) when is_integer(integer) do
{:ok, integer}
end

def load(integer) when is_integer(integer) do
{:ok, integer}
end
end
2 changes: 1 addition & 1 deletion rumbl/priv/repo/migrations/20170207074532_create_user.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Rumbl.Repo.Migrations.CreateUser do
add :username, :string, null: false
add :password_hash, :string

timestamps
timestamps()
end

create unique_index(:users, [:username])
Expand Down
1 change: 0 additions & 1 deletion rumbl/priv/repo/migrations/20170212214945_create_video.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@ defmodule Rumbl.Repo.Migrations.CreateVideo do
timestamps()
end
create index(:videos, [:user_id])

end
end
13 changes: 13 additions & 0 deletions rumbl/priv/repo/migrations/20170218093549_create_category.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule Rumbl.Repo.Migrations.CreateCategory do
use Ecto.Migration

def change do
create table(:categories) do
add :name, :string, null: false

timestamps() # function call parenthesis not in book!
end

create unique_index(:categories, [:name])
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule Rumbl.Repo.Migrations.AddCategoryIdToVideo do
use Ecto.Migration

def change do
alter table(:videos) do
add :category_id, references(:categories)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule Rumbl.Repo.Migrations.AddSlugToVideo do
use Ecto.Migration

def change do
alter table(:videos) do
add :slug, :string
end
end
end
8 changes: 8 additions & 0 deletions rumbl/priv/repo/seeds.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@
#
# We recommend using the bang functions (`insert!`, `update!`
# and so on) as they will fail if something goes wrong.

alias Rumbl.Repo
alias Rumbl.Category

for category <- ~w(Action Drama Romance Comedy Sci-fi) do
Repo.get_by(Category, name: category) ||
Repo.insert!(%Category{name: category})
end
83 changes: 83 additions & 0 deletions rumbl/test/controllers/auth_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
defmodule Rumb.AuthTest do
use Rumbl.ConnCase
alias Rumbl.Auth

setup %{conn: conn} do
conn =
conn
|> bypass_through(Rumbl.Router, :browser)
|> get("/")

{:ok, %{conn: conn}}
end

test "authenticate_user halts when no current_user exists", %{conn: conn} do
conn = Auth.authenticate_user(conn, [])
assert conn.halted
end

test "authenticate_user continues when the current_user exists",
%{conn: conn} do

conn =
conn
|> assign(:current_user, %Rumbl.User{})
|> Auth.authenticate_user([])
refute conn.halted
end

test "login puts the user in the session", %{conn: conn} do
login_conn =
conn
|> Auth.login(%Rumbl.User{id: 123})
|> send_resp(:ok, "")

next_conn = get(login_conn, "/")
assert get_session(next_conn, :user_id) == 123
end

test "logout drops the session", %{conn: conn} do
logout_conn =
conn
|> put_session(:user_id, 123)
|> Auth.logout()
|> send_resp(:ok, "")

next_conn = get(logout_conn, "/")
refute get_session(next_conn, :user_id)
end

test "call places user from session into assigns", %{conn: conn} do
user = insert_user()
conn =
conn
|> put_session(:user_id, user.id)
|> Auth.call(Repo)

assert conn.assigns.current_user.id == user.id
end

test "call with no session sets current_user assign to nil", %{conn: conn} do
conn = Auth.call(conn, Repo)
assert conn.assigns.current_user == nil
end

test "login with a valid username and pass", %{conn: conn} do
user = insert_user(username: "me", password: "secret")
{:ok, conn} =
Auth.login_by_username_and_pass(conn, "me", "secret", repo: Repo)

assert conn.assigns.current_user.id == user.id
end

test "login with a not found user", %{conn: conn} do
assert {:error, :not_found, _conn} =
Auth.login_by_username_and_pass(conn, "me", "secret", repo: Repo)
end

test "login with password missmatch", %{conn: conn} do
_ = insert_user(username: "me", password: "secret")
assert {:error, :unauthorized, _conn} =
Auth.login_by_username_and_pass(conn, "me", "wrong", repo: Repo)
end
end
2 changes: 1 addition & 1 deletion rumbl/test/controllers/page_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ defmodule Rumbl.PageControllerTest do

test "GET /", %{conn: conn} do
conn = get conn, "/"
assert html_response(conn, 200) =~ "Welcome to Phoenix!"
assert html_response(conn, 200) =~ "Welcome to Rumbl.io"
end
end
117 changes: 70 additions & 47 deletions rumbl/test/controllers/video_controller_test.exs
Original file line number Diff line number Diff line change
@@ -1,66 +1,89 @@
defmodule Rumbl.VideoControllerTest do
use Rumbl.ConnCase

alias Rumbl.Video
@valid_attrs %{description: "some content", title: "some content", url: "some content"}
@invalid_attrs %{}

test "lists all entries on index", %{conn: conn} do
conn = get conn, video_path(conn, :index)
assert html_response(conn, 200) =~ "Listing videos"
setup %{conn: conn} = config do
if username = config[:login_as] do
user = insert_user(username: username)
conn = assign(conn, :current_user, user)
{:ok, conn: conn, user: user}
else
:ok
end
end

test "renders form for new resources", %{conn: conn} do
conn = get conn, video_path(conn, :new)
assert html_response(conn, 200) =~ "New video"
test "requires user authentication on all actions", %{conn: conn} do
Enum.each([
get(conn, video_path(conn, :new)),
get(conn, video_path(conn, :index)),
get(conn, video_path(conn, :show, "123")),
get(conn, video_path(conn, :edit, "123")),
put(conn, video_path(conn, :update, "123", %{})),
post(conn, video_path(conn, :create, %{})),
delete(conn, video_path(conn, :delete, "123")),
], fn conn ->
assert html_response(conn, 302)
assert conn.halted
end)
end

test "creates resource and redirects when data is valid", %{conn: conn} do
conn = post conn, video_path(conn, :create), video: @valid_attrs
assert redirected_to(conn) == video_path(conn, :index)
assert Repo.get_by(Video, @valid_attrs)
end
# setup do
# user = insert_user(username: "max")
# conn = assign(build_conn, :current_user, user)
# {:ok, conn: conn, user: user}
# end

test "does not create resource and renders errors when data is invalid", %{conn: conn} do
conn = post conn, video_path(conn, :create), video: @invalid_attrs
assert html_response(conn, 200) =~ "New video"
end
@tag login_as: "max"
test "lists all user's video on index", %{conn: conn, user: user} do
user_video = insert_video(user, title: "funny cats")
other_video = insert_video(insert_user(username: "other"),
title: "another video")

test "shows chosen resource", %{conn: conn} do
video = Repo.insert! %Video{}
conn = get conn, video_path(conn, :show, video)
assert html_response(conn, 200) =~ "Show video"
conn = get conn, video_path(conn, :index)
assert html_response(conn, 200) =~ ~r/Listing videos/
assert String.contains?(conn.resp_body, user_video.title)
refute String.contains?(conn.resp_body, other_video.title)
end

test "renders page not found when id is nonexistent", %{conn: conn} do
assert_error_sent 404, fn ->
get conn, video_path(conn, :show, -1)
end
end
alias Rumbl.Video
@valid_attrs %{url: "http://youtu.be", title: "vid", description: "a vid"}
@invalid_attrs %{title: "invalid"}

test "renders form for editing chosen resource", %{conn: conn} do
video = Repo.insert! %Video{}
conn = get conn, video_path(conn, :edit, video)
assert html_response(conn, 200) =~ "Edit video"
end
defp video_count(query), do: Repo.one(from v in query, select: count(v.id))

test "updates chosen resource and redirects when data is valid", %{conn: conn} do
video = Repo.insert! %Video{}
conn = put conn, video_path(conn, :update, video), video: @valid_attrs
assert redirected_to(conn) == video_path(conn, :show, video)
assert Repo.get_by(Video, @valid_attrs)
@tag login_as: "max"
test "creates user video and redirects", %{conn: conn, user: user} do
conn = post conn, video_path(conn, :create), video: @valid_attrs
# assert redirected_to(conn) = video_path(conn, :index) # see: https://github.com/dwyl/learn-phoenix-framework/issues/40
assert Repo.get_by!(Video, @valid_attrs).user_id == user.id
end

test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
video = Repo.insert! %Video{}
conn = put conn, video_path(conn, :update, video), video: @invalid_attrs
assert html_response(conn, 200) =~ "Edit video"
@tag login_as: "max"
test "does not create video and renders errors when invalid", %{conn: conn} do
count_before = video_count(Video)
conn = post conn, video_path(conn, :create), video: @invalid_attrs
assert html_response(conn, 200) =~ "check the errors"
assert video_count(Video) == count_before
end

test "deletes chosen resource", %{conn: conn} do
video = Repo.insert! %Video{}
conn = delete conn, video_path(conn, :delete, video)
assert redirected_to(conn) == video_path(conn, :index)
refute Repo.get(Video, video.id)
@tag login_as: "max"
test "authorizes actions against access by other users",
%{user: owner, conn: conn} do

video = insert_video(owner, @valid_attrs)
non_owner = insert_user(username: "sneaky") # https://youtu.be/_YQpbzQ6gzs?t=2m48s
conn = assign(conn, :current_user, non_owner)

assert_error_sent :not_found, fn ->
get(conn, video_path(conn, :show, video))
end
assert_error_sent :not_found, fn ->
get(conn, video_path(conn, :edit, video))
end
assert_error_sent :not_found, fn ->
put(conn, video_path(conn, :update, video, video: @valid_attrs))
end
assert_error_sent :not_found, fn ->
delete(conn, video_path(conn, :delete, video))
end
end
end
Loading

0 comments on commit 59cb5d2

Please sign in to comment.