wordnik 4.07 → 4.08
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile.lock +14 -10
- data/README.md +24 -18
- data/lib/wordnik.rb +36 -22
- data/lib/wordnik/configuration.rb +20 -8
- data/lib/wordnik/load_balancer.rb +71 -0
- data/lib/wordnik/request.rb +53 -32
- data/lib/wordnik/version.rb +1 -3
- data/spec/100words.txt +100 -0
- data/spec/load_balancer_spec.rb +28 -0
- data/spec/performance.rb +140 -0
- data/spec/request_spec.rb +14 -14
- data/spec/resource_spec.rb +24 -24
- data/spec/response_spec.rb +24 -24
- data/spec/spec_helper.rb +21 -17
- data/spec/wordnik_spec.rb +73 -41
- data/wordnik.gemspec +3 -1
- metadata +125 -25
data/lib/wordnik/version.rb
CHANGED
data/spec/100words.txt
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
sustain
|
|
2
|
+
almandite
|
|
3
|
+
source
|
|
4
|
+
dessert
|
|
5
|
+
scorching
|
|
6
|
+
illuminant
|
|
7
|
+
impeccancy
|
|
8
|
+
impetrated
|
|
9
|
+
acetaminophen
|
|
10
|
+
phreatophyte
|
|
11
|
+
churlish
|
|
12
|
+
pitchblende
|
|
13
|
+
delicious
|
|
14
|
+
clinquant
|
|
15
|
+
balmacaan
|
|
16
|
+
mordant
|
|
17
|
+
exhume
|
|
18
|
+
scribbling
|
|
19
|
+
kookaburra
|
|
20
|
+
cogwheel
|
|
21
|
+
pallesthesia
|
|
22
|
+
expertise
|
|
23
|
+
attitudinal
|
|
24
|
+
omission
|
|
25
|
+
paralipomena
|
|
26
|
+
hooroosh
|
|
27
|
+
caret
|
|
28
|
+
gristliness
|
|
29
|
+
jaded
|
|
30
|
+
provolone
|
|
31
|
+
quizzical
|
|
32
|
+
grotto
|
|
33
|
+
flammeous
|
|
34
|
+
courageous
|
|
35
|
+
phoneme
|
|
36
|
+
chough
|
|
37
|
+
ooze
|
|
38
|
+
soundproof
|
|
39
|
+
cynodont
|
|
40
|
+
conjunction
|
|
41
|
+
mamba
|
|
42
|
+
garlicky
|
|
43
|
+
pageantry
|
|
44
|
+
gnosis
|
|
45
|
+
heifer
|
|
46
|
+
binturong
|
|
47
|
+
emissaries
|
|
48
|
+
ombrophobous
|
|
49
|
+
forbiddance
|
|
50
|
+
junior
|
|
51
|
+
sporophore
|
|
52
|
+
namaycush
|
|
53
|
+
dwindling
|
|
54
|
+
covinous
|
|
55
|
+
parent
|
|
56
|
+
jackroller
|
|
57
|
+
saccharine
|
|
58
|
+
gabelle
|
|
59
|
+
exequy
|
|
60
|
+
demurred
|
|
61
|
+
effervescently
|
|
62
|
+
ickiness
|
|
63
|
+
criophore
|
|
64
|
+
deity
|
|
65
|
+
aliquant
|
|
66
|
+
threshold
|
|
67
|
+
chessman
|
|
68
|
+
isallotherm
|
|
69
|
+
abominable
|
|
70
|
+
grease
|
|
71
|
+
smuggleable
|
|
72
|
+
heraldry
|
|
73
|
+
bleakness
|
|
74
|
+
rhododendron
|
|
75
|
+
paradoxical
|
|
76
|
+
ronquil
|
|
77
|
+
graminaceous
|
|
78
|
+
submerge
|
|
79
|
+
rivulet
|
|
80
|
+
asthma
|
|
81
|
+
anemochore
|
|
82
|
+
animadversion
|
|
83
|
+
glabrescent
|
|
84
|
+
argot
|
|
85
|
+
insincere
|
|
86
|
+
sponson
|
|
87
|
+
biennial
|
|
88
|
+
keenness
|
|
89
|
+
stade
|
|
90
|
+
hesitancy
|
|
91
|
+
charades
|
|
92
|
+
brilliantly
|
|
93
|
+
dillydally
|
|
94
|
+
fibrinogen
|
|
95
|
+
apyretic
|
|
96
|
+
japonaiserie
|
|
97
|
+
oxlip
|
|
98
|
+
amass
|
|
99
|
+
satiny
|
|
100
|
+
unpronounceable
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Wordnik::LoadBalancer do
|
|
4
|
+
before(:each) do
|
|
5
|
+
@hosts = ["alpha","beta","gamma","delta"].map{|x| "#{x}.wordnik.com"}
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
describe "Load Balancer" do
|
|
9
|
+
it "allows creation with a list of hosts" do
|
|
10
|
+
lb = Wordnik::LoadBalancer.new(@hosts)
|
|
11
|
+
h = lb.host
|
|
12
|
+
@hosts.should include(h)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "returns different (random) hosts" do
|
|
16
|
+
lb = Wordnik::LoadBalancer.new(@hosts)
|
|
17
|
+
counts = Hash.new(0)
|
|
18
|
+
1.upto(1000) do
|
|
19
|
+
h = lb.host
|
|
20
|
+
counts[h] += 1
|
|
21
|
+
end
|
|
22
|
+
@hosts.each {|host| counts[host].should > 10}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "should not leak memory"
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
end
|
data/spec/performance.rb
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'ruby-prof'
|
|
3
|
+
|
|
4
|
+
describe "Load Balancer Performance" do
|
|
5
|
+
|
|
6
|
+
describe "With single host -- host performance" do
|
|
7
|
+
Wordnik.configuration.clear
|
|
8
|
+
Wordnik.configure do |config|
|
|
9
|
+
config.api_key = CREDENTIALS[:api_key]
|
|
10
|
+
config.username = CREDENTIALS[:username]
|
|
11
|
+
config.password = CREDENTIALS[:password]
|
|
12
|
+
config.logger = Logger.new('/dev/null')
|
|
13
|
+
config.host = CREDENTIALS[:host] = 'api.wordnik.com'
|
|
14
|
+
config.hosts = []
|
|
15
|
+
config.base_path = CREDENTIALS[:base_path] || '/v4'
|
|
16
|
+
end
|
|
17
|
+
start_time = Time.now
|
|
18
|
+
RubyProf.start
|
|
19
|
+
100000.times {|i| host = Wordnik.configuration.host}
|
|
20
|
+
result = RubyProf.stop
|
|
21
|
+
printer = RubyProf::FlatPrinter.new(result)
|
|
22
|
+
printer.print(STDOUT)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe "With multiple hosts -- host performance" do
|
|
26
|
+
Wordnik.configuration.clear
|
|
27
|
+
Wordnik.configure do |config|
|
|
28
|
+
config.api_key = CREDENTIALS[:api_key]
|
|
29
|
+
config.username = CREDENTIALS[:username]
|
|
30
|
+
config.password = CREDENTIALS[:password]
|
|
31
|
+
config.logger = Logger.new('/dev/null')
|
|
32
|
+
config.host = CREDENTIALS[:host] = 'api.wordnik.com'
|
|
33
|
+
config.hosts = CREDENTIALS[:hosts] || ['ec2-50-18-25-12.us-west-1.compute.amazonaws.com',' ec2-50-18-67-92.us-west-1.compute.amazonaws.com']
|
|
34
|
+
config.base_path = CREDENTIALS[:base_path] || '/v4'
|
|
35
|
+
end
|
|
36
|
+
start_time = Time.now
|
|
37
|
+
RubyProf.start
|
|
38
|
+
100000.times {|i| host = Wordnik.configuration.host}
|
|
39
|
+
result = RubyProf.stop
|
|
40
|
+
printer = RubyProf::FlatPrinter.new(result)
|
|
41
|
+
printer.print(STDOUT)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "Wordnik GET performance" do
|
|
47
|
+
|
|
48
|
+
describe "With single host" do
|
|
49
|
+
RubyProf.start
|
|
50
|
+
Wordnik.configuration.clear
|
|
51
|
+
Wordnik.configure do |config|
|
|
52
|
+
config.api_key = CREDENTIALS[:api_key]
|
|
53
|
+
config.username = CREDENTIALS[:username]
|
|
54
|
+
config.password = CREDENTIALS[:password]
|
|
55
|
+
config.logger = Logger.new('/dev/null')
|
|
56
|
+
config.host = CREDENTIALS[:host] = 'api.wordnik.com'
|
|
57
|
+
config.hosts = []
|
|
58
|
+
config.base_path = CREDENTIALS[:base_path] || '/v4'
|
|
59
|
+
end
|
|
60
|
+
words = File.open(File.dirname(__FILE__) + "/100words.txt").readlines.map{|w| w.strip}
|
|
61
|
+
start_time = Time.now
|
|
62
|
+
Wordnik.logger.debug "Starting at #{start_time}"
|
|
63
|
+
found = 0
|
|
64
|
+
defs = 0
|
|
65
|
+
examples = 0
|
|
66
|
+
words.each do |word|
|
|
67
|
+
found += 1 if Wordnik.word.get_word(word)["id"]==0
|
|
68
|
+
defs += Wordnik.word.get_definitions(word).size
|
|
69
|
+
examples += Wordnik.word.get_examples(word).size
|
|
70
|
+
end
|
|
71
|
+
total = Time.now - start_time
|
|
72
|
+
result = RubyProf.stop
|
|
73
|
+
STDOUT.puts "Found #{found} words out of #{words.size}; #{defs} definitions; #{examples} examples in #{total} seconds"
|
|
74
|
+
printer = RubyProf::FlatPrinter.new(result)
|
|
75
|
+
printer.print(STDOUT)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe "With two hosts" do
|
|
79
|
+
RubyProf.start
|
|
80
|
+
Wordnik.configuration.clear
|
|
81
|
+
Wordnik.configure do |config|
|
|
82
|
+
config.api_key = CREDENTIALS[:api_key]
|
|
83
|
+
config.username = CREDENTIALS[:username]
|
|
84
|
+
config.password = CREDENTIALS[:password]
|
|
85
|
+
config.logger = Logger.new('/dev/null')
|
|
86
|
+
config.host = CREDENTIALS[:host] = 'api.wordnik.com'
|
|
87
|
+
config.hosts = CREDENTIALS[:hosts] || ['ec2-50-18-25-12.us-west-1.compute.amazonaws.com',' ec2-50-18-67-92.us-west-1.compute.amazonaws.com']
|
|
88
|
+
config.base_path = CREDENTIALS[:base_path] || '/v4'
|
|
89
|
+
end
|
|
90
|
+
words = File.open(File.dirname(__FILE__) + "/100words.txt").readlines.map{|w| w.strip}
|
|
91
|
+
start_time = Time.now
|
|
92
|
+
Wordnik.logger.debug "Starting at #{start_time}"
|
|
93
|
+
found = 0
|
|
94
|
+
defs = 0
|
|
95
|
+
examples = 0
|
|
96
|
+
words.each do |word|
|
|
97
|
+
found += 1 if Wordnik.word.get_word(word)["id"]==0
|
|
98
|
+
defs += Wordnik.word.get_definitions(word).size
|
|
99
|
+
examples += Wordnik.word.get_examples(word).size
|
|
100
|
+
end
|
|
101
|
+
total = Time.now - start_time
|
|
102
|
+
result = RubyProf.stop
|
|
103
|
+
STDOUT.puts "Found #{found} words out of #{words.size}; #{defs} definitions; #{examples} examples in #{total} seconds"
|
|
104
|
+
printer = RubyProf::FlatPrinter.new(result)
|
|
105
|
+
printer.print(STDOUT)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
describe "With three hosts -- one invalid" do
|
|
109
|
+
RubyProf.start
|
|
110
|
+
Wordnik.configuration.clear
|
|
111
|
+
Wordnik.configure do |config|
|
|
112
|
+
config.api_key = CREDENTIALS[:api_key]
|
|
113
|
+
config.username = CREDENTIALS[:username]
|
|
114
|
+
config.password = CREDENTIALS[:password]
|
|
115
|
+
config.logger = Logger.new('/dev/null')
|
|
116
|
+
config.host = CREDENTIALS[:host] = 'api.wordnik.com'
|
|
117
|
+
config.hosts = CREDENTIALS[:hosts] || ['ec2-50-18-25-12.us-west-1.compute.amazonaws.com',
|
|
118
|
+
'ec2-50-18-67-92.us-west-1.compute.amazonaws.com',
|
|
119
|
+
'localhost']
|
|
120
|
+
config.base_path = CREDENTIALS[:base_path] || '/v4'
|
|
121
|
+
end
|
|
122
|
+
words = File.open(File.dirname(__FILE__) + "/100words.txt").readlines.map{|w| w.strip}
|
|
123
|
+
start_time = Time.now
|
|
124
|
+
Wordnik.logger.debug "Starting at #{start_time}"
|
|
125
|
+
found = 0
|
|
126
|
+
defs = 0
|
|
127
|
+
examples = 0
|
|
128
|
+
words.each do |word|
|
|
129
|
+
found += 1 if Wordnik.word.get_word(word)["id"]==0
|
|
130
|
+
defs += Wordnik.word.get_definitions(word).size
|
|
131
|
+
examples += Wordnik.word.get_examples(word).size
|
|
132
|
+
end
|
|
133
|
+
total = Time.now - start_time
|
|
134
|
+
result = RubyProf.stop
|
|
135
|
+
STDOUT.puts "Found #{found} words out of #{words.size}; #{defs} definitions; #{examples} examples in #{total} seconds"
|
|
136
|
+
printer = RubyProf::FlatPrinter.new(result)
|
|
137
|
+
printer.print(STDOUT)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
data/spec/request_spec.rb
CHANGED
|
@@ -15,7 +15,7 @@ describe Wordnik::Request do
|
|
|
15
15
|
it "sets default response format to json" do
|
|
16
16
|
@request.format.should == 'json'
|
|
17
17
|
end
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
it "allows params to be nil" do
|
|
20
20
|
@request = Wordnik::Request.new(@default_http_method, @default_path, :params => nil)
|
|
21
21
|
@request.query_string.should == ""
|
|
@@ -44,11 +44,11 @@ describe Wordnik::Request do
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
it "constructs a full url" do
|
|
47
|
-
@request.url.
|
|
47
|
+
@request.url.has_suffix?("#{Wordnik.configuration.base_path}/words.json/fancy?bar=2&foo=1").should == true
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
end
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
describe "body" do
|
|
53
53
|
|
|
54
54
|
it "camelCases parameters" do
|
|
@@ -60,7 +60,7 @@ describe Wordnik::Request do
|
|
|
60
60
|
}))
|
|
61
61
|
@request.body.keys.should == [:badDog, :goodDog]
|
|
62
62
|
end
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
describe "path" do
|
|
@@ -72,7 +72,7 @@ describe Wordnik::Request do
|
|
|
72
72
|
:word => "cat"
|
|
73
73
|
}
|
|
74
74
|
}))
|
|
75
|
-
@request.url.
|
|
75
|
+
@request.url.has_suffix?("#{Wordnik.configuration.base_path}/word.xml/cat/entries").should == true
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
it "does string substitution on path params" do
|
|
@@ -82,7 +82,7 @@ describe Wordnik::Request do
|
|
|
82
82
|
:word => "cat"
|
|
83
83
|
}
|
|
84
84
|
}))
|
|
85
|
-
@request.url.
|
|
85
|
+
@request.url.has_suffix?("#{Wordnik.configuration.base_path}/word.xml/cat/entries").should == true
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
it "leaves path-bound params out of the query string" do
|
|
@@ -135,7 +135,7 @@ describe Wordnik::Request do
|
|
|
135
135
|
@request.query_string.should =~ /\?limit=100/
|
|
136
136
|
@request.url.should =~ /\?limit=100/
|
|
137
137
|
end
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
it "camelCases parameters" do
|
|
140
140
|
@request = Wordnik::Request.new(@default_http_method, @default_path, @default_params.merge({
|
|
141
141
|
:params => {
|
|
@@ -145,22 +145,22 @@ describe Wordnik::Request do
|
|
|
145
145
|
}))
|
|
146
146
|
@request.query_string.should == "?badDog=bud&goodDog=dud"
|
|
147
147
|
end
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
it "converts boolean values to their string representation" do
|
|
150
150
|
params = {:stringy => "fish", :truthy => true, :falsey => false}
|
|
151
151
|
@request = Wordnik::Request.new(:get, 'fakeMethod', :params => params)
|
|
152
152
|
@request.query_string.should == "?falsey=false&stringy=fish&truthy=true"
|
|
153
153
|
end
|
|
154
|
-
|
|
154
|
+
|
|
155
155
|
end
|
|
156
|
-
|
|
156
|
+
|
|
157
157
|
describe "API key" do
|
|
158
|
-
|
|
158
|
+
|
|
159
159
|
it "is inferred from the Wordnik base configuration by default" do
|
|
160
160
|
Wordnik.configure {|c| c.api_key = "xyz" }
|
|
161
161
|
Wordnik::Request.new(:get, "word/json").headers[:api_key].should == "xyz"
|
|
162
162
|
end
|
|
163
|
-
|
|
163
|
+
|
|
164
164
|
it "can be obfuscated for public display" do
|
|
165
165
|
@request = Wordnik::Request.new(:get, "words/fancy", @default_params.merge({
|
|
166
166
|
:params => {
|
|
@@ -178,7 +178,7 @@ describe Wordnik::Request do
|
|
|
178
178
|
@request_with_key = Wordnik::Request.new(:get, "word/json", :params => {:api_key => "jkl"})
|
|
179
179
|
@request_with_key.headers[:api_key].should be_nil
|
|
180
180
|
@request_with_key.params[:api_key].should == "jkl"
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
@request_without_key = Wordnik::Request.new(:get, "word/json", :params => {:api_key => nil})
|
|
183
183
|
@request_without_key.headers[:api_key].should be_nil
|
|
184
184
|
@request_without_key.params[:api_key].should be_nil
|
|
@@ -193,4 +193,4 @@ describe Wordnik::Request do
|
|
|
193
193
|
|
|
194
194
|
end
|
|
195
195
|
|
|
196
|
-
end
|
|
196
|
+
end
|
data/spec/resource_spec.rb
CHANGED
|
@@ -21,7 +21,7 @@ describe Wordnik::Resource do
|
|
|
21
21
|
@resource.endpoints.size.should >= 10
|
|
22
22
|
@resource.endpoints.first.class.should == Wordnik::Endpoint
|
|
23
23
|
end
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
it "defines a method for each operation nickname" do
|
|
26
26
|
@resource.public_methods.should include(:get_word)
|
|
27
27
|
@resource.public_methods.should include(:get_definitions)
|
|
@@ -30,14 +30,14 @@ describe Wordnik::Resource do
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
end
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
describe "auto-generated methods" do
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
before(:each) do
|
|
37
37
|
configure_wordnik
|
|
38
|
-
VCR.use_cassette('wordnik_authenticate', :record => :new_episodes) do
|
|
39
|
-
|
|
40
|
-
end
|
|
38
|
+
#VCR.use_cassette('wordnik_authenticate', :record => :new_episodes) do
|
|
39
|
+
# Wordnik.authenticate
|
|
40
|
+
#end
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
it "builds requests but doesn't run them if :request_only is passed" do
|
|
@@ -46,25 +46,25 @@ describe Wordnik::Resource do
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
it "runs requests and returns their body if :request_only is absent" do
|
|
49
|
-
VCR.use_cassette('get_word_dynamo', :record => :new_episodes) do
|
|
49
|
+
#VCR.use_cassette('get_word_dynamo', :record => :new_episodes) do
|
|
50
50
|
@response_body = Wordnik.word.get_word('dynamo')
|
|
51
|
-
end
|
|
51
|
+
#end
|
|
52
52
|
@response_body.class.should == Hash
|
|
53
53
|
@response_body.keys.sort.should == %w(canonicalForm id lookupCount scrabble word)
|
|
54
54
|
end
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
it "allows the same auto-generated method to be called with different parameters" do
|
|
57
57
|
request1 = Wordnik.word_list.get_word_list_by_id('dog', :request_only => true)
|
|
58
58
|
request2 = Wordnik.word_list.get_word_list_by_id('cat', :request_only => true)
|
|
59
59
|
request1.path.should_not == request2.path
|
|
60
60
|
end
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
context "argument handling" do
|
|
63
63
|
|
|
64
64
|
before(:each) do
|
|
65
65
|
@request = Wordnik.word.get_examples('dynamo', :skip => 2, :limit => 10, :request_only => true)
|
|
66
66
|
end
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
it "injects required arguments into the path" do
|
|
69
69
|
@request.path.should == "/word/dynamo/examples"
|
|
70
70
|
end
|
|
@@ -72,7 +72,7 @@ describe Wordnik::Resource do
|
|
|
72
72
|
it "passes optional key-value arguments to the query string" do
|
|
73
73
|
@request.query_string.should == "?limit=10&skip=2"
|
|
74
74
|
end
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
it "puts key-value arguments in the request body instead of the query params for POSTs and PUTs" do
|
|
77
77
|
body = {
|
|
78
78
|
:name => "Wordnik Ruby Test List #{RAND}",
|
|
@@ -87,19 +87,19 @@ describe Wordnik::Resource do
|
|
|
87
87
|
@request.body.should have_key(:type)
|
|
88
88
|
@request.body.should have_key(:userId)
|
|
89
89
|
end
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
end
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
context "response transmogrification" do
|
|
94
|
-
|
|
95
|
-
it "converts definitions response into an array of definition objects" #
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
|
|
94
|
+
|
|
95
|
+
it "converts definitions response into an array of definition objects" #do
|
|
96
|
+
#defs = Wordnik.word.get_definitions('set')
|
|
97
|
+
#defs.should be_an(Array)
|
|
98
|
+
#defs.first.should be_a(Wordnik::Definition)
|
|
99
|
+
#end
|
|
100
|
+
|
|
101
101
|
end
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
end
|
|
104
|
-
|
|
105
|
-
end
|
|
104
|
+
|
|
105
|
+
end
|