hanami 2.3.0.beta2 → 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.
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0.beta2
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hanakai team
@@ -147,7 +147,7 @@ dependencies:
147
147
  requirements:
148
148
  - - "~>"
149
149
  - !ruby/object:Gem::Version
150
- version: '1.0'
150
+ version: '1.2'
151
151
  - - "<"
152
152
  - !ruby/object:Gem::Version
153
153
  version: '2'
@@ -157,7 +157,7 @@ dependencies:
157
157
  requirements:
158
158
  - - "~>"
159
159
  - !ruby/object:Gem::Version
160
- version: '1.0'
160
+ version: '1.2'
161
161
  - - "<"
162
162
  - !ruby/object:Gem::Version
163
163
  version: '2'
@@ -165,30 +165,30 @@ dependencies:
165
165
  name: hanami-cli
166
166
  requirement: !ruby/object:Gem::Requirement
167
167
  requirements:
168
- - - "~>"
168
+ - - ">="
169
169
  - !ruby/object:Gem::Version
170
- version: 2.3.0.beta2
170
+ version: 2.3.0
171
171
  type: :runtime
172
172
  prerelease: false
173
173
  version_requirements: !ruby/object:Gem::Requirement
174
174
  requirements:
175
- - - "~>"
175
+ - - ">="
176
176
  - !ruby/object:Gem::Version
177
- version: 2.3.0.beta2
177
+ version: 2.3.0
178
178
  - !ruby/object:Gem::Dependency
179
179
  name: hanami-utils
180
180
  requirement: !ruby/object:Gem::Requirement
181
181
  requirements:
182
- - - "~>"
182
+ - - ">="
183
183
  - !ruby/object:Gem::Version
184
- version: 2.3.0.beta2
184
+ version: 2.3.0
185
185
  type: :runtime
186
186
  prerelease: false
187
187
  version_requirements: !ruby/object:Gem::Requirement
188
188
  requirements:
189
- - - "~>"
189
+ - - ">="
190
190
  - !ruby/object:Gem::Version
191
- version: 2.3.0.beta2
191
+ version: 2.3.0
192
192
  - !ruby/object:Gem::Dependency
193
193
  name: json
194
194
  requirement: !ruby/object:Gem::Requirement
@@ -309,6 +309,7 @@ files:
309
309
  - lib/hanami/extensions/action/slice_configured_action.rb
310
310
  - lib/hanami/extensions/db/repo.rb
311
311
  - lib/hanami/extensions/operation.rb
312
+ - lib/hanami/extensions/operation/slice_configured_db_operation.rb
312
313
  - lib/hanami/extensions/router/errors.rb
313
314
  - lib/hanami/extensions/view.rb
314
315
  - lib/hanami/extensions/view/context.rb
@@ -413,6 +414,7 @@ files:
413
414
  - spec/integration/rack_app/non_booted_rack_app_spec.rb
414
415
  - spec/integration/rack_app/rack_app_spec.rb
415
416
  - spec/integration/rake_tasks_spec.rb
417
+ - spec/integration/router/resource_routes_spec.rb
416
418
  - spec/integration/settings/access_in_slice_class_body_spec.rb
417
419
  - spec/integration/settings/access_to_constants_spec.rb
418
420
  - spec/integration/settings/loading_from_env_spec.rb
@@ -568,6 +570,7 @@ test_files:
568
570
  - spec/integration/rack_app/non_booted_rack_app_spec.rb
569
571
  - spec/integration/rack_app/rack_app_spec.rb
570
572
  - spec/integration/rake_tasks_spec.rb
573
+ - spec/integration/router/resource_routes_spec.rb
571
574
  - spec/integration/settings/access_in_slice_class_body_spec.rb
572
575
  - spec/integration/settings/access_to_constants_spec.rb
573
576
  - spec/integration/settings/loading_from_env_spec.rb