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

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions