azeroth 0.6.3 → 0.7.2
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 +4 -4
- data/.circleci/config.yml +37 -2
- data/.rubocop.yml +11 -0
- data/Dockerfile +2 -2
- data/README.md +164 -1
- data/azeroth.gemspec +6 -5
- data/lib/azeroth.rb +1 -0
- data/lib/azeroth/decorator.rb +123 -0
- data/lib/azeroth/decorator/key_value_extractor.rb +2 -2
- data/lib/azeroth/decorator/options.rb +3 -3
- data/lib/azeroth/model.rb +3 -1
- data/lib/azeroth/options.rb +38 -1
- data/lib/azeroth/request_handler.rb +39 -2
- data/lib/azeroth/request_handler/create.rb +31 -5
- data/lib/azeroth/request_handler/update.rb +3 -3
- data/lib/azeroth/resourceable.rb +27 -0
- data/lib/azeroth/resourceable/class_methods.rb +120 -6
- data/lib/azeroth/routes_builder.rb +2 -1
- data/lib/azeroth/version.rb +1 -1
- data/spec/controllers/pokemon_masters_controller_spec.rb +56 -0
- data/spec/controllers/pokemons_controller_spec.rb +56 -0
- data/spec/dummy/app/controllers/games_controller.rb +22 -0
- data/spec/dummy/app/controllers/pokemon_masters_controller.rb +9 -0
- data/spec/dummy/app/controllers/pokemons_controller.rb +27 -0
- data/spec/dummy/app/controllers/publishers_controller.rb +8 -0
- data/spec/dummy/app/models/game.rb +5 -0
- data/spec/dummy/app/models/game/decorator.rb +9 -0
- data/spec/dummy/app/models/name_decorator.rb +5 -0
- data/spec/dummy/app/models/pokemon.rb +8 -0
- data/spec/dummy/app/models/pokemon/decorator.rb +16 -0
- data/spec/dummy/app/models/pokemon/favorite_decorator.rb +7 -0
- data/spec/dummy/app/models/pokemon_master.rb +7 -0
- data/spec/dummy/app/models/pokemon_master/decorator.rb +17 -0
- data/spec/dummy/app/models/publisher.rb +5 -0
- data/spec/dummy/config/routes.rb +8 -0
- data/spec/dummy/db/schema.rb +26 -0
- data/spec/integration/readme/azeroth/decorator_spec.rb +48 -0
- data/spec/integration/readme/controllers/games_controller_spec.rb +42 -0
- data/spec/integration/yard/azeroth/decorator_spec.rb +58 -17
- data/spec/integration/yard/controllers/games_controller_spec.rb +42 -0
- data/spec/lib/azeroth/decorator/key_value_extractor_spec.rb +32 -0
- data/spec/lib/azeroth/decorator_spec.rb +28 -2
- data/spec/lib/azeroth/request_handler/create_spec.rb +166 -0
- data/spec/lib/azeroth/request_handler/update_spec.rb +91 -0
- data/spec/lib/azeroth/request_handler_spec.rb +3 -1
- data/spec/support/app/controllers/request_handler_controller.rb +12 -1
- data/spec/support/factories/game.rb +8 -0
- data/spec/support/factories/pokemon.rb +8 -0
- data/spec/support/factories/pokemon_master.rb +9 -0
- data/spec/support/factories/publisher.rb +7 -0
- data/spec/support/shared_examples/request_handler.rb +5 -2
- metadata +70 -12
@@ -13,7 +13,7 @@ module Azeroth
|
|
13
13
|
DEFAULT_OPTIONS = {
|
14
14
|
as: nil,
|
15
15
|
if: nil,
|
16
|
-
decorator:
|
16
|
+
decorator: true
|
17
17
|
}.freeze
|
18
18
|
|
19
19
|
with_options DEFAULT_OPTIONS
|
@@ -62,8 +62,8 @@ module Azeroth
|
|
62
62
|
# #as_json call on value
|
63
63
|
#
|
64
64
|
# @return [TrueClass,FalseClass]
|
65
|
-
def
|
66
|
-
decorator
|
65
|
+
def decorator_defined?
|
66
|
+
decorator.is_a?(Class)
|
67
67
|
end
|
68
68
|
end
|
69
69
|
end
|
data/lib/azeroth/model.rb
CHANGED
@@ -62,6 +62,8 @@ module Azeroth
|
|
62
62
|
#
|
63
63
|
# @return [Azeroth::Options]
|
64
64
|
|
65
|
+
delegate :decorator, to: :options
|
66
|
+
|
65
67
|
# @private
|
66
68
|
#
|
67
69
|
# Returns decorator class for the object
|
@@ -85,7 +87,7 @@ module Azeroth
|
|
85
87
|
# @return [Azeroth::Decorator,DummyDecorator]
|
86
88
|
def calculate_decorator_class
|
87
89
|
return DummyDecorator unless options.decorator
|
88
|
-
return
|
90
|
+
return decorator if decorator.is_a?(Class)
|
89
91
|
|
90
92
|
klass::Decorator
|
91
93
|
rescue NameError
|
data/lib/azeroth/options.rb
CHANGED
@@ -10,10 +10,16 @@ module Azeroth
|
|
10
10
|
# Sinclair::Options
|
11
11
|
class Options < Sinclair::Options
|
12
12
|
# Default options
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
#
|
16
|
+
# @see Resourceable::ClassMethods#resource_for
|
13
17
|
DEFAULT_OPTIONS = {
|
14
18
|
only: %i[create destroy edit index new show update],
|
15
19
|
except: [],
|
16
|
-
decorator: true
|
20
|
+
decorator: true,
|
21
|
+
before_save: nil,
|
22
|
+
build_with: nil
|
17
23
|
}.freeze
|
18
24
|
|
19
25
|
with_options DEFAULT_OPTIONS
|
@@ -25,6 +31,19 @@ module Azeroth
|
|
25
31
|
[only].flatten.map(&:to_sym) - [except].flatten.map(&:to_sym)
|
26
32
|
end
|
27
33
|
|
34
|
+
# Returns event dispatcher
|
35
|
+
#
|
36
|
+
# Event dispatcher is responsible for
|
37
|
+
# sending events such as +before_save+
|
38
|
+
# to it's correct calling point
|
39
|
+
#
|
40
|
+
# @return [Jace::Dispatcher]
|
41
|
+
def event_dispatcher(event)
|
42
|
+
Jace::Dispatcher.new(
|
43
|
+
before: try("before_#{event}")
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
28
47
|
# @method only
|
29
48
|
# @api private
|
30
49
|
#
|
@@ -50,5 +69,23 @@ module Azeroth
|
|
50
69
|
# model.as_json
|
51
70
|
#
|
52
71
|
# @return [Decorator,TrueClass,FalseClass]
|
72
|
+
|
73
|
+
# @method before_save
|
74
|
+
# @api private
|
75
|
+
#
|
76
|
+
# Block or method name to be run before save
|
77
|
+
#
|
78
|
+
# The given method or block will be ran
|
79
|
+
# before committing changes in models
|
80
|
+
# to database
|
81
|
+
#
|
82
|
+
# @return [Symbol,Proc]
|
83
|
+
|
84
|
+
# @method build_with
|
85
|
+
# @api private
|
86
|
+
#
|
87
|
+
# Block or method name to be ran when building the resource
|
88
|
+
#
|
89
|
+
# @return [Symbol,Proc]
|
53
90
|
end
|
54
91
|
end
|
@@ -18,9 +18,10 @@ module Azeroth
|
|
18
18
|
|
19
19
|
# @param controller [ApplicationController]
|
20
20
|
# @param model [Azeroth::Model]
|
21
|
-
def initialize(controller, model)
|
21
|
+
def initialize(controller, model, options)
|
22
22
|
@controller = controller
|
23
23
|
@model = model
|
24
|
+
@options = options
|
24
25
|
end
|
25
26
|
|
26
27
|
# process the request
|
@@ -44,7 +45,7 @@ module Azeroth
|
|
44
45
|
|
45
46
|
private
|
46
47
|
|
47
|
-
attr_reader :controller, :model
|
48
|
+
attr_reader :controller, :model, :options
|
48
49
|
# @method controller
|
49
50
|
# @api private
|
50
51
|
# @private
|
@@ -61,6 +62,14 @@ module Azeroth
|
|
61
62
|
#
|
62
63
|
# @return [Azeroth::Model]
|
63
64
|
|
65
|
+
# @method options
|
66
|
+
# @api private
|
67
|
+
# @private
|
68
|
+
#
|
69
|
+
# Handling options
|
70
|
+
#
|
71
|
+
# @return [Azeroth::Options]
|
72
|
+
|
64
73
|
delegate :params, to: :controller
|
65
74
|
# @method params
|
66
75
|
# @api private
|
@@ -104,5 +113,33 @@ module Azeroth
|
|
104
113
|
def status
|
105
114
|
:ok
|
106
115
|
end
|
116
|
+
|
117
|
+
# @private
|
118
|
+
#
|
119
|
+
# Run a block triggering the event
|
120
|
+
#
|
121
|
+
# @return [Object] Result of given block
|
122
|
+
def trigger_event(event, &block)
|
123
|
+
options.event_dispatcher(event)
|
124
|
+
.dispatch(controller, &block)
|
125
|
+
end
|
126
|
+
|
127
|
+
# @private
|
128
|
+
#
|
129
|
+
# Attributes to be used on resource creating
|
130
|
+
#
|
131
|
+
# @return [Hash]
|
132
|
+
def attributes
|
133
|
+
@attributes ||= controller.send("#{model.name}_params")
|
134
|
+
end
|
135
|
+
|
136
|
+
# @private
|
137
|
+
#
|
138
|
+
# Collection scope of the resource
|
139
|
+
#
|
140
|
+
# @return [ActiveRecord::Relation]
|
141
|
+
def collection
|
142
|
+
@collection = controller.send(model.plural)
|
143
|
+
end
|
107
144
|
end
|
108
145
|
end
|
@@ -8,6 +8,8 @@ module Azeroth
|
|
8
8
|
class Create < RequestHandler
|
9
9
|
private
|
10
10
|
|
11
|
+
delegate :build_with, to: :options
|
12
|
+
|
11
13
|
# @private
|
12
14
|
#
|
13
15
|
# Creates and return an instance of the model
|
@@ -17,16 +19,40 @@ module Azeroth
|
|
17
19
|
#
|
18
20
|
# @return [Object]
|
19
21
|
def resource
|
20
|
-
@resource ||=
|
22
|
+
@resource ||= build_and_save_resource
|
21
23
|
end
|
22
24
|
|
23
|
-
#
|
25
|
+
# @private
|
26
|
+
#
|
27
|
+
# build resource for create and save it
|
24
28
|
#
|
25
29
|
# @return [Object]
|
30
|
+
def build_and_save_resource
|
31
|
+
@resource = build_resource
|
32
|
+
controller.instance_variable_set("@#{model.name}", resource)
|
33
|
+
|
34
|
+
trigger_event(:save) do
|
35
|
+
resource.tap(&:save)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @private
|
40
|
+
#
|
41
|
+
# build resource without saving it
|
42
|
+
#
|
43
|
+
# when +build_with+ option is given, the proc/method
|
44
|
+
# is called instead of collection.build
|
45
|
+
#
|
46
|
+
# @return [Object] resource built
|
26
47
|
def build_resource
|
27
|
-
|
28
|
-
|
29
|
-
|
48
|
+
return collection.build(attributes) unless build_with
|
49
|
+
|
50
|
+
case build_with
|
51
|
+
when Proc
|
52
|
+
controller.instance_eval(&build_with)
|
53
|
+
else
|
54
|
+
controller.send(build_with)
|
55
|
+
end
|
30
56
|
end
|
31
57
|
|
32
58
|
# @private
|
@@ -24,10 +24,10 @@ module Azeroth
|
|
24
24
|
#
|
25
25
|
# @return [Object]
|
26
26
|
def update_resource
|
27
|
-
attributes = controller.send("#{model.name}_params")
|
28
|
-
|
29
27
|
controller.send(model.name).tap do |entry|
|
30
|
-
|
28
|
+
trigger_event(:save) do
|
29
|
+
entry.update(attributes)
|
30
|
+
end
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
data/lib/azeroth/resourceable.rb
CHANGED
@@ -7,6 +7,10 @@ module Azeroth
|
|
7
7
|
# @author Darthjee
|
8
8
|
#
|
9
9
|
# Concern for building controller methods for the routes
|
10
|
+
#
|
11
|
+
# @example (see Resourceable::ClassMethods#resource_for)
|
12
|
+
#
|
13
|
+
# @see Resourceable::ClassMethods
|
10
14
|
module Resourceable
|
11
15
|
extend ActiveSupport::Concern
|
12
16
|
|
@@ -17,6 +21,29 @@ module Azeroth
|
|
17
21
|
autoload :Builder, 'azeroth/resourceable/builder'
|
18
22
|
autoload :ClassMethods, 'azeroth/resourceable/class_methods'
|
19
23
|
|
24
|
+
class << self
|
25
|
+
# @method self.resource_for(name, **options)
|
26
|
+
# @api public
|
27
|
+
#
|
28
|
+
# @param name [String, Symbol] Name of the resource
|
29
|
+
# @param options [Hash] resource building options
|
30
|
+
# @option options only [Array<Symbol,String>] List of
|
31
|
+
# actions to be built
|
32
|
+
# @option options except [Array<Symbol,String>] List of
|
33
|
+
# actions to not to be built
|
34
|
+
# @option options decorator [Azeroth::Decorator,TrueClass,FalseClass]
|
35
|
+
# Decorator class or flag allowing/disallowing decorators
|
36
|
+
# @option options before_save [Symbol,Proc] method/block
|
37
|
+
# to be ran on the controller before saving the resource
|
38
|
+
# @option options build_with [Symbol,Proc] method/block
|
39
|
+
# to be ran when building resource
|
40
|
+
# (default proc { <resource_collection>.build(resource_params) }
|
41
|
+
#
|
42
|
+
# @return [Array<MethodDefinition>] list of methods created
|
43
|
+
#
|
44
|
+
# @see Options::DEFAULT_OPTIONS
|
45
|
+
end
|
46
|
+
|
20
47
|
private
|
21
48
|
|
22
49
|
# @api private
|
@@ -9,13 +9,11 @@ module Azeroth
|
|
9
9
|
module ClassMethods
|
10
10
|
# Adds resource methods for resource
|
11
11
|
#
|
12
|
-
# @param
|
13
|
-
# @
|
14
|
-
# @
|
12
|
+
# @param (see Resourceable.resource_for)
|
13
|
+
# @option (see Resourceable.resource_for)
|
14
|
+
# @return (see Resourceable.resource_for)
|
15
15
|
#
|
16
|
-
# @
|
17
|
-
#
|
18
|
-
# @see Options
|
16
|
+
# @see (see Resourceable.resource_for)
|
19
17
|
#
|
20
18
|
# @example Controller without delete
|
21
19
|
# class DocumentsController < ApplicationController
|
@@ -30,6 +28,122 @@ module Azeroth
|
|
30
28
|
#
|
31
29
|
# resource_for :document, only: %w[create index show]
|
32
30
|
# end
|
31
|
+
#
|
32
|
+
# @example complete example gmaes and publishers
|
33
|
+
# class PublishersController < ApplicationController
|
34
|
+
# include Azeroth::Resourceable
|
35
|
+
# skip_before_action :verify_authenticity_token
|
36
|
+
#
|
37
|
+
# resource_for :publisher, only: %i[create index]
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# class GamesController < ApplicationController
|
41
|
+
# include Azeroth::Resourceable
|
42
|
+
# skip_before_action :verify_authenticity_token
|
43
|
+
#
|
44
|
+
# resource_for :game, except: :delete
|
45
|
+
#
|
46
|
+
# private
|
47
|
+
#
|
48
|
+
# def games
|
49
|
+
# publisher.games
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# def publisher
|
53
|
+
# @publisher ||= Publisher.find(publisher_id)
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# def publisher_id
|
57
|
+
# params.require(:publisher_id)
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# ActiveRecord::Schema.define do
|
62
|
+
# self.verbose = false
|
63
|
+
#
|
64
|
+
# create_table :publishers, force: true do |t|
|
65
|
+
# t.string :name
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# create_table :games, force: true do |t|
|
69
|
+
# t.string :name
|
70
|
+
# t.integer :publisher_id
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# class Publisher < ActiveRecord::Base
|
75
|
+
# has_many :games
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# class Game < ActiveRecord::Base
|
79
|
+
# belongs_to :publisher
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# class Game::Decorator < Azeroth::Decorator
|
83
|
+
# expose :id
|
84
|
+
# expose :name
|
85
|
+
# expose :publisher, decorator: NameDecorator
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# @example requesting games and publishers
|
89
|
+
# post "/publishers.json", params: {
|
90
|
+
# publisher: {
|
91
|
+
# name: 'Nintendo'
|
92
|
+
# }
|
93
|
+
# }
|
94
|
+
#
|
95
|
+
# publisher = JSON.parse(response.body)
|
96
|
+
# # returns
|
97
|
+
# # {
|
98
|
+
# # 'id' => 11,
|
99
|
+
# # 'name' => 'Nintendo'
|
100
|
+
# # }
|
101
|
+
#
|
102
|
+
# publisher = Publisher.last
|
103
|
+
# post "/publishers/#{publisher['id']}/games.json", params: {
|
104
|
+
# game: {
|
105
|
+
# name: 'Pokemon'
|
106
|
+
# }
|
107
|
+
# }
|
108
|
+
#
|
109
|
+
# game = Game.last
|
110
|
+
#
|
111
|
+
# JSON.parse(response.body)
|
112
|
+
# # returns
|
113
|
+
# # {
|
114
|
+
# # id: game.id,
|
115
|
+
# # name: 'Pokemon',
|
116
|
+
# # publisher: {
|
117
|
+
# # name: 'Nintendo'
|
118
|
+
# # }
|
119
|
+
# }
|
120
|
+
#
|
121
|
+
# @example Controller with before_save
|
122
|
+
# class PokemonsController < ApplicationController
|
123
|
+
# include Azeroth::Resourceable
|
124
|
+
#
|
125
|
+
# resource_for :pokemon,
|
126
|
+
# only: %i[create update],
|
127
|
+
# before_save: :set_favorite
|
128
|
+
#
|
129
|
+
# private
|
130
|
+
#
|
131
|
+
# def set_favorite
|
132
|
+
# pokemon.favorite = true
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# def pokemons
|
136
|
+
# master.pokemons
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# def master
|
140
|
+
# @master ||= PokemonMaster.find(master_id)
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
# def master_id
|
144
|
+
# params.require(:pokemon_master_id)
|
145
|
+
# end
|
146
|
+
# end
|
33
147
|
def resource_for(name, **options)
|
34
148
|
Builder.new(
|
35
149
|
self, name, Azeroth::Options.new(options)
|
@@ -62,13 +62,14 @@ module Azeroth
|
|
62
62
|
|
63
63
|
def route_code(route)
|
64
64
|
model_interface = model
|
65
|
+
options_object = options
|
65
66
|
handler_class = Azeroth::RequestHandler.const_get(
|
66
67
|
route.to_s.capitalize
|
67
68
|
)
|
68
69
|
|
69
70
|
proc do
|
70
71
|
handler_class.new(
|
71
|
-
self, model_interface
|
72
|
+
self, model_interface, options_object
|
72
73
|
).process
|
73
74
|
end
|
74
75
|
end
|
data/lib/azeroth/version.rb
CHANGED
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe PokemonMastersController do
|
6
|
+
describe 'POST create' do
|
7
|
+
let(:parameters) do
|
8
|
+
{
|
9
|
+
format: :json,
|
10
|
+
pokemon_master: {
|
11
|
+
first_name: 'Ash'
|
12
|
+
}
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'creates pokemon master' do
|
17
|
+
expect { post :create, params: parameters }
|
18
|
+
.to change(PokemonMaster, :count)
|
19
|
+
.by(1)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'updates pokemon master age' do
|
23
|
+
post :create, params: parameters
|
24
|
+
|
25
|
+
expect(PokemonMaster.last.age).to eq(10)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'POST update' do
|
30
|
+
let(:master) do
|
31
|
+
create(:pokemon_master, age: 20, last_name: nil)
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:parameters) do
|
35
|
+
{
|
36
|
+
id: master.id,
|
37
|
+
format: :json,
|
38
|
+
pokemon_master: { last_name: 'Joe' }
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'updates pokemon master' do
|
43
|
+
expect { post :update, params: parameters }
|
44
|
+
.to change { master.reload.last_name }
|
45
|
+
.from(nil)
|
46
|
+
.to('Joe')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'updates pokemon master age' do
|
50
|
+
expect { post :update, params: parameters }
|
51
|
+
.to change { master.reload.age }
|
52
|
+
.from(20)
|
53
|
+
.to(10)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|