9780596521424
active-support.html

Chapter 13. Active Support

Active Support is a grab bag of utility methods and extensions to the Ruby standard library that accelerate development by providing syntactic sugar and easy ways to write expressive and DRY code. It makes time-related calculations easy, provides memoization & caching, handles multibyte/unicode operations, and offers all sorts of other convenience methods and nifty tricks. This chapter documents some of the most useful bits and pieces.

Note that while Rails makes all these class extensions and methods available to you everywhere within your application, you can use Active Support outside of a Rails application by just requiring it in:

require "active_support/all"  # Note the /all part
%w(1 2 3 4 5 6 7).in_groups_of(3) 
# => [["1", "2", "3"], ["4", "5", "6"], ["7", nil, nil]]

If you can get away with it, it’s possible to just require the parts of Active Support you use, so take a look at the source for Active Support should you choose to go down that route.

Note

When requiring-in Rails-related libraries, be sure to use an underscore in separating their names:

require "active_support/all"  # Do this

require "activesupport/all"   # Don't do this.

The version without an underscore will work, but brings in the older versions of the library (Rails 2 and previous), which might lead to frustrating mysteries down the road.

Object

The Object class gets several upgrades from Active Support:

Object#blank?

Returns true if the receiver is false, empty, or a string of whitespace:

"Keep calm and carry on".blank? # => false
"    ".blank?                   # => true
"".blank?                       # => true
[].blank?                       # => true
["apple"].blank?                # => false
42.blank?                       # => false
true.blank?                     # => false
nil.blank?                      # => true

This behaviour means you can replace code like this

if !title.nil? && !title.empty?
  # do something
end

with this instead:

if !title.blank?
  # do something
end
Object#present?

Returns the negation of Object#blank? – let’s use that to reduce the previous example to just

if title.present?
  # do something
end
Object#to_query(key)

Returns a URL query string using the key as the parameter name and the receiver as the value:

"Snow".to_query("white")     # => "white=Snow"
7.to_query("stinky dwarves") # => "stinky+dwarves=7"
Object#try(method, *args, &block)

Attempts to call a method with whatever arguments and optional block. In the event the method doesn’t exist, it returns nil instead of raising a NoMethod exception.

What this means is that you can turn lines like

@video && @person.title

into

@video.try(:title)

Class

Active Support extends the Class class to provide Class#class_attribute, which functions just like the attr_reader, attr_writer, and attr_accessor methods for instances in standard Ruby, but at a class level:

class Video < ActiveRecord::Base
  class_attribute :commentable
end

class CellphoneVideo < Video
end

Video.commentable = true
CellphoneVideo.commentable  # => true
CellphoneVideo.commentable = false
CellphoneVideo.commentable  # => false
Video.commentable   # => true

# For convenience, a query method is also supplied
Video.commentable?  # => true

Class attributes are inherited and overwritable by subclasses. Class#class_attribute also produces instance attribute accessors by default:

Video.commentable = true
v = Video.new

v.commentable? # => true
v.commentable = false
v.commentable?  # => false
Video.commentable # => true

To disable the creation of instance writers (to just have instance readers), pass in :instance_writer => false

class Video < ActiveRecord::Base
  class_attribute :commentable, :instance_writer => false
end

Video.commentable = true
v = Video.new
v.commentable  # => true
v.commentable = false  # => raises NoMethodError: undefined method `commentable='

Module

Active Support extends the Module class to provide several convenience methods for aliasing:

Module#alias_attribute(new_name, old_name)

Provides a more complete interface compared to just using alias or alias_method to give another name to an Active Record model’s attribute:

class Video < ActiveRecord::Base

  # Adds #name-version aliases for:
  #   #title
  #   #title?
  #   #title=
  alias_attribute :name, :title
end

v = Video.first

v.name  # => "John Candy"
v.name?  # => true
v.name = "Canadian Bacon"  # => "Canadian Bacon"
v.title  # => "Canadian Bacon"
Module#alias_method_chain(target, feature)

Makes writing method wrappers using aliases easy.

Let’s say that you want to wrap each call to a method Video#calculate_rating with a logging statement indicating that a rating has been calculated.

One way to do it would be the following:

class Video < ActiveRecord::Base
  def calculate_rating
    # does some kind of rating calculation
  end
  
  def calculate_rating_with_logging(*args)
    logger.info "Calulating a rating!"
    calculate_rating_without_logging(*args)
  end
  
  alias_method :calculate_rating_without_logging, :calculate_rating
  alias_method :calculate_rating, :calculate_rating_with_logging
end

By using aliases to wrap the #calculate_rating method we’ve unobtrusively added logging. This pattern is popular enough that Active Support extended Module to have a method alias_method_chain(target, feature) which does some meta-programming to specifically look for a method named target_with_feature and constructs the corresponding aliases for you to use. Here’s how to rewrite the above using it:

class Video < ActiveRecord::Base
  def calculate_rating
    # does some kind of rating calculation
  end
  
  def calculate_rating_with_logging(*args)
    logger.info "Calulating a rating!"
    calculate_rating_without_logging(*args)
  end
  
  alias_method_chain :calculate_rating, :logging
end

The key to alias_method_chain is that we’ve written a method according to the expected pattern named correctly: calculate_rating_with_logging – again, going by the method definintion, that’s target_with_feature and making use of target_without_feature inside of it.

Warning

This pattern used to be popular and we’re covering it here so you know how it works and to use it when you come across it. However, by extending or overriding the functionality of a method using alias_method_chain, it becomes more difficult down the road to extend.

Whenever you see this method being used to override a method, there’s usually a more maintainable way to do the same using inheritance and the super keyword. For more on what that’s about, see Yehuda Katz’s post on alias_method_chain.

NilClass

Curious about the following exception?

RuntimeError: Called id for nil, which would mistakenly be 4 -- if you really
wanted the id of nil, use object_id

It’s Active Support’s extension of NilClass at work.

When you see that exception raised, it’s probably because an object you’re calling #id on is nil, but you expected it to be an ActiveRecord::Base object. Code like

@video = Video.find_by_title(params[:title])

puts @video.id

could cause this exception to be raised if params[:title] is nil.

These kinds of exceptions are a sign that your code needs more validation to check if values like params[:title] or the resulting @video in this case aren’t nil.

This feature is active by default when running in development and test environments, and disabled in production.

Disable it manually by setting

config.whiny_nils = false

in config/application.rb

Integer

Integer#multiple_of?(number)

Returns true or false whether the integer is evenly divisible by number:

8.multiple_of?(2)  # => true
9.multiple_of?(3)  # => true
10.multiple_of?(3) # => false

Float

Float#round(precision = nil)

Rounds the Float by the given precision:

x = 1.337
x.round    # => 1
x.round(1) # => 1.3
x.round(2) # => 1.34

Numeric

Active Support augments the Numeric class to naturally have an interface for working with byte-sized numbers:

1.byte  # => 1
2.bytes # => 2

1.kilobyte # => 1024
1.megabyte # => 1048576
1.gigabyte # => 1073741824
1.terabyte # => 1099511627776
1.petabyte # => 1125899906842624
1.exabyte  # => 1152921504606846976

1.kilobyte + 2.megabytes + 42.terabytes # => 46179490464768

Hash

Hash#diff(other_hash)

Returns a Hash consisting of the strict difference between the Hash and the other_hash:

        {1 => 2}.diff(1 => 2) # => {}
        {1 => 2}.diff(1 => 3) # => {1 => 2}
              {}.diff(1 => 2) # => {1 => 2}
{1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4}
Hash#slice(*keys)

Returns a copy of the Hash with only the given keys. Useful for limiting an options hash to a set of valid keys prior to processing.

{:mass => 2, :speed => 4, :deliciosity => 6}.slice(:mass, :speed)
# => {:mass=>2, :speed=>4}

When limiting based on an Array of keys, splat the Array when passing it to #slice:

valid_keys = [:mass, :speed]

{:mass => 2, :speed => 4, :deliciosity => 6}.slice(*valid_keys)
# => {:mass=>2, :speed=>4}

Hash#slice! is also available, replacing the Hash in-place.

Hash#except(*keys)

Returns a copy of the Hash without the given keys:

{:a => 1, :b => 2}.except(:a)    # => {:b => 2}

If the Hash or Hash class-descendent responds to #convert_key, that method is called on each key prior to determining if the key/value pair should be included in the returned results. (This is how Rails’ hashes with indifferent access work:

{:a => 1}.with_indifferent_access.except("a") # => {}

Hash#except! also exists, destructively modifying the Hash in-place.

Hash#extract!(*keys)

Returns a Hash using the given keys, removing those key/value pairs extracted.

h = {"rock" => "lobster", "king" => "crimson"}

h.extract!("king") # => {"king" => "crimson"}
h  # => {"rock" => "lobster"}
Hash#assert_valid_keys(*valid_keys)

Asserts that the Hash’s keys are valid_keys, raising ArgumentError upon a mismatch or absence.

h = {:name => "Edward", :age => 26}
                                
h.assert_valid_keys(:name, :age)  # => no exception raised
h.assert_valid_keys(:name, :hair_colour) # => mismatch; exception raised
h.assert_valid_keys(:name)  # => absence; exception raised
Hash#reverse_merge(other_hash)

Merges an other_hash with the Hash, giving precedent to the Hash this method is called on.

{:red => "Swingline"}.reverse_merge(:blue => "Whale")
# => {:blue=>"Whale", :red=>"Swingline"}

{:red => "Swingline"}.reverse_merge(:red => "Ninja Turtle")
# => {:red=>"Swingline"}

Hash#reverse_merge!, the in-place, destructive version, is particularly useful for initializing an option hash with default values in a way that reads nicely:

def setup(options = {})
  options.reverse_merge! :size => 25, :velocity => 10
end

This example setup method sets a default :size and :velocity if the options hash passed in didn’t set them.

Hash#deep_merge(other_hash)

Returns a new Hash produced from merging the Hash this method is called on, and the other_hash, merged recursively with precedence given to other_hash.

hash_1 = { :a => "a",
           :b => "b",
           :c => { :c1 => "c1", 
                   :c2 => "c2", 
                   :c3 => { :d1 => "d1" } } }
hash_2 = { :a => 1, 
           :c => { :c1 => 2, 
                   :c3 => { :d2 => "d2" } } }
                                
hash_1.deep_merge!(hash_2)
# => { :a => 1, 
#      :b => "b", 
#      :c => { :c1 => 2, 
#              :c2 => "c2", 
#              :c3 => { :d1 => "d1", 
#                       :d2 => "d2" } } }

A Hash#deep_merge! variant also exists, destructively altering the Hash in-place.

Hash#to_param(namespace = nil)

Returns a URL query string using the keys and values of the Hash this method is called on:

{ :name => 'Edward', :nationality => 'Canadian' }.to_param
# => "name=Edward&nationality=Canadian"

When an optional namespace is provided, it’s used accordingly:

{ :name => 'Edward', :nationality => 'Canadian' }.to_param('author')
# => "author[name]=Edward&author[nationality]=Canadian"
Hash#to_query

An alias for #to_param.

Array

The standard Enumerable module in Ruby is extended by Active Support in order to provide instance methods like the following:

Array#in_groups_of(number, fill_with = nil)

Splits up an Array into groups containing number elements, optionally filling in remainder slots with fill_with unless it’s false:

[1, 2, 3, 4, 5, 6, 7].in_groups_of(3)
# => [[1, 2, 3], [4, 5, 6], [7, nil, nil]]

[1, 2, 3, 4, 5, 6, 7].in_groups_of(3, '&nbsp;')
# => [[1, 2, 3], [4, 5, 6], [7, "&nbsp;", "&nbsp;"]]

[1, 2, 3, 4, 5, 6, 7].in_groups_of(3, false)
# => [[1, 2, 3], [4, 5, 6], [7]]

Array#in_groups_of also conveniently yields each group when passed a block:

[1, 2, 3, 4, 5, 6, 7].in_groups_of(3, false) {|group| puts group.size }
# => prints "3\n3\n1"
Array#in_groups(number, fill_with = nil)

Splits up an Array into number groups, optionally filling in remainder slots with fill_with unless it’s false:

[1, 2, 3, 4, 5, 6, 7].in_groups(3)
# => [[1, 2, 3], [4, 5, nil], [6, 7, nil]]

[1, 2, 3, 4, 5, 6, 7].in_groups(3, '&nbsp;')
# => [[1, 2, 3], [4, 5, "&nbsp;"], [6, 7, "&nbsp;"]]

[1, 2, 3, 4, 5, 6, 7].in_groups(3, false)
# => [[1, 2, 3], [4, 5], [6, 7]]

Array#in_groups also conveniently yields each group when passed a block:

[1, 2, 3, 4, 5, 6, 7].in_groups(3, false) {|group| puts group.size }
# => prints "3\n2\n2"
Array#split(value = nil)

Splits up an Array into subarrays based on a delimiting value or the result of an optional block.

[1, 2, 3, 4, 5].split(3)                # => [[1, 2], [4, 5]]
(1..10).to_a.split { |i| i % 3 == 0 }   # => [[1, 2], [4, 5], [7, 8], [10]]
Array#sample

Returns a random element from the Array:

[1, 2, 3, 4, 5].sample # => 5

This method is backported from Ruby 1.9

Array#to_param

Returns a String to be used as part of a URL. Produced by calling #to_param on each element and joining the results with forward-slashes. Used by Action Pack’s url_for:

["brave", "new", "world"].to_param # => "brave/new/world"
Array#to_query(key)

Returns a URL query string using the given key as the parameter name and the Array this method is called on as the values:

['Documenting', 'makes me thirsty'].to_query('thoughts')
# => "thoughts%5B%5D=Documenting&thoughts%5B%5D=makes+me+thirsty"
Array#uniq_by

Returns an Array of unique elements based on criteria in a supplied block.

[1, 2, 3, 4].uniq_by { |i| i.odd? }    # => [1, 2]

Array#uniq_by! is also available, doing the same thing but modifies the Array in-place.

Array#extract_options!

Extracts options from a set of arguments, removing and returning the last element if it's a Hash or an empty Hash if not. Particularly useful when writing a method that takes *args as arguments, yet still needs to take a Hash of options:

def awesome(*args)
  options = args.extract_options!

  puts "The options are #{options.inspect}"
  puts "The arguments are #{args.inspect}"
end

awesome("dogs", "cats", "bears", :ruby_slippers => true)
# => prints "The options are {:ruby_slippers=>true}\nThe arguments are [\"dogs\", \"cats\", \"bears\"]"
Array.wrap(object)

Wraps the object in an Array, converting it using #to_ary if the object implements it. Does nothing on an existing Array.

Array(:chunky => :bacon)      # => [[:chunky, :bacon]]
Array.wrap(:chunky => :bacon) # => [{:chunky=>:bacon}]

Use this instead of Array() when you don’t want the implicit #to_a call on the object:

Array("lol\ncats")        # => ["lol\n", "cats"], in Ruby 1.8
Array.wrap("lol\ncats")   # => ["lol\ncats"]

Note

Ruby 1.8’s String#to_a uses \n as a default delimiter when transforming itself into an Array.

Range

Range#overlaps?(other)

Returns true or false depending on if a Range overlaps an other Range.

(1..5).overlaps?(4..6) # => true
(1..5).overlaps?(7..9) # => false
Range#step(n = 1)

Same as Ruby’s own Range#step which iterates over the Range instance, passing each nth element to a passed block, but extended by Active Support to return an Array of values when invoked without a block:

(1..10).step(2) { |x| puts x } # => prints "1\n3\n5\n7\n9"
(1..10).step(2) # => [1, 3, 5, 7, 9]
Range#include?

Extended to return whether the enumeration of a Range includes a given element or other Range:

(1..5).include?(1..5)    # => true
(1..5).include?(2..3)    # => true
(1..5).include?(2..6)    # => false

("a".."f").include?("c") # => true
(5..9).include?(11)      # => false

Enumerable

The standard Enumerable module in Ruby is extended by Active Support in order to provide instance methods like the following:

Enumerable#sum(identity = 0, &block)

Sums up a total from the enumerable object’s elements. Optionally takes a block to augment summation.

Usage without a block looks like what you would expect:

[5, 5, 5].sum # => 15

Calling sum with a block lets you specify the addition, much like an inject call:

products.sum { |p| p.price * tax }

You can also use a shortcut syntax to have the summation add up the results of a call on each element:

products.sum(&:price)  # Adds up all product prices
                                
# This is the same as writing:
#
#   products.inject(0) {|sum, p| sum + p.price }

You can also specify the value of an empty enumerable collection:

# By default, the sum of an empty list is 0
[].sum # => 0
# Specify the identity value by passing in a value for it:
[].sum("Nothing") # => "Nothing"

Warning

Whenever the Enumerable#sum method call behaves oddly or there’s an error associated with it, check to see if the object you’re calling sum on really is an Enumerable, and not a ActiveRecord::Base instance. The Active Record instance also implements sum, but does so slightly differently, and also calls out to MySQL to perform the summation.

Enumerable#index_by

Converts an Enumerable to a Hash by iterating over the collection and building key/value pairs using what the block returns as the key, and the current element as the value:

["dogs", "cats", "turtles"].index_by {|e| e[0...1] }
# => {"c"=>"cats", "d"=>"dogs", "t"=>"turtles"}
Author.all.index_by(&:first_name)
# => {"Cody"=> <Author ... >, "Edward" => <Author ... >, 
#     "James" => <Author ... >, "John" => <Author ... >}
Enumerable#many?(&block)

Returns true if the collection has more than 1 element:

["The Art of Conversation", "On Simplicity"].many? # => true
[1].many? # => false
{}.many? # => false

When provided an optional block, #many? passes iterates through the collection, passing each element to the block and counts how many times the block returns true. If it’s more than once, #many? returns true:

[2, 4, 6].many? {|n| n.even? } # => true
[2, 4, 6].many? {|n| n.odd? } # => false
Enumerable#exclude?(object)

The negative of Enumerable#include? – returns true if the collection does not include object

[2, 4, 6].exclude?(1) # => true
[2, 4, 6].exclude?(2) # => false
Enumerable#each_with_object(memo, &block)

Iterates over a collection, passing the current element and memo object to the block. Handy for reducing a collection of objects down to a single one, like a numerical value. Also useful for transforming a collection into another, like from an Array to a Hash.

(1..10).each_with_object([1, 1]) do |i, memo|
  memo[0] = i + (memo[0] || 1)
  memo[1] = i * (memo[1] || 1)
end # => [56, 3628800]
["Some", "words"].each_with_object({}) do |word, word_counts|
  word_counts[word] = word.length
end # => {"Some"=>4, "words"=>5}

Curious about what the difference between this and Enumerable#inject is? With #each_with_object, you don’t need to worry about what the block returns. With #inject, the block must return the accumulated object, or memo. Here’s what the last example would look like using #inject:

["Some", "words"].inject({}) do |word_counts, word|
  word_counts[word] = word.length
  word_counts
end # => {"Some"=>4, "words"=>5}

By using #each_with_object you save a line of code as well as tend not to fall into head-scratching debugging situations where you see the following because you forgot to return the memo at the end of the block:

["Some", "words"].inject({}) do |word_counts, word|
  word_counts[word] = word.length
end

# ~> -:3: syntax error, unexpected kEND
# ~> ...mp_1277015147_730_23377 = (end);$stderr.puts("!XMP1277015147...
# ~>                               ^
# ~> -:3: syntax error, unexpected $end, expecting ')'

Note

The block arguments passed to #each_with_object and #inject are swapped.

A final note: the memo must be mutable, so you can’t use immutable objects like numbers, true, or false. Your code will not raise any exceptions, but your results will be surprising:

(1..5).each_with_object(1) { |value, memo| memo *= value } # => 1

Enumerable#each_with_object is backported from Ruby 1.9.

Enumerable#group_by

Groups an enumerable into sets using the resulting value from the provided block. Useful for grouping records by date or another attribute. Here’s an example where we want to group a list of names by their first character:

people = ["Harry", "Tom", "Dick", "Darcy", "Dwemthy", "Harold", "Tim"]
                                
people.group_by {|name| name[0...1] }
# => #<OrderedHash {"D"=>["Dick", "Darcy", "Dwemthy"], "H"=>["Harry", "Harold"], "T"=>["Tom", "Tim"]}>

Note

ActiveSupport::OrderedHash is an implementation of Ruby 1.9’s ordered Hashes, backported for Ruby 1.8 users.

Time, Date, and DateTime

The Time, Date, and DateTime classes are heavily extended by Active Support to provide a better language for describing moments in time, asking if a moment is in the future or past, doing arithmetic with time, as well as much richer support for time zones in comparison to what comes out of the box with Ruby.

Time, Date, and DateTime are fairly consistent in terms of their classes & instances’ public methods, so if you see something described here for a Time object, it likely also works for a Date or DateTime object.

Describing moments in time

Need to express a moment in time 10 minutes ago or 2 days from now? Take a look at this list of examples to get a feel of what’s possible:

# Reads best without an argument
10.minutes.ago                      # => Thu, 15 Sep 2011 06:14:18 UTC +00:00

# Same, but reads best with an argument
10.minutes.until(Time.now)          # => Thu Sep 15 02:14:18 -0400 2011

# Reads best with an argument
2.days.since(Date.yesterday)        # => Fri, 16 Sep 2011

# Reads best without an argument
2.days.from_now                     # => Sat, 17 Sep 2011 06:24:18 UTC +00:00

# Same as Time.now.advance(:months => 4, :years => 5)
(4.months + 5.years).from_now       # => Sun, 15 Jan 2017 06:24:18 UTC +00:00

# Be careful to only call #from_now on Time-like objects
# to ensure calculations keep their precision, so don't do this:
1.month.to_i.from_now               # => Sat, 15 Oct 2011 06:24:18 UTC +00:00

t = Time.now                        # => Thu Sep 15 02:24:18 -0400 2011

# Time#since(seconds) so the equivalent arithmetic
# would be Time.now + 10.seconds
t.since(10)                         # => Thu Sep 15 02:24:28 -0400 2011

# Same
t.in(10)                            # => Thu Sep 15 02:24:28 -0400 2011

# Time.now - 9.months
t.months_ago(9)                     # => Wed Dec 15 02:24:18 -0500 2010

# Time.now + 9.months
t.months_since(9)                   # => Fri Jun 15 02:24:18 -0400 2012

# Time.now - 18.years
t.years_ago(18)                     # => Wed Sep 15 02:24:18 -0400 1993

# Time.now + 18.years
t.years_since(18)                   # => Sat Sep 15 02:24:18 -0400 2029

# Same as Time#years_ago(1)
t.prev_year                         # => Wed Sep 15 02:24:18 -0400 2010
t.next_year                         # => Sat Sep 15 02:24:18 -0400 2012

t.prev_month                        # => Mon Aug 15 02:24:18 -0400 2011
t.next_month                        # => Sat Oct 15 02:24:18 -0400 2011

# Refers to the start of this week, Monday at 0:00
t.beginning_of_week                 # => Mon Sep 12 00:00:00 -0400 2011
t.at_beginning_of_week              # => Mon Sep 12 00:00:00 -0400 2011
t.monday                            # => Mon Sep 12 00:00:00 -0400 2011

t.end_of_week                       # => Sun Sep 18 23:59:59 -0400 2011
t.at_end_of_week                    # => Sun Sep 18 23:59:59 -0400 2011

# Returns a new Time object in the next week.
t.next_week                         # => Mon Sep 19 00:00:00 -0400 2011

# Great for referring to moments in time like "next Tuesday"
t.next_week(:tuesday)               # => Tue Sep 20 00:00:00 -0400 2011

t.beginning_of_day                  # => Thu Sep 15 00:00:00 -0400 2011
t.midnight                          # => Thu Sep 15 00:00:00 -0400 2011
t.at_midnight                       # => Thu Sep 15 00:00:00 -0400 2011
t.at_beginning_of_day               # => Thu Sep 15 00:00:00 -0400 2011

# Last moment of the day - 23:59:59.999999
t.end_of_day                        # => Thu Sep 15 23:59:59 -0400 2011

# First moment of the month
t.beginning_of_month                # => Thu Sep 01 00:00:00 -0400 2011

# Last moment of the month
t.end_of_month                      # => Fri Sep 30 23:59:59 -0400 2011

# Start of the quarter (1st of January, April, July, October at 0:00)
t.beginning_of_quarter              # => Fri Jul 01 00:00:00 -0400 2011
t.at_beginning_of_quarter           # => Fri Jul 01 00:00:00 -0400 2011

t.beginning_of_year                 # => Sat Jan 01 00:00:00 -0500 2011
t.end_of_year                       # => Sat Dec 31 23:59:59 -0500 2011

t.yesterday                         # => Wed Sep 14 02:24:18 -0400 2011

t.tomorrow                          # => Fri Sep 16 02:24:18 -0400 2011

Time.now.seconds_since_midnight     # => 8658.4142
DateTime.now.seconds_since_midnight # => 8658

Direct time duration manipulation

Need to set a specific date or time or advance an existing one? There’s more than one way to do it. Here’s a few examples:

t = Time.now                                  # => Thu Sep 15 02:24:18 -0400 2011
t.change(:utc => true, :year => 1, :month => 2, :day => 3, :hour => 4, 
         :min => 5, :sec => 6, :usec => 7)   
# => Thu, 03 Feb 0001 04:05:06 -0500

d = Date.today                                # => Thu, 15 Sep 2011
d.change(:year => 1, :month => 2, :day => 3)  # => Thu, 03 Feb 0001

d = DateTime.now                              # => Thu, 15 Sep 2011 02:24:18 -0400
d.change(:year => 1, :month => 2, :day => 3, :hour => 4, 
         :min => 5, :sec => 6, :offset => 7, :start => DateTime.yesterday) 
# => Sat, 03 Feb 0001 04:05:06 +16800

# Also works on Date and DateTime
t = Time.now                                  # => Thu Sep 15 02:24:18 -0400 2011
t.advance(:years => 1, :months => 2, :weeks => 3, :days => 4, :hours => 5, 
          :minutes => 6, :seconds => 7)      
# => Mon Dec 10 07:30:25 -0500 2012

Note

#advance has differently named options than #change

Is it in the future? Is it today? Is it in the past?

Determining if a date is in the past, present, or future is easy:

1.day.ago.past?        # => true
1.day.from_now.past?   # => false

Date.today.today?      # => true
Date.yesterday.today?  # => false

Date.tomorrow.future?  # => true
Date.yesterday.future? # => false

Time Arithmetic

As you’ve already seen, it’s easy to perform arithmetic on time-like objects. Here are some more examples of what you can do:

t = Time.now                         # => Wed Jul 14 00:03:22 -0400 2010
t + 1.day + 2.weeks + 3.months + 4.years 
# => Wed Oct 29 00:03:22 -0400 2014
t + 1.seconds + 2.minutes + 3.hours  # => Wed Jul 14 03:05:23 -0400 2010
t + 1.fortnight                      # => Wed Jul 28 00:03:22 -0400 2010

# Timezone arithmetic is internally handled; just pick a starting timezone...
Time.zone = "Hawaii"
t1 = Time.zone.now                   # => Tue, 13 Jul 2010 18:03:22 HST -10:00

# ... and parse the incoming string in whatever zone it's in
t2 = Time.zone.parse('2010-05-05 15:30:45 -0300') 
# => Wed, 05 May 2010 08:30:45 HST -10:00

# Addition and subtraction work, even with different timezones
# (Subtraction gives you how many seconds elapsed between the two moments)
t2 - t1                              # => -5995957.11177897

# Comparisons work as expected between the various time-related classes
t1 = Time.now                        # => Wed Jul 14 00:03:22 -0400 2010
t2 = Date.tomorrow                   # => Thu, 15 Jul 2010
t3 = DateTime.now - 1.day            # => Tue, 13 Jul 2010 00:03:22 -0400

t1 < t2                              # => true
t2 > t3                              # => true
t3 > t1                              # => false

Time Zone Conversions

Active Support adds a TimeZone class that wraps around TZInfo::Timezone instances that makes it easy to set a current time zone, retrieve the current timezone, and have it all transparently work with existing and newly created time objects.

Set a time zone in config/application.rb like so:

Rails::Initializer.run do |config|
  config.time_zone = "Eastern Time (US & Canada)"
end

This default time zone is accessible from within your application via Time.zone. Likewise, to set a different time zone later on in your application, do

Time.zone = "Eastern Time (US & Canada)"

For a full set of available time zones, check ActiveSupport::TimeZone::MAPPING.keys in a console.

In order to create Time objects that use a set time zone, you need to use the following methods:

Time.zone.local(year, month=1, day=1, hour=0, min=0, sec=0, usec=0)

Returns a TimeWithZone object with the time zone from Time.zone set.

Note that like other methods with similar arguments, if the requested time is not between 1970 and 2038 (or 1902 and 2038, depending on system architecture), a DateTime instance will be returned instead.

Time.zone.parse(time, now = Time.now)

Parses time string using Time.parse and returns a TimeWithZone object, setting the time zone using Time.zone.

Time.zone.at(seconds_since_unix_epoch)

Returns a TimeWithZone object representing a moment in time a given amount of seconds since the Unix epoch (the year 1970). Time zone set using Time.zone.

The reason you need to use these methods is because they return TimeWithZone objects; a new Time-like class that Active Support provides that represents a time in any time zone. Since standard Ruby Time instances can only handle UTC and the system’s local time zone, it’s important to use the above methods when setting your own time zone within the application.

Here are some more examples of it in action:

Time.zone = 'Eastern Time (US & Canada)'

Time.zone.local(2007, 2, 10, 15, 30, 45)  # => Sat, 10 Feb 2007 15:30:45 EST -05:00
Time.zone.parse('2007-02-01 15:30:45')    # => Thu, 01 Feb 2007 15:30:45 EST -05:00
Time.zone.at(1170361845)                  # => Thu, 01 Feb 2007 15:30:45 EST -05:00
Time.zone.now                             # => Tue, 13 Jul 2010 01:20:55 EDT -04:00

# Representing the time in another time zone is easy
t = Time.utc(2007, 2, 10, 20, 30, 45)  # => Sat Feb 10 20:30:45 UTC 2007
t.in_time_zone                         # => Sat, 10 Feb 2007 15:30:45 EST -05:00

# Return the simultaneous time in Time.zone or the specified zone
Time.now.in_time_zone                      # => Tue, 13 Jul 2010 01:20:55 EDT -04:00
Time.now.in_time_zone(Time.zone)           # => Tue, 13 Jul 2010 01:20:55 EDT -04:00
Time.now.in_time_zone("Asia/Vladivostok")  # => Tue, 13 Jul 2010 16:20:55 VLAST +11:00
Time.now.in_time_zone(-3.hours)            # => Tue, 13 Jul 2010 02:20:55 BRT -03:00

# Switch to a given timezone within a block
Time.use_zone "Asia/Vladivostok" do
  Time.zone.now # => Tue, 13 Jul 2010 16:20:55 VLAST +11:00
end

Time and TimeZone objects also come with methods for determining the answers to common time-zone-related questions:

Time.zone = "Eastern Time (US & Canada)"

t = Time.zone.now

# Is it daylight savings time?
t.dst?          # => true

# UTC offset in seconds
t.utc_offset    # => -14400

t.zone          # => "EDT"

# Format representation in RFC-822 standard
t.to_s(:rfc822) # => "Thu, 15 Sep 2011 02:24:18 -0400"

Formatting Time-like objects

Time, Date, and DateTime instances all implement a method #to_formatted_s, aliased to #to_s which converts the date into a string of a given format using a given #strtime string or Proc. Table 13.1, “Built-in Time, Date, DateTime#to_s formats” lists the built-in conversion formats.

Table 13.1. Built-in Time, Date, DateTime#to_s formats

Built-in Conversion FormatsInternal Format/ImplementationExample Result
to_s "Thu Jan 18 06:10:17 CST 2007"
to_s(:time)"%H:%M""06:10:17"
to_s(:db)"%Y-%m-%d %H:%M:%S""2007-01-18 06:10:17"
to_s(:number)"%Y%m%d%H%M%S""20070118061017"
to_s(:short)"%d %b %H:%M""18 Jan 06:10"
to_s(:long)"%B %d, %Y %H:%M""January 18, 2007 06:10"
to_s(:long_ordinal)lambda { |time| time.strftime("%B #{ActiveSupport::Inflector.ordinalize(time.day)}, %Y %H:%M") }"January 18th, 2007 06:10"
to_s(:rfc822)lambda { |time| time.strftime("%a, %d %b %Y %H:%M:%S #{time.formatted_offset(false)}") }"Thu, 18 Jan 2007 06:10:17 -0600"


The list of formats themselves are held in a Hash in Time::DATE_FORMATS. Note that this Hash for formats is used for instances of Date and DateTime classes as well.

To add a format, the cleanest way is to create a file config/initializers/time_formats.rb in which formats are added to Time::DATE_FORMATS like so:

Time::DATE_FORMATS[:month_and_year] = "%B %Y"
Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }

Days in a month

It’s easy to find out how many days are in the current month:

# Time.days_in_month(month, year = now.year)
Time.days_in_month(1) # => 31

MessageVerifier

ActiveSupport::MessageVerifier is used to sign and verify messages to prevent tampering. It’s great for situations where you need to confirm that a request really did originate from your own site, and that the attributes in the URI haven’t been messed with. Here’s an example of MessageVerifier in action:

secret = "a secret, complex and long string"
message = 'Some important message'
verifier = ActiveSupport::MessageVerifier.new(secret)

signature = verifier.generate(message)
verifier.verify(signature) # => "Some important message"
verifier.verify(signature + 'tampered')
# => "raises ActiveSupport::MessageVerifier::InvalidSignature"

A usecase for MessageVerifier is this common situation: you’re writing an auto-unsubscribe feature and need to include a link in an email that unsubscribes the user from a given newsletter. The link must verifiably originate from the app itself, so we use MessageVerifier:

# Defined somewhere in scope
@verifier ||= ActiveSupport::MessageVerifier.new(Rails.application.config.secret_token)

# Elsewhere in a mailer helper
def unsubscribe_link
  signature = @verifier.generate(@user.id, @newsletter.id)
  url_for(:controller => 'newsletters', :action => 'unsubscribe', :signature => signature)
end

# In NewslettersController
def unsubscribe
  user_id, newsletter_id = @verifier.verify(params[:signature])
  # [...]
rescue ActiveSupport::MessageVerifier::InvalidSignature
  # Respond appropriately
  # [...]
end

MessageVerifier signs and verifies messages by using a Hash-based Message Authentication Code (HMAC) which works by hashing together the data and your Rails application’s secret token using the SHA1 digest algorithm by default. MessageVerifier also supports other digest algorithms; see Action Controller’s Cookie Store’s options for :digest for what’s possible.

MessageVerifier is a good fit for any similar situation where the session store isn’t suitable or available, like Rails’ own cookie signing and store, which is where it’s used internally.

Warning

Just because the message’s content has been verified to originate from where you think it does, doesn’t mean that the message is encrypted.

To prevent others from being able to read its contents, see MessageEncryptor in the next section.

One final note about MessageVerifier: the signed message’s data is a base-64-encoded Marshal dump. Because Ruby’s marshalling library can only reconstitute data that’s been dumped by a version of Ruby with the same major and minor version numbers, be careful when upgrading your Ruby interpreter as it could break all existing messages signed with MessageVerifier.

MessageEncryptor

ActiveSupport::MessageEncryptor does what you expect: it makes it easy to encrypt content so you can store sensitive data in untrusted places. Here’s how to use it in its most basic form:

secret = "a secret, complex and long string"
encryptor = ActiveSupport::MessageEncryptor.new(secret)

ciphertext = encryptor.encrypt("some message")
encryptor.decrypt(ciphertext) # => "some message"

Here’s how to use it to store a phone number in a cookie, a place that’s easily read by other users on a shared machine:

encryptor = ActiveSupport::MessageEncryptor.new(Rails.application.config.secret_token)
cookie[:phone_number] = encryptor.encrypt(@user.phone_number)

# (later)
encryptor = ActiveSupport::MessageEncryptor.new(Rails.application.config.secret_token)
phone_number = encryptor.decrypt(cookie[:phone_number])

MessageEncryptor also features MessageEncryptor#encrypt_and_sign and MessageEncryptor#decrypt_and_verify which do exactly what you expect, using MessageVerifier in tandem and in that order.

MessageEncryptor uses the AES-256-CBC cipher by default, but other ciphers are available, like BF, DES, RC2, RC4, and RC5. Longer AES keylengths are also available:

ActiveSupport::MessageEncryptor.new('sekrit', 'aes-192-cbc')
ActiveSupport::MessageEncryptor.new('sekrit', 'aes-128-cbc')

To list all available ciphers, see

OpenSSL::Cipher.ciphers

SecureRandom

ActiveSupport::SecureRandom generates secure random numbers for things like session keys in HTTP cookies which Rails does internally when automatically handling the session for you. Uses OpenSSL’s random number generator by default. If not available, /dev/urandom or a CryptGenRandom Win32 API call is used, depending on the platform.

SecureRandom.hex(n = nil)

Generates a random hex String of length 2n:

SecureRandom.hex(10) # => "c0a0e3b1c8f70c9fb2e9"

If n is nil, 16 is assumed.

Raises NotImplementedError when a secure random number generator is not available.

SecureRandom.base64(n = nil)

Genrates a random base64 String of length ≈ 4/3n:

# Random 10-character base64 string 
SecureRandom.base64(10) # => "vA1N9RKHBD5n8Q=="

If n is nil, 16 is assumed.

Raises NotImplementedError when a secure random number generator is not available.

SecureRandom.random_number(n = 0)

Generates a random integer between 0 (inclusive) and n when n > 0:

SecureRandom.random_number(20) # => 13

Generates a random float between 0.0 (inclusive) and 1.0 when n is 0 or nil:

SecureRandom.random_number # => 0.405922930890556
SecureRandom.random_bytes(n = nil)

Generates a random binary String of length n:

SecureRandom.random_bytes(10) # => "\f\205\206M@\245\362\217\027\212"

Inflections

Active Support extends the String class to provide some of the functionality Active Record needs when figuring out which database tables map to which models by singularizing or pluralizing words and names.

Here’s an example of some of the methods at work:

"person".pluralize # => "people"
"octopus".pluralize # => "octopi"

"animals".singularize # => "animal"

"videos of things".titleize # => "Videos Of Things"

"lol_cat".camelize # => "LolCat"

"LolCat".underscore # => "lol_cat"

"lol_cat".dasherize # => "lol-cat"

# Capitalizes the first word, turns underscores into spaces, and strips '_id'
"video_id".humanize # => "Video"

To add an inflection the built-in rules don’t cover, take a look at config/initializers/inflections.rb and follow the comments there, which look like this:

# Add new inflection rules using the following format 
# (all these examples are active by default):
ActiveSupport::Inflector.inflections do |inflect|
  inflect.plural /^(ox)$/i, '\1en'
  inflect.singular /^(ox)en/i, '\1'
  inflect.irregular 'person', 'people'
  inflect.uncountable %w( fish sheep )
end

The Array and Integer classes are also extended to provide methods like Array#to_sentence and Integer#ordinalize which you can use to ease making pretty output:

["dogs", "cats"].to_sentence # => "dogs and cats"

["dogs", "cats"].to_sentence(:two_words_connector => ' & ') # => "dogs & cats"

["planes", "trains", "automobiles"].to_sentence(:words_connector => '; ') 
# => "planes; trains, and automobiles"

["planes", "trains", "automobiles", 
 "John Candy"].to_sentence(:last_word_connector => ", and the inimitable ") 
# => "planes, trains, automobiles, and the inimitable John Candy"

1002.ordinalize # => "1002nd"

There are even more methods like these available under ActiveSupport::Inflector – see the official documentation for more.

StringInquirer

Provides a nicer way to test for string-equality.

To determine if you’re running in the production environment, one way to do it is:

Rails.env == "production"

However, you can also be a little more elegant and write:

Rails.env.production?

The above comes to you via StringInquirer – it wraps any string, does some method_missing magic and lets you write the above. Here’s an implementation example:

class Video
  def initialize(title)
    @title = title
  end
  
  def title
    ActiveSupport::StringInquirer.new(@title)
  end
end

v = Video.new("awesome")
v.title.radical? # => false
v.title.awesome? # => true

Multibyte

Multibyte backports multibyte-aware String operations for Ruby 1.8. Let’s take a look at what that means:

# Tell Ruby that we want to work with UTF8-encoded strings
$KCODE = 'UTF8'

s = 'Québec'

# Grabbing the first 3 chars ends up grabbing the first 2 and half
puts s[0..2] # => "Qu�"

# This is one character too long
puts s.length # => 7

# Nice work multibyte Strings. Nice work.
puts s.reverse # => ceb��uQ

What’s happening here is that Ruby is getting really confused trying to deal with the “é” being represented with two bytes internally. In order to work with these sorts of String operations (#slice, #reverse, #length, and friends), Active Support provides a proxy accessible through #mb_chars that figures it out for you:

s = 'Québec'
mb_s = s.mb_chars

puts mb_s[0..2]  # => Qué

puts mb_s.length # => 6

puts mb_s.reverse # => cebéuQ

This proxy object is interchangeable for String except when performing explicit class-comparison – in that case, just call #to_s to get back a String:

s = ''

s.mb_chars.class == String  # => false
s.mb_chars.to_s.class == String  # => true

Usage of ActiveSupport::Multibyte is unnecessary when working with Ruby 1.9 as its String class is already encoding-aware.

Base64

Encodes and decodes Strings of data in base-64 representation.

ActiveSupport::Base64.encode64(data)

Encodes a String data to its base-64 representation. Each 60 characters of output is separated by a newline (\n) character.

ActiveSupport::Base64.encode64("Original unencoded string")
# => "T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==\n"
ActiveSupport::Base64.decode64(data)

Decodes a base-64 encoded String data to its original representation.

ActiveSupport::Base64.decode64("T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==")
# => "Original unencoded string"
ActiveSupport::Base64.encode64s(data)

Encodes the String data to its base-64 representation. Does not separate output with newlines. Useful for encoding URI parameters or memcache keys.

ActiveSupport::Base64.encode64s("Original unencoded string")
# => "T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw=="

Gzip compression & decompression

Compress and decompress Gzip-ed Strings using these convenience methods. Internally mplemented using the Ruby standard library’s Zlib class.

ActiveSupport::Gzip.compress(source)

Compresses a Gzip-ed String source.

ActiveSupport::Gzip.compress("Nutshell")
# => "\037\213\b\000\315\330\002L\000\003\363+-)\316H\315\311\001\000\r\372f\364\b\000\000\000"
ActiveSupport::Gzip.decompress(source)

Decompresses a Gzip-ed String source.

ActiveSupport::Gzip.decompress("\037\213\b\000\315\330\002L\000\003\363+-)\316H\315\311\001\000\r\372f\364\b\000\000\000")
# => "Nutshell"

Callbacks

The callback behaviour present in Active Record objects is available to be mixed in to your own non-Active Record classes. If you wanted to trigger some side-effect code upon a method firing, callbacks are a convenient way of accomplishing that.

Here’s an example of using callbacks:

class Vehicle
  include ActiveSupport::Callbacks

  define_callbacks :ride

  set_callback :ride, :before, :check_the_weather
  def check_the_weather
    puts "Ok! Looks like it's not going to rain."
  end
end

class Bicycle < Vehicle
  set_callback :ride, :before, :put_on_helmet
  def put_on_helmet
    puts "I'm putting on my helmet. Safety first!"
  end

  set_callback :ride, :after do |object|
    puts "Locking up my bike."
  end

  def ride
    run_callbacks :ride do
      puts "Whooo! Riding bikes is awesome!"
    end
  end
end

bike = Bicycle.new
bike.ride

# Outputs:
#   Ok! Looks like it's not going to rain.
#   I'm putting on my helmet. Safety first!
#   Whooo! Riding bikes is awesome!
#   Locking up my bike.

Callbacks are inherited – note how the check_for_weather callback is called as a side-effect of bike.ride.

The most commonly used methods for callbacks are as follows:

ActiveSupport::Callbacks::Callback.define_callbacks(*callbacks)

Defines callback types. Example usage:

define_callbacks :ride, :terminator => "result == false"
define_callbacks :glide, :rescuable => false

The :terminator option specifies when a “before filter” should halt the callback chain. Here, we specify that if any ride callback returns false, halt the chain. By default, this is what it does.

The :rescuable option specifies whether or not “after filters” execute if the given block or a “before filter” raises an exception. By default, this is set to false.

ActiveSupport::Callbacks::Callback.set_callback(name, *filter_list, &block)

Sets callbacks. See the previous example for usage, as well as the following on how to use the various filter list options:

set_callback :ride, :before, :check_the_weather
set_callback :ride, :after, :check_the_weather, :if => weather_is_a_big_deal?
set_callback :ride, :after, :check_the_weather, :unless => "weather == 'nice'"
set_callback :save, :around, lambda { |r| code_before; yield; code_after }

The conditions provided to the :if and :unless keys work the same way as they do for Active Record callback conditions: an Array of multiple conditions, the name of a method as a Symbol, a String which is eval-ed, a Proc to call with the object having its callback executed, or an Object that defines the after_ride method (for example).

ActiveSupport::Callbacks::Callback.skip_callback(name, *filter_list, &block)

Callbacks may be skipped in child classes by using this method. Example usage:

class Unicycle < Vehicle
  # [...]
  skip_callback :ride
  # [...]
end
ActiveSupport::Callbacks.run_callbacks(kind, *args, &block)

Runs a given callback. See the Bicycle#ride method in the example at the start of this Callbacks section.

Cache

ActiveSupport::Cache is an abstract cache store; essentially a big Hash used to stash already-computed results in the form of serialized Ruby objects.

All cache keys are stored as case-sensitive strings, derived by calling #cache_key on the key object. If #cache_key is not defined, the result of #to_param is used instead. If an Array is used as a key, its elements are joined by slashes to form a String. If a Hash is used as a cache key, the Hash’s keys will be sorted, then joined by slashes to form a String.

Example usage of the cache store looks like this:

cache = Rails.cache   # initialized with a cache store instance during Rails boot
                        
cache.read("author")  # => nil
cache.write("author", "Edward")
cache.read("author")   # => "Edward"

If your cache occupies shared infrastructure, you can set a namespace to prefix all cache keys:

cache.namespace = 'some-prefix'
cache.read("author")  # Reads value for key "some-prefix:author"

Prefixes can also take the shape of a Proc, evaluated each time a key is determined so that application logic can be used to invalidate keys:

# Cache is invalidated each time @last_mod_time is updated
@last_mod_time = Time.now
cache.namespace = lambda { @last_mod_time }

Namespaces may also be specified on a per-call basis.

Auto-expiring content after a given amount of time in seconds is also supported both at a global and per-call level:

cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 5.minutes)

cache.write(key, value, :expires_in => 1.minute)
cache.fetch(key, :expires_in => 1.minute) do
  "replacement value"
end

MemCacheStorage supports Gzip compression to reduce data transmission time and storage space. Activate it at initialization time or on #write and #fetch calls:

cache = ActiveSupport::Cache::MemCacheStorage.new(:compress => true)

cache.write(key, value, :compress => true)
cache.fetch(key, :compress => true) do
  "replacement value"
end

Because compression produces negligible or detrimental results with small amounts of data, a default threshold of 16KB is set by default. To change it, set it in the initializer or on #write or #fetch calls:

cache = ActiveSupport::Cache::MemCacheStorage.new(:compress => true, :compress_threshold => 64.kilobytes)

cache.write(key, value, :compress_threshold => 32.kilobytes)
cache.fetch(key, :compress_threshold => 32.kilobytes) do
  "replacement value"
end

Commonly Used Methods

ActiveSupport::Cache offers an extensive and flexible API that goes beyond what’s covered here. Here’s a listing of the methods you will most likely need:

Cache#fetch(key, options = {}, &block)

Returns data from the cache using key.

cache = ActiveSupport::Cache::MemoryStore.new
cache.write("city", "Ottawa")

cache.fetch("city")  # => "Ottawa"

In the event of a cache-miss (i.e. nothing found for that key), does one of two things:

  1. When called without a block, returns nil.

    cache.fetch("food")  # => nil
  2. When called with a block, the block is executed. Its return value is written to the cache using the request key and is also returned by fetch.

    cache.fetch("food") do
      "Gazpacho"
    end  # => "Gazpacho"
    
    cache.fetch("food") # => "Gazpacho"

Passing :force => true as an option will force a cache miss.

Supports :compress, :compress_threshold options when using a cache store implementation like MemCacheStore that supports compression.

Supports :expires_in for per-call expiration date setting.

Note that when using :expires_in, it’s a good idea to take a look at the :race_condition_ttl option, as it prevents race conditions that may occur during heavy load. Think about this situation:

require "active_support/cache"

cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 2.seconds)

cache.write("foo", "original value")
val_1 = nil
val_2 = nil

# Wait till the cache value has expired
sleep 2

threads = []

# Start a thread that will read the cache first, but take
# a little while to generate the replacement value
threads << Thread.new do
  val_1 = cache.fetch("foo") do
    sleep 1
    "new value 1"
  end
end

# Start a second thread that will read the cache second,
# but finish first
threads << Thread.new do
  val_2 = cache.fetch("foo") do
    "new value 2"
  end
end

threads.each(&:join)

val_1  # => "new value 1"
val_2  # => "new value 2"
cache.fetch("foo") # => "new value 1"

This example demonstrates a situation where multiple threads are competing to fetch and write to a single cache entry may reveal surprising and inconsistent results from fetch calls. In a production environment with greater concurrency, this problem only gets worse.

A solution to this race condition situation is to introduce a time-to-live (TTL) timespan by which a fetch call can immediately advance the entry’s expiration date:

cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 2.seconds)

cache.write("foo", "original value")
val_1 = nil
val_2 = nil
sleep 2

threads = []

threads << Thread.new do

  # If a cache entry has been expired for less than 10 seconds,
  # reset the entry to expire 10 seconds in the future
  val_1 = cache.fetch("foo", :race_condition_ttl => 10.seconds) do
    sleep 1
    "new value 1"
  end
end

threads << Thread.new do
  val_2 = cache.fetch("foo", :race_condition_ttl => 10.seconds) do
    "new value 2"
  end
end

threads.each(&:join)

val_1 # => "new value 1"
val_2 # => "original value"
cache.fetch("foo") # => "new value 1"

By having the first thread to fetch an expired key advance its expiration timestamp, other threads asking for the entry will predictably get back the now-no-longer-expired value while the first thread generates the replacement value.

Note that if the TTL values are too short, and the first thread exceeds twice the TTL timespan while generating the replacement value, your inconsistency problems will return. For this reason, it’s important you measure carefully.

When using MemCacheStore, supports :raw => true.

Cache#read(key, options = nil)

Returns data for key in the cache.

Returns nil in the event of a cache-miss.

Cache#read_multi(*names)

Returns a Hash of multiple values from the cache in one call. Options may be passed in as the last argument

Cache#write(key, value, options = nil)

Writes data to the cache for the given key.

Cache#delete(key, options = nil)

Deletes data in the cache for the given key.

Cache#exist?(key, options = nil)

Returns true or false if key exists in the cache or not.

The following methods are only available on some cache storage implementations:

Cache#delete_matched(matcher, options = nil)

Deletes all data in the cache with keys matching the regular expression matcher. Implemented by FileStore, and MemoryStore.

Cache#increment(key, amount = 1)

Adds amount to value for key in the cache. Implemented by FileStore, MemCacheStore, and MemoryStore. (Note: can only be used on values written with the :raw option – calling it on others will set the value to 0.)

Cache#decrement(key, amount = 1)

Subtracts amount from value for key in the cache. Implemented by FileStore, MemCacheStore, and MemoryStore. (Note: can only be used on values written with the :raw option – calling it on others will set the value to 0.)

Cache#cleanup(options = nil)

Removes expired entries. Implemented by FileStore, and MemoryStore.

Cache#clear(options = nil)

Clears the entire cache store. Implemented by FileStore, MemCacheStore, and MemoryStore.

Storage Implementations

ActiveSupport::Cache uses of one of the following storage implementations as its backend. See the the section called “Active Support Cache Store” for more on configuring your Rails application to use one of the following backends.

Cache::MemCacheStorage

Stores content in Memcached. This is the recommended choice for production environments.

Supports clustering and load balancing across multiple memcached servers. See the section called “Memcached” for more on setup.

Supports the :expires_in option on #write which returns nil for cached content older than the expiry period.

Also supports an option for automatic Gzip-compression of values:

ActiveSupport::Cache::MemCacheStore(:compress => true)

Also supports the :raw option to store values as strings instead of serialized objects. This option must be active for entries you want to use #increment and #decrement on.

Cache::FileStore

Stores content on the filesystem.

Supports the :expires_in option on #read which returns nil for cached content older than the expiry period.

Cache::MemoryStore

Stores content in memory in the same process as the executing Rails application, meaning that if you’re running a cluster of application servers (which you probably will), you won’t be able to share a cache between them. If you’re using generational cache keys and never manually expiring your cache, this might work for you. Consider another option otherwise.

Able to store arbitrary Ruby objects as well as strings (other stores can’t do this).

Note that MemoryStore in Rails 3 is thread-safe, unlike earlier versions.

Note that like MessageVerifier, the data Cache stores is marshal-dumped, and sensitive to the Ruby implementation used to do the marshalling. Be careful of data portability issues when planning migrations that involve changing your version of Ruby.

Memoizable

Memoization is the practice of caching the results of a calculation or method call to avoid re-computing the calculation/method call. You’ve probably done this already without knowing what the pattern is called when writing code like

def some_long_running_computation
  @computation_result ||= factorial(1000)
end

whereupon calling some_long_running_computation will result in only one actual calculation internally, and subsequent calls will use the cached value. (Note however that if your internal computation returns nil or false, the above will compute every time.)

ActiveSupport::Memoizable provides this kind of functionality in a declarative fashion, taking into account the nil issue:

class MathematicalThing
  extend ActiveSupport::Memoizable        # Mix-in memoization with this extend

  def some_long_running_computation(n)
    factorial(n)
  end
  memoize :some_long_running_computation  # Declare this method to be memoized
end

m = MathematicalThing.new

m.some_long_running_computation(100)  # First call takes a long time
m.some_long_running_computation(100)  # Second call takes no time; uses memoized result

To clear the memoized cache for a single method, pass true or :reload as the last argument when calling that method:

m = MathematicalThing.new

m.some_long_running_computation(100)  # Nothing cached; computation occurs
m.some_long_running_computation(100)  # Cache hit; computation skipped

m.some_long_running_computation(100, :reload)  # Cache cleared; computation occurs
m.some_long_running_computation(100)  # Cache hit; computation skipped

When memoizing methods that take no arguments, pass in true or :reload as an argument to clear the cache and force computation:

class SomeThing
  extend ActiveSupport::Memoizable

  def long_running_computation
    factorial(1000)
  end
  memoize :long_running_computation
end

s = SomeThing.new

s.long_running_computation  # Nothing cached; computation occurs
s.long_running_computation  # Cache hit; computation skipped

s.long_running_computation(:reload)  # Cache cleared; computation forced

ActiveSupport::Memoizable handles instance-wide cache invalidation via the following methods:

Memoizable#flush_cache(:some_memoized_method_name)

Expire just one memoized method’s cache:

thing = SomeOtherThing.new
                            
thing.some_long_running_computation  # Nothing cached; computation occurs
thing.some_long_running_computation  # Cache hit; computation skipped

thing.flush_cache(:some_long_running_computation)  # Flush just this method's cache

thing.some_long_running_computation  # Nothing cached; computation occurs
Memoizable#unmemoize_all

Expires the cache for all memoized methods in the instance:

class SomeOtherThing
  extend ActiveSupport::Memoizable

  def long_running_computation
    factorial(1000)
  end
  memoize :long_running_computation

  def other_long_running_computation
    sleep(42)
    factorial(100)
  end
  memoize :other_long_running_computation
end

thing = SomeOtherThing.new

thing.some_long_running_computation   # Nothing cached; computation occurs
thing.other_long_running_computation  # Nothing cached; computation occurs

thing.some_long_running_computation   # Cache hit; computation skipped
thing.other_long_running_computation  # Cache hit; computation skipped

thing.unmemoize_all  # Clear all memoized methods' cached results

thing.some_long_running_computation   # Nothing cached; computation occurs
thing.other_long_running_computation  # Nothing cached; computation occurs

JSON serialization

JSON encoding and decoding is provided as part of Active Support:

require "active_support/json"

{1 => {"Fruit" => ["Bananas", "Strawberries"]}}.to_json # => "{\"1\":{\"Fruit\":[\"Bananas\",\"Strawberries\"]}}"
Date.today.to_json # => "\"2010-05-01\""

JSON.parse("{\"1\":{\"Fruit\":[\"Bananas\",\"Strawberries\"]}}") # => {"1"=>{"Fruit"=>["Bananas", "Strawberries"]}}

XML serialization

XML encoding and decoding is provided as part of Active Support:

Video.first.to_xml

# <?xml version="1.0" encoding="UTF-8"?>
# <video>
#   <created-at type="datetime">2010-02-16T04:46:28Z</created-at>
#   <embed-code>&lt;object width="425" [...] </embed-code>
#   <id type="integer">1</id>
#   <title>First thing I could think of</title>
#   <updated-at type="datetime">2010-02-16T04:46:28Z</updated-at>
# </video>

Hash.from_xml(Video.first.to_xml)
# => {"video"=>{"created_at"=>Tue Feb 16 04:46:28 UTC 2010, 
#     "embed_code"=>"<object width=\"425\" [...] </embed></object>",
#     "title"=>"First thing I could think of",
#     "updated_at"=>Tue Feb 16 04:46:28 UTC 2010,
#     "id"=>1}}

These methods play a big part of Active Resource, which uses #to_xml and #from_xml on various classes like Array, Hash, and ActiveRecord::Base in order to do its magic.

Note

If your application is doing a lot of XML consumption, install the much faster libxml parser by adding:

gem 'libxml-ruby', '>=0.9.7'

in your Gemfile, and

ActiveSupport::XmlMini.backend = 'LibXML'

at the end of config/application.rb

While it is possible to use Hash#to_xml and Array#to_xml to manually form XML, in most cases, it’s more work than it’s worth.

When dealing with another Rails application you need to produce and consume XML for, it’s best to just let Active Resource do the work and stay at a high level.

When dealing with another application having a RESTful API, it’s easiest to use a library/gem like HTTParty to consume XML, and to write a Builder template for producing it.

When dealing with another application that’s not RESTful, use a library like Nokogiri to parse and consume XML and write a Builder template for producing it. The built-in #from_xml methods will cause you headaches if you try using them in situations like these because they expect nodes to come with metadata like type='integer', but that sort of information usually lives in a schema which Rails doesn’t use. Fortunately, Nokogiri and Builder are easy to use and make short work of these scenarios.

Site last updated on: December 11, 2012 at 10:21:28 PM PST
Cover for Rails 3 in a Nutshell

View 1 comment

  1. edwardog – Posted July 23, 2010

    Regarding Multibyte/Unicode operations being missing from this chapter: I haven’t written it yet :P – I’ll update this comment thread when they are.

Add a comment

View 1 comment

  1. DLitz – Posted July 27, 2010

    SHA1 is obsolete, and is broken in many use cases. Cryptography experts have been recommending against using it in new designs for several years now. Today, SHA256 should be used in new designs.

    The U.S. National Institute for Standards and Technology (NIST) is currently holding a competition to produce a new hash function, which will be called SHA-3. It is scheduled to be released sometime in 2012-2013.

Add a comment

View 1 comment

  1. nuna – Posted Oct. 7, 2010

    encrypt/decrypt methods have padding oracle attack vulnerability https://www.usenix.org/events/woot10/tech/full_papers/Rizzo.pdf

Add a comment

View 1 comment

  1. DLitz – Posted July 27, 2010
    • AES-192 and AES-128 are not "longer AES keylengths" than AES-256.
    • There's really no reason for most people to use anything other than the default of aes-256-cbc. EXCEPT in JRuby, where the Java crypto policy might only allow you to use aes-128-cbc (if you're using Sun's JRE instead of OpenJDK---you'll notice that you get an "Illegal key size" error no matter what secret you put in).

Add a comment

View 1 comment

  1. jduff – Posted July 28, 2010

    This is not true as of right now. See: http://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/date/conversions.rb#L6

    Date has its own Hash of formats, which is very confusing. This means you will likely want to add the same formats to Date::DATE_FORMATS. DateTime uses the format Hash from Time.

Add a comment

View 1 comment

  1. redronin – Posted July 28, 2010

    Should this be @video && @video.title ?

Add a comment