arstotzka 1.1.0 → 1.2.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.
Files changed (34) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +1 -1
  3. data/Dockerfile +2 -4
  4. data/README.md +1 -1
  5. data/arstotzka.gemspec +1 -1
  6. data/config/yardstick.yml +10 -10
  7. data/lib/arstotzka.rb +12 -11
  8. data/lib/arstotzka/class_methods.rb +38 -2
  9. data/lib/arstotzka/crawler.rb +14 -21
  10. data/lib/arstotzka/fetcher.rb +24 -21
  11. data/lib/arstotzka/fetcher_builder.rb +167 -0
  12. data/lib/arstotzka/{builder.rb → method_builder.rb} +22 -49
  13. data/lib/arstotzka/options.rb +13 -6
  14. data/lib/arstotzka/reader.rb +4 -4
  15. data/lib/arstotzka/type_cast.rb +3 -2
  16. data/lib/arstotzka/version.rb +1 -1
  17. data/lib/arstotzka/wrapper.rb +1 -1
  18. data/spec/integration/yard/arstotzka/crawler_spec.rb +5 -4
  19. data/spec/integration/yard/arstotzka/fetcher_builder_spec.rb +91 -0
  20. data/spec/integration/yard/arstotzka/fetcher_spec.rb +2 -2
  21. data/spec/integration/yard/arstotzka/{builder_spec.rb → method_builder_spec.rb} +3 -2
  22. data/spec/integration/yard/arstotzka/reader_spec.rb +2 -1
  23. data/spec/lib/arstotzka/crawler_spec.rb +3 -2
  24. data/spec/lib/arstotzka/fetcher_builder_spec.rb +30 -0
  25. data/spec/lib/arstotzka/fetcher_spec.rb +13 -13
  26. data/spec/lib/arstotzka/{builder_spec.rb → method_builder_spec.rb} +2 -1
  27. data/spec/lib/arstotzka/options_spec.rb +83 -0
  28. data/spec/lib/arstotzka/reader_spec.rb +2 -1
  29. data/spec/support/models/account.rb +6 -0
  30. data/spec/support/models/arstotzka/fetcher/dummy.rb +7 -0
  31. data/spec/support/models/my_model.rb +2 -0
  32. data/spec/support/models/star.rb +13 -2
  33. data/spec/support/models/star_gazer.rb +6 -0
  34. metadata +15 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 74d1946cebd9e40fa273fc644a478e809ce9d888
4
- data.tar.gz: 66abec44e1e8090d5507c8f4c59532dce3e8be03
2
+ SHA256:
3
+ metadata.gz: 3d37d619db8888a43c76400066d7657b53bbf94297e2be0f900fd7f1c391d080
4
+ data.tar.gz: 98d542ee52adac431bfe9acfcbc4a1bdd6cfdc002647b594b0f4fedfe371a208
5
5
  SHA512:
6
- metadata.gz: 931eb74bb1e15412c7927c613204dbac3d48dc1ca6f3d81d58c941540ccf23ed530a19c4f8b1214ef06ba0f878d44e5e28cc6b55f3a32c0930123336d4fe3b43
7
- data.tar.gz: fac8783602cc90be4933d9dce2f2fe5b6e24fd284ec2c53925ee75cd5cb5895aa62a1de5a40acdef5e158c66cc174df8fe3390ebfacacd7460f5d39c705830f6
6
+ metadata.gz: 94dcbbed1f5665dab68a52f82222016aad12c68aca3f6676bbf95727f2131bd94c48217e584a245342519ac7c3da887852f85ca3300bf9b5c2a12b2e9d6847e1
7
+ data.tar.gz: cc368f423651b046af8965f8ca5fc34efbfc8e026c5f894d615621d597360009f713a81554b57e5d8361559f4cf013909be0411ee47bce5a2259f25eef3a8851
data/.circleci/config.yml CHANGED
@@ -2,7 +2,7 @@ version: 2
2
2
  jobs:
3
3
  build:
4
4
  docker:
5
- - image: darthjee/circleci_ruby_240:0.1.0
5
+ - image: darthjee/circleci_ruby_gems:0.0.1
6
6
  steps:
7
7
  - checkout
8
8
  - run:
data/Dockerfile CHANGED
@@ -1,8 +1,6 @@
1
- FROM darthjee/ruby_240:0.2.2
1
+ FROM darthjee/ruby_gems:0.0.1
2
2
 
3
3
  USER app
4
- COPY ./ /home/app/app/
4
+ COPY --chown=app ./ /home/app/app/
5
5
 
6
- RUN gem uninstall bundler
7
- RUN gem install bundler -v '1.17.3'
8
6
  RUN bundle install
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.1.0
41
+ https://www.rubydoc.info/gems/arstotzka/1.2.0
42
42
 
43
43
  Getting Started
44
44
  ---------------
data/arstotzka.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.add_runtime_dependency 'activesupport', '~> 5.x'
22
22
  spec.add_runtime_dependency 'sinclair', '>= 1.1.1'
23
23
 
24
- spec.add_development_dependency 'bundler', '~> 1.17.x'
24
+ spec.add_development_dependency 'bundler', '~> 1.16.1'
25
25
  spec.add_development_dependency 'pry-nav', '~> 0.2.4'
26
26
  spec.add_development_dependency 'rake', '>= 12.3.1'
27
27
  spec.add_development_dependency 'rspec', '>= 3.8'
data/config/yardstick.yml CHANGED
@@ -19,30 +19,30 @@ rules:
19
19
  ReturnTag:
20
20
  enabled: true
21
21
  exclude:
22
- - Arstotzka::Builder#attr_names
23
- - Arstotzka::Builder#options
24
- - Arstotzka::Builder#json_name
25
22
  - Arstotzka::Crawler#post_process
26
23
  - Arstotzka::Crawler#options
27
- - Arstotzka::Fetcher#keys
28
- - Arstotzka::Fetcher#hash
29
24
  - Arstotzka::Fetcher#instance
30
25
  - Arstotzka::Fetcher#options
26
+ - Arstotzka::Fetcher#hash
27
+ - Arstotzka::FetcherBuilder#options
28
+ - Arstotzka::MethodBuilder#attr_names
29
+ - Arstotzka::MethodBuilder#options
30
+ - Arstotzka::Options#keys
31
31
  - Arstotzka::Reader#keys
32
32
  - Arstotzka::Reader#options
33
33
  - Arstotzka::Wrapper#options
34
34
  Summary::Presence:
35
35
  enabled: true
36
36
  exclude:
37
- - Arstotzka::Builder#attr_names
38
- - Arstotzka::Builder#options
39
- - Arstotzka::Builder#json_name
40
37
  - Arstotzka::Crawler#post_process
41
38
  - Arstotzka::Crawler#options
42
- - Arstotzka::Fetcher#keys
43
- - Arstotzka::Fetcher#hash
44
39
  - Arstotzka::Fetcher#instance
45
40
  - Arstotzka::Fetcher#options
41
+ - Arstotzka::Fetcher#hash
42
+ - Arstotzka::FetcherBuilder#options
43
+ - Arstotzka::MethodBuilder#attr_names
44
+ - Arstotzka::MethodBuilder#options
45
+ - Arstotzka::Options#keys
46
46
  - Arstotzka::Reader#keys
47
47
  - Arstotzka::Reader#options
48
48
  - Arstotzka::Wrapper#options
data/lib/arstotzka.rb CHANGED
@@ -159,19 +159,20 @@ require 'sinclair'
159
159
  # # Collector::Game.new(name: "Zelda", played: 90.0)
160
160
  # # ]
161
161
  #
162
- # @see Arstotzka::Builder
162
+ # @see Arstotzka::MethodBuilder
163
163
  # @see Arstotzka::ClassMethods
164
164
  module Arstotzka
165
165
  extend ActiveSupport::Concern
166
166
 
167
- autoload :Base, 'arstotzka/base'
168
- autoload :Options, 'arstotzka/options'
169
- autoload :Builder, 'arstotzka/builder'
170
- autoload :ClassMethods, 'arstotzka/class_methods'
171
- autoload :Crawler, 'arstotzka/crawler'
172
- autoload :Exception, 'arstotzka/exception'
173
- autoload :Fetcher, 'arstotzka/fetcher'
174
- autoload :Reader, 'arstotzka/reader'
175
- autoload :Wrapper, 'arstotzka/wrapper'
176
- autoload :TypeCast, 'arstotzka/type_cast'
167
+ autoload :Base, 'arstotzka/base'
168
+ autoload :ClassMethods, 'arstotzka/class_methods'
169
+ autoload :Crawler, 'arstotzka/crawler'
170
+ autoload :Exception, 'arstotzka/exception'
171
+ autoload :Fetcher, 'arstotzka/fetcher'
172
+ autoload :FetcherBuilder, 'arstotzka/fetcher_builder'
173
+ autoload :MethodBuilder, 'arstotzka/method_builder'
174
+ autoload :Options, 'arstotzka/options'
175
+ autoload :Reader, 'arstotzka/reader'
176
+ autoload :TypeCast, 'arstotzka/type_cast'
177
+ autoload :Wrapper, 'arstotzka/wrapper'
177
178
  end
@@ -4,6 +4,32 @@ module Arstotzka
4
4
  # As Arstotzka extends ActiveSupport::Concern, Arstotzka::ClassMethods define
5
5
  # methods that will be available when defining a class that includes Arstotka
6
6
  module ClassMethods
7
+ # @api private
8
+ #
9
+ # Create builder that will be used to create Fetchers
10
+ #
11
+ # @param attribute [Symbol,String] attribute key
12
+ # @param options [Arstotzka::Options] fetcher options
13
+ #
14
+ # @return [Artotzka::FetcherBuilder]
15
+ def add_fetcher(attribute, options)
16
+ fetcher_builders[attribute.to_sym] = FetcherBuilder.new(options.merge(key: attribute))
17
+ end
18
+
19
+ # @api private
20
+ #
21
+ # Return the fetcher for an attribute and instance
22
+ #
23
+ # a new fetcher is built everytime this method is called
24
+ #
25
+ # @param attribute [Symbol,String] Name of method that will use this Fetcher
26
+ # @param instance [Object] instance that will contain the Hash needed by fetcher
27
+ #
28
+ # @return [Arstotzka::Fetcher]
29
+ def fetcher_for(attribute, instance)
30
+ fetcher_builders[attribute.to_sym].build(instance)
31
+ end
32
+
7
33
  private
8
34
 
9
35
  # @api public
@@ -37,13 +63,23 @@ module Arstotzka
37
63
  #
38
64
  # @return [Array<Sinclair::MethodDefinition>]
39
65
  #
40
- # @see Builder Arstotzka::Builder
66
+ # @see MethodBuilder Arstotzka::MethodBuilder
41
67
  # @see
42
68
  # https://www.rubydoc.info/gems/activesupport/5.2.2/ActiveSupport/Concern
43
69
  # ActiveSupport::Concern
44
70
  def expose(*attr_names, **options_hash)
45
71
  options = Options.new(options_hash.symbolize_keys)
46
- Builder.new(attr_names, self, options).build
72
+ MethodBuilder.new(attr_names, self, options).build
73
+ end
74
+
75
+ # @private
76
+ # @api private
77
+ #
78
+ # Map of FetcherBuilders
79
+ #
80
+ # @return [Hash<FetcherBuilder>]
81
+ def fetcher_builders
82
+ @fetcher_builders ||= {}
47
83
  end
48
84
  end
49
85
  end
@@ -5,8 +5,8 @@ module Arstotzka
5
5
  #
6
6
  # @api private
7
7
  #
8
- # @example
9
- # crawler = Arstotzka::Crawler.new(keys: %w(person information first_name))
8
+ # @example Simple usage
9
+ # crawler = Arstotzka::Crawler.new(full_path: 'person.information.first_name')
10
10
  # hash = {
11
11
  # person: {
12
12
  # 'information' => {
@@ -45,21 +45,13 @@ module Arstotzka
45
45
  # @return [Object] value fetched from the last Hash#fetch call using the last part
46
46
  # of keys
47
47
  #
48
- # @example
49
- # crawler = Arstotzka::Crawler.new(keys: %w(person information first_name))
50
- # hash = {
51
- # person: {
52
- # 'information' => {
53
- # 'firstName' => 'John'
54
- # }
55
- # }
56
- # }
57
- # crawler.value(hash) # returns 'John'
48
+ # @example (see Arstotzka::Crawler)
58
49
  #
59
- # @example
50
+ # @example Passing compact and case options
60
51
  # crawler = Arstotzka::Crawler.new(
61
- # keys: %w(companies games hero),
62
- # compact: true, case: :snake
52
+ # full_path: 'companies.games.hero',
53
+ # compact: true,
54
+ # case: :snake
63
55
  # )
64
56
  # games_hash = {
65
57
  # 'companies' => [{
@@ -77,18 +69,19 @@ module Arstotzka
77
69
  #
78
70
  # crawler.value(games_hash) # returns [['Rakhar']]
79
71
  #
80
- # @example
72
+ # @example Passing default option
81
73
  # crawler = Arstotzka::Crawler.new(
82
- # keys: %w(companies games hero),
74
+ # full_path: 'companies.games.hero',
83
75
  # compact: true, case: :snake, default: 'NO HERO'
84
76
  # )
85
77
  #
86
78
  # crawler.value(games_hash) # returns [['NO HERO', 'Rakhar'], 'NO HERO']
87
79
  #
88
- # @example
80
+ # @example Passing a post processor block
89
81
  # crawler = Arstotzka::Crawler.new(
90
- # keys: %w(companies games hero),
91
- # compact: true, case: :snake
82
+ # full_path: 'companies.games.hero',
83
+ # compact: true,
84
+ # case: : snake
92
85
  # ) { |value| value.&to_sym }
93
86
  #
94
87
  # crawler.value(games_hash) # returns [[:Rakhar]]
@@ -102,7 +95,7 @@ module Arstotzka
102
95
 
103
96
  # @private
104
97
  attr_reader :post_process, :options
105
- delegate :keys, :compact, :default, to: :options
98
+ delegate :compact, :default, to: :options
106
99
 
107
100
  # Fetch the value from hash by crawling the keys
108
101
  #
@@ -10,14 +10,16 @@ module Arstotzka
10
10
 
11
11
  # Creates an instance of Artotzka::Fetcher
12
12
  #
13
- # @param hash [Hash] Hash to be crawled for value
14
13
  # @param instance [Object] object whose methods will be called after for processing
15
- # @param options_hash [Hash] options that will be passed to {Crawler}, {Wrapper} and {Reader}
16
- def initialize(hash, instance, options_hash = {})
14
+ #
15
+ # @overload iniitalize(instance, options_hash = {})
16
+ # @param options_hash [Hash] options for {Crawler}, {Wrapper} and {Reader}
17
+ #
18
+ # @overload iniitalize(instance, options)
19
+ # @param options [Arstotzka::Options] options for {Crawler}, {Wrapper} and {Reader}
20
+ def initialize(instance, options_hash = {})
17
21
  self.options = options_hash
18
22
 
19
- @keys = options.path.to_s.split('.')
20
- @hash = hash
21
23
  @instance = instance
22
24
  end
23
25
 
@@ -28,7 +30,7 @@ module Arstotzka
28
30
  #
29
31
  # @return [Object] The final value found and transformed
30
32
  #
31
- # @example
33
+ # @example Fetching with wrapping and processing
32
34
  # class Transaction
33
35
  # attr_reader :value, :type
34
36
  #
@@ -43,8 +45,14 @@ module Arstotzka
43
45
  # end
44
46
  #
45
47
  # class Account
48
+ # def initialize(json = {})
49
+ # @json = json
50
+ # end
51
+ #
46
52
  # private
47
53
  #
54
+ # attr_reader :json
55
+ #
48
56
  # def filter_income(transactions)
49
57
  # transactions.select(&:positive?)
50
58
  # end
@@ -61,8 +69,10 @@ module Arstotzka
61
69
  # { value: 101.00, type: 'outcome' }
62
70
  # ]
63
71
  # }
72
+ #
64
73
  # instance = Account.new
65
- # fetcher = Arstotzka::Fetcher.new(hash, instance,
74
+ #
75
+ # fetcher = Arstotzka::Fetcher.new(instance,
66
76
  # path: 'transactions',
67
77
  # klass: Transaction,
68
78
  # after: :filter_income
@@ -82,10 +92,14 @@ module Arstotzka
82
92
  private
83
93
 
84
94
  # @private
85
- attr_reader :keys, :hash, :instance, :options
95
+ attr_reader :instance, :options
86
96
  delegate :after, :flatten, to: :options
87
97
  delegate :wrap, to: :wrapper
88
98
 
99
+ def hash
100
+ @hash ||= instance.send(:eval, options.json.to_s)
101
+ end
102
+
89
103
  # @private
90
104
  #
91
105
  # Returns an instance of Aristotzka::Craler
@@ -96,29 +110,18 @@ module Arstotzka
96
110
  # @return [Arstotzka::Crawler] the crawler object
97
111
  def crawler
98
112
  @crawler ||=
99
- Arstotzka::Crawler.new(crawler_options) do |value|
113
+ Crawler.new(options) do |value|
100
114
  wrap(value)
101
115
  end
102
116
  end
103
117
 
104
- # @private
105
- #
106
- # Hash for crawler initialization
107
- #
108
- # @return [Hash]
109
- #
110
- # @see #crawler
111
- def crawler_options
112
- options.merge(keys: keys)
113
- end
114
-
115
118
  # @private
116
119
  #
117
120
  # Wrapper responsible for wrapping the value found
118
121
  #
119
122
  # @return [Arstotzka::Wrapper] the wrapper
120
123
  def wrapper
121
- @wrapper ||= Arstotzka::Wrapper.new(options)
124
+ @wrapper ||= Wrapper.new(options)
122
125
  end
123
126
  end
124
127
  end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arstotzka
4
+ # @api private
5
+ #
6
+ # Class responsible for building fetcher that will be used
7
+ # to create instance fetchers
8
+ #
9
+ # @example Building a simple fetcher
10
+ # class MyModel
11
+ # include Arstotzka
12
+ #
13
+ # attr_reader :json
14
+ #
15
+ # def initialize(json)
16
+ # @json = json
17
+ # end
18
+ # end
19
+ #
20
+ # options = Arstotzka::Options.new(key: :id, path: :person)
21
+ # builder = Arstotzka::FetcherBuilder.new(options)
22
+ # instance = MyModel.new(
23
+ # person: {
24
+ # id: 101
25
+ # }
26
+ # )
27
+ #
28
+ # fetcher = builder.build(instance)
29
+ #
30
+ # fetcher.fetch # returns 101
31
+ class FetcherBuilder
32
+ include Base
33
+
34
+ # Creates an instance of Artotzka::FetcherBuilder
35
+ #
36
+ # @overload initialize(options_hash = {})
37
+ # @param options_hash [Hash] options (see {Options})
38
+ #
39
+ # @overload initialize(options)
40
+ # @param options [Arstotzka::Options] options
41
+ def initialize(options_hash = {})
42
+ self.options = options_hash
43
+ end
44
+
45
+ # Builds a fetcher responsible for fetchin a value
46
+ #
47
+ # @param instance [Object] object that includes Arstotzka
48
+ #
49
+ # instance should be able to provide the hash where values
50
+ # will be fetched from
51
+ #
52
+ # @example (see Arstotzka::FetcherBuilder)
53
+ #
54
+ # @example Building a fetcher using full path
55
+ # class MyModel
56
+ # include Arstotzka
57
+ #
58
+ # attr_reader :json
59
+ #
60
+ # def initialize(json)
61
+ # @json = json
62
+ # end
63
+ # end
64
+ #
65
+ # options = Arstotzka::Options.new(
66
+ # key: :player_ids,
67
+ # full_path: 'teams.players.person_id',
68
+ # flatten: true,
69
+ # case: :snake
70
+ # )
71
+ # builder = Arstotzka::FetcherBuilder.new(options)
72
+ # hash = {
73
+ # teams: [
74
+ # {
75
+ # name: 'Team War',
76
+ # players: [
77
+ # { person_id: 101 },
78
+ # { person_id: 102 }
79
+ # ]
80
+ # }, {
81
+ # name: 'Team not War',
82
+ # players: [
83
+ # { person_id: 201 },
84
+ # { person_id: 202 }
85
+ # ]
86
+ # }
87
+ # ]
88
+ # }
89
+ # instance = MyModel.new(hash)
90
+ #
91
+ # fetcher = builder.build(instance)
92
+ #
93
+ # fetcher.fetch # returns [101, 102, 201, 202]
94
+ #
95
+ # @example Post processing results
96
+ # class StarGazer
97
+ # include Arstotzka
98
+ #
99
+ # attr_reader :json
100
+ #
101
+ # def initialize(json = {})
102
+ # @json = json
103
+ # end
104
+ #
105
+ # private
106
+ #
107
+ # def only_yellow(stars)
108
+ # stars.select(&:yellow?)
109
+ # end
110
+ # end
111
+ #
112
+ # class Star
113
+ # attr_reader :name, :color
114
+ #
115
+ # def initialize(name:, color: 'yellow')
116
+ # @name = name
117
+ # @color = color
118
+ # end
119
+ #
120
+ # def yellow?
121
+ # color == 'yellow'
122
+ # end
123
+ # end
124
+ #
125
+ # hash = {
126
+ # teams: [
127
+ # {
128
+ # name: 'Team War',
129
+ # players: [
130
+ # { person_id: 101 },
131
+ # { person_id: 102 }
132
+ # ]
133
+ # }, {
134
+ # name: 'Team not War',
135
+ # players: [
136
+ # { person_id: 201 },
137
+ # { person_id: 202 }
138
+ # ]
139
+ # }
140
+ # ]
141
+ # }
142
+ #
143
+ # instance = StarGazer.new(hash)
144
+ #
145
+ # options = Arstotzka::Options.new(
146
+ # key: :stars, klass: Star, after: :only_yellow
147
+ # )
148
+ #
149
+ # builder = Arstotzka::FetcherBuilder.new(options)
150
+ # fetcher = builder.build(instance)
151
+ #
152
+ # fetcher.fetch # returns [
153
+ # # Star.new(name: 'Sun', color: 'yellow'),
154
+ # # Star.new(name: 'HB0124-C', color: 'yellow'),
155
+ # # ]
156
+ #
157
+ # @return Arstotzka::Fetcher
158
+ def build(instance)
159
+ Fetcher.new(instance, options)
160
+ end
161
+
162
+ private
163
+
164
+ # @private
165
+ attr_reader :options
166
+ end
167
+ end