Phoenix 1.4 is on it’s way and one of the big changes is that webpack is replacing brunch. If you are a SASS fan then this is how to update the default Webpack configuration to use SASS (SCSS flavour).
Install NPM packages
The first step is to install the node-sass
and sass-loader
packages from NPM.
Using Yarn
$ yarn add node-sass sass-loader --dev
Using NPM
$ npm install node-sass sass-loader --save-dev
Update webpack.config.js
Update the assets/webpack.config.js
file with a change to chain the sass-loader
plugin after the css-loader
.
diff --git a/assets/webpack.config.js b/assets/webpack.config.js
index 5225785..4c14948 100644
--- a/assets/webpack.config.js
+++ b/assets/webpack.config.js
@@ -26,8 +26,18 @@ module.exports = (env, options) => ({
}
},
{
- test: /\.css$/,
- use: [MiniCssExtractPlugin.loader, 'css-loader']
+ test: /\.scss$/,
+ use: [
+ MiniCssExtractPlugin.loader,
+ {
+ loader: 'css-loader',
+ options: {}
+ },
+ {
+ loader: 'sass-loader',
+ options: {}
+ }
+ ]
}
]
},
Update app.css
Rename your assets/css/app.css
to assets/css/app.scss
.
$ mv assets/css/app.css assets/css/app.scss
Update app.js
Because the CSS files are loaded by Webpack through the javascript file, you need to update the css import path as well.
diff --git a/assets/js/app.js b/assets/js/app.js
index 8ee7177..0aa55a0 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -1,7 +1,7 @@
// We need to import the CSS so that webpack will load it.
// The MiniCssExtractPlugin is used to separate it out into
// its own CSS file.
-import css from "../css/app.css"
+import css from "../css/app.scss"
// webpack automatically bundles all modules in your
// entry points. Those entry points can be configured
Build assets
Finally test your assets build.
$ node node_modules/webpack/bin/webpack.js --mode development
With Phoenix 1.4 announced at ElixirConf EU (https://youtu.be/MTT1Jl4Fs-E) I was keen to try it out. I was specifically interested in seeing the new Webpack integration.
Getting started with Phoenix 1.4 is really quite easy.
Uninstall the existing Phoenix 1.3 archive
From the README,
Remove any previously installed phx_new
archives so that Mix will pick up the local source code. This can be done with mix archive.uninstall phx_new
or by simply deleting the file, which is usually in ~/.mix/archives/
.
Clone the Phoenix master repo
$ git clone https://github.com/phoenixframework/phoenix
Build and install the Phoenix archive
$ cd phoenix/installer
$ MIX_ENV=prod mix do archive.build, archive.install
Generate your new Phoenix 1.4 app
Run mix phx.new my_app
Your mix.exs deps will now look like this:
defp deps do
[
{:phoenix, github: "phoenixframework/phoenix", override: true},
#…
]
end
When Phoenix 1.4 is released, you can just update this line to:
defp deps do
[
{:phoenix, "~> 1.4.0"},
#…
]
end
Revert back to the Phoenix 1.3 installer
Reverting to the 1.3 installer is as easy as uninstalling and reinstalling the Phoenix archive.
Related
mix archive.uninstall phx_new-1.3.0
mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez
Simply await on a promise which resolves after a timeout.
test("my test", async function(assert) {
// setup…
await new Promise(resolve => setTimeout(resolve, 30000));
// …assert
});
I have often wanted to just do the following but Ecto’s Repo module doesn’t have a count method.
iex> MyApp.Repo.count(MyApp.Account)
42
It is not too difficult to create a count
function that will allow you to count the results of any query.
defmodule MyApp.DBUtils do
import Ecto.Query, only: [from: 2]
@doc "Generate a select count(id) on any query"
def count(query),
do: from t in clean_query_for_count(query), select: count(t.id)
# Remove the select field from the query if it exists
defp clean_query_for_count(query),
do: Ecto.Query.exclude(query, :select)
end
This will provide a shortcut for counting any query
MyApp.DBUtils.count(MyApp.Account) |> Repo.one!
Now, to enable Repo.count
we can modify the repo module usually found in lib/my_app/repo.ex
defmodule MyApp.Repo do
use Ecto.Repo, otp_app: :my_app
def count(query),
do: MyApp.DBUtils.count(query) |> __MODULE__.one!
end
That’s it. This will enable a count on any query including complicated queries and those that have a select expression set.
Appending to a list in Elixir ([1] ++ [2]
) is slower than prepending and reversing [ 2 | [1] ] |> Enum.reverse
but how bad is it?
Start by creating a new project, mix new benchmarking
and add benchfella as a dependency in your mix.exs file
defp deps do
[{:benchfella, "~> 0.3.2"}]
end
and run mix deps.get
Benchfella benchmarks work similarly to tests. Create a directory named bench
and then create a file ending in _bench.exs
. Benchfella will find these files and run them.
Create a file bench/list_append_bench.exs
We will write our functions in the bench file but you can reference functions in another module to benchmark your project code.
This benchmark will test three different ways to build a list, (1) append each element to the list using ++
, (2) build up the list using a recursive tail where the element is added to the head but the tail is built up recursively, and (3) prepending the element to a list accumulator and then reversing the list at the end.
defmodule ListAppendBench do
use Benchfella
@length 1_000
# First bench mark
bench "list1 ++ list2" do
build_list_append(1, @length)
end
# Second bench mark
bench "[head | recurse ]" do
build_list_recursive_tail(1, @length)
end
# Third bench mark
bench "[head | tail] + Enum.reverse" do
build_list_prepend(1, @length)
end
@doc """
Build a list of numbers from `num` to `total` by appending each item
to the end of the list
"""
def build_list_append(num, total, acc \\ [])
def build_list_append(total, total, acc), do: acc
def build_list_append(num, total, acc) do
acc = acc ++ [num]
next_num = num + 1
build_list_append(next_num, total, acc)
end
@doc """
Build a list of numbers from `num` to `total` by building
the list with a recursive tail instead of using an accumulator
"""
def build_list_recursive_tail(total, total), do: []
def build_list_recursive_tail(num, total) do
[ num | build_list_recursive_tail(num + 1, total) ]
end
@doc """
Build a list of numbers from `num` to `total` by prepending each item
and reversing the list at the end
"""
def build_list_prepend(num, total, acc \\ [])
def build_list_prepend(total, total, acc), do: Enum.reverse(acc)
def build_list_prepend(num, total, acc) do
acc = [num | acc]
next_num = num + 1
build_list_prepend(next_num, total, acc)
end
end
Run the benchmark with mix bench
and you see the results,
Settings:
duration: 1.0 s
## ListAppendBench
[10:15:32] 1/3: list1 ++ list2
[10:15:34] 2/3: [head | tail] + Enum.reverse
[10:15:37] 3/3: [head | recurse ]
Finished in 6.66 seconds
## ListAppendBench
[head | tail] + Enum.reverse 100000 20.87 µs/op
[head | recurse ] 100000 21.25 µs/op
list1 ++ list2 500 3228.16 µs/op
The results: prepending to a list and reversing it is 200 times faster than appending and only fractionally faster than building the tail recursively.
For more complex benchmarks, Benchfella has various hooks for test setup and teardown.
It also has ability to compare benchmark runs with mix bench.cmp
and graph the results with mix bench.graph
.
TL;DR, All the code can be found here
Sometimes, when you want complete control, you want to be able to install packages from source and still use an automated tool like Ansible to do that.
A simple set of tasks can check for the existence of files to eliminate the need for running tasks that are already complete but that doesn’t help us with making sure we have the correct version installed.
I’m going to walk through creating a play that will build ruby from source. It will not do any work if ruby is already installed and is already the correct version. If not correct, it will:
- download the source tarball
- extract the source
- configure the install
- make the build
- install ruby
- cleanup the build directory
A first pass can be found in this gist
If repeated, this build will re-download the archive, extract it, configure it and make it. It won’t install the binary again because it checks for the existence of the file /usr/local/bin/ruby
but other than that, all tasks will re-run.
The first step is to create a task that will determine the installed ruby version if present.
- name: Get installed ruby version
command: ruby --version # Run this command
ignore_errors: true # We don’t want and error in this command to cause the task to fail
changed_when: false
failed_when: false
register: ruby_installed_version # Register a variable with the result of the command
This task will run ruby --version
but will silently fail if ruby is not installed. If ruby is installed, then it registers the version string in a variable named ruby_installed_version
.
The next step is to create a variable we can use to test whether to build ruby or not. This is set in our global_vars to a default of false. Then add a task that will set that variable to true if the version string doesn’t match.
- name: Force install if the version numbers do not match
set_fact:
ruby_reinstall_from_source: true
when: '(ruby_installed_version|success and (ruby_installed_version.stdout | regex_replace("^.*?([0-9\.]+).*$", "\\1") | version_compare(ruby_version, operator="!=")))'
Now we can add a when
clause to all our other tasks. This will skip the task if ruby is correctly installed. That can be seen in this gist
The when clause checks for two things, (1) the task which checked the ruby version failed (i.e. there is no ruby installed) or (2) the ruby_reinstall_from_source
variable is true (i.e. the versions don’t match).
An example task with the when clause:
- name: Download Ruby
when: ruby_installed_version|failed or ruby_reinstall_from_source
get_url:
url: "https://cache.ruby-lang.org/pub/ruby/2.3/ruby-{{ruby_version}}.tar.gz"
dest: "/tmp/ruby-{{ruby_version}}.tar.gz"
sha256sum: "{{ruby_sha256sum}}"
# …
We now have a conditional on every test. That seems a bit redundant. This can be improved by using the block syntax. By using a block we can check the condition once, and then run or skip the whole installation in one move.
- when: ruby_installed_version|failed or ruby_reinstall_from_source
block:
- name: Download Ruby
when: ruby_installed_version|failed or ruby_reinstall_from_source
get_url:
url: "https://cache.ruby-lang.org/pub/ruby/2.3/ruby-{{ruby_version}}.tar.gz"
dest: "/tmp/ruby-{{ruby_version}}.tar.gz"
sha256sum: "{{ruby_sha256sum}}"
# …
The final code can be found in this gist, https://gist.github.com/andrewtimberlake/802bd8d285b3e18c5ebe, where you can walk through the three revisions as outlined in the article.
Using Dead Man's Snitch with Whenever
A quick tip to make it easier to use Dead Man's Snitch with the whenever gem
Whenever is a great gem for managing cron jobs. Dead Man’s Snitch is a fantastic and useful tool for making sure those cron jobs actually run when they should.
Whenever includes a number of predefined job types which can be overridden to include snitch support.
The job_type
command allows you to register a job type. It takes a name and a string representing the command. Within the command string, anything that begins with :
is replaced with the value from the jobs options hash. Sounds complicated but is in fact quite easy.
Include the whenever
gem in your Gemfile and then run
$ bundle exec wheneverize
This will create a file, config/schedule.rb
. Insert these lines at the top of your config file, I have mine just below set :output
.
These lines add && curl https://nosnch.in/:snitch
to each job type just before :output
.
job_type :command, "cd :path && :task && curl https://nosnch.in/:snitch :output"
job_type :rake, "cd :path && :environment_variable=:environment bin/rake :task --silent && curl https://nosnch.in/:snitch :output"
job_type :runner, "cd :path && bin/rails runner -e :environment ':task' && curl https://nosnch.in/:snitch :output"
job_type :script, "cd :path && :environment_variable=:environment bundle exec script/:task && curl https://nosnch.in/:snitch :output"
Now add your job to the schedule. A simple rake task would like this:
every 1.day, roles: [:app] do
rake "log:clear"
end
Now it’s time to create the snitch. You can grab a free account at deadmanssnitch.com and add a new snitch.

Then, once that’s saved, you’ll see a screen with your snitch URL. All you need to do is copy the hex code at the end.

Use that hex code in your whenever job as follows:
every 1.day, roles: [:app] do
rake "log:clear", snitch: "06ebef375f"
end
Now deploy and update your whenverized cron job. DMS will let you know as soon as your job runs for the first time so you know it has begun to work. After that, they’ll only let you know if it fails to check in.
Tip: For best tracking, you want your DMS job to check in just before the end of the period you’re monitoring (in the above example 1 day). To do that, I revert to cron syntax in whenever and set my job up as:
# Assuming your server time zone is set to UTC
every "59 23 * * *", roles: [:app] do
rake "log:clear", snitch: "06ebef375f"
end
See Does it matter when I ping a snitch?. Remember to allow time for the job to run and complete.
For more information, read through the full DMS FAQ
I’ve found a number of times where I have needed to iterate over a hash and modify the values. The most recent was stripping excess spaces from the values of a Rails params hash.
The only way I know of doing this is:
hash = {one: " one ", two: "two "}
hash.each do |key, value|
hash[key] = value.strip!
end
#=> {:one=>“one”, :two=>“two”}
This is a lot less elegant than using map
on an Array
[" one ", "two "].map(&:strip!)
#=> ["one", "two"]
I wanted something like #map
for a Hash
So I came up with Hash#clean
(this is a monkey patch so exercise with caution)
class Hash
def clean(&block)
each { |key, value|
self[key] = yield(value)
}
end
end
Now it’s as easy as,
{one: " one ", two: "two "}.clean(&:strip!)
#=> {:one=>"one", :two=>"two"}
Now I can easily sanitise Rails parameter hashes
def model_params
params.require(:model).permit(:name, :email, :phone).clean(&:strip!)
end
I quickly drew out the graph from the video on determining great feature fit. What you're looking for is features that will be used by all your users all of the time.
I use a large 27" iMac which I divide up windows with a browser in the top right of the screen. One thing that often frustrated me is that I could not maximise a video to fill the window completely. I had to fill my entire screen or watch it in the embedded size.
It turns out this is not too hard, change the URL in the browser from https://www.youtube.com/watch?v=oHg5SJYRHA0 to https://www.youtube.com/embed/oHg5SJYRHA0
Next page