LoginSignup
5
2

More than 3 years have passed since last update.

[Elixir]SlackのAPIを例にuseするものを自分で作って使ってみる

Last updated at Posted at 2020-06-10

はじめに

  • たとえばPhoenixControllersを書く時に、use HelloWeb, :controllerみたいなことを書きますが、useしているものを自分で書くにはどうしたらいいのでしょうか
  • useしているものを自分で書きたいという思いは、単なる憧れのようなものでした
    • quoteとかみたらそっと閉じていました
  • SlackのAPIをコールすることを通じてこの体験ができましたので書いておきます
  • sapporo.beam #326というほぼ毎週水曜日に「思い思いの場所」で行われる勉強会での成果であります
  • Elixir 1.10.3

初期バージョン

users.list

lib/awesome_app/slack/api/users/list.ex
defmodule AwesomeApp.Slack.Api.Users.List do
  @token Application.get_env(:awesome_app, :slack_token)

  def run do
    get(url(), [])
  end

  def get(url, got_members) do
    url
    |> HTTPoison.get()
    |> handle_response()
    |> Jason.decode!()
    |> next_action(got_members)
  end

  defp handle_response({:ok, %{body: body, status_code: 200}}), do: body

  defp handle_response(_), do: ~s/{"members":[], "response_metadata":{"next_cursor":""}}/

  defp next_action(
         %{"members" => members, "response_metadata" => %{"next_cursor" => ""}},
         got_members
       ) do
    got_members ++ members
  end

  defp next_action(
         %{"members" => members, "response_metadata" => %{"next_cursor" => next_cursor}},
         got_members
       ) do
    url(next_cursor)
    |> get(got_members ++ members)
  end

  defp url do
    "https://slack.com/api/users.list?token=#{@token}"
  end

  defp url(next_cursor) do
    "#{url()}&cursor=#{next_cursor}"
  end
end

conversations.list

lib/awesome_app/slack/api/conversations/list.ex
defmodule AwesomeApp.Slack.Api.Conversations.List do
  @token Application.get_env(:awesome_app, :slack_token)

  def run do
    get(url(), [])
  end

  def get(url, got_channels) do
    url
    |> HTTPoison.get()
    |> handle_response()
    |> Jason.decode!()
    |> next_action(got_channels)
  end

  defp handle_response({:ok, %{body: body, status_code: 200}}), do: body

  defp handle_response(_), do: ~s/{"channels":[], "response_metadata":{"next_cursor":""}}/

  defp next_action(
         %{"channels" => channels, "response_metadata" => %{"next_cursor" => ""}},
         got_channels
       ) do
    got_channels ++ channels
  end

  defp next_action(
         %{"channels" => channels, "response_metadata" => %{"next_cursor" => next_cursor}},
         got_channels
       ) do
    url(next_cursor)
    |> get(got_channels ++ channels)
  end

  defp url do
    "https://slack.com/api/conversations.list?token=#{@token}"
  end

  defp url(next_cursor) do
    "#{url()}&cursor=#{next_cursor}"
  end
end
  • 構造は全く同じです
  • membersという字面とchannelsという字面の違いとAPIのパスが違うくらいです

改善

  • defmacroで共通部分を書きます
    • ざっくりいうとquoteの中に共通部分を押し込みます
      • ほぼ大部分は初期バージョンのコピペでいけます
    • オプションで受け取った違いの部分は、unquoteで押し込みます
  • use するときに違う部分をオプションで渡します
lib/awesome_app/slack/api.ex
defmodule AwesomeApp.Slack.Api do
  defmacro __using__(opts) do
    key = Keyword.get(opts, :key)
    method = Keyword.get(opts, :method)

    quote do
      @token Application.get_env(:awesome_app, :slack_token)

      def run do
        get(url(), [])
      end

      def get(url, got_values) do
        url
        |> HTTPoison.get()
        |> handle_response()
        |> Jason.decode!()
        |> next_action(got_values)
      end

      defp handle_response({:ok, %{body: body, status_code: 200}}), do: body

      defp handle_response(_),
        do: ~s/{"#{unquote(key)}":[], "response_metadata":{"next_cursor":""}}/

      defp next_action(
             %{unquote(key) => values, "response_metadata" => %{"next_cursor" => ""}},
             got_values
           ) do
        got_values ++ values
      end

      defp next_action(
             %{unquote(key) => values, "response_metadata" => %{"next_cursor" => next_cursor}},
             got_values
           ) do
        url(next_cursor)
        |> get(got_values ++ values)
      end

      defp url do
        "https://slack.com/api/#{unquote(method)}?token=#{@token}"
      end

      defp url(next_cursor) do
        "#{url()}&cursor=#{next_cursor}"
      end
    end
  end
end
lib/awesome_app/slack/api/users/list.ex
defmodule AwesomeApp.Slack.Api.Users.List do
  use AwesomeApp.Slack.Api, key: "members", method: "users.list"
end
lib/awesome_app/slack/api/conversations/list.ex
defmodule AwesomeApp.Slack.Api.Conversations.List do
  use AwesomeApp.Slack.Api, key: "channels", method: "conversations.list"
end

Wrapping Up

  • defmacro
  • use
  • 共通部分をくくりだすには↑↑↑を使えばよいのであります
  • We are the Alchemists, my friends!
  • Enjoy!
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2