azeroth 0.10.1 → 1.1.0

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