Ruby on Rails Michael Hartl Chapter 13 Cant Upload Picture

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.

Minitwitter App Homepage

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.

Sign up page

Log in page

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

Update profile page

Showing users folio

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:

Forgot countersign page

Reset password page

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.

Microposts with picture and fourth dimension format

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

Following, Followers back up

Follow/Unfollow back up

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

Related Posts

0 Response to "Ruby on Rails Michael Hartl Chapter 13 Cant Upload Picture"

Postar um comentário

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel