arstotzka 1.2.0 → 1.2.1
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 +4 -4
- data/.rubocop.yml +4 -0
- data/README.md +12 -11
- data/lib/arstotzka.rb +2 -0
- data/lib/arstotzka/fetcher.rb +10 -13
- data/lib/arstotzka/fetcher_builder.rb +1 -1
- data/lib/arstotzka/options.rb +273 -26
- data/lib/arstotzka/version.rb +1 -1
- data/lib/arstotzka/wrapper.rb +28 -2
- data/spec/integration/yard/arstotzka/fetcher_spec.rb +5 -4
- data/spec/integration/yard/arstotzka/options_spec.rb +175 -0
- data/spec/lib/arstotzka/fetcher_spec.rb +53 -8
- data/spec/support/models/application.rb +24 -0
- data/spec/support/models/bar.rb +21 -0
- data/spec/support/models/customer.rb +20 -0
- data/spec/support/models/drink.rb +20 -0
- data/spec/support/models/group.rb +13 -0
- data/spec/support/models/job_seeker.rb +13 -0
- data/spec/support/models/shopping_mall.rb +16 -0
- data/spec/support/models/store.rb +19 -0
- metadata +20 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fcdfefb20e55716afccbdb1db14cb24c1b23f5c3609e0cdaccfb897e6f949c32
|
|
4
|
+
data.tar.gz: f374f9f717e7f3b867d40270bc531eb3c22e54b9060f232e5984157c4039e612
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c2b0cce20c80bdfdc92bfd3dbb0eeff17585e7c2373daa5cfb960bf9356b54feb5d5c9ea66353a52f9b6400720e3fdcc2d27529f5456b9b2c6f0f50a066adc7
|
|
7
|
+
data.tar.gz: 2996ef066a6d98673f10961f82962e9d570fc23a34882d3a5d280efdb93677425eac4a52b0786551c324d04b296333b0c551603a5fd8923b99e61f81229ccf8e
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
|
@@ -38,7 +38,7 @@ gem 'arstotzka'
|
|
|
38
38
|
|
|
39
39
|
Yard Documentation
|
|
40
40
|
-------------------
|
|
41
|
-
https://www.rubydoc.info/gems/arstotzka/1.2.
|
|
41
|
+
https://www.rubydoc.info/gems/arstotzka/1.2.1
|
|
42
42
|
|
|
43
43
|
Getting Started
|
|
44
44
|
---------------
|
|
@@ -94,17 +94,18 @@ MyParser.new.name # returns nil
|
|
|
94
94
|
|
|
95
95
|
Options
|
|
96
96
|
-------
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
- compact: indicator telling to ignore nil values inside array (false by default)
|
|
103
|
-
- flatten: indicator telling that to flattern the resulting array (false by default)
|
|
104
|
-
- after: name of a method to be called after with the resulting value
|
|
105
|
-
- case: case of the keys from the json (camel by default)
|
|
106
|
-
- type: Type that the value must be cast into ([TypeCast](#typecast))
|
|
97
|
+
- after: Name of a method to be called after on the values returned
|
|
98
|
+
- after_each: Name of a method to be called after each result
|
|
99
|
+
- cached: Indicator that, once the value has been fetched, it should be cached (false by default)
|
|
100
|
+
- case: Case of the keys from the json (lower_camel by default)
|
|
101
|
+
- compact: Indicator telling to ignore nil values inside array (false by default)
|
|
107
102
|
- default: Default value (prior to casting and wrapping, see [Default](#default))
|
|
103
|
+
- flatten: Indicator telling that to flattern the resulting array (false by default)
|
|
104
|
+
- full_path: Full path to fetch the value (empty by default)
|
|
105
|
+
- klass: Class to be used when wrapping the final value
|
|
106
|
+
- json: Method that contains the hash to be parsed (json by default)
|
|
107
|
+
- path: Path where to find the sub hash that contains the key (empty by default)
|
|
108
|
+
- type: Type that the value must be cast into ([TypeCast](#typecast))
|
|
108
109
|
|
|
109
110
|
## TypeCast
|
|
110
111
|
The type casting, when the option `type` is passed, is done through the `Arstotzka::TypeCast` which can
|
data/lib/arstotzka.rb
CHANGED
data/lib/arstotzka/fetcher.rb
CHANGED
|
@@ -10,17 +10,13 @@ module Arstotzka
|
|
|
10
10
|
|
|
11
11
|
# Creates an instance of Artotzka::Fetcher
|
|
12
12
|
#
|
|
13
|
-
# @
|
|
14
|
-
#
|
|
15
|
-
# @overload iniitalize(instance, options_hash = {})
|
|
13
|
+
# @overload iniitalize(options_hash = {})
|
|
16
14
|
# @param options_hash [Hash] options for {Crawler}, {Wrapper} and {Reader}
|
|
17
15
|
#
|
|
18
|
-
# @overload iniitalize(
|
|
16
|
+
# @overload iniitalize(options)
|
|
19
17
|
# @param options [Arstotzka::Options] options for {Crawler}, {Wrapper} and {Reader}
|
|
20
|
-
def initialize(
|
|
18
|
+
def initialize(options_hash = {})
|
|
21
19
|
self.options = options_hash
|
|
22
|
-
|
|
23
|
-
@instance = instance
|
|
24
20
|
end
|
|
25
21
|
|
|
26
22
|
# Crawls the hash for the value
|
|
@@ -72,10 +68,11 @@ module Arstotzka
|
|
|
72
68
|
#
|
|
73
69
|
# instance = Account.new
|
|
74
70
|
#
|
|
75
|
-
# fetcher = Arstotzka::Fetcher.new(
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
71
|
+
# fetcher = Arstotzka::Fetcher.new(
|
|
72
|
+
# instance: instance,
|
|
73
|
+
# path: 'transactions',
|
|
74
|
+
# klass: Transaction,
|
|
75
|
+
# after: :filter_income
|
|
79
76
|
# )
|
|
80
77
|
#
|
|
81
78
|
# fetcher.fetch # retruns [
|
|
@@ -93,7 +90,7 @@ module Arstotzka
|
|
|
93
90
|
|
|
94
91
|
# @private
|
|
95
92
|
attr_reader :instance, :options
|
|
96
|
-
delegate :after, :flatten, to: :options
|
|
93
|
+
delegate :instance, :after, :flatten, to: :options
|
|
97
94
|
delegate :wrap, to: :wrapper
|
|
98
95
|
|
|
99
96
|
def hash
|
|
@@ -121,7 +118,7 @@ module Arstotzka
|
|
|
121
118
|
#
|
|
122
119
|
# @return [Arstotzka::Wrapper] the wrapper
|
|
123
120
|
def wrapper
|
|
124
|
-
@wrapper ||= Wrapper.new(options)
|
|
121
|
+
@wrapper ||= Wrapper.new(options.merge(instance: instance))
|
|
125
122
|
end
|
|
126
123
|
end
|
|
127
124
|
end
|
data/lib/arstotzka/options.rb
CHANGED
|
@@ -4,48 +4,295 @@ module Arstotzka
|
|
|
4
4
|
# @api private
|
|
5
5
|
#
|
|
6
6
|
# Class responsible to hold the options
|
|
7
|
+
#
|
|
8
|
+
# Options is initialized and merged with {DEFAULT_OPTIONS}
|
|
9
|
+
# when using {ClassMethods#expose}
|
|
10
|
+
#
|
|
11
|
+
# @example Using options klass and after
|
|
12
|
+
# class Customer
|
|
13
|
+
# attr_reader :name, :age
|
|
14
|
+
#
|
|
15
|
+
# def initialize(name:, age:)
|
|
16
|
+
# @name = name
|
|
17
|
+
# @age = age
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# def adult?
|
|
21
|
+
# age >= 18
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# class Store
|
|
26
|
+
# include Arstotzka
|
|
27
|
+
#
|
|
28
|
+
# expose :customers, klass: Customer, after: :filter_adults
|
|
29
|
+
#
|
|
30
|
+
# def initialize(json)
|
|
31
|
+
# @json = json
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# private
|
|
35
|
+
#
|
|
36
|
+
# attr_reader :json
|
|
37
|
+
#
|
|
38
|
+
# def filter_adults(values)
|
|
39
|
+
# values.select(&:adult?)
|
|
40
|
+
# end
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# hash = {
|
|
44
|
+
# customers: [{
|
|
45
|
+
# name: 'John', age: 21
|
|
46
|
+
# }, {
|
|
47
|
+
# name: 'Julia', age: 15
|
|
48
|
+
# }, {
|
|
49
|
+
# name: 'Carol', age: 22
|
|
50
|
+
# }, {
|
|
51
|
+
# name: 'Bobby', age: 12
|
|
52
|
+
# }]
|
|
53
|
+
# }
|
|
54
|
+
#
|
|
55
|
+
# instance = Store.new(hash)
|
|
56
|
+
#
|
|
57
|
+
# instance.customers # returns [
|
|
58
|
+
# # Customer.new(name: 'John', age: 21),
|
|
59
|
+
# # Customer.new(name: 'Carol', age: 22)
|
|
60
|
+
# # ]
|
|
61
|
+
#
|
|
62
|
+
# @example Using type with klass and after_each
|
|
63
|
+
# module Arstotzka::TypeCast
|
|
64
|
+
# def to_symbolized_hash(value)
|
|
65
|
+
# value.symbolize_keys
|
|
66
|
+
# end
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
# class Drink
|
|
70
|
+
# attr_reader :name, :price
|
|
71
|
+
#
|
|
72
|
+
# def initialize(name:, price:)
|
|
73
|
+
# @name = name
|
|
74
|
+
# @price = price
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# def inflate(inflation)
|
|
78
|
+
# @price = (price * (1 + inflation)).round(2)
|
|
79
|
+
# end
|
|
80
|
+
# end
|
|
81
|
+
#
|
|
82
|
+
# class Bar
|
|
83
|
+
# include Arstotzka
|
|
84
|
+
#
|
|
85
|
+
# expose :drinks, type: :symbolized_hash,
|
|
86
|
+
# klass: Drink, after_each: :add_inflation
|
|
87
|
+
#
|
|
88
|
+
# def initialize(json)
|
|
89
|
+
# @json = json
|
|
90
|
+
# end
|
|
91
|
+
#
|
|
92
|
+
# private
|
|
93
|
+
#
|
|
94
|
+
# attr_reader :json
|
|
95
|
+
#
|
|
96
|
+
# def add_inflation(drink)
|
|
97
|
+
# drink.inflate(0.1)
|
|
98
|
+
# drink
|
|
99
|
+
# end
|
|
100
|
+
# end
|
|
101
|
+
#
|
|
102
|
+
# json = '{"drinks":[{"name":"tequila","price":7.50},{ "name":"vodka","price":5.50}]}'
|
|
103
|
+
#
|
|
104
|
+
# hash = JSON.parse(hash)
|
|
105
|
+
#
|
|
106
|
+
# instance = Bar.new(hash)
|
|
107
|
+
#
|
|
108
|
+
# instance.drinks # returns [
|
|
109
|
+
# # Drink.new(name: 'tequila', price: 8.25),
|
|
110
|
+
# # Drink.new(name: 'vodka', price: 6.05)
|
|
111
|
+
# # ]
|
|
112
|
+
#
|
|
113
|
+
# @example Using cached, compact, after and full_path
|
|
114
|
+
# class Person
|
|
115
|
+
# attr_reader :name
|
|
116
|
+
#
|
|
117
|
+
# def initialize(name)
|
|
118
|
+
# @name = name
|
|
119
|
+
# end
|
|
120
|
+
# end
|
|
121
|
+
#
|
|
122
|
+
# class Application
|
|
123
|
+
# include Arstotzka
|
|
124
|
+
#
|
|
125
|
+
# expose :users, full_path: 'users.first_name',
|
|
126
|
+
# compact: true, cached: true,
|
|
127
|
+
# after: :create_person
|
|
128
|
+
#
|
|
129
|
+
# def initialize(json)
|
|
130
|
+
# @json = json
|
|
131
|
+
# end
|
|
132
|
+
#
|
|
133
|
+
# private
|
|
134
|
+
#
|
|
135
|
+
# attr_reader :json
|
|
136
|
+
#
|
|
137
|
+
# def create_person(names)
|
|
138
|
+
# names.map do |name|
|
|
139
|
+
# warn "Creating person #{name}"
|
|
140
|
+
# Person.new(name)
|
|
141
|
+
# end
|
|
142
|
+
# end
|
|
143
|
+
# end
|
|
144
|
+
#
|
|
145
|
+
# # Keys are on camel case (lower camel case)
|
|
146
|
+
# hash = {
|
|
147
|
+
# users: [
|
|
148
|
+
# { firstName: 'Lucy', email: 'lucy@gmail.com' },
|
|
149
|
+
# { firstName: 'Bobby', email: 'bobby@hotmail.com' },
|
|
150
|
+
# { email: 'richard@tracy.com' },
|
|
151
|
+
# { firstName: 'Arthur', email: 'arthur@kamelot.uk' }
|
|
152
|
+
# ]
|
|
153
|
+
# }
|
|
154
|
+
#
|
|
155
|
+
# instance = Application.new(hash)
|
|
156
|
+
#
|
|
157
|
+
# instance.users # trigers the warn "Creating person <name>" 3 times
|
|
158
|
+
# # returns [
|
|
159
|
+
# # Person.new('Lucy'),
|
|
160
|
+
# # Person.new('Bobby'),
|
|
161
|
+
# # Person.new('Arthur')
|
|
162
|
+
# # ]
|
|
163
|
+
# instance.users # returns the same value, without triggering warn
|
|
164
|
+
#
|
|
165
|
+
# @example Working with snake case hash
|
|
166
|
+
# class JobSeeker
|
|
167
|
+
# include Arstotzka
|
|
168
|
+
#
|
|
169
|
+
# expose :applicants, case: :snake, default: 'John Doe',
|
|
170
|
+
# full_path: 'applicants.full_name',
|
|
171
|
+
# compact: true, json: :@hash
|
|
172
|
+
#
|
|
173
|
+
# def initialize(hash)
|
|
174
|
+
# @hash = hash
|
|
175
|
+
# end
|
|
176
|
+
# end
|
|
177
|
+
#
|
|
178
|
+
# hash = {
|
|
179
|
+
# 'applicants' => [
|
|
180
|
+
# {
|
|
181
|
+
# 'full_name' => 'Robert Hatz',
|
|
182
|
+
# 'email' => 'robert.hatz@gmail.com'
|
|
183
|
+
# }, {
|
|
184
|
+
# 'full_name' => 'Marina Wantz',
|
|
185
|
+
# 'email' => 'marina.wantz@gmail.com'
|
|
186
|
+
# }, {
|
|
187
|
+
# 'email' => 'albert.witz@gmail.com'
|
|
188
|
+
# }
|
|
189
|
+
# ]
|
|
190
|
+
# }
|
|
191
|
+
#
|
|
192
|
+
# instance = JobSeeker.new(hash)
|
|
193
|
+
#
|
|
194
|
+
# instance.applicants # returns [
|
|
195
|
+
# # 'Robert Hatz',
|
|
196
|
+
# # 'Marina Wantz',
|
|
197
|
+
# # 'John Doe'
|
|
198
|
+
# # ]
|
|
199
|
+
#
|
|
200
|
+
# @example Deep path with flatten option
|
|
201
|
+
# class ShoppingMall
|
|
202
|
+
# include Arstotzka
|
|
203
|
+
#
|
|
204
|
+
# expose :customers, path: 'floors.stores',
|
|
205
|
+
# flatten: true, json: :hash
|
|
206
|
+
#
|
|
207
|
+
# def initialize(hash)
|
|
208
|
+
# @hash = hash
|
|
209
|
+
# end
|
|
210
|
+
#
|
|
211
|
+
# private
|
|
212
|
+
#
|
|
213
|
+
# attr_reader :hash
|
|
214
|
+
# end
|
|
215
|
+
#
|
|
216
|
+
# hash = {
|
|
217
|
+
# floors: [{
|
|
218
|
+
# name: 'ground', stores: [{
|
|
219
|
+
# name: 'Starbucks', customers: %w[
|
|
220
|
+
# John Bobby Maria
|
|
221
|
+
# ]
|
|
222
|
+
# }, {
|
|
223
|
+
# name: 'Pizza Hut', customers: %w[
|
|
224
|
+
# Danny LJ
|
|
225
|
+
# ]
|
|
226
|
+
# }]
|
|
227
|
+
# }, {
|
|
228
|
+
# name: 'first', stores: [{
|
|
229
|
+
# name: 'Disney', customers: %w[
|
|
230
|
+
# Robert Richard
|
|
231
|
+
# ]
|
|
232
|
+
# }, {
|
|
233
|
+
# name: 'Comix', customers: %w[
|
|
234
|
+
# Linda Ariel
|
|
235
|
+
# ]
|
|
236
|
+
# }]
|
|
237
|
+
# }]
|
|
238
|
+
# }
|
|
239
|
+
#
|
|
240
|
+
# instance = ShoppingMall.new(hash)
|
|
241
|
+
#
|
|
242
|
+
# instance.customers # returns %w[
|
|
243
|
+
# # John Bobby Maria
|
|
244
|
+
# # Danny LJ Robert Richard
|
|
245
|
+
# # Linda Ariel
|
|
246
|
+
# # ]
|
|
7
247
|
class Options < ::OpenStruct
|
|
8
248
|
DEFAULT_OPTIONS = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
249
|
+
after: false,
|
|
250
|
+
after_each: nil,
|
|
251
|
+
cached: false,
|
|
252
|
+
case: :lower_camel,
|
|
253
|
+
compact: false,
|
|
254
|
+
default: nil,
|
|
255
|
+
flatten: false,
|
|
256
|
+
full_path: nil,
|
|
257
|
+
json: :json,
|
|
258
|
+
klass: nil,
|
|
259
|
+
path: nil,
|
|
260
|
+
type: :none
|
|
20
261
|
}.freeze
|
|
21
262
|
|
|
22
263
|
# Creates a new instance of Options
|
|
23
264
|
#
|
|
24
265
|
# @param options [Hash] options hash
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# @option options [String,Symbol]
|
|
30
|
-
# value
|
|
31
|
-
#
|
|
266
|
+
#
|
|
267
|
+
# Options hash are initialized and merged with {DEFAULT_OPTIONS}
|
|
268
|
+
# when using {ClassMethods#expose}
|
|
269
|
+
#
|
|
270
|
+
# @option options [String,Symbol] after: {Fetcher} option with the name of the method to be
|
|
271
|
+
# called once the value is fetched for mapping the value
|
|
272
|
+
#
|
|
273
|
+
# @option options [String,Symbol] after_each: {Wrapper} option with method that will be called
|
|
274
|
+
# on each individual result (while after is called on the whole collection)
|
|
32
275
|
# @option options [Boolean] cached: flag if the result should be memorized instead of repeating
|
|
33
276
|
# the crawling
|
|
34
|
-
# @option options [String,Symbol] case:
|
|
277
|
+
# @option options [String,Symbol] case: {Reader} flag definining on which case will
|
|
35
278
|
# the keys be defined
|
|
36
279
|
# - lower_camel: keys in the hash are lowerCamelCase
|
|
37
280
|
# - upper_camel: keys in the hash are UpperCamelCase
|
|
38
281
|
# - snake: keys in the hash are snake_case
|
|
39
|
-
# @option options [Boolean] compact:
|
|
282
|
+
# @option options [Boolean] compact: {Crawler} flag to apply Array#compact thus
|
|
40
283
|
# removing nil results
|
|
41
|
-
# @option options [
|
|
42
|
-
#
|
|
43
|
-
# @option options [String,Symbol] after: {Fetcher} option with the name of the method to be
|
|
44
|
-
# called once the value is fetched for mapping the value
|
|
45
|
-
# @option options [Boolean] flatten: {Fetcher} flag to aplly Array#flatten thus
|
|
284
|
+
# @option options [Boolean] default: {Crawler} option to return default value instead of nil
|
|
285
|
+
# @option options [Boolean] flatten: {Fetcher} flag to aplly Array#flatten thus
|
|
46
286
|
# avoing nested arrays
|
|
287
|
+
# @option options [String,Symbol] full_path: path of hash attributes to find exacttly where the
|
|
288
|
+
# value live (ignoring the attribute name)
|
|
289
|
+
# @option options [Class] klass: {Fetcher} option that, when passed, wraps the individual
|
|
290
|
+
# results in an instance of the given class
|
|
291
|
+
# @option options [String,Symbol] json: name of the method containing the hash to be crawled
|
|
292
|
+
# @option options [String,Symbol] path path of hash attributes to find the root
|
|
293
|
+
# where the attribute live (then fetching it using the attribute name)
|
|
47
294
|
# @option options [String,Symbol] type: {Fetcher} option declaring the type of the returned
|
|
48
|
-
# value (to use casting)
|
|
295
|
+
# value (to use casting) (see {TypeCast})
|
|
49
296
|
# - integer
|
|
50
297
|
# - string
|
|
51
298
|
# - float
|
data/lib/arstotzka/version.rb
CHANGED
data/lib/arstotzka/wrapper.rb
CHANGED
|
@@ -74,10 +74,12 @@ module Arstotzka
|
|
|
74
74
|
#
|
|
75
75
|
# @return [Object]
|
|
76
76
|
def wrap_element(value)
|
|
77
|
-
value = cast(value)
|
|
77
|
+
value = cast(value)
|
|
78
78
|
return if value.nil?
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
value = wrap_in_class(value)
|
|
81
|
+
|
|
82
|
+
after(value)
|
|
81
83
|
end
|
|
82
84
|
|
|
83
85
|
# @private
|
|
@@ -108,7 +110,31 @@ module Arstotzka
|
|
|
108
110
|
#
|
|
109
111
|
# @return [Object]
|
|
110
112
|
def cast(value)
|
|
113
|
+
return if value.nil?
|
|
114
|
+
return value unless type?
|
|
115
|
+
|
|
111
116
|
public_send("to_#{type}", value)
|
|
112
117
|
end
|
|
118
|
+
|
|
119
|
+
# @private
|
|
120
|
+
#
|
|
121
|
+
# Wrap resulting value in class
|
|
122
|
+
#
|
|
123
|
+
# @return [Object] instance of +options.klass+
|
|
124
|
+
def wrap_in_class(value)
|
|
125
|
+
return value unless klass
|
|
126
|
+
klass.new(value)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @private
|
|
130
|
+
#
|
|
131
|
+
# Process and wrap value trhough a method call
|
|
132
|
+
#
|
|
133
|
+
# @return [Object] result of method call
|
|
134
|
+
def after(value)
|
|
135
|
+
return value unless options.after_each
|
|
136
|
+
|
|
137
|
+
options.instance.send(options.after_each, value)
|
|
138
|
+
end
|
|
113
139
|
end
|
|
114
140
|
end
|
|
@@ -5,14 +5,15 @@ require 'spec_helper'
|
|
|
5
5
|
describe Arstotzka::Fetcher do
|
|
6
6
|
describe 'yard' do
|
|
7
7
|
describe '#fetch' do
|
|
8
|
-
subject(:fetcher) { described_class.new(
|
|
8
|
+
subject(:fetcher) { described_class.new(**options) }
|
|
9
9
|
|
|
10
10
|
let(:instance) { Account.new(hash) }
|
|
11
11
|
let(:options) do
|
|
12
12
|
{
|
|
13
|
-
path:
|
|
14
|
-
klass:
|
|
15
|
-
after:
|
|
13
|
+
path: 'transactions',
|
|
14
|
+
klass: Transaction,
|
|
15
|
+
after: :filter_income,
|
|
16
|
+
instance: instance
|
|
16
17
|
}
|
|
17
18
|
end
|
|
18
19
|
let(:hash) do
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Arstotzka::Options do
|
|
6
|
+
subject(:instance) { clazz.new(hash) }
|
|
7
|
+
|
|
8
|
+
describe 'yard' do
|
|
9
|
+
describe 'Using options klass and after' do
|
|
10
|
+
let(:hash) do
|
|
11
|
+
{
|
|
12
|
+
customers: [{
|
|
13
|
+
name: 'John', age: 21
|
|
14
|
+
}, {
|
|
15
|
+
name: 'Julia', age: 15
|
|
16
|
+
}, {
|
|
17
|
+
name: 'Carol', age: 22
|
|
18
|
+
}, {
|
|
19
|
+
name: 'Bobby', age: 12
|
|
20
|
+
}]
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
let(:clazz) { Store }
|
|
25
|
+
let(:expected) do
|
|
26
|
+
[
|
|
27
|
+
Customer.new(name: 'John', age: 21),
|
|
28
|
+
Customer.new(name: 'Carol', age: 22)
|
|
29
|
+
]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'returns only the adults' do
|
|
33
|
+
expect(instance.customers).to eq(expected)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe 'type with klass and after_each' do
|
|
38
|
+
let(:clazz) { Bar }
|
|
39
|
+
let(:hash) { JSON.parse(json) }
|
|
40
|
+
|
|
41
|
+
let(:json) do
|
|
42
|
+
'{"drinks":[{"name":"tequila","price":7.50},{ "name":"vodka","price":5.50}]}'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
let(:expected) do
|
|
46
|
+
[
|
|
47
|
+
Drink.new(name: 'tequila', price: 8.25),
|
|
48
|
+
Drink.new(name: 'vodka', price: 6.05)
|
|
49
|
+
]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
before do
|
|
53
|
+
module Arstotzka::TypeCast
|
|
54
|
+
def to_symbolized_hash(value)
|
|
55
|
+
value.symbolize_keys
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'returns inflated drinks' do
|
|
61
|
+
expect(instance.drinks).to eq(expected)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe 'Using cached, compact, after and full_path' do
|
|
66
|
+
let(:clazz) { Application }
|
|
67
|
+
|
|
68
|
+
let(:hash) do
|
|
69
|
+
{
|
|
70
|
+
users: [
|
|
71
|
+
{ firstName: 'Lucy', email: 'lucy@gmail.com' },
|
|
72
|
+
{ firstName: 'Bobby', email: 'bobby@hotmail.com' },
|
|
73
|
+
{ email: 'richard@tracy.com' },
|
|
74
|
+
{ firstName: 'Arthur', email: 'arthur@kamelot.uk' }
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
let(:expected) do
|
|
80
|
+
[
|
|
81
|
+
Person.new('Lucy'),
|
|
82
|
+
Person.new('Bobby'),
|
|
83
|
+
Person.new('Arthur')
|
|
84
|
+
]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
before do
|
|
88
|
+
# rubocop:disable RSpec/SubjectStub
|
|
89
|
+
allow(instance).to receive(:warn)
|
|
90
|
+
# rubocop:enable RSpec/SubjectStub
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'Returns created users' do
|
|
94
|
+
expect(instance.users).to eq(expected)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'Triggers warn 3 times' do
|
|
98
|
+
instance.users
|
|
99
|
+
instance.users
|
|
100
|
+
expect(instance).to have_received(:warn).exactly(3).times
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
describe 'working with snake case hash' do
|
|
105
|
+
let(:clazz) { JobSeeker }
|
|
106
|
+
|
|
107
|
+
let(:hash) do
|
|
108
|
+
{
|
|
109
|
+
'applicants' => [
|
|
110
|
+
{
|
|
111
|
+
'full_name' => 'Robert Hatz',
|
|
112
|
+
'email' => 'robert.hatz@gmail.com'
|
|
113
|
+
}, {
|
|
114
|
+
'full_name' => 'Marina Wantz',
|
|
115
|
+
'email' => 'marina.wantz@gmail.com'
|
|
116
|
+
}, {
|
|
117
|
+
'email' => 'albert.witz@gmail.com'
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
let(:expected) do
|
|
124
|
+
['Robert Hatz', 'Marina Wantz', 'John Doe']
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'treats keys as snake case keys' do
|
|
128
|
+
expect(instance.applicants).to eq(expected)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
describe 'Deep path with flatten' do
|
|
133
|
+
let(:clazz) { ShoppingMall }
|
|
134
|
+
|
|
135
|
+
let(:hash) do
|
|
136
|
+
{
|
|
137
|
+
floors: [{
|
|
138
|
+
name: 'ground', stores: [{
|
|
139
|
+
name: 'Starbucks', customers: %w[
|
|
140
|
+
John Bobby Maria
|
|
141
|
+
]
|
|
142
|
+
}, {
|
|
143
|
+
name: 'Pizza Hut', customers: %w[
|
|
144
|
+
Danny LJ
|
|
145
|
+
]
|
|
146
|
+
}]
|
|
147
|
+
}, {
|
|
148
|
+
name: 'first', stores: [{
|
|
149
|
+
name: 'Disney', customers: %w[
|
|
150
|
+
Robert Richard
|
|
151
|
+
]
|
|
152
|
+
}, {
|
|
153
|
+
name: 'Comix', customers: %w[
|
|
154
|
+
Linda Ariel
|
|
155
|
+
]
|
|
156
|
+
}]
|
|
157
|
+
}]
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
let(:expected) do
|
|
162
|
+
%w[
|
|
163
|
+
John Bobby Maria
|
|
164
|
+
Danny LJ
|
|
165
|
+
Robert Richard
|
|
166
|
+
Linda Ariel
|
|
167
|
+
]
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it 'returns all the customers in one array' do
|
|
171
|
+
expect(instance.customers).to eq(expected)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -4,16 +4,17 @@ require 'spec_helper'
|
|
|
4
4
|
|
|
5
5
|
describe Arstotzka::Fetcher do
|
|
6
6
|
subject(:fetcher) do
|
|
7
|
-
described_class.new
|
|
7
|
+
described_class.new options
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
let(:options) { Arstotzka::Options.new(options_hash) }
|
|
10
11
|
let(:instance) { Arstotzka::Fetcher::Dummy.new(json) }
|
|
11
12
|
let(:json) { load_json_fixture_file('arstotzka.json') }
|
|
12
13
|
let(:value) { fetcher.fetch }
|
|
13
14
|
|
|
14
15
|
context 'when fetching with no options' do
|
|
15
|
-
let(:
|
|
16
|
-
let(:key)
|
|
16
|
+
let(:options_hash) { { instance: instance, key: key } }
|
|
17
|
+
let(:key) { 'id' }
|
|
17
18
|
|
|
18
19
|
it 'retrieves attribute from base json' do
|
|
19
20
|
expect(value).to eq(json['id'])
|
|
@@ -48,7 +49,9 @@ describe Arstotzka::Fetcher do
|
|
|
48
49
|
let(:value) { [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] }
|
|
49
50
|
|
|
50
51
|
context 'when flatten option is true' do
|
|
51
|
-
let(:
|
|
52
|
+
let(:options_hash) do
|
|
53
|
+
{ instance: instance, flatten: true, key: :value }
|
|
54
|
+
end
|
|
52
55
|
|
|
53
56
|
it 'returns the fetched value flattened' do
|
|
54
57
|
expect(fetcher.fetch).to eq((1..8).to_a)
|
|
@@ -64,7 +67,9 @@ describe Arstotzka::Fetcher do
|
|
|
64
67
|
end
|
|
65
68
|
|
|
66
69
|
context 'when flatten option is false' do
|
|
67
|
-
let(:
|
|
70
|
+
let(:options_hash) do
|
|
71
|
+
{ instance: instance, flatten: false, key: :value }
|
|
72
|
+
end
|
|
68
73
|
|
|
69
74
|
it 'returns the fetched value non flattened' do
|
|
70
75
|
expect(fetcher.fetch).to eq(value)
|
|
@@ -75,7 +80,10 @@ describe Arstotzka::Fetcher do
|
|
|
75
80
|
describe 'after option' do
|
|
76
81
|
let(:instance) { MyParser.new(json) }
|
|
77
82
|
let(:json) { { value: [100, 250, -25] } }
|
|
78
|
-
|
|
83
|
+
|
|
84
|
+
let(:options_hash) do
|
|
85
|
+
{ instance: instance, after: :sum, key: :value }
|
|
86
|
+
end
|
|
79
87
|
|
|
80
88
|
it 'applies after call ' do
|
|
81
89
|
expect(fetcher.fetch).to eq(325)
|
|
@@ -83,12 +91,15 @@ describe Arstotzka::Fetcher do
|
|
|
83
91
|
end
|
|
84
92
|
|
|
85
93
|
describe 'klass options' do
|
|
86
|
-
let(:path)
|
|
94
|
+
let(:path) { 'name' }
|
|
87
95
|
let(:name) { 'Robert' }
|
|
88
96
|
let(:json) { { name: name } }
|
|
89
|
-
let(:options) { { klass: wrapper, path: path } }
|
|
90
97
|
let(:wrapper) { Person }
|
|
91
98
|
|
|
99
|
+
let(:options_hash) do
|
|
100
|
+
{ instance: instance, klass: wrapper, path: path }
|
|
101
|
+
end
|
|
102
|
+
|
|
92
103
|
it 'wraps the result in an object' do
|
|
93
104
|
expect(fetcher.fetch).to be_a(wrapper)
|
|
94
105
|
end
|
|
@@ -97,4 +108,38 @@ describe Arstotzka::Fetcher do
|
|
|
97
108
|
expect(fetcher.fetch.name).to eq(name)
|
|
98
109
|
end
|
|
99
110
|
end
|
|
111
|
+
|
|
112
|
+
describe 'after_each options' do
|
|
113
|
+
let(:full_path) { 'people.name' }
|
|
114
|
+
let(:instance) { Group.new(json) }
|
|
115
|
+
|
|
116
|
+
let(:options_hash) do
|
|
117
|
+
{
|
|
118
|
+
instance: instance,
|
|
119
|
+
full_path: full_path,
|
|
120
|
+
after_each: :create_person,
|
|
121
|
+
json: :@hash,
|
|
122
|
+
compact: true
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
let(:json) do
|
|
127
|
+
{
|
|
128
|
+
people: [
|
|
129
|
+
{ name: 'Robert', age: 20 },
|
|
130
|
+
{ name: 'John', age: 25 },
|
|
131
|
+
{ name: 'Leeloo', age: 3570 },
|
|
132
|
+
{ age: 10 }
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it do
|
|
138
|
+
expect(fetcher.fetch).to be_a(Array)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'calls the given method on each value' do
|
|
142
|
+
expect(fetcher.fetch).to all(be_a(Person))
|
|
143
|
+
end
|
|
144
|
+
end
|
|
100
145
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Application
|
|
4
|
+
include Arstotzka
|
|
5
|
+
|
|
6
|
+
expose :users, full_path: 'users.first_name',
|
|
7
|
+
compact: true, cached: true,
|
|
8
|
+
after: :create_person
|
|
9
|
+
|
|
10
|
+
def initialize(json)
|
|
11
|
+
@json = json
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
attr_reader :json
|
|
17
|
+
|
|
18
|
+
def create_person(names)
|
|
19
|
+
names.map do |name|
|
|
20
|
+
warn "Creating person #{name}"
|
|
21
|
+
Person.new(name)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Bar
|
|
4
|
+
include Arstotzka
|
|
5
|
+
|
|
6
|
+
expose :drinks, type: :symbolized_hash,
|
|
7
|
+
klass: Drink, after_each: :add_inflation
|
|
8
|
+
|
|
9
|
+
def initialize(json)
|
|
10
|
+
@json = json
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
attr_reader :json
|
|
16
|
+
|
|
17
|
+
def add_inflation(drink)
|
|
18
|
+
drink.inflate(0.1)
|
|
19
|
+
drink
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Customer
|
|
4
|
+
attr_reader :name, :age
|
|
5
|
+
|
|
6
|
+
def initialize(name:, age:)
|
|
7
|
+
@name = name
|
|
8
|
+
@age = age
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def adult?
|
|
12
|
+
age >= 18
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ==(other)
|
|
16
|
+
return false unless other.class == self.class
|
|
17
|
+
other.name == name &&
|
|
18
|
+
other.age == age
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Drink
|
|
4
|
+
attr_reader :name, :price
|
|
5
|
+
|
|
6
|
+
def initialize(name:, price:)
|
|
7
|
+
@name = name
|
|
8
|
+
@price = price
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def inflate(inflation)
|
|
12
|
+
@price = (price * (1 + inflation)).round(2)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ==(other)
|
|
16
|
+
return false unless other.class == self.class
|
|
17
|
+
other.name == name &&
|
|
18
|
+
other.price == price
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Store
|
|
4
|
+
include Arstotzka
|
|
5
|
+
|
|
6
|
+
expose :customers, klass: Customer, after: :filter_adults
|
|
7
|
+
|
|
8
|
+
def initialize(json)
|
|
9
|
+
@json = json
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
attr_reader :json
|
|
15
|
+
|
|
16
|
+
def filter_adults(values)
|
|
17
|
+
values.select(&:adult?)
|
|
18
|
+
end
|
|
19
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: arstotzka
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Darthjee
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2019-03-
|
|
11
|
+
date: 2019-03-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -228,6 +228,7 @@ files:
|
|
|
228
228
|
- spec/integration/yard/arstotzka/fetcher_builder_spec.rb
|
|
229
229
|
- spec/integration/yard/arstotzka/fetcher_spec.rb
|
|
230
230
|
- spec/integration/yard/arstotzka/method_builder_spec.rb
|
|
231
|
+
- spec/integration/yard/arstotzka/options_spec.rb
|
|
231
232
|
- spec/integration/yard/arstotzka/reader_spec.rb
|
|
232
233
|
- spec/integration/yard/arstotzka/type_cast_spec.rb
|
|
233
234
|
- spec/integration/yard/arstotzka/wrapper_spec.rb
|
|
@@ -244,22 +245,30 @@ files:
|
|
|
244
245
|
- spec/support/fixture_helpers.rb
|
|
245
246
|
- spec/support/models.rb
|
|
246
247
|
- spec/support/models/account.rb
|
|
248
|
+
- spec/support/models/application.rb
|
|
247
249
|
- spec/support/models/arstotzka/dummy.rb
|
|
248
250
|
- spec/support/models/arstotzka/fetcher/dummy.rb
|
|
249
251
|
- spec/support/models/arstotzka/type_cast.rb
|
|
250
252
|
- spec/support/models/arstotzka/wrapper/dummy.rb
|
|
253
|
+
- spec/support/models/bar.rb
|
|
251
254
|
- spec/support/models/car.rb
|
|
252
255
|
- spec/support/models/car_collector.rb
|
|
253
256
|
- spec/support/models/collector.rb
|
|
254
257
|
- spec/support/models/collector/game.rb
|
|
258
|
+
- spec/support/models/customer.rb
|
|
259
|
+
- spec/support/models/drink.rb
|
|
255
260
|
- spec/support/models/game.rb
|
|
261
|
+
- spec/support/models/group.rb
|
|
256
262
|
- spec/support/models/house.rb
|
|
263
|
+
- spec/support/models/job_seeker.rb
|
|
257
264
|
- spec/support/models/my_model.rb
|
|
258
265
|
- spec/support/models/my_parser.rb
|
|
259
266
|
- spec/support/models/person.rb
|
|
260
267
|
- spec/support/models/request.rb
|
|
268
|
+
- spec/support/models/shopping_mall.rb
|
|
261
269
|
- spec/support/models/star.rb
|
|
262
270
|
- spec/support/models/star_gazer.rb
|
|
271
|
+
- spec/support/models/store.rb
|
|
263
272
|
- spec/support/models/transaction.rb
|
|
264
273
|
- spec/support/models/type_caster.rb
|
|
265
274
|
- spec/support/shared_examples/wrapper.rb
|
|
@@ -299,6 +308,7 @@ test_files:
|
|
|
299
308
|
- spec/integration/yard/arstotzka/fetcher_builder_spec.rb
|
|
300
309
|
- spec/integration/yard/arstotzka/fetcher_spec.rb
|
|
301
310
|
- spec/integration/yard/arstotzka/method_builder_spec.rb
|
|
311
|
+
- spec/integration/yard/arstotzka/options_spec.rb
|
|
302
312
|
- spec/integration/yard/arstotzka/reader_spec.rb
|
|
303
313
|
- spec/integration/yard/arstotzka/type_cast_spec.rb
|
|
304
314
|
- spec/integration/yard/arstotzka/wrapper_spec.rb
|
|
@@ -315,22 +325,30 @@ test_files:
|
|
|
315
325
|
- spec/support/fixture_helpers.rb
|
|
316
326
|
- spec/support/models.rb
|
|
317
327
|
- spec/support/models/account.rb
|
|
328
|
+
- spec/support/models/application.rb
|
|
318
329
|
- spec/support/models/arstotzka/dummy.rb
|
|
319
330
|
- spec/support/models/arstotzka/fetcher/dummy.rb
|
|
320
331
|
- spec/support/models/arstotzka/type_cast.rb
|
|
321
332
|
- spec/support/models/arstotzka/wrapper/dummy.rb
|
|
333
|
+
- spec/support/models/bar.rb
|
|
322
334
|
- spec/support/models/car.rb
|
|
323
335
|
- spec/support/models/car_collector.rb
|
|
324
336
|
- spec/support/models/collector.rb
|
|
325
337
|
- spec/support/models/collector/game.rb
|
|
338
|
+
- spec/support/models/customer.rb
|
|
339
|
+
- spec/support/models/drink.rb
|
|
326
340
|
- spec/support/models/game.rb
|
|
341
|
+
- spec/support/models/group.rb
|
|
327
342
|
- spec/support/models/house.rb
|
|
343
|
+
- spec/support/models/job_seeker.rb
|
|
328
344
|
- spec/support/models/my_model.rb
|
|
329
345
|
- spec/support/models/my_parser.rb
|
|
330
346
|
- spec/support/models/person.rb
|
|
331
347
|
- spec/support/models/request.rb
|
|
348
|
+
- spec/support/models/shopping_mall.rb
|
|
332
349
|
- spec/support/models/star.rb
|
|
333
350
|
- spec/support/models/star_gazer.rb
|
|
351
|
+
- spec/support/models/store.rb
|
|
334
352
|
- spec/support/models/transaction.rb
|
|
335
353
|
- spec/support/models/type_caster.rb
|
|
336
354
|
- spec/support/shared_examples/wrapper.rb
|