Goalkeeper

Goalkeeper is a simple library for tracking if, and when, a Goal has been met. A Set of goals can be combined to check if all of them have been completed.

This could be used as a checklist for process completion.

Examples

Check if your application ran its daily backup

# send the report and mark the report as sent
def backup
  # ... backup code ...
  Goalkeeper.met!("backup:#{Date.today}")
end

# elsewhere warn if any customer has not had a report sent
def did_backup_today?
  Goalkeeper.met?("backup:#{Date.today}")
end

Wait for a Set of Goals to be met before starting another.

# The email can't be sent until the 'prices' and 'awards' are downloaded
def email_report
  set = Goalkeeper::Set.new.
    add("prices:#{Date.today}").
    add("awards:#{Date.today}")

  if set.met?
    log.info "Downloads completed by #{set.met_at}"
    deliver
  else
    log.info "#{set.unmet.map(&:label).join(', ')} have not been downloaded"
    retry_later
  end
end

# elsewhere the code to get the data and mark it complete
def download_prices
  # ... download code ...
  Goalkeeper.met!('prices:#{Date.today}')
end

def download_awards
  # ... download code ...
  Goalkeeper.met!('awards:#{Date.today}')
end

Installation

Add this line to your application's Gemfile:

gem 'goalkeeper'

And then execute:

$ bundle

Or install it yourself as:

$ gem install goalkeeper

Usage

Its goes likes this...

You might never need to reference the Goal class. Most everything can be done via Goalkeeper and Goalkeeper::Set.

Goalkeeper.met? "label" #=> false
Goalkeeper.met! "label" #=> <Goalkeeper::Goal>
Goalkeeper.met? "label" #=> true

But lets have a granular example. Lets ensure we wakeup New Years Day 2020. The Goal will be named 'wakeup:2020-01-01'

g = Goalkeeper::Goal.new('wakeup:2020-01-01')
g.met? #=> false

Time flies... it is New Years Day 2020.

g.met! # or Goalkeeper.met!('wakeup:2020-01-01')
g.met? #=> true
g.met_at #=> 2020-01-01 05:01:31 -0500

Now if our application checks our goal, it will be met.

Goalkeeper.met?('wakeup:2020-01-01') #=> true
# or
Goalkeeper::Goal.new('wakeup:2020-01-01').met? #=> true

Note: Once a Goal is 'met' the 'met_at' timestamp will not change, unless 'clear!' is called.

We are probably only interested in this goal being complete for a limited time, so it will expire and be removed from Redis.

g.ttl #=> 86400 (1 day)

If you need to reference the Redis key

g.key #=> Goalkeeper:wakeup:2020-01-01

Finally deleting the Goal is simple

g.clear!
g.met? #=> false

Sets

Perhaps you have a series of Goals you want to track, and see if they all have been met, or not.

set = Goalkeeper::Set.new
set.add('goal1').add('goal2')
set.met? #=> false

Lets have 1 goal met:

Goalkeeper.met!('goal1')

But our set is not met yet

set.met? #=> false

See which goals are met, or unmet

set.met #=> [#<Goalkeeper::Goal @label="goal1">]
set.unmet #=> [#<Goalkeeper::Goal @label="goal2">]

Lets complete our set.

Goalkeeper.met!('goal1')
set.met? #=> true

See the time the final goal was met

set.met_at #=> 2015-01-01 08:02:15 -0500

Customization

Customize the redis client by setting it in your application

Goalkeeper.redis = your_redis_client

Each record has a default expiration of 24 hours, but this can be modified.

Goalkeeper.expiration = number_of_seconds

Redis keys are stored under the default namespace of "Goalkeeper:". The namespace can be configured:

Goalkeeper.namespace = string

API

Goal

method description example
new(label) Creates a new Goal with the unique label g = Goal.new('process')
met? Returns true if the Goal has been met g.met? #=> false
met! Marks the goal as completed with a timestamp g.met! #=> Time.now
met_at Nil if unmet, otherwise returns the time the Goal was met. g.met_at #=> 2015-02-09 22:24:07 -0500
ttl The Redis ttl value if there is a record for the goal. g.ttl #=> 86400
key The key used to store the record in Redis. g.key #=> "Goalkeeper:process"
clear! Deletes the met record. Goal is now unmet. g.clear!

Set

method description example
new creates an empty set of goals. s = set.new
add(label) Adds a new Goal the Set. chainable s.add('process.a').add('process.b')
met? Returns true if all the Set's Goals have been met. s.met? #=> false
met_at Returns the most recent met_at for the Set's Goals. Nil if no Goal met. s.met_at #=> 2015-02-09 22:24:07 -0500
met Returns a new Set with all Goals which have been met. s.met #=> Set(...)
unmet Returns a new Set with all Goals which have not been met. s.unmet #=> Set(...)

Goalkeeper

method description example
::met!(label) Creates a new Goal and marks it met. Goalkeeper.met!('process') #=> <Goalkeepr::Goal>
::met?(label) Checks if the label has been met or not Goalkeeper.met?('process') #=> true

Contributing

  1. Fork it ( https://github.com/jweir/goalkeeper/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request