Thermometer
Thermometer measures heat on your ActiveRecord Models. This initial release is for Rails 4, not yet tested for Rails 3.
Mixins
This plugin introduces two mixins to your recipe book:
- acts_as_thermometer : Extends Instance with temperature methods
- measure_temperature_for : Allows customization on an association
Motivation
This plugin was started as a way for Bots to quickly assess the state of a User across many dimensions of Game play to determine automated actions for retention. For example, if a User went cold with regards to reading messages, a reminder could be sent. Rather than dealing with dates and duration, coding uses a simplified syntax, for example :
user = User.first
user.*call_to_some_action* if user.unread_messages.has_temperature?(:cold) # false|true
user.*call_to_some_action* if user.unread_messages.is_colder_than?(:warm) # false|true
Installation
In your gemfile
gem "thermometer"
#or to pick up a dev branch
gem "thermometer" , github: 'blotto/temperature', :branch => "first_release"
Then at the command line
bundle update --source thermometer
rails generate thermometer:install
Configuration
After install a YAML file is placed in the config directory, config/thermometer.yml. For a detailed description of the available options, read the comments within the YAML file.
ActiveRecord Dependency
Models must have the managed columns created_at and updated_at in order to evaluate the temperature.
Usage
Measure the temperature on the Class , and on an Instance
class User < ActiveRecord::Base
acts_as_thermometer
end
User.has_temperature # lukewarm
User.is_colder_than? :warm # false
User.is_warmer_than? :cold # true
User.first.has_temperature # lukewarm
User.first.is_colder_than? :warm # false
User.first.is_warmer_than? :cold # true
Measure the temperature on any association. NOTE: declare measures_temperature_for
after your associations!
class User < ActiveRecord::Base
acts_as_thermometer
has_many :messages
has_many :oldest_messages, -> {where('created_at < ?', 4.months.ago)} , class_name: "Message"
has_many :recent_messages, -> {where('created_at > ? AND created_at < ?', 4.months.ago , 1.month.ago)} , class_name: "Message"
has_many :newest_messages, -> {where('created_at > ?', 1.month.ago)} , class_name: "Message"
measures_temperature_for :messages, :oldest_messages , :recent_messages , :newest_messages
end
User.first.messages.has_temperature # temperate
User.first.oldest_messages.has_temperature # frigid
User.first.recent_messages.is_colder_than? :warm # false
User.first.newest_messages.is_warmer_than? :cold # true
Method Chaining
You can check the temperature on scopes...
class User < ActiveRecord::Base
acts_as_thermometer
class << self
def name_like(substring)
where("name LIKE '%#{substring}%'")
end
end
def last_five_messages
messages.limit(5)
end
end
User.name_like("Ms.").has_temperature # temperate
User.name_like("Ms.").first.last_five_messages.has_temperature # warm
Reference Date
The temperature is measured by the age of a record or the average age of a set of records from a reference date.
By default, this reference date is DateTime.now. However you can change the reference date when making calls to
the three main methods of this Gem. For example , on has_temperature
:
User.first.has_temperature
=> "frosty"
User.first.has_temperature date_reference: DateTime.now # equivalent to above call
=> "frosty"
User.first.has_temperature date_reference: DateTime.now - 2.month # what was the temperature 2 months ago.
=> "temperate"
User.last.oldest_messages.has_temperature
=> "frosty"
User.last.oldest_messages.has_temperature date_reference: DateTime.now - 2.months
=> "chilly"
Why is this useful?
Iterating through a range of dates to find temperatures can provide data for a heat map. This code example iterates over the last month for a User.
heat_map = Hash.new
first = DateTime.now - 1.month
last = DateTime.now
first.upto(last).each do |d| heat_map[d.strftime('%F')] = ( User.first.has_temperature date_reference: d ) end
heat_map
=> {"2014-01-25"=>"cold", "2014-01-26"=>"cold", "2014-01-27"=>"cold", "2014-01-28"=>"cold", "2014-01-29"=>"cold",
"2014-01-30"=>"cold", "2014-01-31"=>"cold", "2014-02-01"=>"cold", "2014-02-02"=>"cold", "2014-02-03"=>"cold",
"2014-02-04"=>"cold", "2014-02-05"=>"cold", "2014-02-06"=>"cold", "2014-02-07"=>"cold", "2014-02-08"=>"cold",
"2014-02-09"=>"cold", "2014-02-10"=>"cold", "2014-02-11"=>"cold", "2014-02-12"=>"cold", "2014-02-13"=>"cold",
"2014-02-14"=>"frosty", "2014-02-15"=>"frosty", "2014-02-16"=>"frosty", "2014-02-17"=>"frosty",
"2014-02-18"=>"frosty", "2014-02-19"=>"frosty", "2014-02-20"=>"frosty", "2014-02-21"=>"frosty",
"2014-02-22"=>"frosty", "2014-02-23"=>"frosty", "2014-02-24"=>"frosty", "2014-02-25"=>"frosty"}
Options
In most cases, how you measure the temperature is consistent across Models, and Associations, therefore keep your customizations global by changing options in the YAML file. However, you could pass through customizations specific to a particular use case.
Configs can be passed through in a number of scenarios.
Overriding options via measures_temperature_for
class User < ActiveRecord::Base
acts_as_thermometer
has_many :messages
has_many :oldest_messages, -> {where('created_at < ?', 4.months.ago)} , class_name: "Message"
has_many :recent_messages, -> {where('created_at > ? AND created_at < ?', 4.months.ago , 1.month.ago)} , class_name: "Message"
has_many :newest_messages, -> {where('created_at > ?', 1.month.ago)} , class_name: "Message"
measures_temperature_for :messages, {:explicit=>true, :date => :updated_at} #only use options defined here
measures_temperature_for :recent_messages, {:date => 'updated_at'} #use this date, and default options
measures_temperature_for :newest_messages,
{:date => 'messages.updated_at', :sample => 3} #clearly defined date field, sampling 3 records
end
> User.first.messages.has_temperature
User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
(1.4ms) SELECT "messages"."updated_at" FROM "messages" WHERE "messages"."user_id" = ? [["user_id", 14035331]]
=> "frosty"
> User.first.newest_messages.has_temperature
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
(0.6ms) SELECT messages.created_at FROM "messages" WHERE "messages"."user_id" = ? AND (created_at >
'2014-01-19 23:44:59.413221') ORDER BY messages.created_at DESC LIMIT 3 [["user_id", 14035331]]
=> :none
Overriding options via method calls
You can pass options on all available methods.
> User.first.newest_messages.has_temperature :sample => 5
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
(0.4ms) SELECT messages.created_at FROM "messages" WHERE "messages"."user_id" = ? AND (created_at > '2014-01-20 00:04:54.558920') ORDER BY messages.created_at DESC LIMIT 5 [["user_id", 14035331]]
=> :none
> User.first.messages.has_temperature :date => 'created_at'
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
(0.4ms) SELECT created_at FROM "messages" WHERE "messages"."user_id" = ? [["user_id", 14035331]]
=> "freezing"
> User.is_colder_than? :warm, :sample => 5
(0.4ms) SELECT updated_at FROM "users" ORDER BY updated_at DESC LIMIT 5
=> false
> User.first.is_colder_than? :warm, :sample => 5
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> true
Using AREL Relations directly
You can chain methods on relations. Note, that without explicitly excluding them, defaults will be applied. This example, only samples the last record in the ActiveRecord collection because the default sample size is 1.
> User.where('created_at > ?', (Time.now - 3.weeks)).has_temperature
(0.5ms) SELECT updated_at FROM "users" WHERE (created_at > '2014-01-30 00:18:13.260406') ORDER BY updated_at DESC LIMIT 1
=> "lukewarm"
However, you can disable this by passing the option :explicit=>true
and sample the entire set :
> User.where('created_at > ?', (Time.now - 3.weeks)).has_temperature :explicit=>true
(0.4ms) SELECT updated_at FROM "users" WHERE (created_at > '2014-01-30 00:18:33.207988')
=> "temperate"
You can still apply other options :
> User.where('created_at > ?', (Time.now - 3.weeks)).has_temperature :explicit=>true , :date => :updated_at
(28.7ms) SELECT "users"."updated_at" FROM "users" WHERE (created_at > '2014-01-30 00:25:23.563479')
=> "temperate"
> User.where('created_at > ?', (Time.now - 3.weeks)).has_temperature :explicit=>true , :date => :updated_at, :sample => 6, :order => 'desc'
(0.4ms) SELECT "users"."updated_at" FROM "users" WHERE (created_at > '2014-01-30 00:30:26.123044') ORDER BY updated_at DESC LIMIT 6
=> "temperate"
Copyright
Copyright (c) 2014 Nick Newell. See LICENSE.txt for further details.