Class: Protocol::URL::Relative

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/protocol/url/relative.rb

Overview

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

Direct Known Subclasses

Absolute, Reference

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, query = nil, fragment = nil) ⇒ Relative

Initialize a new relative URL.



20
21
22
23
24
# File 'lib/protocol/url/relative.rb', line 20

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

Instance Attribute Details

#fragmentObject

Returns the value of attribute fragment.



52
53
54
# File 'lib/protocol/url/relative.rb', line 52

def fragment
  @fragment
end

#pathObject

Returns the value of attribute path.



39
40
41
# File 'lib/protocol/url/relative.rb', line 39

def path
  @path
end

#queryObject

Returns the value of attribute query.



49
50
51
# File 'lib/protocol/url/relative.rb', line 49

def query
  @query
end

#The fragment identifier.(fragmentidentifier.) ⇒ Object (readonly)



52
# File 'lib/protocol/url/relative.rb', line 52

attr_accessor :fragment

#The query string component.(querystringcomponent.) ⇒ Object (readonly)



49
# File 'lib/protocol/url/relative.rb', line 49

attr_accessor :query

Instance Method Details

#+(other) ⇒ Object

Combine this relative URL with another URL or path.

Examples:

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"

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"


91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/protocol/url/relative.rb', line 91

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

#<=>(other) ⇒ Object

Compare this URL with another for sorting purposes.



219
220
221
# File 'lib/protocol/url/relative.rb', line 219

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

#==(other) ⇒ Object

Check structural equality by comparing components.



227
228
229
# File 'lib/protocol/url/relative.rb', line 227

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

#===(other) ⇒ Object

Check string equality, useful for case statements.



235
236
237
# File 'lib/protocol/url/relative.rb', line 235

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

#append(buffer = String.new) ⇒ Object

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



179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/protocol/url/relative.rb', line 179

def append(buffer = String.new)
	buffer << @path.encoded
	
	if @query and !@query.empty?
		buffer << "?" << @query
	end
	
	if @fragment and !@fragment.empty?
		buffer << "#" << @fragment
	end
	
	return buffer
end

#as_jsonObject

Convert the URL to a JSON-compatible representation.



249
250
251
# File 'lib/protocol/url/relative.rb', line 249

def as_json(...)
	to_s
end

#equal?(other) ⇒ Boolean

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

Returns:

  • (Boolean)


211
212
213
# File 'lib/protocol/url/relative.rb', line 211

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

#fragment?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/protocol/url/relative.rb', line 71

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

#freezeObject

Freeze the URL and its direct components.



28
29
30
31
32
33
34
35
36
# File 'lib/protocol/url/relative.rb', line 28

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

#hashObject

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



203
204
205
# File 'lib/protocol/url/relative.rb', line 203

def hash
	to_ary.hash
end

#inspectObject

Generate a human-readable representation for debugging.



263
264
265
# File 'lib/protocol/url/relative.rb', line 263

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

#local_path(root) ⇒ Object Also known as: to_local_path

Resolve the URL path beneath a local filesystem root.



59
60
61
# File 'lib/protocol/url/relative.rb', line 59

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

#normalize!Object

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.

Examples:

Basic normalization

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


171
172
173
174
175
# File 'lib/protocol/url/relative.rb', line 171

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

#query?Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/protocol/url/relative.rb', line 66

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

#relative_to(base, explicit: false) ⇒ Object

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.



143
144
145
146
147
148
149
150
151
# File 'lib/protocol/url/relative.rb', line 143

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

#The path component of the URL.=(pathcomponentoftheURL. = (value)) ⇒ Object



39
# File 'lib/protocol/url/relative.rb', line 39

attr :path

#to_aryObject

Convert the URL to an array representation.



196
197
198
# File 'lib/protocol/url/relative.rb', line 196

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

#to_jsonObject

Convert the URL to JSON.



256
257
258
# File 'lib/protocol/url/relative.rb', line 256

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

#to_sObject

Convert the URL to its string representation.



242
243
244
# File 'lib/protocol/url/relative.rb', line 242

def to_s
	append
end

#with(path: nil, query: @query, fragment: @fragment, pop: true) ⇒ Object

Create a new Relative URL with modified components.

Examples:

Update the query string.

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

Append to the path.

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


129
130
131
132
133
# File 'lib/protocol/url/relative.rb', line 129

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