arstotzka 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +13 -0
  3. data/.gitignore +3 -0
  4. data/.rspec +1 -0
  5. data/Gemfile +8 -0
  6. data/Guardfile +13 -0
  7. data/LICENSE +21 -0
  8. data/README.md +179 -0
  9. data/Rakefile +7 -0
  10. data/arstotzka.gemspec +29 -0
  11. data/docker-compose.yml +18 -0
  12. data/lib/arstotzka.rb +16 -0
  13. data/lib/arstotzka/builder.rb +73 -0
  14. data/lib/arstotzka/class_methods.rb +7 -0
  15. data/lib/arstotzka/crawler.rb +45 -0
  16. data/lib/arstotzka/exception.rb +3 -0
  17. data/lib/arstotzka/fetcher.rb +50 -0
  18. data/lib/arstotzka/reader.rb +44 -0
  19. data/lib/arstotzka/type_cast.rb +15 -0
  20. data/lib/arstotzka/version.rb +3 -0
  21. data/lib/arstotzka/wrapper.rb +36 -0
  22. data/spec/fixtures/accounts.json +27 -0
  23. data/spec/fixtures/accounts_missing.json +23 -0
  24. data/spec/fixtures/arstotzka.json +38 -0
  25. data/spec/fixtures/complete_person.json +9 -0
  26. data/spec/fixtures/person.json +5 -0
  27. data/spec/integration/readme/default_spec.rb +37 -0
  28. data/spec/integration/readme/my_parser_spec.rb +86 -0
  29. data/spec/lib/arstotzka/builder_spec.rb +100 -0
  30. data/spec/lib/arstotzka/crawler_spec.rb +276 -0
  31. data/spec/lib/arstotzka/fetcher_spec.rb +96 -0
  32. data/spec/lib/arstotzka/reader_spec.rb +120 -0
  33. data/spec/lib/arstotzka/wrapper_spec.rb +121 -0
  34. data/spec/lib/arstotzka_spec.rb +129 -0
  35. data/spec/spec_helper.rb +23 -0
  36. data/spec/support/fixture_helpers.rb +19 -0
  37. data/spec/support/models.rb +5 -0
  38. data/spec/support/models/arstotzka/dummy.rb +23 -0
  39. data/spec/support/models/arstotzka/fetcher/dummy.rb +3 -0
  40. data/spec/support/models/arstotzka/type_cast.rb +6 -0
  41. data/spec/support/models/arstotzka/wrapper/dummy.rb +7 -0
  42. data/spec/support/models/game.rb +11 -0
  43. data/spec/support/models/house.rb +11 -0
  44. data/spec/support/models/my_parser.rb +28 -0
  45. data/spec/support/models/person.rb +8 -0
  46. data/spec/support/models/star.rb +7 -0
  47. data/spec/support/models/star_gazer.rb +13 -0
  48. data/spec/support/shared_examples/wrapper.rb +19 -0
  49. 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
@@ -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,5 @@
1
+ models = File.expand_path("spec/support/models/*.rb")
2
+ Dir[models].sort.each do |file|
3
+ autoload file.gsub(/.*\/(.*)\..*/, '\1').camelize.to_sym, file
4
+ end
5
+
@@ -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,3 @@
1
+ class Arstotzka::Fetcher::Dummy
2
+ end
3
+
@@ -0,0 +1,6 @@
1
+ module Arstotzka::TypeCast
2
+ def to_money_float(value)
3
+ value.gsub(/\$ */, '').to_f
4
+ end
5
+ end
6
+
@@ -0,0 +1,7 @@
1
+ class Arstotzka::Wrapper::Dummy
2
+ attr_reader :value
3
+ def initialize(value)
4
+ @value = value
5
+ end
6
+ end
7
+
@@ -0,0 +1,11 @@
1
+ class Game
2
+ include Arstotzka
3
+ include SafeAttributeAssignment
4
+ attr_reader :json
5
+
6
+ expose :name, :publisher
7
+
8
+ def initialize(json)
9
+ @json = json
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class House
2
+ include Arstotzka
3
+ include ::SafeAttributeAssignment
4
+ attr_reader :json
5
+
6
+ expose :age, :value, :floors
7
+
8
+ def initialize(json)
9
+ @json = json
10
+ end
11
+ 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,8 @@
1
+ class Person
2
+ attr_reader :name
3
+
4
+ def initialize(name)
5
+ @name = name
6
+ end
7
+ end
8
+
@@ -0,0 +1,7 @@
1
+ class Star
2
+ attr_reader :name
3
+
4
+ def initialize(name:)
5
+ @name = name
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ class StarGazer
2
+ include Arstotzka
3
+
4
+ expose :favorite_star, full_path: 'universe.star',
5
+ default: { name: 'Sun' }, class: ::Star
6
+
7
+ attr_reader :json
8
+
9
+ def initialize(json = {})
10
+ @json = json
11
+ end
12
+ end
13
+
@@ -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