fauna 1.3.4 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/CHANGELOG +2 -0
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/README.md +65 -102
- data/Rakefile +12 -20
- data/fauna.gemspec +19 -44
- data/lib/fauna.rb +12 -14
- data/lib/fauna/client.rb +225 -27
- data/lib/fauna/client_logger.rb +52 -0
- data/lib/fauna/context.rb +135 -0
- data/lib/fauna/errors.rb +181 -0
- data/lib/fauna/json.rb +60 -0
- data/lib/fauna/objects.rb +96 -0
- data/lib/fauna/query.rb +601 -0
- data/lib/fauna/request_result.rb +58 -0
- data/lib/fauna/util.rb +41 -0
- data/lib/fauna/version.rb +4 -0
- data/spec/client_logger_spec.rb +73 -0
- data/spec/client_spec.rb +202 -0
- data/spec/context_spec.rb +121 -0
- data/spec/errors_spec.rb +144 -0
- data/spec/fauna_helper.rb +87 -0
- data/spec/json_spec.rb +123 -0
- data/spec/query_spec.rb +675 -0
- data/spec/ref_spec.rb +77 -0
- data/spec/setref_spec.rb +23 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/util_spec.rb +19 -0
- metadata +65 -83
- data/Manifest +0 -25
- data/lib/fauna/cache.rb +0 -64
- data/lib/fauna/connection.rb +0 -152
- data/lib/fauna/named_resource.rb +0 -17
- data/lib/fauna/rails.rb +0 -120
- data/lib/fauna/resource.rb +0 -175
- data/lib/fauna/set.rb +0 -240
- data/lib/tasks/fauna.rake +0 -71
- data/test/class_test.rb +0 -65
- data/test/client_test.rb +0 -63
- data/test/connection_test.rb +0 -66
- data/test/database_test.rb +0 -48
- data/test/query_test.rb +0 -48
- data/test/readme_test.rb +0 -30
- data/test/set_test.rb +0 -71
- data/test/test_helper.rb +0 -86
@@ -0,0 +1,52 @@
|
|
1
|
+
module Fauna
|
2
|
+
# Example observer that can be used for debugging
|
3
|
+
module ClientLogger
|
4
|
+
##
|
5
|
+
# Lambda that can be the +observer+ for a Client.
|
6
|
+
# Will call the passed block on a string representation of each RequestResult.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# logger = ClientLogger.logger do |str|
|
11
|
+
# puts str
|
12
|
+
# end
|
13
|
+
# Client.new observer: logger, ...
|
14
|
+
def self.logger
|
15
|
+
lambda do |request_result|
|
16
|
+
yield show_request_result(request_result)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Translates a RequestResult to a string suitable for logging.
|
21
|
+
def self.show_request_result(request_result)
|
22
|
+
rr = request_result
|
23
|
+
logged = ''
|
24
|
+
|
25
|
+
logged << "Fauna #{rr.method.to_s.upcase} /#{rr.path}#{query_string_for_logging(rr.query)}\n"
|
26
|
+
logged << " Credentials: #{rr.auth}\n"
|
27
|
+
if rr.request_content
|
28
|
+
logged << " Request JSON: #{indent(FaunaJson.to_json_pretty(rr.request_content))}\n"
|
29
|
+
end
|
30
|
+
logged << " Response headers: #{indent(FaunaJson.to_json_pretty(rr.response_headers))}\n"
|
31
|
+
logged << " Response JSON: #{indent(FaunaJson.to_json_pretty(rr.response_content))}\n"
|
32
|
+
logged << " Response (#{rr.status_code}): Network latency #{(rr.time_taken * 1000).to_i}ms"
|
33
|
+
|
34
|
+
logged
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.indent(str) # :nodoc:
|
38
|
+
indent_str = ' '
|
39
|
+
str.split("\n").join("\n" + indent_str)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.query_string_for_logging(query) # :nodoc:
|
43
|
+
return unless query && !query.empty?
|
44
|
+
|
45
|
+
'?' + query.collect do |k, v|
|
46
|
+
"#{k}=#{v}"
|
47
|
+
end.join('&')
|
48
|
+
end
|
49
|
+
|
50
|
+
private_class_method :indent, :query_string_for_logging
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Fauna
|
2
|
+
##
|
3
|
+
# Error raised when the context is used without a client being set.
|
4
|
+
class NoContextError < RuntimeError; end
|
5
|
+
|
6
|
+
##
|
7
|
+
# The client context wrapper.
|
8
|
+
#
|
9
|
+
# Used for accessing the client without directly passing around the client instance.
|
10
|
+
# Context is scoped to the current thread.
|
11
|
+
class Context
|
12
|
+
##
|
13
|
+
# Returns a context block with the given client.
|
14
|
+
#
|
15
|
+
# +client+:: Client to use for the context block.
|
16
|
+
def self.block(client)
|
17
|
+
push(client)
|
18
|
+
yield
|
19
|
+
ensure
|
20
|
+
pop
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Adds a client to the current context.
|
25
|
+
#
|
26
|
+
# +client+:: Client to add to the current context.
|
27
|
+
def self.push(client)
|
28
|
+
stack.push(client)
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Removes the last client context from the stack and returns it.
|
33
|
+
def self.pop
|
34
|
+
stack.pop
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Resets the current client context, removing all the clients from the stack.
|
39
|
+
def self.reset
|
40
|
+
stack.clear
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Performs a +GET+ request for a REST endpoint within the current client context.
|
45
|
+
#
|
46
|
+
# +path+:: Path to +GET+.
|
47
|
+
# +query+:: Query parameters to append to the path.
|
48
|
+
#
|
49
|
+
# Reference: {FaunaDB REST API}[https://faunadb.com/documentation/rest]
|
50
|
+
#
|
51
|
+
# :category: Client Methods
|
52
|
+
def self.get(path, query = {})
|
53
|
+
client.get(path, query)
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Performs a +POST+ request for a REST endpoint within the current client context.
|
58
|
+
#
|
59
|
+
# +path+:: Path to +POST+.
|
60
|
+
# +data+:: Data to post as the body.
|
61
|
+
#
|
62
|
+
# Reference: {FaunaDB REST API}[https://faunadb.com/documentation/rest]
|
63
|
+
#
|
64
|
+
# :category: Client Methods
|
65
|
+
def self.post(path, data = {})
|
66
|
+
client.post(path, data)
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Performs a +PUT+ request for a REST endpoint within the current client context.
|
71
|
+
#
|
72
|
+
# +path+:: Path to +PUT+.
|
73
|
+
# +data+:: Data to post as the body.
|
74
|
+
#
|
75
|
+
# Reference: {FaunaDB REST API}[https://faunadb.com/documentation/rest]
|
76
|
+
#
|
77
|
+
# :category: Client Methods
|
78
|
+
def self.put(path, data = {})
|
79
|
+
client.put(path, data)
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Performs a +PATCH+ request for a REST endpoint within the current client context.
|
84
|
+
#
|
85
|
+
# +path+:: Path to +PATCH+.
|
86
|
+
# +data+:: Data to post as the body.
|
87
|
+
#
|
88
|
+
# Reference: {FaunaDB REST API}[https://faunadb.com/documentation/rest]
|
89
|
+
#
|
90
|
+
# :category: Client Methods
|
91
|
+
def self.patch(path, data = {})
|
92
|
+
client.patch(path, data)
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Performs a +DELETE+ request for a REST endpoint within the current client context.
|
97
|
+
#
|
98
|
+
# +path+:: Path to +DELETE+.
|
99
|
+
# +data+:: Data to post as the body.
|
100
|
+
#
|
101
|
+
# Reference: {FaunaDB REST API}[https://faunadb.com/documentation/rest]
|
102
|
+
#
|
103
|
+
# :category: Client Methods
|
104
|
+
def self.delete(path)
|
105
|
+
client.delete(path)
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Issues a query to FaunaDB with the current client context.
|
110
|
+
#
|
111
|
+
# Queries are built via the Query helpers. See {FaunaDB Query API}[https://faunadb.com/documentation/queries]
|
112
|
+
# for information on constructing queries.
|
113
|
+
#
|
114
|
+
# +expression+:: A query expression
|
115
|
+
#
|
116
|
+
# :category: Client Methods
|
117
|
+
def self.query(expression = nil, &expr_block)
|
118
|
+
client.query(expression, &expr_block)
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Returns the current context's client, or if there is none, raises NoContextError.
|
123
|
+
def self.client
|
124
|
+
stack.last || fail(NoContextError, 'You must be within a Fauna::Context.block to perform operations.')
|
125
|
+
end
|
126
|
+
|
127
|
+
class << self
|
128
|
+
private
|
129
|
+
|
130
|
+
def stack
|
131
|
+
Thread.current[:fauna_context_stack] ||= []
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/lib/fauna/errors.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
module Fauna
|
2
|
+
##
|
3
|
+
# Error for when the server returns an unexpected kind of response.
|
4
|
+
class UnexpectedError < RuntimeError
|
5
|
+
# RequestResult for the request that caused this error.
|
6
|
+
attr_reader :request_result
|
7
|
+
|
8
|
+
def initialize(description, request_result) # :nodoc:
|
9
|
+
super(description)
|
10
|
+
@request_result = request_result
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.get_or_raise(request_result, hash, key) # :nodoc:
|
14
|
+
unless hash.is_a? Hash and hash.key? key
|
15
|
+
fail UnexpectedError.new("Response JSON does not contain expected key #{key}", request_result)
|
16
|
+
end
|
17
|
+
hash[key]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Error returned by the FaunaDB server.
|
23
|
+
# For documentation of error types, see the docs[https://faunadb.com/documentation#errors].
|
24
|
+
class FaunaError < RuntimeError
|
25
|
+
# List of ErrorData objects returned by the server.
|
26
|
+
attr_reader :errors
|
27
|
+
|
28
|
+
# RequestResult for the request that caused this error.
|
29
|
+
attr_reader :request_result
|
30
|
+
|
31
|
+
##
|
32
|
+
# Raises the associated error from a RequestResult based on the status code.
|
33
|
+
#
|
34
|
+
# Returns +nil+ for 2xx status codes
|
35
|
+
def self.raise_for_status_code(request_result)
|
36
|
+
case request_result.status_code
|
37
|
+
when 200..299
|
38
|
+
|
39
|
+
when 400
|
40
|
+
fail BadRequest.new(request_result)
|
41
|
+
when 401
|
42
|
+
fail Unauthorized.new(request_result)
|
43
|
+
when 403
|
44
|
+
fail PermissionDenied.new(request_result)
|
45
|
+
when 404
|
46
|
+
fail NotFound.new(request_result)
|
47
|
+
when 405
|
48
|
+
fail MethodNotAllowed.new(request_result)
|
49
|
+
when 500
|
50
|
+
fail InternalError.new(request_result)
|
51
|
+
when 503
|
52
|
+
fail UnavailableError.new(request_result)
|
53
|
+
else
|
54
|
+
fail UnexpectedError.new('Unexpected status code.', request_result)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Creates a new error from a given RequestResult.
|
59
|
+
def initialize(request_result)
|
60
|
+
@request_result = request_result
|
61
|
+
errors_raw = UnexpectedError.get_or_raise request_result, request_result.response_content, :errors
|
62
|
+
@errors = catch :invalid_response do
|
63
|
+
throw :invalid_response unless errors_raw.is_a? Array
|
64
|
+
errors_raw.map { |error| ErrorData.from_hash(error) }
|
65
|
+
end
|
66
|
+
|
67
|
+
if @errors.nil?
|
68
|
+
fail UnexpectedError.new('Error data has an unexpected format.', request_result)
|
69
|
+
end
|
70
|
+
|
71
|
+
super(@errors ? @errors[0].description : '(empty `errors`)')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# An exception thrown if FaunaDB cannot evaluate a query.
|
76
|
+
class BadRequest < FaunaError; end
|
77
|
+
|
78
|
+
# An exception thrown if FaunaDB responds with an HTTP 401.
|
79
|
+
class Unauthorized < FaunaError; end
|
80
|
+
|
81
|
+
# An exception thrown if FaunaDB responds with an HTTP 403.
|
82
|
+
class PermissionDenied < FaunaError; end
|
83
|
+
|
84
|
+
# An exception thrown if FaunaDB responds with an HTTP 404 for non-query endpoints.
|
85
|
+
class NotFound < FaunaError; end
|
86
|
+
|
87
|
+
# An exception thrown if FaunaDB responds with an HTTP 405.
|
88
|
+
class MethodNotAllowed < FaunaError; end
|
89
|
+
|
90
|
+
##
|
91
|
+
# An exception thrown if FaunaDB responds with an HTTP 500. Such errors represent an internal
|
92
|
+
# failure within the database.
|
93
|
+
class InternalError < FaunaError; end
|
94
|
+
|
95
|
+
##
|
96
|
+
# An exception thrown if FaunaDB responds with an HTTP 503.
|
97
|
+
class UnavailableError < FaunaError; end
|
98
|
+
|
99
|
+
# Data for one error returned by the server.
|
100
|
+
class ErrorData
|
101
|
+
##
|
102
|
+
# Error code.
|
103
|
+
#
|
104
|
+
# Reference: {FaunaDB Error codes}[https://faunadb.com/documentation#errors]
|
105
|
+
attr_reader :code
|
106
|
+
# Error description.
|
107
|
+
attr_reader :description
|
108
|
+
# Position of the error in a query. May be +nil+.
|
109
|
+
attr_reader :position
|
110
|
+
# List of Failure objects returned by the server. +nil+ except for <code>validation failed</code> errors.
|
111
|
+
attr_reader :failures
|
112
|
+
|
113
|
+
def self.from_hash(hash) # :nodoc:
|
114
|
+
code = ErrorHelpers.get_or_throw hash, :code
|
115
|
+
description = ErrorHelpers.get_or_throw hash, :description
|
116
|
+
position = ErrorHelpers.map_position hash[:position]
|
117
|
+
failures = hash[:failures].map(&Failure.method(:from_hash)) unless hash[:failures].nil?
|
118
|
+
ErrorData.new code, description, position, failures
|
119
|
+
end
|
120
|
+
|
121
|
+
def initialize(code, description, position, failures) # :nodoc:
|
122
|
+
@code = code
|
123
|
+
@description = description
|
124
|
+
@position = position
|
125
|
+
@failures = failures
|
126
|
+
end
|
127
|
+
|
128
|
+
def inspect # :nodoc:
|
129
|
+
"ErrorData(#{code.inspect}, #{description.inspect}, #{position.inspect}, #{failures.inspect})"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Part of ErrorData.
|
135
|
+
# For more information, see the {docs}[https://faunadb.com/documentation#errors-invalid_data].
|
136
|
+
class Failure
|
137
|
+
# Failure code.
|
138
|
+
attr_reader :code
|
139
|
+
# Failure description.
|
140
|
+
attr_reader :description
|
141
|
+
# Field of the failure in the instance.
|
142
|
+
attr_reader :field
|
143
|
+
|
144
|
+
def self.from_hash(hash) # :nodoc:
|
145
|
+
Failure.new(
|
146
|
+
ErrorHelpers.get_or_throw(hash, :code),
|
147
|
+
ErrorHelpers.get_or_throw(hash, :description),
|
148
|
+
ErrorHelpers.map_position(hash[:field]),
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
def initialize(code, description, field) # :nodoc:
|
153
|
+
@code = code
|
154
|
+
@description = description
|
155
|
+
@field = field
|
156
|
+
end
|
157
|
+
|
158
|
+
def inspect # :nodoc:
|
159
|
+
"Failure(#{code.inspect}, #{description.inspect}, #{field.inspect})"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
module ErrorHelpers # :nodoc:
|
164
|
+
def self.map_position(position)
|
165
|
+
unless position.nil?
|
166
|
+
position.map do |part|
|
167
|
+
if part.is_a? String
|
168
|
+
part.to_sym
|
169
|
+
else
|
170
|
+
part
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.get_or_throw(hash, key)
|
177
|
+
throw :invalid_response unless hash.is_a? Hash and hash.key? key
|
178
|
+
hash[key]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
data/lib/fauna/json.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module Fauna
|
2
|
+
module FaunaJson # :nodoc:
|
3
|
+
def self.to_json(value)
|
4
|
+
serialize(value).to_json
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.to_json_pretty(value)
|
8
|
+
JSON.pretty_generate serialize(value)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.deserialize(obj)
|
12
|
+
if obj.is_a?(Hash)
|
13
|
+
if obj.key? :@ref
|
14
|
+
Ref.new obj[:@ref]
|
15
|
+
elsif obj.key? :@set
|
16
|
+
SetRef.new deserialize(obj[:@set])
|
17
|
+
elsif obj.key? :@obj
|
18
|
+
deserialize(obj[:@obj])
|
19
|
+
elsif obj.key? :@ts
|
20
|
+
Time.iso8601 obj[:@ts]
|
21
|
+
elsif obj.key? :@date
|
22
|
+
Date.iso8601 obj[:@date]
|
23
|
+
else
|
24
|
+
Hash[obj.collect { |k, v| [k, deserialize(v)] }]
|
25
|
+
end
|
26
|
+
elsif obj.is_a?(Array)
|
27
|
+
obj.collect { |val| deserialize(val) }
|
28
|
+
else
|
29
|
+
obj
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.json_load(body)
|
34
|
+
JSON.load body, nil, max_nesting: false, symbolize_names: true
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.json_load_or_nil(body)
|
38
|
+
json_load body
|
39
|
+
rescue JSON::ParserError
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.serialize(value)
|
44
|
+
if value.is_a? Time
|
45
|
+
# 9 means: include nanoseconds in encoding
|
46
|
+
{ :@ts => value.iso8601(9) }
|
47
|
+
elsif value.is_a? Date
|
48
|
+
{ :@date => value.iso8601 }
|
49
|
+
elsif value.is_a? Hash
|
50
|
+
Hash[value.collect { |k, v| [k, serialize(v)] }]
|
51
|
+
elsif value.is_a? Array
|
52
|
+
value.collect { |val| serialize(val) }
|
53
|
+
elsif value.respond_to? :to_hash
|
54
|
+
serialize(value.to_hash)
|
55
|
+
else
|
56
|
+
value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Fauna
|
2
|
+
##
|
3
|
+
# A Ref.
|
4
|
+
#
|
5
|
+
# Reference: {FaunaDB Special Types}[https://faunadb.com/documentation/queries-values-special_types]
|
6
|
+
class Ref
|
7
|
+
# The raw ref string.
|
8
|
+
attr_accessor :value
|
9
|
+
|
10
|
+
##
|
11
|
+
# Creates a Ref object.
|
12
|
+
#
|
13
|
+
# :call-seq:
|
14
|
+
# Ref.new('databases/prydain')
|
15
|
+
# Ref.new('databases', 'prydain')
|
16
|
+
# Ref.new(Ref.new('databases'), 'prydain')
|
17
|
+
#
|
18
|
+
# +parts+: A string, or a list of strings/refs to be joined.
|
19
|
+
def initialize(*parts)
|
20
|
+
@value = parts.join '/'
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Gets the class part out of the Ref.
|
25
|
+
# This is done by removing ref.id().
|
26
|
+
# So <code>Fauna::Ref.new('a', 'b/c').to_class</code> will be
|
27
|
+
# <code>Fauna::Ref.new('a/b')</code>.
|
28
|
+
def to_class
|
29
|
+
parts = value.split '/'
|
30
|
+
if parts.length == 1
|
31
|
+
self
|
32
|
+
else
|
33
|
+
Fauna::Ref.new(*parts[0...-1])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Removes the class part of the ref, leaving only the id.
|
39
|
+
# This is everything after the last /.
|
40
|
+
def id
|
41
|
+
parts = value.split '/'
|
42
|
+
fail ArgumentError.new 'The Ref does not have an id.' if parts.length == 1
|
43
|
+
parts.last
|
44
|
+
end
|
45
|
+
|
46
|
+
# Converts the Ref to a string
|
47
|
+
def to_s
|
48
|
+
value
|
49
|
+
end
|
50
|
+
|
51
|
+
# Converts the Ref in Hash form.
|
52
|
+
def to_hash
|
53
|
+
{ :@ref => value }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns +true+ if +other+ is a Ref and contains the same value.
|
57
|
+
def ==(other)
|
58
|
+
return false unless other.is_a? Ref
|
59
|
+
value == other.value
|
60
|
+
end
|
61
|
+
|
62
|
+
alias_method :eql?, :==
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# A SetRef.
|
67
|
+
#
|
68
|
+
# Reference: {FaunaDB Special Types}[https://faunadb.com/documentation/queries-values-special_types]
|
69
|
+
class SetRef
|
70
|
+
# The raw set hash.
|
71
|
+
attr_accessor :value
|
72
|
+
|
73
|
+
##
|
74
|
+
# Creates a new SetRef with the given parameters.
|
75
|
+
#
|
76
|
+
# +params+:: Hash of parameters to build the SetRef with.
|
77
|
+
#
|
78
|
+
# Reference: {FaunaDB Special Types}[https://faunadb.com/documentation/queries-values-special_types]
|
79
|
+
def initialize(params = {})
|
80
|
+
self.value = params
|
81
|
+
end
|
82
|
+
|
83
|
+
# Converts the SetRef to Hash form.
|
84
|
+
def to_hash
|
85
|
+
{ :@set => value }
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns +true+ if +other+ is a SetRef and contains the same value.
|
89
|
+
def ==(other)
|
90
|
+
return false unless other.is_a? SetRef
|
91
|
+
value == other.value
|
92
|
+
end
|
93
|
+
|
94
|
+
alias_method :eql?, :==
|
95
|
+
end
|
96
|
+
end
|