Ruby on Rails Michael Hartl Chapter 13 Cant Upload Picture
Rewriting a Cherry-red/Track app in Elixir/Phoenix (well-nigh) Literally
I have been looking for a somewhat challenging app apart from the unproblematic demos to write in Elixir using Phoenix framework for quite sometime. Having Reddish/Rails familiarity, I picked the Red on Track Tutorial — Larn Spider web Evolution with Rails (https://www.railstutorial.org) by Michael Hartl (in fact, I learned Rails reading this tutorial!) and decided to catechumen the twitter like Sample App presented pretty much throughout the tutorial.
I called it Minitwitter (probably not a skilful selection of proper name but such difficult is naming in Computer Science!). I think it'south a fairly complex app with many features typically not covered in many books. Then, I took the challenge to convert this app to Elixir/Phoenix and share my experience with Elixir/Phoenix community.
Complete Lawmaking: Complete code for the Elixir/Phoenix version of the app is available on my github hither — https://github.com/imeraj/Phoenix_Playground/tree/master/1.4/minitwitter
In instance, anyone is interested in the Reddish/Rail version, I have complete code with some boosted features (like presence) here too — https://github.com/imeraj/miniTwitter
Prerequisites: I assume that yous are familiar with both Ruby/Rails and Elixir/Phoenix. Knowing Ruby/Rails is not much of a requirement to understand the code except that you should be able to follow the tutorial to understand the features of the app and how information technology's implemented to be able to follow the code. I will cover some highlights of the app here merely due to the complication and scope of the app it's not possible to embrace complete implementation here; that will probably require writing some other tutorial/volume! I will refer to Michael'southward tutorial to follow the code.
Elixir/Phoenix versions: I have used Elixir ane.vii.4 with Erlang/OTP 21 and Phoenix 1.four.
What's covered equally part of this rewrite: Pretty much all the features work as is from the tutorial. Non being a frontend developer, I had to sacrifice some artful beauty and some fine-tuning here and there in frontend.
What's not covered as part of this rewrite: By and large tests. I started writing the tests but I became impatient writing all the tests. So I skipped writing the tests. I remember Programming Phoenix 1.iv (https://pragprog.com/book/phoenix14/programming-phoenix-ane-4) covers all details to write tests in Phoenix and if you follow this book you should be able to add the tests required for this app.
I volition provide the tools/libraries used with some code excerpts day past solar day basis. I have provided good description in git commit messages so that those forth with this write-up and Michael's tutorial can help yous empathise the Elixir/Phoenix version.
Day one (January 10, 2019)
What's covered: Affiliate 3 & 4 of the tutorial
Features covered: Mostly app setup and homepage, contact page setup
Libraries/Dependencies used: North/A
I was getting a strange error related to MySQL and mariex (https://github.com/xerions/mariaex) when specifying password in config file. I plant out that it'southward been stock-still in the latest lawmaking. So I had to utilise the latest git dependency and utilize override truthful to go far work with mysql —
From mix.exs —
{:mariaex, git: "https://github.com/xerions/mariaex.git", override: truthful}
Commits for twenty-four hour period ane:
Day 2 (Jan eleven, 2019)
What'southward covered: Chapter 5
Features covered: Mostly fixing the app layout
Libraries/Dependencies used: Northward/A
Commits for mean solar day ii:
Clarification: Stock-still the app layout. Added some missing files from day 1's commit.
Twenty-four hour period 3 (January 12, 2019)
What's covered: Chapter 6
Features covered: Modeling users
Libraries/Dependencies used: N/A
Commits for day 3:
Description: Added Accounts context and User schema with migration file.
User schema at this stage looked similar below -
defmodule Minitwitter.Accounts.User do
use Ecto.Schema
import Ecto.Changeset schema "users" do
field :proper noun, :string
field :email, :string
timestamps()
end
@email_regex ~r/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
@doc false
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :e-mail])
|> validate_required([:proper noun, :email])
|> validate_length(:name, min: 3, max: 50)
|> validate_length(:email, max: 255)
|> validate_format(:electronic mail, @email_regex)
|> downcase_email()
|> unique_constraint(:email)
cease
defp downcase_email(changeset) practise
case fetch_change(changeset, :electronic mail) do
{:ok, email} -> put_change(changeset, :email, String.downcase(electronic mail))
:error -> changeset
end
end
end
@email_regex was used to assistance validate email and downcase_email to catechumen provided electronic mail accost to lowercase before persisting users.
Respective migration file looks as below —
defmodule Minitwitter.Repo.Migrations.CreateUsers do
employ Ecto.Migration def alter exercise
create tabular array(:users) do
add :name, :cord, zero: imitation
add :email, :cord, null: false
timestamps()
finish
create unique_index(:users, [:electronic mail])
terminate
finish
Day 4 (Jan thirteen, 2019)
What'southward covered: Affiliate 7, 8, and 9
Features covered: Mostly covered sign-up users, basic login and avant-garde login with remember user on computer
Libraries/Dependencies used: Had to add additional dependencies in mix.exs to support sign-up and login.
From mix.exs —
{:comeonin, "~> 4.one"},
{:bcrypt_elixir, "~> i.0"},
{:pbkdf2_elixir, "~> 0.12"}
Commits for mean solar day iv:
User schema at his phase looked like —
defmodule Minitwitter.Accounts.User practice
employ Ecto.Schema
import Ecto.Changeset schema "users" do
field :proper noun, :string
field :email, :string
field :password, :cord, virtual: truthful
field :password_confirmation, :cord, virtual: truthful
field :password_hash, :string
field :remember_hash, :string
timestamps()
end
@email_regex ~r/\A[\west+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
@doc false
def changeset(user, attrs) practise
user
|> cast(attrs, [:name, :email, :password, :password_confirmation])
|> validate_required([:name, :email, :password, :password_confirmation])
|> validate_length(:proper name, min: 3, max: fifty)
|> validate_length(:electronic mail, max: 255)
|> validate_format(:email, @email_regex)
|> validate_length(:countersign, min: vi, max: 32)
|> validate_confirmation(:password)
|> downcase_email()
|> unique_constraint(:email)
|> put_pass_hash()
end
def update_changeset(user, attrs) practise
user
|> cast(attrs, [:proper noun, :email, :password, :password_confirmation, :remember_hash])
|> validate_length(:name, min: three, max: 50)
|> validate_length(:email, max: 255)
|> validate_format(:email, @email_regex)
|> validate_length(:countersign, min: half dozen, max: 32)
|> validate_confirmation(:password)
|> downcase_email()
|> unique_constraint(:email)
|> put_pass_hash()
end
def new_token(size \\ 64) do
:crypto.strong_rand_bytes(size)
|> Base.url_encode64(padding: imitation)
end
defp put_pass_hash(changeset) do
case changeset do
%Ecto.Changeset{valid?: true, changes: %{countersign: pass}} ->
put_change(changeset, :password_hash, Comeonin.Pbkdf2.hashpwsalt(pass))
_ ->
changeset
end
cease
defp downcase_email(changeset) exercise
case fetch_change(changeset, :email) practise
{:ok, email} -> put_change(changeset, :email, Cord.downcase(email))
:mistake -> changeset
end
end
end
Necessary migration files were added as well. Too, wrote Auth plug to authenticate users —
defmodule MinitwitterWeb.Auth practice
import Plug.Conn
import Phoenix.Controller alias Minitwitter.Accounts
alias MinitwitterWeb.Router.Helpers, equally: Routes
@max_age 7 * 24 * 60 * 60
def init(opts), exercise: opts
def call(conn, _opts) do
user_id = get_session(conn, :user_id)
cond do
user = conn.assigns[:current_user] ->
put_current_user(conn, user)
user = user_id && Accounts.get_user(user_id) ->
put_current_user(conn, user)
true ->
assign(conn, :current_user, nil)
terminate
cease
def login(conn, user) do
conn
|> put_current_user(user)
|> put_session(:user_id, user.id)
|> configure_session(renew: true)
terminate
defp put_current_user(conn, user) do
conn
|> assign(:current_user, user)
terminate
def authenticate_user(conn, _opts) do
cond exercise
conn.assigns.current_user ->
conn
remember_token = conn.cookies["remember_token"] ->
user_id = conn.cookies["user_id"]
user = Accounts.get_user(user_id)
if user && Accounts.authenticated?(user, remember_token) exercise
login(conn, user)
else
halt_connection(conn)
end
true ->
halt_connection(conn)
finish
terminate
defp halt_connection(conn) practise
conn
|> put_flash(:error, "You must exist logged in to admission that folio!")
|> redirect(to: Routes.page_path(conn, :home))
|> halt()
end
def login_by_email_and_pass(conn, email, given_pass) practise
case Accounts.authenticate_by_email_and_pass(e-mail, given_pass) practise
{:ok, user} -> {:ok, login(conn, user)}
{:mistake, :unauthorized} -> {:mistake, :unauthorized, conn}
{:error, :not_found} -> {:mistake, :not_found, conn}
end
finish
def logout(conn) practice
if conn.assigns.current_user do
Accounts.forget_user(conn.assigns.current_user)
conn
|> delete_resp_cookie("user_id")
|> delete_resp_cookie("remember_token")
|> configure_session(drop: true)
cease
end
def retrieve(conn, user, token) do
conn =
conn
|> put_resp_cookie("user_id", Integer.to_string(user.id), max_age: @max_age)
|> put_resp_cookie("remember_token", token, max_age: @max_age)
conn
end
end
Both session and persistent cookies were used to call up necessary tokens. Acccounts context were updated also with necessary functions.
Twenty-four hours 5 (Jan 14, 2019)
What's covered: Chapter ten
Features covered: Updating, showing, and deleting users
Libraries/Dependencies used: Had to add additional dependencies in mix.exs to support pagination of users and imitation data generation for DB seeds.
From mix.exs —
{:faker, "~> 0.11"},
{:scrivener_ecto, "~> ii.0"}
Commits for day v:
For false information generation below code was used (excerpt from last commit of seeds.exs) —
# Script for populating the database. You lot tin run it as:
#
# mix run priv/repo/seeds.exs
#
# Within the script, you can read and write to any of your
# repositories directly:
#
# Minitwitter.Repo.insert!(%Minitwitter.SomeSchema{})
#
# Nosotros recommend using the blindside functions (`insert!`, `update!`
# and so on) equally they will neglect if something goes incorrect.
defmodule MinitwitterWeb.DevelopmentSeeder practice
import Ecto.Query, just: [from: 2] alias Minitwitter.Accounts
alias Minitwitter.Accounts.User
alias Minitwitter.Microposts
alias Minitwitter.Repo
def insert_data do
# users
Repo.insert!(%Accounts.User{
name: "Admin",
e-mail: "demo.rails007@gmail.com",
countersign: "phoenix",
password_hash: Comeonin.Pbkdf2.hashpwsalt("phoenix"),
admin: true,
activated: true,
activated_at: DateTime.truncate(DateTime.utc_now(), :second)
})
for _ <- i..50,
exercise:
Repo.insert!(%Accounts.User{
name: Faker.Proper name.proper noun(),
email: Faker.Internet.e-mail(),
password: "phoenix",
password_hash: Comeonin.Pbkdf2.hashpwsalt("phoenix"),
activated: truthful,
activated_at: DateTime.truncate(DateTime.utc_now(), :second)
})
# Microposts
query =
from u in "users",
select: u.id
ids = Repo.all(query)
Enum.each(ids, fn id ->
for _ <- 1..50,
do:
Repo.insert!(%Microposts.Post{
content: Faker.Lorem.sentence(5),
user_id: id
})
end)
# Follwing relationships
users = Repo.all(User)
user = Enum.at(users, 0)
following = Enum.slice(users, ii, 50)
followers = Enum.piece(users, 3, 40)
for followed <- post-obit, do: Minitwitter.Accounts.follow(followed, user)
for follower <- followers, do: Minitwitter.Accounts.follow(user, follower)
end
terminate
case Mix.env() do
:dev ->
MinitwitterWeb.DevelopmentSeeder.insert_data()
_ ->
:ignore
end
Day six (Jan xv, 2019)
What'south covered: Chapter 11
Features covered: Account activation support with email
Libraries/Dependencies used: Had to add additional dependencies in mix.exs to back up email sending using smtp.
From mix.exs —
{:bamboo, "~> 1.1"},
{:bamboo_smtp, "~> 1.vi.0"}
Commits for day half dozen:
Configuration for mailer from config.exs —
config :minitwitter, Minitwitter.Mailer,
adapter: Bamboo.SMTPAdapter,
server: "smtp.gmail.com",
port: 587,
username: "demo.rails007",
countersign: "XXXXXXX",
# can be `:always` or `:never`
tls: :if_available,
# tin be `true`
ssl: false,
retries: 3
Email template code goes as below —
<h1>Minitwitter App</h1><p>Hi <%= @user.proper noun %>,</p>
<p>
Welcome to the Minitwitter! Click on the link below to activate your account:
</p> <%= Routes.account_activations_url(@conn, :edit, @user.activation_token, e-mail: @user.email) %>
Twenty-four hour period 7 (January xvi, 2019)
What's covered: Affiliate 12
Features covered: Password reset support with email
Libraries/Dependencies used: N/A
Commits for day 7:
Twenty-four hour period eight & nine (Jan xviii & 19, 2019)
What'south covered: Affiliate 13
Features covered: Microposts support with picture upload
Libraries/Dependencies used: Had to add additional dependencies in mix.exs to back up time formatting in microposts and picture upload —
From mix.exs —
{:timex, "~> 3.4"},
{:arc, "~> 0.xi.0"},
{:arc_ecto, "~> 0.11.1"}
Commits for twenty-four hour period 8 & 9:
Added Postal service schema for microposts with pictures —
defmodule Minitwitter.Microposts.Postal service do
use Ecto.Schema
utilise Arc.Ecto.Schema
import Ecto.Changeset schema "posts" practice
field :content, :cord
field :user_id, :id
field :movie, Minitwitter.ImageUploader.Type
timestamps()
stop
@doc false
def changeset(post, attrs) do
postal service
|> cast(attrs, [:content, :user_id])
|> cast_attachments(attrs, [:film])
|> validate_required([:content, :user_id])
|> validate_length(:content, min: one, max: 140)
|> foreign_key_constraint(:user_id)
end
stop
Also, added necessary migration files.
Day 10 (Jan 20, 2019)
What'southward covered: Chapter 14
Features covered: Following users
Libraries/Dependencies used: N/A
Commits for twenty-four hour period 8 & ix:
Added Relationship schema to back up user following —
defmodule Minitwitter.Accounts.Relationship do
use Ecto.Schema
import Ecto.Changeset schema "relationships" do
belongs_to(:follower, Minitwitter.Accounts.User)
belongs_to(:followed, Minitwitter.Accounts.User)
timestamps()
stop
@doc false
def changeset(relationship, attrs) practice
human relationship
|> cast(attrs, [:follower_id, :followed_id])
|> validate_required([:follower_id, :followed_id])
|> foreign_key_constraint(:follower_id)
|> foreign_key_constraint(:followed_id)
end
end
Updated User schema to support user following —
defmodule Minitwitter.Accounts.User do
use Ecto.Schema
import Ecto.Changeset schema "users" do
field :name, :string
field :email, :string
field :password, :cord, virtual: true
field :password_confirmation, :string, virtual: true
field :password_hash, :string
field :remember_hash, :string
field :admin, :boolean, dafault: false
field :activation_token, :string, virtual: truthful
field :activation_hash, :cord
field :activated, :boolean, default: faux
field :activated_at, :utc_datetime
field :reset_hash, :cord
field :reset_sent_at, :utc_datetime
has_many(:posts, Minitwitter.Microposts.Post)
has_many(:active_relationships, Minitwitter.Accounts.Human relationship, foreign_key: :follower_id)
has_many(:passive_relationships, Minitwitter.Accounts.Human relationship, foreign_key: :followed_id)
has_many(:following, through: [:active_relationships, :followed])
has_many(:followers, through: [:passive_relationships, :follower])
timestamps()
finish
@email_regex ~r/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
@doc false
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :email, :countersign, :password_confirmation])
|> validate_required([:proper noun, :email, :password, :password_confirmation])
|> validate_length(:proper name, min: 3, max: 50)
|> validate_length(:e-mail, max: 255)
|> validate_format(:e-mail, @email_regex)
|> validate_length(:password, min: 6, max: 32)
|> validate_confirmation(:password)
|> downcase_email()
|> unique_constraint(:electronic mail)
|> put_pass_hash()
|> put_activation_hash()
end
def reset_pass_changeset(user, attrs) exercise
user
|> bandage(attrs, [:countersign, :password_confirmation, :reset_hash])
|> validate_required([:password, :password_confirmation])
|> validate_length(:password, min: 6, max: 32)
|> validate_confirmation(:password)
|> put_pass_hash()
stop
def update_changeset(user, attrs) do
user
|> cast(attrs, [
:proper name,
:email,
:password,
:password_confirmation,
:remember_hash,
:activated,
:activated_at,
:reset_hash,
:reset_sent_at
])
|> validate_required([:proper noun, :email])
|> validate_length(:proper noun, min: 3, max: 50)
|> validate_length(:email, max: 255)
|> validate_format(:electronic mail, @email_regex)
|> validate_length(:password, min: 6, max: 32)
|> validate_confirmation(:password)
|> downcase_email()
|> unique_constraint(:e-mail)
|> put_pass_hash()
end
def new_token(size \\ 64) practice
:crypto.strong_rand_bytes(size)
|> Base.url_encode64(padding: imitation)
finish
defp put_pass_hash(changeset) practise
case changeset practice
%Ecto.Changeset{valid?: true, changes: %{password: pass}} ->
put_change(changeset, :password_hash, Comeonin.Pbkdf2.hashpwsalt(pass))
_ ->
changeset
finish
end
defp put_activation_hash(changeset) do
case changeset practise
%Ecto.Changeset{valid?: true} ->
token = new_token()
changeset
|> put_change(:activation_token, token)
|> put_change(:activation_hash, Comeonin.Pbkdf2.hashpwsalt(token))
_ ->
changeset
end
end
defp downcase_email(changeset) do
example fetch_change(changeset, :email) do
{:ok, email} -> put_change(changeset, :email, String.downcase(email))
:error -> changeset
stop
end
end
This is pretty much the consummate application. I suggest you lot accept Michael'south tutorial and read the code day by day basis to get whole idea of the project.
Experience rewriting the app:
My thought/experience later this experiment —
- Crimson/Rails in yet more concise but Elixir/Phoenix is more explicit.
- Elixir/Phoenix libraries seems bachelor and getting mature day by twenty-four hour period.
- Rails and Phoenix are more compatible in Coffeescript and EEx script. I am not a frontend person merely fifty-fifty I could pretty much convert the Coffeescript code into EEx without much trouble (but I think my CSS are messed up!).
- I establish ecto to be a bit circuitous compared to Active Record. Peculiarly, ecto support multiple ways of doing things which may confuse a beginner. But once you get concord of it, it'due south too very explicit and logical. Besides, google is your friend if y'all are stuck :)
Conclusion:
I learned a lot by doing this hobby project. I think I now understand Elixir/Phoenix a lot amend. I hope this write-up will encourage more developers to look into Elixir/Phoenix seriously and possibly brand some converts :)
I hope this post and the project source code were useful for some out there. For more elaborate and in depth future technical posts please follow me here or on twitter .
rodriguezvatir1975.blogspot.com
Source: https://itnext.io/rewriting-a-ruby-rails-app-in-elixir-phoenix-almost-literally-7d50d913c8d3
0 Response to "Ruby on Rails Michael Hartl Chapter 13 Cant Upload Picture"
Postar um comentário