Hi, I’m Andrew, a programer and entrepreneur from South Africa, founder of
Sitesure
for monitoring websites, APIs, and background jobs. Thanks for visiting and reading.
If you want to view the SQL query used to construct the information returned from a psql command (which will help you learn the underlying information schema) then type
\set ECHO_HIDDEN
$ psql test
psql (9.4.1)
Type "help" for help.
test=# \set ECHO_HIDDEN
test=# \dt
********* QUERY **********
SELECT n.nspname as "Schema",
c.relname as "Name",
CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' END as "Type",
pg_catalog.pg_get_userbyid(c.relowner) as "Owner"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','')
AND n.nspname <> 'pg_catalog'
AND n.nspname <> 'information_schema'
AND n.nspname !~ '^pg_toast'
AND pg_catalog.pg_table_is_visible(c.oid)
ORDER BY 1,2;
**************************
List of relations
Schema | Name | Type | Owner
--------+------+-------+--------
public | temp | table | andrew
(1 row)
I recently had a requirement where I needed an account to have zero, one or two actions associated with it. One could be a single action and the other could be one of many repeating types. I didn’t want two single actions and I didn’t want two or more types of repeating actions. To solve this I used two partial indexes to split the data set and apply a unique constraint to each set.
CREATE TABLE accounts (
id integer NOT NULL,
name text NOT NULL
);
CREATE TABLE actions (
id integer NOT NULL,
account_id integer NOT NULL,
repeat_type text NOT NULL DEFAULT 'none'
);
INSERT INTO accounts (id, name) VALUES (1, 'Test 1'), (2, 'Test 2');
If I create a unique index on actions(account_id) then I will only be able to have a single action per account.
CREATE UNIQUE INDEX idx_unique_accounts ON actions(account_id);
INSERT INTO actions (id, account_id, repeat_type) VALUES (1, 1, 'none');
-- INSERT 0 1
INSERT INTO actions (id, account_id, repeat_type) VALUES (1, 1, 'weekly');
-- ERROR: duplicate key value violates unique constraint "idx_unique_accounts"
-- DETAIL: Key (account_id)=(1) already exists.
DROP INDEX idx_unique_accounts;
The solution is to create two partial indexes, one for the single action and one for the repeating action.
TRUNCATE TABLE actions;
CREATE UNIQUE INDEX idx_unique_single_actions ON actions(account_id) WHERE (repeat_type = 'none');
CREATE UNIQUE INDEX idx_unique_repeating_actions ON actions(account_id) WHERE (repeat_type != 'none');
INSERT INTO actions (id, account_id, repeat_type) VALUES (1, 1, 'none');
-- INSERT 0 1
INSERT INTO actions (id, account_id, repeat_type) VALUES (1, 1, 'weekly');
-- INSERT 0 1
Now inserting another single action will result in an error.
Fibers are code blocks that can be paused and resumed. They are unlike threads because they never run concurrently. The programmer is in complete control of when a fiber is run. Because of this we can create two fibers and pass control between them.
Control is passed to a fiber when you call Fiber#resume, the Fiber returns control by calling
Fiber.yield
fiber = Fiber.new do
Fiber.yield 'one'
Fiber.yield 'two'
end
puts fiber.resume
#=> one
puts fiber.resume
#=> two
The above example shows the most common use case where Fiber.yield
is passed an argument which is returned through Fiber#resume.
What’s interesting is that you can pass an argument into the fiber via Fiber#resume
as well. The first call to Fiber#resume
starts the fiber and that argument goes to the block that creates the fiber, all subsequent calls to
Fiber#resume
have their arguments passed to Fiber.yield.
fiber = Fiber.new do |arg|
puts arg # prints 'one'
puts Fiber.yield('two') # prints 'three'
puts Fiber.yield('four') # prints 'five'
end
puts fiber.resume('one') # prints 'two'
#=> one
#=> two
puts fiber.resume('three') # prints 'four'
#=> three
#=> four
puts fiber.resume('five') # prints nil because there's no corresponding yield and the fiber exits
#=> nil
Armed with this information, we can setup two fibers and get them to communicate between each other.
require 'fiber'
fiber2 = nil
fiber1 = Fiber.new do
puts fiber2.resume # start fiber2 and print first result (1)
puts fiber2.resume 2 # send second number and print second result (3)
fiber2.resume 4 # send forth number, print nothing and exit
end
fiber2 = Fiber.new do
puts Fiber.yield 1 # send first number and print returned result (2)
puts Fiber.yield 3 # send third number, print returned result (4) and exit
end
fiber1.resume # start fiber1
#=> 1
#=> 2
#=> 3
#=> 4
puts "fiber1 done" unless fiber1.alive?
#=> fiber1 done
puts "fiber2 done" unless fiber2.alive?
#=> fiber2 done
EachGroup module
Knowing we can send information between two fibers with alternating calls of
Fiber#resume
and Fiber.yield, we have the building blocks to tackle a streaming #each_group method.
Tip:
The fiber you first call #resume
on should always call #resume
on the fiber it is communicating with. The other thread then always calls Fiber.yield. This goes against the natural inclination to pass information with
Fiber.yield
as in the first example above. Because of how the two fibers are setup below, you’ll see that no information is passed with Fiber.yield, information is only passed using
Fiber#resume
—confusing, I know.
# -*- coding: utf-8 -*-
require 'fiber'
module EachGroup
def each_group(*fields, &block)
grouper = Grouper.new(*fields, &block)
loop_fiber = Fiber.new do
each do |result|
grouper.process_result(result)
end
end
loop_fiber.resume
end
class Grouper
def initialize(*fields, &block)
@current_group = nil
@fields = fields
@block = block
end
attr_reader :fields, :block
attr_accessor :current_group
def process_result(result)
group_fiber = get_group_fiber(result)
group_fiber.resume(result) if group_fiber.alive?
end
private
def get_group_fiber(result)
group_value = fields.map{|f| result.public_send(f) }
unless current_group == group_value
self.current_group = group_value
create_group_fiber(result, group_value)
end
@group_fiber
end
def create_group_fiber(result, group_value)
@group_fiber = Fiber.new do |first_result|
group = Group.new(group_value)
block.call(group)
end
@group_fiber.resume(nil) # Start the fiber and wait for its first yield
end
end
class Group
def initialize(value)
@value = value
end
attr_reader :value
def each(&block)
while result = Fiber.yield
block.call(result)
end
end
end
end
This code can be used with ActiveRecord as follows:
ActiveRecord::Relation.send(:include, EachGroup)
Model.order('year, month').each_group do |group|
group.each do
# ...
end
end
I have uploaded a Gist
that shows a previous iteration of the EachGroup module using a nested loop which you may find easier to use to understand how the fibers are used to control the flow of the loop.
Thanks for taking the time to read through this. Explaining complicated concepts like Fibers is a challenge, please leave a comment and let me know if this was helpful or if you still have any questions.
I’m working on an app that creates user accounts and (optionally) subscribes users to our mailing list. Because I’m handling user creation in my app, I need some way to add them to the mailing list which is hosted on MailChimp. To do this, I am using their
API
to send through subscriber information.
The documentation for the ruby gem is not great. You have a few choices:
The source code for the gem
which is auto-generated. Once you get a feel for this you’ll have a better idea of how the API docs translate to gem methods.
In MailChimp, go to your account settings
page, click Extras
and API Keys. If you don’t have an API key yet, click Create A Key.
Get your MailChimp list ID
Every list has a unique ID which is needed to add subscribers to the correct list. Got to Lists, Click on your list name, Click
Settings
and List name & defaults. On the right you’ll see your List ID (a 10 character hex code).
The code
require 'mailchimp' # The gem name is mailchimp-api but you require mailchimp
module MailChimpSubscription
# These should prabably be environment variables or configuration variables
MAIL_CHIMP_API_KEY = "0000000001234567890_us1"
MAIL_CHIMP_LIST_ID = "abcdef1234"
extend self
def subscribe(user)
mail_chimp.lists.subscribe(MAIL_CHIMP_LIST_ID,
# The email field is a struct that can use an
# email address or two MailChimp specific list ids (see API docs)
{email: user.email},
# Set your merge vars here
{'FNAME' => user.first_name, 'LNAME' => user.last_name})
rescue Mailchimp::ListAlreadySubscribedError
# Decide what to do if the user is already subscribed
rescue Mailchimp::ListDoesNotExistError => e
# This is definitely a problem I want to know about
raise e
rescue Mailchimp::Error => e
# Unforeseen errors that need to be dealt with
end
private
def mail_chimp
@mail_chimp ||= Mailchimp::API.new(MAIL_CHIMP_API_KEY)
end
end
To use this module, you pass in a user object that responds to #email, #first_name and #last_name
user = OpenStruct.new(email: 'test@example.com', first_name: 'John', last_name: 'Doe')
MailChimpSubscription.subscribe(user)
Final thoughts
It’s probably a good idea to put mailing list subscription into a background job so that you don’t slow down your user creation response time. You can also handle transient errors, retry failed attempts etc.
I got a message from a client this morning telling me that all users could see all reports on our product. Not good. I use CanCan to manage permissions and until now it has served me well. What went wrong? Whether a bug or not, I discovered that a very recent change I made had openned up the hole.
I wanted to have a permission setting that could prevent anyone from seeing any reports as well as more fine grained control over each individual report. My permissions looked a bit like this:
class Ability
def initialize(user)
can :read, Reports
can :read, Reports::ReportA
end
end
When checking permissions for another report within the module, I didn’t expect this:
module Reports
class ReportBController
def show
authorize! :read, Reports::ReportB #=> I assumed it would not be authorized but it is
...
end
end
end
What I didn’t expect is that when you authorise a module, all classes in that namespace are authorised as well.
As I mentioned above, I don’t know if this is by design or not. Some quick googling didn’t help me so I changed my code for a quick solution.
I post this to warn others who may have made the same assumption.
If you’re reading this and know the project better and can point out if it is a bug or feature, please let me know in the comments.
“If it is I who determine where God is to be found, then I shall always find a God who corresponds to me in some way, who is obliging, who is connected with my own nature. But if God determines where he is to be found, then it will be in a place which is not immediately pleasing to my nature and which is not at all congenial to me. This place is the Cross of Christ. And whoever would find him must go to the foot of the Cross, as the Sermon on the Mount commands. This is not according to our nature at all, it is entirely contrary to it. But this is the message of the Bible, not only in the New Testament but also in the Old Testament.”—Dietrich Bonhoeffer
This tutorial specifically covers Logos 5 but things should also work in Logos 4 though the menus and tools may be in different places.
The Highlighter Tool
To get started you need to open the highlighting tool. Click on Tools
and then Highlighting.
Accessing the Highlighting Tool
You should see the highlighting tool with the default palettes like so:
The Highlighting Palettes
Each palette contains a few highlighters of similar types. To use them:
Select text in an open book
Click on the highlighter
Voila, the text is now highlighted.
Highlighting Text
Where the highlight is stored
By default your highlighting is stored in a notes document named after the palette you used. So in this example my highlight is stored in a notes document named Highlighter Pens. I like to save my notes and highlights in specific note documents. This can be done by clicking on the little icon that appears to the right of the highlight palette name as you hover your mouse over the name (or right-click on the name) and selecting “Save in…”
The option I tend to use is Save in: Most recent note file. When I begin work I will ensure that I have one notes document open in Logos for the specific task I’m working on. That becomes the
most recent note file
and all my highlights and notes go in there. Be careful that you don’t end up with two notes documents open or your highlights will go to the one you last accessed. Remember that you have to change the
Save in
setting for each palette.
Changing the Default
Removing Highlights
To remove a highlight:
Right-click somewhere in the highlight
Select Remove annotations
You can highlight a number of different highlights on the screen, right-click and click
Remove annotations
and all selected highlights will be removed.
Right-click to Remove Highlight
Creating Your Own Highlighter Pens
I like to have my Logos Bibles look like they’re underlined in pencil just like my real Bible. To do this, I’ve created my own highlighters. It’s super easy to do so I’m going to show you how.
On the Highlighters tab, click New palette
Give your new palette a name and click Enter
Create a New Highlighter Palette
Click on the arrow next to the Palette name (mouse over the name to see) (or right-click on the name) and select
Add a New StyleAdd a New Style
Create your new style by:
Giving it a name
Open Borders & Lines
Select Natural for the line style
Make sure Single is selected for the number of lines
Select a grey for the colour
Click on the line under the text
Keep an eye on the example window to make sure you’re getting what you want
Click Save
to finishCreating a New Style
This is a great place to play and personalise how your mark-up your books. Don’t be scared to create various styles or duplicate and modify existing styles from other palettes. You can also move styles between palettes.
Don’t forget to change your Save In: setting for your new palette.
Using the Keyboard
If you have to click the specific highlighter every time you want to highlight something, it becomes a little tedious and you have to always have the highlighters panel open and visible (which means you can’t use the screen for other important documents). To solve this, you can set keyboard shortcuts to your highlighters. Let’s add a keyboard shortcut to our new highlighter style.
Click the little arrow icon next to the highlighter (mouse over the highlighter) (or right-click) Mouse over the
Shortcut Key:
menu Select the letter you want to assign to your highlighter. In this case I chose U
for underlineAssign a Shortcut Key
Now you can highlight text in your book, click U
on your keyboard and your text will be underlined in a nice pencil line.
Text Underlined
I hope this tutorial was helpful and clear. If it wasn’t or you have a question, please feel free to ask in the comments below and I’ll do my best to answer them.