search_flip 2.0.0.beta3 → 2.0.0.beta4
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 +4 -4
- data/UPDATING.md +45 -0
- data/lib/search_flip/aggregation.rb +47 -0
- data/lib/search_flip/connection.rb +11 -0
- data/lib/search_flip/criteria.rb +5 -3
- data/lib/search_flip/index.rb +7 -1
- data/lib/search_flip/version.rb +1 -1
- data/spec/search_flip/aggregation_spec.rb +118 -0
- data/spec/search_flip/criteria_spec.rb +4 -5
- data/spec/search_flip/index_spec.rb +10 -0
- data/spec/spec_helper.rb +12 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c626b4c449c70b071fee204d164b226271d2f098ae48f4c54568e07b6ad8d9d8
|
4
|
+
data.tar.gz: f35599e1cd57a27fe3c008fa20538be08fa9f7251a5c421d25fd98113d2a6cc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95df5e882633308f0dcbcac35d155901db7bfee39f0f37d1ee84c055fdde821a22b697fd0df5ea4ab82204896c3adb483769cae85b1489ec2eca28c4fc0e2de5
|
7
|
+
data.tar.gz: e97fceeacf0186b99829fb70d9a8eb396455c2f2cf3dec58e2f357dd5b8bd542025e7ee33201757af02c45b90752da70e6d28e6cbff2605fc36d0fd7ac9638d2
|
data/UPDATING.md
CHANGED
@@ -127,3 +127,48 @@ query = CommentIndex.highlight(:title).search("hello")
|
|
127
127
|
query.results[0]._hit.highlight.title # => "<em>hello</em> world"
|
128
128
|
```
|
129
129
|
|
130
|
+
* **[BREAKING]** `index_name` no longer defaults to `type_name`
|
131
|
+
|
132
|
+
1.x:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
class CommentIndex
|
136
|
+
include SearchFlip::Index
|
137
|
+
|
138
|
+
def self.type_name
|
139
|
+
"comments"
|
140
|
+
end
|
141
|
+
|
142
|
+
# CommentIndex.index_name defaults to CommentIndex.type_name
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
2.x:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
class CommentIndex
|
150
|
+
include SearchFlip::Index
|
151
|
+
|
152
|
+
def self.type_name
|
153
|
+
"comments"
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.index_name
|
157
|
+
"comments"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
* **[BREAKING]** Multiple calls to `source` no longer concatenate
|
163
|
+
|
164
|
+
1.x:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
CommentIndex.source([:id]).source([:description]) # => CommentIndex.source([:id, :description])
|
168
|
+
```
|
169
|
+
|
170
|
+
2.x:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
CommentIndex.source([:id]).source([:description]) # => CommentIndex.source([:description])
|
174
|
+
```
|
@@ -49,6 +49,53 @@ module SearchFlip
|
|
49
49
|
res
|
50
50
|
end
|
51
51
|
|
52
|
+
# @api private
|
53
|
+
#
|
54
|
+
# Merges a criteria into the aggregation.
|
55
|
+
#
|
56
|
+
# @param other [SearchFlip::Criteria] The criteria to merge in
|
57
|
+
#
|
58
|
+
# @return [SearchFlip::Aggregation] A fresh aggregation including the merged criteria
|
59
|
+
|
60
|
+
def merge(other)
|
61
|
+
other = other.criteria
|
62
|
+
|
63
|
+
fresh.tap do |aggregation|
|
64
|
+
unsupported_methods = [
|
65
|
+
:profile_value, :failsafe_value, :terminate_after_value, :timeout_value, :offset_value, :limit_value,
|
66
|
+
:scroll_args, :highlight_values, :suggest_values, :custom_value, :source_value, :sort_values,
|
67
|
+
:includes_values, :preload_values, :eager_load_values, :post_search_values, :post_must_values,
|
68
|
+
:post_must_not_values, :post_should_values, :post_filter_values
|
69
|
+
]
|
70
|
+
|
71
|
+
unsupported_methods.each do |unsupported_method|
|
72
|
+
unless other.send(unsupported_method).nil?
|
73
|
+
raise(SearchFlip::NotSupportedError, "Using #{unsupported_method} within aggregations is not supported")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
aggregation.search_values = (aggregation.search_values || []) + other.search_values if other.search_values
|
78
|
+
aggregation.must_values = (aggregation.must_values || []) + other.must_values if other.must_values
|
79
|
+
aggregation.must_not_values = (aggregation.must_not_values || []) + other.must_not_values if other.must_not_values
|
80
|
+
aggregation.should_values = (aggregation.should_values || []) + other.should_values if other.should_values
|
81
|
+
aggregation.filter_values = (aggregation.filter_values || []) + other.filter_values if other.filter_values
|
82
|
+
|
83
|
+
aggregation.aggregation_values = (aggregation.aggregation_values || {}).merge(other.aggregation_values) if other.aggregation_values
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def respond_to_missing?(name, *args)
|
88
|
+
target.respond_to?(name, *args)
|
89
|
+
end
|
90
|
+
|
91
|
+
def method_missing(name, *args, &block)
|
92
|
+
if target.respond_to?(name)
|
93
|
+
merge(target.send(name, *args, &block))
|
94
|
+
else
|
95
|
+
super
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
52
99
|
# @api private
|
53
100
|
#
|
54
101
|
# Simply dups the object for api compatability.
|
@@ -137,12 +137,15 @@ module SearchFlip
|
|
137
137
|
.parse
|
138
138
|
end
|
139
139
|
|
140
|
+
alias_method :cat_indices, :get_indices
|
141
|
+
|
140
142
|
# Creates the specified index within ElasticSearch and applies index
|
141
143
|
# settings, if specified. Raises SearchFlip::ResponseError in case any
|
142
144
|
# errors occur.
|
143
145
|
#
|
144
146
|
# @param index_name [String] The index name
|
145
147
|
# @param index_settings [Hash] The index settings
|
148
|
+
#
|
146
149
|
# @return [Boolean] Returns true or raises SearchFlip::ResponseError
|
147
150
|
|
148
151
|
def create_index(index_name, index_settings = {})
|
@@ -157,6 +160,7 @@ module SearchFlip
|
|
157
160
|
#
|
158
161
|
# @param index_name [String] The index name to update the settings for
|
159
162
|
# @param index_settings [Hash] The index settings
|
163
|
+
#
|
160
164
|
# @return [Boolean] Returns true or raises SearchFlip::ResponseError
|
161
165
|
|
162
166
|
def update_index_settings(index_name, index_settings)
|
@@ -170,6 +174,7 @@ module SearchFlip
|
|
170
174
|
# SearchFlip::ResponseError in case any errors occur.
|
171
175
|
#
|
172
176
|
# @param index_name [String] The index name
|
177
|
+
#
|
173
178
|
# @return [Hash] The index settings
|
174
179
|
|
175
180
|
def get_index_settings(index_name)
|
@@ -195,6 +200,7 @@ module SearchFlip
|
|
195
200
|
# @param index_name [String] The index name
|
196
201
|
# @param type_name [String] The type name
|
197
202
|
# @param mapping [Hash] The mapping
|
203
|
+
#
|
198
204
|
# @return [Boolean] Returns true or raises SearchFlip::ResponseError
|
199
205
|
|
200
206
|
def update_mapping(index_name, type_name, mapping)
|
@@ -208,6 +214,7 @@ module SearchFlip
|
|
208
214
|
#
|
209
215
|
# @param index_name [String] The index name
|
210
216
|
# @param type_name [String] The type name
|
217
|
+
#
|
211
218
|
# @return [Hash] The current type mapping
|
212
219
|
|
213
220
|
def get_mapping(index_name, type_name)
|
@@ -218,6 +225,7 @@ module SearchFlip
|
|
218
225
|
# SearchFlip::ResponseError in case any errors occur.
|
219
226
|
#
|
220
227
|
# @param index_name [String] The index name
|
228
|
+
#
|
221
229
|
# @return [Boolean] Returns true or raises SearchFlip::ResponseError
|
222
230
|
|
223
231
|
def delete_index(index_name)
|
@@ -229,6 +237,7 @@ module SearchFlip
|
|
229
237
|
# Returns whether or not the specified index already exists.
|
230
238
|
#
|
231
239
|
# @param index_name [String] The index name
|
240
|
+
#
|
232
241
|
# @return [Boolean] Whether or not the index exists
|
233
242
|
|
234
243
|
def index_exists?(index_name)
|
@@ -246,6 +255,7 @@ module SearchFlip
|
|
246
255
|
#
|
247
256
|
# @param index_name [String] The index name
|
248
257
|
# @param type_name [String] The type name
|
258
|
+
#
|
249
259
|
# @return [String] The ElasticSearch type URL
|
250
260
|
|
251
261
|
def type_url(index_name, type_name)
|
@@ -256,6 +266,7 @@ module SearchFlip
|
|
256
266
|
# URL and index name with prefix.
|
257
267
|
#
|
258
268
|
# @param index_name [String] The index name
|
269
|
+
#
|
259
270
|
# @return [String] The ElasticSearch index URL
|
260
271
|
|
261
272
|
def index_url(index_name)
|
data/lib/search_flip/criteria.rb
CHANGED
@@ -45,8 +45,8 @@ module SearchFlip
|
|
45
45
|
criteria.offset_value = other.offset_value if other.offset_value
|
46
46
|
criteria.limit_value = other.limit_value if other.limit_value
|
47
47
|
criteria.scroll_args = other.scroll_args if other.scroll_args
|
48
|
+
criteria.source_value = other.source_value if other.source_value
|
48
49
|
|
49
|
-
criteria.source_value = (criteria.source_value || []) + other.source_value if other.source_value
|
50
50
|
criteria.sort_values = (criteria.sort_values || []) + other.sort_values if other.sort_values
|
51
51
|
criteria.includes_values = (criteria.includes_values || []) + other.includes_values if other.includes_values
|
52
52
|
criteria.preload_values = (criteria.preload_values || []) + other.preload_values if other.preload_values
|
@@ -122,7 +122,7 @@ module SearchFlip
|
|
122
122
|
|
123
123
|
fresh.tap do |criteria|
|
124
124
|
criteria.search_values = nil if scopes.include?(:search)
|
125
|
-
criteria.post_search_values = nil if scopes.include?(:
|
125
|
+
criteria.post_search_values = nil if scopes.include?(:post_search)
|
126
126
|
criteria.sort_values = nil if scopes.include?(:sort)
|
127
127
|
criteria.hightlight_values = nil if scopes.include?(:highlight)
|
128
128
|
criteria.suggest_values = nil if scopes.include?(:suggest)
|
@@ -375,8 +375,10 @@ module SearchFlip
|
|
375
375
|
#
|
376
376
|
# @example
|
377
377
|
# CommentIndex.source([:id, :message]).search("hello world")
|
378
|
+
# CommentIndex.source(exclude: "description")
|
379
|
+
# CommentIndex.source(false)
|
378
380
|
#
|
379
|
-
# @param value
|
381
|
+
# @param value Pass any allowed value to restrict the returned source
|
380
382
|
#
|
381
383
|
# @return [SearchFlip::Criteria] A newly created extended criteria
|
382
384
|
|
data/lib/search_flip/index.rb
CHANGED
@@ -87,6 +87,7 @@ module SearchFlip
|
|
87
87
|
#
|
88
88
|
# @param index_name [String] A custom index_name
|
89
89
|
# @param connection [SearchFlip::Connection] A custom connection
|
90
|
+
#
|
90
91
|
# @return [Class] An anonymous class
|
91
92
|
|
92
93
|
def with_settings(index_name: nil, connection: nil)
|
@@ -113,6 +114,7 @@ module SearchFlip
|
|
113
114
|
# end
|
114
115
|
#
|
115
116
|
# @param record The record that gets serialized
|
117
|
+
#
|
116
118
|
# @return [Hash] The hash-representation of the record
|
117
119
|
|
118
120
|
def serialize(record)
|
@@ -178,6 +180,7 @@ module SearchFlip
|
|
178
180
|
# end
|
179
181
|
#
|
180
182
|
# @param record The record to get the primary key for
|
183
|
+
#
|
181
184
|
# @return [String, Fixnum] The record's primary key
|
182
185
|
|
183
186
|
def record_id(record)
|
@@ -189,6 +192,7 @@ module SearchFlip
|
|
189
192
|
# keys and/or ORMs.
|
190
193
|
#
|
191
194
|
# @param ids [Array] The array of ids to fetch the records for
|
195
|
+
#
|
192
196
|
# @return The record set or an array of records
|
193
197
|
|
194
198
|
def fetch_records(ids)
|
@@ -267,7 +271,7 @@ module SearchFlip
|
|
267
271
|
# @return [String] The base name of the index, ie without prefix
|
268
272
|
|
269
273
|
def index_name
|
270
|
-
|
274
|
+
raise SearchFlip::MethodNotImplemented, "You must implement #{name}::index_name"
|
271
275
|
end
|
272
276
|
|
273
277
|
# @api private
|
@@ -324,6 +328,7 @@ module SearchFlip
|
|
324
328
|
# occur.
|
325
329
|
#
|
326
330
|
# @param include_mapping [Boolean] Whether or not to include the mapping
|
331
|
+
#
|
327
332
|
# @return [Boolean] Returns true or false
|
328
333
|
|
329
334
|
def create_index(include_mapping: false)
|
@@ -393,6 +398,7 @@ module SearchFlip
|
|
393
398
|
#
|
394
399
|
# @param id [String, Fixnum] The id to get
|
395
400
|
# @param params [Hash] Optional params for the request
|
401
|
+
#
|
396
402
|
# @return [Hash] The specified document
|
397
403
|
|
398
404
|
def get(id, params = {})
|
data/lib/search_flip/version.rb
CHANGED
@@ -262,4 +262,122 @@ RSpec.describe SearchFlip::Aggregation do
|
|
262
262
|
expect(query.aggregations(:category)["category2"].title.buckets.detect { |bucket| bucket[:key] == "title2" }.price.value).to eq(60)
|
263
263
|
end
|
264
264
|
end
|
265
|
+
|
266
|
+
describe "#merge" do
|
267
|
+
it "merges a criteria into the aggregation" do
|
268
|
+
product1 = create(:product, price: 100, category: "category1")
|
269
|
+
product2 = create(:product, price: 150, category: "category1")
|
270
|
+
product3 = create(:product, price: 200, category: "category2")
|
271
|
+
product4 = create(:product, price: 300, category: "category1")
|
272
|
+
|
273
|
+
ProductIndex.import [product1, product2, product3, product4]
|
274
|
+
|
275
|
+
query = ProductIndex.aggregate(categories: {}) do |agg|
|
276
|
+
agg.merge(ProductIndex.where(price: 100..200)).aggregate(:category)
|
277
|
+
end
|
278
|
+
|
279
|
+
result = query.aggregations(:categories).category.buckets.each_with_object({}) do |bucket, hash|
|
280
|
+
hash[bucket["key"]] = bucket.doc_count
|
281
|
+
end
|
282
|
+
|
283
|
+
expect(result).to eq("category1" => 2, "category2" => 1)
|
284
|
+
end
|
285
|
+
|
286
|
+
describe "unsupported methods" do
|
287
|
+
unsupported_methods = [
|
288
|
+
:profile_value, :failsafe_value, :terminate_after_value, :timeout_value, :offset_value, :limit_value,
|
289
|
+
:scroll_args, :highlight_values, :suggest_values, :custom_value, :source_value, :sort_values,
|
290
|
+
:includes_values, :preload_values, :eager_load_values, :post_search_values, :post_must_values,
|
291
|
+
:post_must_not_values, :post_should_values, :post_filter_values
|
292
|
+
]
|
293
|
+
|
294
|
+
unsupported_methods.each do |unsupported_method|
|
295
|
+
it "raises a NotSupportedError #{unsupported_method}" do
|
296
|
+
block = lambda do
|
297
|
+
TestIndex.aggregate(field: {}) do |agg|
|
298
|
+
criteria = SearchFlip::Criteria.new(target: TestIndex)
|
299
|
+
criteria.send("#{unsupported_method}=", "value")
|
300
|
+
|
301
|
+
agg.merge(criteria)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
expect(&block).to raise_error(SearchFlip::NotSupportedError)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
describe "array concatenations" do
|
311
|
+
methods = [:search_values, :must_values, :must_not_values, :should_values, :filter_values]
|
312
|
+
|
313
|
+
methods.each do |method|
|
314
|
+
it "concatenates the values for #{method}" do
|
315
|
+
aggregation = SearchFlip::Aggregation.new(target: TestIndex)
|
316
|
+
aggregation.send("#{method}=", ["value1"])
|
317
|
+
|
318
|
+
criteria = SearchFlip::Criteria.new(target: TestIndex)
|
319
|
+
criteria.send("#{method}=", ["value2"])
|
320
|
+
|
321
|
+
result = aggregation.merge(criteria)
|
322
|
+
|
323
|
+
expect(result.send(method)).to eq(["value1", "value2"])
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
describe "hash merges" do
|
329
|
+
methods = [:aggregation_values]
|
330
|
+
|
331
|
+
methods.each do |method|
|
332
|
+
it "merges the values for #{method}" do
|
333
|
+
aggregation = SearchFlip::Aggregation.new(target: TestIndex)
|
334
|
+
aggregation.send("#{method}=", key1: "value1")
|
335
|
+
|
336
|
+
criteria = SearchFlip::Criteria.new(target: TestIndex)
|
337
|
+
criteria.send("#{method}=", key2: "value2")
|
338
|
+
|
339
|
+
result = aggregation.merge(criteria)
|
340
|
+
|
341
|
+
expect(result.send(method)).to eq(key1: "value1", key2: "value2")
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
describe "#respond_to?" do
|
348
|
+
it "checks whether or not the index class responds to the method" do
|
349
|
+
temp_index = Class.new(ProductIndex)
|
350
|
+
aggregation = SearchFlip::Aggregation.new(target: temp_index)
|
351
|
+
|
352
|
+
expect(aggregation.respond_to?(:test_scope)).to eq(false)
|
353
|
+
|
354
|
+
temp_index.scope(:test_scope) { match_all }
|
355
|
+
|
356
|
+
expect(aggregation.respond_to?(:test_scope)).to eq(true)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
describe "#method_missing" do
|
361
|
+
it "delegates to the index class" do
|
362
|
+
temp_index = Class.new(ProductIndex)
|
363
|
+
temp_index.scope(:with_price_range) { |range| where(price: range) }
|
364
|
+
|
365
|
+
product1 = create(:product, price: 100, category: "category1")
|
366
|
+
product2 = create(:product, price: 150, category: "category1")
|
367
|
+
product3 = create(:product, price: 200, category: "category2")
|
368
|
+
product4 = create(:product, price: 300, category: "category1")
|
369
|
+
|
370
|
+
temp_index.import [product1, product2, product3, product4]
|
371
|
+
|
372
|
+
query = temp_index.aggregate(categories: {}) do |agg|
|
373
|
+
agg.merge(temp_index.with_price_range(100..200)).aggregate(:category)
|
374
|
+
end
|
375
|
+
|
376
|
+
result = query.aggregations(:categories).category.buckets.each_with_object({}) do |bucket, hash|
|
377
|
+
hash[bucket["key"]] = bucket.doc_count
|
378
|
+
end
|
379
|
+
|
380
|
+
expect(result).to eq("category1" => 2, "category2" => 1)
|
381
|
+
end
|
382
|
+
end
|
265
383
|
end
|
@@ -35,7 +35,7 @@ RSpec.describe SearchFlip::Criteria do
|
|
35
35
|
describe "assignments" do
|
36
36
|
methods = [
|
37
37
|
:profile_value, :failsafe_value, :terminate_after_value, :timeout_value,
|
38
|
-
:offset_value, :limit_value, :scroll_args
|
38
|
+
:offset_value, :limit_value, :scroll_args, :source_value
|
39
39
|
]
|
40
40
|
|
41
41
|
methods.each do |method|
|
@@ -53,10 +53,9 @@ RSpec.describe SearchFlip::Criteria do
|
|
53
53
|
|
54
54
|
describe "array concatenations" do
|
55
55
|
methods = [
|
56
|
-
:
|
57
|
-
:
|
58
|
-
:
|
59
|
-
:post_filter_values
|
56
|
+
:sort_values, :includes_values, :preload_values, :eager_load_values, :search_values,
|
57
|
+
:must_values, :must_not_values, :should_values, :filter_values, :post_search_values,
|
58
|
+
:post_must_values, :post_must_not_values, :post_should_values, :post_filter_values
|
60
59
|
]
|
61
60
|
|
62
61
|
methods.each do |method|
|
@@ -42,6 +42,16 @@ RSpec.describe SearchFlip::Index do
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
describe ".type_name" do
|
46
|
+
it "raises a SearchFlip::MethodNotImplemented by default" do
|
47
|
+
klass = Class.new do
|
48
|
+
include SearchFlip::Index
|
49
|
+
end
|
50
|
+
|
51
|
+
expect { klass.index_name }.to raise_error(SearchFlip::MethodNotImplemented)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
45
55
|
describe ".create_index" do
|
46
56
|
it "delegates to connection" do
|
47
57
|
allow(TestIndex.connection).to receive(:create_index).and_call_original
|
data/spec/spec_helper.rb
CHANGED
@@ -88,6 +88,10 @@ class CommentIndex
|
|
88
88
|
"comments"
|
89
89
|
end
|
90
90
|
|
91
|
+
def self.index_name
|
92
|
+
"comments"
|
93
|
+
end
|
94
|
+
|
91
95
|
def self.model
|
92
96
|
Comment
|
93
97
|
end
|
@@ -136,6 +140,10 @@ class ProductIndex
|
|
136
140
|
"products"
|
137
141
|
end
|
138
142
|
|
143
|
+
def self.index_name
|
144
|
+
"products"
|
145
|
+
end
|
146
|
+
|
139
147
|
def self.model
|
140
148
|
Product
|
141
149
|
end
|
@@ -174,6 +182,10 @@ class TestIndex
|
|
174
182
|
def self.type_name
|
175
183
|
"test"
|
176
184
|
end
|
185
|
+
|
186
|
+
def self.index_name
|
187
|
+
"test"
|
188
|
+
end
|
177
189
|
end
|
178
190
|
|
179
191
|
TestIndex.delete_index if TestIndex.index_exists?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: search_flip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0.
|
4
|
+
version: 2.0.0.beta4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamin Vetter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-03-
|
11
|
+
date: 2019-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|