Эликсир, используя функцию из другого модуля - PullRequest
0 голосов
/ 30 ноября 2018

Я чрезвычайно новичок в программировании и в эликсире.Так что я очень рад узнать как можно больше.Но у меня проблема.Я смотрю, как использовать мои функции в другом модуле.Я строю веб-сервер, который хранит карты ключ-значение в памяти.Чтобы сохранить карты временными, я решил использовать агента.Вот часть моего кода:

defmodule Storage do
  use Agent

  def start_link do
    Agent.start_link(fn -> %{} end, name: :tmp_storage)
  end

  def set(key, value) do
    Agent.update(:tmp_storage, fn map -> Map.put_new(map, key, value) end)
  end

  def get(key) do
    Agent.get(:tmp_storage, fn map -> Map.get(map, key) end)
  end
end

Итак, я пытаюсь разместить эти функции на маршрутах веб-сервера:

defmodule Storage_router do
  use Plug.Router
  use Plug.Debugger
  require Logger
  plug(Plug.Logger, log: :debug)
  plug(:match)
  plug(:dispatch)

  post "/storage/set" do
    with {:ok, _} <- Storage.set(key, value) do
      send_resp(conn, 200, "getting the value")
    else
      _ ->
        send_resp(conn, 404, "nothing")
    end
  end
end

И я получаю:

warning: variable "key" does not exist and is being expanded to "key()", please use parentheses to remove the ambiguity or change the variable name lib/storage_route.ex:12

warning: variable "value" does not exist and is being expanded to "value()", please use parentheses to remove the ambiguity or change the variable name lib/storage_route.ex:12

ищу любые предложения \ help

Ответы [ 3 ]

0 голосов
/ 30 ноября 2018

Я чрезвычайно новичок в программировании и в эликсире.

Я не думаю, что было бы разумно начинать обучение программированию с эликсира.Я бы начал с python или ruby, а затем через год или два я бы попробовал эликсир.

Первое, что вам нужно выучить, это как разместить код.Ищите в Google информацию о том, как разместить код в stackoverflow.Затем вы должны выровнять все отступы.Вы используете текстовый редактор компьютерного программирования?Если нет, то вы должны получить один.Есть много бесплатных.Я использую vim, который устанавливается на Unix как компьютеры.Вы можете узнать, как использовать vim, набрав vimtutor в окне терминала.

Далее, у вас есть синтаксическая ошибка в вашем коде:

 Agent.start_link(fn -> %{} end, name: :tmp_storage
    end)  

Это должно быть:

 Agent.start_link(fn -> %{} end, name: :tmp_storage)

Предупреждение, которое вы получили, заключается в том, что ваш код пытается сделать эквивалент:

def show do
   IO.puts x
end

Elixir и любой другой, читающий этот код, спросит: "Что за хрень?"Переменной x никогда нигде не назначается значение, и поэтому переменная x не существует, и вы не можете вывести то, что не существует.Вы делаете то же самое здесь:

   with {:ok, _} <- Storage.set(key, value) do
     send_resp(conn, 200, "getting the value")
   else
     _->
      send_resp(conn, 404, "nothing")
   end

Вы вызываете функцию:

Storage.set(key, value)

, но переменным key и value никогда не присваивалось значение, и эликсир (икто-нибудь еще, читающий этот код), задается вопросом: «Какого черта ключ и значение?»

Так работают функции:

b.ex:

defmodule MyFuncs do
  def show(x, y) do
    IO.puts x
    IO.puts y
  end
end

defmodule MyWeb do
  def go do
    height = 10
    width = 20

    MyFuncs.show(height, width)
  end
end

В iex:

~/elixir_programs$ iex b.ex
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> MyWeb.go
10
20
:ok
iex(2)> 

Итак, в вашем коде вам нужно написать что-то вроде этого:

post "/storage/set" do
  key = "hello"
  value = 10

  with {:ok, _} <- Storage.set(key, value) do
    send_resp(conn, 200, "Server saved the key and value.")
  else
    _->
      send_resp(conn, 404, "nothing")
  end
end

Однако это будет хранить один и тот же ключ / значение для каждого запроса на публикацию.Предположительно, вы хотите хранить все, что отправлено в теле почтового запроса.Знаете ли вы разницу между запросом на получение и запросом по почте?Запрос get прикрепляет данные в конец URL, а запрос post отправляет данные в «теле запроса», поэтому существуют разные процедуры для извлечения данных в зависимости от типа запроса.

Какой учебник ты читаешь?В этом руководстве: https://www.jungledisk.com/blog/2018/03/19/tutorial-a-simple-http-server-in-elixir/, показано, как извлечь данные из тела почтового запроса.Данные в теле почтового запроса - это просто строка.Если строка в формате JSON, вы можете преобразовать строку в карту эликсира, используя Poison.decode!(), что позволит вам легко извлечь значения, связанные с интересующими вас ключами. Например:

  post "/storage/set" do
    {:ok, body_string, conn} = read_body(conn)
    body_map = Poison.decode!(body_string)

    IO.inspect(body_map) #This outputs to terminal window where server is running 

    message = get_in(body_map, ["message"])    
    send_resp(
      conn, 
      201,
      "Server received: #{message}\n"
    )
  end

Затем вы можете использовать следующую команду curl в другом окне терминала для отправки запроса на публикацию по этому маршруту:

$ curl -v -H 'Content-Type: application/json' "http://localhost:8085/storage/set" -d '{"message": "hello world" }'

(-v => подробный вывод, -H => заголовок запроса, -d => data)

Теперь, основываясь на том, что я сказал неправильно с вашим кодом выше, вы должны задаться вопросом об этой строке:

{:ok, body_string, conn} = read_body(conn)

Эта строка вызывает:

read_body(conn)

, но переменной conn нигде не присвоено значение.Тем не менее, Plug незаметно создает переменную conn и присваивает ей значение.

Вот полный пример использования агента для хранения данных запроса поста (в соответствии с указанным выше учебником):

simple_server
   config/
   lib/
       simple_server/
           application.ex
           router.ex
           storage.ex
   test/

Соглашение с эликсиром - иметь каталог в каталоге lib/ с тем же именем, что и у вашего проекта, в данном случае это будет simple_server, а затем вы даете указанным вами модулям имена, которые отражают структуру каталогов.Таким образом, в router.ex вы определяете модуль с именем SimpleServer.Router, а в storage.ex вы определяете модуль с именем SimpleServer.Storage.Тем не менее, . в имени модуля ничего не значит для elixir, поэтому вы не получите сообщение об ошибке, если решите назвать свой модуль F.R.O.G.S в файле lib/rocks.ex - и ваш код будет работать нормально.

router.ex:

defmodule SimpleServer.Router do
  use Plug.Router
  use Plug.Debugger

  require Logger

  plug(Plug.Logger, log: :debug)
  plug(:match)
  plug(:dispatch)

  get "/storage/:key" do
    resp_msg = case SimpleServer.Storage.get(key) do
      nil -> "The key #{key} doesn't exist!\n"
      val -> "The key #{key} has value #{val}.\n"
    end

    send_resp(conn, 200, resp_msg)
  end

  post "/storage/set" do
    {:ok, body_string, conn} = read_body(conn)
    body_map = Poison.decode!(body_string)

    IO.inspect(body_map) #This outputs to terminal window where server is running 

    Enum.each(
      body_map, 
      fn {key, val} -> SimpleServer.Storage.set(key,val) end
    )

    send_resp(
      conn, 
      201,
      "Server stored all key-value pairs\n"
    )
  end

  match _ do
    send_resp(conn, 404, "not found")
  end


end

Первое, на что нужно обратить внимание в приведенном выше коде, это маршрут:

get "/storage/:key" do

, который будет соответствовать пути, например:

/storage/x 

и plug создаст переменную с именем key и присвоит ей значение «x», например:

 key = "x"

Также обратите внимание, что при вызове функции:

width = 10
height = 20
show(width, height)

эликсир просматривает определение функции:

def show(x, y) do
  IO.puts x
  IO.puts y
end

и сопоставляет вызов функции с определением следующим образом:

    show(width, height)
          |       |
          V       V
def show( x    ,  y) do
  ...
end

и выполняет назначения:

 x = width
 y = height

Затем внутри функции вы можете использовать переменные x и y.В этой строке:

    Enum.each(
      body_map, 

      #  | | | | |
      #  V V V V V

      fn {key, val} -> SimpleServer.Storage.set(key,val) end
    )

Elixir будет вызывать анонимную функцию, передавая значения для key и val, например:

func("x", "10")

Следовательно, в теле анонимногоВы можете использовать переменные key и val:

SimpleServer.Storage.set(key,val)

, поскольку переменным key и val уже будут присвоены значения.

storage.ex:

defmodule SimpleServer.Storage do
  use Agent

  def start_link(_args) do  #<*** Note the change here
    Agent.start_link(fn -> %{} end, name: :tmp_storage)
  end

  def set(key, value) do
    Agent.update(
      :tmp_storage, 
      fn(map) -> Map.put_new(map, key, value) end
    )
  end

  def get(key) do
    Agent.get(
      :tmp_storage, 
      fn(map) -> Map.get(map, key) end
    )
  end

end

application.ex:

defmodule SimpleServer.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do
    # List all child processes to be supervised
    children = [
      Plug.Adapters.Cowboy.child_spec(scheme: :http, plug: SimpleServer.Router, options: [port: 8085]),

      {SimpleServer.Storage, []}
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: SimpleServer.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

mix.exs:

defmodule SimpleServer.MixProject do
  use Mix.Project

  def project do
    [
      app: :simple_server,
      version: "0.1.0",
      elixir: "~> 1.6",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger],
      mod: {SimpleServer.Application, []}
    ]
  end


  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
        {:poison, "~> 4.0"},
        {:plug_cowboy, "~> 2.0"}

      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
    ]
  end
end

Обратите внимание: если вы используете зависимости и версии, указанные в руководстве, вы будетеполучите несколько предупреждений, включая предупреждение:

~/elixir_programs/simple_server$ iex -S mix
...
...

12:48:57.767 [warn]  Setting Ranch options together 
with socket options is deprecated. Please use the new
map syntax that allows specifying socket options 
separately from other options.

..., которое является проблемой с Plug.Вот зависимости и версии, которые я использовал, чтобы избавиться от всех предупреждений:

   {:poison, "~> 4.0"},
   {:plug_cowboy, "~> 2.0"}

Кроме того, когда вы указываете приложение в качестве зависимости, вам больше не нужно вводить его в список :extra_applications,Elixir автоматически запустит все приложения, перечисленные как зависимости, перед запуском приложения.См. : приложения v.: Extra_applications .

После запуска сервера вы можете использовать другое окно терминала для отправки почтового запроса с помощью curl (или вы можете использовать другую программу):

~$  curl -v -H 'Content-Type: application/json' "http://localhost:8085/storage/set" -d '{"x": "10", "y": "20" }

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> POST /storage/set HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 23
> 
* upload completely sent off: 23 out of 23 bytes
< HTTP/1.1 201 Created
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:23 GMT
< content-length: 34
< cache-control: max-age=0, private, must-revalidate
< 
Server stored all key-value pairs
* Connection #0 to host localhost left intact

>строки - это запрос, а < - ответ.Также проверьте вывод в окне терминала, где работает сервер.

~$  curl -v http://localhost:8085/storage/z

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> GET /storage/z HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:30 GMT
< content-length: 25
< cache-control: max-age=0, private, must-revalidate
< 
The key z doesn't exist!
* Connection #0 to host localhost left intact

.

~$  curl -v http://localhost:8085/storage/x

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> GET /storage/x HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:37 GMT
< content-length: 24
< cache-control: max-age=0, private, must-revalidate
< 
The key x has value 10.
* Connection #0 to host localhost left intact
0 голосов
/ 30 ноября 2018

Вам необходимо извлечь параметры ключа / значения из вашего %Plug.Conn{} объекта (conn).Переменные ключ / значение еще не были определены в рамках вашего маршрута.Объект conn доступен только потому, что он внедряется макросом post, предоставляемым Plug.

Я не совсем понимаю, какие типы запросов вы отправляете маршрутизатору, но я предполагаю, что этоJSON в качестве примера.Вы можете вручную проанализировать тело в вашем соединении, выполнив что-то вроде:

with {:ok, raw_body} <- Plug.Conn.read_body(conn),
     {:ok, body} <- Poison.decode(raw_body) do
  key = Map.get(body, "key")
  value = map.get(body, "value")
  # ... other logic
end

Тем не менее, проект Plug предоставляет удобный удобный плагин для общего анализа запросов: Plug.Парсеры .

Чтобы реализовать это в вашем маршрутизаторе, вам просто нужно добавить плагин в верхней части вашего маршрутизатора (под Plug.Logger, я думаю):

plug Plug.Parsers, 
  parsers: [:urlencoded, :json]
  json_decoder: Poison,
  pass: ["text/*", "application/json"]

Часть :urlencoded будет анализировать параметры вашего запроса, а часть :json - синтаксический анализ тела запроса.

Затем ниже в вашем маршруте вы можете получить параметры ключ / значение из вашего объекта conn включ :params выглядит примерно так:

%{params: params} = conn
key = Map.get(params, "key")
value = Map.get(params, "value")

Кроме того, я должен отметить, что лучшим JSON-декодером на данный момент является Jason , который в основном является заменой Poison, нобыстрее.

В любом случае, чтение hexdocs действительно помогает разобраться в этом, и у проекта Plug есть отличная документация.Я думаю, что Elixir - отличный язык, с которого можно начать программирование (хотя важно также изучать объектно-ориентированные парадигмы).Удачного кодирования!

0 голосов
/ 30 ноября 2018

Я не совсем уверен, что вы пытаетесь выполнить, но ошибка говорит вам, что key и value, которые передаются оператору with маршрутизатора, не определены.Elixir думает, что вы пытаетесь вызвать функцию с этими аргументами, потому что они не «привязаны» к значению.Вот почему вы видите warning: variable "value" does not exist and is being expanded to "value()"

Полагаю, на самом деле это не ответ, а, возможно, больше объяснение ошибки, которую вы видите.

...