embulk-input-zendesk 0.2.14 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -3
  3. data/.travis.yml +5 -44
  4. data/CHANGELOG.md +3 -0
  5. data/README.md +5 -5
  6. data/build.gradle +123 -0
  7. data/classpath/commons-codec-1.10.jar +0 -0
  8. data/classpath/commons-logging-1.2.jar +0 -0
  9. data/classpath/embulk-input-zendesk-0.3.0.jar +0 -0
  10. data/classpath/httpclient-4.5.6.jar +0 -0
  11. data/classpath/httpcore-4.4.10.jar +0 -0
  12. data/config/checkstyle/checkstyle.xml +128 -0
  13. data/config/checkstyle/default.xml +108 -0
  14. data/gradle/wrapper/gradle-wrapper.jar +0 -0
  15. data/gradle/wrapper/gradle-wrapper.properties +5 -0
  16. data/gradlew +172 -0
  17. data/gradlew.bat +84 -0
  18. data/lib/embulk/guess/zendesk.rb +21 -0
  19. data/lib/embulk/input/zendesk.rb +3 -9
  20. data/src/main/java/org/embulk/input/zendesk/ZendeskInputPlugin.java +471 -0
  21. data/src/main/java/org/embulk/input/zendesk/clients/ZendeskRestClient.java +268 -0
  22. data/src/main/java/org/embulk/input/zendesk/models/AuthenticationMethod.java +23 -0
  23. data/src/main/java/org/embulk/input/zendesk/models/Target.java +46 -0
  24. data/src/main/java/org/embulk/input/zendesk/models/ZendeskException.java +25 -0
  25. data/src/main/java/org/embulk/input/zendesk/services/ZendeskSupportAPIService.java +109 -0
  26. data/src/main/java/org/embulk/input/zendesk/utils/ZendeskConstants.java +61 -0
  27. data/src/main/java/org/embulk/input/zendesk/utils/ZendeskDateUtils.java +51 -0
  28. data/src/main/java/org/embulk/input/zendesk/utils/ZendeskUtils.java +150 -0
  29. data/src/main/java/org/embulk/input/zendesk/utils/ZendeskValidatorUtils.java +92 -0
  30. data/src/test/java/org/embulk/input/zendesk/TestZendeskInputPlugin.java +232 -0
  31. data/src/test/java/org/embulk/input/zendesk/clients/TestZendeskRestClient.java +351 -0
  32. data/src/test/java/org/embulk/input/zendesk/services/TestZendeskSupportAPIService.java +172 -0
  33. data/src/test/java/org/embulk/input/zendesk/utils/TestZendeskDateUtils.java +36 -0
  34. data/src/test/java/org/embulk/input/zendesk/utils/TestZendeskUtil.java +160 -0
  35. data/src/test/java/org/embulk/input/zendesk/utils/TestZendeskValidatorUtils.java +138 -0
  36. data/src/test/java/org/embulk/input/zendesk/utils/ZendeskPluginTestRuntime.java +133 -0
  37. data/src/test/java/org/embulk/input/zendesk/utils/ZendeskTestHelper.java +63 -0
  38. data/src/test/resources/config/base.yml +14 -0
  39. data/src/test/resources/config/base_validator.yml +48 -0
  40. data/src/test/resources/config/incremental.yml +54 -0
  41. data/src/test/resources/config/non-incremental.yml +39 -0
  42. data/src/test/resources/config/util.yml +18 -0
  43. data/src/test/resources/data/client.json +293 -0
  44. data/src/test/resources/data/error_data.json +187 -0
  45. data/src/test/resources/data/expected/ticket_column.json +148 -0
  46. data/src/test/resources/data/expected/ticket_column_with_related_objects.json +152 -0
  47. data/src/test/resources/data/expected/ticket_fields_column.json +92 -0
  48. data/src/test/resources/data/expected/ticket_metrics_column.json +98 -0
  49. data/src/test/resources/data/ticket_fields.json +225 -0
  50. data/src/test/resources/data/ticket_metrics.json +397 -0
  51. data/src/test/resources/data/ticket_with_related_objects.json +67 -0
  52. data/src/test/resources/data/tickets.json +232 -0
  53. data/src/test/resources/data/tickets_continue.json +52 -0
  54. data/src/test/resources/data/util.json +19 -0
  55. data/src/test/resources/data/util_page.json +227 -0
  56. metadata +65 -221
  57. data/.ruby-version +0 -1
  58. data/.travis.yml.erb +0 -43
  59. data/Gemfile +0 -2
  60. data/Rakefile +0 -21
  61. data/embulk-input-zendesk.gemspec +0 -29
  62. data/gemfiles/embulk-0.8.0-latest +0 -4
  63. data/gemfiles/embulk-0.8.1 +0 -4
  64. data/gemfiles/embulk-latest +0 -4
  65. data/gemfiles/template.erb +0 -4
  66. data/lib/embulk/input/zendesk/client.rb +0 -434
  67. data/lib/embulk/input/zendesk/plugin.rb +0 -199
  68. data/test/capture_io.rb +0 -45
  69. data/test/embulk/input/zendesk/test_client.rb +0 -722
  70. data/test/embulk/input/zendesk/test_plugin.rb +0 -628
  71. data/test/fixture_helper.rb +0 -11
  72. data/test/fixtures/invalid_app_marketplace_lack_one_property.yml +0 -13
  73. data/test/fixtures/invalid_app_marketplace_lack_two_property.yml +0 -12
  74. data/test/fixtures/invalid_lack_username.yml +0 -9
  75. data/test/fixtures/invalid_unknown_auth.yml +0 -9
  76. data/test/fixtures/tickets.json +0 -44
  77. data/test/fixtures/valid_app_marketplace.yml +0 -14
  78. data/test/fixtures/valid_auth_basic.yml +0 -11
  79. data/test/fixtures/valid_auth_oauth.yml +0 -10
  80. data/test/fixtures/valid_auth_token.yml +0 -11
  81. data/test/override_assert_raise.rb +0 -21
  82. data/test/run-test.rb +0 -26
@@ -1,628 +0,0 @@
1
- require "embulk"
2
- Embulk.setup
3
-
4
- require "yaml"
5
- require "embulk/input/zendesk"
6
- require "override_assert_raise"
7
- require "fixture_helper"
8
- require "capture_io"
9
-
10
- module Embulk
11
- module Input
12
- module Zendesk
13
- class TestPlugin < Test::Unit::TestCase
14
- include OverrideAssertRaise
15
- include FixtureHelper
16
- include CaptureIo
17
-
18
- def run_with(yml)
19
- silence do
20
- Embulk::Runner.run(YAML.load fixture_load(yml))
21
- end
22
- end
23
-
24
- sub_test_case "exec" do
25
- setup do
26
- stub(Plugin).resume { Hash.new }
27
- end
28
-
29
- test "run with valid.yml (basic)" do
30
- assert_nothing_raised do
31
- run_with("valid_auth_basic.yml")
32
- end
33
- end
34
-
35
- test "run with valid.yml (token)" do
36
- assert_nothing_raised do
37
- run_with("valid_auth_token.yml")
38
- end
39
- end
40
-
41
- test "run with valid.yml (oauth)" do
42
- assert_nothing_raised do
43
- run_with("valid_auth_oauth.yml")
44
- end
45
- end
46
-
47
- test "run with invalid username lack" do
48
- # NOTE: will be raised Java::OrgEmbulkExec::PartialExecutionException, not ConfigError. It is Embulk internally exception handling matter.
49
- assert_raise do
50
- run_with("invalid_lack_username.yml")
51
- end
52
- end
53
-
54
- test "run with valid.yml (app_marketplace) contains three properties" do
55
- assert_nothing_raised do
56
- run_with("valid_app_marketplace.yml")
57
- end
58
- end
59
-
60
- test "run with invalid lack one of app marketplace properties" do
61
- # NOTE: will be raised Java::OrgEmbulkExec::PartialExecutionException, not ConfigError. It is Embulk internally exception handling matter.
62
- assert_raise do
63
- run_with("invalid_app_marketplace_lack_one_property.yml")
64
- end
65
- end
66
-
67
- test "run with invalid lack two of app marketplace properties" do
68
- # NOTE: will be raised Java::OrgEmbulkExec::PartialExecutionException, not ConfigError. It is Embulk internally exception handling matter.
69
- assert_raise do
70
- run_with("invalid_app_marketplace_lack_two_property.yml")
71
- end
72
- end
73
- end
74
-
75
- sub_test_case ".transaction" do
76
- setup do
77
- stub(Plugin).resume { Hash.new }
78
- @control = proc { Hash.new }
79
- end
80
-
81
- def config(yml)
82
- conf = YAML.load fixture_load(yml)
83
- Embulk::DataSource.new(conf["in"])
84
- end
85
-
86
- test "lack username config" do
87
- assert_raise(ConfigError) do
88
- Plugin.transaction(config("invalid_lack_username.yml"), &@control)
89
- end
90
- end
91
-
92
- test "unknown auth_method" do
93
- assert_raise(ConfigError) do
94
- Plugin.transaction(config("invalid_unknown_auth.yml"), &@control)
95
- end
96
- end
97
-
98
- test "invoke Client#validate_config" do
99
- any_instance_of(Client) do |klass|
100
- mock(klass).validate_config
101
- end
102
- Plugin.transaction(config("valid_auth_oauth.yml"), &@control)
103
- end
104
-
105
- test "run as well" do
106
- actual = nil
107
- assert_nothing_raised do
108
- actual = Plugin.transaction(config("valid_auth_oauth.yml"), &@control)
109
- end
110
-
111
- expected = {}
112
- assert_equal expected, actual
113
- end
114
- end
115
-
116
- sub_test_case ".guess" do
117
- setup do
118
- @client = Client.new(task)
119
- stub(Client).new { @client }
120
- @httpclient = @client.httpclient
121
- stub(@client).httpclient { @httpclient }
122
- end
123
-
124
- test "invoke Client#validate_config" do
125
- @httpclient.test_loopback_http_response << [
126
- "HTTP/1.1 200",
127
- "Content-Type: application/json",
128
- "",
129
- JSON.parse(fixture_load("tickets.json")).to_json
130
- ].join("\r\n")
131
- mock(@client).validate_config
132
- Plugin.guess(config)["columns"]
133
- end
134
-
135
- test "guessing" do
136
- @httpclient.test_loopback_http_response << [
137
- "HTTP/1.1 200",
138
- "Content-Type: application/json",
139
- "",
140
- JSON.parse(fixture_load("tickets.json")).to_json
141
- ].join("\r\n")
142
- actual = Plugin.guess(config)["columns"]
143
- assert actual.include?(name: "url", type: :string)
144
- assert actual.include?(name: "id", type: :long)
145
- assert actual.include?(name: "created_at", type: :timestamp, format: "%Y-%m-%dT%H:%M:%S%z")
146
- assert actual.include?(name: "has_incidents", type: :boolean)
147
- assert actual.include?(name: "tags", type: :json)
148
- assert actual.include?(name: "collaborator_ids", type: :json)
149
- assert actual.include?(name: "custom_fields", type: :json)
150
- assert actual.include?(name: "group_id", type: :string)
151
- assert actual.include?(name: "satisfaction_rating", type: :json)
152
- end
153
- end
154
-
155
- sub_test_case "include subresources" do
156
- def page_builder
157
- @page_builder ||= Class.new do
158
- def add(_); end
159
- def finish; end
160
- end.new
161
- end
162
-
163
- sub_test_case "guess" do
164
- def task
165
- t = {
166
- type: "zendesk",
167
- login_url: "https://example.zendesk.com/",
168
- auth_method: "token",
169
- username: "foo@example.com",
170
- token: "token",
171
- target: "tickets",
172
- includes: includes,
173
- }
174
- t.delete :includes unless includes
175
- t
176
- end
177
-
178
- def config
179
- Embulk::DataSource.new(task)
180
- end
181
-
182
- def ticket
183
- JSON.parse(fixture_load("tickets.json"))
184
- end
185
-
186
- setup do
187
- @client = Client.new(task)
188
- stub(Client).new { @client }
189
- stub(@client).public_send(anything) do |*args|
190
- args.last.call(ticket)
191
- end
192
- end
193
-
194
- sub_test_case "includes present" do
195
- def includes
196
- %w(audits comments)
197
- end
198
-
199
- test "guessed includes fields" do
200
- actual = Plugin.guess(config)["columns"]
201
- assert actual.include?(name: "audits", type: :json)
202
- assert actual.include?(name: "comments", type: :json)
203
- end
204
- end
205
-
206
- sub_test_case "includes blank" do
207
- def includes
208
- nil
209
- end
210
-
211
- test "not guessed includes fields" do
212
- actual = Plugin.guess(config)["columns"]
213
- assert !actual.include?(name: "audits", type: :json)
214
- assert !actual.include?(name: "comments", type: :json)
215
- end
216
- end
217
- end
218
-
219
- sub_test_case "#run" do
220
- def schema
221
- [
222
- {"name" => "id", "type" => "long"},
223
- {"name" => "tags", "type" => "json"},
224
- ]
225
- end
226
-
227
- def run_task
228
- task.merge({
229
- schema: schema,
230
- retry_limit: 1,
231
- retry_initial_wait_sec: 0,
232
- includes: includes,
233
- })
234
- end
235
-
236
- setup do
237
- @client = Client.new(run_task)
238
- stub(@client).public_send {|*args| args.last.call({}) }
239
- @plugin = Plugin.new(run_task, nil, nil, page_builder)
240
- stub(@plugin).client { @client }
241
- @httpclient = @client.httpclient
242
- stub(@client).httpclient { @httpclient }
243
- end
244
-
245
- sub_test_case "preview" do
246
- setup do
247
- stub(@plugin).preview? { true }
248
- end
249
-
250
- sub_test_case "includes present" do
251
- def includes
252
- %w(foo bar)
253
- end
254
-
255
- test "call fetch_subresource" do
256
- includes.each do |ent|
257
- mock(@client).fetch_subresource(anything, anything, ent)
258
- end
259
- @plugin.run
260
- end
261
- end
262
-
263
- sub_test_case "includes blank" do
264
- def includes
265
- []
266
- end
267
-
268
- test "don't call fetch_subresource" do
269
- mock(@client).fetch_subresource.never
270
- @plugin.run
271
- end
272
- end
273
- end
274
-
275
- sub_test_case "run" do
276
- setup do
277
- stub(@plugin).preview? { false }
278
- end
279
-
280
- sub_test_case "includes present " do
281
- def includes
282
- %w(foo bar)
283
- end
284
-
285
- test "call fetch_subresource" do
286
- includes.each do |ent|
287
- mock(@client).fetch_subresource(anything, anything, ent).at_least(1)
288
- end
289
- @plugin.run
290
- end
291
- end
292
-
293
- sub_test_case "includes blank" do
294
- def includes
295
- []
296
- end
297
-
298
- test "don't call fetch_subresource" do
299
- mock(@client).fetch_subresource.never
300
- @plugin.run
301
- end
302
- end
303
- end
304
- end
305
- end
306
-
307
- sub_test_case "#run" do
308
- def page_builder
309
- @page_builder ||= Object.new
310
- end
311
-
312
- def schema
313
- [
314
- {"name" => "id", "type" => "long"},
315
- {"name" => "tags", "type" => "json"},
316
- ]
317
- end
318
-
319
- def run_task
320
- task.merge({
321
- schema: schema,
322
- retry_limit: 1,
323
- retry_initial_wait_sec: 0,
324
- includes: [],
325
- })
326
- end
327
-
328
- setup do
329
- @client = Client.new(run_task)
330
- stub(Client).new { @client }
331
- @httpclient = @client.httpclient
332
- stub(@client).httpclient { @httpclient }
333
- @plugin = Plugin.new(run_task, nil, nil, page_builder)
334
- end
335
-
336
- sub_test_case "preview" do
337
- setup do
338
- stub(@plugin).preview? { true }
339
- end
340
-
341
- test "call tickets method instead of ticket_all" do
342
- mock(@client).export.never
343
- mock(@client).incremental_export(anything, "tickets", anything, anything, anything, anything) { [] }
344
- mock(page_builder).finish
345
-
346
- @plugin.run
347
- end
348
-
349
- test "task[:schema] columns passed into page_builder.add" do
350
- tickets = [
351
- {"id" => 1, "created_at" => "2000-01-01T00:00:00+0900", "tags" => ["foo"]},
352
- {"id" => 2, "created_at" => "2000-01-01T00:00:00+0900", "tags" => ["foo"]},
353
- ]
354
-
355
- @httpclient.test_loopback_http_response << [
356
- "HTTP/1.1 200",
357
- "Content-Type: application/json",
358
- "",
359
- {
360
- tickets: tickets
361
- }.to_json
362
- ].join("\r\n")
363
-
364
- first_ticket = tickets[0]
365
- second_ticket = tickets[1]
366
- mock(page_builder).add([first_ticket["id"], first_ticket["tags"]])
367
- mock(page_builder).add([second_ticket["id"], second_ticket["tags"]]).never
368
- mock(page_builder).finish
369
-
370
- @plugin.run
371
- end
372
- end
373
-
374
- sub_test_case "run" do
375
- setup do
376
- stub(@plugin).preview? { false }
377
- stub(Embulk).logger { Logger.new(File::NULL) }
378
- end
379
-
380
- test "call ticket_all method instead of tickets" do
381
- mock(@client).export.never
382
- mock(@client).incremental_export(anything, "tickets", 0, true, Set.new, false) { [] }
383
- mock(page_builder).finish
384
-
385
- @plugin.run
386
- end
387
-
388
- test "task[:schema] columns passed into page_builder.add" do
389
- tickets = [
390
- {"id" => 1, "created_at" => "2000-01-01T00:00:00+0900"},
391
- {"id" => 2, "created_at" => "2000-01-01T00:00:00+0900"},
392
- ]
393
-
394
- @httpclient.test_loopback_http_response << [
395
- "HTTP/1.1 200",
396
- "Content-Type: application/json",
397
- "",
398
- {
399
- tickets: tickets,
400
- count: tickets.length,
401
- }.to_json
402
- ].join("\r\n")
403
-
404
- tickets.each do |ticket|
405
- # schema[:columns] is id and tags. tags should be nil
406
- mock(page_builder).add([ticket["id"], nil])
407
- end
408
- mock(page_builder).finish
409
-
410
- @plugin.run
411
- end
412
-
413
- sub_test_case "config diff" do
414
- def end_time
415
- 1234567890
416
- end
417
-
418
- def next_start_time
419
- Time.at(end_time + 1).strftime("%F %T%z")
420
- end
421
-
422
- def start_time
423
- Time.at(1111111111).strftime("%F %T%z")
424
- end
425
-
426
- setup do
427
- events = [
428
- {"id" => 1, "created_at" => "2000-01-01T00:00:00+0900"},
429
- {"id" => 2, "created_at" => "2000-01-01T01:00:00+0900"},
430
- ]
431
-
432
- @httpclient.test_loopback_http_response << [
433
- "HTTP/1.1 200",
434
- "Content-Type: application/json",
435
- "",
436
- {
437
- ticket_events: events,
438
- end_time: end_time,
439
- count: events.length,
440
- }.to_json
441
- ].join("\r\n")
442
- stub(page_builder).add(anything)
443
- stub(page_builder).finish
444
- stub(Embulk).logger { Logger.new(File::NULL) }
445
- end
446
-
447
- sub_test_case "incremental: true" do
448
- def run_task
449
- task.merge(schema: schema, target: "ticket_events", incremental: true, start_time: start_time)
450
- end
451
-
452
- test "task_report contains next start_time" do
453
- report = @plugin.run
454
- assert_equal next_start_time, report[:start_time]
455
- end
456
-
457
- test "no record" do
458
- first_report = @plugin.run
459
-
460
- @httpclient.test_loopback_http_response << [
461
- "HTTP/1.1 200",
462
- "Content-Type: application/json",
463
- "",
464
- {
465
- ticket_events: [],
466
- count: 0,
467
- }.to_json
468
- ].join("\r\n")
469
- second_report = @plugin.run
470
-
471
- assert second_report.has_key?(:start_time)
472
- end
473
- end
474
-
475
- sub_test_case "incremental: false" do
476
- def run_task
477
- task.merge(schema: schema, target: "ticket_events", incremental: false, start_time: start_time)
478
- end
479
-
480
- test "task_report don't contains start_time" do
481
- report = @plugin.run
482
- assert_nil report[:start_time]
483
- end
484
- end
485
- end
486
-
487
- sub_test_case "casting value" do
488
- setup do
489
- stub(Embulk).logger { Logger.new(File::NULL) }
490
- stub(@plugin).preview? { false }
491
- @httpclient.test_loopback_http_response << [
492
- "HTTP/1.1 200",
493
- "Content-Type: application/json",
494
- "",
495
- {
496
- tickets: data,
497
- count: data.length,
498
- }.to_json
499
- ].join("\r\n")
500
- end
501
-
502
- def schema
503
- [
504
- {"name" => "target_l", "type" => "long"},
505
- {"name" => "target_f", "type" => "double"},
506
- {"name" => "target_str", "type" => "string"},
507
- {"name" => "target_bool", "type" => "boolean"},
508
- {"name" => "target_time", "type" => "timestamp"},
509
- {"name" => "target_json", "type" => "json"},
510
- ]
511
- end
512
-
513
- def data
514
- [
515
- {
516
- "id" => 1, "target_l" => "3", "target_f" => "3", "target_str" => "str",
517
- "target_bool" => false, "target_time" => "2000-01-01",
518
- "target_json" => [1,2,3],
519
- },
520
- {
521
- "id" => 2, "target_l" => 4.5, "target_f" => 4.5, "target_str" => 999,
522
- "target_bool" => "truthy", "target_time" => Time.parse("1999-01-01"),
523
- "target_json" => {"foo" => "bar"},
524
- },
525
- {
526
- "id" => 3, "target_l" => nil, "target_f" => nil, "target_str" => nil,
527
- "target_bool" => nil, "target_time" => nil,
528
- "target_json" => nil,
529
- },
530
- ]
531
- end
532
-
533
- test "cast as given type" do
534
- mock(page_builder).add([3, 3.0, "str", false, Time.parse("2000-01-01"), [1,2,3]])
535
- mock(page_builder).add([4, 4.5, "999", true, Time.parse("1999-01-01"), {"foo" => "bar"}])
536
- mock(page_builder).add([nil, nil, nil, nil, nil, nil])
537
- mock(page_builder).finish
538
-
539
- @plugin.run
540
- end
541
- end
542
-
543
- sub_test_case "start_time option not given" do
544
- test "Nothing passed to client" do
545
- stub(page_builder).finish
546
-
547
- mock(@client).tickets(false, 0)
548
- @plugin.run
549
- end
550
- end
551
-
552
- sub_test_case "start_time option given" do
553
- def run_task
554
- task.merge({
555
- start_time: "2000-01-01T00:00:00+0000",
556
- schema: schema,
557
- retry_limit: 1,
558
- retry_initial_wait_sec: 0,
559
- })
560
- end
561
-
562
- test "Passed to client as integer (epoch)" do
563
- stub(page_builder).finish
564
-
565
- start_time = Time.parse(run_task[:start_time]).to_i
566
- mock(@client).tickets(false, start_time)
567
- @plugin.run
568
- end
569
- end
570
- end
571
-
572
- sub_test_case "flush each 10k records" do
573
- setup do
574
- stub(Embulk).logger { Logger.new(File::NULL) }
575
- stub(@plugin).preview? { false }
576
- @httpclient.test_loopback_http_response << [
577
- "HTTP/1.1 200",
578
- "Content-Type: application/json",
579
- "",
580
- {
581
- tickets: (1..20000).map { |i| { 'id' => i } },
582
- count: 20000,
583
- end_time: 0,
584
- }.to_json
585
- ].join("\r\n")
586
- # to stop pagination (count < 1000)
587
- @httpclient.test_loopback_http_response << [
588
- "HTTP/1.1 200",
589
- "Content-Type: application/json",
590
- "",
591
- {
592
- tickets: [{ 'id' => 20001 }],
593
- count: 1,
594
- end_time: 0,
595
- }.to_json
596
- ].join("\r\n")
597
- end
598
-
599
- test "flush is called twice" do
600
- omit("This test is no longer valid, flushing is removed now")
601
- mock(page_builder).add(anything).times(20001)
602
- mock(page_builder).flush.times(2)
603
- mock(page_builder).finish
604
-
605
- @plugin.run
606
- end
607
- end
608
-
609
- end
610
-
611
- def yml
612
- "valid_auth_basic.yml"
613
- end
614
-
615
- def config
616
- conf = YAML.load fixture_load(yml)
617
- Embulk::DataSource.new(conf["in"])
618
- end
619
-
620
- def task
621
- config.to_h.each_with_object({}) do |(k,v), result|
622
- result[k.to_sym] = v
623
- end
624
- end
625
- end
626
- end
627
- end
628
- end