GithubHelp home page GithubHelp logo

phoenix-elixir's Introduction

Hello

To start your Phoenix server:

  • Install dependencies with mix deps.get
  • Create and migrate your database with mix ecto.setup
  • Start Phoenix endpoint with mix phx.server or inside IEx with iex -S mix phx.server

Now you can visit localhost:4000 from your browser.

Ready to run in production? Please check our deployment guides.

Learn more

phoenix-elixir's People

Contributors

cristhianrodriguezmolina avatar

Watchers

 avatar

phoenix-elixir's Issues

Phoenix chapters 4 to 6

Routing

To see the routes of a project we use the command:

mix phx.routes

This will create the standard matrix of HTTP verbs, paths, and controller actions for the user routes:

resources("/users", UserController)

What above will gives us the following routes:

user_path  GET     /users                                 HelloWeb.UserController :index
user_path  GET     /users/:id/edit                        HelloWeb.UserController :edit
user_path  GET     /users/new                             HelloWeb.UserController :new
user_path  GET     /users/:id                             HelloWeb.UserController :show
user_path  POST    /users                                 HelloWeb.UserController :create
user_path  PATCH   /users/:id                             HelloWeb.UserController :update
           PUT     /users/:id                             HelloWeb.UserController :update
user_path  DELETE  /users/:id                             HelloWeb.UserController :delete

Path helpers to get the path of an action:

iex(9)> HelloWeb.Router.Helpers.hello_path(HelloWeb.Endpoint, :show, "Cristh")
"/hello/Cristh"

And for nested paths we should add all the parameters as arguments of the helper function:

iex(2)> HelloWeb.Router.Helpers.user_post_path(HelloWeb.Endpoint, :edit, 4, 5)  
"/users/4/posts/5/edit"

We can get also the complete url and add params to the path:

iex(7)> Routes.user_post_url(Endpoint, :edit, 4, 5, name: :hello) 
"http://localhost:4000/users/4/posts/5/edit?name=hello"

Scoping routes under a common prefix:

scope "/admin", HelloWeb.Admin, as: :admin do
    pipe_through(:browser)

    resources("/reviews", ReviewController)
end

The following function links a scope to a pipeline:

pipe_through(:browser)

or

pipe_through([:browser, :review_checks])

Plug

A plug receives a connection and a list of options and returns a connection, and can be a function or a module.

  • Example of a plug function:
def introspect(conn, _opts) do
    IO.puts("""
    Verb: #{inspect(conn.method)}
    Host: #{inspect(conn.host)}
    Headers: #{inspect(conn.req_headers)}
    """)

    conn
  end
  • Example of a plug module for locations:
defmodule HelloWeb.Plugs.Locale do
  import Plug.Conn

  @locales ["en", "fr", "de"]

  # Default or options passed to the plug in its utilization
  def init(default), do: default

  def call(%Plug.Conn{params: %{"locale" => loc}} = conn, _default) when loc in @locales do
    # With this function we save data in the conn data struct
    assign(conn, :locale, loc)
  end

  def call(conn, default) do
    # With this function we save data in the conn data struct
    assign(conn, :locale, default)
  end
end

Use of the module plug:

plug HelloWeb.Plugs.Locale, "en"

The endpoint, router, and controllers in Phoenix accept plugs.

Endpoint

The endpoint has a lot of plugs which are called every time a request comes to the application, before matching the request to a path, plugs to handle session, cookies, parsers, telemetry, static content, plugs for code reloading (for dev purposes), etc..
And the last plug is the Router itself:

# The router which matches a path to a particular controller action or plug
plug(HelloWeb.Router)

Phoenix chapters 13 to 15

Context

To create a context having different options mix phx.gen.html, mix phx.gen.json, mix phx.gen.live, and mix phx.gen.context.

Created a basic product CRUD with phoenix context with products, catalog and cart.

Product schema creation with catalog context:

mix phx.gen.html Catalog Product products title:string description:string price:decimal views:integer

Category schema creation with catalog context:

mix phx.gen.context Catalog Category categories title:string:unique

Cart schema creation with a shopping cart context:

mix phx.gen.context ShoppingCart Cart carts user_uuid:uuid:unique

Bugs fixed

  • Bug 1:

Bug: Trying to fix a bug with phoenix link in which the method for request POST doesn't work, and just makes GET requests.

Fixed: Bug fixed confirming the existence of the chatInput in the /assets/user_socket.js file when linking an event:

if (chatInput) {
  chatInput.addEventListener("keypress", event => {
    if (event.key === 'Enter') {
      channel.push("new_msg", { body: chatInput.value })
      chatInput.value = ""
    }
  })
}
  • Bug 2:

Bug: A bug where the confirmation delete popup was called multiple times.
Fixed: The bug was caused due to a multiple call of the /assets/app.js file in multiple files like index and app layouts, reviewed this resource: https://stackoverflow.com/questions/52327992/phoenix-link-confirmation-popup-appears-multiple-times

Mix tasks

phx.gen

Creating a Context Blog with html necessary code, with a Post schema related to a posts table in a database, the Post schema has the attributes of body with type string and a word_count with type integer:

mix phx.gen.html Blog Post posts body:string word_count:integer

We can use mix phx.gen.json and mix phx.gen.context to create context the same way like above but with different output files.

To generate a schema and migration files:

mix phx.gen.schema Accounts.Credential credentials email:string:unique user_id:references:users

To generate the files for an authentication system:

mix phx.gen.auth Accounts User users

To create channels:

mix phx.gen.channel Room

To create a presence tracker:

mix phx.gen.presence Presence

ecto

To the create the database specified in the configuration of the repo:

mix ecto.create

To delete the database specified in the configuration of the repo:

mix ecto.drop

To create a custom repo in /lib/OurCustom/repo.ex:

mix ecto.gen.repo -r OurCustom.Repo

To apply the changes of our migration files to the database:

mix ecto.migrate

Phoenix testing

Testing introduction

To test in Phoenix the HelloWeb.ConnCase is used instead of ExUnit.Case to test a library outside of phoenix.

Tags

Tag for and entire test module:

@moduletag :error_view_case
@moduletag error_view_case: "Something new"

Tag for an individual test in a module:

@tag :individual_test
@tag individual_test: "nope"

We can only use the tests with a specific tag:

> mix test --only error_view_case
> mix test --only error_view_case:something

Or exclude that specific tests:

> mix test --exclude error_view_case
> mix test --exclude error_view_case:something

Random

Running test with an specific random seed:

mix test --seed 401472

Context

To test contexts we have HelloWeb.DataCase that is similar to HelloWeb.ConnCase but it provides functionalities to test contexts and schemas.

Channel

To test contexts we have HelloWeb.ChannelCase that is similar to HelloWeb.ConnCase and HelloWeb.DataCase but it provides functionalities to test channels.

Phoenix chapters 7 to 9

Controllers

Controller functions conventions:

  • index - renders a list of all items of the given resource type
  • show - renders an individual item by ID
  • new - renders a form for creating a new item
  • create - receives parameters for one new item and saves it in a data store
  • edit - retrieves an individual item by ID and displays it in a form for editing
  • update - receives parameters for one edited item and saves the item to a data store
  • delete - receives an ID for an item to be deleted and deletes it from a data store

This functions receive a conn and a params as arguments.

Passing values to use in a template:

def show(conn, %{"messenger" => messenger}) do
    conn
    |> assign(:messenger, messenger)
    |> assign(:receiver, "Dweezil")
    |> render("show.html")
end

Changing the default layout for index to admin.html with the put_root_layout function:

conn
|> put_root_layout("admin.html")
|> render("index.html")

To accept the format returnet by Phoenix.Controller.get_format/1 in a controller we have to put the :index atom as the render:

def index(conn, _params) do
  render(conn, :index)
end

Sending responses with a content type:

def index(conn, _params) do
  conn
  |> put_resp_content_type("text/plain")
  |> send_resp(201, "")
end

We can use the following to redirect to another path, in this case redirecting to the path of page_path (Path helper) that corresponds to the action :redirect_test:

redirect(conn, to: Routes.page_path(conn, :redirect_test))

Views and templates

Rendering a condition:

<%= if some_condition? do %>
  <p>Some condition is true for user: <%= @username %></p>
<% else %>
  <p>Some condition is false for user: <%= @username %></p>
<% end %>

Rendering a for:

<table>
  <tr>
    <th>Number</th>
    <th>Power</th>
  </tr>
<%= for number <- 1..10 do %>
  <tr>
    <td><%= number %></td>
    <td><%= number * number %></td>
  </tr>
<% end %>
</table>

What is inside a <%= %> will be executed and inserted into the template but what is inside a <% %> will be executed but not inserted.

Including an attribute or many attributes to a html tag:

<div title="My div" class={@class} {@many_attributes}>
  <p>Hello <%= @username %></p>
</div>

Created a new component from phoenix:

defmodule MyApp.Weather do
  use Phoenix.Component

  def country(assigns) do
    ~H"""
    The chosen country is: <%= @name %>.
    """
  end
end

Rendering outside the HelloWeb.PageView view the test.html.heex template with the Phoenix.View.render/3:

Phoenix.View.render(HelloWeb.PageView, "test.html", message: "Hello from layout!")

If we create a function in a view that function will be available to the templates that funcion render like below:

def handler_info(conn) do
    "Request Hanlded By: #{controller_module(conn)}.#{action_name(conn)}"
end

@ is actually a macro that translates @key to Map.get(assigns, :key).

Phoenix deployment

Deployment introduction

To get the secret key base of the project we use mix phx.gen.secret.

After this we need to export two env variables, SECRET_KEY_BASE with the value given by the command above and DATABASE_URL with the database information:

> export SECRET_KEY_BASE=REALLY_LONG_SECRET
> export DATABASE_URL=ecto://USER:PASS@HOST/database

And then do a setup before compile and deploy:

> mix deps.get --only prod
> MIX_ENV=prod mix compile

If we have a database we should migrate the data as well before compiling assets:

MIX_ENV=prod mix ecto.migrate

Compiling assets

After the commands above we have to compile the assets with the following command that it will compile the assets to the priv folder:

MIX_ENV=prod mix assets.deploy

Deploying on Heroku

We have to install the Heroku Toolbelt for that we need to use the following command with snap:

sudo snap install --classic heroku

To create the Heroku application:

heroku create --buildpack hashnuke/elixir

To create a database that will be used by the application in Heroku with the Heroku Toolbelt:

heroku addons:create heroku-postgresql:hobby-dev

Pool size configuration:

heroku config:set POOL_SIZE=18

Bugs

  • Fixed bug related to the socket usage in the deployment of the Heroku phoenix app.
  • Fixed bug related to the branch and buildpack at deployment time of the phoenix application.

Phoenix chapters 10 to 12

Channels

To invoke the socket generator:

mix phx.gen.socket User

Specifying the socket handler in endpoint

socket "/socket", HelloWeb.UserSocket,
  websocket: true,
  longpoll: false

Socket connection in the javascript client

let socket = new Socket("/socket", {params: {token: window.userToken}})

Channel specification for the user socket in the server:

channel "room:*", HelloWeb.RoomChannel

Room channel in lib/hello_web/channels/room_channel.ex:

defmodule HelloWeb.RoomChannel do
  use Phoenix.Channel

  def join("room:lobby", _message, socket) do
    {:ok, socket}
  end

  def join("room:" <> _private_room_id, _params, _socket) do
    {:error, %{reason: "unauthorized"}}
  end
end

Presence

To use Presence we should use the presence generator:

 mix phx.gen.presence

And add the new Presence created to the app supervision tree:

children = [
  ...
  HelloWeb.Presence,
]

And after that create the channel we want to communicate presence over:

defmodule HelloWeb.RoomChannel do
  use Phoenix.Channel
  alias HelloWeb.Presence

  def join("room:lobby", %{"name" => name}, socket) do
    send(self(), :after_join)
    {:ok, assign(socket, :name, name)}
  end

  def handle_info(:after_join, socket) do
    {:ok, _} =
      Presence.track(socket, socket.assigns.name, %{
        online_at: inspect(System.system_time(:second))
      })

    push(socket, "presence_state", Presence.list(socket))
    {:noreply, socket}
  end
end

And finally add the functionality to the client app, in this case list the users list, rendering it in a div with id=connections:

function renderOnlineUsers(presence) {
  let response = ""

  presence.list((id, {metas: [first, ...rest]}) => {
    let count = rest.length + 1
    response += `<br>${id} (count: ${count})</br>`
  })

  document.querySelector("div[id=connections]").innerHTML = response
}

Ecto

To create a User schema with four fields we use the mix phx.gen.schema:

mix phx.gen.schema User users name:string email:string bio:string number_of_pets:integer

After this we should migrate to create the tables in the database:

mix ecto.migrate

Example of a migration with timestamps:

defmodule Hello.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :name, :string
      add :email, :string
      add :bio, :string
      add :number_of_pets, :integer

      timestamps()
    end
  end
end

Example of a changeset with validations:

def changeset(%User{} = user, attrs) do
  user
  |> cast(attrs, [:name, :email, :bio, :number_of_pets])
  |> validate_length(:bio, min: 2)
  |> validate_length(:bio, max: 140)
  |> validate_required([:name, :email, :bio, :number_of_pets])
end

Inserting an user to the data base with Repo:

Hello.Repo.insert(%User{email: "[email protected]"})

Initialization chapters in Phoenix framework

Overview

For phoenix we should have installed the Erlang VM and the Elixir programming language, and a database.
After installing Erlang, Elixir and a db like postgresql we have to:

  • Install Hex:
mix local.hex
  • Install phoenix framework:
mix archive.install hex phx_new
  • Install Inotify-tools for Live Reloading en Ubuntu/Debian:
apt-get install inotify-tools

Up and running

Adding pages

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.