hanami 2.3.0.beta1 → 2.3.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 +4 -4
- data/CHANGELOG.md +76 -1
- data/README.md +1 -3
- data/hanami.gemspec +8 -8
- data/lib/hanami/config/actions/content_security_policy.rb +2 -2
- data/lib/hanami/config/actions.rb +3 -2
- data/lib/hanami/config/router.rb +1 -0
- data/lib/hanami/config.rb +11 -19
- data/lib/hanami/extensions/action.rb +1 -0
- data/lib/hanami/extensions/operation/slice_configured_db_operation.rb +88 -0
- data/lib/hanami/extensions/operation.rb +8 -23
- data/lib/hanami/extensions/view/context.rb +2 -11
- data/lib/hanami/extensions/view/part.rb +3 -0
- data/lib/hanami/extensions/view/scope.rb +3 -0
- data/lib/hanami/extensions/view/slice_configured_context.rb +0 -7
- data/lib/hanami/extensions/view.rb +1 -0
- data/lib/hanami/helpers/assets_helper.rb +2 -2
- data/lib/hanami/middleware/content_security_policy_nonce.rb +3 -3
- data/lib/hanami/routes.rb +4 -3
- data/lib/hanami/slice/router.rb +201 -12
- data/lib/hanami/slice.rb +13 -0
- data/lib/hanami/slice_configurable.rb +1 -3
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami/web/rack_logger.rb +25 -8
- data/lib/hanami.rb +15 -1
- data/spec/integration/action/format_config_spec.rb +2 -67
- data/spec/integration/action/slice_configuration_spec.rb +36 -36
- data/spec/integration/logging/request_logging_spec.rb +16 -0
- data/spec/integration/operations/extension_spec.rb +63 -0
- data/spec/integration/rack_app/body_parser_spec.rb +3 -3
- data/spec/integration/rack_app/rack_app_spec.rb +2 -2
- data/spec/integration/router/resource_routes_spec.rb +281 -0
- data/spec/unit/hanami/config/actions_spec.rb +2 -2
- metadata +19 -20
- data/spec/integration/view/context/settings_spec.rb +0 -46
- data/spec/unit/hanami/version_spec.rb +0 -7
|
@@ -34,25 +34,25 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
34
34
|
it "applies default actions config from the app", :aggregate_failures do
|
|
35
35
|
prepare_app
|
|
36
36
|
|
|
37
|
-
expect(TestApp::Action.config.formats.
|
|
37
|
+
expect(TestApp::Action.config.formats.accepted).to eq []
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
it "applies actions config from the app" do
|
|
41
|
-
Hanami.app.config.actions.
|
|
41
|
+
Hanami.app.config.actions.formats.accepted = [:json]
|
|
42
42
|
|
|
43
43
|
prepare_app
|
|
44
44
|
|
|
45
|
-
expect(TestApp::Action.config.formats.
|
|
45
|
+
expect(TestApp::Action.config.formats.accepted).to eq [:json]
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
it "does not override config in the base class" do
|
|
49
|
-
Hanami.app.config.actions.
|
|
49
|
+
Hanami.app.config.actions.formats.accepted = [:csv]
|
|
50
50
|
|
|
51
51
|
prepare_app
|
|
52
52
|
|
|
53
|
-
TestApp::Action.config.
|
|
53
|
+
TestApp::Action.config.formats.accepted = [:json]
|
|
54
54
|
|
|
55
|
-
expect(TestApp::Action.config.formats.
|
|
55
|
+
expect(TestApp::Action.config.formats.accepted).to eq [:json]
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -75,23 +75,23 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
75
75
|
it "applies default actions config from the app", :aggregate_failures do
|
|
76
76
|
prepare_app
|
|
77
77
|
|
|
78
|
-
expect(TestApp::Actions::Articles::Index.config.formats.
|
|
78
|
+
expect(TestApp::Actions::Articles::Index.config.formats.accepted).to eq []
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
it "applies actions config from the app" do
|
|
82
|
-
Hanami.app.config.actions.
|
|
82
|
+
Hanami.app.config.actions.formats.accepted = [:json]
|
|
83
83
|
|
|
84
84
|
prepare_app
|
|
85
85
|
|
|
86
|
-
expect(TestApp::Actions::Articles::Index.config.formats.
|
|
86
|
+
expect(TestApp::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
it "applies config from the base class" do
|
|
90
90
|
prepare_app
|
|
91
91
|
|
|
92
|
-
TestApp::Action.config.
|
|
92
|
+
TestApp::Action.config.formats.accepted = [:json]
|
|
93
93
|
|
|
94
|
-
expect(TestApp::Actions::Articles::Index.config.formats.
|
|
94
|
+
expect(TestApp::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
|
|
@@ -114,23 +114,23 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
114
114
|
it "applies default actions config from the app", :aggregate_failures do
|
|
115
115
|
prepare_app
|
|
116
116
|
|
|
117
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
117
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq []
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
it "applies actions config from the app" do
|
|
121
|
-
Hanami.app.config.actions.
|
|
121
|
+
Hanami.app.config.actions.formats.accepted = [:json]
|
|
122
122
|
|
|
123
123
|
prepare_app
|
|
124
124
|
|
|
125
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
125
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
126
126
|
end
|
|
127
127
|
|
|
128
128
|
it "applies config from the base class" do
|
|
129
129
|
prepare_app
|
|
130
130
|
|
|
131
|
-
TestApp::Action.config.
|
|
131
|
+
TestApp::Action.config.formats.accepted = [:json]
|
|
132
132
|
|
|
133
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
133
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
134
134
|
end
|
|
135
135
|
end
|
|
136
136
|
end
|
|
@@ -151,23 +151,23 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
151
151
|
it "applies default actions config from the app", :aggregate_failures do
|
|
152
152
|
prepare_app
|
|
153
153
|
|
|
154
|
-
expect(Admin::Action.config.formats.
|
|
154
|
+
expect(Admin::Action.config.formats.accepted).to eq []
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
it "applies actions config from the app" do
|
|
158
|
-
Hanami.app.config.actions.
|
|
158
|
+
Hanami.app.config.actions.formats.accepted = [:json]
|
|
159
159
|
|
|
160
160
|
prepare_app
|
|
161
161
|
|
|
162
|
-
expect(Admin::Action.config.formats.
|
|
162
|
+
expect(Admin::Action.config.formats.accepted).to eq [:json]
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
it "applies config from the app base class" do
|
|
166
166
|
prepare_app
|
|
167
167
|
|
|
168
|
-
TestApp::Action.config.
|
|
168
|
+
TestApp::Action.config.formats.accepted = [:json]
|
|
169
169
|
|
|
170
|
-
expect(Admin::Action.config.formats.
|
|
170
|
+
expect(Admin::Action.config.formats.accepted).to eq [:json]
|
|
171
171
|
end
|
|
172
172
|
|
|
173
173
|
context "slice actions config present" do
|
|
@@ -186,24 +186,24 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
186
186
|
it "applies actions config from the slice" do
|
|
187
187
|
prepare_app
|
|
188
188
|
|
|
189
|
-
expect(Admin::Action.config.formats.
|
|
189
|
+
expect(Admin::Action.config.formats.accepted).to eq [:csv]
|
|
190
190
|
end
|
|
191
191
|
|
|
192
192
|
it "prefers actions config from the slice over config from the app-level base class" do
|
|
193
193
|
prepare_app
|
|
194
194
|
|
|
195
|
-
TestApp::Action.config.
|
|
195
|
+
TestApp::Action.config.formats.accepted = [:json]
|
|
196
196
|
|
|
197
|
-
expect(Admin::Action.config.formats.
|
|
197
|
+
expect(Admin::Action.config.formats.accepted).to eq [:csv]
|
|
198
198
|
end
|
|
199
199
|
|
|
200
200
|
it "prefers config from the base class over actions config from the slice" do
|
|
201
201
|
prepare_app
|
|
202
202
|
|
|
203
|
-
TestApp::Action.config.
|
|
204
|
-
Admin::Action.config.
|
|
203
|
+
TestApp::Action.config.formats.accepted = [:csv]
|
|
204
|
+
Admin::Action.config.formats.accepted = [:json]
|
|
205
205
|
|
|
206
|
-
expect(Admin::Action.config.formats.
|
|
206
|
+
expect(Admin::Action.config.formats.accepted).to eq [:json]
|
|
207
207
|
end
|
|
208
208
|
end
|
|
209
209
|
end
|
|
@@ -227,15 +227,15 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
227
227
|
it "applies default actions config from the app", :aggregate_failures do
|
|
228
228
|
prepare_app
|
|
229
229
|
|
|
230
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
230
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq []
|
|
231
231
|
end
|
|
232
232
|
|
|
233
233
|
it "applies actions config from the app" do
|
|
234
|
-
Hanami.app.config.actions.
|
|
234
|
+
Hanami.app.config.actions.formats.accepted = [:json]
|
|
235
235
|
|
|
236
236
|
prepare_app
|
|
237
237
|
|
|
238
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
238
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
239
239
|
end
|
|
240
240
|
|
|
241
241
|
it "applies actions config from the slice" do
|
|
@@ -243,7 +243,7 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
243
243
|
write "config/slices/admin.rb", <<~'RUBY'
|
|
244
244
|
module Admin
|
|
245
245
|
class Slice < Hanami::Slice
|
|
246
|
-
config.actions.
|
|
246
|
+
config.actions.formats.accepted = [:json]
|
|
247
247
|
end
|
|
248
248
|
end
|
|
249
249
|
RUBY
|
|
@@ -251,15 +251,15 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
251
251
|
|
|
252
252
|
prepare_app
|
|
253
253
|
|
|
254
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
254
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
255
255
|
end
|
|
256
256
|
|
|
257
257
|
it "applies config from the slice base class" do
|
|
258
258
|
prepare_app
|
|
259
259
|
|
|
260
|
-
Admin::Action.config.
|
|
260
|
+
Admin::Action.config.formats.accepted = [:json]
|
|
261
261
|
|
|
262
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
262
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
263
263
|
end
|
|
264
264
|
|
|
265
265
|
it "prefers config from the slice base class over actions config from the slice" do
|
|
@@ -275,9 +275,9 @@ RSpec.describe "App action / Slice configuration", :app_integration do
|
|
|
275
275
|
|
|
276
276
|
prepare_app
|
|
277
277
|
|
|
278
|
-
Admin::Action.config.
|
|
278
|
+
Admin::Action.config.formats.accepted = [:json]
|
|
279
279
|
|
|
280
|
-
expect(Admin::Actions::Articles::Index.config.formats.
|
|
280
|
+
expect(Admin::Actions::Articles::Index.config.formats.accepted).to eq [:json]
|
|
281
281
|
end
|
|
282
282
|
end
|
|
283
283
|
end
|
|
@@ -11,10 +11,13 @@ RSpec.describe "Logging / Request logging", :app_integration do
|
|
|
11
11
|
|
|
12
12
|
let(:logger_stream) { StringIO.new }
|
|
13
13
|
|
|
14
|
+
let(:logger_level) { nil }
|
|
15
|
+
|
|
14
16
|
let(:root) { make_tmp_directory }
|
|
15
17
|
|
|
16
18
|
def configure_logger
|
|
17
19
|
Hanami.app.config.logger.stream = logger_stream
|
|
20
|
+
Hanami.app.config.logger.level = logger_level if logger_level
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def logs
|
|
@@ -84,6 +87,19 @@ RSpec.describe "Logging / Request logging", :app_integration do
|
|
|
84
87
|
)
|
|
85
88
|
end
|
|
86
89
|
end
|
|
90
|
+
|
|
91
|
+
context "log level error" do
|
|
92
|
+
let(:logger_level) { :error }
|
|
93
|
+
before do
|
|
94
|
+
expect_any_instance_of(Hanami::Web::RackLogger).to_not receive(:data)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "does not log info" do
|
|
98
|
+
get "/"
|
|
99
|
+
|
|
100
|
+
expect(logs.split("\n").length).to eq 0
|
|
101
|
+
end
|
|
102
|
+
end
|
|
87
103
|
end
|
|
88
104
|
|
|
89
105
|
describe "slice router" do
|
|
@@ -56,4 +56,67 @@ RSpec.describe "Operation / Extensions", :app_integration do
|
|
|
56
56
|
expect(main.rom).to be Main::Slice["db.rom"]
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
|
+
|
|
60
|
+
context "hanami-db bundled, but no db configured" do
|
|
61
|
+
it "does not extend the operation class" do
|
|
62
|
+
with_tmp_directory(Dir.mktmpdir) do
|
|
63
|
+
write "config/app.rb", <<~RUBY
|
|
64
|
+
require "hanami"
|
|
65
|
+
|
|
66
|
+
module TestApp
|
|
67
|
+
class App < Hanami::App
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
RUBY
|
|
71
|
+
|
|
72
|
+
write "app/operation.rb", <<~RUBY
|
|
73
|
+
module TestApp
|
|
74
|
+
class Operation < Dry::Operation
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
RUBY
|
|
78
|
+
|
|
79
|
+
require "hanami/prepare"
|
|
80
|
+
|
|
81
|
+
operation = TestApp::Operation.new
|
|
82
|
+
|
|
83
|
+
expect(operation.rom).to be nil
|
|
84
|
+
expect { operation.transaction }.to raise_error Hanami::ComponentLoadError, "A configured db for TestApp::App is required to run transactions."
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context "hanami-db not bundled" do
|
|
90
|
+
before do
|
|
91
|
+
allow(Hanami).to receive(:bundled?).and_call_original
|
|
92
|
+
allow(Hanami).to receive(:bundled?).with("hanami-db").and_return false
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "does not extend the operation class" do
|
|
96
|
+
with_tmp_directory(Dir.mktmpdir) do
|
|
97
|
+
write "config/app.rb", <<~RUBY
|
|
98
|
+
require "hanami"
|
|
99
|
+
|
|
100
|
+
module TestApp
|
|
101
|
+
class App < Hanami::App
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
RUBY
|
|
105
|
+
|
|
106
|
+
write "app/operation.rb", <<~RUBY
|
|
107
|
+
module TestApp
|
|
108
|
+
class Operation < Dry::Operation
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
RUBY
|
|
112
|
+
|
|
113
|
+
require "hanami/prepare"
|
|
114
|
+
|
|
115
|
+
operation = TestApp::Operation.new
|
|
116
|
+
|
|
117
|
+
expect { operation.rom }.to raise_error NoMethodError
|
|
118
|
+
expect { operation.transaction }.to raise_error NoMethodError
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
59
122
|
end
|
|
@@ -12,14 +12,13 @@ RSpec.describe "Hanami web app", :app_integration do
|
|
|
12
12
|
with_tmp_directory(Dir.mktmpdir, &example)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
specify "
|
|
15
|
+
specify "Default body parser" do
|
|
16
16
|
write "config/app.rb", <<~RUBY
|
|
17
17
|
require "hanami"
|
|
18
18
|
|
|
19
19
|
module TestApp
|
|
20
20
|
class App < Hanami::App
|
|
21
21
|
config.actions.format :json
|
|
22
|
-
config.middleware.use :body_parser, :json
|
|
23
22
|
config.logger.stream = StringIO.new
|
|
24
23
|
end
|
|
25
24
|
end
|
|
@@ -65,7 +64,8 @@ RSpec.describe "Hanami web app", :app_integration do
|
|
|
65
64
|
|
|
66
65
|
module TestApp
|
|
67
66
|
class App < Hanami::App
|
|
68
|
-
config.actions.formats.
|
|
67
|
+
config.actions.formats.register :json, "application/json+scim"
|
|
68
|
+
config.actions.formats.accept :json
|
|
69
69
|
config.middleware.use :body_parser, [json: "application/json+scim"]
|
|
70
70
|
config.logger.stream = StringIO.new
|
|
71
71
|
end
|
|
@@ -305,7 +305,7 @@ RSpec.describe "Hanami web app", :app_integration do
|
|
|
305
305
|
expect { Hanami.app.rack_app }.to raise_error do |exception|
|
|
306
306
|
expect(exception).to be_kind_of(Hanami::Routes::MissingActionError)
|
|
307
307
|
expect(exception.message).to include("Could not find action with key \"actions.missing.action\" in TestApp::App")
|
|
308
|
-
expect(exception.message).to
|
|
308
|
+
expect(exception.message).to include("define the action class TestApp::Actions::Missing::Action in app/actions/missing/action.rb")
|
|
309
309
|
end
|
|
310
310
|
end
|
|
311
311
|
end
|
|
@@ -337,7 +337,7 @@ RSpec.describe "Hanami web app", :app_integration do
|
|
|
337
337
|
expect { Hanami.app.rack_app }.to raise_error do |exception|
|
|
338
338
|
expect(exception).to be_kind_of(Hanami::Routes::MissingActionError)
|
|
339
339
|
expect(exception.message).to include("Could not find action with key \"actions.missing.action\" in Admin::Slice")
|
|
340
|
-
expect(exception.message).to
|
|
340
|
+
expect(exception.message).to include("define the action class Admin::Actions::Missing::Action in slices/admin/actions/missing/action.rb")
|
|
341
341
|
end
|
|
342
342
|
end
|
|
343
343
|
end
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/inflector"
|
|
4
|
+
require "hanami/slice/router"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
RSpec.describe "Router / Resource routes" do
|
|
8
|
+
let(:router) { Hanami::Slice::Router.new(routes:, resolver:, inflector: Dry::Inflector.new) { } }
|
|
9
|
+
|
|
10
|
+
let(:resolver) { Hanami::Slice::Routing::Resolver.new(slice:) }
|
|
11
|
+
let(:slice) {
|
|
12
|
+
Class.new(Hanami::Slice).tap { |slice|
|
|
13
|
+
allow(slice).to receive(:container) { actions_container }
|
|
14
|
+
allow(slice).to receive(:slices) { {reviews: child_slice} }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
let(:child_slice) {
|
|
18
|
+
Class.new(Hanami::Slice).tap { |slice|
|
|
19
|
+
allow(slice).to receive(:container) { actions_container("[reviews]") }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
def actions_container(prefix = nil)
|
|
23
|
+
Hash.new { |_hsh, key|
|
|
24
|
+
Class.new { |klass|
|
|
25
|
+
klass.define_method(:call) do |env|
|
|
26
|
+
body = key
|
|
27
|
+
body = "#{body} #{JSON.generate(env["router.params"])}" if env["router.params"].any?
|
|
28
|
+
body = "#{prefix}#{body}" if prefix
|
|
29
|
+
[200, {}, body]
|
|
30
|
+
end
|
|
31
|
+
}.new
|
|
32
|
+
}.tap { |container|
|
|
33
|
+
def container.resolve(key) = self[key]
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
let(:app) { Rack::MockRequest.new(router) }
|
|
38
|
+
def routed(method, url)
|
|
39
|
+
app.request(method, url).body
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "resources" do
|
|
43
|
+
let(:routes) { proc { resources :posts } }
|
|
44
|
+
|
|
45
|
+
it "routes all RESTful actions to the resource" do
|
|
46
|
+
expect(routed("GET", "/posts")).to eq %(actions.posts.index)
|
|
47
|
+
expect(routed("GET", "/posts/new")).to eq %(actions.posts.new)
|
|
48
|
+
expect(routed("POST", "/posts")).to eq %(actions.posts.create)
|
|
49
|
+
expect(routed("GET", "/posts/1")).to eq %(actions.posts.show {"id":"1"})
|
|
50
|
+
expect(routed("GET", "/posts/1/edit")).to eq %(actions.posts.edit {"id":"1"})
|
|
51
|
+
expect(routed("PATCH", "/posts/1")).to eq %(actions.posts.update {"id":"1"})
|
|
52
|
+
expect(routed("DELETE", "/posts/1")).to eq %(actions.posts.destroy {"id":"1"})
|
|
53
|
+
|
|
54
|
+
expect(router.path("posts")).to eq "/posts"
|
|
55
|
+
expect(router.path("new_post")).to eq "/posts/new"
|
|
56
|
+
expect(router.path("edit_post", id: 1)).to eq "/posts/1/edit"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe "with :only" do
|
|
60
|
+
let(:routes) { proc { resources :posts, only: %i(index show) } }
|
|
61
|
+
|
|
62
|
+
it "routes only the given actions to the resource" do
|
|
63
|
+
expect(routed("GET", "/posts")).to eq %(actions.posts.index)
|
|
64
|
+
expect(routed("GET", "/posts/1")).to eq %(actions.posts.show {"id":"1"})
|
|
65
|
+
|
|
66
|
+
expect(routed("GET", "/posts/new")).not_to eq %(actions.posts.new)
|
|
67
|
+
expect(routed("POST", "/posts")).to eq "Method Not Allowed"
|
|
68
|
+
expect(routed("GET", "/posts/1/edit")).to eq "Not Found"
|
|
69
|
+
expect(routed("PATCH", "/posts/1")).to eq "Method Not Allowed"
|
|
70
|
+
expect(routed("DELETE", "/posts/1")).to eq "Method Not Allowed"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe "with :except" do
|
|
75
|
+
let(:routes) { proc { resources :posts, except: %i(edit update destroy) } }
|
|
76
|
+
|
|
77
|
+
it "routes all except the given actions to the resource" do
|
|
78
|
+
expect(routed("GET", "/posts")).to eq %(actions.posts.index)
|
|
79
|
+
expect(routed("GET", "/posts/new")).to eq %(actions.posts.new)
|
|
80
|
+
expect(routed("POST", "/posts")).to eq %(actions.posts.create)
|
|
81
|
+
expect(routed("GET", "/posts/1")).to eq %(actions.posts.show {"id":"1"})
|
|
82
|
+
|
|
83
|
+
expect(routed("GET", "/posts/1/edit")).to eq "Not Found"
|
|
84
|
+
expect(routed("PATCH", "/posts/1")).to eq "Method Not Allowed"
|
|
85
|
+
expect(routed("DELETE", "/posts/1")).to eq "Method Not Allowed"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
describe "with :to" do
|
|
90
|
+
let(:routes) { proc { resources :posts, to: "articles" } }
|
|
91
|
+
|
|
92
|
+
it "uses actions from the given container key namespace" do
|
|
93
|
+
expect(routed("GET", "/posts")).to eq %(actions.articles.index)
|
|
94
|
+
expect(routed("GET", "/posts/new")).to eq %(actions.articles.new)
|
|
95
|
+
expect(routed("POST", "/posts")).to eq %(actions.articles.create)
|
|
96
|
+
expect(routed("GET", "/posts/1")).to eq %(actions.articles.show {"id":"1"})
|
|
97
|
+
expect(routed("GET", "/posts/1/edit")).to eq %(actions.articles.edit {"id":"1"})
|
|
98
|
+
expect(routed("PATCH", "/posts/1")).to eq %(actions.articles.update {"id":"1"})
|
|
99
|
+
expect(routed("DELETE", "/posts/1")).to eq %(actions.articles.destroy {"id":"1"})
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
describe "witih :path" do
|
|
104
|
+
let(:routes) { proc { resources :posts, path: "articles" } }
|
|
105
|
+
|
|
106
|
+
it "uses the given path for the routes" do
|
|
107
|
+
expect(routed("GET", "/articles")).to eq %(actions.posts.index)
|
|
108
|
+
expect(routed("GET", "/articles/new")).to eq %(actions.posts.new)
|
|
109
|
+
expect(routed("POST", "/articles")).to eq %(actions.posts.create)
|
|
110
|
+
expect(routed("GET", "/articles/1")).to eq %(actions.posts.show {"id":"1"})
|
|
111
|
+
expect(routed("GET", "/articles/1/edit")).to eq %(actions.posts.edit {"id":"1"})
|
|
112
|
+
expect(routed("PATCH", "/articles/1")).to eq %(actions.posts.update {"id":"1"})
|
|
113
|
+
expect(routed("DELETE", "/articles/1")).to eq %(actions.posts.destroy {"id":"1"})
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe "resource" do
|
|
119
|
+
let(:routes) { proc { resource :profile } }
|
|
120
|
+
|
|
121
|
+
it "routes all RESTful actions (except index) to the resource" do
|
|
122
|
+
expect(routed("GET", "/profile/new")).to eq %(actions.profile.new)
|
|
123
|
+
expect(routed("POST", "/profile")).to eq %(actions.profile.create)
|
|
124
|
+
expect(routed("GET", "/profile")).to eq %(actions.profile.show)
|
|
125
|
+
expect(routed("GET", "/profile/edit")).to eq %(actions.profile.edit)
|
|
126
|
+
expect(routed("PATCH", "/profile")).to eq %(actions.profile.update)
|
|
127
|
+
expect(routed("DELETE", "/profile")).to eq %(actions.profile.destroy)
|
|
128
|
+
|
|
129
|
+
expect(routed("GET", "/profiles")).to eq "Not Found"
|
|
130
|
+
expect(routed("GET", "/profiles/1")).to eq "Not Found"
|
|
131
|
+
expect(routed("GET", "/profile/1")).to eq "Not Found"
|
|
132
|
+
|
|
133
|
+
expect(router.path("profile")).to eq "/profile"
|
|
134
|
+
expect(router.path("new_profile")).to eq "/profile/new"
|
|
135
|
+
expect(router.path("edit_profile")).to eq "/profile/edit"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
describe "with :only" do
|
|
139
|
+
let(:routes) { proc { resource :profile, only: %i(show edit update) } }
|
|
140
|
+
|
|
141
|
+
it "routes only the given actions to the resource" do
|
|
142
|
+
expect(routed("GET", "/profile")).to eq %(actions.profile.show)
|
|
143
|
+
expect(routed("GET", "/profile/edit")).to eq %(actions.profile.edit)
|
|
144
|
+
expect(routed("PATCH", "/profile")).to eq %(actions.profile.update)
|
|
145
|
+
|
|
146
|
+
expect(routed("GET", "/profile/new")).to eq "Not Found"
|
|
147
|
+
expect(routed("POST", "/profile")).to eq "Method Not Allowed"
|
|
148
|
+
expect(routed("DELETE", "/profile")).to eq "Method Not Allowed"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe "with :except" do
|
|
153
|
+
let(:routes) { proc { resource :profile, except: %i(edit update destroy) } }
|
|
154
|
+
|
|
155
|
+
it "routes all except the given actions to the resource" do
|
|
156
|
+
expect(routed("GET", "/profile/new")).to eq %(actions.profile.new)
|
|
157
|
+
expect(routed("POST", "/profile")).to eq %(actions.profile.create)
|
|
158
|
+
expect(routed("GET", "/profile")).to eq %(actions.profile.show)
|
|
159
|
+
|
|
160
|
+
expect(routed("GET", "/profile/edit")).to eq "Not Found"
|
|
161
|
+
expect(routed("PATCH", "/profile")).to eq "Method Not Allowed"
|
|
162
|
+
expect(routed("DELETE", "/profile")).to eq "Method Not Allowed"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
describe "with :to" do
|
|
167
|
+
let(:routes) { proc { resource :profile, to: "user" } }
|
|
168
|
+
|
|
169
|
+
it "uses actions from the given container key namespace" do
|
|
170
|
+
expect(routed("GET", "/profile/new")).to eq %(actions.user.new)
|
|
171
|
+
expect(routed("POST", "/profile")).to eq %(actions.user.create)
|
|
172
|
+
expect(routed("GET", "/profile")).to eq %(actions.user.show)
|
|
173
|
+
expect(routed("GET", "/profile/edit")).to eq %(actions.user.edit)
|
|
174
|
+
expect(routed("PATCH", "/profile")).to eq %(actions.user.update)
|
|
175
|
+
expect(routed("DELETE", "/profile")).to eq %(actions.user.destroy)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
describe "with :path" do
|
|
180
|
+
let(:routes) { proc { resource :profile, path: "user"} }
|
|
181
|
+
|
|
182
|
+
it "uses the given path for the routes" do
|
|
183
|
+
expect(routed("GET", "/user/new")).to eq %(actions.profile.new)
|
|
184
|
+
expect(routed("POST", "/user")).to eq %(actions.profile.create)
|
|
185
|
+
expect(routed("GET", "/user")).to eq %(actions.profile.show)
|
|
186
|
+
expect(routed("GET", "/user/edit")).to eq %(actions.profile.edit)
|
|
187
|
+
expect(routed("PATCH", "/user")).to eq %(actions.profile.update)
|
|
188
|
+
expect(routed("DELETE", "/user")).to eq %(actions.profile.destroy)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
describe "nested resources" do
|
|
194
|
+
let(:routes) {
|
|
195
|
+
proc {
|
|
196
|
+
resources :cafes, only: :show do
|
|
197
|
+
resources :reviews, only: :index do
|
|
198
|
+
resources :comments, only: [:index, :new, :create]
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
resource :profile, only: :show do
|
|
203
|
+
resource :avatar, only: :show do
|
|
204
|
+
resources :comments, only: :index
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
it "routes to the nested resources" do
|
|
211
|
+
expect(routed("GET", "/cafes/1")).to eq %(actions.cafes.show {"id":"1"})
|
|
212
|
+
expect(routed("GET", "/cafes/1/reviews")).to eq %(actions.cafes.reviews.index {"cafe_id":"1"})
|
|
213
|
+
expect(routed("GET", "/cafes/1/reviews/2/comments")).to eq %(actions.cafes.reviews.comments.index {"cafe_id":"1","review_id":"2"})
|
|
214
|
+
|
|
215
|
+
expect(router.path("cafe", id: 1)).to eq "/cafes/1"
|
|
216
|
+
expect(router.path("cafe_reviews", cafe_id: 1)).to eq "/cafes/1/reviews"
|
|
217
|
+
expect(router.path("cafe_review_comments", cafe_id: 1, review_id: 1)).to eq "/cafes/1/reviews/1/comments"
|
|
218
|
+
expect(router.path("new_cafe_review_comment", cafe_id: 1, review_id: 1)).to eq "/cafes/1/reviews/1/comments/new"
|
|
219
|
+
|
|
220
|
+
expect(routed("GET", "/profile")).to eq %(actions.profile.show)
|
|
221
|
+
expect(routed("GET", "/profile/avatar")).to eq %(actions.profile.avatar.show)
|
|
222
|
+
expect(routed("GET", "/profile/avatar/comments")).to eq %(actions.profile.avatar.comments.index)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
describe "standalone routes nested under resources" do
|
|
227
|
+
let(:routes) {
|
|
228
|
+
proc {
|
|
229
|
+
resources :cafes, only: :show do
|
|
230
|
+
get "/top-reviews", to: "cafes.top_reviews.index", as: :top_reviews
|
|
231
|
+
end
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
it "nests the standalone route under the resource" do
|
|
236
|
+
expect(routed("GET", "/cafes/1")).to eq %(actions.cafes.show {"id":"1"})
|
|
237
|
+
expect(routed("GET", "/cafes/1/top-reviews")).to eq %(actions.cafes.top_reviews.index {"cafe_id":"1"})
|
|
238
|
+
|
|
239
|
+
expect(router.path("cafe_top_reviews", cafe_id: 1)).to eq "/cafes/1/top-reviews"
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
describe "resources nested under scopes" do
|
|
244
|
+
let(:routes) {
|
|
245
|
+
proc {
|
|
246
|
+
scope "coffee-lovers" do
|
|
247
|
+
resources :cafes, only: :show do
|
|
248
|
+
get "/top-reviews", to: "cafes.top_reviews.index", as: :top_reviews
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
it "routes to the resources under the scope" do
|
|
255
|
+
expect(routed("GET", "/coffee-lovers/cafes/1")).to eq %(actions.cafes.show {"id":"1"})
|
|
256
|
+
expect(routed("GET", "/coffee-lovers/cafes/1/top-reviews")).to eq %(actions.cafes.top_reviews.index {"cafe_id":"1"})
|
|
257
|
+
|
|
258
|
+
expect(router.path("coffee_lovers_cafe", id: 1)).to eq "/coffee-lovers/cafes/1"
|
|
259
|
+
expect(router.path("coffee_lovers_cafe_top_reviews", cafe_id: 1)).to eq "/coffee-lovers/cafes/1/top-reviews"
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
describe "slices nested under resources" do
|
|
264
|
+
let(:routes) {
|
|
265
|
+
proc {
|
|
266
|
+
resources :cafes, only: :show do
|
|
267
|
+
slice :reviews, at: "" do
|
|
268
|
+
resources :reviews, only: :index
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
it "routes to actions within the nested slice" do
|
|
275
|
+
expect(routed("GET", "/cafes/1")).to eq %(actions.cafes.show {"id":"1"})
|
|
276
|
+
expect(routed("GET", "/cafes/1/reviews")).to eq %([reviews]actions.cafes.reviews.index {"cafe_id":"1"})
|
|
277
|
+
|
|
278
|
+
expect(router.path("cafe_reviews", cafe_id: 1)).to eq "/cafes/1/reviews"
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
@@ -23,9 +23,9 @@ RSpec.describe Hanami::Config, "#actions" do
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
it "configures base actions settings using custom methods" do
|
|
26
|
-
expect { actions.formats.
|
|
26
|
+
expect { actions.formats.register(:json, "app/json") }
|
|
27
27
|
.to change { actions.formats.mapping }
|
|
28
|
-
.to include("app/json"
|
|
28
|
+
.to include(json: have_attributes(media_type: "app/json"))
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
it "can be finalized" do
|