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
@@ -40,200 +40,42 @@ module Azeroth
40
40
  # # 'pokemon' => 'Arcanine'
41
41
  # # }
42
42
  class Decorator
43
+ autoload :ClassMethods, 'azeroth/decorator/class_methods'
43
44
  autoload :HashBuilder, 'azeroth/decorator/hash_builder'
44
45
  autoload :KeyValueExtractor, 'azeroth/decorator/key_value_extractor'
45
46
  autoload :MethodBuilder, 'azeroth/decorator/method_builder'
46
47
  autoload :Options, 'azeroth/decorator/options'
47
48
 
48
- class << self
49
- # @api private
50
- #
51
- # All attributes exposed
52
- #
53
- # @return [Hash<Symbol,Hash>]
54
- def attributes_map
55
- @attributes_map ||= build_attributes_map
56
- end
57
-
58
- private
59
-
60
- # @api private
61
- # @private
62
- #
63
- # Initialize attributes to be exposed map
64
- #
65
- # When the class inherity from another
66
- # decorator, the new class should expose the
67
- # same attributes.
68
- #
69
- # @return [Hash<Symbol,Hash>]
70
- def build_attributes_map
71
- superclass.try(:attributes_map).dup || {}
72
- end
73
-
74
- # @visibility public
75
- # @api public
76
- # @private
77
- #
78
- # Expose attributes on json decorated
79
- #
80
- # @param attribute [Symbol,String] attribute to be exposed
81
- # @param options_hash [Hash] exposing options
82
- # @option options_hash as [Symbol,String] custom key
83
- # to expose
84
- # @option options_hash if [Symbol,Proc] method/block
85
- # to be called
86
- # checking if an attribute should or should not
87
- # be exposed
88
- # @option options_hash decorator [FalseClass,TrueClass,Class]
89
- # flag to use or not a decorator or decorator class to be used
90
- #
91
- # @return [Array<Symbol>]
92
- #
93
- # @example
94
- # class DummyModel
95
- # include ActiveModel::Model
96
- #
97
- # attr_accessor :id, :first_name, :last_name, :age,
98
- # :favorite_pokemon
99
- #
100
- # class Decorator < Azeroth::Decorator
101
- # expose :name
102
- # expose :age
103
- # expose :favorite_pokemon, as: :pokemon
104
- #
105
- # def name
106
- # [object.first_name, object.last_name].join(' ')
107
- # end
108
- # end
109
- # end
110
- #
111
- # @example With relations
112
- # # pokemon/decorator.rb
113
- #
114
- # class Pokemon::Decorator < Azeroth::Decorator
115
- # expose :name
116
- # expose :previous_form_name, as: :evolution_of, if: :evolution?
117
- #
118
- # def evolution?
119
- # previous_form
120
- # end
121
- #
122
- # def previous_form_name
123
- # previous_form.name
124
- # end
125
- # end
126
- #
127
- # # pokemon/favorite_decorator.rb
128
- #
129
- # class Pokemon::FavoriteDecorator < Pokemon::Decorator
130
- # expose :nickname
131
- # end
132
- #
133
- # # pokemon_master/decorator.rb
134
- #
135
- # class PokemonMaster < ActiveRecord::Base
136
- # has_one :favorite_pokemon, -> { where(favorite: true) },
137
- # class_name: 'Pokemon'
138
- # has_many :pokemons
139
- # end
140
- #
141
- # # pokemon.rb
142
- #
143
- # class Pokemon < ActiveRecord::Base
144
- # belongs_to :pokemon_master
145
- # has_one :previous_form,
146
- # class_name: 'Pokemon',
147
- # foreign_key: :previous_form_id
148
- # end
149
- #
150
- # # pokemon_master.rb
151
- #
152
- # class PokemonMaster::Decorator < Azeroth::Decorator
153
- # expose :name
154
- # expose :age
155
- # expose :favorite_pokemon, decorator: Pokemon::FavoriteDecorator
156
- # expose :pokemons
157
- #
158
- # def name
159
- # [
160
- # first_name,
161
- # last_name
162
- # ].compact.join(' ')
163
- # end
164
- # end
165
- #
166
- # # schema.rb
167
- #
168
- # ActiveRecord::Schema.define do
169
- # self.verbose = false
170
- #
171
- # create_table :pokemon_masters, force: true do |t|
172
- # t.string :first_name, null: false
173
- # t.string :last_name
174
- # t.integer :age, null: false
175
- # end
176
- #
177
- # create_table :pokemons, force: true do |t|
178
- # t.string :name, null: false
179
- # t.string :nickname
180
- # t.integer :pokemon_master_id
181
- # t.boolean :favorite
182
- # t.integer :previous_form_id
183
- # t.index %i[pokemon_master_id favorite], unique: true
184
- # end
185
- #
186
- # add_foreign_key 'pokemons', 'pokemon_masters'
187
- # end
188
- #
189
- # # test.rb
190
- #
191
- # master = PokemonMaster.create(
192
- # first_name: 'Ash',
193
- # last_name: 'Ketchum',
194
- # age: 10
195
- # )
196
- #
197
- # master.create_favorite_pokemon(
198
- # name: 'pikachu',
199
- # nickname: 'Pikachu'
200
- # )
201
- #
202
- # metapod = Pokemon.create(name: :metapod)
203
- #
204
- # master.pokemons.create(
205
- # name: 'butterfree', previous_form: metapod
206
- # )
207
- # master.pokemons.create(name: 'squirtle')
208
- #
209
- # decorator = PokemonMaster::Decorator.new(master)
210
- #
211
- # decorator.as_json
212
- # # returns
213
- # # {
214
- # # 'age' => 10,
215
- # # 'name' => 'Ash Ketchum',
216
- # # 'favorite_pokemon' => {
217
- # # 'name' => 'pikachu',
218
- # # 'nickname' => 'Pikachu'
219
- # # },
220
- # # 'pokemons' => [{
221
- # # 'name' => 'butterfree',
222
- # # 'evolution_of' => 'metapod'
223
- # # }, {
224
- # # 'name' => 'squirtle'
225
- # # }, {
226
- # # 'name' => 'pikachu'
227
- # # }]
228
- # # }
229
- def expose(attribute, **options_hash)
230
- options = Decorator::Options.new(options_hash)
49
+ extend ClassMethods
231
50
 
232
- MethodBuilder.build_reader(self, attribute)
233
-
234
- attributes_map[attribute] = options
235
- end
236
- end
51
+ # @method self.expose(attribute, **options_hash)
52
+ # @visibility public
53
+ # @api public
54
+ # @private
55
+ #
56
+ # Expose attributes on json decorated
57
+ #
58
+ # @param attribute [Symbol,String] attribute to be exposed
59
+ # @param options_hash [Hash] exposing options
60
+ # @option options_hash as [Symbol,String] custom key
61
+ # to expose the value as
62
+ # @option options_hash if [Symbol,Proc] method/block
63
+ # to be called
64
+ # checking if an attribute should or should not
65
+ # be exposed
66
+ # @option options_hash decorator [FalseClass,TrueClass,Class]
67
+ # flag to use or not a decorator or decorator class to be used
68
+ # @option options_hash reader [Boolean] Flag indicating if a reader
69
+ # to access the attribute should be created. usefull if you want
70
+ # method_missing to take over
71
+ # @option options_hash override [Boolean] Flag indicating if an
72
+ # existing method should be overriden.
73
+ # This is useful when a method acessor was included from another module
74
+ #
75
+ # @return [Array<Symbol>]
76
+ #
77
+ # @see Decorator::ClassMethods#expose Decorator::ClassMethods#expose
78
+ # for examples
237
79
 
238
80
  # @api private
239
81
  #
@@ -5,13 +5,15 @@ module Azeroth
5
5
  # @author Darthjee
6
6
  #
7
7
  # Class responsible for adding params handling methods to a controller
8
- class ParamsBuilder
9
- # @param model [Model] resource interface
10
- # @param builder [Sinclair] methods builder
11
- def initialize(model, builder)
12
- @model = model
13
- @builder = builder
14
- end
8
+ class ParamsBuilder < Sinclair::Model
9
+ # @!method initialize(model:, builder:)
10
+ # @api private
11
+ #
12
+ # @param model [Model] Resource interface
13
+ # @param builder [Sinclair] Methods builder
14
+ #
15
+ # @return [ParamsBuilder]
16
+ initialize_with(:model, :builder, writter: false)
15
17
 
16
18
  # Append the params methods to be built
17
19
  #
@@ -27,7 +29,8 @@ module Azeroth
27
29
  end
28
30
  end
29
31
 
30
- attr_reader :model, :builder
32
+ private
33
+
31
34
  # @method model
32
35
  # @api private
33
36
  # @private
@@ -8,7 +8,13 @@ module Azeroth
8
8
  class Index < RequestHandler
9
9
  private
10
10
 
11
- delegate :paginated?, :per_page, to: :options
11
+ delegate :per_page, :offset, :limit,
12
+ :current_page, to: :pagination
13
+ delegate :paginated?, to: :options
14
+
15
+ def pagination
16
+ @pagination ||= Pagination.new(params, options)
17
+ end
12
18
 
13
19
  # @private
14
20
  #
@@ -45,29 +51,6 @@ module Azeroth
45
51
  scoped_entries.offset(offset).limit(limit)
46
52
  end
47
53
 
48
- # @private
49
- #
50
- # offest used in pagination
51
- #
52
- # offset is retrieved from +params[:per_page]+
53
- # or +options.per_page+
54
- #
55
- # @return [Integer]
56
- def offset
57
- (current_page - 1) * limit
58
- end
59
-
60
- # @private
61
- #
62
- # limit used in pagination
63
- #
64
- # limit is retrieved from +params[:page]+
65
- #
66
- # @return [Integer]
67
- def limit
68
- (params[:per_page] || per_page).to_i
69
- end
70
-
71
54
  # @private
72
55
  #
73
56
  # default scope of elements
@@ -77,15 +60,6 @@ module Azeroth
77
60
  controller.send(model.plural)
78
61
  end
79
62
 
80
- # @private
81
- #
82
- # calculates current page
83
- #
84
- # @return [Integer]
85
- def current_page
86
- (params[:page] || 1).to_i
87
- end
88
-
89
63
  # @private
90
64
  #
91
65
  # calculates how many pages are there
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Azeroth
4
+ class RequestHandler
5
+ # @api private
6
+ #
7
+ # Wrapper for request finding pagination attributes
8
+ class Pagination
9
+ def initialize(params, options)
10
+ @params = params
11
+ @options = options
12
+ end
13
+
14
+ delegate :per_page, to: :options
15
+
16
+ # @private
17
+ #
18
+ # offest used in pagination
19
+ #
20
+ # offset is retrieved from +params[:per_page]+
21
+ # or +options.per_page+
22
+ #
23
+ # @return [Integer]
24
+ def offset
25
+ (current_page - 1) * limit
26
+ end
27
+
28
+ # @private
29
+ #
30
+ # limit used in pagination
31
+ #
32
+ # limit is retrieved from +params[:page]+
33
+ #
34
+ # @return [Integer]
35
+ def limit
36
+ (params[:per_page] || per_page).to_i
37
+ end
38
+
39
+ # @private
40
+ #
41
+ # calculates current page
42
+ #
43
+ # @return [Integer]
44
+ def current_page
45
+ (params[:page] || 1).to_i
46
+ end
47
+
48
+ private
49
+
50
+ attr_reader :params, :options
51
+ end
52
+ end
53
+ end
@@ -8,13 +8,14 @@ module Azeroth
8
8
  # Request handler sends messages to the controller
9
9
  # in order to get the resource and rendering the response
10
10
  class RequestHandler
11
- autoload :Create, 'azeroth/request_handler/create'
12
- autoload :Destroy, 'azeroth/request_handler/destroy'
13
- autoload :Edit, 'azeroth/request_handler/edit'
14
- autoload :Index, 'azeroth/request_handler/index'
15
- autoload :New, 'azeroth/request_handler/new'
16
- autoload :Show, 'azeroth/request_handler/show'
17
- autoload :Update, 'azeroth/request_handler/update'
11
+ autoload :Create, 'azeroth/request_handler/create'
12
+ autoload :Destroy, 'azeroth/request_handler/destroy'
13
+ autoload :Edit, 'azeroth/request_handler/edit'
14
+ autoload :Index, 'azeroth/request_handler/index'
15
+ autoload :New, 'azeroth/request_handler/new'
16
+ autoload :Pagination, 'azeroth/request_handler/pagination'
17
+ autoload :Show, 'azeroth/request_handler/show'
18
+ autoload :Update, 'azeroth/request_handler/update'
18
19
 
19
20
  # @param controller [ApplicationController]
20
21
  # @param model [Azeroth::Model]
@@ -9,13 +9,15 @@ module Azeroth
9
9
  # The builder adds 2 methods, one for listing all
10
10
  # entries of a resource, and one for fetching an specific
11
11
  # entry
12
- class ResourceBuilder
13
- # @param model [Model] Resource model interface
14
- # @param builder [Sinclair] method builder
15
- def initialize(model, builder)
16
- @model = model
17
- @builder = builder
18
- end
12
+ class ResourceBuilder < Sinclair::Model
13
+ # @!method initialize(model:, builder:)
14
+ # @api private
15
+ #
16
+ # @param model [Model] Resource model interface
17
+ # @param builder [Sinclair] method builder
18
+ #
19
+ # @return [ResourceBuilder]
20
+ initialize_with(:model, :builder, writter: false)
19
21
 
20
22
  # Append methods to be built to the builder
21
23
  #
@@ -28,9 +30,6 @@ module Azeroth
28
30
  add_method(name, "@#{name} ||= #{plural}.find(#{name}_id)")
29
31
  end
30
32
 
31
- private
32
-
33
- attr_reader :model, :builder
34
33
  # @method model
35
34
  # @api private
36
35
  # @private
@@ -13,12 +13,12 @@ module Azeroth
13
13
  # @see ResourceRouteBuilder
14
14
  # @see RoutesBuilder
15
15
  class Builder
16
- # @param clazz [ActionController::Base] Controller to
16
+ # @param klass [ActionController::Base] Controller to
17
17
  # to be changed
18
18
  # @param model_name [Symbol,String]
19
19
  # @param options [Options]
20
- def initialize(clazz, model_name, options)
21
- @clazz = clazz
20
+ def initialize(klass, model_name, options)
21
+ @klass = klass
22
22
  @options = options
23
23
  @model = Azeroth::Model.new(model_name, options)
24
24
 
@@ -30,8 +30,8 @@ module Azeroth
30
30
 
31
31
  private
32
32
 
33
- attr_reader :clazz, :model, :options
34
- # @method clazz
33
+ attr_reader :klass, :model, :options
34
+ # @method klass
35
35
  # @api private
36
36
  # @private
37
37
  #
@@ -89,36 +89,40 @@ module Azeroth
89
89
  #
90
90
  # @see https://www.rubydoc.info/gems/sinclair Sinclair
91
91
  def builder
92
- @builder ||= Sinclair.new(clazz)
92
+ @builder ||= Sinclair.new(klass)
93
93
  end
94
94
 
95
95
  # Add methods for id and parameters
96
96
  #
97
97
  # @return [Array<Sinclair::MethodDefinition>]
98
98
  def add_params
99
- ParamsBuilder.new(model, builder).append
99
+ ParamsBuilder.new(
100
+ model: model, builder: builder
101
+ ).append
100
102
  end
101
103
 
102
104
  # Add methods for resource fetching
103
105
  #
104
106
  # @return [Array<Sinclair::MethodDefinition>]
105
107
  def add_resource
106
- ResourceBuilder.new(model, builder).append
108
+ ResourceBuilder.new(model: model, builder: builder).append
107
109
  end
108
110
 
109
111
  # Add metohods for each route
110
112
  #
111
113
  # @return [Array<Sinclair::MethodDefinition>]
112
114
  def add_routes
113
- RoutesBuilder.new(model, builder, options).append
115
+ RoutesBuilder.new(
116
+ model: model, builder: builder, options: options
117
+ ).append
114
118
  end
115
119
 
116
120
  # Add helpers to render objects on template
117
121
  #
118
122
  # @return [String]
119
123
  def add_helpers
120
- clazz.public_send(:helper_method, model.name)
121
- clazz.public_send(:helper_method, model.plural)
124
+ klass.public_send(:helper_method, model.name)
125
+ klass.public_send(:helper_method, model.plural)
122
126
  end
123
127
  end
124
128
  end
@@ -5,15 +5,14 @@ module Azeroth
5
5
  # @author Darthjee
6
6
  #
7
7
  # Builder resposible for adding routes methods to the controller
8
- class RoutesBuilder
9
- # @param model [Model] resource interface
10
- # @param builder [Sinclair] methods builder
11
- # @param options [Option]
12
- def initialize(model, builder, options)
13
- @model = model
14
- @builder = builder
15
- @options = options
16
- end
8
+ class RoutesBuilder < Sinclair::Model
9
+ initialize_with(:model, :builder, :options, writter: false)
10
+
11
+ # @method initialize(model:, builder:, options:)
12
+ # @param model [Model] resource interface
13
+ # @param builder [Sinclair] methods builder
14
+ # @param options [Option]
15
+ # @return [RoutesBuilder]
17
16
 
18
17
  # Append the routes methods to be built
19
18
  #
@@ -26,7 +25,6 @@ module Azeroth
26
25
 
27
26
  private
28
27
 
29
- attr_reader :model, :builder, :options
30
28
  # @method model
31
29
  # @api private
32
30
  # @private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Azeroth
4
- VERSION = '0.10.1'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -128,7 +128,9 @@ describe DocumentsWithErrorController do
128
128
 
129
129
  it 'returns updated document json' do
130
130
  patch :update, params: parameters
131
- expect(response.body).to eq(expected_body)
131
+
132
+ expect(JSON.parse(response.body))
133
+ .to eq(JSON.parse(expected_body))
132
134
  end
133
135
 
134
136
  it do
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Website < ActiveRecord::Base
4
+ class Decorator < Azeroth::Decorator
5
+ include WithLocation
6
+
7
+ expose :location, override: false
8
+
9
+ alias website object
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Website < ActiveRecord::Base
4
+ module WithLocation
5
+ def location
6
+ "#{protocol}://#{domain}:#{port}"
7
+ end
8
+
9
+ def protocol
10
+ website.protocol || '*'
11
+ end
12
+
13
+ def domain
14
+ website.domain || '*'
15
+ end
16
+
17
+ def port
18
+ website.port || '*'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Website < ActiveRecord::Base
4
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Rails.application.configure do
4
+ config.hosts << 'www.example.com'
4
5
  # Settings specified here will take precedence over
5
6
  # those in config/application.rb.
6
7
 
@@ -52,5 +52,9 @@ ActiveRecord::Schema.define do
52
52
  t.string :director, null: false
53
53
  end
54
54
 
55
- add_foreign_key 'pokemons', 'pokemon_masters'
55
+ create_table :websites, force: true do |t|
56
+ t.string :domain, null: false
57
+ t.integer :port, limit: 2, unsigned: true
58
+ t.string :protocol, limit: 5
59
+ end
56
60
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'spec_helper'
4
+
3
5
  describe Azeroth::Decorator do
4
6
  describe 'yard' do
5
7
  describe '#as_json' do
@@ -60,6 +62,19 @@ describe Azeroth::Decorator do
60
62
  }
61
63
  )
62
64
  end
65
+
66
+ it 'example without override' do
67
+ website = Website.create(
68
+ protocol: :http,
69
+ domain: 'google.com'
70
+ )
71
+
72
+ decorator = Website::Decorator.new(website)
73
+
74
+ expect(decorator.as_json).to eq(
75
+ 'location' => 'http://google.com:*'
76
+ )
77
+ end
63
78
  end
64
79
  end
65
80
  end
@@ -4,7 +4,9 @@ require 'spec_helper'
4
4
 
5
5
  describe Azeroth::Decorator::KeyValueExtractor do
6
6
  subject(:extractor) do
7
- described_class.new(decorator, attribute, options)
7
+ described_class.new(
8
+ decorator: decorator, attribute: attribute, options: options
9
+ )
8
10
  end
9
11
 
10
12
  let(:decorator_class) { Class.new(Azeroth::Decorator) }