Building an app with elixir-3
Disclaimer: I’m a beginner in elixir, so this is not an ideal way of doing things in elixir. I’m just writing my experience on learning elixir.
So in the last post we ran some CRUD operation to our database. In this post we’ll write some functions to get latest tweets of different languages via twitter search api. First create a languages table to store all the languages we want to fetch tweets.
iex(1)> LanguageTrends.Query.create_table("languages")
{:ok,
%RethinkDB.Record{data: %{"config_changes" => [%{"new_val" => %{"db" => "language_trends",
"durability" => "hard", "id" => "cf275a3c-6984-47a6-9ad2-a292917024a0",
"indexes" => [], "name" => "languages", "primary_key" => "id",
"shards" => [%{"nonvoting_replicas" => [],
"primary_replica" => "elixir_elixir_local_v5q",
"replicas" => ["elixir_elixir_local_v5q"]}],
"write_acks" => "majority"}, "old_val" => nil}],
"tables_created" => 1}, profile: nil}}
Now lets add some languages with their hashtag to the table
iex(1)> LanguageTrends.Query.insert_document("languages", %{"name" => "elixir", "hashtag" => "elixirlang"}){:ok,
%RethinkDB.Record{data: %{"deleted" => 0, "errors" => 0,
"generated_keys" => ["8499795f-dd1b-4d25-9501-43550f0942b1"],
"inserted" => 1, "replaced" => 0, "skipped" => 0, "unchanged" => 0},
profile: nil}}
Add some more languages like the above. I’ve added few mores and lets see all the languages I’ve added
iex(2)> LanguageTrends.Query.get_document("languages")
{:ok,
%RethinkDB.Collection{data: [%{"hashtag" => "clojure",
"id" => "b08e8940-7c74-4bad-9a8f-e1259ba8ecc5", "name" => "clojure"},
%{"hashtag" => "rustlang", "id" => "759cd575-df54-425d-abc9-116c46517610",
"name" => "rust"},
%{"hashtag" => "scala", "id" => "fc17ad49-928b-4156-9229-82cfa3a85ede",
"name" => "scala"},
%{"hashtag" => "kotlin", "id" => "651a6716-9b53-4caa-91a8-05c9e05f55e4",
"name" => "kotlin"},
%{"hashtag" => "swift", "id" => "0649e075-ae83-48a4-8b21-2cea0e0b2124",
"name" => "swift"},
%{"hashtag" => "haskell", "id" => "d387f0b2-a7c6-489d-8757-a16164ac6742",
"name" => "haskell"},
%{"hashtag" => "golang", "id" => "b1666a91-7a8f-4cd8-8ab4-9cb22c93e02a",
"name" => "go"},
%{"hashtag" => "crystallang", "id" => "4988fb3a-cbae-4086-8fbd-813a3e3ff1fe",
"name" => "crystal"},
%{"hashtag" => "elmlang", "id" => "6bb447b6-3bc4-485b-be4d-3d0ca42cba11",
"name" => "elm"},
%{"hashtag" => "elixirlang", "id" => "8499795f-dd1b-4d25-9501-43550f0942b1",
"name" => "elixir"},
%{"hashtag" => "julialang", "id" => "e744a5f9-055c-41fa-b2ad-473905175a92",
"name" => "julia"], profile: nil}}
Next we’ll create a file twitter.ex
in lib/language_trends
folder where we’ll write all the api call to twitter. We’ll use a library called twittex
to make the api call. Add the library name in mix.exs
file
defp deps do
[
{:rethinkdb, github: "hamiltop/rethinkdb-elixir"},
{:twittex, github: "almightycouch/twittex"},
{:poison, "~> 2.0", override: true}
]
end
I’ve also added poison
library (a json parser) because twittex
and rethinkdb
require different version of poison
which conflicts at compile time, so we added it manually and locked the version with option override
so that other library can’t upgrade it to another version.\.
Now open twitter.ex
file and add this
defmodule LanguageTrends.Twitter do
import Twittex.Client
end
First I defined the module name LanguageTrends.Twitter
and imported Twittex.Client
from twittex
library. Lets write a function for fetching tweets by hashtag
def search_with_hashtag(hashtag, since_id \\ nil, count \\ 100) do
search("#" <> hashtag <> "-filter:retweets", since_id: since_id,
count: count, result_type: "recent")
end
Above function need one required argument and two optional argument. First argument is hashtag
that’ll be used to search tweets. count
is to determine how many tweets we want to retrieve in our api call (Twitter returns max 100 tweets in a single call). And we’ll use since_id
later. Within the function we are calling search function of twittex
library passing four arguments. First we are adding pound #
symbol in front of hashtag
argument and “-filter:retweets” at the end to filter out the retweets. <>
is used for string concatenation in elixir. Fourth argument is result_type
, which is “recent” to fetch only recent tweets. Let’s run this function now
We passed “elixirlang” as hashtag, 0 as since_id and 1 as count to get only 1 recent tweets. Twitter api has returned a lot of data in response. We need two information from this response, one is max_id
and the other is the count of statuses
. So let’s extract those two information
def search_with_hashtag(hashtag, since_id \\ nil, count \\ 100) do
search("#" <> hashtag <> "-filter:retweets", since_id: since_id,
count: count, result_type: "recent")
|> (fn {:ok, x} -> %{"since_id" => get_in(x, ["search_metadata",
"max_id"]), "statuses" => Enum.count(get_in(x, ["statuses"]))}
end).()
end
We are piping a anonymous function to the output of our search method call. Anonymous function in elixir written in following format
fn x, y -> x + y end
Anonymous function starts with fn
keyword followed by arguments, function body comes after ->
symbol and finish with end
keyword. In our code the anonymous function is wrapped in bracket and then we added .()
to invoke the function. The search method return the response in a tuple {:ok, list}
format where second element is the list with all the data. So we passed that to anonymous function in x
variable and we are returning a map from the function. Within the map we are defining first key as “since_id” to store the id of the last tweet and to get that from the response body we’re using get_in
function from Kernel
module. get_in
function takes a collection from which we want to extract value as first argument and a list of the key structure as second argument to get the value of a key. In our response the “max_id” key is under the “search_metadata” key, so we pass a list as ["search_metadata", "max_id"]
to get that value. And the second key is “statuses” where we’ll store the total number of tweets returned to us. The statuses
key in twitter api response is a map which is enumerable or iterable. Elixir has a Enum
module with lots of function to work with collections. Here we are using count
function of Enum
module which returns the number of elements of a collection. We used get_in
function again to get the collection instatuses
key from api response and pass it to count
function. Now let’s run the function now
iex(2)> LanguageTrends.Twitter.search_with_hashtag("elixirlang", 0, 100)
%{"since_id" => 863782766407806976, "statuses" => 100}
We can see it returned a map with two keys we defined earlier and their respective values. We’ll use the since_id
in next api call to get all the tweets posted after the last tweet from this api call so that we don’t get same tweets again.
That’s it for today. In next post we’ll fetch all the languages hashtag from database and call search api to get the tweets count for each of the hashtag.
You can find the source code here.
Happy brewing some elixir.