Tuesday, January 31, 2017
Monday, January 30, 2017
Authentication in Rails Using Clearance
Clearance is a simple authentication system with email and password built by the team at Thoughtbot. It has opinionated defaults but is intended to be easy to override. The system is actively maintained, and you can follow up on GitHub.
In this tutorial, you will see how to integrate Clearance into a Rails application. We will make use of a miniature application. Let's begin!
Getting Started
You'll start by generating your Rails application. For the purpose of this tutorial, I'll name mine tutsplus-clearance
.
rails new tutsplus-clearance -T
That will do the magic.
You'll need bootstrap to make your application look good. Add the Bootstrap gem to your Gemfile
.
#Gemfile ... gem 'bootstrap-sass'
Install the gem by running bundle install
.
Now modify application
.
scss
to look like this:
#app/assets/stylesheets/application.scss @import 'bootstrap-sprockets'; @import 'bootstrap';
Clearance Setup
Open your Gemfile
to add the Clearance gem.
#Gemfile gem 'clearance'
Now install the gem.
bundle install
At this point, run the generator command to install clearance
.
rails generate clearance:install
This will generate some outputs on your terminal, which look like what I have below:
create config/initializers/clearance.rb insert app/controllers/application_controller.rb create app/models/user.rb create db/migrate/20161115101323_create_users.rb ******************************************************************************* Next steps: 1. Configure the mailer to create full URLs in emails: # config/environments/{development,test}.rb config.action_mailer.default_url_options = { host: 'localhost:3000' } In production it should be your app's domain name. 2. Display user session and flashes. For example, in your application layout: <% if signed_in? %> Signed in as: <%= current_user.email %> <%= button_to 'Sign out', sign_out_path, method: :delete %> <% else %> <%= link_to 'Sign in', sign_in_path %> <% end %> <div id="flash"> <% flash.each do |key, value| %> <div class="flash <%= key %>"><%= value %></div> <% end %> </div> 3. Migrate: rake db:migrate *******************************************************************************
When you ran the command, a couple of files were generated in your application. One such file is clearance.rb, which you can find in the config/initializers
directory. A User
model was also generated, and along with that you also have a migration file that looks like this:
class CreateUsers < ActiveRecord::Migration def change create_table :users do |t| t.timestamps null: false t.string :email, null: false t.string :encrypted_password, limit: 128, null: false t.string :confirmation_token, limit: 128 t.string :remember_token, limit: 128, null: false end add_index :users, :email add_index :users, :remember_token end end
According to the output, the first thing you want to do is edit your config environment. To do that, navigate to config/environments/development.rb
and add the line below, just above the end
delimiter.
... config.action_mailer.default_url_options = { host: 'localhost:3000' } end
Next, navigate to config/initializers/clearance.rb
to edit it, and when you're there, change the sender email address from the default to any of your choosing. This is what you will see when you open the file.
#config/initializers/clearance.rb Clearance.configure do |config| config.mailer_sender = "reply@example.com" end
You can override the default configuration by pasting in the following code snippet and configuring it to your requirements.
#config/initializers/clearance.rb Clearance.configure do |config| config.allow_sign_up = true config.cookie_domain = ".example.com" config.cookie_expiration = lambda { |cookies| 1.year.from_now.utc } config.cookie_name = "remember_token" config.cookie_path = "/" config.routes = true config.httponly = false config.mailer_sender = "reply@example.com" config.password_strategy = Clearance::PasswordStrategies::BCrypt config.redirect_url = "/" config.secure_cookie = false config.sign_in_guards = [] config.user_model = User end
Run the command to migrate your database.
rake db:migrate
Open your PagesController
and add an index
action.
#app/controllers/pages_controller.rb class PagesController < ApplicationController def index end end
Next, create a view for the index
action you just created.
Add the code snippet below:
#app/views/pages/index.html.erb <h1>Tutsplus Clearance</h1> <p>Welcome to our Clearance Page.</p>
Edit your routes to:
#config/routes.rb Rails.application.routes.draw do root to: "pages#index" end
Create a partial named _navigation.html.erb
inside the layouts
directory. This will be used to handle everything that has to do with navigation on your application.
Paste the following code and save.
#app/views/layouts/_navigation.html.erb <nav class="navbar navbar-inverse"> <div class="container"> <div class="navbar-header"> <%= link_to 'Tutsplus-Clearance', root_path, class: 'navbar-brand' %> </div> <div id="navbar"> <% if signed_in? %> <ul class="nav navbar-nav"> <li><%= link_to 'Add Page', new_page_path %></li> </ul> <% end %> <ul class="nav navbar-nav pull-right"> <% if signed_in? %> <li><span><%= current_user.email %></span></li> <li><%= link_to 'Sign out', sign_out_path, method: :delete %></li> <% else %> <li><%= link_to 'Sign in', sign_in_path %></li> <% end %> </ul> </div> </div> </nav> <div class="container"> <% flash.each do |key, value| %> <div class="alert alert-<%= key %>"> <%= value %> </div> <% end %> </div>
Restricted Access
With Clearance, you can be able to create restricted access to specific pages of your choice in your application. Let's see how it is done.
Create a view for a new
action in app/views/pages
, the name of the file should be new.html.erb
. Paste in the code below.
#app/views/pages/new.html.erb <h1>Restricted Page</h1> <p>This page is restricted to authenticated users, if you can see this it means you are a superstar!</p>
Now you need to add the line below to config/routes.rb
.
#config/routes.rb ... resources :pages, only: :new ...
Finally, go to your PagesController
make it like what I have below.
#apps/controllers/pages_controller.rb class PagesController < ApplicationController before_action :require_login, only: [:new] def index end def new end end
In the above code, we are making use of the Clearance helper, require_login
, to restrict access to the new
action. To see how it works, start up your rails server by running rails server
from your terminal. Point your browser to http://locahost:3000/pages/new
and it should redirect you to the sign in page.
Clearance also provides routing constraints that can be used to control access.
#config/routes.rb Rails.application.routes.draw do constraints Clearance::Constraints::SignedOut.new do root to: 'pages#index' end constraints Clearance::Constraints::SignedIn.new do root to: "pages#new', as: :signed_in_root end end
In the code above, a different route has been created for authenticated users.
Overriding Clearance Defaults
A lot of things happen behind the scenes when you start using Clearance, things you cannot see. There might come a time when you want to customize things differently, depending on the specification of your application. Clearance allows you to override the default configuration it comes with.
To override (or generate) Clearance routes, run this command from your terminal.
rails generate clearance:routes
Your routes file should now look like this:
#config/routes.rb Rails.application.routes.draw do resources :passwords, controller: "clearance/passwords", only: [:create, :new] resource :session, controller: "clearance/sessions", only: [:create] resources :users, controller: "clearance/users", only: [:create] do resource :password, controller: "clearance/passwords", only: [:create, :edit, :update] end get "/sign_in" => "clearance/sessions#new", as: "sign_in" delete "/sign_out" => "clearance/sessions#destroy", as: "sign_out" get "/sign_up" => "clearance/users#new", as: "sign_up" root to: "pages#index" resources :pages, only: :new end
The command will also set the config.routes
setting to false in your config/initializers/clearance.rb
file. This means that the custom file which has just been generated will be used.
To generate views for modification, run:
rails generate clearance:views
Some of the files that will be generated include:
app/views/passwords/create.html.erb app/views/passwords/edit.html.erb app/views/passwords/new.html.erb app/views/sessions/_form.html.erb app/views/sessions/new.html.erb app/views/users/_form.html.erb app/views/users/new.html.erb config/locales/clearance.en.yml
You will see a prompt in your terminal asking to overwrite your app/views/layouts/application.html.erb
file. Choose the option you want.
Layouts
By default, Clearance uses your application's default layout. If you would like to change the layout that Clearance uses when rendering its views, simply specify the layout in an initializer.
Clearance::PasswordsController.layout "my_passwords_layout" Clearance::SessionsController.layout "my_sessions_layout" Clearance::UsersController.layout "my_admin_layout"
Helper Methods
Clearance provides you with helper methods that can be used in your controllers
, views
, and helpers
. These methods include signed_in?
, signed_out?
, and current_user
. For example:
<% if signed_in? %> <%= current_user.email %> <%= button_to "Sign out", sign_out_path, method: :delete %> <% else %> <%= link_to "Sign in", sign_in_path %> <% end %>
Conclusion
Clearance has a lot to offer you when it comes to authentication, so be sure to try it out in your next project. You can learn more by checking out the GitHub page.
Mathematical Modules in Python: Statistics
Statistical analysis of data helps us make sense of the information as a whole. This has applications in a lot of fields like biostatistics and business analytics.
Instead of going through individual data points, just one look at their collective mean value or variance can reveal trends and features that we might have missed by observing all the data in raw format. It also makes the comparison between two large data sets way easier and more meaningful.
Keeping these needs in mind, Python has provided us with the statistics module.
In this tutorial, you will learn about different ways of calculating averages and measuring the spread of a given set of data. Unless stated otherwise, all the functions in this module support int
, float
, decimal
and fraction
based data sets as input.
Calculating the Mean
You can use the mean(data)
function to calculate the mean of some given data. It is calculated by dividing the sum of all data points by the number of data points. If the data is empty, a StatisticsError will be raised. Here are a few examples:
import statistics from fractions import Fraction as F from decimal import Decimal as D statistics.mean([11, 2, 13, 14, 44]) # returns 16.8 statistics.mean([F(8, 10), F(11, 20), F(2, 5), F(28, 5)]) # returns Fraction(147, 80) statistics.mean([D("1.5"), D("5.75"), D("10.625"), D("2.375")]) # returns Decimal('5.0625')
You learned about a lot of functions to generate random numbers in our last tutorial. Let's use them now to generate our data and see if the final mean is equal to what we expect it to be.
import random import statistics data_points = [ random.randint(1, 100) for x in range(1,1001) ] statistics.mean(data_points) # returns 50.618 data_points = [ random.triangular(1, 100, 80) for x in range(1,1001) ] statistics.mean(data_points) # returns 59.93292281437689
With the randint()
function, the mean is expected to be close to the mid-point of both extremes, and with the triangular distribution, it is supposed to be close to low + high + mode / 3
. Therefore, the mean in the first and second case should be 50 and 60.33 respectively, which is close to what we actually got.
Calculating the Mode
Mean is a good indicator of the average, but a few extreme values can result in an average that is far from the actual central location. In some cases it is more desirable to determine the most frequent data point in a data set. The mode()
function will return the most common data point from discrete numerical as well as non-numerical data. This is the only statistical function that can be used with non-numeric data.
import random import statistics data_points = [ random.randint(1, 100) for x in range(1,1001) ] statistics.mode(data_points) # returns 94 data_points = [ random.randint(1, 100) for x in range(1,1001) ] statistics.mode(data_points) # returns 49 data_points = [ random.randint(1, 100) for x in range(1,1001) ] statistics.mode(data_points) # returns 32 mode(["cat", "dog", "dog", "cat", "monkey", "monkey", "dog"]) # returns 'dog'
The mode of randomly generated integers in a given range can be any of those numbers as the frequency of occurrence of each number is unpredictable. The three examples in the above code snippet prove that point. The last example shows us how we can calculate the mode of non-numeric data.
Calculating the Median
Relying on mode to calculate a central value can be a bit misleading. As we just saw in the previous section, it will always be the most popular data point, irrespective of all other values in the data set. Another way of determining a central location is by using the median()
function. It will return the median value of given numeric data by calculating the mean of two middle points if necessary. If the number of data points is odd, it returns the middle point. If the number of data points is even, it returns the average of two median values.
The problem with the median()
function is that the final value may not be an actual data point when the number of data points is even. In such cases, you can either use median_low()
or median_high()
to calculate the median. With an even number of data points, these functions will return the smaller and larger value of the two middle points respectively.
import random import statistics data_points = [ random.randint(1, 100) for x in range(1,50) ] statistics.median(data_points) # returns 53 data_points = [ random.randint(1, 100) for x in range(1,51) ] statistics.median(data_points) # returns 51.0 data_points = [ random.randint(1, 100) for x in range(1,51) ] statistics.median(data_points) # returns 49.0 data_points = [ random.randint(1, 100) for x in range(1,51) ] statistics.median_low(data_points) # returns 50 statistics.median_high(data_points) # returns 52 statistics.median(data_points) # returns 51.0
In the last case, the low and high median were 50 and 52. This means that there was no data point with value 51 in our data set, but the median()
function still calculated the median to be 51.0.
Measuring the Spread of Data
Determining how much the data points deviate from the typical or average value of the data set is just as important as calculating the central or average value itself. The statistics module has four different functions to help us calculate this spread of data.
You can use the pvariance(data, mu=None)
function to calculate the population variance of a given data set.
The second argument in this case is optional. The value of mu, when provided, should be equal to the mean of the given data. The mean is calculated automatically if the value is missing. This function is helpful when you want to calculate the variance of an entire population. If your data is only a sample of the population, you can use the variance(data, xBar=None)
function to calculate the sample variance. Here, xBar is the mean of the given sample and is calculated automatically if not provided.
To calculate the population standard definition and sample standard deviation, you can use the pstdev(data, mu=None)
and stdev(data, xBar=None)
functions respectively.
import statistics from fractions import Fraction as F data = [1, 2, 3, 4, 5, 6, 7, 8, 9] statistics.pvariance(data) # returns 6.666666666666667 statistics.pstdev(data) # returns 2.581988897471611 statistics.variance(data) # returns 7.5 statistics.stdev(data) # returns 2.7386127875258306 more_data = [3, 4, 5, 5, 5, 5, 5, 6, 6] statistics.pvariance(more_data) # returns 0.7654320987654322 statistics.pstdev(more_data) # returns 0.8748897637790901 some_fractions = [F(5, 6), F(2, 3), F(11, 12)] statistics.variance(some_fractions) # returns Fraction(7, 432)
As evident from the above example, smaller variance implies that more data points are closer in value to the mean. You can also calculate the standard deviation of decimals and fractions.
Final Thoughts
In this last tutorial of the series, we learned about different functions available in the statistics module. You might have observed that the data given to the functions was sorted in most cases, but it doesn't have to be. I have used sorted lists in this tutorial because they make it easier to understand how the value returned by different functions is related to the input data.
Sunday, January 29, 2017
Saturday, January 28, 2017
Friday, January 27, 2017
Erlang and Elixir, Part 4: Control Flow
If, else, and logical operators are probably the most common similarity between the popular languages, and unsurprisingly Elixir has them, too. The if
, case
, and cond
macros are provided for giving us control flow structure.
For pattern matching, we mentioned before that using case
provides matches against any pattern iteratively:
iex> checkUser = 'simon' 'simon' iex> case {checkUser} do ...> {'simon'} -> ...> 'User Match - Simon' ...> {'mary'} -> ...> 'User Match - Mary' ...> _ -> ...> 'This will match any value.. use as a catch-all' ...> end 'User Match - Simon'
Worth noticing here is the _
case, which is essentially a default catch-all case. The case can be also used with an atom
or any variable type like so.
iex> case {:user} do ...> {:user} -> ...> 'User Match' ...> _ -> ...> 'No match' ...> end 'User Match'
Guard Clause Expressions
Elixir provides many operators to check in our expression as a guard against catching the wrong data. By default, the following are supported:
- comparison operators (
==
,!=
,===
,!==
,>
,>=
,<
,<=
) - boolean operators (
and
,or
,not
) - arithmetic operations (
+
,-
,*
,/
) - arithmetic unary operators (
+
,-
) - the binary concatenation operator
<>
- the
in
operator as long as the right side is a range or a list - all the following type check functions:
is_atom/1
is_binary/1
is_bitstring/1
is_boolean/1
is_float/1
is_function/1
is_function/2
is_integer/1
is_list/1
is_map/1
is_nil/1
is_number/1
is_pid/1
is_port/1
is_reference/1
is_tuple/1
- plus these functions:
-
abs(number)
binary_part(binary, start, length)
bit_size(bitstring)
byte_size(bitstring)
div(integer, integer)
elem(tuple, n)
hd(list)
length(list)
map_size(map)
node()
node(pid | ref | port)
rem(integer, integer)
round(number)
self()
tl(list)
trunc(number)
tuple_size(tuple)
We can use them like so:
iex> case 1 do ...> x when is_number(x) -> "Number #{x}" ...> x when is_boolean(x) -> "Boolean #{x}" ...> end "Number 1"
Additionally, an anonymous function can have multiple guards. For example, to calculate a pivot point from a financial market data using the high, low, and close values, we can do this:
iex> pivot = fn ...> h, l, c when h < l -> "Error" ...> h, l, c -> (h + l + c) / 3 ...> end iex> pivot.(1233, 1212, 1226) # Usage: High, Low, Close.. 1223.6666666666667
Here, if the high value is less than low, the pivot anonymous function will return an Error message. This one-line fashion of writing guards is phenomenally powerful.
Cond
Like a switch
statement, Elixir's cond
is where we can perform a string of if else like blocks and execute on the match.
iex> cond do ...> 2 + 2 == 5 -> ...> "This is never true" ...> 2 * 2 == 3 -> ...> "Nor this" ...> true -> ...> "This is always true (equivalent to else)" ...> end "This is always true (equivalent to else)
Inside a cond
block, everything is evaluating to true except nil
or false
. That's all numerical values and strings included.
For example, we can check the current status for various possibilities with a cond
block:
iex> cond do ...> status == 'available' -> ...> "User is available" ...> status == 'busy' -> ...> "User is busy" ...> true -> ...> "No input provided" ...> end
When the value of status is set to a string, it will evaluate; otherwise, an error message will be output by default via the last true
case.
If and Unless
Elixir also provides us with unless
, which is a default part of the if else block and can be demonstrated as so:
iex> if true do ...> 'works' ...> end 'works' iex> unless false do ...> 'other case' ...> end 'other case'
When the if
case evaluates as true, unless
will allow you to have access to the opposite. As demonstrated, we can catch the other side of the conditional this way.
In a real-life example, this can be used for displaying conditional information during a conditional check, such as a reminder or hint.
iex> if t do ...> 'Process information here...' ...> end nil iex> unless false do ...> 'Hint to show user when information is not right' ...> end 'Hint to show user when information is not right'
Do/End Blocks
Throughout these examples, we have seen the usage of the do
and end
macros. Let's have a closer look at them now before we conclude this section:
iex> if true, do: 1 + 2 3
The comma after true
is for Elixir's regular syntax, where each argument is comma-separated. This syntax uses keyword lists, and when used in conjunction with the else
macro it will look like this:
iex> if false, do: :this, else: :that :that
To make this nicer for development, we can use the do/end
block, which does not require the comma for a direct approach:
iex> if true do ...> a = 1 ...> a + 10 ...> end 11 iex> if true, do: ( ...> a = 1 ...> a + 10 ...> ) 11
Conclusion
Elixir has the familiar control flow structures if/else
and case
, and it also has unless
and the do/end
block.
We can achieve any logic via manipulation of control flow with these structures. When you use them in conjunction with macros, anonymous functions, and modules, you have a great supply of options to get the work done.
For continued reading on the topic, the manual provides more insights into some of the further caveats of using the do/end
block, such as function scope limitations.
Thursday, January 26, 2017
Wednesday, January 25, 2017
Tuesday, January 24, 2017
How to Make Magic, Animated Tooltips With CSS
Tooltips are a great way to enhance a UI when your users need some extra context for that fancy icon, or when they want some reassurance for clicking a button, or maybe an Easter Egg caption to go along with an image. Let’s make some animated tooltips, right now, with nothing but HTML and CSS.
Demo
Here’s what we’re working towards:
Before we get immersed in the cauldron, let’s take a peek at what we’re actually brewing. The main goal is to have a simple way to add a tooltip, so we’ll do that by adding a custom tooltip
attribute:
<span tooltip="message">visible text or icon, etc.</span>
A Note About Accessibility and Capability
If you are looking for 508-compliant tooltips, or need smarter tooltips with container collision detection and/or support for HTML content vs. plain text, there are plenty of solutions that use third-party scripts to solve those needs for you.
“JavaScript is imperative to make fully-accessible interactive components.” – Sara Soueidan, Building a fully-accessible help tooltip...is harder than I thought
This tutorial doesn’t specifically address accessibility needs. You know your users and what they need, so be sure to consider their needs in this respect too.
Let’s Set Some Expectations
- No JavaScript required
- We’ll be using attribute selectors (not classnames), with CSS built-in pattern matching
- Add to existing DOM elements (no new elements required in your markup*)
- Code examples are prefix-free (add vendor prefixes for your target browsers if needed)
- Assumes mouseover/hover to trigger tooltips
- Plain text tooltips only (HTML, images, etc. are not supported)
- Subtle animations while invoking tooltips
Alright! Let’s Rock This Boat!
Oh, wait. We have an asterisk to deal with first, about “not needing any extra markup”. This is magic, after all. Our tooltips don’t really need any extra DOM elements as they are made up completely of pseudo-elements (the ::before
and ::after
things) which we can control via CSS.
If you’re already utilizing an element’s pseudo-elements from another set of styles and you want a tooltip on that element, then you may need to restructure a little bit.
Ain’t No Party Like A Tooltip Party!
Wait. Gremlins! One more caveat: CSS positioning. In order for the tooltips to function properly, their parent element (the thing to which we are attaching the tooltip) needs to be
position: relative
, orposition: absolute
, orposition: fixed
Basically, anything other than position: static
— that’s the default position mode assigned to pretty much all elements by the browser. The tooltips are positioned absolutely and so they need to know the boundaries in which their absoluteness has meaning. The default position directive static does not declare its own boundaries and will not give our tooltips a context to push against, so the tooltips will use the next closest parental element that does have a declared boundary.
You’ll need to decide which position directive works best with how you are using the tooltips. This tutorial assumes position: relative
for the parent element. If your UI relies on an absolutely positioned element, then some restructuring (extra markup) may also be needed to deploy a tooltip on that element.
Let’s jump in and see what’s up.
Attribute Selectors; A Quick Refresher
Most CSS rules are written with classnames in mind, like .this-thing
, but CSS has a handful of selector types. Our magic tooltips are going to use attribute selectors–that's the square bracket notation:
[foo] { background: rgba(0, 0, 0, 0.8); color: #fff; }
When the browser encounters something like this:
<span foo>Check it out!</span>
it will know it needs to apply the [foo]
rules because that <span>
tag has an attribute named foo. In this case, the span itself would have a translucent-black background with white text.
HTML elements have various built-in attributes, but we can also make up our own. Like foo
, or tooltip
. By default, HTML doesn’t know what these mean, but with CSS we can tell HTML what this means.
Why Attribute Selectors?
We’ll use attribute selectors primarily for a separation of concerns. Using attributes over classnames does not get us any bonus points in the specificity wars; classes and attributes have the same specificity. However, by using attributes we can keep our content with the content as HTML attributes can have values, whereas classnames do not.
Consider the classname .tooltip
vs. the attribute [tooltip]
in this example code. The classname is one of the values for the attribute [class]
while the tooltip attribute has a value, which is the text we want to display.
<span class="tooltip another-classname">lorem ipsum</span> <span tooltip="sit dolar amet">lorem ipsum</span>
Now Entering Tooltip Alchemy
Our tooltips will use two different attributes:
tooltip
: this holds the tooltip’s content (a plain text string)flow
: optional; this allows us to control how to expose the tooltip. There are many placements we could support but we’ll cover four common placements:
up, left, right, down.
Now, let’s set up the ground work for all tooltips. The rules from steps 1–5 apply to all tooltips regardless of what flow we give them. Steps 6–7 have distinctions between the various flow
values.
1. Relativity
This is for the tooltip’s parent element. Let’s assign a position directive so the absolute positioning of the tooltip’s parts (the ::before
and ::after
pseudo-elements) are positioned in context of this parent element and not in context of the page at-large or a grandparent element or some other outer element up the DOM tree.
[tooltip] { position: relative; }
2. Pseudo-element Prime Time
It’s time to prime the pseudo-elements. Here we’ll set common properties to both the ::before
and ::after
pieces. The content
property is what actually makes a pseudo-element work, but we’ll get there soon.
[tooltip]::before, [tooltip]::after { line-height: 1; user-select: none; pointer-events: none; position: absolute; display: none; opacity: 0; /* opinions */ text-transform: none; font-size: .9em; }
3. The Dink
I don’t know why “dink” makes sense, I’ve just always called it that. This is the little triangle pointy part that gives the tooltips their speech bubble feel by pointing at the thing which invoked it. Notice we’re using transparent
for the border color; we’,ll add in the color later as how we add it depends on the tooltip’s flow
.
[tooltip]::before { content: ''; z-index: 1001; border: 5px solid transparent; }
It’s not a typo that the content: '';
declaration has an empty string for a value. We don’t want anything in there, but we do need that property for the pseudo-element to exist.
To make a triangle we are defining a solid border with some thickness on an empty box (no content) with no width and no height, and only giving one side of the box a border color. For more details check out the following tutorial:
4. Bubbles!
Here is the meat of the thing. Notice the content: attr(tooltip)
part saying, “This pseudo-element should use the value of the tooltip
attribute as its content.” This is why using attributes over classnames is so great!
[tooltip]::after { content: attr(tooltip); /* magic! */ z-index: 1000; /* most of the rest of this is opinion */ font-family: Helvetica, sans-serif; text-align: center; /* Let the content set the size of the tooltips but this will also keep them from being obnoxious */ min-width: 3em; max-width: 21em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; /* visible design of the tooltip bubbles */ padding: 1ch 1.5ch; border-radius: .3ch; box-shadow: 0 1em 2em -.5em rgba(0, 0, 0, 0.35); background: #333; color: #fff; }
Notice the z-index
values for both the dink and the bubble. These are arbitrary values, but keep in mind that a z-index
value is relative. Meaning: a z-index value of 1001 inside an element with a z-index of 3 just means that the 1001 element will be the top-most element inside that z-index: 3 container.
The bubble’s z-index
should be at least one step down from the dink’s z-index
. If it is the same as or higher than the dink, you can end up with an inconsistent coloring effect on the dink if your tooltips employ a box-shadow
.
For a more detailed look at the z-index property, take a look at the following tutorial:
5. Interaction Action
Our tooltips are activated by hovering the mouse over the element with the tooltip... Almost.
[tooltip]:hover::before, [tooltip]:hover::after { display: block; }
If you look back at our style block in Step 2, you should see that we’ve used opacity: 0;
along with display: none;
for our tooltip parts. We did this so we can use CSS animation effects when the tooltips show and hide.
The display
property cannot be animated, but opacity
can! We’ll deal with the animations last. If you don’t care for animated tooltips, just wipe out the opacity: 0;
declaration from Step 2 and ignore the animation in Step 7.
The last thing we’ll need that still applies to all the tooltips is a way to suppress a tooltip if it has no content. If you are populating tooltips with some sort of dynamic system (Vue.js, Angular, or React, PHP, etc.) we don’t want silly empty bubbles!
/* don't show empty tooltips */ [tooltip='']::before, [tooltip='']::after { display: none !important; }
6. Flow Control
This step can get rather complicated as we’ll be using some not so common selectors to help our tooltips deal with their placements based on their flow
values (or lack thereof).
“Strange things are afoot at the Circle-K.” — Ted Theodore Logan
Before we jump into the styles, let’s take a look at some selector patterns we’ll be using.
[tooltip]:not([flow])::before, [tooltip][flow^="up"]::before { /* ... properties: values ... */ }
This is telling the browser: “For all elements with a tooltip
attribute that either do not have a flow
attribute, or have a flow
with a value that starts with ‘up’: apply these styles to its ::before
pseudo-element.”
We’re using a pattern here so these can be extended to other flows without needing to repeat so much CSS. This pattern flow^="up"
is using the ^=
(starts with) matcher. This allows the styles to also apply to up-right and up-left should you want to add those flow controls. We’re not going to cover those here, but you can see them in use on my original tooltip demo on CodePen.
Here are the CSS blocks for all four flows this tutorial covers.
Up (default):
/* ONLY the ::before */ [tooltip]:not([flow])::before, [tooltip][flow^="up"]::before { bottom: 100%; border-bottom-width: 0; border-top-color: #333; } /* ONLY the ::after */ [tooltip]:not([flow])::after, [tooltip][flow^="up"]::after { bottom: calc(100% + 5px); } /* Both ::before & ::after */ [tooltip]:not([flow])::before, [tooltip]:not([flow])::after, [tooltip][flow^="up"]::before, [tooltip][flow^="up"]::after { left: 50%; transform: translate(-50%, -.5em); }
Down:
[tooltip][flow^="down"]::before { top: 100%; border-top-width: 0; border-bottom-color: #333; } [tooltip][flow^="down"]::after { top: calc(100% + 5px); } [tooltip][flow^="down"]::before, [tooltip][flow^="down"]::after { left: 50%; transform: translate(-50%, .5em); }
Left:
[tooltip][flow^="left"]::before { top: 50%; border-right-width: 0; border-left-color: #333; left: calc(0em - 5px); transform: translate(-.5em, -50%); } [tooltip][flow^="left"]::after { top: 50%; right: calc(100% + 5px); transform: translate(-.5em, -50%); }
Right:
[tooltip][flow^="right"]::before { top: 50%; border-left-width: 0; border-right-color: #333; right: calc(0em - 5px); transform: translate(.5em, -50%); } [tooltip][flow^="right"]::after { top: 50%; left: calc(100% + 5px); transform: translate(.5em, -50%); }
7. Animate All the Things
Animations are amazing. Animations can:
- help users feel comfortable
- help users with the spacial awareness of your UI
- call attention to things that need to be seen
- soften elements of a UI that would otherwise be a binary on/off jarring effect
Our tooltips will fall into that last description. Rather than having a text bubble pop into existence and pop out in a flash, let’s make them softer.
@keyframes
We’ll need two @keyframe
animations. The up/down tooltips will use the tooltips-vert
keyframe, and the left/right tooltips will use the tooltips-horz
keyframe. Notice in both of these keyframes we are only defining the desired ending state of the tooltips. We don’t need to know where they come from (the tooltips themselves have that style information). We just want to control where they go to.
@keyframes tooltips-vert { to { opacity: .9; transform: translate(-50%, 0); } } @keyframes tooltips-horz { to { opacity: .9; transform: translate(0, -50%); } }
Now, we need to apply these keyframes to the tooltips when a user hovers over the triggering elements (the elements with the [tooltip]
attributes). Since we are employing various flows to control how the tooltips will show, we need to identify those possibilities in the styles.
Use :hover to Pass Control to Animations
[tooltip]:not([flow]):hover::before, [tooltip]:not([flow]):hover::after, [tooltip][flow^="up"]:hover::before, [tooltip][flow^="up"]:hover::after, [tooltip][flow^="down"]:hover::before, [tooltip][flow^="down"]:hover::after { animation: tooltips-vert 300ms ease-out forwards; } [tooltip][flow^="left"]:hover::before, [tooltip][flow^="left"]:hover::after, [tooltip][flow^="right"]:hover::before, [tooltip][flow^="right"]:hover::after { animation: tooltips-horz 300ms ease-out forwards; }
Remember we cannot animate the display
property, but we can give the tooltips a fade-in effect by manipulating the opacity
. We are also animating the transform property which gives the tooltips a subtle movement as if they are flying in to point at their triggering elements.
Notice the forwards
keyword in the animation declaration. This tells the animation to not reset once it completes, but to proceed forward and stay at the end.
Conclusion
Fantastic job! We covered a lot in this tutorial, and now have a neat collection of tooltips to show for our hard work:
We’ve only scratched the surface of what can be done with CSS tooltips. Have fun playing with them and keep on experimenting, and concocting your own recipes!
More CSS UI Tutorials
- Quick Tip: Easy CSS3 Checkboxes and Radio Buttons
- Taking CSS Shapes to the Next Level
- Solving Problems With CSS Grid and Flexbox: The Card UI
- Get Started With Web Animation
Monday, January 23, 2017
Mathematical Modules in Python: Random
Randomness is all around us. When you flip a coin or roll a die, you can never be sure of the final outcome. This unpredictability has a lot of applications like determining the winners of a lucky draw or generating test cases for an experiment with random values produced based on an algorithm.
Keeping this usefulness in mind, Python has provided us with the random module. You can use it in games to spawn enemies randomly or to shuffle the elements in a list.
How Does Random Work?
Nearly all of the functions in this module depend on the basic random()
function, which will generate a random float greater than or equal to zero and less than one. Python uses the Mersenne Twister to generate the floats. It produces 53-bit precision floats with a period of 2**19937-1. It is actually the most widely used general-purpose pseudo-random number generator.
Sometimes, you want the random number generator to reproduce the sequence of numbers it created the first time. This can be achieved by providing the same seed value both times to the generator using the seed(s, version)
function. If the parameter s is omitted, the generator will use the current system time to generate the numbers. Here is an example:
import random random.seed(100) random.random() # returns 0.1456692551041303 random.random() # returns 0.45492700451402135
Keep in mind that unlike a coin flip, the module generates pseudo-random numbers which are completely deterministic, so it is not suitable for cryptographic purposes.
Generating Random Integers
The module has two different functions for generating random integers. You can use randrange(a)
to generate a random whole number smaller than a
.
Similarly, you can use randrange(a, b[,step])
to generate a random number from range(a, b, step)
. For example, using random.randrange(0, 100, 3)
will only return those numbers between 0 and 100 which are also divisible by 3.
If you know both the lower and upper limit between which you want to generate the numbers, you can use a simpler and more intuitive function called randint(a, b)
. It is simply an alias for randrange(a, b+1)
.
import random random.randrange(100) # returns 65 random.randrange(100) # returns 98 random.randrange(0, 100, 3) # returns 33 random.randrange(0, 100, 3) # returns 75 random.randint(1,6) # returns 4 random.randint(1,6) # returns 6
Functions for Sequences
To select a random element from a given non-empty sequence, you can use the choice(seq)
function. With randint()
, you are limited to a selection of numbers from a given range. The choice(seq)
function allows you choose a number from any sequence you want.
Another good thing about this function is that it is not limited to just numbers. It can select any type of element randomly from a sequence. For example, the name of the winner of a lucky draw among five different people, provided as a string, can be determined using this function easily.
If you want to shuffle a sequence instead of selecting a random element from it, you can use the shuffle(seq)
function. This will result in an in place shuffling of the sequence. For a sequence with just 10(n) elements, there can be a total 3628800(n!) different arrangements. With a larger sequence, the number of possible permutations will be even higher—this implies that the function can never generate all the permutations of a large sequence.
Let's say you have to pick 50 students from a group of 100 students to go on a trip.
At this point, you may be tempted use the choice(seq)
function. The problem is that you will have to call it about 50 times in the best case scenario where it does not choose the same student again.
A better solution is to use the sample(seq, k)
function. It will return a list of k unique elements from the given sequence. The original sequence is left unchanged. The elements in the resulting list will be in selection order. If k is greater than the number of elements in the sequence itself, a ValueError will be raised.
import random ids = [1, 8, 10, 12, 15, 17, 25] random.choice(ids) # returns 8 random.choice(ids) # returns 15 names = ['Tom', 'Harry', 'Andrew', 'Robert'] random.choice(names) # returns Tom random.choice(names) # returns Robert random.shuffle(names) names # returns ['Robert', 'Andrew', 'Tom', 'Harry'] random.sample(names, 2) # returns ['Andrew', 'Robert'] random.sample(names, 2) # returns ['Tom', 'Robert'] names # returns ['Robert', 'Andrew', 'Tom', 'Harry']
As you can see, shuffle(seq)
modified the original list, but sample(seq, k)
kept it intact.
Generating Random Floats
In this section, you will learn about functions that can be used to generate random numbers based on specific real-value distributions. The parameters of most of these functions are named after the corresponding variable in that distribution's actual equation.
When you just want a number between 0 and 1, you can use the random()
function. If you want the number to be in a specific range, you can use the uniform(a, b)
function with a and b as the lower and higher limits respectively.
Let's say you need to generate a random number between low and high such that it has a higher probability of lying in the vicinity of another number mode. You can do this with the triangular(low, high, mode)
function. The low and high values will be 0 and 1 by default. Similarly, the mode value defaults to the mid-point of the low and high value, resulting in a symmetrical distribution.
There are a lot of other functions as well to generate random numbers based on different distributions. As an example, you can use normalvariate(mu, sigma)
to generate a random number based on a normal distribution, with mu as mean and sigma as standard deviation.
import random random.random() # returns 0.8053547502449923 random.random() # returns 0.05966180559620815 random.uniform(1, 20) # returns 11.970525425108205 random.uniform(1, 20) # returns 7.731292430291898 random.triangular(1, 100, 80) # returns 42.328674062298816 random.triangular(1, 100, 80) # returns 73.54693076132074
Weighted Probabilities
As we just saw, it is possible to generate random numbers with uniform distribution as well as triangular or normal distribution. Even in a finite range like 0 to 100, there are an infinite number of floats that can be generated. What if there is a finite set of elements and you want to add more weight to some specific values while selecting a random number? This situation is common in lottery systems where numbers with little reward are given a high weighting.
If it is acceptable for your application to have weights that are integer values, you can create a list of elements whose frequency depends on their weight. You can then use the choice(seq)
function to select an element from this weighted list randomly. Here is an example showing the selection of a prize amount randomly.
import random w_prizes = [('$1', 300), ('$2', 50), ('$10', 5), ('$100', 1)] prize_list = [prize for prize, weight in w_prizes for i in range(weight)] random.choice(prize_list) # returns '$1'
In my case, it took ten trials to get a $2 prize chosen from the list. The chances of getting a $100 prize would be much lower. Similarly, you can also add bias to other such programs.
Final Thoughts
This module can be useful in a lot of situations like shuffling the questions in an assignment or generating random usernames or passwords for your users by using the shuffle()
function. You can also generate random numbers uniformly as well as give weighting to numbers in a specific range. In our next tutorial, we will be using the functions from this module to generate random data for statistical analysis.
Do you have some interesting applications of random number generators in mind that can be useful to fellow readers? Let us know in the comments.
Sunday, January 22, 2017
Saturday, January 21, 2017
Friday, January 20, 2017
Erlang and Elixir, Part 3: Functions
Elixir is built on Erlang, and in both Erlang and Elixir a function is not just identified by its name, but by its name and arity. Remember: Everything in Elixir is an expression.
To give you a clear example of this, below we have four functions, but all defined with different arity.
def sum, do: 0 def sum(a), do: a def sum(a, b), do: a + b def sum(a, b, c), do: a + b + c
To provide a concise way to work with data, we can use guard expressions like so:
def sum(a, b) when is_integer(a) and is_integer(b) do a + b end def sum(a, b) when is_list(a) and is_list(b) do a ++ b end def sum(a, b) when is_binary(a) and is_binary(b) do a <> b end sum 1, 2 #=> 3 sum [1], [2] #=> [1, 2] sum "a", "b" #=> "ab"
Guard expressions such as when is_binary(a)
allow us to check for the correct type before performing an operation.
Unlike Erlang, Elixir allows for default values in its functions via usage of the \\
syntax like so:
def mul_by(x, n \\ 2) do x * n end mul_by 4, 3 #=> 12 mul_by 4 #=> 8
Anonymous Functions
In the previous part, we discussed anonymous functions briefly as a data type. To elaborate on this further, take this example:
sum = fn(a, b) -> a + b end sum.(4, 3) #=> 7 square = fn(x) -> x * x end Enum.map [1, 2, 3, 4], square #=> [1, 4, 9, 16]
We see this powerful shorthand here on the first line fn(a, b) -> a + b end
. With this we are able to produce a basic operation sum.(4, 3)
and get the output in just two lines of code.
Now, looking to the square method, fn(x) -> x * x end
, can it really be any simpler? Working now with the map
, we can perform the square anonymous function over the whole map—again in just two lines of code!
Pattern Matching
Arithmetic is all fun and good, but let's see what we can do with text.
f = fn {:simon} = tuple -> IO.puts "Good morning Sir #{inspect tuple}" {:kate} = tuple -> IO.puts "Lovely day #{inspect tuple}" [] -> "Empty" end f.([]) #=> "Empty" f.({:simon}) #=> "Good morning Sir {:simon}" f.({:kate}) #=> "Lovely day {:kate}"
Here, with pattern matching we can define several outcomes in our control flow, again in hardly any code. Elixir's syntax is rapid to work with and mightily powerful, as we will see in the next example.
First-Class Functions
The anonymous functions we just covered are first-class values. This means that they can be passed as arguments to other functions and also can serve as a return value themselves. There is a special syntax to work with named functions in the same way:
defmodule Math do def square(x) do x * x end end Enum.map [1, 2, 3], &Math.square/1 #=> [1, 4, 9]
Here we define a Math
module with defmodule
and define the square function. Then we can use this in conjunction with the map method demonstrated earlier and the Math
module we just defined. We use the same operator &
, allowing us to pass our function Math.square/1
to capture the square function's output for each entry in our list.
That's a whole lot of power for just one line. This is referred to as a partial function capture in Elixir.
Control Flow
We use the constructs if
and case
to control flow in Elixir. Like everything in Elixir, if
and case
are expressions.
For pattern matching, we use case
:
case {x, y} do {:a, :b} -> :ok {:b, :c} -> :good other -> other end
And for comparison logic, we use if
:
test_fun = fn(x) -> cond do x > 10 -> :greater_than_ten x < 10 and x > 0 -> :less_than_ten_positive x < 0 or x === 0 -> :zero_or_negative true -> :exactly_ten end end test_fun.(44) #=> :greater_than_ten test_fun.(0) #=> :zero_or_negative test_fun.(10) #=> :exactly_ten
For ease of use, Elixir also provides an if
function that resembles many other languages and is useful when you need to check if one clause is true or false:
if x > 10 do :greater_than_ten else :not_greater_than_ten end
Dynamic Functions
This is possible in Elixir via usage of the unquote
method mentioned earlier. For example, to check some hard-coded Admins in our system, we can do the following:
defmodule Admins do [:manager, :super] |> Enum.each fn level -> def unquote(:"check_#{level}")() do IO.inspect("Access Level: #{unquote(level)}") end end end Admins.check_manager # => "Access Level: manager" Admins.check_super # => "Access Level: super"
Here we have created the methods Admins.check_manager
and Admins.check_super
from the atom names.
Conclusion
Everything is an expression in Elixir, and that means we can get a whole heap of power out of writing very little code.
For me, Elixir looks similar to CoffeeScript, Ruby, Python or any minimalist syntax as the form is so direct and to the point, but Elixir is far more powerful than these languages due to the meta-programming aspect.
Going forward, we will see how to utilise control flow and functions more to create interactivity in our app.
Thursday, January 19, 2017
Wednesday, January 18, 2017
Tuesday, January 17, 2017
Monday, January 16, 2017
Mathematical Modules in Python: Decimal and Fractions
Even the most basic mathematical operations can sometimes give an erroneous result. This happens due to limitations in storing the exact value of some numbers. You can overcome these limitations by using the decimal module in Python. Similarly, neither the math nor the cmath module that we learned about in our last tutorial can help us in doing fraction-based arithmetic. However, the fractions module in Python does exactly that.
In this tutorial, you will learn about both these modules and the different functions they make available.
Why Do We Need a Decimal Module?
You are probably wondering why we need a module to do basic arithmetic with decimal numbers when we can already do the same using floats.
Before I answer this question, I want you to take a guess about the output value if you type 0.1 + 0.2
in the Python console. If you guessed that the output should be 0.3, you will be surprised when you check out the actual result, which is 0.30000000000000004. You can try some other calculation like 0.05 + 0.1
and you will get 0.15000000000000002.
To understand what's going on here, try to represent 1/3
in decimal form, and you will notice that the number is actually non-terminating in base 10. Similarly, some numbers like 0.1 or 1/10 are non-terminating in base 2. Since these numbers still need to be represented somehow, a few approximations are made while storing them, which results in those errors.
The number 0.30000000000000004 is actually very close to 0.3, so we can get away with this approximation most of the time. Unfortunately, this approximation is not going to cut it when you are simulating a satellite launch or dealing with money. Another problem with these approximations is that the errors keep piling up.
To get precise results like the ones we are used to dealing with when doing calculations by hand, we need something that supports fast, correctly rounded, decimal floating point arithmetic, and the decimal module does exactly that.
Using the Decimal Module
Before using the module, you need to import it first. After that, you can create decimals from integers, strings, floats, or tuples. When the decimal is constructed from an integer or a float, there is an exact conversion of the value of that number. Take a look at the examples below to see what I mean:
from decimal import Decimal Decimal(121) # returns Decimal('121') Decimal(0.05) # returns Decimal('0.05000000000000000277555756') Decimal('0.05') # returns Decimal('0.05') Decimal((0, (8, 3, 2, 4), -3)) # returns Decimal('8.324') Decimal((1, (8, 3, 2, 4), -1)) # returns Decimal('-832.4')
As you can see, the value of Decimal(0.05)
is slightly different from Decimal('0.05')
. This means that when you add 0.05 and 0.1, you should use decimal.Decimal('0.05')
and decimal.Decimal('0.1')
to construct the decimals.
from decimal import Decimal Decimal('0.05') + Decimal('0.1') # returns Decimal('0.15') Decimal(0.05) + Decimal(0.1) # returns Decimal('0.1500000000000000083266726847')
Now that you can perform various operations on decimals, you might want to control the precision or rounding for those operations. This can be done by using the getcontext()
function. This function allows you to get as well as set the value of the precision and rounding options, among other things.
Please keep in mind that both rounding and precision come into play only during arithmetic operations and not while you are creating the decimals themselves.
import decimal from decimal import Decimal, getcontext Decimal(1) / Decimal(13) # returns Decimal('0.07692307692307692307692307692') getcontext().prec = 10 Decimal(0.03) # returns Decimal('0.02999999999999999888977697537') Decimal(1) / Decimal(7) # returns Decimal('0.1428571429') getcontext().rounding = decimal.ROUND_DOWN Decimal(1) / Decimal(7) # returns Decimal('0.1428571428')
You can also use some of the mathematical functions like sqrt()
, exp()
, and log()
with decimals. Here are a few examples:
import decimal from decimal import Decimal, getcontext Decimal(2).sqrt() # returns Decimal('1.414213562373095048801688724') getcontext().prec = 4 Decimal('2').sqrt() # returns Decimal('1.414') Decimal('2000').log10() # returns Decimal('3.301')
Using the Fractions Module
Sometimes, you might face situations where you need to perform various operations on fractions or the final result needs to be a fraction. The fractions module can be of great help in these cases. It allows you to create a Fraction
instance from numbers, floats, decimals, and even strings. Just like the decimal module, there are a few issues with this module as well when it comes to creating fractions from floats. Here are a few examples:
from fractions import Fraction from decimal import Decimal Fraction(11, 35) # returns Fraction(11, 35) Fraction(10, 18) # returns Fraction(5, 9) Fraction('8/25') # returns Fraction(8, 25) Fraction(1.13) # returns Fraction(1272266894732165, 1125899906842624) Fraction('1.13') # returns Fraction(113, 100) Fraction(Decimal('1.13')) # returns Fraction(113, 100)
You can also perform simple mathematical operations like addition and subtraction on fractions just like regular numbers.
from fractions import Fraction Fraction(113, 100) + Fraction(25, 18) # returns Fraction(2267, 900) Fraction(18, 5) / Fraction(18, 10) # returns Fraction(2, 1) Fraction(18, 5) * Fraction(16, 19) # returns Fraction(288, 95) Fraction(18, 5) * Fraction(15, 36) # returns Fraction(3, 2) Fraction(12, 5) ** Fraction(12, 10) # returns 2.8592589556010197
The module also has a few important methods like limit_denominator(max_denominator)
which will find and return a fraction closest in value to the given fraction whose denominator is at most max_denominator
. You can also return the numerator of a given fraction in the lowest term by using the numerator
property and the denominator by using the denominator
property.
from fractions import Fraction Fraction('3.14159265358979323846') # returns Fraction(157079632679489661923, 50000000000000000000) Fraction('3.14159265358979323846').limit_denominator(10000) # returns Fraction(355, 113) Fraction('3.14159265358979323846').limit_denominator(100) # returns Fraction(311, 99) Fraction('3.14159265358979323846').limit_denominator(10) # returns Fraction(22, 7) Fraction(125, 50).numerator # returns 5 Fraction(125, 50).denominator # returns 2
You can also use this module with various functions in the math module to perform fraction-based calculations.
import math from fractions import Fraction math.sqrt(Fraction(25, 4)) # returns 2.5 math.sqrt(Fraction(28,3)) # returns 3.0550504633038935 math.floor(Fraction(3558, 1213)) # returns 2 Fraction(math.sin(math.pi/3)) # returns Fraction(3900231685776981, 4503599627370496) Fraction(math.sin(math.pi/3)).limit_denominator(10) # returns Fraction(6, 7)
Final Thoughts
These two modules should be sufficient to help you perform common operations on both decimals and fractions. As shown in the last section, you can use these modules along with the math module to calculate the value of all kinds of mathematical functions in the format you desire.
In the next tutorial of the series, you will learn about the random module in Python.
Sunday, January 15, 2017
Friday, January 13, 2017
Erlang and Elixir, Part 2: Data Types
Elixir has a wealth of data types available. The usual basic types integer
, float
, boolean
, and string
are here, but so are the atom
/ symbol, list
, tuple
, and anonymous functions
. You'll learn all about them in this tutorial.
Before we get started: We will be running these examples in Elixir's interactive mode. Type iex
in your terminal to enter interactive mode. For more information, read Part 1 of this guide. (Windows users run iex.bat --werl
.)
Preface
Elixir is built with meta-programming in mind. All of it is built upon macros. If you are not familiar with macros, they are simply a single instruction which performs a particular task. Not very hard to get your head around maybe, but here are some real-life examples:
if worked? do IO.puts("You just used a macro") end if :name? do IO.puts(:name) end
if
here is a macro for the standard conditional if ... structure we all know. Elixir will compile the result from the macro for you internally.
Meta-Programming?
Fundamental to Elixir is the manipulation of quoted expressions.
Meta-programming essentially means you can create code from code (programs have the ability to treat their own code as data essentially... a huge step indeed.)
An Elixir program can be represented as its own data structure.
For example, the building blocks of Elixir are represented by a tuple
with three elements (more on tuples
later). The function call sum(1,2,3)
is defined as so:
{:sum, [], [1, 2, 3]}
You can retrieve the representation of any macro via usage of the quote
macro:
iex> quote do: sum(1, 2, 3) {:sum, [], [1, 2, 3]}
Note: Additional to the quote
macro is the unquote
macro—you can read more on the topic in the Elixir documentation.
So we see the first element is the function name of :sum
, the second is the keyword list
(more on this later) which contains metadata (currently blank in the example), and the third is the arguments list.
All macros are built using these data structures, so this means our code can have the innate ability to modify and recompile its own code. So your application can now write its own code, check for faults in code and fix them, or even scan a plugin being added and check the code inside and modify it on the fly.
The implications for artificial intelligence are obviously huge—but first, in this guide we must continue with the basics and get a strong footing in the various data types used by Elixir before we begin delving deeper.
Basic Types
Now that you are in the interactive console, let's look at Booleans
.
iex> true true iex> true == false false
The standard behaviours of true
and false
are supported. To check the value, we use the function provided by Elixir, is_boolean
.
iex> is_boolean(true) true iex> is_boolean(1) false
Each type also has one of these predicate functions. For example, is_integer/1
, is_float/1
, or is_number/1
all will check if an argument is an integer, a float, or either.
Elixir always refers to functions this way, with a slash followed by a number to signify the number of arguments the function takes. So is_boolean/1
requires 1 argument, e.g. is_boolean(1)
.
Note: If you want to access help at any time in the interactive shell, just type h
and you will be able to access information on how to use the Elixir shell. Also, you can find information on any of Elixir's operators or functions by using h is_integer/1
to receive documentation on is_integer/1
.
Mathematically, we can also perform functions like so:
iex> round(3.58) 4 iex> trunc(3.58) 3
In Elixir, when division is performed on an integer
, the return is always a float
:
iex> 5 / 2 2.5 iex> 10 / 2 5.0
If we want to get the remainder of the division or do integer division, we can use the functions div/2
and rem/2
as so:
iex> div(10, 2) 5 iex> div 10, 2 5 iex> rem 10, 3 1
Note: Parentheses are not required for function calls.
You can also just enter any binary
, octal
, or hexadecimal
numbers into iex
.
iex> 0b1010 10 iex> 0o777 511 iex> 0x1F 31
Atoms (Symbols)
These are like constants
, but their name is their own value. Some languages refer to this as symbols. Booleans true
and false
are also examples of symbols.
iex> :hello :hello iex> :hello == :world false
Tuples
Similar to lists
and defined by curly braces, they can contain any data like so:
iex> {:ok, "hello"} {:ok, "hello"} iex> tuple_size {:ok, "hello"} 2
Lists
Similar to tuples
, you define a list
with square brackets like so:
iex> [1, 2, true, 3] [1, 2, true, 3] iex> length [1, 2, 3] 3
Two lists can be concatenated and subtracted also:
iex> [1, 2, 3] ++ [4, 5, 6] [1, 2, 3, 4, 5, 6] iex> [1, true, 2, false, 3, true] -- [true, false] [1, 2, 3, true]
To return the start of a list or the end of the list, we use the hd
and tl
functions (short for head and tail).
What's the Difference Between Lists and Tuples?
Lists
are stored in memory as linked lists, a value pair list that is iterated and accessed in a linear operation. So updating is fast as long as we are appending to the list
, but if we are modifying inside the list
, the operation will be slower.
Tuples
, on the other hand, are stored contiguously in memory. This means that getting the tuple total size or accessing just one element is fast. But here, in comparison to the lists, appending to an existing tuple
is slow and requires copying the whole tuple
in memory internally.
Anonymous Functions
We can define a function by using the fn
and end
keywords.
iex> myFunc = fn a, b -> a + b end #Function<12.71889879/2 in :erl_eval.expr/5> iex> is_function(myFunc) true iex> is_function(myFunc, 2) true iex> is_function(myFunc, 1) false iex> myFunc.(1, 2) 3
Elixir's manual refers to Anonymous Functions as “first class citizens”. This means that you can pass arguments to other functions the same way as integers or strings can.
So, in our example, we have declared the anonymous function in the variable myFunc
to the is_function(myFunc)
checking function, which correctly returned true
. This means we have successfully created our function.
We can also check the number of arguments of our myFunc
function by calling is_function(myFunc, 2)
.
Note: Using a dot (.
) between the variable and parenthesis is required to call an anonymous function.
Strings
We define Strings in Elixir between double quotes:
iex> "yo" "yo"
Interpolation is supported also with the #
sign:
iex> "Mr #{:Smith}" "Mr Smith"
For concatenation, use <>
.
iex> "foo" <> "bar" "foobar"
To get the length of a string, use the String.length
function:
iex> String.length("yo") 2
To split a string based on a pattern, you can use the String.split
method:
iex> String.split("foo bar", " ") ["foo", "bar"] iex> String.split("foo bar!", [" ", "!"]) ["foo", "bar", ""]
Keyword Lists
Commonly used in programming, a key-value pair of two data entries, essentially two item tuples
, can be created like so where the key is an atom
:
iex> list = [{:a, 1}, {:b, 2}] [a: 1, b: 2] iex> list == [a: 1, b: 2] true
Elixir has a syntax for defining lists as [key: value]
. We can then use any of the other operators available in Elixir such as ++
to append to the back or front of the list.
iex> list ++ [c: 3] [a: 1, b: 2, c: 3] iex> [a: 0] ++ list [a: 0, a: 1, b: 2]
Keyword lists have three important points to remember:
- Keys must be
atoms
. - Keys are ordered, as specified by the developer.
- Keys can be given more than once.
For database queries, the Ecto Library makes use of this when performing a query like so:
query = from w in Weather, where: w.prcp > 0, where: w.temp < 20, select: w
Elixir's if
macro also incorporates this into its design:
iex> if(false, [{:do, :this}, {:else, :that}]) :that
In general, when a list is an argument to a function, the square brackets are optional.
Maps
Similar to Keyword lists, maps
are there to help your key-value pair needs. They're created via the %{}
syntax as so:
iex> map = %{:a => 1, 2 => :b} %{2 => :b, :a => 1} iex> map[:a] 1 iex> map[2] :b iex> map[:c] nil
They're similar to keyword lists, but not identical—there are two differences:
- Maps allow any value as a key.
- Maps’ keys do not follow any ordering.
Note: When you set all the keys in map
as atoms
, the keyword syntax can be very convenient:
iex> map = %{a: 1, b: 2} %{a: 1, b: 2}
Matching
Maps are very good for pattern matching, unlike keyword lists. When a map
is used in a pattern, it will always match on a subset of a given value.
iex> %{} = %{:a => 1, 2 => :b} %{2 => :b, :a => 1} iex> %{:a => a} = %{:a => 1, 2 => :b} %{2 => :b, :a => 1} iex> a 1 iex> %{:c => c} = %{:a => 1, 2 => :b} ** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}
A pattern will always match as long as the keys exist in the provided map. So to match all, we use an empty map.
To take maps further, the Map Module provides a powerful API to manipulate maps:
iex> Map.get(%{:a => 1, 2 => :b}, :a) 1 iex> Map.to_list(%{:a => 1, 2 => :b}) [{2, :b}, {:a, 1}]
Nested Data Structures
Data often requires hierarchies and structuring. Elixir provides support for maps
inside maps
, or keyword lists
inside maps
and so on. Manipulation of this is made convenient using the put_in
and update_in
macros, which can be used as follows:
Let's say we have the following:
iex> users = [ john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]}, mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]} ] [john: %{age: 27, languages: ["Erlang", "Ruby", "Elixir"], name: "John"}, mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}]
So now we have some data on the table to play with, and each keyword list of users has a map containing the name, age, and some other information. If we want to access John's age, we can do the following:
iex> users[:john].age 27
It happens we can also use this same syntax for updating the value:
iex> users = put_in users[:john].age, 31
[john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}]
RegEx
Elixir uses Erlang's :re
module, which is based on the PCRE. You can find more information in the documentation.
To create a Regular expression in Elixir, use the Regex.compile
method or the special short-hand forms ~r
or ~R
.
# A simple regular expression that matches foo anywhere in the string ~r/foo/ # A regular expression with case insensitive and Unicode options ~r/foo/iu
The Regex module has a plethora of useful functions that you can use to validate regex expressions, compile them, and escape correctly. Here are some examples:
iex> Regex.compile("foo") {:ok, ~r"foo"} iex> Regex.compile("*foo") {:error, {'nothing to repeat', 0}} iex> Regex.escape(".") "\\." iex> Regex.escape("\\what if") "\\\\what\\ if" iex> Regex.replace(~r/d/, "abc", "d") "abc" iex> Regex.replace(~r/b/, "abc", "d") "adc" iex> Regex.replace(~r/b/, "abc", "[\\0]") "a[b]c" iex> Regex.match?(~r/foo/, "foo") true
You can also run a regex over a map
and retrieve the results with the named_captures
method:
iex> Regex.named_captures(~r/c(?<foo>d)/, "abcd") %{"foo" => "d"} iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "abcd") %{"bar" => "d", "foo" => "b"} iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "efgh") nil
Conclusion
Elixir is a fully featured meta-programming environment based on the usage of macros fundamentally. It can help to bring you as a developer to a new level of application design and data structuring thanks to its powerful lists
, maps
, and anonymous functions
when used in conjunction with its meta-programming capabilities.
We can look at specific solutions in our approaches, which in non-macro languages may have taken many lines or classes of code to create, due to the powerful wealth offered to Elixir in the form of its adoption of DSL (Domain Specific Language) and Erlang's Modules.
Key-pair value store manipulation, list
and basic data manipulation are just a fraction of the full spectrum offered to developers. We'll cover all of this in more detail as we continue in the next parts of the series.
Thursday, January 12, 2017
Wednesday, January 11, 2017
Tuesday, January 10, 2017
Monday, January 9, 2017
Integrate External Libraries in OpenCart Using Composer
Almost every framework nowadays has built-in support of Composer, an awesome dependency management tool in PHP, and OpenCart is no exception. In this tutorial, you'll learn how to use Composer to integrate external libraries in OpenCart.
The Role of Composer in OpenCart
Since the introduction of OpenCart 2.2, the Composer-based workflow is supported. So go ahead and grab the latest version of OpenCart; as of writing this, it's 2.3.0.2. Make sure that you install and configure the latest version that you've downloaded as that will be useful later in the article.
Explore the directory structure and you'll notice certain differences compared to earlier versions of OpenCart. In the context of this article, the interesting candidates are the composer.json file and the vendor directory.
Let's quickly go through the composer.json file.
{ "name": "opencart/opencart", "type": "project", "description": "OpenCart", "keywords": ["opencart", "ecommerce", "framework", "opensource"], "homepage": "http://www.opencart.com", "license": "GPL-3.0+", "require": { "cardinity/cardinity-sdk-php": "^1.0", "braintree/braintree_php" : "3.2.0", "leafo/scssphp": "0.0.12", "divido/divido-php": ">=1.1.1", "klarna/kco_rest": "^2.2", "php": ">=5.4.0" } }
Although a discussion of Composer syntax is out of the scope of this article, let's quickly go through what it says in layman terms.
First, the OpenCart project itself is now available as a library, so you could install it using Composer itself without manually downloading it from the site. Further, it also requires other third-party libraries to work properly, like divido, leafo, etc. Of course, you don't need to worry about it as that will be handled automatically when you run related Composer commands.
When you install a new library, the related entry will be added to the composer.json file. The related library files are placed under the vendor directory at the same level. Just explore that directory and you should see the libraries are installed already!
The vendor directory also contains autoload.php, generated by Composer itself, which makes sure that the libraries are loaded automatically in OpenCart, so you could use it straight away. Of course, OpenCart includes autoload.php while bootstrapping the project.
So that's a quick introduction of how Composer works with OpenCart. For demonstration purposes, we'll install the popular PHPMailer library using Composer.
Install PHPMailer Using Composer
The PHPMailer is a popular PHP library that's used to send emails. We'll install it in OpenCart using Composer. So go to your terminal and change the directory so that you are at the same level where the vendor directory and composer.json file reside.
Now, run the command composer require phpmailer/phpmailer
and press enter! Assuming that everything goes fine, it should look like the following.
$composer require phpmailer/phpmailer Using version ^5.2 for phpmailer/phpmailer ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) - Installing phpmailer/phpmailer (v5.2.16) Downloading: 100% phpmailer/phpmailer suggests installing league/oauth2-google (Needed for Google XOAUTH2 authentication) Writing lock file Generating autoload files
So that's it! PHPMailer is downloaded and installed successfully, and that's the beauty of Composer! Verify that by looking into the vendor directory, and you'll find it installed in the phpmailer/phpmailer directory.
Also, let's open composer.json to see what it looks like.
{ "name": "opencart/opencart", "type": "project", "description": "OpenCart", "keywords": ["opencart", "ecommerce", "framework", "opensource"], "homepage": "http://www.opencart.com", "license": "GPL-3.0+", "require": { "cardinity/cardinity-sdk-php": "^1.0", "braintree/braintree_php" : "3.2.0", "leafo/scssphp": "0.0.12", "divido/divido-php": ">=1.1.1", "klarna/kco_rest": "^2.2", "php": ">=5.4.0", "phpmailer/phpmailer": "^5.2" } }
As you can see, the entry "phpmailer/phpmailer": "^5.2"
is added into the require
section. So it means that your project requires PHPMailer to work properly.
Let's assume that you're working with other developers and need to share your work regularly. In that case, you just need to share your composer.json file with them and the rest will be handled by Composer itself! They just need to run the composer update
command, and that should take care of installing the required dependencies in their copy!
Now, we've installed PHPMailer using Composer, but how to use it? Don't worry, I won't leave you that soon—that's exactly the recipe of our next section!
How to Use the PHPMailer Library?
You've already done yourself a favor by using Composer to install the PHPMailer library, and you'll witness it in this section as we explore how straightforward it is to use in the code.
For example purposes, we'll build a pretty simple custom controller file that you could call to send an email.
Open your favorite text editor and create example/email.php
under the catalog/controller
directory with the following contents.
<?php class ControllerExampleEmail extends Controller { public function index() { // just instantiate the mailer object, no need to include anything to use it. $objPhpMailer = new PHPMailer(); $objPhpMailer->From = "sender@example.com"; $objPhpMailer->FromName = "Sajal Soni"; $objPhpMailer->AddAddress("receiver@example.com"); $objPhpMailer->WordWrap = 50; $objPhpMailer->IsHTML(true); $objPhpMailer->Subject = "Subject"; $objPhpMailer->Body = "<h2>HTML Body</h2>"; $objPhpMailer->AltBody = "Plain Body"; if(!$objPhpMailer->Send()) { echo "Message could not be sent. <p>"; echo "Mailer Error: " . $objPhpMailer->ErrorInfo; exit; } echo "Message has been sent"; exit; } }
You could test it by accessing your site via http://your-opencart-site-url/index.php?route=example/email.
In the index
method, you can see that we've instantiated the PHPMailer
object without any include statements that would have included the required PHPMailer classes had we not used a Composer-based workflow. You've got it right, it's auto-loaded by OpenCart itself. Recall that autoload.php
in the vendor directory does all the magic!
Following that is some pretty standard stuff required by PHPMailer to send an email. Of course, I've tried to keep the example as simple as possible since the discussion of PHPMailer requires a separate article!
So, that was a quick and simple introduction of how you could use Composer with OpenCart to integrate external libraries.
Conclusion
In this article, we've just scratched the surface of a Composer-based workflow in OpenCart to use third-party libraries in your project. Not to mention that Composer is the future of dependency management tools in PHP. So it's always good to get your hands dirty with that as it's becoming the standard in all popular frameworks.
Queries and suggestions are always appreciated!