azeroth 1.0.0 → 1.1.0

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