elixir-path's People
elixir-path's Issues
Ecto Elixir path (Chapter 1 to 3)
Ecto chapter 1 - Introduction
There are diferent adapter for ecto in Elixir, keeping this in mind for this project the postgresql adapter was used, defining it in the dependencies of a new project:
{:ecto_sql, "~> 3.2"}, # Ecto
{:postgrex, "~> 0.15"} # Adapter
Database configuration for the Friends application:
config :friends, Friends.Repo,
database: "friends_repo",
username: "postgres",
password: "postgres",
hostname: "localhost"
Migration to create the people table:
defmodule Friends.Repo.Migrations.CreatePeople do
use Ecto.Migration
def change do
create table(:people) do
add :name, :string, null: false
add :age, :integer, default: 0
end
end
end
And finally the schema to access to that table:
defmodule Friends.Person do
use Ecto.Schema
schema "people" do
field :name, :string
field :age, :integer, default: 0
end
end
Changesets
We can do changes over a struct schema with the Changeset functions, cast/3 and change/2, like the example below, where we change the name of a Person struct:
iex> Ecto.Changeset.cast(%App.Person{name: "Bob"}, %{"name" => "Jack"}, [:name, :age])
%Ecto.Changeset<
action: nil,
changes: %{name: "Jack"},
errors: [],
data: %Friends.Person<>,
valid?: true
>
Validations
There are some validations for changesets, in order to guarantee that the information given to the changeset is valid:
def test_changeset(struct, params) do
struct
# Fields allowed to go through and be modified
|> cast(params, [:name, :age])
# ensures age is included in the range 0 to 99 (enumerable)
|> validate_inclusion(:age, 0..99)
|> validate_exclusion(:name, ~w(max ben))
# ensures that the name is always required
|> validate_required([:name])
# ensures that the name length is always above 2
|> validate_length(:name, min: 4)
|> validate_fictional_name()
end
Ecto Elixir path (Chapter 4 to 5)
Associations
Created and tested the associations Belongs to/Has many
, Belongs to/Has one
and Many to Many
.
Then create a new Movie to test the insertion, association, etc:
movie = %Movie{title: "Jurassic Park", tagline: "Just dinosaurs"}
And then insert it to the database with the Repo module:
movie = Repo.insert!(movie)
Associating a new character to the movie created above and inserting it to the database:
character = Ecto.build_assoc(movie, :characters, %{name: "Alan Grant"})
Repo.insert!(character)
Querying
With the data already in the database we can now query that data with the following functions:
get and get_by
Using Repo.get/3
for IDs:
movie = Repo.get(Movie, 1)
Using Repo.get_by/3
for given criterias:
movie = Repo.get(Movie, title: "Jurassic Park")
from
We can use Ecto.Query.from/2
macro to create a query and then execute it with Repo.all/2
like so:
query = from(Movie, where: [title: "Jurassic Park"], select: [:title, :tagline])
Repo.all(query)
The code above will give us the title and tagline of the movies call explicitly Jurassic Park
.
query = from m in Movie,
join: c in Character,
on: m.id == c.movie_id,
where: c.name == "Alant Grant",
select: {m.title, c.name}
Repo.all(query)
The code above will give us just the title of a movie and the name of a character where the movie id is equals in both, and additional where the name of the character is Alant Grant
.
Metaprogramming (Chapter 1 to 3)
Quote and unquote
We can see what is the building block of a code in elixir with the quote
macro like so:
iex> quote do: sum(1, 2, 3)
{:sum, [], [1, 2, 3]}
And even get something more complex, like nested representations:
iex> quote do: String.upcase("foo")
{{:., [], [{:__aliases__, [alias: false], [:String]}, :upcase]}, [], ["foo"]}
All this quotes are represented in the format: {atom | tuple, list, list | atom}
.
To get the representation with the value of number and not with the variable itself ("11 + number").
iex> number = 13
13
iex> Macro.to_string(quote do: 11 + unquote(number))
"11 + 13"
Macros
Defined some macros with defmacro
and tested it:
defmodule Unless do
# Unless representation with macros
defmacro unless_macro(clause, do: expression) do
quote do
if(!unquote(clause), do: unquote(expression))
end
end
end
And tested it like so:
IO.inspect(Unless.unless_macro(true, do: IO.puts("This is not showing up")))
Also tested the interference with macros, using two macros in a function :
defmodule Hygiene do
defmacro no_interference do
quote do: a = 1
end
# This produces an interference due to the var function which changes the value of a in the context where the macro is used
defmacro interference do
quote do: var!(a) = 1
end
end
defmodule HygieneTest do
def go do
require Hygiene
a = 13
Hygiene.no_interference()
IO.puts("This has no interference #{a}")
# This has a warning cause this value is never used
a = 13
Hygiene.interference()
IO.puts("This has interferenece #{a}")
end
end
Domain Specific Lenguages (DSL)
Created a test module and tested it with some test cases:
defmodule UsingTestCase do
use TestCase
test "New test" do
5 = 4 + 1
end
test "New test with map" do
%{a: 1, b: 3} = Map.put(%{a: 1}, :b, 3)
end
# This will throw an error
test "New test with list" do
[1, 2, 3] = [1, 2] ++ [4]
end
end
Advanced chapter 10 to 11
Distributed tasks and tags
Distributed tasks
Starting the use of distributed nodes with distributed iex sessions, each one with a diferent name reference, like so:
iex --sname bar
Using async/await with Task:
task = Task.async(fn -> compute_something_expensive() end)
res = compute_something_else()
res + Task.await(task)
Keeping this in mind, a new process to the supervision tree of the kv application was added, a task supervisor to perform processess in diferent nodes:
{Task.Supervisor, name: KV.RouterTasks},
And a new file for routing was created in /kv/lib/router.ex
, routing to different nodes based on the bucket names like so:
[{?a..?m, :"foo@computer-name"}, {?n..?z, :"bar@computer-name"}]
Finally changing the lookup
and run
with the :create
action methods in /kv_server/lib/command.ex
to include the router in the server behaviour:
def run({:create, bucket}, pid) do
case KV.Router.route(bucket, KV.Registry, :create, [KV.Registry, bucket]) do
pid when is_pid(pid) -> {:ok, "OK\r\n"}
_ -> {:error, "FAILED TO CREATE BUCKET"}
end
end
defp lookup(bucket, callback, server) do
case KV.Router.route(bucket, KV.Registry, :lookup, [KV.Registry, bucket]) do
{:ok, pid} -> callback.(pid)
:error -> {:error, :not_found}
end
end
tags
Finally adding a new tag @tag :distributed
to the router test, like so:
@tag :distributed
test "route requests across nodes" do
assert KV.Router.route("hello", Kernel, :node, []) ==
:"foo@cristh-MS-7B85"
assert KV.Router.route("world", Kernel, :node, []) ==
:"bar@cristh-MS-7B85"
end
And adding the exclusion to the test_helper.exs
file:
exclude = if Node.alive?(), do: [], else: [distributed: true]
ExUnit.start(exclude: exclude)
This to avoid an error due to the need of two nodes foo
and bar
for the test.
Configuration and releases
Finishing advanced path in Elixir with configuration and package for production.
Application enviroment
Creating an enviroment variable adding it to the application function
in the mix.exs
file in a project like so:
def application do
[
extra_applications: [:logger],
env: [routing_table: []],
mod: {KV, []}
]
end
After this i can now access to that env variable:
Application.fetch_env!(:kv, :routing_table)
Now inside the config folder of the umbrella project in a file runtime.exs
i can configure the env variable with:
import Config
# Configuring the enviroment variable `:routing_table`
config :kv, :routing_table, [{?a..?z, node()}]
Finally to pass successfully the test presented in tags with two nodes, i had to add a set_up
function to set up a test env like so:
# This setup runs after all the test in the file
setup_all do
# Getting the current state of the env variable `:routing_table`
current = Application.get_env(:kv, :routing_table)
Application.put_env(:kv, :routing_table, [
{?a..?m, :"foo@cristh-MS-7B85"},
{?n..?z, :"bar@cristh-MS-7B85"}
])
# Setting back the real value of `:routing_table` on exit
on_exit(fn -> Application.put_env(:kv, :routing_table, current) end)
end
We can run the test above with iex --sname bar -S mix
in one terminal and PORT=4041 elixir --sname foo -S mix test --only distributed
(the PORT=4041 option is because the other session runs on PORT=4040) in another one.
releases
To create a release we have to add the releases we want to the project/0
function in the mix.exs
of the umbrella.
The two releases for this project was foo
containing the kv_server
and kv
, and bar
just containing the kv
(what is just the necessary) to avoid problems with the ports.
And then we can run the next commands to create the releases respectively:
MIX_ENV=prod mix release foo
and
MIX_ENV=prod mix release bar
And after this just runs both of the releases that are in /_build/prod/rel
folder of the umbrella with the next commands, respectively:
_build/prod/rel/foo/bin/foo
and
_build/prod/rel/bar/bin/bar
Specifics (Chapter 1 to 4)
Plug
Basic configuration of the plug to verify a request in the router
defmodule Example.Plug.VerifyRequest do
defmodule IncompleteRequestError do
@moduledoc """
Error raised when a required field is missing.
"""
defexception message: ""
end
def init(options), do: options
def call(%Plug.Conn{request_path: path} = conn, opts) do
if path in opts[:paths], do: verify_request!(conn.params, opts[:fields])
conn
end
Child configuration of the router for plug_cowboy dependency (The cowboy_port() gets the port defined in the environment configuration or return 8080 if there is no definition):
{Plug.Cowboy, scheme: :http, plug: Example.Router, options: [port: cowboy_port()]}
Basic router definition with a main route and a 404 error route:
defmodule Example.Router do
use Plug.Router
# Importing the plug to the router
alias Example.Plug.VerifyRequest
plug(Plug.Parsers, parsers: [:urlencoded, :multipart])
# Plug configuration for the router
plug(VerifyRequest, fields: ["content", "mimetype"], paths: ["/upload"])
plug :match
plug :dispatch
get "/" do
send_resp(conn, 200, "Welcome")
end
get "/upload" do
send_resp(conn, 201, "Uploaded")
end
match _ do
send_resp(conn, 404, "Oops!")
end
end
Basic testing for the route /upload
which has the verification with the plug:
defmodule Example.RouterTest do
use ExUnit.Case
use Plug.Test
alias Example.Router
@content "<html><body>Hi!</body></html>"
@mimetype "text/html"
@opts Router.init([])
test "returns uploaded" do
conn =
:get
|> conn("/upload?content=#{@content}&mimetype=#{@mimetype}")
|> Router.call(@opts)
assert conn.state == :sent
assert conn.status == 201
end
EEx
Basic file greeting.eex:
<%= if (String.length name) > 3 do %>
Hi, small <%= name %>
<% else %>
Hi, big <%= name %>
<% end %>
Using function_from_file
function of EEx in a module:
defmodule Example do
require EEx
# Compiling a eex file
EEx.function_from_file(:def, :greeting, "lib/greeting.eex", [:name])
end
Using it:
Example.greeting "Sean"
Mnesia
Starting a schema with mnesia and starting the mnesia DBMS:
:mnesia.create_schema([node()])
:mnesia.start()
Creating a table Person:
:mnesia.create_table(Person, [attributes: [:id, :name, :job]])
Adding some data to the Person table with the mnesia.transaction
and mnesia.write
:
data_to_write = fn ->
:mnesia.write({Person, 1, "Cristhian Rodriguez", "developer"})
:mnesia.write({Person, 2, "John Doe", "unknown"})
end
:mnesia.transaction(data_to_write)
And reading that information:
data_to_read = fn ->
:mnesia.read({Person, 1})
:mnesia.read({Person, 2})
end
:mnesia.transaction(data_to_read)
Debugging
The following is a testing file with a module that has a require of our debugging module IEx, and a call to IEx.pry
which works as a breakpoint in the code:
defmodule TestMod do
require IEx
def sum([a, b]) do
b = 0
# Works like a breakpoint
IEx.pry()
a + b
end
end
To test this module, the iex -r test-exs
should have be called.
Debugger
To use the debugger first of all we have to start an iex of our app:
iex -S mix
Basic operations for debugger:
- Start the debugger
iex> :debugger.start()
- Attach the modules to the debugger
iex> :int.ni(Example)
- Create breakpoints in the specific lines
iex> :int.break(Example, 8)
- Create breakpoints in the specific lines
iex> :int.break(Example, 8)
- Delete a breakpoint in the specific line
iex> :int.delete_break(Example, 8)
Basics chapter 19 to 22
Chapter 19
Theme:
- try, catch, and rescue.
- Typespecs and behaviours.
- Debugging.
- Erlang libraries
Advanced chapter 4 to 6
Supervisor and application
Continuing the process management with supervisors and applications.
Supervisor
Creating supervisors in a module with the init/1 macro provided by:
use Supervisor
The init macro starts the modules (children) in a list in init:
@impl true
def init(:ok) do
children = [
{KV.Registry, name: KV.Registry}
]
# The strategy :one_for_one means that if a child in the supervisor dies it will be the only one restarted
# This opcion will call the start_link implemetation of KV.Registry with the parameter "name: KV.Registry"
Supervisor.init(children, strategy: :one_for_one)
end
Application
Creating an application to start the supervisor once the system is on, with the module:
use Application
The start macro starts the supervisor with the name KV.Supervisor:
@impl true
def start(_type, _args) do
KV.Supervisor.start_link(name: KV.Supervisor)
end
Dynamic supervisors
DynamicSupervisor
Continuing the process management with DynamicSupervisor.
To start a DynamicSupervisor we put it in the Supervidor implemented in chapter 4
as a children:
children = [
# Inititializating the DynamicSupervisor for buckets
# We should place the BucketSupervisor before the Registry cause it invokes the BucketSupervisor
# The strategy :one_for_one means that if a child in the supervisor dies it will be the only one restarted
{DynamicSupervisor, name: KV.BucketSupervisor, strategy: :one_for_one},
{KV.Registry, name: KV.Registry}
]
# The strategy :one_for_all means that if a child dies the supervisor will kill and restart all of its childs
# This opcion will call the start_link implemetation of KV.Registry with the parameter "name: KV.Registry"
Supervisor.init(children, strategy: :one_for_all)
And then we start the buckets in the cast callback of the registry like so:
# With this every bucket starts with the DynamicSupervisor
{:ok, bucket} = DynamicSupervisor.start_child(KV.BucketSupervisor, KV.Bucket)
ETS
Continuing with process management mixing the ETS to the GenServer to do a cache mechanism
To mix the ETS with the GenServer i had to modify the registry file like so,
this is an example in the init method of GenServer where now im creating a
ets table directly for the registry:
names = :ets.new(table, [:named_table, read_concurrency: true])
To search now in the registry we fetch directly in the ets:
case :ets.lookup(server, name) do
# The `^` is to force the name to be equals to the provided
[{^name, pid}] -> {:ok, pid}
[] -> :error
end
And in the test we have now to start the registry with a name provided by the test context:
# This is accessing to a non-shared partition of the state of KV.Registry
_ = start_supervised!({KV.Registry, name: context.test})
Advanced chapter 1 to 3
- Starting a project with mix.
- Learning the basics of mix.
- Proving do tests with mix.
- Starting with the use of Agents
- Improving the use of tests
- GenServer
Advanced chapter 7 to 9
Dependencies and umbrellas
Starting with dependencies and umbrella projects
Dependencies
The external dependencies in a project can be like so, with the name of the dependency and the version associated:
def deps do
[{:plug, "~> 1.0"}]
end
You can also fetch dependencies and compile it with the tasks provided by mix:
mix deps.get
and
mix deps.update
And the internal dependencies can be placed like so, in this case importing the kv module:
def deps do
[{:kv, git: "https://github.com/YOUR_ACCOUNT/kv.git"}]
end
Umbrella projects
I started a new umbrella project where will be all the apps for this practice:
mix new kv_umbrella --umbrella
Every project in /apps needs to have the shared configurations of the umbrella project, like so:
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
Task and gen_tcp
Wrote an basic server using tasks and gen_tcp, firstly creating a method to accept connections in a port, and then serving that connections with a Task.Supervisor:
defp loop_acceptor(socket) do
{:ok, client} = :gen_tcp.accept(socket)
# We start a serve task with the Task Supervisor
{:ok, pid} = Task.Supervisor.start_child(KVServer.TaskSupervisor, fn -> serve(client) end)
# Without this the acceptor will bring down all the clients if it crashed (Like when quitting a client)
:ok = :gen_tcp.controlling_process(client, pid)
loop_acceptor(socket)
end
The serve function inside the task just reads the line from the socket and then replies it to the socket in an infinite loop:
# This method reads a line from the socket and then replies the same line to the socket
defp serve(socket) do
socket
|> read_line()
|> write_line(socket)
serve(socket)
end
Doctest, patterns and with
Doctests
Using doctest to document and make tests at the same time in a specific function, considering a test and a function like so:
@doc ~S"""
Parses the given `line` into a command.
## Examples
iex> KVServer.Command.parse "CREATE shopping\r\n"
{:ok, {:create, "shopping"}}
"""
def parse(line) do
case String.split(line) do
["CREATE", bucket] -> {:ok, {:create, bucket}}
end
end
The example above can be tested with ExUnit and will only pass if the examples in the doc get the same result in the function, we can test the doc like so:
defmodule KVServer.CommandTest do
use ExUnit.Case, async: true
doctest KVServer.Command
end
with
The with allow us to do something similar to a pipeline (|>), to short our code,
for example, doing a flow like this:
msg =
with {:ok, data} <- read_line(socket),
{:ok, command} <- KVServer.Command.parse(data),
do: KVServer.Command.run(command, KV.Registry)
testing the server with gen_tcp
To test the server we need to clean the state of the application like so:
setup do
Application.stop(:kv)
:ok = Application.start(:kv)
end
And then create a socket connection to play like a cliente connecting to the server:
setup do
opts = [:binary, packet: :line, active: false]
# This connects to the server in the port 4040
{:ok, socket} = :gen_tcp.connect('localhost', 4040, opts)
%{socket: socket}
end
And then make the requests:
test "server interaction", %{socket: socket} do
assert send_and_recv(socket, "UNKNOWN shopping\r\n") == "UNKNOWN COMMAND\r\n"
end
# Method to send and receive messages working as a client
defp send_and_recv(socket, command) do
:ok = :gen_tcp.send(socket, command)
{:ok, data} = :gen_tcp.recv(socket, 0, 1000)
data
end
Continuing with chapter 16 to 18
Chapter 16 to 18
Going over:
- Going over structs.
Continuing with:
- Protocols.
- Comprehensions
- Sigils
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google โค๏ธ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.