azeroth 0.10.1 → 1.1.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +3 -3
  3. data/Dockerfile +1 -1
  4. data/README.md +43 -30
  5. data/azeroth.gemspec +8 -7
  6. data/config/check_specs.yml +1 -0
  7. data/lib/azeroth/decorator/class_methods.rb +249 -0
  8. data/lib/azeroth/decorator/hash_builder.rb +3 -1
  9. data/lib/azeroth/decorator/key_value_extractor.rb +3 -8
  10. data/lib/azeroth/decorator/method_builder.rb +23 -7
  11. data/lib/azeroth/decorator/options.rb +3 -1
  12. data/lib/azeroth/decorator.rb +30 -188
  13. data/lib/azeroth/params_builder.rb +11 -8
  14. data/lib/azeroth/request_handler/index.rb +7 -33
  15. data/lib/azeroth/request_handler/pagination.rb +53 -0
  16. data/lib/azeroth/request_handler.rb +8 -7
  17. data/lib/azeroth/resource_builder.rb +9 -10
  18. data/lib/azeroth/resourceable/builder.rb +15 -11
  19. data/lib/azeroth/routes_builder.rb +8 -10
  20. data/lib/azeroth/version.rb +1 -1
  21. data/spec/controllers/documents_with_error_controller_spec.rb +3 -1
  22. data/spec/dummy/app/models/website/decorator.rb +11 -0
  23. data/spec/dummy/app/models/website/with_location.rb +21 -0
  24. data/spec/dummy/app/models/website.rb +4 -0
  25. data/spec/dummy/config/environments/development.rb +1 -0
  26. data/spec/dummy/db/schema.rb +5 -1
  27. data/spec/integration/yard/azeroth/decorator_spec.rb +15 -0
  28. data/spec/lib/azeroth/decorator/key_value_extractor_spec.rb +3 -1
  29. data/spec/lib/azeroth/decorator/method_builder_spec.rb +61 -2
  30. data/spec/lib/azeroth/decorator_spec.rb +96 -3
  31. data/spec/lib/azeroth/params_builder_spec.rb +3 -1
  32. data/spec/lib/azeroth/request_handler/pagination_spec.rb +133 -0
  33. data/spec/lib/azeroth/resource_builder_spec.rb +3 -1
  34. data/spec/lib/azeroth/routes_builder_spec.rb +3 -1
  35. metadata +41 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcc284d1b166fec28f03e31cd42642ad09a86c85a81718ba401531443b1bbc08
4
- data.tar.gz: 335851aa5d9a160cf387971e904248c2f89041594ccdaec57fa9ab130624558f
3
+ metadata.gz: 7c8e0229917b320108b25371d2c30ca48f83663288d50d2e767b8cf45c037c95
4
+ data.tar.gz: 2e6fe774d10fa5b5ca3f22c0b4deac4828a28480ebd3ebb1cce370c84c0f359c
5
5
  SHA512:
6
- metadata.gz: 7ef7afcbd3282d181339fa6f154d1b4dcb6c6bad00f7c4e33e5c0b912b55eecc7de9876e44d2d54e7709a006a7cef487ee6c057dc0645bc0e336ff2b70629fdf
7
- data.tar.gz: d37ba6ffdfe83b55fd51f103fd9ba3f35a3edf5eb0121c5aab424da29ffcce3e7850d52acdb4c41097c773d2f5b56692dc5f0e6c751ebce1651153bde42f2e1d
6
+ metadata.gz: b517e33573aa729a94fee810f0f4cea17a39533359332017e1dea8a3d80c74aacaac1010754739d8ba312d42353c57bb93d0c172aa640453450383ea1ff99328
7
+ data.tar.gz: 7bdfbfce3bdc6f40c2c82ea904a5778b5d0e7cc037f4fbf73f2e7d5fe8a449b7dacbac41f55523afea298bea5de998cc5d442e395fbf4949e555775aeabccff4
data/.circleci/config.yml CHANGED
@@ -22,7 +22,7 @@ workflows:
22
22
  jobs:
23
23
  test:
24
24
  docker:
25
- - image: darthjee/circleci_rails_gems:1.1.0
25
+ - image: darthjee/circleci_rails_gems:1.2.0
26
26
  environment:
27
27
  PROJECT: azeroth
28
28
  steps:
@@ -41,7 +41,7 @@ jobs:
41
41
  command: cc-test-reporter after-build --exit-code $?
42
42
  checks:
43
43
  docker:
44
- - image: darthjee/circleci_rails_gems:1.1.0
44
+ - image: darthjee/circleci_rails_gems:1.2.0
45
45
  environment:
46
46
  PROJECT: azeroth
47
47
  steps:
@@ -66,7 +66,7 @@ jobs:
66
66
  command: check_specs
67
67
  build-and-release:
68
68
  docker:
69
- - image: darthjee/circleci_rails_gems:1.1.0
69
+ - image: darthjee/circleci_rails_gems:1.2.0
70
70
  environment:
71
71
  PROJECT: azeroth
72
72
  steps:
data/Dockerfile CHANGED
@@ -1,6 +1,6 @@
1
1
  FROM darthjee/scripts:0.3.1 as scripts
2
2
 
3
- FROM darthjee/rails_gems:1.1.0 as base
3
+ FROM darthjee/rails_gems:1.2.0 as base
4
4
 
5
5
  COPY --chown=app:app ./ /home/app/app/
6
6
 
data/README.md CHANGED
@@ -11,7 +11,7 @@ Azeroth
11
11
 
12
12
  Yard Documentation
13
13
  -------------------
14
- [https://www.rubydoc.info/gems/azeroth/0.10.1](https://www.rubydoc.info/gems/azeroth/0.10.1)
14
+ [https://www.rubydoc.info/gems/azeroth/1.1.0](https://www.rubydoc.info/gems/azeroth/1.1.0)
15
15
 
16
16
  Azeroth has been designed making the coding of controllers easier
17
17
  as routes in controllers are usually copy, paste and replace of same
@@ -25,6 +25,10 @@ does not perform database operations
25
25
  Future versions will enable `html` rendering to also perform
26
26
  database operations.
27
27
 
28
+ Current Release: [1.1.0](https://github.com/darthjee/azeroth/tree/1.1.0)
29
+
30
+ [Next release](https://github.com/darthjee/azeroth/compare/1.1.0...master)
31
+
28
32
  Installation
29
33
  ---------------
30
34
 
@@ -131,12 +135,50 @@ It accepts options
131
135
  end
132
136
  ```
133
137
 
138
+ ```ruby
139
+ class PaginatedDocumentsController < ApplicationController
140
+ include Azeroth::Resourceable
141
+
142
+ resource_for :document, only: 'index', paginated: true
143
+ end
144
+
145
+ 30.times { create(:document) }
146
+
147
+ get '/paginated_documents.json'
148
+
149
+ # returns Array with 20 first documents
150
+ # returns in the headers pagination headers
151
+ # {
152
+ # 'pages' => 2,
153
+ # 'per_page' => 20,
154
+ # 'page' => 1
155
+ # }
156
+
157
+ get '/paginated_documents.json?page=2'
158
+
159
+ # returns Array with 10 next documents
160
+ # returns in the headers pagination headers
161
+ # {
162
+ # 'pages' => 2,
163
+ # 'per_page' => 20,
164
+ # 'page' => 2
165
+ # }
166
+ ```
167
+
134
168
  ## Azeroth::Decorator
135
169
 
136
170
  [Decorators](https://www.rubydoc.info/gems/azeroth/Azeroth/Decorator) are
137
171
  used to define how an object is exposed as json on controller responses
138
172
  defining which and how fields will be exposed
139
173
 
174
+ Exposing options:
175
+
176
+ - as: custom key to expose the value as
177
+ - if: method/block to be called checking if an attribute should or should not be exposed
178
+ - decorator: flag to use or not a decorator or decorator class to be used
179
+ - reader: Flag indicating if a reader to access the attribute should be created. usefull if you want method_missing to take over
180
+ - override: Flag indicating if an existing method should be overriden. This is useful when a method acessor was included from another module
181
+
140
182
  ```ruby
141
183
  # pokemon/decorator.rb
142
184
 
@@ -172,35 +214,6 @@ defining which and how fields will be exposed
172
214
  end
173
215
  ```
174
216
 
175
- ```ruby
176
- class PaginatedDocumentsController < ApplicationController
177
- include Azeroth::Resourceable
178
-
179
- resource_for :document, only: 'index', paginated: true
180
- end
181
-
182
- 30.times { create(:document) }
183
-
184
- get '/paginated_documents.json'
185
-
186
- # returns Array with 20 first documents
187
- # returns in the headers pagination headers
188
- {
189
- 'pages' => 2,
190
- 'per_page' => 20,
191
- 'page' => 1
192
- }
193
-
194
- get '/paginated_documents.json?page=2'
195
-
196
- # returns Array with 10 next documents
197
- # returns in the headers pagination headers
198
- {
199
- 'pages' => 2,
200
- 'per_page' => 20,
201
- 'page' => 2
202
- }
203
- ```
204
217
  Exposing is done through the class method
205
218
  [expose](https://www.rubydoc.info/gems/azeroth/Azeroth/Decorator#expose-class_method)
206
219
  which accepts several options:
data/azeroth.gemspec CHANGED
@@ -18,20 +18,20 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ['lib']
20
20
 
21
- gem.add_runtime_dependency 'activesupport', '~> 5.2.x'
21
+ gem.add_runtime_dependency 'activesupport', '~> 7.0.x'
22
22
  gem.add_runtime_dependency 'darthjee-active_ext', '>= 1.3.2'
23
23
  gem.add_runtime_dependency 'jace', '>= 0.1.1'
24
- gem.add_runtime_dependency 'sinclair', '>= 1.6.7'
24
+ gem.add_runtime_dependency 'sinclair', '>= 2.0.0'
25
25
 
26
- gem.add_development_dependency 'actionpack', '5.2.8.1'
27
- gem.add_development_dependency 'activerecord', '5.2.8.1'
28
- gem.add_development_dependency 'bundler', '~> 2.3.14'
26
+ gem.add_development_dependency 'actionpack', '7.0.4.3'
27
+ gem.add_development_dependency 'activerecord', '7.0.4.3'
28
+ gem.add_development_dependency 'bundler', '~> 2.4.8'
29
29
  gem.add_development_dependency 'factory_bot', '6.2.1'
30
30
  gem.add_development_dependency 'minitest', '5.16.2'
31
- gem.add_development_dependency 'nokogiri', '1.13.6'
31
+ gem.add_development_dependency 'nokogiri', '1.13.8'
32
32
  gem.add_development_dependency 'pry', '0.14.1'
33
33
  gem.add_development_dependency 'pry-nav', '1.0.0'
34
- gem.add_development_dependency 'rails', '5.2.8.1'
34
+ gem.add_development_dependency 'rails', '7.0.4.3'
35
35
  gem.add_development_dependency 'rails-controller-testing', '1.0.5'
36
36
  gem.add_development_dependency 'rake', '13.0.6'
37
37
  gem.add_development_dependency 'reek', '6.0.3'
@@ -47,6 +47,7 @@ Gem::Specification.new do |gem|
47
47
  gem.add_development_dependency 'rubycritic', '4.7.0'
48
48
  gem.add_development_dependency 'shoulda-matchers', '4.3.0'
49
49
  gem.add_development_dependency 'simplecov', '0.21.2'
50
+ gem.add_development_dependency 'sprockets-rails', '3.4.2'
50
51
  gem.add_development_dependency 'sqlite3', '1.4.2'
51
52
  gem.add_development_dependency 'tzinfo-data', '~> 1.2022.1'
52
53
  gem.add_development_dependency 'yard', '0.9.27'
@@ -1,4 +1,5 @@
1
1
  ignore:
2
+ - lib/azeroth/decorator/class_methods.rb
2
3
  - lib/azeroth/decorator/options.rb
3
4
  - lib/azeroth/resourceable/class_methods.rb
4
5
  - lib/azeroth/resourceable.rb
@@ -0,0 +1,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Azeroth
4
+ class Decorator
5
+ # @api private
6
+ # @author Darthjee
7
+ #
8
+ # Class methods added by {Decorator}
9
+ #
10
+ # @see ClassMethods#expose
11
+ module ClassMethods
12
+ # @api private
13
+ #
14
+ # All attributes exposed
15
+ #
16
+ # @return [Hash<Symbol,Hash>]
17
+ def attributes_map
18
+ @attributes_map ||= build_attributes_map
19
+ end
20
+
21
+ private
22
+
23
+ # @api private
24
+ # @private
25
+ #
26
+ # Initialize attributes to be exposed map
27
+ #
28
+ # When the class inherity from another
29
+ # decorator, the new class should expose the
30
+ # same attributes.
31
+ #
32
+ # @return [Hash<Symbol,Hash>]
33
+ def build_attributes_map
34
+ superclass.try(:attributes_map).dup || {}
35
+ end
36
+
37
+ # @visibility public
38
+ # @api (see Decorator.expose)
39
+ # @private
40
+ #
41
+ # Expose attributes on json decorated
42
+ #
43
+ # @param (see Decorator.expose)
44
+ # @option (see Decorator.expose)
45
+ #
46
+ # @return (see Decorator.expose)
47
+ #
48
+ # @example Simple usage
49
+ # class DummyModel
50
+ # include ActiveModel::Model
51
+ #
52
+ # attr_accessor :id, :first_name, :last_name, :age,
53
+ # :favorite_pokemon
54
+ #
55
+ # class Decorator < Azeroth::Decorator
56
+ # expose :name
57
+ # expose :age
58
+ # expose :favorite_pokemon, as: :pokemon
59
+ #
60
+ # def name
61
+ # [object.first_name, object.last_name].join(' ')
62
+ # end
63
+ # end
64
+ # end
65
+ #
66
+ # @example With relations
67
+ # # pokemon/decorator.rb
68
+ #
69
+ # class Pokemon::Decorator < Azeroth::Decorator
70
+ # expose :name
71
+ # expose :previous_form_name, as: :evolution_of, if: :evolution?
72
+ #
73
+ # def evolution?
74
+ # previous_form
75
+ # end
76
+ #
77
+ # def previous_form_name
78
+ # previous_form.name
79
+ # end
80
+ # end
81
+ #
82
+ # # pokemon/favorite_decorator.rb
83
+ #
84
+ # class Pokemon::FavoriteDecorator < Pokemon::Decorator
85
+ # expose :nickname
86
+ # end
87
+ #
88
+ # # pokemon_master/decorator.rb
89
+ #
90
+ # class PokemonMaster < ActiveRecord::Base
91
+ # has_one :favorite_pokemon, -> { where(favorite: true) },
92
+ # class_name: 'Pokemon'
93
+ # has_many :pokemons
94
+ # end
95
+ #
96
+ # # pokemon.rb
97
+ #
98
+ # class Pokemon < ActiveRecord::Base
99
+ # belongs_to :pokemon_master
100
+ # has_one :previous_form,
101
+ # class_name: 'Pokemon',
102
+ # foreign_key: :previous_form_id
103
+ # end
104
+ #
105
+ # # pokemon_master.rb
106
+ #
107
+ # class PokemonMaster::Decorator < Azeroth::Decorator
108
+ # expose :name
109
+ # expose :age
110
+ # expose :favorite_pokemon, decorator: Pokemon::FavoriteDecorator
111
+ # expose :pokemons
112
+ #
113
+ # def name
114
+ # [
115
+ # first_name,
116
+ # last_name
117
+ # ].compact.join(' ')
118
+ # end
119
+ # end
120
+ #
121
+ # # schema.rb
122
+ #
123
+ # ActiveRecord::Schema.define do
124
+ # self.verbose = false
125
+ #
126
+ # create_table :pokemon_masters, force: true do |t|
127
+ # t.string :first_name, null: false
128
+ # t.string :last_name
129
+ # t.integer :age, null: false
130
+ # end
131
+ #
132
+ # create_table :pokemons, force: true do |t|
133
+ # t.string :name, null: false
134
+ # t.string :nickname
135
+ # t.integer :pokemon_master_id
136
+ # t.boolean :favorite
137
+ # t.integer :previous_form_id
138
+ # t.index %i[pokemon_master_id favorite], unique: true
139
+ # end
140
+ #
141
+ # add_foreign_key 'pokemons', 'pokemon_masters'
142
+ # end
143
+ #
144
+ # # test.rb
145
+ #
146
+ # master = PokemonMaster.create(
147
+ # first_name: 'Ash',
148
+ # last_name: 'Ketchum',
149
+ # age: 10
150
+ # )
151
+ #
152
+ # master.create_favorite_pokemon(
153
+ # name: 'pikachu',
154
+ # nickname: 'Pikachu'
155
+ # )
156
+ #
157
+ # metapod = Pokemon.create(name: :metapod)
158
+ #
159
+ # master.pokemons.create(
160
+ # name: 'butterfree', previous_form: metapod
161
+ # )
162
+ # master.pokemons.create(name: 'squirtle')
163
+ #
164
+ # decorator = PokemonMaster::Decorator.new(master)
165
+ #
166
+ # decorator.as_json
167
+ # # returns
168
+ # # {
169
+ # # 'age' => 10,
170
+ # # 'name' => 'Ash Ketchum',
171
+ # # 'favorite_pokemon' => {
172
+ # # 'name' => 'pikachu',
173
+ # # 'nickname' => 'Pikachu'
174
+ # # },
175
+ # # 'pokemons' => [{
176
+ # # 'name' => 'butterfree',
177
+ # # 'evolution_of' => 'metapod'
178
+ # # }, {
179
+ # # 'name' => 'squirtle'
180
+ # # }, {
181
+ # # 'name' => 'pikachu'
182
+ # # }]
183
+ # # }
184
+ #
185
+ # @example With method building options
186
+ # ActiveRecord::Schema.define do
187
+ # self.verbose = false
188
+ #
189
+ # create_table :websites, force: true do |t|
190
+ # t.string :domain, null: false
191
+ # t.integer :port, limit: 2, unsigned: true
192
+ # t.string :protocol, limit: 5
193
+ # end
194
+ # end
195
+ #
196
+ # class Website < ActiveRecord::Base
197
+ # end
198
+ #
199
+ # class Website < ActiveRecord::Base
200
+ # module WithLocation
201
+ # def location
202
+ # "#{protocol}://#{domain}:#{port}"
203
+ # end
204
+ #
205
+ # def protocol
206
+ # website.protocol || '*'
207
+ # end
208
+ #
209
+ # def domain
210
+ # website.domain || '*'
211
+ # end
212
+ #
213
+ # def port
214
+ # website.port || '*'
215
+ # end
216
+ # end
217
+ # end
218
+ #
219
+ # class Website < ActiveRecord::Base
220
+ # class Decorator < Azeroth::Decorator
221
+ # include WithLocation
222
+ #
223
+ # expose :location, override: false
224
+ #
225
+ # alias website object
226
+ # end
227
+ # end
228
+ #
229
+ # website = Website.create(
230
+ # protocol: :http,
231
+ # domain: 'google.com'
232
+ # )
233
+ #
234
+ # decorator = Website::Decorator.new(website)
235
+ #
236
+ # decorator.as_json
237
+ #
238
+ # # returns
239
+ # # { 'location' => 'http://google.com:*' }
240
+ def expose(attribute, **options_hash)
241
+ options = Decorator::Options.new(options_hash)
242
+
243
+ MethodBuilder.build_reader(self, attribute, options)
244
+
245
+ attributes_map[attribute] = options
246
+ end
247
+ end
248
+ end
249
+ end
@@ -25,7 +25,9 @@ module Azeroth
25
25
  # @return [Hash]
26
26
  def as_json
27
27
  attributes_map.inject({}) do |hash, (method, options)|
28
- new_hash = KeyValueExtractor.new(decorator, method, options).as_json
28
+ new_hash = KeyValueExtractor.new(
29
+ decorator: decorator, attribute: method, options: options
30
+ ).as_json
29
31
  hash.merge!(new_hash)
30
32
  end
31
33
  end
@@ -11,18 +11,14 @@ module Azeroth
11
11
  #
12
12
  # A decorator is infered for the value / attribute
13
13
  # or used from the options given
14
- class KeyValueExtractor
14
+ class KeyValueExtractor < Sinclair::Model
15
+ initialize_with(:decorator, :attribute, :options)
15
16
  # @param decorator [Decorator] decorator object
16
17
  # @param attribute [Symbol] attribute to be used on output hash
17
18
  # @param options [Decorator::Options] decoration options
18
19
  # @option options if [Proc,Symbol] conditional to be
19
20
  # checked when exposing field
20
21
  # (see {Decorator::Options#if})
21
- def initialize(decorator, attribute, options)
22
- @decorator = decorator
23
- @attribute = attribute
24
- @options = options
25
- end
26
22
 
27
23
  # Return hash for attribute
28
24
  #
@@ -38,9 +34,8 @@ module Azeroth
38
34
  }
39
35
  end
40
36
 
41
- # private
37
+ private
42
38
 
43
- attr_reader :decorator, :attribute, :options
44
39
  # @method decorator
45
40
  # @api private
46
41
  # @private
@@ -7,20 +7,36 @@ module Azeroth
7
7
  # Responsible for building readers for attributes
8
8
  # @api private
9
9
  class MethodBuilder < Sinclair
10
- # Builds reader
11
- #
12
- # reaader delegate method calls to @object
13
- #
14
- # @return [Array<Sinclair::MethodDefinition>]
15
- def self.build_reader(klass, attribute)
16
- new(klass).build_reader(attribute)
10
+ class << self
11
+ # Builds reader
12
+ #
13
+ # reaader delegate method calls to @object
14
+ #
15
+ # @return [Array<Sinclair::MethodDefinition>]
16
+ def build_reader(klass, attribute, options)
17
+ new(klass, options).build_reader(attribute)
18
+ end
19
+ end
20
+
21
+ def initialize(klass, options)
22
+ super(klass)
23
+ @options = options
17
24
  end
18
25
 
19
26
  # (see MethodBuilder.build_reader)
20
27
  def build_reader(attribute)
28
+ return if skip_creation?(attribute)
29
+
21
30
  add_method(attribute, "@object.#{attribute}")
22
31
  build
23
32
  end
33
+
34
+ def skip_creation?(attribute)
35
+ return true unless options.reader
36
+ return false unless klass.method_defined?(attribute)
37
+
38
+ !options.override
39
+ end
24
40
  end
25
41
  end
26
42
  end
@@ -13,7 +13,9 @@ module Azeroth
13
13
  DEFAULT_OPTIONS = {
14
14
  as: nil,
15
15
  if: nil,
16
- decorator: true
16
+ decorator: true,
17
+ override: true,
18
+ reader: true
17
19
  }.freeze
18
20
 
19
21
  with_options DEFAULT_OPTIONS