arstotzka 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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