elasticsearch-model 0.1.1 → 0.1.2
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.
- checksums.yaml +15 -0
- data/CHANGELOG.md +10 -0
- data/README.md +11 -3
- data/elasticsearch-model.gemspec +1 -0
- data/lib/elasticsearch/model.rb +4 -1
- data/lib/elasticsearch/model/adapters/active_record.rb +10 -5
- data/lib/elasticsearch/model/adapters/default.rb +6 -0
- data/lib/elasticsearch/model/adapters/mongoid.rb +6 -4
- data/lib/elasticsearch/model/importing.rb +25 -4
- data/lib/elasticsearch/model/indexing.rb +2 -0
- data/lib/elasticsearch/model/response/pagination.rb +65 -0
- data/lib/elasticsearch/model/response/records.rb +2 -2
- data/lib/elasticsearch/model/response/result.rb +19 -6
- data/lib/elasticsearch/model/version.rb +1 -1
- data/test/integration/active_record_basic_test.rb +32 -0
- data/test/integration/active_record_import_test.rb +28 -3
- data/test/integration/mongoid_basic_test.rb +12 -0
- data/test/unit/adapter_active_record_test.rb +32 -3
- data/test/unit/adapter_default_test.rb +14 -4
- data/test/unit/adapter_mongoid_test.rb +15 -0
- data/test/unit/importing_test.rb +34 -1
- data/test/unit/indexing_test.rb +6 -0
- data/test/unit/{response_pagination_test.rb → response_pagination_kaminari_test.rb} +1 -1
- data/test/unit/response_pagination_will_paginate_test.rb +189 -0
- data/test/unit/response_result_test.rb +42 -4
- metadata +23 -55
@@ -95,6 +95,18 @@ if ENV["MONGODB_AVAILABLE"]
|
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
98
|
+
should "preserve the search results order for records" do
|
99
|
+
response = MongoidArticle.search('title:code')
|
100
|
+
|
101
|
+
response.records.each_with_hit do |r, h|
|
102
|
+
assert_equal h._id, r.id.to_s
|
103
|
+
end
|
104
|
+
|
105
|
+
response.records.map_with_hit do |r, h|
|
106
|
+
assert_equal h._id, r.id.to_s
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
98
110
|
should "remove document from index on destroy" do
|
99
111
|
article = MongoidArticle.first
|
100
112
|
|
@@ -82,13 +82,42 @@ class Elasticsearch::Model::AdapterActiveRecordTest < Test::Unit::TestCase
|
|
82
82
|
end
|
83
83
|
|
84
84
|
context "Importing" do
|
85
|
+
setup do
|
86
|
+
DummyClassForActiveRecord.__send__ :extend, Elasticsearch::Model::Adapter::ActiveRecord::Importing
|
87
|
+
end
|
88
|
+
|
89
|
+
should "raise an exception when passing an invalid scope" do
|
90
|
+
assert_raise NoMethodError do
|
91
|
+
DummyClassForActiveRecord.__find_in_batches(scope: :not_found_method) do; end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
85
95
|
should "implement the __find_in_batches method" do
|
86
96
|
DummyClassForActiveRecord.expects(:find_in_batches).returns([])
|
87
|
-
|
88
|
-
DummyClassForActiveRecord.__send__ :extend, Elasticsearch::Model::Adapter::ActiveRecord::Importing
|
89
97
|
DummyClassForActiveRecord.__find_in_batches do; end
|
90
98
|
end
|
91
|
-
end
|
92
99
|
|
100
|
+
should "limit the relation to a specific scope" do
|
101
|
+
DummyClassForActiveRecord.expects(:find_in_batches).returns([])
|
102
|
+
DummyClassForActiveRecord.expects(:published).returns(DummyClassForActiveRecord)
|
103
|
+
|
104
|
+
DummyClassForActiveRecord.__find_in_batches(scope: :published) do; end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when transforming models" do
|
108
|
+
setup do
|
109
|
+
@transform = DummyClassForActiveRecord.__transform
|
110
|
+
end
|
111
|
+
|
112
|
+
should "provide an object that responds to #call" do
|
113
|
+
assert_respond_to @transform, :call
|
114
|
+
end
|
115
|
+
|
116
|
+
should "provide default transformation" do
|
117
|
+
model = mock("model", id: 1, __elasticsearch__: stub(as_indexed_json: {}))
|
118
|
+
assert_equal @transform.call(model), { index: { _id: 1, data: {} } }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
93
122
|
end
|
94
123
|
end
|
@@ -19,11 +19,21 @@ class Elasticsearch::Model::AdapterDefaultTest < Test::Unit::TestCase
|
|
19
19
|
assert_instance_of Module, Elasticsearch::Model::Adapter::Default::Callbacks
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
context "concerning abstract methods" do
|
23
|
+
setup do
|
24
|
+
DummyClassForDefaultAdapter.__send__ :include, Elasticsearch::Model::Adapter::Default::Importing
|
25
|
+
end
|
26
|
+
|
27
|
+
should "have the default Importing implementation" do
|
28
|
+
assert_raise Elasticsearch::Model::NotImplemented do
|
29
|
+
DummyClassForDefaultAdapter.new.__find_in_batches
|
30
|
+
end
|
31
|
+
end
|
24
32
|
|
25
|
-
|
26
|
-
|
33
|
+
should "have the default transform implementation" do
|
34
|
+
assert_raise Elasticsearch::Model::NotImplemented do
|
35
|
+
DummyClassForDefaultAdapter.new.__transform
|
36
|
+
end
|
27
37
|
end
|
28
38
|
end
|
29
39
|
|
@@ -81,6 +81,21 @@ class Elasticsearch::Model::AdapterMongoidTest < Test::Unit::TestCase
|
|
81
81
|
DummyClassForMongoid.__send__ :extend, Elasticsearch::Model::Adapter::Mongoid::Importing
|
82
82
|
DummyClassForMongoid.__find_in_batches do; end
|
83
83
|
end
|
84
|
+
|
85
|
+
context "when transforming models" do
|
86
|
+
setup do
|
87
|
+
@transform = DummyClassForMongoid.__transform
|
88
|
+
end
|
89
|
+
|
90
|
+
should "provide an object that responds to #call" do
|
91
|
+
assert_respond_to @transform, :call
|
92
|
+
end
|
93
|
+
|
94
|
+
should "provide basic transformation" do
|
95
|
+
model = mock("model", id: 1, as_indexed_json: {})
|
96
|
+
assert_equal @transform.call(model), { index: { _id: "1", data: {} } }
|
97
|
+
end
|
98
|
+
end
|
84
99
|
end
|
85
100
|
|
86
101
|
end
|
data/test/unit/importing_test.rb
CHANGED
@@ -10,6 +10,9 @@ class Elasticsearch::Model::ImportingTest < Test::Unit::TestCase
|
|
10
10
|
def __find_in_batches(options={}, &block)
|
11
11
|
yield if block_given?
|
12
12
|
end
|
13
|
+
def __transform
|
14
|
+
lambda {|a|}
|
15
|
+
end
|
13
16
|
end
|
14
17
|
|
15
18
|
def importing_mixin
|
@@ -41,7 +44,7 @@ class Elasticsearch::Model::ImportingTest < Test::Unit::TestCase
|
|
41
44
|
DummyImportingModel.expects(:client).returns(client)
|
42
45
|
DummyImportingModel.expects(:index_name).returns('foo')
|
43
46
|
DummyImportingModel.expects(:document_type).returns('foo')
|
44
|
-
|
47
|
+
DummyImportingModel.stubs(:__batch_to_bulk)
|
45
48
|
assert_equal 0, DummyImportingModel.import
|
46
49
|
end
|
47
50
|
|
@@ -58,6 +61,7 @@ class Elasticsearch::Model::ImportingTest < Test::Unit::TestCase
|
|
58
61
|
DummyImportingModel.stubs(:client).returns(client)
|
59
62
|
DummyImportingModel.stubs(:index_name).returns('foo')
|
60
63
|
DummyImportingModel.stubs(:document_type).returns('foo')
|
64
|
+
DummyImportingModel.stubs(:__batch_to_bulk)
|
61
65
|
|
62
66
|
assert_equal 1, DummyImportingModel.import
|
63
67
|
end
|
@@ -75,6 +79,7 @@ class Elasticsearch::Model::ImportingTest < Test::Unit::TestCase
|
|
75
79
|
DummyImportingModel.stubs(:client).returns(client)
|
76
80
|
DummyImportingModel.stubs(:index_name).returns('foo')
|
77
81
|
DummyImportingModel.stubs(:document_type).returns('foo')
|
82
|
+
DummyImportingModel.stubs(:__batch_to_bulk)
|
78
83
|
|
79
84
|
DummyImportingModel.import do |response|
|
80
85
|
assert_equal 2, response['items'].size
|
@@ -116,8 +121,36 @@ class Elasticsearch::Model::ImportingTest < Test::Unit::TestCase
|
|
116
121
|
.returns({'items' => [ {'index' => {} }]})
|
117
122
|
|
118
123
|
DummyImportingModel.stubs(:client).returns(client)
|
124
|
+
DummyImportingModel.stubs(:__batch_to_bulk)
|
119
125
|
|
120
126
|
DummyImportingModel.import index: 'my-new-index', type: 'my-other-type'
|
121
127
|
end
|
128
|
+
|
129
|
+
should "use the default transform from adapter" do
|
130
|
+
client = mock('client', bulk: {'items' => []})
|
131
|
+
transform = lambda {|a|}
|
132
|
+
|
133
|
+
DummyImportingModel.stubs(:client).returns(client)
|
134
|
+
DummyImportingModel.expects(:__transform).returns(transform)
|
135
|
+
DummyImportingModel.expects(:__batch_to_bulk).with(anything, transform)
|
136
|
+
|
137
|
+
DummyImportingModel.import index: 'foo', type: 'bar'
|
138
|
+
end
|
139
|
+
|
140
|
+
should "use the transformer from options" do
|
141
|
+
client = mock('client', bulk: {'items' => []})
|
142
|
+
transform = lambda {|a|}
|
143
|
+
|
144
|
+
DummyImportingModel.stubs(:client).returns(client)
|
145
|
+
DummyImportingModel.expects(:__batch_to_bulk).with(anything, transform)
|
146
|
+
|
147
|
+
DummyImportingModel.import index: 'foo', type: 'bar', transform: transform
|
148
|
+
end
|
149
|
+
|
150
|
+
should "raise an ArgumentError if transform doesn't respond to the call method" do
|
151
|
+
assert_raise ArgumentError do
|
152
|
+
DummyImportingModel.import index: 'foo', type: 'bar', transform: "not_callable"
|
153
|
+
end
|
154
|
+
end
|
122
155
|
end
|
123
156
|
end
|
data/test/unit/indexing_test.rb
CHANGED
@@ -47,6 +47,12 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
|
|
47
47
|
assert_instance_of Elasticsearch::Model::Indexing::Mappings, DummyIndexingModel.mappings
|
48
48
|
end
|
49
49
|
|
50
|
+
should "raise an exception when not passed type" do
|
51
|
+
assert_raise ArgumentError do
|
52
|
+
Elasticsearch::Model::Indexing::Mappings.new
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
50
56
|
should "be convertible to hash" do
|
51
57
|
mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype, { foo: 'bar' }
|
52
58
|
assert_equal( { :mytype => { foo: 'bar', :properties => {} } }, mappings.to_hash )
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class Elasticsearch::Model::
|
3
|
+
class Elasticsearch::Model::ResponsePaginationKaminariTest < Test::Unit::TestCase
|
4
4
|
context "Response pagination" do
|
5
5
|
class ModelClass
|
6
6
|
include ::Kaminari::ConfigurationMethods
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'will_paginate'
|
3
|
+
require 'will_paginate/collection'
|
4
|
+
|
5
|
+
class Elasticsearch::Model::ResponsePaginationWillPaginateTest < Test::Unit::TestCase
|
6
|
+
context "Response pagination" do
|
7
|
+
class ModelClass
|
8
|
+
def self.index_name; 'foo'; end
|
9
|
+
def self.document_type; 'bar'; end
|
10
|
+
|
11
|
+
# WillPaginate adds this method to models (see WillPaginate::PerPage module)
|
12
|
+
def self.per_page
|
13
|
+
33
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Subsclass Response so we can include WillPaginate module without conflicts with Kaminari.
|
18
|
+
class WillPaginateResponse < Elasticsearch::Model::Response::Response
|
19
|
+
include Elasticsearch::Model::Response::Pagination::WillPaginate
|
20
|
+
end
|
21
|
+
|
22
|
+
RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'},
|
23
|
+
'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } }
|
24
|
+
|
25
|
+
setup do
|
26
|
+
@search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*'
|
27
|
+
@response = WillPaginateResponse.new ModelClass, @search, RESPONSE
|
28
|
+
@response.klass.stubs(:client).returns mock('client')
|
29
|
+
|
30
|
+
@expected_methods = [
|
31
|
+
# methods needed by WillPaginate::CollectionMethods
|
32
|
+
:current_page,
|
33
|
+
:per_page,
|
34
|
+
:total_entries,
|
35
|
+
|
36
|
+
# methods defined by WillPaginate::CollectionMethods
|
37
|
+
:total_pages,
|
38
|
+
:previous_page,
|
39
|
+
:next_page,
|
40
|
+
:out_of_bounds?,
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
should "have pagination methods" do
|
45
|
+
assert_respond_to @response, :paginate
|
46
|
+
|
47
|
+
@expected_methods.each do |method|
|
48
|
+
assert_respond_to @response, method
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "response.results" do
|
53
|
+
should "have pagination methods" do
|
54
|
+
@expected_methods.each do |method|
|
55
|
+
assert_respond_to @response.results, method
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "response.records" do
|
61
|
+
should "have pagination methods" do
|
62
|
+
@expected_methods.each do |method|
|
63
|
+
@response.klass.stubs(:find).returns([])
|
64
|
+
assert_respond_to @response.records, method
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "#paginate method" do
|
70
|
+
should "set from/size using defaults" do
|
71
|
+
@response.klass.client
|
72
|
+
.expects(:search)
|
73
|
+
.with do |definition|
|
74
|
+
assert_equal 0, definition[:from]
|
75
|
+
assert_equal 33, definition[:size]
|
76
|
+
end
|
77
|
+
.returns(RESPONSE)
|
78
|
+
|
79
|
+
assert_nil @response.search.definition[:from]
|
80
|
+
assert_nil @response.search.definition[:size]
|
81
|
+
|
82
|
+
@response.paginate(page: nil).to_a
|
83
|
+
assert_equal 0, @response.search.definition[:from]
|
84
|
+
assert_equal 33, @response.search.definition[:size]
|
85
|
+
end
|
86
|
+
|
87
|
+
should "set from/size using default per_page" do
|
88
|
+
@response.klass.client
|
89
|
+
.expects(:search)
|
90
|
+
.with do |definition|
|
91
|
+
assert_equal 33, definition[:from]
|
92
|
+
assert_equal 33, definition[:size]
|
93
|
+
end
|
94
|
+
.returns(RESPONSE)
|
95
|
+
|
96
|
+
assert_nil @response.search.definition[:from]
|
97
|
+
assert_nil @response.search.definition[:size]
|
98
|
+
|
99
|
+
@response.paginate(page: 2).to_a
|
100
|
+
assert_equal 33, @response.search.definition[:from]
|
101
|
+
assert_equal 33, @response.search.definition[:size]
|
102
|
+
end
|
103
|
+
|
104
|
+
should "set from/size using custom page and per_page" do
|
105
|
+
@response.klass.client
|
106
|
+
.expects(:search)
|
107
|
+
.with do |definition|
|
108
|
+
assert_equal 18, definition[:from]
|
109
|
+
assert_equal 9, definition[:size]
|
110
|
+
end
|
111
|
+
.returns(RESPONSE)
|
112
|
+
|
113
|
+
assert_nil @response.search.definition[:from]
|
114
|
+
assert_nil @response.search.definition[:size]
|
115
|
+
|
116
|
+
@response.paginate(page: 3, per_page: 9).to_a
|
117
|
+
assert_equal 18, @response.search.definition[:from]
|
118
|
+
assert_equal 9, @response.search.definition[:size]
|
119
|
+
end
|
120
|
+
|
121
|
+
should "searches for page 1 if specified page is < 1" do
|
122
|
+
@response.klass.client
|
123
|
+
.expects(:search)
|
124
|
+
.with do |definition|
|
125
|
+
assert_equal 0, definition[:from]
|
126
|
+
assert_equal 33, definition[:size]
|
127
|
+
end
|
128
|
+
.returns(RESPONSE)
|
129
|
+
|
130
|
+
assert_nil @response.search.definition[:from]
|
131
|
+
assert_nil @response.search.definition[:size]
|
132
|
+
|
133
|
+
@response.paginate(page: "-1").to_a
|
134
|
+
assert_equal 0, @response.search.definition[:from]
|
135
|
+
assert_equal 33, @response.search.definition[:size]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "#page and #per_page shorthand methods" do
|
140
|
+
should "set from/size using default per_page" do
|
141
|
+
@response.page(5)
|
142
|
+
assert_equal 132, @response.search.definition[:from]
|
143
|
+
assert_equal 33, @response.search.definition[:size]
|
144
|
+
end
|
145
|
+
|
146
|
+
should "set from/size when calling #page then #per_page" do
|
147
|
+
@response.page(5).per_page(3)
|
148
|
+
assert_equal 12, @response.search.definition[:from]
|
149
|
+
assert_equal 3, @response.search.definition[:size]
|
150
|
+
end
|
151
|
+
|
152
|
+
should "set from/size when calling #per_page then #page" do
|
153
|
+
@response.per_page(3).page(5)
|
154
|
+
assert_equal 12, @response.search.definition[:from]
|
155
|
+
assert_equal 3, @response.search.definition[:size]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context "#current_page method" do
|
160
|
+
should "return 1 by default" do
|
161
|
+
@response.paginate({})
|
162
|
+
assert_equal 1, @response.current_page
|
163
|
+
end
|
164
|
+
|
165
|
+
should "return current page number" do
|
166
|
+
@response.paginate(page: 3, per_page: 9)
|
167
|
+
assert_equal 3, @response.current_page
|
168
|
+
end
|
169
|
+
|
170
|
+
should "return nil if not pagination set" do
|
171
|
+
assert_equal nil, @response.current_page
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context "#per_page method" do
|
176
|
+
should "return value set in paginate call" do
|
177
|
+
@response.paginate(per_page: 8)
|
178
|
+
assert_equal 8, @response.per_page
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context "#total_entries method" do
|
183
|
+
should "return total from response" do
|
184
|
+
@response.expects(:results).returns(mock('results', total: 100))
|
185
|
+
assert_equal 100, @response.total_entries
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -15,6 +15,20 @@ class Elasticsearch::Model::ResultTest < Test::Unit::TestCase
|
|
15
15
|
assert_raise(NoMethodError) { result.xoxo }
|
16
16
|
end
|
17
17
|
|
18
|
+
should "return _id as #id" do
|
19
|
+
result = Elasticsearch::Model::Response::Result.new foo: 'bar', _id: 42, _source: { id: 12 }
|
20
|
+
|
21
|
+
assert_equal 42, result.id
|
22
|
+
assert_equal 12, result._source.id
|
23
|
+
end
|
24
|
+
|
25
|
+
should "return _type as #type" do
|
26
|
+
result = Elasticsearch::Model::Response::Result.new foo: 'bar', _type: 'baz', _source: { type: 'BAM' }
|
27
|
+
|
28
|
+
assert_equal 'baz', result.type
|
29
|
+
assert_equal 'BAM', result._source.type
|
30
|
+
end
|
31
|
+
|
18
32
|
should "delegate method calls to `_source` when available" do
|
19
33
|
result = Elasticsearch::Model::Response::Result.new foo: 'bar', _source: { bar: 'baz' }
|
20
34
|
|
@@ -27,12 +41,27 @@ class Elasticsearch::Model::ResultTest < Test::Unit::TestCase
|
|
27
41
|
assert_equal 'baz', result.bar
|
28
42
|
end
|
29
43
|
|
44
|
+
should "delegate existence method calls to `_source`" do
|
45
|
+
result = Elasticsearch::Model::Response::Result.new foo: 'bar', _source: { bar: { bam: 'baz' } }
|
46
|
+
|
47
|
+
assert_respond_to result._source, :bar?
|
48
|
+
assert_respond_to result, :bar?
|
49
|
+
|
50
|
+
assert_equal true, result._source.bar?
|
51
|
+
assert_equal true, result.bar?
|
52
|
+
assert_equal false, result.boo?
|
53
|
+
|
54
|
+
assert_equal true, result.bar.bam?
|
55
|
+
assert_equal false, result.bar.boo?
|
56
|
+
end
|
57
|
+
|
30
58
|
should "delegate methods to @result" do
|
31
59
|
result = Elasticsearch::Model::Response::Result.new foo: 'bar'
|
32
60
|
|
33
|
-
assert_equal 'bar',
|
34
|
-
assert_equal 'bar',
|
35
|
-
assert_equal 'moo',
|
61
|
+
assert_equal 'bar', result.foo
|
62
|
+
assert_equal 'bar', result.fetch('foo')
|
63
|
+
assert_equal 'moo', result.fetch('NOT_EXIST', 'moo')
|
64
|
+
assert_equal ['foo'], result.keys
|
36
65
|
|
37
66
|
assert_respond_to result, :to_hash
|
38
67
|
assert_equal({'foo' => 'bar'}, result.to_hash)
|
@@ -40,6 +69,16 @@ class Elasticsearch::Model::ResultTest < Test::Unit::TestCase
|
|
40
69
|
assert_raise(NoMethodError) { result.does_not_exist }
|
41
70
|
end
|
42
71
|
|
72
|
+
should "delegate existence method calls to @result" do
|
73
|
+
result = Elasticsearch::Model::Response::Result.new foo: 'bar', _source: { bar: 'bam' }
|
74
|
+
assert_respond_to result, :foo?
|
75
|
+
|
76
|
+
assert_equal true, result.foo?
|
77
|
+
assert_equal false, result.boo?
|
78
|
+
assert_equal false, result._source.foo?
|
79
|
+
assert_equal false, result._source.boo?
|
80
|
+
end
|
81
|
+
|
43
82
|
should "delegate as_json to @result even when ActiveSupport changed half of Ruby" do
|
44
83
|
require 'active_support/json/encoding'
|
45
84
|
result = Elasticsearch::Model::Response::Result.new foo: 'bar'
|
@@ -47,6 +86,5 @@ class Elasticsearch::Model::ResultTest < Test::Unit::TestCase
|
|
47
86
|
result.instance_variable_get(:@result).expects(:as_json)
|
48
87
|
result.as_json(except: 'foo')
|
49
88
|
end
|
50
|
-
|
51
89
|
end
|
52
90
|
end
|