arstotzka 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +1 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +11 -0
  5. data/.rubocop_todo.yml +20 -0
  6. data/Gemfile +3 -2
  7. data/Guardfile +3 -2
  8. data/README.md +24 -5
  9. data/Rakefile +5 -3
  10. data/arstotzka.gemspec +18 -15
  11. data/arstotzka.jpg +0 -0
  12. data/lib/arstotzka.rb +2 -0
  13. data/lib/arstotzka/builder.rb +109 -47
  14. data/lib/arstotzka/class_methods.rb +34 -0
  15. data/lib/arstotzka/crawler.rb +86 -11
  16. data/lib/arstotzka/exception.rb +7 -2
  17. data/lib/arstotzka/fetcher.rb +93 -36
  18. data/lib/arstotzka/reader.rb +59 -11
  19. data/lib/arstotzka/type_cast.rb +24 -10
  20. data/lib/arstotzka/version.rb +3 -1
  21. data/lib/arstotzka/wrapper.rb +59 -34
  22. data/spec/integration/readme/default_spec.rb +2 -0
  23. data/spec/integration/readme/my_parser_spec.rb +2 -0
  24. data/spec/integration/yard/arstotzka/builder_spec.rb +63 -0
  25. data/spec/integration/yard/arstotzka/class_methods_spec.rb +49 -0
  26. data/spec/integration/yard/arstotzka/crawler_spec.rb +77 -0
  27. data/spec/integration/yard/arstotzka/fetcher_spec.rb +51 -0
  28. data/spec/integration/yard/arstotzka/reader_spec.rb +77 -0
  29. data/spec/integration/yard/arstotzka/wrapper_spec.rb +29 -0
  30. data/spec/lib/arstotzka/builder_spec.rb +6 -4
  31. data/spec/lib/arstotzka/crawler_spec.rb +30 -17
  32. data/spec/lib/arstotzka/fetcher_spec.rb +4 -2
  33. data/spec/lib/arstotzka/reader_spec.rb +13 -11
  34. data/spec/lib/arstotzka/wrapper_spec.rb +10 -10
  35. data/spec/lib/arstotzka_spec.rb +12 -8
  36. data/spec/spec_helper.rb +6 -2
  37. data/spec/support/fixture_helpers.rb +4 -2
  38. data/spec/support/models.rb +4 -3
  39. data/spec/support/models/account.rb +9 -0
  40. data/spec/support/models/arstotzka/dummy.rb +21 -17
  41. data/spec/support/models/arstotzka/fetcher/dummy.rb +7 -2
  42. data/spec/support/models/arstotzka/type_cast.rb +7 -4
  43. data/spec/support/models/arstotzka/wrapper/dummy.rb +10 -5
  44. data/spec/support/models/game.rb +2 -0
  45. data/spec/support/models/house.rb +2 -0
  46. data/spec/support/models/my_model.rb +9 -0
  47. data/spec/support/models/my_parser.rb +6 -5
  48. data/spec/support/models/person.rb +7 -1
  49. data/spec/support/models/star.rb +2 -0
  50. data/spec/support/models/star_gazer.rb +3 -2
  51. data/spec/support/models/transaction.rb +19 -0
  52. data/spec/support/shared_examples/wrapper.rb +6 -5
  53. metadata +64 -15
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Arstotzka::ClassMethods do
6
+ describe 'yard' do
7
+ let!(:instance) { klass.new(hash) }
8
+ let(:klass) do
9
+ Class.new(MyModel) do
10
+ include Arstotzka
11
+ expose :first_name, full_path: 'name.first'
12
+ expose :age, 'cars', type: :integer
13
+ end
14
+ end
15
+ let(:hash) do
16
+ {
17
+ 'name' => { first: 'John', last: 'Williams' },
18
+ :age => '20',
19
+ 'cars' => 2.0
20
+ }
21
+ end
22
+
23
+ describe '#first_name' do
24
+ it 'crawls into the hash to find the value of the first name' do
25
+ expect(instance.first_name).to eq('John')
26
+ end
27
+ end
28
+
29
+ describe '#age' do
30
+ it 'crawls into the hash to find the value of the age' do
31
+ expect(instance.age).to eq(20)
32
+ end
33
+
34
+ it do
35
+ expect(instance.age).to be_a(Integer)
36
+ end
37
+ end
38
+
39
+ describe '#cars' do
40
+ it 'crawls into the hash to find the value of the age' do
41
+ expect(instance.cars).to eq(2)
42
+ end
43
+
44
+ it do
45
+ expect(instance.cars).to be_a(Integer)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Arstotzka::Crawler do
6
+ describe 'yard' do
7
+ subject do
8
+ described_class.new(path: path, **options)
9
+ end
10
+
11
+ let(:options) { {} }
12
+ let(:path) { %w[person information first_name] }
13
+ let(:hash) do
14
+ {
15
+ person: {
16
+ 'information' => {
17
+ 'firstName' => 'John'
18
+ }
19
+ }
20
+ }
21
+ end
22
+
23
+ it 'crawls to find the value' do
24
+ expect(subject.value(hash)).to eq('John')
25
+ end
26
+
27
+ describe '#value' do
28
+ context 'when hash contains the path' do
29
+ it 'crawls to find the value' do
30
+ expect(subject.value(hash)).to eq('John')
31
+ end
32
+ end
33
+
34
+ context 'when we have an array of arrays' do
35
+ let(:path) { %w[companies games hero_name] }
36
+ let(:options) { { compact: true, case_type: :snake } }
37
+ let(:hash) do
38
+ {
39
+ 'companies' => [{
40
+ name: 'Lucas Pope',
41
+ games: [{
42
+ 'name' => 'papers, please'
43
+ }, {
44
+ 'name' => 'TheNextBigThing',
45
+ hero_name: 'Rakhar'
46
+ }]
47
+ }, {
48
+ name: 'Old Company'
49
+ }]
50
+ }
51
+ end
52
+
53
+ it 'crawls to find the value' do
54
+ expect(subject.value(hash)).to eq([['Rakhar']])
55
+ end
56
+
57
+ context 'and we set a default value' do
58
+ let(:options) { { compact: true, case_type: :snake, default: 'NO HERO' } }
59
+
60
+ it 'return default value for missed keys' do
61
+ expect(subject.value(hash)).to eq([['NO HERO', 'Rakhar'], 'NO HERO'])
62
+ end
63
+ end
64
+
65
+ context 'and we give a block' do
66
+ subject do
67
+ described_class.new(path: path, **options) { |value| value&.to_sym }
68
+ end
69
+
70
+ it 'returns the post processed values' do
71
+ expect(subject.value(hash)).to eq([[:Rakhar]])
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Arstotzka::Fetcher do
6
+ describe 'yard' do
7
+ describe '#fetch' do
8
+ subject { described_class.new(hash, instance, path: path, **options) }
9
+
10
+ let(:instance) { Account.new }
11
+ let(:path) { 'transactions' }
12
+ let(:options) do
13
+ {
14
+ clazz: Transaction,
15
+ after: :filter_income
16
+ }
17
+ end
18
+ let(:hash) do
19
+ {
20
+ transactions: [
21
+ { value: 1000.53, type: 'income' },
22
+ { value: 324.56, type: 'outcome' },
23
+ { value: 50.23, type: 'income' },
24
+ { value: 150.00, type: 'outcome' },
25
+ { value: 10.23, type: 'outcome' },
26
+ { value: 100.12, type: 'outcome' },
27
+ { value: 101.00, type: 'outcome' }
28
+ ]
29
+ }
30
+ end
31
+
32
+ describe 'incoming transactions' do
33
+ it 'returns only the income payments' do
34
+ expect(subject.fetch.count).to eq(2)
35
+ end
36
+
37
+ it 'returns Transactions' do
38
+ expect(subject.fetch.map(&:class).uniq).to eq([Transaction])
39
+ end
40
+
41
+ it 'returns results wrapped in Transactions' do
42
+ expected = [
43
+ Transaction.new(value: 1000.53, type: 'income'),
44
+ Transaction.new(value: 50.23, type: 'income')
45
+ ]
46
+ expect(subject.fetch).to eq(expected)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Arstotzka::Reader do
6
+ describe 'yard' do
7
+ subject { described_class.new(path: path, case_type: case_type) }
8
+
9
+ let(:path) { %w[person full_name] }
10
+ let(:case_type) { :snake }
11
+
12
+ describe '#read' do
13
+ let(:hash) do
14
+ {
15
+ full_name: 'John',
16
+ 'Age' => 23,
17
+ 'carCollection' => [
18
+ { maker: 'Ford', 'model' => 'Model A' },
19
+ { maker: 'BMW', 'model' => 'Jetta' }
20
+ ]
21
+ }
22
+ end
23
+
24
+ context 'when using snake_case' do
25
+ it 'fetches the value using snake case key' do
26
+ expect(subject.read(hash, 1)).to eq('John')
27
+ end
28
+
29
+ context 'when key is missing' do
30
+ let(:path) { %w[person car_collection model] }
31
+
32
+ it do
33
+ expect do
34
+ subject.read(hash, 1)
35
+ end.to raise_error(Arstotzka::Exception::KeyNotFound)
36
+ end
37
+ end
38
+ end
39
+
40
+ context 'when using lowerCamel' do
41
+ let(:case_type) { :lower_camel }
42
+ let(:path) { %w[person car_collection model] }
43
+
44
+ it 'fetches the value using lower camel case key' do
45
+ expected = [
46
+ { maker: 'Ford', 'model' => 'Model A' },
47
+ { maker: 'BMW', 'model' => 'Jetta' }
48
+ ]
49
+ expect(subject.read(hash, 1)).to eq(expected)
50
+ end
51
+ end
52
+
53
+ context 'when using UpperCamel' do
54
+ let(:case_type) { :upper_camel }
55
+ let(:path) { %w[person age] }
56
+
57
+ it 'fetches the value using uper camel case key' do
58
+ expect(subject.read(hash, 1)).to eq(23)
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '#ended?' do
64
+ context 'when the fetches have not ended' do
65
+ it do
66
+ expect(subject.ended?(1)).to be_falsey
67
+ end
68
+ end
69
+
70
+ context 'when the fetches have ended' do
71
+ it do
72
+ expect(subject.ended?(2)).to be_truthy
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Arstotzka::Wrapper do
6
+ describe 'yard' do
7
+ describe '#wrap' do
8
+ subject { described_class.new(clazz: clazz, type: type) }
9
+ let(:clazz) { nil }
10
+ let(:type) { nil }
11
+
12
+ context 'when definning clazz' do
13
+ let(:clazz) { Person }
14
+
15
+ it 'returns the valued wrapped in a class' do
16
+ expect(subject.wrap('John')).to eq(Person.new('John'))
17
+ end
18
+ end
19
+
20
+ context 'when defing type' do
21
+ let(:type) { :integer }
22
+
23
+ it 'casts all values' do
24
+ expect(subject.wrap(%w[10 20 30])).to eq([10, 20, 30])
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Arstotzka::Builder do
4
6
  let(:clazz) do
5
7
  Class.new.tap do |c|
6
8
  c.send(:attr_reader, :json)
7
- c.send(:define_method, :initialize) do |json={}|
9
+ c.send(:define_method, :initialize) do |json = {}|
8
10
  @json = json
9
11
  end
10
12
  end
@@ -13,7 +15,7 @@ describe Arstotzka::Builder do
13
15
  let(:options) { {} }
14
16
  let(:name) { 'Robert' }
15
17
  let(:attr_name) { :name }
16
- let(:attr_names) { [ attr_name ] }
18
+ let(:attr_names) { [attr_name] }
17
19
  let(:json) { {} }
18
20
  let(:instance) { clazz.new(json) }
19
21
 
@@ -25,14 +27,14 @@ describe Arstotzka::Builder do
25
27
  it 'adds the reader' do
26
28
  expect do
27
29
  subject.build
28
- end.to change { clazz.new.respond_to?(attr_name) }
30
+ end.to add_method(attr_name).to(clazz)
29
31
  end
30
32
 
31
33
  context 'after building' do
32
34
  before { subject.build }
33
35
 
34
36
  context 'when building several attributes' do
35
- let(:attr_names) { [ :id, :name, :age ] }
37
+ let(:attr_names) { %i[id name age] }
36
38
 
37
39
  it 'adds all the readers' do
38
40
  attr_names.each do |attr|
@@ -1,19 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Arstotzka::Crawler do
4
- let(:subject) do
6
+ subject do
5
7
  described_class.new default_options.merge(options), &block
6
8
  end
7
9
  let(:block) { proc { |v| v } }
8
10
  let(:path) { '' }
9
- let(:default_options) { { path: path, case_type: :lower_camel} }
11
+ let(:default_options) { { path: path, case_type: :lower_camel } }
10
12
  let(:options) { {} }
11
13
  let(:json_file) { 'arstotzka.json' }
12
14
  let(:json) { load_json_fixture_file(json_file) }
13
15
  let(:value) { subject.value(json) }
14
16
 
17
+ context 'when no block is given' do
18
+ let(:path) { %w[user name] }
19
+ subject do
20
+ described_class.new default_options.merge(options)
21
+ end
22
+
23
+ it 'retrieves attribute from base json' do
24
+ expect(value).to eq(json['user']['name'])
25
+ end
26
+ end
27
+
15
28
  context 'when parsing with a path' do
16
- let(:path) { %w(user name) }
29
+ let(:path) { %w[user name] }
17
30
 
18
31
  it 'retrieves attribute from base json' do
19
32
  expect(value).to eq(json['user']['name'])
@@ -29,7 +42,7 @@ describe Arstotzka::Crawler do
29
42
  end
30
43
 
31
44
  context 'crawler finds a nil attribute' do
32
- let(:path) { %w(car model) }
45
+ let(:path) { %w[car model] }
33
46
 
34
47
  it 'returns nil' do
35
48
  expect(value).to be_nil
@@ -42,7 +55,7 @@ describe Arstotzka::Crawler do
42
55
 
43
56
  context 'when there is an array of arrays' do
44
57
  let(:json_file) { 'accounts.json' }
45
- let(:path) { %w(banks accounts balance) }
58
+ let(:path) { %w[banks accounts balance] }
46
59
 
47
60
  it 'returns the values as array of arrays' do
48
61
  expect(value).to eq([[1000.0, 1500.0], [50.0, -500.0]])
@@ -74,7 +87,7 @@ describe Arstotzka::Crawler do
74
87
 
75
88
  context 'when json is empty' do
76
89
  let(:json) { nil }
77
- let(:path) { %w(car model) }
90
+ let(:path) { %w[car model] }
78
91
 
79
92
  it 'returns nil' do
80
93
  expect(value).to be_nil
@@ -94,7 +107,7 @@ describe Arstotzka::Crawler do
94
107
  end
95
108
 
96
109
  context 'when dealing with json inside arrays' do
97
- let(:path) { %w(animals race species name)}
110
+ let(:path) { %w[animals race species name] }
98
111
  let(:expected) do
99
112
  ['European squid', 'Macaque monkey', 'Mexican redknee tarantula']
100
113
  end
@@ -111,7 +124,7 @@ describe Arstotzka::Crawler do
111
124
  context 'with compact option as false' do
112
125
  let(:options) { { compact: false } }
113
126
  before do
114
- json["animals"].last['race'] = nil
127
+ json['animals'].last['race'] = nil
115
128
  end
116
129
  let(:expected) do
117
130
  ['European squid', 'Macaque monkey', nil]
@@ -125,7 +138,7 @@ describe Arstotzka::Crawler do
125
138
  context 'with compact option' do
126
139
  let(:options) { { compact: true } }
127
140
  before do
128
- json["animals"].last['race'] = nil
141
+ json['animals'].last['race'] = nil
129
142
  end
130
143
  let(:expected) do
131
144
  ['European squid', 'Macaque monkey']
@@ -141,7 +154,7 @@ describe Arstotzka::Crawler do
141
154
  context 'with default option' do
142
155
  let(:default_value) { 'NotFound' }
143
156
  let(:options) { { default: default_value } }
144
- let(:path) { %w(projects name) }
157
+ let(:path) { %w[projects name] }
145
158
 
146
159
  context 'when there is a key missing' do
147
160
  it 'returns the default value' do
@@ -163,7 +176,7 @@ describe Arstotzka::Crawler do
163
176
 
164
177
  context 'when the key is not missing but the value is nil' do
165
178
  let(:json_file) { 'person.json' }
166
- let(:path) { %w(user name) }
179
+ let(:path) { %w[user name] }
167
180
 
168
181
  it { expect(value).to be_nil }
169
182
 
@@ -182,7 +195,7 @@ describe Arstotzka::Crawler do
182
195
 
183
196
  context 'when the key last key is missing but the value is nil' do
184
197
  let(:json_file) { 'person.json' }
185
- let(:path) { %w(user nick_name) }
198
+ let(:path) { %w[user nick_name] }
186
199
 
187
200
  it 'returns the default value' do
188
201
  expect(value).to eq(default_value)
@@ -203,7 +216,7 @@ describe Arstotzka::Crawler do
203
216
 
204
217
  context 'when the node is missing but default has the same node' do
205
218
  let(:default_value) { { node: { value: 1 } } }
206
- let(:path) { %w(node node node) }
219
+ let(:path) { %w[node node node] }
207
220
  let(:json) { {} }
208
221
 
209
222
  it 'does not crawl through default value' do
@@ -214,7 +227,7 @@ describe Arstotzka::Crawler do
214
227
 
215
228
  context 'when using a snake case' do
216
229
  let(:json) { { snake_cased: 'snake', snakeCased: 'Camel' }.stringify_keys }
217
- let(:path) { [ 'snake_cased' ] }
230
+ let(:path) { ['snake_cased'] }
218
231
  let(:options) { { case_type: :snake } }
219
232
 
220
233
  it 'fetches from snake cased fields' do
@@ -224,7 +237,7 @@ describe Arstotzka::Crawler do
224
237
 
225
238
  context 'when using a upper camel case' do
226
239
  let(:json) { { UpperCase: 'upper', upperCase: 'lower' }.stringify_keys }
227
- let(:path) { [ 'upper_case' ] }
240
+ let(:path) { ['upper_case'] }
228
241
  let(:options) { { case_type: :upper_camel } }
229
242
 
230
243
  it 'fetches from upper camel cased fields' do
@@ -234,14 +247,14 @@ describe Arstotzka::Crawler do
234
247
 
235
248
  context 'when using a symbol keys' do
236
249
  let(:json) { load_json_fixture_file('arstotzka.json').symbolize_keys }
237
- let(:path) { [ 'id' ] }
250
+ let(:path) { ['id'] }
238
251
 
239
252
  it 'fetches from symbol keys' do
240
253
  expect(value).to eq(json[:id])
241
254
  end
242
255
 
243
256
  context 'crawler finds a nil attribute' do
244
- let(:path) { %w(car model) }
257
+ let(:path) { %w[car model] }
245
258
 
246
259
  it 'returns nil' do
247
260
  expect(value).to be_nil