Elixir Phoenix Cache

An implementation with ets.

Let’s start with implementation

defmodule SimpleCache do
  @table :simple_cache

  def init(_) do
    :ets.new(@table, [
      :set,
      :named_table,
      :public,
      read_concurrency: true,
      write_concurrency: true
    ])

    {:ok, %{}}
  end

  def start_link do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  def fetch(key, expires_in_seconds, fun) do
    case lookup(key) do
      {:hit, value} ->
        value

      :miss ->
        value = fun.()
        put(key, expires_in_seconds, value)
        value
    end
  end

  defp lookup(key) do
    case :ets.lookup(@table, key) do
      [{^key, expires_at, value}] ->
        case now < expires_at do
          true -> {:hit, value}
          false -> :miss
        end

      _ ->
        :miss
    end
  end

  defp put(key, expires_in_seconds, value) do
    expires_at = now + expires_in_seconds
    :ets.insert(@table, {key, expires_at, value})
  end

  defp now do
    :erlang.system_time(:seconds)
  end
end


Update application.ex

 def start(_type, _args) do
    import Supervisor.Spec

    children = [
      supervisor(SimpleCache, [])
    ]
    opts = [strategy: :one_for_one, name: Supervisor]
    Supervisor.start_link(children, opts)
  end

Finally, use it

    cache_for_seconds = 60
    key = 'key'

    SimpleCache.fetch(key, cache_for_seconds, fn ->
      {:ok, some_expensive_operation}
    end)

Relavent links:
https://stackoverflow.com/questions/35218738/caching-expensive-computation-in-elixir
https://dockyard.com/blog/2017/05/19/optimizing-elixir-and-phoenix-with-ets

Setting up VS Code with Rails, Elixir, JavaScript

Let’s make sure we can start VS Code from the terminal:

Command + Shift + P
Type Shell
Select Command : Install code in PATH

Extensions

Rails

JavaScript

Git

Elixir

Other stuff

Personal Settings

"editor.formatOnSave": true,
  "editor.fontLigatures": true,
  "editor.fontFamily": "FiraCode-Retina",
  "editor.fontSize": 18,
  "editor.renderIndentGuides": true,
  "files.exclude": {
    "**/.git": true,
    "**/node_modules": true,
    "**/bower_components": true,
    "**/tmp": true,
    "tmp/**": true,
    "**/vendor": true,
    "vendor": true,
    ".bundle": true,
    ".github": true,
    ".sass-cache": true,
    "features/reports": true
  },

  "editor.tabSize": 2,
  "prettier.singleQuote": true,
  "workbench.colorTheme": "Monokai",
  "window.zoomLevel": 0,
  "editor.renderWhitespace": "boundary",
  "editor.renderControlCharacters": true,

  "ruby.lint": {
    "rubocop": true,
    "ruby": true,
    "fasterer": true,
    "reek": false,
    "ruby-lint": false
  },
  "editor.quickSuggestions": {
    "strings": true
  },

  "cucumberautocomplete.steps": [
    "features/step_definitions/*.rb",
    "features/step_definitions/**/*.rb",
    "features/step_definitions/**/**/*.rb"
  ],
  "cucumberautocomplete.syncfeatures": "features/*feature"

Some common exclusions for .solagraph.yml (can place it in the root of your project)

---
include:
- "app/**/*.rb"
- "lib/**/*.rb"
- "engines/engine_name/app/**/*.rb"
- "engines/engine_name/lib/**/*.rb"
- "config/**/*.rb"
exclude:
- app/javascript/**/*
- node_modules/**/**
- spec/**/*
- test/**/*
- vendor/**/*
- ".bundle/**/*"
- .bundle/**/*
- uploads/**/*
- .bundle/**/*
- .git/**/*
- engines/engine_name/.bundle/**/*
- engines/engine_name/vendor/**/*
- coverage/**/*
require: []
domains: []
reporters:
- rubocop
- require_not_found
plugins: []
require_paths: []
max_files: 5000
plugins:
- runtime

Elixir Phoenix Deployment with Distillery

OS: Ubuntu 17.04/zesty

We’ll be deploying using a git hook. This is not intended to be used in a production environment, but works just fine for a personal side project.

Let’s start with the server setup
Erlang/Elixir

#Add Erlang Solutions repo
wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb

sudo apt-get update

#Install Erlang/OTP
sudo apt-get install esl-erlang

We’ll be using kiex for Elixir

#Install Elixir
\curl -sSL https://raw.githubusercontent.com/taylor/kiex/master/install | bash -s

#Add to .bashrc
test -s "$HOME/.kiex/scripts/kiex" && source "$HOME/.kiex/scripts/kiex"

kiex install 1.4.0
kiex use 1.4.0
kiex default 1.4.0

Let’s init a repo

apt-get install git-core
mkdir repos && cd repos
mkdir my_repo 
cd my_repo
git init --bare

Add a remote to your local repo:

git remote add some_name root@IP_ADDRESS:repos/your_repo.git

Go back to the server and create a post-deploy hook. This will generate a new release after each git push.

Create post-receive file inside of your git hooks directory with this content:

#!/bin/bash -l

[[ -s "$HOME/.kiex/scripts/kiex" ]] && source "$HOME/.kiex/scripts/kiex"

GIT_REPO=$HOME/repos/your_repo.git
TMP_GIT_CLONE=$HOME/tmp/git/your_repo

cd $TMP_GIT_CLONE

PORT=4000 _build/prod/rel/{app_name}/bin/{app_name} stop
cd $HOME
rm -r $TMP_GIT_CLONE
git clone $GIT_REPO $TMP_GIT_CLONE
cd $TMP_GIT_CLONE

mix deps.get
cd assets
npm install
brunch build --production
cd ..
MIX_ENV=prod mix do compile, phx.digest, release --env=prod

MIX_ENV=prod mix ecto.migrate
PORT=4000 _build/prod/rel/{app_name}/bin/{app_name} start

This will stop your existing elixir app, remove old code, compile and release your new code, run the migration, and start the app on port 4000.

If you run it behind NGINX, set it up as a reverse proxy:

server {
  listen 80;
  listen [::]:80;

  server_name domain.com;

  location / {
     proxy_redirect off;
     proxy_pass http://127.0.0.1:4000/;
  }
}

Let’s go back to your Phoenix project and add distillery
Add to mix.exs deps:

  {:distillery, "~> 1.5"}

Initialize reliase config:

mix release.init

Update config/prod.exs

  config :app_name, AiPhoenixWeb.Endpoint,
    http: [port: {:system, "PORT"}],
    url: [host: "localhost", port: {:system, "PORT"}], # This is critical for ensuring web-sockets properly authorize.
    cache_static_manifest: "priv/static/cache_manifest.json",
    server: true,
    root: ".",
    version: Mix.Project.config[:version]

If using PostgreSQL, let’s install Postgresql and create a deploy user for our database:

sudo apt-get install postgresql postgresql-contrib libpq-dev
sudo su - postgres
createuser --pwprompt deploy
createdb -O deploy my_app_name_production
Exit

Auto-start your phoenix app on reboot:
Create a new file:

#!/bin/bash

cd $HOME/phoenix_app_location
PORT=4001 _build/prod/rel/phoenix_name/bin/phoenix_name start

Make it an executable
chmod +x phoenix_autostart.sh

Schedule it as a cron task:
Crontab -e

@reboot /root/path_to_script/phoenix_autostart.sh

The last step to to “git push” and verify that everything works.

NGINX Subdomains

In the example below we serve index.html from /var/www/html/your_site directory when accessing “domain.com”; and we send all requests to local application server running locally on port 4000 when accessing subdomain.domain.com

File: /etc/nginx/sites-enabled/default


server {
        listen 80;
        listen [::]:80;

        server_name domain.com;

        location / {
                root /var/www/html/your_site;
                try_files $uri /index.html;
        }
}

server {
  listen 80;
  listen [::]:80;

  server_name subdomain.domain.com;

  location / {
     proxy_redirect off;
     proxy_pass http://127.0.0.1:4000/;
  }
}

Deploying React to Linux Server with Git Push

Selected VPS: Linode, 1GB Ram, 20 GB SSD, 1 TB transfer
OS: Ubuntu 17.04
Web Server: Ngnix
If you’d like to try Linode, I would greatly appreciate using this referral link – Linode: SSD Cloud Hosting & Linux Servers

Start with regular updates

apt-get update && apt-get upgrade

Set up fail2ban and Firewall

I’m installing fail2ban 0.10 since it supports ipv6. At the time of this post, it is not available as a regular package.

wget https://github.com/fail2ban/fail2ban/archive/0.10.0.tar.gz
tar -xvzf 0.10.0.tar.gz
python3 setup.py install

#To enable fail2ban as an automatic service, copy the script for your distro from the files directory to /etc/init.d.

cp files/debian-initd /etc/init.d/fail2ban
update-rc.d fail2ban defaults
service fail2ban start

#Add local jail
awk '{ printf "# "; print; }' /etc/fail2ban/jail.conf | sudo tee /etc/fail2ban/jail.local
vim /etc/fail2ban/jail.local

uncomment sshd section and add
enabled = true

sudo apt-get install sendmail iptables-persistent
sudo service fail2ban start

Firewall ( allow established connections, traffic generated by the server itself, traffic destined for our SSH and web server ports. We will drop all other traffic):

sudo service fail2ban stop
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
sudo iptables -A INPUT -j DROP

# easy way to rate-limit ssh with ufw:
# technically, we could do all of the iptables stuff with ufw
ufw enable
ufw limit ssh

If using IPv6:

ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT # (replace with your undisclosed port)
ip6tables -A INPUT -p icmpv6 -j ACCEPT
ip6tables -A INPUT -j REJECT
ip6tables -A FORWARD -j REJECT

View iptables rules:

sudo iptables -S

Save iptables rules:

sudo dpkg-reconfigure iptables-persistent
sudo service fail2ban start

SSH

vim /etc/ssh/sshd_config

#Add or uncomment (if using Ubuntu < 17.04)
protocol 2

#Add allowed ciphers
Ciphers aes128-ctr,aes192-ctr,aes256-ctr
KexAlgorithms ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256
MACs hmac-sha2-256,hmac-sha2-512

Restart and test ssh config:

service sshd restart
#returns nothing if everything configured properly
sshd -t

NGINX

sudo apt-get install software-properties-common
sudo add-apt-repository ppa:nginx/stable
sudo apt-get install nginx
service nginx status

Update /etc/nginx/sites-enabled/default

root /var/www/html/your_site;

location / {
# Some comments...
try_files $uri /index.html;   # ADD THIS
}

sudo service nginx restart

Installing React Dependencies

# install yarn
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update && sudo apt-get install yarn
 
#install node (apt-get repo has an older version of Node)
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo apt-get install -y build-essential

GIT Push Deploy

Let’s set up git on the server

apt-get install git-core
mkdir repos && cd repos
mkdir your_site.git
cd your_site.git
git init --bare

Set up a post-push hook
cd /repos/your_app.git/hooks
touch post-receive

#!/bin/bash -l

GIT_REPO=$HOME/repos/your_app.git
TMP_GIT_CLONE=$HOME/tmp/git/your_app
PUBLIC_WWW=/var/www/html

git clone $GIT_REPO $TMP_GIT_CLONE
cd $TMP_GIT_CLONE
yarn install
yarn build
rm -rf $PUBLIC_WWW/your_app_bup
mv $PUBLIC_WWW/your_app $PUBLIC_WWW/your_app_bup
cp -a build/. $PUBLIC_WWW/your_app
rm -Rf $TMP_GIT_CLONE
exit

Run on post-receiv:

chmod +x post-receive

On your local machine:

git remote add linode root@remote_server_address:repos/your_app.git
git push linode master

Agile Estimation Tools

What’s the best online story estimation tool you ask? Well, Story Estimate sounds like one of the best options out there. Sure, I may be a little biased, since I wrote it, but still, it’s pretty good.

Nonetheless, I’ll pretend to be impartial and give a “fair” overview of several options.

  • Story Estimate – free, presents a modern UI, has no known vulnerabilities. It allows to use multiple estimation techniques (planning poker, T-shirt sizes…), including custom ones (specify whatever you want for your votes). Registered users can see additional statistics, including how their vote compare to the rest of the team. Simple to use. Best of all, it is written by me. What not to like?
  • Pointing Poker – a little outdated UI, but a pretty nice tool, also free. Unfortunately, it is open to JavaScript injection. Which, in a way, can be thought of as a feature (you can inject some fun JavaScript scripts), as long as you can trust your teammates not to inject any harmful scripts.
  • Planning Poker – the original planning poker. Free tier is limited (up to 5 players), paid version has some nice integrations with other tools (JIRA, TFS…). It’s a little more complicated, takes a little longer to setup, requires a user account.
  • PlanITPoker – Doesn’t require an account, although you can create one. It is a little more involved to setup, but not bad in terms of functionality.
  • Firepoker – Another free and open source tool. Extra points for being open source on GitHub. It’s a little limited in terms of the points you can select (offers two presets – powers of 2 and a slightly modified Fibonacci sequence). Also open to JavaScript injection.

RSpec, Action Cable, and Capybara (As of Rails 5.1.2)

Gems in Gemfile:

group :test, :development do
  gem 'database_cleaner'
  gem "rspec-rails", "~> 3.6.0"
  gem 'selenium-webdriver'
end

group :test do
  gem "capybara", "~> 2.14.0"
end

rails_helper:

config.use_transactional_fixtures = false

  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, js: true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

  #Puma makes it possible to run RSpec with ActionCable
  Capybara.server = :puma

  Capybara.register_driver :selenium_chrome do |app|
    Capybara::Selenium::Driver.new(app, browser: :chrome)
  end
  Capybara.javascript_driver = :selenium_chrome

And, the driver:

brew install chromedriver

Setting up a cron job on Mac

In this example, we’re setting up a job, which will run at 08:00 am daily and clean rails logs

Step 1 – create a file with what we want to run (delete_logs.sh):

#!/bin/(shell) 

cd ~/code/rails_project
/Users/username/.rbenv/shims/bundle exec rake log:clear

Setp 2 – set up the cron scheduler:

$ env EDITOR=vim crontab -e

# Add the line to run the script at 8am daily:

0 8 * * * sh ~/scripts/delete_logs.sh >/tmp/stdout.log 2>/tmp/stderr.log

Configuring ESLint and Prettier in Atom

Install eslint and optional plugins:

npm install -g eslint eslint-plugin-react

Install atom plugins:

linter-eslint
prettier-atom

Create .eslintrc either in the root of your application (or your root path if you want it to be global)

Add simple configuration rules:

{
    "plugins": [
        "react"
    ],
    "parserOptions": {
        "ecmaVersion": 6,
        "sourceType": "module",
        "ecmaFeatures": {
            "jsx": true
        }
    },
    "env": {
        "es6":     true,
        "browser": true,
        "node":    true,
        "mocha":   true
    },   
    "extends": [
        "eslint:recommended", 
        "plugin:react/recommended"
    ],    
    "rules": {
    }
}

Optionally, update atom prettier-atom to format on save (in plugin settings)