rdf 0.3.3 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README +107 -20
- data/VERSION +1 -1
- data/bin/rdf +4 -8
- data/etc/doap.nt +8 -8
- data/lib/rdf.rb +1 -0
- data/lib/rdf/cli.rb +146 -19
- data/lib/rdf/format.rb +59 -10
- data/lib/rdf/mixin/type_check.rb +21 -0
- data/lib/rdf/model/graph.rb +1 -0
- data/lib/rdf/model/list.rb +36 -3
- data/lib/rdf/model/literal.rb +113 -74
- data/lib/rdf/model/literal/boolean.rb +15 -5
- data/lib/rdf/model/literal/date.rb +24 -6
- data/lib/rdf/model/literal/datetime.rb +21 -4
- data/lib/rdf/model/literal/decimal.rb +3 -128
- data/lib/rdf/model/literal/double.rb +4 -107
- data/lib/rdf/model/literal/integer.rb +3 -97
- data/lib/rdf/model/literal/numeric.rb +178 -3
- data/lib/rdf/model/literal/time.rb +23 -3
- data/lib/rdf/model/literal/token.rb +2 -2
- data/lib/rdf/model/literal/xml.rb +1 -1
- data/lib/rdf/model/node.rb +35 -5
- data/lib/rdf/model/statement.rb +7 -6
- data/lib/rdf/model/term.rb +32 -0
- data/lib/rdf/model/uri.rb +13 -7
- data/lib/rdf/model/value.rb +10 -0
- data/lib/rdf/nquads.rb +120 -3
- data/lib/rdf/ntriples/format.rb +9 -1
- data/lib/rdf/ntriples/writer.rb +1 -0
- data/lib/rdf/query.rb +121 -13
- data/lib/rdf/query/pattern.rb +28 -15
- data/lib/rdf/query/solution.rb +47 -0
- data/lib/rdf/query/solutions.rb +45 -10
- data/lib/rdf/query/variable.rb +39 -4
- data/lib/rdf/reader.rb +47 -4
- data/lib/rdf/repository.rb +8 -4
- data/lib/rdf/util/file.rb +5 -2
- data/lib/rdf/version.rb +1 -1
- data/lib/rdf/writer.rb +34 -5
- metadata +56 -88
data/lib/rdf/format.rb
CHANGED
@@ -19,7 +19,7 @@ module RDF
|
|
19
19
|
# RDF::Format.content_types #=> {"text/plain" => [RDF::NTriples::Format]}
|
20
20
|
#
|
21
21
|
# @example Obtaining serialization format file extension mappings
|
22
|
-
# RDF::Format.file_extensions #=> {:nt =>
|
22
|
+
# RDF::Format.file_extensions #=> {:nt => [RDF::NTriples::Format]}
|
23
23
|
#
|
24
24
|
# @example Defining a new RDF serialization format class
|
25
25
|
# class RDF::NTriples::Format < RDF::Format
|
@@ -77,11 +77,18 @@ module RDF
|
|
77
77
|
# @option options [String, #to_s] :file_name (nil)
|
78
78
|
# @option options [Symbol, #to_sym] :file_extension (nil)
|
79
79
|
# @option options [String, #to_s] :content_type (nil)
|
80
|
+
# Note that content_type will be taken from a URL opened using {RDF::Util::File.open_file}.
|
81
|
+
# @option options [String] :sample (nil)
|
82
|
+
# A sample of input used for performing format detection.
|
83
|
+
# If we find no formats, or we find more than one, and we have a sample, we can
|
84
|
+
# perform format detection to find a specific format to use, in which case
|
85
|
+
# we pick the first one we find
|
80
86
|
# @return [Class]
|
87
|
+
# @yieldreturn [String] another way to provide a sample, allows lazy for retrieving the sample.
|
81
88
|
#
|
82
89
|
# @return [Class]
|
83
90
|
def self.for(options = {})
|
84
|
-
case options
|
91
|
+
format = case options
|
85
92
|
when String
|
86
93
|
# Find a format based on the file name
|
87
94
|
self.for(:file_name => options)
|
@@ -94,15 +101,13 @@ module RDF
|
|
94
101
|
# @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
95
102
|
mime_type = mime_type.to_s
|
96
103
|
mime_type = mime_type.split(';').first if mime_type.include?(?;) # remove any media type parameters
|
97
|
-
content_types
|
104
|
+
content_types[mime_type]
|
98
105
|
# Find a format based on the file name:
|
99
106
|
when file_name = options[:file_name]
|
100
107
|
self.for(:file_extension => File.extname(file_name.to_s)[1..-1])
|
101
108
|
# Find a format based on the file extension:
|
102
109
|
when file_ext = options[:file_extension]
|
103
|
-
|
104
|
-
self.for(:content_type => file_extensions[file_ext])
|
105
|
-
end
|
110
|
+
file_extensions[file_ext.to_sym]
|
106
111
|
end
|
107
112
|
|
108
113
|
when Symbol
|
@@ -112,15 +117,31 @@ module RDF
|
|
112
117
|
RDF::NTriples::Format
|
113
118
|
# For anything else, find a match based on the full class name
|
114
119
|
else
|
115
|
-
format = format.to_s.downcase
|
116
120
|
@@subclasses.each do |klass|
|
117
|
-
if klass.
|
121
|
+
if klass.to_sym == format ||
|
122
|
+
klass.name.to_s.split('::').map(&:downcase).include?(format.to_s.downcase)
|
118
123
|
return klass
|
119
124
|
end
|
120
125
|
end
|
121
126
|
nil # not found
|
122
127
|
end
|
123
128
|
end
|
129
|
+
|
130
|
+
if format.is_a?(Array)
|
131
|
+
return format.first if format.uniq.length == 1
|
132
|
+
elsif !format.nil?
|
133
|
+
return format
|
134
|
+
end
|
135
|
+
|
136
|
+
# If we have a sample, use that for format detection
|
137
|
+
if sample = (options[:sample] if options.is_a?(Hash)) || (yield if block_given?)
|
138
|
+
# Given a sample, perform format detection across the appropriate formats, choosing
|
139
|
+
# the first that matches
|
140
|
+
format ||= @@subclasses
|
141
|
+
|
142
|
+
# Return first format that has a positive detection
|
143
|
+
format.detect {|f| f.detect(sample)}
|
144
|
+
end
|
124
145
|
end
|
125
146
|
|
126
147
|
##
|
@@ -134,11 +155,21 @@ module RDF
|
|
134
155
|
##
|
135
156
|
# Returns file extensions for known RDF serialization formats.
|
136
157
|
#
|
137
|
-
# @return [Hash{Symbol =>
|
158
|
+
# @return [Hash{Symbol => Array<Class>}]
|
138
159
|
def self.file_extensions
|
139
160
|
@@file_extensions
|
140
161
|
end
|
141
162
|
|
163
|
+
##
|
164
|
+
# Returns a symbol appropriate to use with RDF::Format.for()
|
165
|
+
# @return [Symbol]
|
166
|
+
def self.to_sym
|
167
|
+
elements = self.to_s.split("::")
|
168
|
+
sym = elements.pop
|
169
|
+
sym = elements.pop if sym == 'Format'
|
170
|
+
sym.downcase.to_s.to_sym
|
171
|
+
end
|
172
|
+
|
142
173
|
##
|
143
174
|
# Retrieves or defines the reader class for this RDF serialization
|
144
175
|
# format.
|
@@ -225,6 +256,24 @@ module RDF
|
|
225
256
|
end
|
226
257
|
end
|
227
258
|
|
259
|
+
|
260
|
+
##
|
261
|
+
# Use a text sample to detect the format of an input file. Sub-classes implement
|
262
|
+
# a matcher sufficient to detect probably format matches, including disambiguating
|
263
|
+
# between other similar formats.
|
264
|
+
#
|
265
|
+
# Used to determine format class from loaded formats by {RDF::Format.for} when a
|
266
|
+
# match cannot be unambigiously found otherwise.
|
267
|
+
#
|
268
|
+
# @example
|
269
|
+
# RDF::NTriples::Format.detect("<a> <b> <c> .") => true
|
270
|
+
#
|
271
|
+
# @param [String] sample Beginning several bytes (~ 1K) of input.
|
272
|
+
# @return [Boolean]
|
273
|
+
def self.detect(sample)
|
274
|
+
false
|
275
|
+
end
|
276
|
+
|
228
277
|
class << self
|
229
278
|
alias_method :reader_class, :reader
|
230
279
|
alias_method :writer_class, :writer
|
@@ -267,7 +316,7 @@ module RDF
|
|
267
316
|
|
268
317
|
if extensions = (options[:extension] || options[:extensions])
|
269
318
|
extensions = [extensions].flatten.map(&:to_sym)
|
270
|
-
extensions.each { |ext| @@file_extensions[ext]
|
319
|
+
extensions.each { |ext| (@@file_extensions[ext] ||= []) << self }
|
271
320
|
end
|
272
321
|
if aliases = (options[:alias] || options[:aliases])
|
273
322
|
aliases = [aliases].flatten.each { |a| (@@content_types[a] ||= []) << self }
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RDF
|
2
|
+
##
|
3
|
+
# An RDF type check mixin.
|
4
|
+
#
|
5
|
+
# This module implements #raise_error, which will raise RDF::TypeError.
|
6
|
+
#
|
7
|
+
# @see RDF::Value
|
8
|
+
# @see RDF::Literal
|
9
|
+
# @see RDF::Literal
|
10
|
+
module TypeCheck
|
11
|
+
##
|
12
|
+
# Default implementation of type_error, which returns false.
|
13
|
+
# Classes including RDF::TypeCheck will raise TypeError
|
14
|
+
# instead.
|
15
|
+
#
|
16
|
+
# @raise [TypeError]
|
17
|
+
def type_error(message)
|
18
|
+
raise TypeError, message
|
19
|
+
end
|
20
|
+
end # TypeCheck
|
21
|
+
end # RDF
|
data/lib/rdf/model/graph.rb
CHANGED
data/lib/rdf/model/list.rb
CHANGED
@@ -56,17 +56,50 @@ module RDF
|
|
56
56
|
# The canonical empty list.
|
57
57
|
NIL = RDF::List.new(RDF.nil).freeze
|
58
58
|
|
59
|
+
##
|
60
|
+
# Validate the list ensuring that
|
61
|
+
# * rdf:rest values are all BNodes are nil
|
62
|
+
# * rdf:type, if it exists, is rdf:List
|
63
|
+
# * each subject has no properties other than single-valued rdf:first, rdf:rest
|
64
|
+
# other than for the first node in the list
|
65
|
+
# @return [Boolean]
|
66
|
+
def valid?
|
67
|
+
li = subject
|
68
|
+
while li != RDF.nil do
|
69
|
+
rest = nil
|
70
|
+
firsts = rests = 0
|
71
|
+
@graph.query(:subject => li) do |st|
|
72
|
+
case st.predicate
|
73
|
+
when RDF.type
|
74
|
+
# Be tollerant about rdf:type entries, as some OWL vocabularies use it excessively
|
75
|
+
when RDF.first
|
76
|
+
firsts += 1
|
77
|
+
when RDF.rest
|
78
|
+
rest = st.object
|
79
|
+
return false unless rest.node? || rest == RDF.nil
|
80
|
+
rests += 1
|
81
|
+
else
|
82
|
+
# First node may have other properties
|
83
|
+
return false unless li == subject
|
84
|
+
end
|
85
|
+
end
|
86
|
+
return false unless firsts == 1 && rests == 1
|
87
|
+
li = rest
|
88
|
+
end
|
89
|
+
true
|
90
|
+
end
|
91
|
+
|
59
92
|
##
|
60
93
|
# Returns the subject term of this list.
|
61
94
|
#
|
62
|
-
# @
|
95
|
+
# @attr_reader [RDF::Resource]
|
63
96
|
attr_reader :subject
|
64
97
|
|
65
98
|
##
|
66
99
|
# Returns the underlying graph storing the statements that constitute
|
67
100
|
# this list.
|
68
101
|
#
|
69
|
-
# @
|
102
|
+
# @attr_reader [RDF::Graph]
|
70
103
|
attr_reader :graph
|
71
104
|
|
72
105
|
##
|
@@ -489,7 +522,7 @@ module RDF
|
|
489
522
|
##
|
490
523
|
# Returns the first subject term constituting this list.
|
491
524
|
#
|
492
|
-
# This is equivalent to
|
525
|
+
# This is equivalent to `subject`.
|
493
526
|
#
|
494
527
|
# @example
|
495
528
|
# RDF::List[1, 2, 3].first_subject #=> RDF::Node(...)
|
data/lib/rdf/model/literal.rb
CHANGED
@@ -2,9 +2,30 @@ module RDF
|
|
2
2
|
##
|
3
3
|
# An RDF literal.
|
4
4
|
#
|
5
|
+
# Subclasses of {RDF::Literal} should define DATATYPE and GRAMMAR constants, which are used
|
6
|
+
# for identifying the appropriate class to use for a datatype URI and to perform lexical
|
7
|
+
# matching on the value.
|
8
|
+
#
|
9
|
+
# Literal comparison with other {RDF::Value} instances call {RDF::Value#type_error},
|
10
|
+
# which, returns false. Implementations wishing to have {RDF::TypeError} raised
|
11
|
+
# should mix-in {RDF::TypeCheck}. This is required for strict SPARQL conformance.
|
12
|
+
#
|
13
|
+
# Specific typed literals may have behavior different from the default implementation. See
|
14
|
+
# the following defined sub-classes for specific documentation. Additional sub-classes may
|
15
|
+
# be defined, and will interoperate by defining `DATATYPE` and `GRAMMAR` constants, in addition
|
16
|
+
# other required overrides of RDF::Literal behavior.
|
17
|
+
#
|
18
|
+
# * {RDF::Literal::Boolean}
|
19
|
+
# * {RDF::Literal::Date}
|
20
|
+
# * {RDF::Literal::DateTime}
|
21
|
+
# * {RDF::Literal::Decimal}
|
22
|
+
# * {RDF::Literal::Double}
|
23
|
+
# * {RDF::Literal::Integer}
|
24
|
+
# * {RDF::Literal::Time}
|
25
|
+
#
|
5
26
|
# @example Creating a plain literal
|
6
27
|
# value = RDF::Literal.new("Hello, world!")
|
7
|
-
# value.plain? #=> true
|
28
|
+
# value.plain? #=> true`
|
8
29
|
#
|
9
30
|
# @example Creating a language-tagged literal (1)
|
10
31
|
# value = RDF::Literal.new("Hello!", :language => :en)
|
@@ -39,53 +60,48 @@ module RDF
|
|
39
60
|
# @see http://www.w3.org/TR/rdf-concepts/#section-Literals
|
40
61
|
# @see http://www.w3.org/TR/rdf-concepts/#section-Datatypes-intro
|
41
62
|
class Literal
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
63
|
+
|
64
|
+
private
|
65
|
+
@@subclasses = [] # @private
|
66
|
+
|
67
|
+
##
|
68
|
+
# @private
|
69
|
+
# @return [void]
|
70
|
+
def self.inherited(child)
|
71
|
+
@@subclasses << child
|
72
|
+
super
|
73
|
+
end
|
74
|
+
|
75
|
+
public
|
76
|
+
|
77
|
+
require 'rdf/model/literal/numeric'
|
78
|
+
require 'rdf/model/literal/boolean'
|
79
|
+
require 'rdf/model/literal/decimal'
|
80
|
+
require 'rdf/model/literal/integer'
|
81
|
+
require 'rdf/model/literal/double'
|
82
|
+
require 'rdf/model/literal/date'
|
83
|
+
require 'rdf/model/literal/dateTime'
|
84
|
+
require 'rdf/model/literal/time'
|
85
|
+
require 'rdf/model/literal/token'
|
86
|
+
require 'rdf/model/literal/xml'
|
52
87
|
|
53
88
|
include RDF::Term
|
54
89
|
|
90
|
+
##
|
91
|
+
# @private
|
92
|
+
# Return datatype class for uri, or nil if none is found
|
93
|
+
def self.datatyped_class(uri)
|
94
|
+
@@subclasses.detect {|klass| klass.const_defined?(:DATATYPE) && klass.const_get(:DATATYPE) == uri}
|
95
|
+
end
|
96
|
+
|
55
97
|
##
|
56
98
|
# @private
|
57
99
|
def self.new(value, options = {})
|
58
100
|
klass = case
|
59
101
|
when !self.equal?(RDF::Literal)
|
60
102
|
self # subclasses can be directly constructed without type dispatch
|
61
|
-
when
|
62
|
-
|
63
|
-
when XSD.boolean
|
64
|
-
RDF::Literal::Boolean
|
65
|
-
when XSD.integer, XSD.long, XSD.int, XSD.short, XSD.byte
|
66
|
-
RDF::Literal::Integer
|
67
|
-
when XSD.double, XSD.float
|
68
|
-
RDF::Literal::Double
|
69
|
-
when XSD.decimal
|
70
|
-
RDF::Literal::Decimal
|
71
|
-
when XSD.date
|
72
|
-
RDF::Literal::Date
|
73
|
-
when XSD.dateTime
|
74
|
-
RDF::Literal::DateTime
|
75
|
-
when XSD.time
|
76
|
-
RDF::Literal::Time
|
77
|
-
when XSD.nonPositiveInteger, XSD.negativeInteger
|
78
|
-
RDF::Literal::Integer
|
79
|
-
when XSD.nonNegativeInteger, XSD.positiveInteger
|
80
|
-
RDF::Literal::Integer
|
81
|
-
when XSD.unsignedLong, XSD.unsignedInt, XSD.unsignedShort, XSD.unsignedByte
|
82
|
-
RDF::Literal::Integer
|
83
|
-
when XSD.token, XSD.language
|
84
|
-
RDF::Literal::Token
|
85
|
-
when RDF.XMLLiteral
|
86
|
-
RDF::Literal::XML
|
87
|
-
else self
|
88
|
-
end
|
103
|
+
when typed_literal = datatyped_class(RDF::URI(options[:datatype]))
|
104
|
+
typed_literal
|
89
105
|
else case value
|
90
106
|
when ::TrueClass then RDF::Literal::Boolean
|
91
107
|
when ::FalseClass then RDF::Literal::Boolean
|
@@ -123,8 +139,10 @@ module RDF
|
|
123
139
|
def initialize(value, options = {})
|
124
140
|
@object = value
|
125
141
|
@string = options[:lexical] if options[:lexical]
|
142
|
+
@string = value if !defined?(@string) && value.is_a?(String)
|
126
143
|
@language = options[:language].to_s.to_sym if options[:language]
|
127
144
|
@datatype = RDF::URI(options[:datatype]) if options[:datatype]
|
145
|
+
@datatype ||= self.class.const_get(:DATATYPE) if self.class.const_defined?(:DATATYPE)
|
128
146
|
end
|
129
147
|
|
130
148
|
##
|
@@ -138,30 +156,7 @@ module RDF
|
|
138
156
|
##
|
139
157
|
# @return [Object]
|
140
158
|
def object
|
141
|
-
@object
|
142
|
-
when XSD.string, nil
|
143
|
-
value
|
144
|
-
when XSD.boolean
|
145
|
-
%w(true 1).include?(value)
|
146
|
-
when XSD.integer, XSD.long, XSD.int, XSD.short, XSD.byte
|
147
|
-
value.to_i
|
148
|
-
when XSD.double, XSD.float
|
149
|
-
value.to_f
|
150
|
-
when XSD.decimal
|
151
|
-
::BigDecimal.new(value)
|
152
|
-
when XSD.date
|
153
|
-
::Date.parse(value)
|
154
|
-
when XSD.dateTime
|
155
|
-
::DateTime.parse(value)
|
156
|
-
when XSD.time
|
157
|
-
::Time.parse(value)
|
158
|
-
when XSD.nonPositiveInteger, XSD.negativeInteger
|
159
|
-
value.to_i
|
160
|
-
when XSD.nonNegativeInteger, XSD.positiveInteger
|
161
|
-
value.to_i
|
162
|
-
when XSD.unsignedLong, XSD.unsignedInt, XSD.unsignedShort, XSD.unsignedByte
|
163
|
-
value.to_i
|
164
|
-
end
|
159
|
+
defined?(@object) ? @object : value
|
165
160
|
end
|
166
161
|
|
167
162
|
##
|
@@ -189,7 +184,7 @@ module RDF
|
|
189
184
|
end
|
190
185
|
|
191
186
|
##
|
192
|
-
#
|
187
|
+
# Determins if `self` is the same term as `other`.
|
193
188
|
#
|
194
189
|
# @example
|
195
190
|
# RDF::Literal(1).eql?(RDF::Literal(1.0)) #=> false
|
@@ -199,27 +194,43 @@ module RDF
|
|
199
194
|
def eql?(other)
|
200
195
|
self.equal?(other) ||
|
201
196
|
(self.class.eql?(other.class) &&
|
202
|
-
self.
|
203
|
-
self
|
197
|
+
self.value.eql?(other.value) &&
|
198
|
+
self.language.to_s.downcase.eql?(other.language.to_s.downcase) &&
|
199
|
+
self.datatype.eql?(other.datatype))
|
204
200
|
end
|
205
201
|
|
206
202
|
##
|
207
|
-
# Returns `true` if this literal is equivalent to `other
|
203
|
+
# Returns `true` if this literal is equivalent to `other` (with type check).
|
208
204
|
#
|
209
205
|
# @example
|
210
206
|
# RDF::Literal(1) == RDF::Literal(1.0) #=> true
|
211
207
|
#
|
212
208
|
# @param [Object] other
|
213
209
|
# @return [Boolean] `true` or `false`
|
210
|
+
#
|
211
|
+
# @see http://www.w3.org/TR/rdf-sparql-query/#func-RDFterm-equal
|
212
|
+
# @see http://www.w3.org/TR/rdf-concepts/#section-Literal-Equality
|
214
213
|
def ==(other)
|
215
214
|
case other
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
when
|
221
|
-
|
222
|
-
|
215
|
+
when Literal
|
216
|
+
case
|
217
|
+
when self.eql?(other)
|
218
|
+
true
|
219
|
+
when self.has_language? && self.language.to_s.downcase == other.language.to_s.downcase
|
220
|
+
# Literals with languages can compare if languages are identical
|
221
|
+
self.value == other.value
|
222
|
+
when (self.simple? || self.datatype == XSD.string) && (other.simple? || other.datatype == XSD.string)
|
223
|
+
self.value == other.value
|
224
|
+
when other.comperable_datatype?(self) || self.comperable_datatype?(other)
|
225
|
+
# Comoparing plain with undefined datatypes does not generate an error, but returns false
|
226
|
+
# From data-r2/expr-equal/eq-2-2.
|
227
|
+
false
|
228
|
+
else
|
229
|
+
type_error("unable to determine whether #{self.inspect} and #{other.inspect} are equivalent")
|
230
|
+
end
|
231
|
+
when String
|
232
|
+
self.plain? && self.value.eql?(other)
|
233
|
+
else false
|
223
234
|
end
|
224
235
|
end
|
225
236
|
alias_method :===, :==
|
@@ -277,6 +288,34 @@ module RDF
|
|
277
288
|
!valid?
|
278
289
|
end
|
279
290
|
|
291
|
+
##
|
292
|
+
# Returns `true` if the literal has a datatype and the comparison should
|
293
|
+
# return false instead of raise a type error.
|
294
|
+
#
|
295
|
+
# This behavior is intuited from SPARQL data-r2/expr-equal/eq-2-2
|
296
|
+
# @return [Boolean]
|
297
|
+
def comperable_datatype?(other)
|
298
|
+
return false unless self.plain? || self.has_language?
|
299
|
+
|
300
|
+
case other
|
301
|
+
when RDF::Literal::Numeric, RDF::Literal::Boolean,
|
302
|
+
RDF::Literal::Date, RDF::Literal::Time, RDF::Literal::DateTime
|
303
|
+
# Invald types can be compared without raising a TypeError if literal has a language (open-eq-08)
|
304
|
+
!other.valid? && self.has_language?
|
305
|
+
else
|
306
|
+
case other.datatype
|
307
|
+
when XSD.string
|
308
|
+
true
|
309
|
+
when nil
|
310
|
+
# A different language will not generate a type error
|
311
|
+
other.has_language?
|
312
|
+
else
|
313
|
+
# An unknown datatype may not be used for comparison, unless it has a language? (open-eq-8)
|
314
|
+
self.has_language?
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
280
319
|
##
|
281
320
|
# Validates the value using {#valid?}, raising an error if the value is
|
282
321
|
# invalid.
|