My favorite ActiveSupport features

… and check why 5600+ Rails engineers read also this

My favorite ActiveSupport features

This is short and not so comprehensie list of my favorite ActiveSupport features.

Array#second

%i(a b).second
# => :b

Useful in ad-hoc scripts when you get primitive data from file or API. Together with map they give you some_array.map(&:second) to get what you want.

Array#extract_options

def options(*args)
  args.extract_options!
end

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

But with ruby 2.0 keyword arguments aka kwargs already present and ruby 1.9.3 support ending in February 2015 you should probably migrate to it:

def options(*args, **kwargs)
  args
  kwargs
end

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

Array#in_groups_of and Array#in_groups

%w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group}
#   ["1", "2", "3", "4"]
#   ["5", "6", "7", nil]
#   ["8", "9", "10", nil]

%w(1 2 3 4 5 6 7 8 9 10).in_groups_of(3) {|group| p group}
  #   ["1", "2", "3"]
  #   ["4", "5", "6"]
  #   ["7", "8", "9"]
  #   ["10", nil, nil]

Remember that you can add false as a second argument to avoid nils in the arrays.

Array#wrap

Wraps its argument in an array unless it is already an array.

def method(one_or_many)
  Array.wrap(one_or_many).each(&:do_something)
end

method(1)
method([3,4,5])

Nicely explained in the documentation why it is better then usual ruby idioms

to_formatted_s on many types

BigDecimal.new("12.23").to_s
#=> "0.1223E2"

require 'active_support/all'

BigDecimal.new("12.23").to_s
#=> "12.23"
BigDecimal.new("12.23").to_formatted_s
#=> "12.23"

Time::DATE_FORMATS[:w3c] = "%Y-%m-%dT%H:%M:%S%:z"
Time.now.to_s(:w3c)
#=> "2015-02-25T17:51:53+00:00"

ActiveSupport overwrites to_s on my types to use its to_formatted_s version instead (especially when arguments provided)

#ago, #from_now


Time.now.ago(3.months)
# => 2014-11-25 17:55:53 +0000

3.months.ago
# => 2014-11-25 17:56:00 +0000

3.months.ago(Time.now)
#=> 2014-11-25 17:56:03 +0000

beginning_of_... & end_of_...

%i(
  beginning_of_minute
  beginning_of_hour
  beginning_of_day
  beginning_of_week
  beginning_of_quarter
  beginning_of_month
  beginning_of_year
  end_of_minute
  end_of_hour
  end_of_day
  end_of_week
  end_of_month
  end_of_quarter
  end_of_year
).each{|method| puts "#{method} - #{Time.now.public_send(method)}"}


# beginning_of_minute - 2015-02-25 18:03:00 +0000
# beginning_of_hour - 2015-02-25 18:00:00 +0000
# beginning_of_day - 2015-02-25 00:00:00 +0000
# beginning_of_week - 2015-02-23 00:00:00 +0000
# beginning_of_quarter - 2015-01-01 00:00:00 +0000
# beginning_of_month - 2015-02-01 00:00:00 +0000
# beginning_of_year - 2015-01-01 00:00:00 +0000
# end_of_minute - 2015-02-25 18:03:59 +0000
# end_of_hour - 2015-02-25 18:59:59 +0000
# end_of_day - 2015-02-25 23:59:59 +0000
# end_of_week - 2015-03-01 23:59:59 +0000
# end_of_month - 2015-02-28 23:59:59 +0000
# end_of_quarter - 2015-03-31 23:59:59 +0000
# end_of_year - 2015-12-31 23:59:59 +0000

ActiveSupport::Duration

The class behind the little trick:

3.seconds.class
# => ActiveSupport::Duration

ActiveSupport::TimeWithZone

I hate time zones, but I love ActiveSupport::TimeWithZone. It is so easy to use.

Time.use_zone("Europe/Moscow"){ Time.zone.now }
# => Wed, 25 Feb 2015 21:09:12 MSK +03:00

Time.find_zone!("America/New_York").parse("2015-03-03 12:00:11")
# => Tue, 03 Mar 2015 12:00:11 EST -05:00

Time.find_zone!("America/New_York").parse("2015-03-03 12:00:11").utc
# => 2015-03-03 17:00:11 UTC

Time.utc("2015-03-03 12:00:11").zone
# => "UTC"

And I love that it can properly compare times from different timezones based on what moment of time they point to.

moment = Time.utc("2015-03-03 12:00:11")
#=> 2015-01-01 00:00:00 UTC

moment.in_time_zone("Europe/Warsaw") == moment.in_time_zone("America/Chicago")
=> true

Hash#except

Returns a hash that includes everything but the given keys.

hash = { a: true, b: false, c: nil}
hash.except(:c)
# => { a: true, b: false}

Except that I always think that this method is called #without.

Hash#slice

Slice a hash to include only the given keys.


{ a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b)
# => {:a=>1, :b=>2}

If only I could remember that this method is not named #only :)

Hash#reverse_merge

options.reverse_merge(size: 25, velocity: 10)

is equivalent to

{ size: 25, velocity: 10 }.merge(options)

This is particularly useful for default values.

Module#delegate

class Foo < ActiveRecord::Base
  belongs_to :greeter
  delegate :hello, to: :greeter
end

Reading this is way easier for me, compared to Forwardable#def_delegator.

With prefix and allow_nil options that you can use with it, it probably solves 95% of my delegation cases.

Object#blank? and Object#present? and Object#presence


nil.blank?
# => true
"  ".blank?
#=> true
[].blank?
=> true

title = commment[:title].presence || "Missing title"

Never check for nil or empty string again.

Enumerable#sum

[2,3,5].sum
# => 10

ActiveSupport::Notifications

Too long for our short blogpost but check out instrumentation API

ActiveSupport::MessageVerifier

You can use it to generate and verify signed messages

@verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: JSON)
@verifier.generate("private message")
#=> "InByaXZhdGUgbWVzc2FnZSI=--43fc83190b28daf8df04c0b86ff2976931a6dcd2"
@verifier.verify("InByaXZhdGUgbWVzc2FnZSI=--43fc83190b28daf8df04c0b86ff2976931a6dcd2")
#=> "private message"

@verifier.generate("a" => "private message")
#=> "eyJhIjoicHJpdmF0ZSBtZXNzYWdlIn0=--b253af3e77622f743cf6804c870f4a95cbbd6f00"
@verifier.verify("eyJhIjoicHJpdmF0ZSBtZXNzYWdlIn0=--b253af3e77622f743cf6804c870f4a95cbbd6f00")
=> {"a"=>"private message"}

Summary

That’s it. You can browse entire ActiveSupport codebase quickly and easily at github

If you liked it, you may also enjoy Hidden features of Ruby you may not know about

You might also like