azeroth 0.6.3 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +37 -2
  3. data/.rubocop.yml +11 -0
  4. data/Dockerfile +2 -2
  5. data/README.md +164 -1
  6. data/azeroth.gemspec +6 -5
  7. data/lib/azeroth.rb +1 -0
  8. data/lib/azeroth/decorator.rb +123 -0
  9. data/lib/azeroth/decorator/key_value_extractor.rb +2 -2
  10. data/lib/azeroth/decorator/options.rb +3 -3
  11. data/lib/azeroth/model.rb +3 -1
  12. data/lib/azeroth/options.rb +38 -1
  13. data/lib/azeroth/request_handler.rb +39 -2
  14. data/lib/azeroth/request_handler/create.rb +31 -5
  15. data/lib/azeroth/request_handler/update.rb +3 -3
  16. data/lib/azeroth/resourceable.rb +27 -0
  17. data/lib/azeroth/resourceable/class_methods.rb +120 -6
  18. data/lib/azeroth/routes_builder.rb +2 -1
  19. data/lib/azeroth/version.rb +1 -1
  20. data/spec/controllers/pokemon_masters_controller_spec.rb +56 -0
  21. data/spec/controllers/pokemons_controller_spec.rb +56 -0
  22. data/spec/dummy/app/controllers/games_controller.rb +22 -0
  23. data/spec/dummy/app/controllers/pokemon_masters_controller.rb +9 -0
  24. data/spec/dummy/app/controllers/pokemons_controller.rb +27 -0
  25. data/spec/dummy/app/controllers/publishers_controller.rb +8 -0
  26. data/spec/dummy/app/models/game.rb +5 -0
  27. data/spec/dummy/app/models/game/decorator.rb +9 -0
  28. data/spec/dummy/app/models/name_decorator.rb +5 -0
  29. data/spec/dummy/app/models/pokemon.rb +8 -0
  30. data/spec/dummy/app/models/pokemon/decorator.rb +16 -0
  31. data/spec/dummy/app/models/pokemon/favorite_decorator.rb +7 -0
  32. data/spec/dummy/app/models/pokemon_master.rb +7 -0
  33. data/spec/dummy/app/models/pokemon_master/decorator.rb +17 -0
  34. data/spec/dummy/app/models/publisher.rb +5 -0
  35. data/spec/dummy/config/routes.rb +8 -0
  36. data/spec/dummy/db/schema.rb +26 -0
  37. data/spec/integration/readme/azeroth/decorator_spec.rb +48 -0
  38. data/spec/integration/readme/controllers/games_controller_spec.rb +42 -0
  39. data/spec/integration/yard/azeroth/decorator_spec.rb +58 -17
  40. data/spec/integration/yard/controllers/games_controller_spec.rb +42 -0
  41. data/spec/lib/azeroth/decorator/key_value_extractor_spec.rb +32 -0
  42. data/spec/lib/azeroth/decorator_spec.rb +28 -2
  43. data/spec/lib/azeroth/request_handler/create_spec.rb +166 -0
  44. data/spec/lib/azeroth/request_handler/update_spec.rb +91 -0
  45. data/spec/lib/azeroth/request_handler_spec.rb +3 -1
  46. data/spec/support/app/controllers/request_handler_controller.rb +12 -1
  47. data/spec/support/factories/game.rb +8 -0
  48. data/spec/support/factories/pokemon.rb +8 -0
  49. data/spec/support/factories/pokemon_master.rb +9 -0
  50. data/spec/support/factories/publisher.rb +7 -0
  51. data/spec/support/shared_examples/request_handler.rb +5 -2
  52. metadata +70 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff267cb06b99dea008313ebf30569e33565cbbe210602b3008bd859016c28755
4
- data.tar.gz: 494a34050515c0dcd7e05ecdce0151e26f089e0ae39d3c1652a38d210ec1f99d
3
+ metadata.gz: dbddea8e78679a5a20ccb7c04fd94da0f55718713add9d352d97bff07921df9c
4
+ data.tar.gz: e5cc1161a4b36df822458101ce8e4f443a8be05eaf5a0430a0ae09db313e8794
5
5
  SHA512:
6
- metadata.gz: 1e89465dc060b9ab4a0b9a6c95e6b2cb054ab08efd32ebf312bbe94f751f5d505f20230632e8435d0de57898f9bbd9875215fd23c59f51a1de6516a2bfb0e273
7
- data.tar.gz: fe6c480bc37d5e03ea61eb22df4c66154537fca6917d671e19fac1ce7bdf5de5051dfd736de0c7529ffa240512f615bd5a1c1a2e5ed285a3326956bd59758dd4
6
+ metadata.gz: dd4a272074e69969eddf5d7c3fa3b96d1b0154e117d81b6be419b59573169e100773ecbd97d96f2465a72a5c5e0458c3d7e13d12b5e2c1ed710412a8139e7b4c
7
+ data.tar.gz: 0eb07f9c6da10355a9c9db82e555d0eecfaefa75abce57603109efa8aa841a53a70f372afeef6dafe5df9e88504a4180e76f66a8060cbf5841dbdeb6895f6a9f
@@ -1,8 +1,24 @@
1
1
  version: 2
2
+ workflows:
3
+ version: 2
4
+ test-and-build:
5
+ jobs:
6
+ - test:
7
+ filters:
8
+ tags:
9
+ only: /.*/
10
+ - build-and-release:
11
+ requires: [test]
12
+ filters:
13
+ tags:
14
+ only: /\d+\.\d+\.\d+/
15
+ branches:
16
+ only:
17
+ - master
2
18
  jobs:
3
- build:
19
+ test:
4
20
  docker:
5
- - image: darthjee/circleci_rails_gems:0.5.1
21
+ - image: darthjee/circleci_rails_gems:0.5.4
6
22
  environment:
7
23
  PROJECT: azeroth
8
24
  steps:
@@ -34,3 +50,22 @@ jobs:
34
50
  - run:
35
51
  name: Check unit tests
36
52
  command: check_specs
53
+ build-and-release:
54
+ docker:
55
+ - image: darthjee/circleci_rails_gems:0.5.4
56
+ environment:
57
+ PROJECT: azeroth
58
+ steps:
59
+ - checkout
60
+ - run:
61
+ name: Bundle Install
62
+ command: bundle install
63
+ - run:
64
+ name: Signin
65
+ command: build_gem.sh signin
66
+ - run:
67
+ name: Build Gem
68
+ command: build_gem.sh build
69
+ - run:
70
+ name: Push Gem
71
+ command: build_gem.sh push
@@ -7,6 +7,7 @@ AllCops:
7
7
  Metrics/BlockLength:
8
8
  Exclude:
9
9
  - 'spec/**/*_spec.rb'
10
+ - 'spec/dummy/db/schema.rb'
10
11
  - 'spec/support/shared_*/**/*.rb'
11
12
  - 'azeroth.gemspec'
12
13
 
@@ -24,3 +25,13 @@ Style/HashTransformKeys:
24
25
 
25
26
  Style/HashTransformValues:
26
27
  Enabled: true
28
+
29
+ RSpec/ExampleLength:
30
+ Exclude:
31
+ - 'spec/integration/readme/**/*_spec.rb'
32
+ - 'spec/integration/yard/**/*_spec.rb'
33
+
34
+ RSpec/MultipleExpectations:
35
+ Exclude:
36
+ - 'spec/integration/readme/**/*_spec.rb'
37
+ - 'spec/integration/yard/**/*_spec.rb'
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
- FROM darthjee/rails_gems:0.5.2 as base
2
- FROM darthjee/scripts:0.1.7 as scripts
1
+ FROM darthjee/rails_gems:0.5.4 as base
2
+ FROM darthjee/scripts:0.1.8 as scripts
3
3
 
4
4
  ######################################
5
5
 
data/README.md CHANGED
@@ -11,4 +11,167 @@ Azeroth
11
11
 
12
12
  Yard Documentation
13
13
  -------------------
14
- [https://www.rubydoc.info/gems/azeroth/0.6.3](https://www.rubydoc.info/gems/azeroth/0.6.3)
14
+ [https://www.rubydoc.info/gems/azeroth/0.7.1](https://www.rubydoc.info/gems/azeroth/0.7.2)
15
+
16
+ Azeroth has been designed making the coding of controllers easier
17
+ as routes in controllers are usually copy, paste and replace of same
18
+ code.
19
+
20
+ Azeroth was originally developed for controller actions
21
+ which will respond with json or template rendering based
22
+ on the requested format `.json` or `.html` where `html` rendering
23
+ does not perform database operations
24
+
25
+ Future versions will enable `html` rendering to also perform
26
+ database operations.
27
+
28
+ Installation
29
+ ---------------
30
+
31
+ - Install it
32
+
33
+ ```ruby
34
+ gem install azeroth
35
+ ```
36
+
37
+ - Or add Sinclair to your `Gemfile` and `bundle install`:
38
+
39
+ ```ruby
40
+ gem 'azeroth'
41
+ ```
42
+
43
+ ```bash
44
+ bundle install azeroth
45
+ ```
46
+
47
+ Usage
48
+ -----
49
+
50
+ ## Azeroth::Resourceable
51
+
52
+ [Resourceable](https://www.rubydoc.info/gems/azeroth/Azeroth/Resourceable)
53
+ module adds class method [resource_for](https://www.rubydoc.info/gems/azeroth/Azeroth/Resourceable/ClassMethods#resource_for-instance_method)
54
+ which adds a resource and action methods for `create`, `show`, `index`,
55
+ `update`, `delete`, `edit`
56
+
57
+ It accepts options
58
+ - only: List of actions to be built
59
+ - except: List of actions to not to be built
60
+ - decorator: Decorator class or flag allowing/disallowing decorators
61
+ - before_save: Method/Proc to be ran before saving the resource on create or update
62
+ - build_with: Method/Block to be ran when building the reource on create
63
+
64
+ ```ruby
65
+ # publishers_controller.rb
66
+
67
+ class PublishersController < ApplicationController
68
+ include Azeroth::Resourceable
69
+ skip_before_action :verify_authenticity_token
70
+
71
+ resource_for :publisher, only: %i[create index]
72
+ end
73
+ ```
74
+
75
+ ```ruby
76
+ # games_controller.rb
77
+
78
+ class GamesController < ApplicationController
79
+ include Azeroth::Resourceable
80
+ skip_before_action :verify_authenticity_token
81
+
82
+ resource_for :game, except: :delete
83
+
84
+ private
85
+
86
+ def games
87
+ publisher.games
88
+ end
89
+
90
+ def publisher
91
+ @publisher ||= Publisher.find(publisher_id)
92
+ end
93
+
94
+ def publisher_id
95
+ params.require(:publisher_id)
96
+ end
97
+ end
98
+ ```
99
+
100
+ ```ruby
101
+ # pokemons_controller.rb
102
+
103
+ class PokemonsController < ApplicationController
104
+ include Azeroth::Resourceable
105
+
106
+ resource_for :pokemon,
107
+ only: %i[create update],
108
+ before_save: :set_favorite
109
+
110
+ private
111
+
112
+ def set_favorite
113
+ pokemon.favorite = true
114
+ end
115
+
116
+ def pokemons
117
+ master.pokemons
118
+ end
119
+
120
+ def master
121
+ @master ||= PokemonMaster.find(master_id)
122
+ end
123
+
124
+ def master_id
125
+ params.require(:pokemon_master_id)
126
+ end
127
+ end
128
+ ```
129
+
130
+ ## Azeroth::Decorator
131
+
132
+ [Decorators](https://www.rubydoc.info/gems/azeroth/Azeroth/Decorator) are
133
+ used to define how an object is exposed as json on controller responses
134
+ defining which and how fields will be exposed
135
+
136
+ ```ruby
137
+ # pokemon/decorator.rb
138
+
139
+ class Pokemon::Decorator < Azeroth::Decorator
140
+ expose :name
141
+ expose :previous_form_name, as: :evolution_of, if: :evolution?
142
+
143
+ def evolution?
144
+ previous_form
145
+ end
146
+
147
+ def previous_form_name
148
+ previous_form.name
149
+ end
150
+ end
151
+ ```
152
+
153
+ ```ruby
154
+ # pokemon/favorite_decorator.rb
155
+
156
+ class Pokemon::FavoriteDecorator < Pokemon::Decorator
157
+ expose :nickname
158
+ end
159
+ ```
160
+
161
+ ```ruby
162
+ # pokemon_master/decorator.rb
163
+
164
+ class PokemonMaster < ActiveRecord::Base
165
+ has_one :favorite_pokemon, -> { where(favorite: true) },
166
+ class_name: 'Pokemon'
167
+ has_many :pokemons
168
+ end
169
+ ```
170
+
171
+ Exposing is done through the class method
172
+ [expose](https://www.rubydoc.info/gems/azeroth/Azeroth/Decorator#expose-class_method)
173
+ which accepts several options:
174
+
175
+ - as: custom key to expose
176
+ - if: method/block to be called checking if an attribute should or should not be exposed
177
+ - decorator: flag to use or not a decorator or decorator class to be used
@@ -18,18 +18,19 @@ 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.0'
21
+ gem.add_runtime_dependency 'activesupport', '~> 5.2.4.3'
22
22
  gem.add_runtime_dependency 'darthjee-active_ext', '>= 1.3.2'
23
- gem.add_runtime_dependency 'sinclair', '>= 1.6.4'
23
+ gem.add_runtime_dependency 'jace', '>= 0.0.3'
24
+ gem.add_runtime_dependency 'sinclair', '>= 1.6.5'
24
25
 
25
- gem.add_development_dependency 'actionpack', '5.2.4.2'
26
- gem.add_development_dependency 'activerecord', '5.2.4.2'
26
+ gem.add_development_dependency 'actionpack', '5.2.4.3'
27
+ gem.add_development_dependency 'activerecord', '5.2.4.3'
27
28
  gem.add_development_dependency 'bundler', '1.16.1'
28
29
  gem.add_development_dependency 'factory_bot', '5.2.0'
29
30
  gem.add_development_dependency 'nokogiri', '1.10.9'
30
31
  gem.add_development_dependency 'pry', '0.12.2'
31
32
  gem.add_development_dependency 'pry-nav', '0.3.0'
32
- gem.add_development_dependency 'rails', '5.2.4.2'
33
+ gem.add_development_dependency 'rails', '5.2.4.3'
33
34
  gem.add_development_dependency 'rails-controller-testing', '1.0.4'
34
35
  gem.add_development_dependency 'rake', '13.0.1'
35
36
  gem.add_development_dependency 'reek', '5.6.0'
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'sinclair'
4
+ require 'jace'
4
5
 
5
6
  # @api public
6
7
  #
@@ -84,6 +84,8 @@ module Azeroth
84
84
  # to be called
85
85
  # checking if an attribute should or should not
86
86
  # be exposed
87
+ # @option options_hash decorator [FalseClass,TrueClass,Class]
88
+ # flag to use or not a decorator or decorator class to be used
87
89
  #
88
90
  # @return [Array<Symbol>]
89
91
  #
@@ -104,6 +106,125 @@ module Azeroth
104
106
  # end
105
107
  # end
106
108
  # end
109
+ #
110
+ # @example With relations
111
+ # # pokemon/decorator.rb
112
+ #
113
+ # class Pokemon::Decorator < Azeroth::Decorator
114
+ # expose :name
115
+ # expose :previous_form_name, as: :evolution_of, if: :evolution?
116
+ #
117
+ # def evolution?
118
+ # previous_form
119
+ # end
120
+ #
121
+ # def previous_form_name
122
+ # previous_form.name
123
+ # end
124
+ # end
125
+ #
126
+ # # pokemon/favorite_decorator.rb
127
+ #
128
+ # class Pokemon::FavoriteDecorator < Pokemon::Decorator
129
+ # expose :nickname
130
+ # end
131
+ #
132
+ # # pokemon_master/decorator.rb
133
+ #
134
+ # class PokemonMaster < ActiveRecord::Base
135
+ # has_one :favorite_pokemon, -> { where(favorite: true) },
136
+ # class_name: 'Pokemon'
137
+ # has_many :pokemons
138
+ # end
139
+ #
140
+ # # pokemon.rb
141
+ #
142
+ # class Pokemon < ActiveRecord::Base
143
+ # belongs_to :pokemon_master
144
+ # has_one :previous_form,
145
+ # class_name: 'Pokemon',
146
+ # foreign_key: :previous_form_id
147
+ # end
148
+ #
149
+ # # pokemon_master.rb
150
+ #
151
+ # class PokemonMaster::Decorator < Azeroth::Decorator
152
+ # expose :name
153
+ # expose :age
154
+ # expose :favorite_pokemon, decorator: Pokemon::FavoriteDecorator
155
+ # expose :pokemons
156
+ #
157
+ # def name
158
+ # [
159
+ # first_name,
160
+ # last_name
161
+ # ].compact.join(' ')
162
+ # end
163
+ # end
164
+ #
165
+ # # schema.rb
166
+ #
167
+ # ActiveRecord::Schema.define do
168
+ # self.verbose = false
169
+ #
170
+ # create_table :pokemon_masters, force: true do |t|
171
+ # t.string :first_name, null: false
172
+ # t.string :last_name
173
+ # t.integer :age, null: false
174
+ # end
175
+ #
176
+ # create_table :pokemons, force: true do |t|
177
+ # t.string :name, null: false
178
+ # t.string :nickname
179
+ # t.integer :pokemon_master_id
180
+ # t.boolean :favorite
181
+ # t.integer :previous_form_id
182
+ # t.index %i[pokemon_master_id favorite], unique: true
183
+ # end
184
+ #
185
+ # add_foreign_key 'pokemons', 'pokemon_masters'
186
+ # end
187
+ #
188
+ # # test.rb
189
+ #
190
+ # master = PokemonMaster.create(
191
+ # first_name: 'Ash',
192
+ # last_name: 'Ketchum',
193
+ # age: 10
194
+ # )
195
+ #
196
+ # master.create_favorite_pokemon(
197
+ # name: 'pikachu',
198
+ # nickname: 'Pikachu'
199
+ # )
200
+ #
201
+ # metapod = Pokemon.create(name: :metapod)
202
+ #
203
+ # master.pokemons.create(
204
+ # name: 'butterfree', previous_form: metapod
205
+ # )
206
+ # master.pokemons.create(name: 'squirtle')
207
+ #
208
+ # decorator = PokemonMaster::Decorator.new(master)
209
+ #
210
+ # decorator.as_json
211
+ # # returns
212
+ # # {
213
+ # # 'age' => 10,
214
+ # # 'name' => 'Ash Ketchum',
215
+ # # 'favorite_pokemon' => {
216
+ # # 'name' => 'pikachu',
217
+ # # 'nickname' => 'Pikachu'
218
+ # # },
219
+ # # 'pokemons' => [{
220
+ # # 'name' => 'butterfree',
221
+ # # 'evolution_of' => 'metapod'
222
+ # # }, {
223
+ # # 'name' => 'squirtle'
224
+ # # }, {
225
+ # # 'name' => 'pikachu'
226
+ # # }]
227
+ # # }
107
228
  def expose(attribute, **options_hash)
108
229
  options = Decorator::Options.new(options_hash)
109
230
 
@@ -136,6 +257,8 @@ module Azeroth
136
257
  #
137
258
  # @return [Hash]
138
259
  def as_json(*args)
260
+ return nil if object.nil?
261
+
139
262
  return array_as_json(*args) if enum?
140
263
 
141
264
  HashBuilder.new(self).as_json
@@ -97,9 +97,9 @@ module Azeroth
97
97
  #
98
98
  # @return [Class<Decorator>, NilClass]
99
99
  def decorator_from_options
100
- return options.decorator if options.decorator
100
+ return options.decorator if options.decorator_defined?
101
101
 
102
- Azeroth::DummyDecorator unless options.any_decorator?
102
+ Azeroth::DummyDecorator unless options.decorator
103
103
  end
104
104
 
105
105
  # @private