rubyguides

ActiveSupport Outside of Rails

ActiveSupport is the Ruby gem that powers Ruby on Rails. But using ActiveSupport outside of Rails is straightforward: its utilities are plain Ruby extensions that work perfectly in scripts, gems, and any Ruby project, without loading the full framework.

This guide covers how to bring ActiveSupport outside of Rails into a plain Ruby project and which parts give you the most value.

The easiest way to think about ActiveSupport is as a bundle of targeted conveniences. You do not need the entire framework to benefit from it. In many projects, a small set of extensions gives you clearer code without adding much overhead. That is why it is worth being selective: the right require statement can keep startup time low while still giving you the helpers that make the code easier to read.

What is ActiveSupport?

ActiveSupport is a collection of utility classes and standard library extensions. Rails ships it as a dependency, but it is a fully standalone gem with its own official documentation. You can add it to any Ruby project via Bundler:

# Gemfile
gem "activesupport"

Then bundle install and you’re ready to go.

Requiring only what you need

One of the best things about ActiveSupport is that you can cherry-pick the parts you want. You do not have to load everything at once.

Load all core extensions

The most common approach is to load the core extensions in one line:

require "active_support/core_ext"

This gives you extensions to Ruby’s built-in classes: String, Array, Hash, Object, Numeric, Date, Time, and more. ActiveSupport also extends Range, Regexp, Module, and Enumerable with helpers that smooth over everyday Ruby code. Loading everything at once is fine for scripts and one-off tools, but when you write a library or a gem that other projects will depend on, being precise about what you pull in keeps the dependency surface small and startup times predictable.

Load specific extensions

If you know exactly what you need, require it directly for faster startup:

require "active_support/core_ext/object/blank"
require "active_support/core_ext/string/inflections"
require "active_support/core_ext/array/wrap"

Here are the most commonly used extension modules:

ModuleAdds
active_support/core_ext/object#try, #blank?, #present?, #presence
active_support/core_ext/string#blank?, #squish, #titleize, #camelize, #underscore
active_support/core_ext/array#wrap, #inquiry, #to_sentence
active_support/core_ext/hash#slice, #deep_merge, #.stringify_keys
active_support/core_ext/numericTime and byte size helpers (#days, #megabytes)
active_support/core_ext/timeTime.current, #ago, #since, #advance
active_support/core_ext/dateDate.yesterday, Date.tomorrow, #ago, #since

Core class extensions

These are the extensions you will reach for most often in everyday Ruby code. They smooth over repetitive nil checks, whitespace handling, and collection shaping without making the code harder to follow. If you only need a few helpers, require them directly and stop there. That keeps the dependency footprint small while still giving you the pieces that improve the most common Ruby code paths.

Object#try

#try calls a method on an object only if it is not nil. It saves you from repetitive nil checks:

require "active_support/core_ext/object/try"

user = nil
user.name  # => NoMethodError: undefined method `name' for nil:NilClass
user.try(:name)  # => nil

user = OpenStruct.new(name: "Alice")
user.try(:name)  # => "Alice"

This is especially useful when working with nested data structures or objects that may or may not be present. The #try method pairs well with #blank? for defensive coding patterns: you can safely walk down a chain of potential nil values and then check whether you arrived at something real, without the awkward if obj && obj.method guard clauses that tend to multiply in Ruby code.

Object#blank? and #present?

These two methods cover all the “empty” cases in one shot:

require "active_support/core_ext/object/blank"

nil.blank?     # => true
false.blank?   # => true
"".blank?      # => true
"  ".blank?    # => true  (whitespace-only strings are blank)
[].blank?      # => true
{}.blank?      # => true

# present? is just the opposite of blank?
"hello".present?  # => true

Ruby already has #empty? on String, Array, and Hash. But #blank? goes further. It handles nil, false, and whitespace-only strings too. No more writing str && !str.empty? && !str.strip.empty?. Because blank? and present? work uniformly across nil, strings, and collections, you can use them in templates, form objects, and validation logic without checking the type of the value first.

String#squish

Removes leading/trailing whitespace and collapses internal whitespace:

require "active_support/core_ext/string"

"  hello    world  ".squish  # => "hello world"

There is also #squish! which modifies the string in place. ActiveSupport also provides String#truncate and String#excerpt for trimming text to a character limit and pulling out the first occurrence of a phrase with surrounding context. Together these string helpers handle the most common text-shaping tasks that plain Ruby makes you assemble from #strip, #gsub, and manual length checks.

String#titleize

Converts a string to title case:

require "active_support/core_ext/string/inflections"

"hello world".titleize      # => "Hello World"
"active_support".titleize   # => "Active Support"

The inflector also handles pluralization and singularization through String#pluralize and String#singularize. For debugging output, log messages, and user-facing labels, having titleized strings without writing a regex or a case statement keeps formatting code compact and predictable.

Array#wrap

Array#wrap ensures you always get an array, without the || [] dance:

require "active_support/core_ext/array/wrap"

Array.wrap(nil)      # => []
Array.wrap([1, 2])   # => [1, 2]
Array.wrap(42)       # => [42]

Compare that to the common pattern [*value] which has surprising behaviour with hashes and OpenStructs. The splat operator is a Ruby idiom that works well for simple lists, but it calls #to_a under the hood and produces different results depending on the object’s class. Using Array.wrap every time you need an array-shaped argument means you stop having to remember which types are safe to splat and which ones will trip you up.

[*nil]       # => [nil];   not an empty array!
[*[1, 2]]    # => [1, 2];   works
[*42]        # => [42];     works for primitives

Array.wrap is predictable: it always returns an array or an empty array. This guarantee is especially useful at method boundaries, where you cannot control whether the caller passes a single value, an array, or nil. Wrapping the input at the top of the method removes a whole class of type-checking branches from the rest of the logic.

Hash#slice

Extract only the keys you want from a hash:

require "active_support/core_ext/hash"

params = { name: "Alice", age: 30, email: "alice@example.com", active: true }
params.slice(:name, :email)  # => { name: "Alice", email: "alice@example.com" }

This is extremely useful for whitelisting parameters before passing them somewhere. The inverse operation, Hash#except, removes specific keys while keeping everything else, which is often a better fit when there are many allowed keys and only a few to filter out. Together, slice and except replace the manual hash.reject { |k, _| forbidden.include?(k) } pattern that tends to show up in controllers, form handlers, and service objects.

def create_user(params)
  User.create(params.slice(:name, :email, :password))
end

The slice method returns a new hash, so the original is left untouched. This is important when the same params hash is passed through multiple layers of a call chain; each layer can pull out what it needs without a side effect that downstream code did not expect.

Hash#deep_merge

Merges two hashes recursively, so nested hashes are merged rather than replaced:

require "active_support/core_ext/hash/deep_merge"

defaults = { db: { host: "localhost", port: 5432 }, pool: 5 }
override = { db: { host: "production.db.com" }, pool: 10 }

defaults.deep_merge(override)
# => { db: { host: "production.db.com", port: 5432 }, pool: 10 }

Plain Ruby #merge would replace the entire :db hash. #deep_merge preserves the keys you do not override.

That difference is easy to miss until nested configuration starts to grow. Once a hash contains several layers, shallow merging becomes risky because one update can wipe out unrelated settings. deep_merge is especially useful for configuration files, test setup, and feature flags where the nested shape matters more than the top-level key.

Time and date extensions

ActiveSupport extends Ruby’s time classes with a more readable API.

Time.current

Time.current is like Time.now but timezone-aware. It uses Time.zone if set, otherwise falls back to Time.now:

require "active_support/core_ext/time"
require "active_support/time"

Time.zone = "London"
Time.current  # => Tue, 31 Mar 2026 16:00:00 GMT +01:00

In a plain Ruby script, set the timezone before using it. Without explicit timezone awareness, Time.now returns the system clock, which may not match the timezone your application logic expects. Setting Time.zone at startup removes the guesswork when the script moves between machines or is deployed to servers in different regions. This is especially relevant for background jobs that write timestamps to a database consumed by users in multiple timezones.

require "active_support/all"

Time.zone = "America/New_York"
Time.current  # => Tue, 31 Mar 2026 11:00:00 EST -05:00

Duration methods on time and date

ActiveSupport adds duration methods (#ago, #from_now, #since, #until) to Time, Date, and DateTime. These methods return new time objects rather than mutating the receiver, so they compose cleanly in expressions and are safe to pass around without worrying about accidental modification. Each duration method accepts a numeric value combined with the unit helpers covered later in this section.

require "active_support/core_ext/date"
require "active_support/core_ext/time"

Time.current.ago(1.week)          # => roughly 7 days ago
Time.current.from_now(2.hours)    # => roughly 2 hours from now
Date.current.yesterday            # => the day before today
Date.current.tomorrow             # => the day after today

Date calculations

ActiveSupport adds calculation methods to Date that handle common calendar operations without pulling in a full date library. These helpers know about month boundaries, leap years, and week start conventions, so you get correct results for edge cases like the last day of February or the beginning of a week that straddles a month change.

require "active_support/core_ext/date/calculations"

Date.current - 3.days    # => 3 days before today
Date.current + 1.month   # => 1 month from today
Date.current.beginning_of_week  # => Monday of this week
Date.current.end_of_month       # => Last day of this month

Duration methods on Numeric

These extensions on Numeric make time expressions read naturally. Rather than passing raw integers that force every reader to remember which unit a particular number represents, the unit methods embed the meaning directly in the code. This is especially valuable in configuration constants and time-related calculations where a bare number like 3600 is ambiguous without a comment.

require "active_support/core_ext/numeric/time"

10.seconds   # => 10 seconds
5.minutes    # => 5 minutes
2.hours      # => 2 hours
1.day        # => 1 day
3.weeks      # => 3 weeks
6.months     # => 6 months
1.year       # => 1 year

Chaining duration methods with time helpers produces clean, readable expressions for relative time calculations. Because each method in the chain builds on the result of the previous one, you can move forward or backward from the current moment without nesting calls or tracking intermediate variables.

2.hours.ago                  # => 2 hours ago
1.day.from_now               # => tomorrow at the same time
45.minutes.since             # => 45 minutes from now

Number extensions

These numeric helpers are small, but they make intent clearer in places where a plain integer would be hard to read. A value like 5.minutes communicates a time window, while 512.megabytes makes a size limit obvious at a glance. That readability matters when the code is revisited months later, especially in scripts or configuration code where the numbers themselves are easy to forget.

ActiveSupport adds meaningful units to numbers, which is particularly useful for file sizes and percentages.

Byte size helpers

require "active_support/core_ext/numeric/bytes"

1.kilobyte   # => 1024
5.megabytes  # => 5242880
2.gigabytes  # => 2147483648

Percentage helpers

ActiveSupport lets you express percentages as method calls on numbers, which is far more readable than dividing by 100.0 everywhere. The #percent method returns a float representing the ratio, and #percent.of(value) multiplies that ratio against a target value for quick proportional calculations.

require "active_support/core_ext/numeric/percentage"

100.percent      # => 1.0
50.percent       # => 0.5
5.percent.of(200)  # => 10.0

These are especially nice in configuration files and calculations. Combining byte size helpers with percentage methods gives you a clean way to express derived limits: a cache that should use a quarter of total memory, or a buffer that reserves ten percent of an allocation. The intent stays visible in the code even when the raw numbers are large and hard to mentally verify.

MAX_MEMORY = 512.megabytes
CACHE_SIZE = 25.percent.of(MAX_MEMORY)  # => 134217728 bytes

Tagged logging

Tagged logging is one of the easiest ways to make logs easier to scan in a background job or service script. If you are new to logging in Ruby, see our /guides/ruby-logging/ guide for the basics. A tag gives you a lightweight breadcrumb that travels with each message, so one worker or request can be separated from another without adding a custom logging framework.

Rails uses ActiveSupport::TaggedLogging to add contextual tags to log output. You can use this in any Ruby project:

require "active_support/tagged_logging"

logger = ActiveSupport::TaggedLogging.new(Rails.logger || Logger.new(STDOUT))
logger.tagged("Request#123") do
  logger.info "Processing..."
  logger.tagged("User#Alice") do
    logger.info "Signed in"
  end
end

The tagged method accepts one or more string arguments and scopes all log output inside the block with those tags. When you nest tagged blocks, tags accumulate, so inner blocks inherit the tags of outer blocks. This produces the hierarchical prefix shown below.

Output:

[Request#123] Processing...
[Request#123] [User#Alice] Signed in

Each tag is automatically prepended in brackets to every log message emitted inside the block. Tags nest, so the output clearly shows the call path that produced each line. This is more structured than prepending a manual prefix string to every log call, and it works with any logger that responds to the standard Ruby Logger interface.

In a plain Ruby script without Rails, you can wrap any logger:

require "active_support/tagged_logging"
require "logger"

log = Logger.new(STDOUT)
tagged = ActiveSupport::TaggedLogging.new(log)

tagged.tagged("MY_APP", "worker-1") do
  tagged.info "Starting job"
  tagged.warn "Retrying..."
  tagged.error "Job failed"
end

Lazy-loading constants outside of Rails

Most plain Ruby projects should still prefer explicit require calls. ActiveSupport::Dependencies is useful when you are building a larger library or plugin system and want a controlled form of lazy loading. In that case, keep the loading rules close to the entry point so the behavior stays understandable.

Rails uses ActiveSupport::Dependencies for automatic constant loading. You can use this pattern in plain Ruby to get lazy-loading of constants:

require "active_support/dependencies"

module MyLib
  extend ActiveSupport::Dependencies

  def self.root
    @root ||= Pathname.new(File.dirname(__FILE__))
  end

  def self.load(file)
    require_dependency root.join(file).expand_path
  end
end

This keeps constants from being loaded until they are actually referenced. For most projects, standard require is fine, but if you are building something with many optional components, this can speed up startup time significantly.

Performance Considerations

Performance is mostly about restraint here. The modular design of ActiveSupport is what makes it a good fit outside Rails, so resist the temptation to pull in more than a script actually needs. If a file only uses blank? and squish, loading the whole suite is unnecessary. Smaller requires are easier to audit, and they make it clearer which helpers a script truly depends on.

ActiveSupport is modular for a reason. Here is how to keep things fast:

Load only what you use. Loading the full active_support/core_ext adds roughly 0.1–0.3 seconds of startup time. For scripts and CLIs, that is often fine. For library code loaded on every request, load only the specific extensions you need.

Watch the order of requires. Some extensions depend on others. For example, active_support/core_ext/time requires active_support/core_ext/object/try. If you try to load them out of order, you may get NoMethodError. The safe pattern is to load active_support/core_ext for the full suite, or read the dependency tree in the source before cherry-picking.

Be cautious with require "active_support/all" in production. It loads everything — including ActiveSupport::Dependencies autoloading machinery, internationalisation, and ERB::Util. In a non-Rails context, those add overhead for no benefit. Using ActiveSupport outside of Rails is at its best when you stay selective: pull in only the helpers your code actually calls, and you get all the readability benefits with none of the framework weight.

See Also