azeroth 1.0.0 → 2.0.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +3 -3
  3. data/.rubocop.yml +15 -1
  4. data/Dockerfile +2 -2
  5. data/Gemfile +30 -0
  6. data/Makefile +6 -0
  7. data/README.md +13 -1
  8. data/azeroth.gemspec +6 -36
  9. data/config/check_specs.yml +1 -0
  10. data/lib/azeroth/controller_interface.rb +3 -2
  11. data/lib/azeroth/decorator/class_methods.rb +249 -0
  12. data/lib/azeroth/decorator/hash_builder.rb +1 -0
  13. data/lib/azeroth/decorator/method_builder.rb +23 -7
  14. data/lib/azeroth/decorator/options.rb +3 -1
  15. data/lib/azeroth/decorator.rb +35 -192
  16. data/lib/azeroth/model.rb +1 -0
  17. data/lib/azeroth/request_handler/index.rb +7 -33
  18. data/lib/azeroth/request_handler/pagination.rb +53 -0
  19. data/lib/azeroth/request_handler.rb +11 -9
  20. data/lib/azeroth/resourceable/builder.rb +3 -2
  21. data/lib/azeroth/version.rb +1 -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/production.rb +2 -2
  26. data/spec/dummy/config/puma.rb +3 -3
  27. data/spec/dummy/db/schema.rb +6 -0
  28. data/spec/integration/readme/controllers/games_controller_spec.rb +1 -1
  29. data/spec/integration/yard/azeroth/decorator_spec.rb +15 -0
  30. data/spec/integration/yard/controllers/games_controller_spec.rb +1 -1
  31. data/spec/integration/yard/controllers/paginated_documents_controller_spec.rb +1 -1
  32. data/spec/lib/azeroth/controller_interface_spec.rb +1 -1
  33. data/spec/lib/azeroth/decorator/method_builder_spec.rb +61 -2
  34. data/spec/lib/azeroth/decorator_spec.rb +96 -5
  35. data/spec/lib/azeroth/request_handler/pagination_spec.rb +133 -0
  36. data/spec/lib/azeroth/request_handler/update_spec.rb +1 -1
  37. data/spec/spec_helper.rb +1 -1
  38. data/spec/support/app/controllers/controller.rb +1 -0
  39. data/spec/support/matchers/add_method.rb +6 -4
  40. data/spec/support/shared_examples/request_handler.rb +1 -5
  41. metadata +17 -569
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72a4e7c837a4053e850b9479d42cfccd2e279ac028f57da2c5d3baed6c6babfa
4
- data.tar.gz: 84591492fc859555d87b86704a61e68c0b8171e42a8fd77d1f4e502216516fc0
3
+ metadata.gz: 65ba326980d7b37bb878cf1f230dea518561a68fdc337e53b9ca6391766957f3
4
+ data.tar.gz: 6f39fcee3056ad4eccbc72d1fc268460144bf3ec442cc4292f31a3bbf98f77b4
5
5
  SHA512:
6
- metadata.gz: ad6ca16b03b7b99b051bf9bfd25e25d1d522fa89e5b5ff31e495fa1af4fbb193dd269b062d8a87205aa8eeea84bd8a405b84de5d1663d7a1636239d4dd6b973f
7
- data.tar.gz: 3eec0d1c24bb260dab5fb3ccccf5deedc8ae2eeff7378084fd501c82e740f776f1e3a14e32cee939155f5f833c4c1affdd3ca19f6636681f664be3deca36ed23
6
+ metadata.gz: 10907b167920b813c0de6aa9a77dad7366cf8a16179a4179c6343ab4544d9b86560ed812a2d897aaf702d4d556b3d77d45de2a03b00a47e34368488230ef2cf7
7
+ data.tar.gz: 805c0ea2d52a06e1c2472ba390d973fcf0b06e3ae16fcf03cde5cae97a70b0b25072aff1f7295a42cbcfd4c228a62e32481e126bbc9191394f2031d1df5b7482
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.2.0
25
+ - image: darthjee/circleci_rails_gems:2.0.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.2.0
44
+ - image: darthjee/circleci_rails_gems:2.0.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.2.0
69
+ - image: darthjee/circleci_rails_gems:2.0.0
70
70
  environment:
71
71
  PROJECT: azeroth
72
72
  steps:
data/.rubocop.yml CHANGED
@@ -2,7 +2,8 @@ require: rubocop-rspec
2
2
  inherit_from: .rubocop_todo.yml
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 2.5
5
+ TargetRubyVersion: 3.3
6
+ NewCops: enable
6
7
 
7
8
  Metrics/BlockLength:
8
9
  Exclude:
@@ -35,3 +36,16 @@ RSpec/MultipleExpectations:
35
36
  Exclude:
36
37
  - 'spec/integration/readme/**/*_spec.rb'
37
38
  - 'spec/integration/yard/**/*_spec.rb'
39
+
40
+ RSpec/MultipleMemoizedHelpers:
41
+ Enabled: false
42
+
43
+ Gemspec/RequireMFA:
44
+ Enabled: false
45
+
46
+ Lint/EmptyBlock:
47
+ Exclude:
48
+ - 'spec/**/*_spec.rb'
49
+
50
+ Lint/EmptyClass:
51
+ Enabled: false
data/Dockerfile CHANGED
@@ -1,6 +1,6 @@
1
- FROM darthjee/scripts:0.3.1 as scripts
1
+ FROM darthjee/scripts:0.4.3 as scripts
2
2
 
3
- FROM darthjee/rails_gems:1.2.0 as base
3
+ FROM darthjee/rails_gems:2.0.0 as base
4
4
 
5
5
  COPY --chown=app:app ./ /home/app/app/
6
6
 
data/Gemfile CHANGED
@@ -3,3 +3,33 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
+
7
+ gem 'actionpack', '7.2.2.1'
8
+ gem 'activerecord', '7.2.2.1'
9
+ gem 'bundler', '~> 2.6.8'
10
+ gem 'factory_bot', '6.2.1'
11
+ gem 'minitest', '5.25.4'
12
+ gem 'nokogiri', '1.18.8'
13
+ gem 'pry', '0.14.2'
14
+ gem 'pry-nav', '1.0.0'
15
+ gem 'rails', '7.2.2.1'
16
+ gem 'rails-controller-testing', '1.0.5'
17
+ gem 'rake', '13.2.1'
18
+ gem 'reek', '6.4.0'
19
+ gem 'rspec', '3.13.0'
20
+ gem 'rspec-collection_matchers', '1.2.1'
21
+ gem 'rspec-core', '3.13.3'
22
+ gem 'rspec-expectations', '3.13.3'
23
+ gem 'rspec-mocks', '3.13.2'
24
+ gem 'rspec-rails', '8.0.0'
25
+ gem 'rspec-support', '3.13.2'
26
+ gem 'rubocop', '1.75.5'
27
+ gem 'rubocop-rspec', '3.6.0'
28
+ gem 'rubycritic', '4.9.2'
29
+ gem 'shoulda-matchers', '6.5.0'
30
+ gem 'simplecov', '0.22.0'
31
+ gem 'sprockets-rails', '3.5.2'
32
+ gem 'sqlite3', '1.4.2'
33
+ gem 'tzinfo-data', '~> 1.2025.2'
34
+ gem 'yard', '0.9.37'
35
+ gem 'yardstick', '0.9.9'
data/Makefile ADDED
@@ -0,0 +1,6 @@
1
+ .PHONY: dev
2
+
3
+ PROJECT?=azeroth
4
+
5
+ dev:
6
+ docker-compose run $(PROJECT) /bin/bash
data/README.md CHANGED
@@ -11,7 +11,7 @@ Azeroth
11
11
 
12
12
  Yard Documentation
13
13
  -------------------
14
- [https://www.rubydoc.info/gems/azeroth/1.0.0](https://www.rubydoc.info/gems/azeroth/1.0.0)
14
+ [https://www.rubydoc.info/gems/azeroth/2.0.0](https://www.rubydoc.info/gems/azeroth/2.0.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: [2.0.0](https://github.com/darthjee/azeroth/tree/2.0.0)
29
+
30
+ [Next release](https://github.com/darthjee/azeroth/compare/2.0.0...master)
31
+
28
32
  Installation
29
33
  ---------------
30
34
 
@@ -167,6 +171,14 @@ It accepts options
167
171
  used to define how an object is exposed as json on controller responses
168
172
  defining which and how fields will be exposed
169
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
+
170
182
  ```ruby
171
183
  # pokemon/decorator.rb
172
184
 
data/azeroth.gemspec CHANGED
@@ -10,46 +10,16 @@ Gem::Specification.new do |gem|
10
10
  gem.authors = ['Darthjee']
11
11
  gem.email = ['darthjee@gmail.com']
12
12
  gem.summary = 'Azeroth'
13
- gem.description = gem.description
13
+ gem.description = 'Rails controller builder'
14
14
  gem.homepage = 'https://github.com/darthjee/azeroth'
15
+ gem.required_ruby_version = '>= 3.3.1'
15
16
 
16
17
  gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
18
  gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
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', '~> 7.0.x'
22
- gem.add_runtime_dependency 'darthjee-active_ext', '>= 1.3.2'
23
- gem.add_runtime_dependency 'jace', '>= 0.1.1'
24
- gem.add_runtime_dependency 'sinclair', '>= 2.0.0'
25
-
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
- gem.add_development_dependency 'factory_bot', '6.2.1'
30
- gem.add_development_dependency 'minitest', '5.16.2'
31
- gem.add_development_dependency 'nokogiri', '1.13.8'
32
- gem.add_development_dependency 'pry', '0.14.1'
33
- gem.add_development_dependency 'pry-nav', '1.0.0'
34
- gem.add_development_dependency 'rails', '7.0.4.3'
35
- gem.add_development_dependency 'rails-controller-testing', '1.0.5'
36
- gem.add_development_dependency 'rake', '13.0.6'
37
- gem.add_development_dependency 'reek', '6.0.3'
38
- gem.add_development_dependency 'rspec', '3.11.0'
39
- gem.add_development_dependency 'rspec-collection_matchers', '1.2.0'
40
- gem.add_development_dependency 'rspec-core', '3.11.0'
41
- gem.add_development_dependency 'rspec-expectations', '3.11.0'
42
- gem.add_development_dependency 'rspec-mocks', '3.11.1'
43
- gem.add_development_dependency 'rspec-rails', '5.1.2'
44
- gem.add_development_dependency 'rspec-support', '3.11.0'
45
- gem.add_development_dependency 'rubocop', '0.80.1'
46
- gem.add_development_dependency 'rubocop-rspec', '1.38.1'
47
- gem.add_development_dependency 'rubycritic', '4.7.0'
48
- gem.add_development_dependency 'shoulda-matchers', '4.3.0'
49
- gem.add_development_dependency 'simplecov', '0.21.2'
50
- gem.add_development_dependency 'sprockets-rails', '3.4.2'
51
- gem.add_development_dependency 'sqlite3', '1.4.2'
52
- gem.add_development_dependency 'tzinfo-data', '~> 1.2022.1'
53
- gem.add_development_dependency 'yard', '0.9.27'
54
- gem.add_development_dependency 'yardstick', '0.9.9'
21
+ gem.add_dependency 'activesupport', '~> 7.2.x'
22
+ gem.add_dependency 'darthjee-active_ext', '>= 1.3.2'
23
+ gem.add_dependency 'jace', '>= 0.1.1'
24
+ gem.add_dependency 'sinclair', '>= 3.0.0'
55
25
  end
@@ -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
@@ -60,6 +60,7 @@ module Azeroth
60
60
  private
61
61
 
62
62
  attr_reader :controller
63
+
63
64
  # @method controller
64
65
  # @private
65
66
  # @api private
@@ -78,9 +79,9 @@ module Azeroth
78
79
  # method called
79
80
  #
80
81
  # @return [Object]
81
- def method_missing(method_name, *args)
82
+ def method_missing(method_name, *)
82
83
  if controller.respond_to?(method_name, true)
83
- controller.send(method_name, *args)
84
+ controller.send(method_name, *)
84
85
  else
85
86
  super
86
87
  end
@@ -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
@@ -35,6 +35,7 @@ module Azeroth
35
35
  private
36
36
 
37
37
  attr_reader :decorator
38
+
38
39
  # @method decorator
39
40
  # @api private
40
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