Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Build modern applications with Elixir lang

Build modern applications with Elixir lang

Build modern applications with Elixir language. Presentation prepared by Bartosz Górka - Poznań Elixir Meetup #8.

Bartosz Górka

June 07, 2018
Tweet

More Decks by Bartosz Górka

Other Decks in Technology

Transcript

  1. iex(1)> "Hello World" "Hello World" iex(2)> "Today Poznan Elixir Meetup

    #8!" "Today Poznan Elixir Meetup #8!" iex(3)> v "Today Poznan Elixir Meetup #8!" iex(4)> 40 + 4 44 iex(5)> v 1 "Hello World" iex(6)> v(4) 44 iex(7)> v(-4) "Today Poznan Elixir Meetup #8!”
  2. iex(1)> i(%{key: "value"}) Term %{key: "value"} Data type Map Reference

    modules Map Implemented protocols Inspect, Ecto.DataType, Phoenix.Param, Poison.Encoder, IEx.Info, JOSE.Poison.LexicalEncoder, Timex.Protocol, Joken.Claims, Collectable, Enumerable, Bamboo.Formatter, Poison.Decoder, Plug.Exception iex(1)> i(%{key: "value"}) Term %{key: "value"} Data type Map Reference modules Map Implemented protocols IEx.Info, Inspect, Collectable, Enumerable
  3. iex(1)> h(Enum) Enum Provides a set of algorithms that enumerate

    over enumerables according to the Enumerable protocol. iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end) [2, 4, 6] Some particular types, like maps, yield a specific format on enumeration. For example, the argument is always a {key, value} tuple for maps: iex> map = %{a: 1, b: 2} iex> Enum.map(map, fn {k, v} -> {k, v * 2} end) [a: 2, b: 4] Note that the functions in the Enum module are eager: they always start the enumeration of the given enumerable. The Stream module allows lazy enumeration of enumerables and provides infinite streams. Since the majority of the functions in Enum enumerate the whole enumerable and return a list as result, infinite streams need to be carefully used with such functions, as they can potentially run forever. For example: Enum.each Stream.cycle([1, 2, 3]), &IO.puts(&1)
  4. iex(2)> h Enum.min_max def min_max(enumerable, empty_fallback \\ fn -> raise(Enum.EmptyError)

    end) @spec min_max(t(), (() -> empty_result)) :: {element(), element()} | empty_result | no_return() when empty_result: any() Returns a tuple with the minimal and the maximal elements in the enumerable according to Erlang's term ordering. If multiple elements are considered maximal or minimal, the first one that was found is returned. Calls the provided empty_fallback function and returns its value if enumerable is empty. The default empty_fallback raises Enum.EmptyError. ## Examples iex> Enum.min_max([2, 3, 1]) {1, 3} iex> Enum.min_max([], fn -> {nil, nil} end) {nil, nil}
  5. def valid_checksum?(cc) when is_integer(cc) do sum = cc |> Integer.digits()

    |> Enum.reverse() |> Enum.chunk(2, 2, [0]) |> Enum.reduce(0, fn [odd, even], sum -> Enum.sum([sum, odd | Integer.digits(even * 2)]) end) |> rem(10) sum == 0 end
  6. def valid_checksum?(cc) when is_integer(cc) do cc |> Integer.digits() |> Enum.reverse()

    |> Enum.chunk(2, 2, [0]) |> Enum.reduce(0, fn [odd, even], sum -> Enum.sum([sum, odd | Integer.digits(even * 2)]) end) |> rem(10) |> Kernel.==(0) end
  7. def valid_checksum?(cc) when is_integer(cc) do cc |> Integer.digits() |> Enum.reverse()

    |> Enum.chunk(2, 2, [0]) |> Enum.reduce(0, fn [odd, even], sum -> Enum.sum([sum, odd | Integer.digits(even * 2)]) end) |> rem(10) |> Kernel.==(0) end def valid_checksum?(cc) when is_integer(cc) do sum = cc |> Integer.digits() |> Enum.reverse() |> Enum.chunk(2, 2, [0]) |> Enum.reduce(0, fn [odd, even], sum -> Enum.sum([sum, odd | Integer.digits(even * 2)]) end) |> rem(10) sum == 0 end
  8. defmodule MyProject.Structs.InvoiceDetails do @enforce_keys [ :id, :signed_url, :expires_at, :size, :reference_number,

    :number_of_pages, :requirements ] defstruct [ :id, :signed_url, :expires_at, :size, :reference_number, :number_of_pages, requirements: [] ] end
  9. case item[:secret][:details][:id] do nil -> :error identifier -> identifier end

    case get_in(item,[:secret, :details, :id]) do nil -> :error identifier -> identifier end Find identifier: %{secret: %{details: %{id: identifier}}} You can fetch structure: item = MyRepo.Items.find_by_resource("book") case item.secret do nil -> :error secret -> case secret.details do nil -> :error details -> case details.id do nil -> :error identifier -> identifier end end end
  10. Installation Add package to deps() {:credo, "~> 0.9”, only: [:dev,

    :test], runtime: false} Prepare new alias to enable strict mode check defp aliases do [ "credo": ["credo —strict"] ] end mix deps.get Update mix.lock file - download package and append to project * You can adjust settings to your preferences - configure .credo file
  11. Consistency These checks take a look at your code and

    ensure a consistent coding style. Using tabs or spaces? Both is fine, just don't mix them or Credo will tell you. Readability Readability checks do not concern themselves with the technical correctness of your code, but how easy it is to digest. Refactoring Opportunities The Refactor checks show you opportunities to avoid future problems and technical debt. Software Design While refactor checks show you possible problems, these checks try to highlight possibilities, like - potentially intended - duplicated code or TODO: and FIXME annotations. Warnings These checks warn you about things that are potentially dangerous, like a missed call to IEx.pry or a call to String.downcase without saving the result.
  12. Checking 117 source files (this might take a while) ...

    Code Readability ┃ ┃ [R] → Modules should have a @moduledoc tag. ┃ apps/plant_monitor/lib/plant_monitor/oauth/permissions_service.ex:1:11 #(PlantMonitor.OAuth.PermissionService Consistency ┃ ┃ [C] ↗ There is no whitespace around parentheses/brackets most of the time, but ┃ here there is. ┃ apps/plant_monitor_web/lib/plant_monitor_web/controllers/api/authorized_user_controller.ex:21 #(PlantMonitorWeb.API.AuthorizedUserController.index) Please report incorrect results: https://github.com/rrrene/credo/issues Analysis took 3.5 seconds (1.5s to load, 2.0s running checks) 276 mods/funs, found 1 consistency issue, 1 code readability issue.
  13. Installation Add package to deps() {:dialyxir, "~> 0.5”, only: [:dev,

    :test], runtime: false} Dialyzer configurations as private function defp dialyzer do [ flags: [:error_handling, :race_conditions, :underspecs, :unmatched_returns], plt_add_apps: [:ex_unit, :mix] ] end mix deps.get Update mix.lock file - download package and append to project Update project() - add dialyzer configuration dialyzer: dialyzer()
  14. mix dialyzer Checking PLT... [:asn1, :base64url, :bcrypt_elixir, :certifi, :combine, :comeonin,

    :compiler, :conductor, :connection, :cors_plug, :cowboy, :cowlib, :crypto, :db_connection, :decimal, :ecto, :eex, :elixir, :ex_unit, :file_system, :gettext, :hackney, :idna, :joken, :jose, :kernel, :logger, :metrics, :mime, :mimerl, :mix, :phoenix, :phoenix_html, :phoenix_live_reload, :phoenix_pubsub, :phoenix_swagger, :plant_monitor, :plug, :poison, :poolboy, :postgrex, :public_key, :ranch, :rsa_ex, :runtime_tools, :sentry, :ssl, :ssl_verify_fun, :stdlib, :timex, ...] Finding suitable PLTs Looking up modules in dialyxir_erlang-20.1_elixir-1.6.1_deps-dev.plt Looking up modules in dialyxir_erlang-20.1_elixir-1.6.1.plt Finding applications for dialyxir_erlang-20.1_elixir-1.6.1.plt Finding modules for dialyxir_erlang-20.1_elixir-1.6.1.plt Checking 391 modules in dialyxir_erlang-20.1_elixir-1.6.1.plt Finding applications for dialyxir_erlang-20.1_elixir-1.6.1_deps-dev.plt Finding modules for dialyxir_erlang-20.1_elixir-1.6.1_deps-dev.plt Copying dialyxir_erlang-20.1_elixir-1.6.1.plt to dialyxir_erlang-20.1_elixir-1.6.1_deps-dev.plt Looking up modules in dialyxir_erlang-20.1_elixir-1.6.1_deps-dev.plt Checking 391 modules in dialyxir_erlang-20.1_elixir-1.6.1_deps-dev.plt Adding 1126 modules to dialyxir_erlang-20.1_elixir-1.6.1_deps-dev.plt Starting Dialyzer dialyzer args: [ check_plt: false, init_plt: '_build/dev/dialyxir_erlang-20.1_elixir-1.6.1_deps-dev.plt', files_rec: ['_build/dev/lib/plant_monitor_web/ebin', '_build/dev/lib/plant_monitor/ebin'], warnings: [:error_handling, :race_conditions, :underspecs, :unmatched_returns, :unknown] ] done in 1m0.57s done (passed successfully)
  15. @doc """ Fetch user by email ## Parameters email ::

    String.t() ## Returns PlantMonitor.User -> user found nil -> no user found """ @type fetch_response :: %PlantMonitor.User{} | nil @spec fetch_user_by_email(email :: String.t()) :: fetch_response def fetch_user_by_email(email) do User |> where([u], u.email == ^email) |> Repo.one() end
  16. @doc """ Fetch user by email ## Parameters email ::

    String.t() ## Returns PlantMonitor.User -> user found nil -> no user found """ @type fetch_response :: %PlantMonitor.Data{} | nil @spec fetch_user_by_email(email :: String.t()) :: fetch_response def fetch_user_by_email(email) do User |> where([u], u.email == ^email) |> Repo.one() end
  17. @doc """ Fetch user by email ## Parameters email ::

    String.t() ## Returns PlantMonitor.User -> user found nil -> no user found """ @type fetch_response :: %PlantMonitor.Data{} | nil @spec fetch_user_by_email(email :: String.t()) :: fetch_response def fetch_user_by_email(email) do User |> where([u], u.email == ^email) |> Repo.one() end apps/plant_monitor/lib/plant_monitor/user/service.ex:84: The pattern Vuser@1 = #{'id':=Vuser_id@1, 'permissions':=Vpermissions@1, '__struct__':='Elixir.PlantMonitor.User'} can never match the type 'nil' | #{'__meta__':=_, '__struct__':='Elixir.PlantMonitor.Data', 'air_humidity':=_, 'air_temperature':=_, 'device_id':=_, 'id':=_, 'inserted_at':=_, 'liquid_level_millimeters':=_, 'soil_humidity':=_, 'updated_at':=_} apps/plant_monitor/lib/plant_monitor/user/service.ex:94: Function validate_password/2 will never be called apps/plant_monitor_web/lib/plant_monitor_web/controllers/api/session_controller.ex:26: The pattern {'ok', Vtoken_details@1} can never match the type {'error','invalid_credentials'} done (warnings were emitted)
  18. defmodule LousyCalculator do @moduledoc """ Just a number followed by

    a string. """ @type number_with_remark :: {number, String.t()} @spec add(number, number) :: number_with_remark def add(x, y), do: {x + y, "You need a calculator to do that?"} @spec multiply(number, number) :: number_with_remark def multiply(x, y), do: {x * y, "It is like addition on steroids."} end Typespecs and behaviours
  19. Installation Add package to deps() {:mockery, "~> 2.1”, runtime: false}

    mix deps.get Update mix.lock file - download package and append to project
  20. def send_invitation(invitation, profile, link) do with {:ok, _body} <- @sms_adapter.send(invitation,

    profile, link) do {:ok, :sent} else _ -> {:error, :sending_error} end end @sms_adapter Mockery.of("Project.SmsAdapter”) import Mockery mock(Project.SmsAdapter, :send, {:error, %{status: 400, body: %{}}}) mock(Project.SmsAdapter, :send, {:ok, %{}}) TEST CODE
  21. @pdf_generator Mockery.of("ProjectPDF”) import Mockery mock(ProjectPDF, :invoice_pdf, {:ok, "invoice.pdf"}) TEST CODE

    import Mockery.Assertions assert_called ProjectPDF, :invoice_pdf {:ok, pdf_path} <- @pdf_generator.invoice_pdf(assigns) mock(ProjectPDF, :invoice_pdf, {:error, :generator_error})
  22. References Slide 1: Image by asoggetti from https://unsplash.com/photos/gdE-5Oui1Y0 Credo: https://github.com/rrrene/credo

    Dialyzer: https://github.com/jeremyjh/dialyxir Mockery: https://github.com/appunite/mockery Typespec: https://hexdocs.pm/elixir/typespecs.html Behavior: https://elixir-lang.org/getting-started/typespecs-and-behaviours.html Awesome Elixir: https://github.com/h4cc/awesome-elixir My GitHub repo: https://github.com/bartoszgorka