arstotzka 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +1 -0
- data/.gitignore +2 -1
- data/.rubocop.yml +11 -0
- data/.rubocop_todo.yml +20 -0
- data/Gemfile +3 -2
- data/Guardfile +3 -2
- data/README.md +24 -5
- data/Rakefile +5 -3
- data/arstotzka.gemspec +18 -15
- data/arstotzka.jpg +0 -0
- data/lib/arstotzka.rb +2 -0
- data/lib/arstotzka/builder.rb +109 -47
- data/lib/arstotzka/class_methods.rb +34 -0
- data/lib/arstotzka/crawler.rb +86 -11
- data/lib/arstotzka/exception.rb +7 -2
- data/lib/arstotzka/fetcher.rb +93 -36
- data/lib/arstotzka/reader.rb +59 -11
- data/lib/arstotzka/type_cast.rb +24 -10
- data/lib/arstotzka/version.rb +3 -1
- data/lib/arstotzka/wrapper.rb +59 -34
- data/spec/integration/readme/default_spec.rb +2 -0
- data/spec/integration/readme/my_parser_spec.rb +2 -0
- data/spec/integration/yard/arstotzka/builder_spec.rb +63 -0
- data/spec/integration/yard/arstotzka/class_methods_spec.rb +49 -0
- data/spec/integration/yard/arstotzka/crawler_spec.rb +77 -0
- data/spec/integration/yard/arstotzka/fetcher_spec.rb +51 -0
- data/spec/integration/yard/arstotzka/reader_spec.rb +77 -0
- data/spec/integration/yard/arstotzka/wrapper_spec.rb +29 -0
- data/spec/lib/arstotzka/builder_spec.rb +6 -4
- data/spec/lib/arstotzka/crawler_spec.rb +30 -17
- data/spec/lib/arstotzka/fetcher_spec.rb +4 -2
- data/spec/lib/arstotzka/reader_spec.rb +13 -11
- data/spec/lib/arstotzka/wrapper_spec.rb +10 -10
- data/spec/lib/arstotzka_spec.rb +12 -8
- data/spec/spec_helper.rb +6 -2
- data/spec/support/fixture_helpers.rb +4 -2
- data/spec/support/models.rb +4 -3
- data/spec/support/models/account.rb +9 -0
- data/spec/support/models/arstotzka/dummy.rb +21 -17
- data/spec/support/models/arstotzka/fetcher/dummy.rb +7 -2
- data/spec/support/models/arstotzka/type_cast.rb +7 -4
- data/spec/support/models/arstotzka/wrapper/dummy.rb +10 -5
- data/spec/support/models/game.rb +2 -0
- data/spec/support/models/house.rb +2 -0
- data/spec/support/models/my_model.rb +9 -0
- data/spec/support/models/my_parser.rb +6 -5
- data/spec/support/models/person.rb +7 -1
- data/spec/support/models/star.rb +2 -0
- data/spec/support/models/star_gazer.rb +3 -2
- data/spec/support/models/transaction.rb +19 -0
- data/spec/support/shared_examples/wrapper.rb +6 -5
- 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) { [
|
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
|
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) { [
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
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[
|
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[
|
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
|
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
|
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
|
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
|
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) { [
|
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) { [
|
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) { [
|
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
|
257
|
+
let(:path) { %w[car model] }
|
245
258
|
|
246
259
|
it 'returns nil' do
|
247
260
|
expect(value).to be_nil
|