azeroth 1.0.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72a4e7c837a4053e850b9479d42cfccd2e279ac028f57da2c5d3baed6c6babfa
4
- data.tar.gz: 84591492fc859555d87b86704a61e68c0b8171e42a8fd77d1f4e502216516fc0
3
+ metadata.gz: 7c8e0229917b320108b25371d2c30ca48f83663288d50d2e767b8cf45c037c95
4
+ data.tar.gz: 2e6fe774d10fa5b5ca3f22c0b4deac4828a28480ebd3ebb1cce370c84c0f359c
5
5
  SHA512:
6
- metadata.gz: ad6ca16b03b7b99b051bf9bfd25e25d1d522fa89e5b5ff31e495fa1af4fbb193dd269b062d8a87205aa8eeea84bd8a405b84de5d1663d7a1636239d4dd6b973f
7
- data.tar.gz: 3eec0d1c24bb260dab5fb3ccccf5deedc8ae2eeff7378084fd501c82e740f776f1e3a14e32cee939155f5f833c4c1affdd3ca19f6636681f664be3deca36ed23
6
+ metadata.gz: b517e33573aa729a94fee810f0f4cea17a39533359332017e1dea8a3d80c74aacaac1010754739d8ba312d42353c57bb93d0c172aa640453450383ea1ff99328
7
+ data.tar.gz: 7bdfbfce3bdc6f40c2c82ea904a5778b5d0e7cc037f4fbf73f2e7d5fe8a449b7dacbac41f55523afea298bea5de998cc5d442e395fbf4949e555775aeabccff4
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/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
 
@@ -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
 
@@ -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
@@ -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
@@ -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
  #
@@ -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]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Azeroth
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -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
@@ -51,4 +51,10 @@ ActiveRecord::Schema.define do
51
51
  t.string :name, null: false
52
52
  t.string :director, null: false
53
53
  end
54
+
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
54
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
@@ -3,21 +3,80 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Azeroth::Decorator::MethodBuilder do
6
+ subject(:build_reader) do
7
+ described_class.build_reader(decorator_class, :age, options)
8
+ end
9
+
6
10
  let(:decorator_class) { Class.new(Azeroth::Decorator) }
7
11
  let(:decorator) { decorator_class.new(object) }
8
12
  let(:model) { build(:dummy_model) }
9
13
  let(:object) { model }
14
+ let(:options) { Azeroth::Decorator::Options.new(options_hash) }
15
+ let(:options_hash) { {} }
10
16
 
11
17
  describe '.build_reader' do
12
18
  it do
13
- expect { described_class.build_reader(decorator_class, :age) }
19
+ expect { build_reader }
14
20
  .to add_method(:age).to(decorator)
15
21
  end
16
22
 
17
23
  it do
18
- described_class.build_reader(decorator_class, :age)
24
+ build_reader
19
25
 
20
26
  expect(decorator.age).to eq(model.age)
21
27
  end
28
+
29
+ context 'when passing reader option' do
30
+ context 'when passing true' do
31
+ let(:options_hash) { { reader: true } }
32
+
33
+ it do
34
+ expect { build_reader }
35
+ .to add_method(:age).to(decorator)
36
+ end
37
+ end
38
+
39
+ context 'when passing false' do
40
+ let(:options_hash) { { reader: false } }
41
+
42
+ it do
43
+ expect { build_reader }
44
+ .not_to add_method(:age).to(decorator)
45
+ end
46
+ end
47
+ end
48
+
49
+ context 'when passing override option as true' do
50
+ let(:options_hash) { { override: false } }
51
+
52
+ it do
53
+ expect { build_reader }
54
+ .to add_method(:age).to(decorator)
55
+ end
56
+ end
57
+
58
+ context 'when method already existed' do
59
+ before do
60
+ decorator_class.define_method(:age) { 1 }
61
+ end
62
+
63
+ context 'when passing override option as true' do
64
+ let(:options_hash) { { override: true } }
65
+
66
+ it do
67
+ expect { build_reader }
68
+ .to change_method(:age).on(decorator)
69
+ end
70
+ end
71
+
72
+ context 'when passing override option as false' do
73
+ let(:options_hash) { { override: false } }
74
+
75
+ it do
76
+ expect { build_reader }
77
+ .not_to change_method(:age).on(decorator)
78
+ end
79
+ end
80
+ end
22
81
  end
23
82
  end
@@ -5,14 +5,16 @@ require 'spec_helper'
5
5
  describe Azeroth::Decorator do
6
6
  subject(:decorator) { DummyModel::Decorator.new(object) }
7
7
 
8
- let(:model) { build(:dummy_model) }
9
- let(:object) { model }
8
+ let(:model) { build(:dummy_model) }
9
+ let(:object) { model }
10
+ let(:instance) { decorator.new(object) }
10
11
 
11
12
  describe '.expose' do
12
13
  subject(:decorator) { Class.new(described_class) }
13
14
 
15
+ let(:options_hash) { {} }
14
16
  let(:expected_options) do
15
- Azeroth::Decorator::Options.new
17
+ Azeroth::Decorator::Options.new(options_hash)
16
18
  end
17
19
 
18
20
  it do
@@ -59,6 +61,97 @@ describe Azeroth::Decorator do
59
61
  .and raise_error(Sinclair::Exception::InvalidOptions)
60
62
  end
61
63
  end
64
+
65
+ context 'when decorator already has the method' do
66
+ before do
67
+ decorator.define_method(:name) do
68
+ 'some name'
69
+ end
70
+ end
71
+
72
+ context 'when not passing override' do
73
+ it do
74
+ expect { decorator.send(:expose, :name) }
75
+ .to change(decorator, :attributes_map)
76
+ .from({})
77
+ .to({ name: expected_options })
78
+ end
79
+
80
+ it do
81
+ expect { decorator.send(:expose, :name) }
82
+ .to change_method(:name)
83
+ .on(instance)
84
+ end
85
+ end
86
+
87
+ context 'when passing override as true' do
88
+ let(:options_hash) { { override: true } }
89
+
90
+ it do
91
+ expect { decorator.send(:expose, :name, **options_hash) }
92
+ .to change(decorator, :attributes_map)
93
+ .from({})
94
+ .to({ name: expected_options })
95
+ end
96
+
97
+ it do
98
+ expect { decorator.send(:expose, :name, **options_hash) }
99
+ .to change_method(:name)
100
+ .on(instance)
101
+ end
102
+ end
103
+
104
+ context 'when passing override as false' do
105
+ let(:options_hash) { { override: false } }
106
+
107
+ it do
108
+ expect { decorator.send(:expose, :name, **options_hash) }
109
+ .to change(decorator, :attributes_map)
110
+ .from({})
111
+ .to({ name: expected_options })
112
+ end
113
+
114
+ it do
115
+ expect { decorator.send(:expose, :name, **options_hash) }
116
+ .not_to change_method(:name)
117
+ .on(instance)
118
+ end
119
+ end
120
+ end
121
+
122
+ context 'when passing reader option' do
123
+ context 'when option is true' do
124
+ let(:options_hash) { { reader: true } }
125
+
126
+ it do
127
+ expect { decorator.send(:expose, :name, **options_hash) }
128
+ .to change(decorator, :attributes_map)
129
+ .from({})
130
+ .to({ name: expected_options })
131
+ end
132
+
133
+ it do
134
+ expect { decorator.send(:expose, :name, **options_hash) }
135
+ .to add_method(:name).to(decorator)
136
+ end
137
+ end
138
+
139
+ context 'when option is false' do
140
+ let(:options_hash) { { reader: false } }
141
+
142
+ it do
143
+ expect { decorator.send(:expose, :name, **options_hash) }
144
+ .to change(decorator, :attributes_map)
145
+ .from({})
146
+ .to({ name: expected_options })
147
+ end
148
+
149
+ it do
150
+ expect { decorator.send(:expose, :name, **options_hash) }
151
+ .not_to add_method(:name).to(decorator)
152
+ end
153
+ end
154
+ end
62
155
  end
63
156
 
64
157
  describe '#as_json' do
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Azeroth::RequestHandler::Pagination do
6
+ subject(:pagination) { described_class.new(params, options) }
7
+
8
+ let(:options) { Azeroth::Options.new(options_hash) }
9
+ let(:options_hash) { {} }
10
+ let(:params) { ActionController::Parameters.new(parameters) }
11
+ let(:parameters) { {} }
12
+
13
+ describe '#offset' do
14
+ context 'when nothing was defined' do
15
+ it do
16
+ expect(pagination.offset).to be_zero
17
+ end
18
+ end
19
+
20
+ context 'when parameters has page value' do
21
+ let(:parameters) { { page: page } }
22
+ let(:page) { Random.rand(1..10) }
23
+ let(:expected_offset) { (page - 1) * expected_per_page }
24
+ let(:expected_per_page) { 20 }
25
+
26
+ it 'returns value from request using default per_page' do
27
+ expect(pagination.offset).to eq(expected_offset)
28
+ end
29
+ end
30
+
31
+ context 'when parameters has per_page value' do
32
+ let(:parameters) { { per_page: per_page } }
33
+ let(:per_page) { Random.rand(1..10) }
34
+
35
+ it do
36
+ expect(pagination.offset).to be_zero
37
+ end
38
+ end
39
+
40
+ context 'when parameters has page and per page values' do
41
+ let(:parameters) { { page: page, per_page: per_page } }
42
+ let(:page) { Random.rand(1..10) }
43
+ let(:per_page) { Random.rand(1..10) }
44
+ let(:expected_offset) { (page - 1) * expected_per_page }
45
+ let(:expected_per_page) { per_page }
46
+
47
+ it 'returns value from request' do
48
+ expect(pagination.offset).to eq(expected_offset)
49
+ end
50
+ end
51
+
52
+ context 'when parameters has page and options per page values' do
53
+ let(:parameters) { { page: page } }
54
+ let(:options_hash) { { per_page: per_page } }
55
+ let(:page) { Random.rand(1..10) }
56
+ let(:per_page) { Random.rand(1..10) }
57
+ let(:expected_offset) { (page - 1) * expected_per_page }
58
+ let(:expected_per_page) { per_page }
59
+
60
+ it 'returns value from request and options' do
61
+ expect(pagination.offset).to eq(expected_offset)
62
+ end
63
+ end
64
+
65
+ context 'when params has page and per_page and options per page values' do
66
+ let(:parameters) { { page: page, per_page: per_page } }
67
+ let(:options_hash) { { per_page: options_per_page } }
68
+ let(:page) { Random.rand(1..10) }
69
+ let(:options_per_page) { Random.rand(1..10) }
70
+ let(:per_page) { Random.rand(1..10) }
71
+ let(:expected_offset) { (page - 1) * expected_per_page }
72
+ let(:expected_per_page) { per_page }
73
+
74
+ it 'returns value from request' do
75
+ expect(pagination.offset).to eq(expected_offset)
76
+ end
77
+ end
78
+ end
79
+
80
+ describe '#limit' do
81
+ context 'when nothing was defined' do
82
+ it 'returns default value' do
83
+ expect(pagination.limit).to eq(20)
84
+ end
85
+ end
86
+
87
+ context 'when parameters has per_page' do
88
+ let(:parameters) { { per_page: per_page } }
89
+ let(:per_page) { Random.rand(1..10) }
90
+
91
+ it 'returns value from request' do
92
+ expect(pagination.limit).to eq(per_page)
93
+ end
94
+ end
95
+
96
+ context 'when options has per_page' do
97
+ let(:options_hash) { { per_page: per_page } }
98
+ let(:per_page) { Random.rand(1..10) }
99
+
100
+ it 'returns value from options' do
101
+ expect(pagination.limit).to eq(per_page)
102
+ end
103
+ end
104
+
105
+ context 'when params and options have per_page' do
106
+ let(:parameters) { { per_page: per_page } }
107
+ let(:per_page) { Random.rand(1..10) }
108
+ let(:options_hash) { { per_page: options_per_page } }
109
+ let(:options_per_page) { Random.rand(1..10) }
110
+
111
+ it 'returns value from request' do
112
+ expect(pagination.limit).to eq(per_page)
113
+ end
114
+ end
115
+ end
116
+
117
+ describe '#current_page' do
118
+ context 'when nothing was defined' do
119
+ it 'returns first page' do
120
+ expect(pagination.current_page).to eq(1)
121
+ end
122
+ end
123
+
124
+ context 'when parameters has page' do
125
+ let(:parameters) { { page: page } }
126
+ let(:page) { Random.rand(1..10) }
127
+
128
+ it 'returns value from request' do
129
+ expect(pagination.current_page).to eq(page)
130
+ end
131
+ end
132
+ end
133
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: azeroth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Darthjee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-13 00:00:00.000000000 Z
11
+ date: 2024-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -500,6 +500,7 @@ files:
500
500
  - lib/azeroth.rb
501
501
  - lib/azeroth/controller_interface.rb
502
502
  - lib/azeroth/decorator.rb
503
+ - lib/azeroth/decorator/class_methods.rb
503
504
  - lib/azeroth/decorator/hash_builder.rb
504
505
  - lib/azeroth/decorator/key_value_extractor.rb
505
506
  - lib/azeroth/decorator/method_builder.rb
@@ -514,6 +515,7 @@ files:
514
515
  - lib/azeroth/request_handler/edit.rb
515
516
  - lib/azeroth/request_handler/index.rb
516
517
  - lib/azeroth/request_handler/new.rb
518
+ - lib/azeroth/request_handler/pagination.rb
517
519
  - lib/azeroth/request_handler/show.rb
518
520
  - lib/azeroth/request_handler/update.rb
519
521
  - lib/azeroth/resource_builder.rb
@@ -578,6 +580,9 @@ files:
578
580
  - spec/dummy/app/models/product/decorator_with_factory.rb
579
581
  - spec/dummy/app/models/publisher.rb
580
582
  - spec/dummy/app/models/user.rb
583
+ - spec/dummy/app/models/website.rb
584
+ - spec/dummy/app/models/website/decorator.rb
585
+ - spec/dummy/app/models/website/with_location.rb
581
586
  - spec/dummy/app/views/documents/edit.html
582
587
  - spec/dummy/app/views/documents/index.html
583
588
  - spec/dummy/app/views/documents/new.html
@@ -648,6 +653,7 @@ files:
648
653
  - spec/lib/azeroth/request_handler/edit_spec.rb
649
654
  - spec/lib/azeroth/request_handler/index_spec.rb
650
655
  - spec/lib/azeroth/request_handler/new_spec.rb
656
+ - spec/lib/azeroth/request_handler/pagination_spec.rb
651
657
  - spec/lib/azeroth/request_handler/show_spec.rb
652
658
  - spec/lib/azeroth/request_handler/update_spec.rb
653
659
  - spec/lib/azeroth/request_handler_spec.rb
@@ -754,6 +760,9 @@ test_files:
754
760
  - spec/dummy/app/models/product/decorator_with_factory.rb
755
761
  - spec/dummy/app/models/publisher.rb
756
762
  - spec/dummy/app/models/user.rb
763
+ - spec/dummy/app/models/website.rb
764
+ - spec/dummy/app/models/website/decorator.rb
765
+ - spec/dummy/app/models/website/with_location.rb
757
766
  - spec/dummy/app/views/documents/edit.html
758
767
  - spec/dummy/app/views/documents/index.html
759
768
  - spec/dummy/app/views/documents/new.html
@@ -824,6 +833,7 @@ test_files:
824
833
  - spec/lib/azeroth/request_handler/edit_spec.rb
825
834
  - spec/lib/azeroth/request_handler/index_spec.rb
826
835
  - spec/lib/azeroth/request_handler/new_spec.rb
836
+ - spec/lib/azeroth/request_handler/pagination_spec.rb
827
837
  - spec/lib/azeroth/request_handler/show_spec.rb
828
838
  - spec/lib/azeroth/request_handler/update_spec.rb
829
839
  - spec/lib/azeroth/request_handler_spec.rb