Elixir, using function from another module

  • A+
Category:Languages

I am extremly new to the programming and to the elixir. So I am very exited to learn as much as i can. But ive got a trouble. I looking the way how to use my functions in another module. I am building the web-server wich stores the key-value maps in the memory. To keep the maps temporary Ive decided to use Agent. Here is the part of my code:

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 

So I m trying to put this functions to the routes of the web server:

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 

And i recieve:

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

looking for any suggestions/help

 


I am extremly new to the programming and to the elixir.

I do not think it is wise to begin learning programming with elixir. I would start with python or ruby, and then after a year or two then I would try elixir.

The first thing you need to learn is how to post code. Search google for how to post code on stackoverflow. Then, you have to get your indenting all lined up. Are you using a computer programming text editor? If not, then you have to get one. There are many free ones. I use vim, which comes installed on Unix like computers. You can learn how to use vim by typing vimtutor in a terminal window.

Next, you have a syntax error in your code:

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

That should be:

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

The warning you got is because your code tries to do the equivalent of:

def show do    IO.puts x end 

Elixir and anyone else reading that code would ask, "What the heck is x?" The variable x is never assigned a value anywhere, and therefore the variable x does not exist, and you cannot output something that is non-existent. You do the same thing here:

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

You call the function:

Storage.set(key, value) 

but the variables key and value were never assigned a value, and elixir (and anyone else reading that code) wonders, "What the heck are key and value?"

This is the way functions work:

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 

In 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)>  

So, in your code you need to write something like this:

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 

However, that will store the same key/value for every post request. Presumably, you want to store whatever is sent in the body of the post request. Do you know the difference between a get request and a post request? A get request tacks data onto the end of the url, while a post request sends the data in the "body of the request", so there are different procedures for extracting the data depending on the type of the request.

What tutorial are you reading? This tutorial: https://www.jungledisk.com/blog/2018/03/19/tutorial-a-simple-http-server-in-elixir/, shows you how to extract the data from the body of a post request. The data in the body of a post request is just a string. If the string is in JSON format, then you can convert the string into an elixir map using Poison.decode!(), which will allow you to easily extract the values associated with the keys that you are interested in. For example:

  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 

Then you can use the following curl command in another terminal window to send a post request to that route:

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

(-v => verbose output, -H => request header, -d => data)

Now, based on what I said was wrong with your code above, you should be wondering about this line:

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

That line calls:

read_body(conn) 

but the variable conn is not assigned a value anywhere. However, Plug invisibly creates the conn variable and assigns a value to it.

Here is a complete example using Agent to store post request data (following the tutorial I linked above):

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

An elixir convention is to have a directory in the lib/ directory with the same name as your project, in this case that would be simple_server, then you give the modules you define names that reflect the directory structure. So, in router.ex you would define a module named SimpleServer.Router and in storage.ex you would define a module named SimpleServer.Storage. However, the . in a module name means nothing special to elixir, so you will not get an error if you decide to name your module F.R.O.G.S in the file lib/rocks.ex--and your code will work just fine.

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 

The first thing to note in the code above is the route:

get "/storage/:key" do 

That will match a path like:

/storage/x  

and plug will create a variable named key and assign it the value "x", like this:

 key = "x" 

Also, note that when you call a function:

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

elixir looks at the function definition:

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

and matches the function call to the def like this:

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

and performs the assignments:

 x = width  y = height 

Then, inside the function you can use the x and y variables. In this line:

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

Elixir will call the anonymous function passing values for key and val, like this:

func("x", "10") 

Therefore, in the body of the anonymous function you can use the variables key and val:

SimpleServer.Storage.set(key,val) 

because the variables key and val will already have been assigned values.

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 

Note, if you use the dependencies and versions specified in the tutorial you will get some warnings, including the warning:

~/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. 

...which is an issue with Plug. Here are the dependencies and versions that I used to get rid of all the warnings:

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

Also, when you list an application as a dependency, you no longer have to enter it in the :extra_applications list. Elixir will automatically start all the applications listed as dependencies before starting your application. See :applications v. :extra_applications.

Once the server has started, you can use another terminal window to send a post request with curl (or you can use some other program):

~$  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 

The > lines are the request, and the < lines are the response. Also, check the output in the terminal window where the server is running.

~$  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 

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: