Skip to content

Foundation: Create OptionsLike module and BaseOptions class #1649

@iMacTia

Description

@iMacTia

Foundation: Create OptionsLike and BaseOptions

Phase: 1 - Foundation
Release Target: v2.x.0
Tracking Issue: #1647
RFC: docs/options-detach-plan.md

Overview

Create the foundational components for the new Options architecture:

  • Faraday::OptionsLike - Marker module for duck-typed interop
  • Faraday::BaseOptions - Abstract superclass with shared behavior

This establishes the base architecture that all Options subclasses will use.

Implementation Details

1. Create OptionsLike Module

File: lib/faraday/options_like.rb

# frozen_string_literal: true

module Faraday
  # Marker module for Options-like objects.
  # Enables duck-typed interop and integration with Utils.deep_merge!
  module OptionsLike
  end
end

Purpose: Allows both legacy Options and new BaseOptions classes to be treated uniformly in duck-typed contexts.

2. Create BaseOptions Class

File: lib/faraday/base_options.rb

# frozen_string_literal: true

module Faraday
  # Abstract base class for Options-like classes.
  # Provides common functionality for nested coercion, deep merging, and duplication.
  class BaseOptions
    include OptionsLike

    # Subclasses must define:
    # - MEMBERS: Array of attribute names (symbols)
    # - COERCIONS: Hash mapping attribute names to coercion classes

    class << self
      # Create new instance from hash or existing instance
      def from(value)
        return value if value.is_a?(self)
        return new if value.nil?
        new(value)
      end
    end

    def initialize(options = {})
      options = options.to_hash if options.respond_to?(:to_hash)
      self.class::MEMBERS.each do |key|
        value = options[key] || options[key.to_s]
        value = coerce(key, value)
        instance_variable_set(:"@#{key}", value)
      end
    end

    # Update this instance with values from another hash/instance
    def update(obj)
      obj = obj.to_hash if obj.respond_to?(:to_hash)
      obj.each do |key, value|
        key = key.to_sym
        if self.class::MEMBERS.include?(key)
          value = coerce(key, value)
          instance_variable_set(:"@#{key}", value)
        end
      end
      self
    end

    # Non-destructive merge
    def merge(obj)
      deep_dup.merge!(obj)
    end

    # Destructive merge (uses Utils.deep_merge!)
    def merge!(obj)
      obj = obj.to_hash if obj.respond_to?(:to_hash)
      Utils.deep_merge!(to_hash, obj)
      update(to_hash)
    end

    # Deep duplication
    def deep_dup
      self.class.new(
        self.class::MEMBERS.each_with_object({}) do |key, hash|
          value = instance_variable_get(:"@#{key}")
          hash[key] = Utils.deep_dup(value)
        end
      )
    end

    # Convert to hash
    def to_hash
      self.class::MEMBERS.each_with_object({}) do |key, hash|
        hash[key] = instance_variable_get(:"@#{key}")
      end
    end

    # Inspect
    def inspect
      "#<#{self.class} #{to_hash.inspect}>"
    end

    private

    def coerce(key, value)
      coercion = self.class::COERCIONS[key]
      return value unless coercion
      return value if value.is_a?(coercion)
      coercion.from(value)
    end
  end
end

3. Update Autoload

File: lib/faraday.rb

Add to autoloads section:

autoload :OptionsLike, 'faraday/options_like'
autoload :BaseOptions, 'faraday/base_options'

Tasks

  • Create lib/faraday/options_like.rb
  • Create lib/faraday/base_options.rb with full implementation
  • Add autoloads to lib/faraday.rb
  • Create spec/faraday/base_options_spec.rb with comprehensive tests:
    • Test .from class method
    • Test initialization from hash
    • Test initialization from instance
    • Test update method
    • Test merge (non-destructive)
    • Test merge! (destructive)
    • Test deep_dup
    • Test to_hash
    • Test inspect
    • Test nested coercion behavior
  • Run full test suite to ensure no regressions
  • Integration tests pass (Add comprehensive integration tests using faraday-live approach #1648)

Acceptance Criteria

  • OptionsLike module exists and can be included
  • BaseOptions provides all shared functionality
  • All tests pass
  • No breaking changes to existing Options classes
  • Documentation includes YARD comments

Dependencies

Files to Create

  • lib/faraday/options_like.rb
  • lib/faraday/base_options.rb
  • spec/faraday/base_options_spec.rb

Files to Modify

  • lib/faraday.rb (add autoloads)

Backward Compatibility

No breaking changes - this only adds new classes, doesn't modify existing ones.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions