arstotzka 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +13 -0
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/Gemfile +8 -0
- data/Guardfile +13 -0
- data/LICENSE +21 -0
- data/README.md +179 -0
- data/Rakefile +7 -0
- data/arstotzka.gemspec +29 -0
- data/docker-compose.yml +18 -0
- data/lib/arstotzka.rb +16 -0
- data/lib/arstotzka/builder.rb +73 -0
- data/lib/arstotzka/class_methods.rb +7 -0
- data/lib/arstotzka/crawler.rb +45 -0
- data/lib/arstotzka/exception.rb +3 -0
- data/lib/arstotzka/fetcher.rb +50 -0
- data/lib/arstotzka/reader.rb +44 -0
- data/lib/arstotzka/type_cast.rb +15 -0
- data/lib/arstotzka/version.rb +3 -0
- data/lib/arstotzka/wrapper.rb +36 -0
- data/spec/fixtures/accounts.json +27 -0
- data/spec/fixtures/accounts_missing.json +23 -0
- data/spec/fixtures/arstotzka.json +38 -0
- data/spec/fixtures/complete_person.json +9 -0
- data/spec/fixtures/person.json +5 -0
- data/spec/integration/readme/default_spec.rb +37 -0
- data/spec/integration/readme/my_parser_spec.rb +86 -0
- data/spec/lib/arstotzka/builder_spec.rb +100 -0
- data/spec/lib/arstotzka/crawler_spec.rb +276 -0
- data/spec/lib/arstotzka/fetcher_spec.rb +96 -0
- data/spec/lib/arstotzka/reader_spec.rb +120 -0
- data/spec/lib/arstotzka/wrapper_spec.rb +121 -0
- data/spec/lib/arstotzka_spec.rb +129 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/fixture_helpers.rb +19 -0
- data/spec/support/models.rb +5 -0
- data/spec/support/models/arstotzka/dummy.rb +23 -0
- data/spec/support/models/arstotzka/fetcher/dummy.rb +3 -0
- data/spec/support/models/arstotzka/type_cast.rb +6 -0
- data/spec/support/models/arstotzka/wrapper/dummy.rb +7 -0
- data/spec/support/models/game.rb +11 -0
- data/spec/support/models/house.rb +11 -0
- data/spec/support/models/my_parser.rb +28 -0
- data/spec/support/models/person.rb +8 -0
- data/spec/support/models/star.rb +7 -0
- data/spec/support/models/star_gazer.rb +13 -0
- data/spec/support/shared_examples/wrapper.rb +19 -0
- metadata +229 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Arstotzka::Wrapper do
|
4
|
+
let(:options) { {} }
|
5
|
+
let(:subject) { described_class.new options }
|
6
|
+
let(:hash) { { a: 1 } }
|
7
|
+
|
8
|
+
describe '#wrap' do
|
9
|
+
let(:value) { hash }
|
10
|
+
let(:result) { subject.wrap(value) }
|
11
|
+
|
12
|
+
context 'with default options' do
|
13
|
+
it 'does not change the json value' do
|
14
|
+
expect(result).to eq(value)
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with an array as value' do
|
18
|
+
let(:value) { [hash] }
|
19
|
+
|
20
|
+
it 'does not change the array' do
|
21
|
+
expect(result).to eq(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'with clazz otpion' do
|
27
|
+
let(:options) { { clazz: OpenStruct } }
|
28
|
+
|
29
|
+
it 'creates new instance from given class' do
|
30
|
+
expect(result).to be_a(OpenStruct)
|
31
|
+
expect(result.a).to eq(hash[:a])
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with an array as value' do
|
35
|
+
let(:value) { [hash] }
|
36
|
+
|
37
|
+
it 'returns an array of objects of the given class' do
|
38
|
+
expect(result).to be_a(Array)
|
39
|
+
expect(result.first).to be_a(OpenStruct)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'with type otpion' do
|
45
|
+
let(:type) { :integer }
|
46
|
+
let(:value) { '1' }
|
47
|
+
let(:options) { { type: type } }
|
48
|
+
let(:cast) { result }
|
49
|
+
|
50
|
+
it_behaves_like 'casts basic types'
|
51
|
+
|
52
|
+
context 'when processing an array' do
|
53
|
+
let(:value) { [1.0] }
|
54
|
+
let(:cast) { result.first }
|
55
|
+
|
56
|
+
it_behaves_like 'casts basic types'
|
57
|
+
|
58
|
+
it do
|
59
|
+
expect(result).to be_a(Array)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'with nil value' do
|
64
|
+
let(:value) { nil }
|
65
|
+
|
66
|
+
it do
|
67
|
+
expect(result).to be_nil
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'when passing clazz parameter' do
|
71
|
+
let(:options) { { type: type, clazz: Arstotzka::Wrapper::Dummy } }
|
72
|
+
|
73
|
+
it do
|
74
|
+
expect(result).to be_nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'with blank value' do
|
80
|
+
let(:value) { '' }
|
81
|
+
|
82
|
+
it_behaves_like 'a result that is type cast', {
|
83
|
+
integer: NilClass,
|
84
|
+
float: NilClass,
|
85
|
+
string: String
|
86
|
+
}
|
87
|
+
|
88
|
+
context 'when passing clazz parameter' do
|
89
|
+
let(:options) { { type: type, clazz: Arstotzka::Wrapper::Dummy } }
|
90
|
+
|
91
|
+
it_behaves_like 'a result that is type cast', {
|
92
|
+
integer: NilClass,
|
93
|
+
float: NilClass,
|
94
|
+
string: Arstotzka::Wrapper::Dummy
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when passing clazz parameter' do
|
100
|
+
let(:value) { 1 }
|
101
|
+
let(:options) { { type: type, clazz: Arstotzka::Wrapper::Dummy } }
|
102
|
+
let(:cast) { result.value }
|
103
|
+
|
104
|
+
it_behaves_like 'casts basic types'
|
105
|
+
|
106
|
+
it 'wraps the result inside the given class' do
|
107
|
+
expect(result).to be_a(Arstotzka::Wrapper::Dummy)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'with none for type' do
|
112
|
+
let(:type) { :none }
|
113
|
+
let(:value) { 1.0 }
|
114
|
+
|
115
|
+
it 'does not cast the value' do
|
116
|
+
expect(result).to eq(value)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Arstotzka do
|
4
|
+
let(:dummy) { Arstotzka::Dummy.new(json) }
|
5
|
+
let(:json) { load_json_fixture_file('arstotzka.json') }
|
6
|
+
let(:value) { dummy.public_send(attribute) }
|
7
|
+
|
8
|
+
context 'when parser is configured with no options' do
|
9
|
+
let(:attribute) { :id }
|
10
|
+
|
11
|
+
it 'retrieves attribute from base json' do
|
12
|
+
expect(value).to eq(json['id'])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when parser is configured with a path' do
|
17
|
+
let(:attribute) { :name }
|
18
|
+
|
19
|
+
it 'retrieves attribute from base json' do
|
20
|
+
expect(value).to eq(json['user']['name'])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when configuring full path' do
|
25
|
+
let(:attribute) { :father_name }
|
26
|
+
|
27
|
+
it 'returns nil' do
|
28
|
+
expect(value).to eq(json['father']['name'])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when caching the value' do
|
33
|
+
let(:attribute) { :age }
|
34
|
+
let!(:old_value) { json['age'] }
|
35
|
+
before do
|
36
|
+
dummy.age
|
37
|
+
json['age'] = old_value + 100
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns cached value' do
|
41
|
+
expect(value).to eq(old_value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when wrapping it with a class' do
|
46
|
+
let(:attribute) { :house }
|
47
|
+
|
48
|
+
it 'returns an onject wrap' do
|
49
|
+
expect(value).to be_a(House)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'creates the object with the given json' do
|
53
|
+
expect(value.age).to eq(json['house']['age'])
|
54
|
+
expect(value.value).to eq(json['house']['value'])
|
55
|
+
expect(value.floors).to eq(json['house']['floors'])
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'when dealing with an array' do
|
59
|
+
let(:attribute) { :games }
|
60
|
+
|
61
|
+
it 'returns an array of json wrapped' do
|
62
|
+
expect(value).to be_a(Array)
|
63
|
+
value.each do |object|
|
64
|
+
expect(object).to be_a(Game)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when dealing with multiple level arrays' do
|
69
|
+
let(:attribute) { :games }
|
70
|
+
before do
|
71
|
+
json['games'].map! { |j| [j] }
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns an array of json wrapped' do
|
75
|
+
expect(value).to be_a(Array)
|
76
|
+
value.each do |object|
|
77
|
+
expect(object).to be_a(Array)
|
78
|
+
object.each do |game|
|
79
|
+
expect(game).to be_a(Game)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'when wrapping it with a class and caching' do
|
88
|
+
let(:attribute) { :old_house }
|
89
|
+
let!(:old_value) { json['oldHouse'] }
|
90
|
+
before do
|
91
|
+
dummy.old_house
|
92
|
+
json['oldHouse'] = {}
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'returns an onject wrap' do
|
96
|
+
expect(value).to be_a(House)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'creates the object with the given json' do
|
100
|
+
expect(value.age).not_to eq(json['oldHouse']['age'])
|
101
|
+
expect(value.age).to eq(old_value['age'])
|
102
|
+
expect(value.value).not_to eq(json['oldHouse']['value'])
|
103
|
+
expect(value.value).to eq(old_value['value'])
|
104
|
+
expect(value.floors).not_to eq(json['oldHouse']['floors'])
|
105
|
+
expect(value.floors).to eq(old_value['floors'])
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'when passing an after filter' do
|
110
|
+
let(:attribute) { :games_filtered }
|
111
|
+
|
112
|
+
it 'applies the filter after parsing the json' do
|
113
|
+
expect(value.map(&:publisher)).not_to include('sega')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'when casting the result' do
|
118
|
+
class Arstotzka::Dummy
|
119
|
+
expose :float_value, type: :float
|
120
|
+
end
|
121
|
+
|
122
|
+
let(:json) { { floatValue: '1' } }
|
123
|
+
let(:attribute) { :float_value }
|
124
|
+
|
125
|
+
it do
|
126
|
+
expect(value).to be_a(Float)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter 'spec/support/models/'
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'pry-nav'
|
7
|
+
require 'arstotzka'
|
8
|
+
require 'safe_attribute_assignment'
|
9
|
+
|
10
|
+
support_files = File.expand_path("spec/support/**/*.rb")
|
11
|
+
Dir[support_files].sort.each { |file| require file }
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
15
|
+
config.run_all_when_everything_filtered = true
|
16
|
+
config.filter_run :focus
|
17
|
+
config.filter_run_excluding :integration unless ENV['ALL']
|
18
|
+
|
19
|
+
config.order = 'random'
|
20
|
+
|
21
|
+
config.before do
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
module FixtureHelpers
|
4
|
+
def load_fixture_file(filename)
|
5
|
+
File.read (['./spec/', 'fixtures', filename].join('/'))
|
6
|
+
end
|
7
|
+
|
8
|
+
def load_json_fixture_file(filename)
|
9
|
+
cached_json_fixture_file(filename)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def cached_json_fixture_file(filename)
|
15
|
+
ActiveSupport::JSON.decode(load_fixture_file(filename))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
RSpec.configuration.include FixtureHelpers
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Arstotzka::Dummy
|
2
|
+
include Arstotzka
|
3
|
+
attr_reader :json
|
4
|
+
|
5
|
+
expose :id
|
6
|
+
expose :name, path: 'user'
|
7
|
+
expose :father_name, full_path: 'father.name'
|
8
|
+
expose :age, cached: true
|
9
|
+
expose :house, class: ::House
|
10
|
+
expose :old_house, class: ::House, cached: true
|
11
|
+
expose :games, class: ::Game
|
12
|
+
expose :games_filtered, class: ::Game, after: :filter_games, full_path: 'games'
|
13
|
+
|
14
|
+
def initialize(json)
|
15
|
+
@json = json
|
16
|
+
end
|
17
|
+
|
18
|
+
def filter_games(games)
|
19
|
+
games.select do |g|
|
20
|
+
g.publisher != 'sega'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class MyParser
|
2
|
+
include Arstotzka
|
3
|
+
|
4
|
+
expose :id
|
5
|
+
expose :name, :age, path: :person
|
6
|
+
expose :total_money, full_path: 'accounts.balance', after: :sum,
|
7
|
+
cached: true, type: :money_float
|
8
|
+
expose :total_owed, full_path: 'loans.value', after: :sum,
|
9
|
+
cached: true, type: :money_float
|
10
|
+
|
11
|
+
attr_reader :json
|
12
|
+
|
13
|
+
def initialize(json = {})
|
14
|
+
@json = json
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def sum(balances)
|
20
|
+
balances.sum if balances
|
21
|
+
end
|
22
|
+
|
23
|
+
models = File.expand_path("spec/support/models/my_parser/*.rb")
|
24
|
+
Dir[models].each do |file|
|
25
|
+
autoload file.gsub(/.*\/(.*)\..*/, '\1').camelize.to_sym, file
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
shared_context 'a result that is type cast' do |types|
|
2
|
+
types.each do |type, clazz|
|
3
|
+
context "with #{type} type" do
|
4
|
+
let(:type) { type }
|
5
|
+
|
6
|
+
it do
|
7
|
+
expect(cast).to be_a(clazz)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
shared_context 'casts basic types' do
|
14
|
+
it_behaves_like 'a result that is type cast', {
|
15
|
+
integer: Integer,
|
16
|
+
float: Float,
|
17
|
+
string: String
|
18
|
+
}
|
19
|
+
end
|