Understanding `cattr_accessor` in Ruby on Rails

phanil kumarphanil kumar
3 min read

In Ruby on Rails development, sharing data across multiple instances of a class often requires careful handling of class-level variables. ActiveSupport, a core component of Rails, provides several elegant methods to define class-level attributes. Among these, cattr_accessor stands out as a powerful yet sometimes overlooked utility. This post explores what cattr_accessor is, how it works, and when to use it in your Rails applications.

What is cattr_accessor?

cattr_accessor is a method provided by Rails' ActiveSupport that creates both getter and setter methods for class variables. It generates methods to access a class variable from both the class level and instance level, streamlining the management of shared state across all instances of a class.

How cattr_accessor Works

When you declare a class attribute using cattr_accessor, Rails automatically creates:

  1. A class variable (prefixed with @@)

  2. Class-level getter and setter methods

  3. Instance-level methods that read from and write to the class variable

This single declaration produces multiple methods, saving you from writing boilerplate code.

A Practical Example: Configuration Settings

Consider a scenario where you need to manage configuration settings for a payment gateway integration:

class PaymentGateway
  cattr_accessor :api_key, :environment, :timeout

  # Default values
  @@api_key = nil
  @@environment = :sandbox
  @@timeout = 30

  def process_payment(amount)
    # The instance method can access the class variables
    client = ApiClient.new(
      key: self.api_key,
      env: self.environment,
      timeout: self.timeout
    )

    client.charge(amount)
  end

  def self.configure
    yield self if block_given?
  end
end

This implementation enables elegant configuration patterns:

# In an initializer file:
PaymentGateway.configure do |config|
  config.api_key = ENV['PAYMENT_API_KEY']
  config.environment = Rails.env.production? ? :production : :sandbox
  config.timeout = 60
end

# Later, in your application:
gateway = PaymentGateway.new
gateway.process_payment(100.00) # Uses the configured values

Comparing with Other Accessor Types

To understand cattr_accessor better, let's compare it with other accessor types:

Accessor TypeCreatesAccess LevelUse Case
attr_accessorInstance variablesInstance onlyPer-instance state
cattr_accessorClass variablesClass & instanceShared state across all instances
mattr_accessorModule variablesModule & included classesShared state in a module context

Real-World Application: Circuit Breaker Pattern

A practical application of cattr_accessor is implementing a circuit breaker pattern for external API calls:

class ExternalApiClient
  cattr_accessor :failure_count, :last_failure_time, :circuit_open

  # Initialize class variables
  @@failure_count = 0
  @@last_failure_time = nil
  @@circuit_open = false

  FAILURE_THRESHOLD = 5
  RETRY_TIMEOUT = 300 # seconds

  def self.check_circuit
    # Reset circuit if timeout has passed
    if @@circuit_open && @@last_failure_time && (Time.now - @@last_failure_time) > RETRY_TIMEOUT
      @@circuit_open = false
      @@failure_count = 0
    end

    @@circuit_open
  end

  def make_api_call(endpoint)
    if self.class.check_circuit
      raise "Circuit breaker open - too many failures"
    end

    begin
      response = HTTP.get(endpoint)

      # Reset on success
      @@failure_count = 0

      response
    rescue => e
      @@failure_count += 1
      @@last_failure_time = Time.now

      if @@failure_count >= FAILURE_THRESHOLD
        @@circuit_open = true
      end

      raise e
    end
  end
end

In this example, all instances of ExternalApiClient share the same circuit state. If one instance experiences multiple failures, all instances will stop attempting API calls until the retry timeout expires.

Best Practices

When using cattr_accessor, consider these best practices:

  1. Use sparingly: Class variables maintain state across all instances, which can lead to unexpected behavior if overused.

  2. Initialize values: Always set default values for your class variables to avoid nil errors.

  3. Consider thread safety: In multi-threaded environments, protect writes to class variables with synchronization mechanisms if needed.

  4. Document clearly: Since class variables affect all instances, clearly document their purpose and expected usage.

Conclusion

The cattr_accessor method elegantly solves the problem of sharing state across all instances of a class. It's particularly useful for configuration settings, global state management, and patterns like circuit breakers. By understanding how and when to use this utility, you can write more maintainable and concise Rails code.

When used appropriately, cattr_accessor helps strike the right balance between encapsulation and shared functionality, enabling clean patterns that would otherwise require more complex code structures.

0
Subscribe to my newsletter

Read articles from phanil kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

phanil kumar
phanil kumar

Ruby/Rails