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.



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

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

#==(other) ⇒ Object

Check structural equality by comparing components.



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

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

#===(other) ⇒ Object

Check string equality, useful for case statements.



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

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.



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

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.



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

def as_json(...)
	to_s
end

#equal?(other) ⇒ Boolean

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

Returns:

  • (Boolean)


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

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.



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

def hash
	to_ary.hash
end

#inspectObject

Generate a human-readable representation for debugging.



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

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"


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

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) ⇒ 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.



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

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

#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.



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

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

#to_jsonObject

Convert the URL to JSON.



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

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

#to_sObject

Convert the URL to its string representation.



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

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