Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Ruby on Rails  Module 15 – Rails internals and extending Rails

Active Support: extensions and notifications

Core extensions add methods like blank? and 2.days to Ruby's classes; ActiveSupport::Notifications lets your code publish events a subscriber reads.

  • Intermediate
  • 15 minutes
  • Examples run with Ruby 4.0.7 and rails 8.1.4
  • By MySmartCoPilot
Free core, Pro depth: 5 of 15 minutes free
Free: Core extensions add methods to Ruby's classes · Instrument publishes an event; subscribers collect it. With a pass: 2 in-depth parts and 3 interview answers, about 10 minutes.

What you will learn

  • Recognise the core extensions Rails code relies on and name their plain-Ruby equivalents
  • Measure a calculation with ActiveSupport::Notifications.instrument and attach a subscriber to its events
  • Match a subscriber pattern against event names, and predict the events a regex catches

Before you start

What you’ll learn · Before you start

What you will learn

  • Recognise the core extensions Rails code relies on and name their plain-Ruby equivalents
  • Measure a calculation with ActiveSupport::Notifications.instrument and attach a subscriber to its events
  • Match a subscriber pattern against event names, and predict the events a regex catches

Before you start

On this page

Deploying a new monitoring subscriber on Kaksha’s /batches/7 action pushed its p99 latency from 80 ms to 160 ms. Twenty lines of subscriber code added that much to every request because ActiveSupport::Notifications.instrument runs its subscribers inline, inside the request thread Rails is answering.

Active Support is the library every other part of Rails depends on. It adds blank?, presence, 2.days and to_sentence to the Ruby classes you already use, and gives you instrument and subscribe so your own code can publish events a subscriber can collect. Both halves show up in every Rails codebase, and both reward careful use.

Core extensions add methods to Ruby’s classes

When Rails loads, require "active_support/all" runs. That line adds dozens of methods to Object, String, Hash, Integer, Time and the other classes you already know. Rails calls these core extension: A method that Active Support adds to a Ruby class it does not own, such as blank? on Object or 2.days on Integer, available in any Rails app.: they let Rails code read like English sentences, at the cost of changing classes that Ruby ships with. The example below pairs the extensions Kaksha uses most often with the plain-Ruby code that does the same thing:

Core extensions used across Kaksha Ruby · core_ext_tour.rb
# Active Support adds methods to Ruby's built-in classes. These are called core extensions: they let Rails code read
# like prose. This file shows the extensions Kaksha uses most often, paired with their plain-Ruby equivalents.
require "active_support/all"

puts "=== blank? and present? (Object) ==="
# blank? returns true for nil, false, empty strings, empty arrays, and whitespace-only strings.
# present? is the opposite: !blank?.
name = ""
puts "name.blank?           => #{name.blank?}"         # true
puts "name.nil? || name.empty? => #{name.nil? || name.empty?}" # same thing for strings

guardian_phone = "   "
puts "guardian_phone.blank? => #{guardian_phone.blank?}" # true (whitespace only)
puts "guardian_phone.strip.empty? => #{guardian_phone.strip.empty?}" # plain Ruby equivalent

batch_ids = [1, 2, 3]
puts "batch_ids.present?    => #{batch_ids.present?}"  # true
puts "!batch_ids.empty?     => #{!batch_ids.empty?}"   # plain Ruby

puts "\n=== presence (Object) ==="
# presence returns the receiver if present?, otherwise nil. Useful for defaults.
input = "  "
fallback = input.presence || "N/A"
puts "input.presence || 'N/A' => #{fallback.inspect}" # "N/A"
# Plain Ruby: the conditional is longer
plain = (input && !input.strip.empty?) ? input : "N/A"
puts "plain Ruby conditional  => #{plain.inspect}"

puts "\n=== try (Object) ==="
# try calls a method only if the receiver responds to it; returns nil otherwise.
student = nil
puts "student.try(:name)    => #{student.try(:name).inspect}" # nil (no NoMethodError)
puts "student&.name         => #{student&.name.inspect}"      # Ruby 2.3+ safe navigation (similar)

# try also accepts a block
arr = [1, 2, 3]
puts "arr.try { |a| a.sum } => #{arr.try { |a| a.sum }}" # 6

puts "\n=== in? (Object) ==="
# in? checks membership, reading left to right.
status = "paid"
puts "status.in?(%w[paid pending]) => #{status.in?(%w[paid pending])}" # true
puts "%w[paid pending].include?(status) => #{%w[paid pending].include?(status)}" # plain Ruby

puts "\n=== Duration (Numeric) ==="
# Active Support lets you write durations as methods on numbers.
puts "2.days               => #{2.days.inspect}"       # 2 days
puts "2 * 24 * 60 * 60     => #{2 * 24 * 60 * 60}"     # 172800 seconds

# Durations work with Time
Time.zone = "Asia/Kolkata"
now = Time.zone.local(2031, 5, 10, 9, 0, 0)
puts "now                  => #{now}"
puts "now + 2.days         => #{now + 2.days}"         # 2031-05-12 09:00:00
puts "3.hours.ago(now)     => #{3.hours.ago(now)}"     # 2031-05-10 06:00:00

puts "\n=== to_sentence (Array) ==="
subjects = %w[Physics Chemistry Biology]
puts "subjects.to_sentence => #{subjects.to_sentence}" # "Physics, Chemistry, and Biology"
# Plain Ruby: subjects[0..-2].join(", ") + ", and " + subjects[-1]

puts "\n=== squish (String) ==="
# squish collapses all whitespace (including newlines) into single spaces and strips edges.
messy = "  Class   10\n\nScience  "
puts "messy.squish         => #{messy.squish.inspect}" # "Class 10 Science"
puts "messy.split.join(' ') => #{messy.split.join(' ').inspect}" # close, but split uses default separator

puts "\n=== inquiry (String) ==="
# inquiry wraps a string so you can ask questions about it with predicate methods.
env = "production".inquiry
puts "env.production?      => #{env.production?}"      # true
puts "env.development?     => #{env.development?}"     # false
puts "env == 'production'  => #{env == 'production'}"  # plain Ruby

puts "\n=== deep_symbolize_keys (Hash) ==="
config = { "database" => { "host" => "localhost", "port" => 5432 } }
puts "config.deep_symbolize_keys => #{config.deep_symbolize_keys.inspect}"
# { database: { host: "localhost", port: 5432 } }

puts "\n=== with_indifferent_access (Hash) ==="
params = { name: "Priya" }.with_indifferent_access
puts "params[:name]        => #{params[:name].inspect}"   # "Priya"
puts "params['name']       => #{params['name'].inspect}"  # "Priya" (same value)

puts "\n=== inflections (String) ==="
puts "'batch'.pluralize    => #{'batch'.pluralize}"       # "batches"
puts "'batches'.singularize => #{'batches'.singularize}"  # "batch"
puts "'fee_payment'.camelize => #{'fee_payment'.camelize}" # "FeePayment"
puts "'FeePayment'.underscore => #{'FeePayment'.underscore}" # "fee_payment"
puts "'fee_payments'.classify => #{'fee_payments'.classify}" # "FeePayment"
puts "'FeePayment'.tableize => #{'FeePayment'.tableize}"   # "fee_payments"

Output

=== blank? and present? (Object) ===
name.blank?           => true
name.nil? || name.empty? => true
guardian_phone.blank? => true
guardian_phone.strip.empty? => true
batch_ids.present?    => true
!batch_ids.empty?     => true

=== presence (Object) ===
input.presence || 'N/A' => "N/A"
plain Ruby conditional  => "N/A"

=== try (Object) ===
student.try(:name)    => nil
student&.name         => nil
arr.try { |a| a.sum } => 6

=== in? (Object) ===
status.in?(%w[paid pending]) => true
%w[paid pending].include?(status) => true

=== Duration (Numeric) ===
2.days               => 2 days
2 * 24 * 60 * 60     => 172800
now                  => 2031-05-10 09:00:00 +0530
now + 2.days         => 2031-05-12 09:00:00 +0530
3.hours.ago(now)     => 2031-05-10 06:00:00 +0530

=== to_sentence (Array) ===
subjects.to_sentence => Physics, Chemistry, and Biology

=== squish (String) ===
messy.squish         => "Class 10 Science"
messy.split.join(' ') => "Class 10 Science"

=== inquiry (String) ===
env.production?      => true
env.development?     => false
env == 'production'  => true

=== deep_symbolize_keys (Hash) ===
config.deep_symbolize_keys => {database: {host: "localhost", port: 5432}}

=== with_indifferent_access (Hash) ===
params[:name]        => "Priya"
params['name']       => "Priya"

=== inflections (String) ===
'batch'.pluralize    => batches
'batches'.singularize => batch
'fee_payment'.camelize => FeePayment
'FeePayment'.underscore => fee_payment
'fee_payments'.classify => FeePayment
'FeePayment'.tableize => fee_payments

Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby core_ext_tour.rb

Each extension has a reason to exist. blank? folds four checks (nil, empty, whitespace, false) into one, so name.blank? replaces name.nil? || name.strip.empty?. presence returns the receiver only when it is present, giving you name.presence || "N/A" for a default. 2.days lets you write durations as words, and to_sentence turns an array into "Physics, Chemistry, and Biology" for display. The inflections power Rails’ naming conventions: the Zeitwerk lesson uses them to turn FeePayment into app/models/fee_payment.rb.

Interactive · message by message Chaining presence, try and in? Step a status value from params through three Active Support helpers.
Chaining presence, try and in?, step 5 of 5. Messages so far: Input to Helpers: presence; Helpers to Result: in?.Inputtwo spacesHelpersstill nilResultallowedpresencein?

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. A value arrives. The status comes from params. It may be nil, an empty string, whitespace, or a real word like "active". Here it is two spaces.
  2. presence returns nil or self. status.presence returns status when it is present and nil when it is blank. A whitespace-only string is blank, so presence returns nil.
  3. try short-circuits on nil. The chain goes on with try(:downcase). The previous step returned nil, so try returns nil without calling downcase, and no NoMethodError is raised.
  4. in? checks the allowed list. A default stands in with ||, so the value becomes "pending", and in? checks it against the list of allowed statuses.
  5. Final result. The chain handled a blank value without raising and fell back to the default. The same chain works for nil, a blank string or a valid status.

Three more Active Support ideas sit beyond the extensions on built-in classes. delegate forwards method calls to another object. class_attribute: A declaration from Active Support that defines an inheritable, per-class value on a class and its subclasses. lets a subclass set its own value for a class-level setting without changing the parent’s. Writing a module as an ActiveSupport::Concern lets scopes, validations and class methods sit in one include-able file. The sections on concerns, delegation and subclass values work through all three on Kaksha’s models.

Version note

Rails main adds this_week?, this_month?, this_year? and this_quarter? on Date and Time, plus SecureRandom.base32. require_dependency is deprecated without a replacement.

Checkpoint

Check: Core extensions on Ruby's classes

  1. Question 1 of 2 What does name.blank? return when name is an empty string?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: true

    blank? returns true for nil, false, empty strings, empty arrays, and strings containing only whitespace. An empty string is blank.

  2. Question 2 of 2 What class is the result of 2.days + 3.hours?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: ActiveSupport::Duration

    ActiveSupport::Duration objects can be added together, returning another Duration. The result becomes a Time or DateTime only when added to one.

Instrument publishes an event; subscribers collect it

The second half of Active Support is instrumentation: The ActiveSupport::Notifications API: wrap a block of code with instrument(name, payload) and subscribers receive one event with a monotonic duration, so Rails and the app measure what they do., for observability. When you wrap a block in ActiveSupport::Notifications.instrument, Rails announces that something is happening: any notification subscriber: A block or callable registered with ActiveSupport::Notifications that receives every event whose name matches its pattern. whose pattern matches the event name receives timing and a payload hash. Rails fires an event for every SQL query, every template render and every controller action, so your own code joins the stream Rails already uses.

Instrumenting fee calculations Ruby · fee_events.rb
# ActiveSupport::Notifications lets you instrument your own code and subscribe to events. This example instruments
# fee calculations in Kaksha: the code announces what it is doing, and a subscriber listens and logs.
require "active_support/all"

# A simple fee calculator. We will instrument calculate_total so that subscribers know when it runs.
class FeeCalculator
  def calculate_total(student_id:, items:)
    # instrument wraps a block and publishes an event. The payload hash travels with the event.
    # The block form captures timing; we add the result to the payload explicitly.
    payload = { student_id: student_id, items: items }
    ActiveSupport::Notifications.instrument("calculate_total.fees", payload) do
      subtotal = items.sum { |item| item[:amount] }
      # Simulate applying a discount for early payment
      discount = subtotal > 10_000 ? (subtotal * 0.05).round : 0
      total = subtotal - discount
      # Add the result to the payload so subscribers can see it
      payload[:result] = { subtotal: subtotal, discount: discount, total: total }
      payload[:result]
    end
  end
end

# Subscribe to events. The block receives an ActiveSupport::Notifications::Event with timing and payload.
puts "=== Subscribing to calculate_total.fees ==="
collected_events = []
subscription = ActiveSupport::Notifications.subscribe("calculate_total.fees") do |event|
  collected_events << {
    name: event.name,
    student_id: event.payload[:student_id],
    items_count: event.payload[:items].size,
    result: event.payload[:result]
  }
end

# Run some calculations
calc = FeeCalculator.new

puts "\n=== Running two calculations ==="
result1 = calc.calculate_total(
  student_id: 42,
  items: [{ name: "Tuition", amount: 8_000 }, { name: "Lab", amount: 1_500 }]
)
puts "Student 42: #{result1}"

result2 = calc.calculate_total(
  student_id: 17,
  items: [{ name: "Tuition", amount: 12_000 }, { name: "Books", amount: 2_000 }]
)
puts "Student 17: #{result2}"

# Show what the subscriber collected
puts "\n=== Events collected by the subscriber ==="
collected_events.each do |e|
  puts "  #{e[:name]}: student #{e[:student_id]}, #{e[:items_count]} items"
  puts "    result: #{e[:result]}"
end

# Unsubscribe when done (important in tests to avoid leaking subscribers)
ActiveSupport::Notifications.unsubscribe(subscription)
puts "\n=== Unsubscribed ==="

# A second subscriber can filter by regex
puts "\n=== Subscribing to all *.fees events with a regex ==="
regex_sub = ActiveSupport::Notifications.subscribe(/\.fees$/) do |event|
  puts "  [regex subscriber] saw: #{event.name} for student #{event.payload[:student_id]}"
end

# Instrument another event that the regex subscriber will catch
puts "\n=== Applying a late fee (instrumented) ==="
ActiveSupport::Notifications.instrument("late_fee_applied.fees", student_id: 42, amount: 500) do
  puts "  Late fee of 500 applied to student 42"
end

ActiveSupport::Notifications.unsubscribe(regex_sub)

Output

=== Subscribing to calculate_total.fees ===

=== Running two calculations ===
Student 42: {subtotal: 9500, discount: 0, total: 9500}
Student 17: {subtotal: 14000, discount: 700, total: 13300}

=== Events collected by the subscriber ===
  calculate_total.fees: student 42, 2 items
    result: {subtotal: 9500, discount: 0, total: 9500}
  calculate_total.fees: student 17, 2 items
    result: {subtotal: 14000, discount: 700, total: 13300}

=== Unsubscribed ===

=== Subscribing to all *.fees events with a regex ===

=== Applying a late fee (instrumented) ===
  Late fee of 500 applied to student 42
  [regex subscriber] saw: late_fee_applied.fees for student 42

Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby fee_events.rb

The fee calculator wraps its work in instrument. The first argument is the event name; the convention is action.namespace, so calculate_total.fees fits the pattern Rails uses for process_action.action_controller and sql.active_record. The second argument is a payload hash that travels with the event, and the block does the real work. A subscriber whose pattern matches the name — an exact string or a regex like /\.fees$/ — receives an event object with timing and the payload.

Interactive · stage by stage Publishing and receiving a calculate_total.fees event Follow the instrument call through the block, then watch the subscriber receive the event.
Publishing and receiving a calculate_total.fees event, step 7 of 7. Showing Your code (payload[:result] = 1050), Notifier (finish: duration computed), Subscriber (captured: result 1050). In focus: Subscriber.Your codepayload[:result] = 1050Notifierfinish: duration computedSubscribercaptured: result 1050

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Subscribe first. Before any event is published, register a subscriber. We pass a regex that matches any event ending in .fees.
  2. Call instrument with a block. Your code calls instrument with the event name and a payload hash. The block does the real work, here a fee calculation.
  3. Notifier records a start time. The notifier reads the monotonic clock before yielding to the block, so timing is correct even if the system clock changes.
  4. Your block runs. The notifier yields to your block. Inside the block you can add to the payload, like payload[:result] = total.
  5. Notifier records the end time. After the block finishes, the notifier reads the monotonic clock again and computes the duration.
  6. Event sent to subscribers. The notifier looks up every subscriber whose pattern matches calculate_total.fees. Our regex /\.fees$/ matches, so our subscriber is called.
  7. Subscriber reads the event. The subscriber reads name, start, finish, id and the full payload, including whatever was set inside the block.

The subscriber runs synchronously, on the same thread as the block, after the block returns. That is why Kaksha’s p99 doubled when its monitoring subscriber was deployed: a network call inside the subscriber added its own latency to every request. For slow work, hand the event off to a background job and return at once. The sections on naming and testing cover namespace design, test teardown and ActiveSupport::EventReporter, the structured event API that Rails 8.1 added.

Checkpoint

Check: Publishing events with instrument and subscribe

  1. Question 1 of 2 Which event name follows the Rails convention?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: calculate_total.fees

    Rails names events as action.namespace, such as process_action.action_controller and sql.active_record. The dot lets regex subscribers filter by namespace.

  2. Question 2 of 2 A subscriber uses /\.fees$/ as its pattern. Which events will it receive?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: calculate_total.fees and late_fee_applied.fees

    A subscriber can pass a regex instead of a string. /\.fees$/ matches any event name ending in .fees, so both calculate_total.fees and late_fee_applied.fees are received.

Concerns, delegate and class_attribute, with trade-offs Pro: opens with a Pro, Premium or Ultimate pass

A module included in a model is the standard way to share Ruby code. Active Support adds three tools that make this cleaner: ActiveSupport::Concern wraps the include-and-extend dance, delegate writes forwarding methods for you, and class_attribute gives each subclass its own copy of a class value.

In this part:

  • Writing a concern with ActiveSupport::Concern
  • Choose a concern for shared behaviour, not for a long file
  • Delegating methods to another object
  • Giving each subclass its own value
  • What to keep from concerns, delegation and subclass values

4 runnable examples (Ruby) · about 8 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Naming, testing and structured events Pro: opens with a Pro, Premium or Ultimate pass

Instrumentation becomes design once you own more than one subscriber. Pick a name convention that lets a regex filter cleanly, keep each subscriber short so the request thread does not pay for its work, and consider the structured events Rails 8.1 added when a payload hash grows hard to read.

In this part:

  • Namespaces that filter cleanly with a regex
  • Testing notification payloads
  • Structured events with EventReporter
  • What to keep from naming, testing and structured events

3 runnable examples (Ruby) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Practise with the examples

  1. Copy core_ext_tour.rb and add a line that prints ({ a: 1, b: 2 }.except(:a)), then another that prints "a long sentence".truncate(10). Run it and check the output.
  2. In fee_events.rb, add a second subscriber that prints event.duration for each calculate_total.fees event, then run the file to see how long each calculation took on your machine.
  3. In fee_events.rb, replace the regex /\.fees$/ with the string "calculate_total.fees". Which of the two events still reaches the second subscriber? Run and compare.

Practice that adapts to you

Practise this lesson

4 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 4 Given user = nil, what does user.presence || Guest.new return?

    Choose one answer.

    Show the answer: Question 1 of 4

    Answer: A new Guest instance

    nil.presence returns nil (nil is not present), so the || short-circuits to Guest.new. The pattern provides a default when a value is blank.

  2. Question 2 of 4 What does [1, 2, 3].try { |a| a.sum } return?

    Choose one answer.

    Show the answer: Question 2 of 4

    Answer: 6

    try with a block yields the receiver to the block, so the array is yielded and a.sum returns 6. If the receiver were nil, try would return nil without calling the block.

  3. Question 3 of 4 A subscriber uses the pattern /^calculate_/. Which of these events does it receive?

    Choose one answer.

    Show the answer: Question 3 of 4

    Answer: calculate_total.fees and calculate_discount.fees

    The anchor ^calculate_ matches event names that start with calculate_, regardless of their namespace. Only events with that prefix reach the subscriber.

  4. Question 4 of 4 In instrument('x.y', payload) { payload[:result] = 42 }, when does the subscriber see :result?

    Choose one answer.

    Show the answer: Question 4 of 4

    Answer: After the block finishes, because the subscriber runs at the end of the block

    instrument publishes the event after the block finishes. The payload hash is the same object throughout, so adding payload[:result] inside the block makes it visible to the subscriber.

Interview questions

A warm-up on three predicates. What is the difference between nil?, blank? and present? in Rails?

These three predicates differ in what they consider empty. nil? returns true only for nil and is pure Ruby, with no Active Support needed. blank? is an Active Support method added to Object; it returns true for nil, false, empty strings, empty arrays, and strings containing only whitespace. present? is the logical opposite of blank?, so it returns true when the value is neither nil nor empty nor whitespace-only. params[:name].present? guards against both missing parameters and forms submitted with only spaces. presence goes one step further: it returns the receiver when present, otherwise nil, so name.presence || "Guest" provides a default without a longer conditional.

The three important questions below carry these patterns into a monitoring regression, a multi-tenant race and a testable fee event.

  1. Mid to senior

    After deploying a new monitoring subscriber, the p99 latency of every controller action doubled. The subscriber logs event names to an external service. Explain how you would diagnose this, what the likely cause is, and how you would fix it without removing the monitoring.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A multi-tenant application sets the current tenant in ApplicationController and reads it in models using a class variable @@current_tenant. One customer reports seeing another customer's data. What caused this, and how would you fix the models without changing the controller?

    Model answer, Pro. First step and follow-ups

  3. Senior to staff

    You need to add instrumentation to a fee calculation service so that tests can assert which fee rules were applied and integration tests can measure timing. Design the event naming, payload structure, and show how a test would subscribe, trigger a calculation, and verify the payload.

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What are Active Support's core extensions, and what do they cost?

    Methods such as blank?, presence, 2.days and to_sentence that Rails adds to Ruby's own classes when it loads, so code reads almost like English. The cost is that they change classes Ruby ships with.

  2. How do nil?, blank? and present? differ?

    nil? is plain Ruby and is true only for nil. blank? is true for nil, false, empty strings and arrays, and strings of only whitespace. present? is the opposite of blank?.

  3. What does presence return, and what is it good for?

    The receiver when it is present, otherwise nil, so name.presence || "N/A" gives a default in one expression.

  4. What does ActiveSupport::Notifications.instrument do?

    It wraps a block and publishes an event with a name and a payload hash. Every subscriber whose pattern matches the name, as an exact string or a regex, receives an event object with the timing and the payload.

  5. How should your own notification events be named, and why?

    As action.namespace, such as calculate_total.fees, the same shape Rails uses for process_action.action_controller and sql.active_record, so a regex like one ending in .fees can collect a whole family.

  6. A new monitoring subscriber doubled a page's p99 latency. Why, and what is the fix?

    Subscribers run inline, on the request's own thread, right after the instrumented block, so a network call inside one adds its time to every request. Hand slow work to a background job and return at once.

Key takeaways

  • Core extensions add blank?, present?, presence, try, in? and 2.days to Ruby’s classes; they make Rails code readable but change classes you did not write.
  • ActiveSupport::Notifications.instrument wraps a block and publishes an event; subscribers match the name by string or regex and receive timing and a payload hash.
  • Subscribers run synchronously on the request thread, so slow work inside one shows up as request latency; a short subscriber that hands off the event is the fix.
  • The action.namespace convention lets a regex like /\.fees$/ catch every event in a stream without listing each one by hand.

Terms in this lesson

class_attribute
A declaration from Active Support that defines an inheritable, per-class value on a class and its subclasses.
concern
A module included in a model or controller that uses ActiveSupport::Concern to share scopes, validations and methods between classes.
core extension
A method that Active Support adds to a Ruby class it does not own, such as blank? on Object or 2.days on Integer, available in any Rails app.
notification subscriber
A block or callable registered with ActiveSupport::Notifications that receives every event whose name matches its pattern.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.