Protocol::URLSourceProtocolURLRelative

class Relative

Represents a relative URL, which does not include a scheme or authority.

Definitions

def initialize(path, query = nil, fragment = nil)

Initialize a new relative URL.

Signature

parameter path String | Path

The encoded path component.

parameter query String | Nil

The query string.

parameter fragment String | Nil

The fragment identifier.

Implementation

def initialize(path, query = nil, fragment = nil)
	@path = Path[path]
	@query = query
	@fragment = fragment
end

def freeze

Freeze the URL and its direct components.

Signature

returns Relative

The frozen URL.

Implementation

def freeze
	return self if frozen?
	
	@path.freeze
	@query.freeze
	@fragment.freeze
	
	return super
end

attr :path

Signature

attribute Path

The path component of the URL.

def path=(path)

Replace the path component of this URL.

Signature

parameter path String | Path

The encoded path component.

returns Path

The assigned path component.

Implementation

def path=(path)
	@path = Path[path]
end

attr_accessor :query

Signature

attribute String | Nil

The query string component.

attr_accessor :fragment

Signature

attribute String | Nil

The fragment identifier.

def local_path(root)

Resolve the URL path beneath a local filesystem root.

Signature

parameter root String

The filesystem root beneath which to resolve the URL path.

returns String

The expanded local filesystem path.

raises ArgumentError

If a URL segment is invalid or the path escapes the specified root.

Implementation

def local_path(root)
	@path.local_path(root)
end

def query?

Signature

returns Boolean

If there is a query string.

Implementation

def query?
	@query and !@query.empty?
end

def fragment?

Signature

returns Boolean

If there is a fragment.

Implementation

def fragment?
	@fragment and !@fragment.empty?
end

def +(other)

Combine this relative URL with another URL or path.

Example: Combine two relative paths.

base = Relative.new("/documents/reports/")
other = Relative.new("invoices/2024.pdf")
result = base + other
result.path.to_s  # => "/documents/reports/invoices/2024.pdf"

Example: Navigate to parent directory.

base = Relative.new("/documents/reports/archive/")
other = Relative.new("../../summary.pdf")
result = base + other
result.path.to_s  # => "/documents/summary.pdf"

Signature

parameter other String, Absolute, Relative

The URL or path to combine.

returns Absolute, Relative

The combined URL.

Implementation

def +(other)
	case other
	when Absolute
		# Relative + Absolute: the absolute URL takes precedence
		# You can't apply relative navigation to an absolute URL
		other
	when Relative
		# Relative + Relative: merge paths directly
		self.class.new(
			@path.join(other.path),
			other.query,
			other.fragment
		)
	when String
		# Relative + String: parse and combine
		self + URL[other]
	else
		raise ArgumentError, "Cannot combine Relative URL with #{other.class}"
	end
end

def with(path: nil, query: @query, fragment: @fragment, pop: true)

Create a new Relative URL with modified components.

Example: Update the query string.

url = Relative.new("/search", "query=ruby")
updated = url.with(query: "query=python")
updated.to_s  # => "/search?query=python"

Example: Append to the path.

url = Relative.new("/documents/")
updated = url.with(path: "report.pdf", pop: false)
updated.to_s  # => "/documents/report.pdf"

Signature

parameter path String | Nil

The path to merge with the current path.

parameter query String | Nil

The query string to use.

parameter fragment String | Nil

The fragment to use.

parameter pop Boolean

Whether to pop the last path component before merging.

returns Relative

A new Relative URL with the modified components.

Implementation

def with(path: nil, query: @query, fragment: @fragment, pop: true)
	path = @path.join(path, pop: pop) unless path.nil?
	
	self.class.new(path || @path, query, fragment)
end

def relative_to(base)

Express this URL relative to the given base path.

Already-relative paths are returned unchanged. Query and fragment components are preserved when converting a root-relative path.

Signature

parameter base Relative | Path | String

The base URL or path.

returns Relative

The relative URL.

Implementation

def relative_to(base)
	return self unless @path.absolute?
	
	if base.is_a?(Relative)
		base = base.path
	end
	
	return self.class.new(@path.relative(base), @query, @fragment)
end

def normalize!

Normalize the encoded path and simplify its structure.

This modifies the URL in-place by normalizing and simplifying the path component:

  • Decodes percent-encoded unreserved characters
  • Uses uppercase hexadecimal digits for retained percent escapes
  • Removes "." segments (current directory)
  • Resolves ".." segments (parent directory)
  • Collapses empty path segments represented by consecutive slashes

Normalization is intentionally lossy. Callers that need to preserve the original path structure should retain the parsed URL and avoid this method.

Example: Basic normalization

url = Relative.new("/foo//bar/./baz/../qux")
url.normalize!
url.path.to_s  # => "/foo/bar/qux"

Signature

returns self

The normalized URL.

Implementation

def normalize!
	@path = @path.normalize.simplify
	
	return self
end

def append(buffer = String.new, explicit: false)

Append the relative URL to the given buffer. The path, query, and fragment are expected to already be properly encoded.

Signature

parameter explicit Boolean

Whether the result should be lexically identifiable as a URL in a mixed grammar.

Implementation

def append(buffer = String.new, explicit: false)
	append_path(buffer, explicit: explicit)
	
	if @query and !@query.empty?
		buffer << "?" << @query
	end
	
	if @fragment and !@fragment.empty?
		buffer << "#" << @fragment
	end
	
	return buffer
end

def to_ary

Convert the URL to an array representation.

Signature

returns Array

An array of [path, query, fragment].

Implementation

def to_ary
	[@path, @query, @fragment]
end

def hash

Compute a hash value for the URL based on its components.

Signature

returns Integer

The hash value.

Implementation

def hash
	to_ary.hash
end

def equal?(other)

Check if this URL is equal to another URL by comparing components.

Signature

parameter other Relative

The URL to compare with.

returns Boolean

True if the URLs have identical components.

Implementation

def equal?(other)
	to_ary == other.to_ary
end

def <=>(other)

Compare this URL with another for sorting purposes.

Signature

parameter other Relative

The URL to compare with.

returns Integer

-1, 0, or 1 based on component-wise comparison.

Implementation

def <=>(other)
	to_ary <=> other.to_ary
end

def ==(other)

Check structural equality by comparing components.

Signature

parameter other Relative

The URL to compare with.

returns Boolean

True if the URLs have identical components.

Implementation

def ==(other)
	to_ary == other.to_ary
end

def ===(other)

Check string equality, useful for case statements.

Signature

parameter other String, Relative

The value to compare with.

returns Boolean

True if the string representations match.

Implementation

def ===(other)
	to_s === other
end

def to_s(explicit: false)

Convert the URL to its string representation. When explicit, same-directory references start with ./ so they can be distinguished from non-URL values in a mixed grammar.

Signature

parameter explicit Boolean

Whether the result should be lexically identifiable as a URL in a mixed grammar.

returns String

The formatted URL string.

Implementation

def to_s(explicit: false)
	append(explicit: explicit)
end

def as_json(...)

Convert the URL to a JSON-compatible representation.

Signature

returns String

The URL as a string.

Implementation

def as_json(...)
	to_s
end

def to_json(...)

Convert the URL to JSON.

Signature

returns String

The JSON-encoded URL.

Implementation

def to_json(...)
	as_json.to_json(...)
end

def inspect

Generate a human-readable representation for debugging.

Signature

returns String

A string like #<Protocol::URL::Relative /path?query#fragment>.

Implementation

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