arstotzka 1.0.0
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 +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
|