much-rails 0.0.1 → 0.2.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.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/lib/much-rails.rb +85 -0
  4. data/lib/much-rails/action.rb +415 -0
  5. data/lib/much-rails/action/base_command_result.rb +22 -0
  6. data/lib/much-rails/action/base_result.rb +14 -0
  7. data/lib/much-rails/action/base_router.rb +474 -0
  8. data/lib/much-rails/action/controller.rb +100 -0
  9. data/lib/much-rails/action/head_result.rb +18 -0
  10. data/lib/much-rails/action/redirect_to_result.rb +18 -0
  11. data/lib/much-rails/action/render_result.rb +25 -0
  12. data/lib/much-rails/action/router.rb +101 -0
  13. data/lib/much-rails/action/send_data_result.rb +18 -0
  14. data/lib/much-rails/action/send_file_result.rb +18 -0
  15. data/lib/much-rails/action/unprocessable_entity_result.rb +37 -0
  16. data/lib/much-rails/assets.rb +54 -0
  17. data/lib/much-rails/boolean.rb +7 -0
  18. data/lib/much-rails/call_method.rb +27 -0
  19. data/lib/much-rails/call_method_callbacks.rb +122 -0
  20. data/lib/much-rails/change_action.rb +83 -0
  21. data/lib/much-rails/change_action_result.rb +59 -0
  22. data/lib/much-rails/config.rb +55 -0
  23. data/lib/much-rails/date.rb +50 -0
  24. data/lib/much-rails/decimal.rb +7 -0
  25. data/lib/much-rails/destroy_action.rb +32 -0
  26. data/lib/much-rails/destroy_service.rb +67 -0
  27. data/lib/much-rails/has_slug.rb +7 -0
  28. data/lib/much-rails/input_value.rb +19 -0
  29. data/lib/much-rails/json.rb +29 -0
  30. data/lib/much-rails/layout.rb +142 -0
  31. data/lib/much-rails/layout/helper.rb +25 -0
  32. data/lib/much-rails/mixin.rb +7 -0
  33. data/lib/much-rails/not_given.rb +7 -0
  34. data/lib/much-rails/rails_routes.rb +29 -0
  35. data/lib/much-rails/railtie.rb +39 -0
  36. data/lib/much-rails/records.rb +5 -0
  37. data/lib/much-rails/records/always_destroyable.rb +30 -0
  38. data/lib/much-rails/records/not_destroyable.rb +30 -0
  39. data/lib/much-rails/records/validate_destroy.rb +92 -0
  40. data/lib/much-rails/result.rb +7 -0
  41. data/lib/much-rails/save_action.rb +32 -0
  42. data/lib/much-rails/save_service.rb +68 -0
  43. data/lib/much-rails/service.rb +18 -0
  44. data/lib/much-rails/service_validation_errors.rb +41 -0
  45. data/lib/much-rails/time.rb +28 -0
  46. data/lib/much-rails/version.rb +3 -1
  47. data/lib/much-rails/view_models.rb +3 -0
  48. data/lib/much-rails/view_models/breadcrumb.rb +11 -0
  49. data/lib/much-rails/wrap_and_call_method.rb +41 -0
  50. data/lib/much-rails/wrap_method.rb +45 -0
  51. data/much-rails.gemspec +20 -4
  52. data/test/helper.rb +20 -2
  53. data/test/support/actions/show.rb +11 -0
  54. data/test/support/config/routes/test.rb +3 -0
  55. data/test/support/factory.rb +2 -0
  56. data/test/support/fake_action_controller.rb +63 -0
  57. data/test/unit/action/base_command_result_tests.rb +43 -0
  58. data/test/unit/action/base_result_tests.rb +22 -0
  59. data/test/unit/action/base_router_tests.rb +530 -0
  60. data/test/unit/action/controller_tests.rb +110 -0
  61. data/test/unit/action/head_result_tests.rb +24 -0
  62. data/test/unit/action/redirect_to_result_tests.rb +24 -0
  63. data/test/unit/action/render_result_tests.rb +43 -0
  64. data/test/unit/action/router_tests.rb +252 -0
  65. data/test/unit/action/send_data_result_tests.rb +24 -0
  66. data/test/unit/action/send_file_result_tests.rb +24 -0
  67. data/test/unit/action/unprocessable_entity_result_tests.rb +51 -0
  68. data/test/unit/action_tests.rb +400 -0
  69. data/test/unit/assets_tests.rb +127 -0
  70. data/test/unit/boolean_tests.rb +17 -0
  71. data/test/unit/call_method_callbacks_tests.rb +176 -0
  72. data/test/unit/call_method_tests.rb +62 -0
  73. data/test/unit/change_action_result_tests.rb +113 -0
  74. data/test/unit/change_action_tests.rb +260 -0
  75. data/test/unit/config_tests.rb +68 -0
  76. data/test/unit/date_tests.rb +55 -0
  77. data/test/unit/decimal_tests.rb +17 -0
  78. data/test/unit/destroy_action_tests.rb +83 -0
  79. data/test/unit/destroy_service_tests.rb +238 -0
  80. data/test/unit/has_slug_tests.rb +17 -0
  81. data/test/unit/input_value_tests.rb +34 -0
  82. data/test/unit/json_tests.rb +55 -0
  83. data/test/unit/layout_tests.rb +155 -0
  84. data/test/unit/mixin_tests.rb +17 -0
  85. data/test/unit/much-rails_tests.rb +82 -4
  86. data/test/unit/not_given_tests.rb +17 -0
  87. data/test/unit/rails_routes_tests.rb +28 -0
  88. data/test/unit/records/always_destroyable_tests.rb +43 -0
  89. data/test/unit/records/not_destroyable_tests.rb +40 -0
  90. data/test/unit/records/validate_destroy_tests.rb +252 -0
  91. data/test/unit/result_tests.rb +17 -0
  92. data/test/unit/save_action_tests.rb +83 -0
  93. data/test/unit/save_service_tests.rb +264 -0
  94. data/test/unit/service_tests.rb +33 -0
  95. data/test/unit/service_validation_errors_tests.rb +107 -0
  96. data/test/unit/time_tests.rb +58 -0
  97. data/test/unit/view_models/breadcrumb_tests.rb +53 -0
  98. data/test/unit/wrap_and_call_method_tests.rb +163 -0
  99. data/test/unit/wrap_method_tests.rb +112 -0
  100. metadata +356 -7
  101. data/test/unit/.keep +0 -0
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails/action/base_result"
4
+
5
+ module MuchRails; end
6
+ module MuchRails::Action; end
7
+
8
+ # MuchRails::Action::BaseCommandResult is a base result that, when
9
+ # executed, runs a generic controller command with some given args.
10
+ class MuchRails::Action::BaseCommandResult < MuchRails::Action::BaseResult
11
+ attr_reader :command_name, :command_args
12
+
13
+ def initialize(command_name, *command_args)
14
+ @command_name = command_name
15
+ @command_args = command_args
16
+ end
17
+
18
+ # This block is called using `instance_exec` in the scope of the controller
19
+ def execute_block
20
+ ->(result){ public_send(result.command_name, *result.command_args) }
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuchRails; end
4
+ module MuchRails::Action; end
5
+
6
+ # MuchRails::Action::BaseResult is a base result returned by calling
7
+ # a view action. Its only purpose is to provide an `execute_block` that
8
+ # defines what commands the controller should execute. This block is called
9
+ # using `instance_exec` in the scope of the controller.
10
+ class MuchRails::Action::BaseResult
11
+ def execute_block
12
+ raise NotImplementedError
13
+ end
14
+ end
@@ -0,0 +1,474 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuchRails; end
4
+ module MuchRails::Action; end
5
+
6
+ class MuchRails::Action::BaseRouter
7
+ DEFAULT_BASE_URL = "/"
8
+
9
+ # Override as needed.
10
+ def self.url_class
11
+ MuchRails::Action::BaseRouter::BaseURL
12
+ end
13
+
14
+ attr_reader :name
15
+ attr_reader :request_type_set, :url_set, :definitions
16
+
17
+ def initialize(name = nil, &block)
18
+ @name = name
19
+ @request_type_set = RequestTypeSet.new
20
+ @url_set = URLSet.new(self)
21
+ @definitions = []
22
+ @defined_urls = []
23
+
24
+ @base_url = DEFAULT_BASE_URL
25
+ instance_exec(&(block || proc{}))
26
+ end
27
+
28
+ def url_class
29
+ self.class.url_class
30
+ end
31
+
32
+ def unrouted_urls
33
+ @url_set.urls - @defined_urls
34
+ end
35
+
36
+ def validate!
37
+ definitions.each do |definition|
38
+ definition.request_type_actions.each do |request_type_action|
39
+ begin
40
+ request_type_action.class_name.constantize
41
+ rescue NameError => ex
42
+ raise(NameError, ex.message, definition.called_from, cause: ex)
43
+ end
44
+ end
45
+
46
+ next unless definition.has_default_action_class_name?
47
+
48
+ begin
49
+ definition.default_action_class_name.constantize
50
+ rescue NameError => ex
51
+ raise(NameError, ex.message, definition.called_from, cause: ex)
52
+ end
53
+ end
54
+ end
55
+
56
+ def apply_to(to_scope)
57
+ raise NotImplementedError
58
+ end
59
+
60
+ # Example:
61
+ # MyRouter =
62
+ # MuchRails::Action::Router.new {
63
+ # url :root, "/", "Root"
64
+ # }
65
+ # MyRouter.path_for(:root) # => "/"
66
+ #
67
+ # AdminRouter =
68
+ # MuchRails::Action::Router.new(:admin) {
69
+ # base_url "/admin"
70
+ # url :users, "/users", "Users::Index"
71
+ # }
72
+ # AdminRouter.path_for(:users) # => "/admin/users"
73
+ def path_for(name, *args)
74
+ @url_set.path_for(name, *args)
75
+ end
76
+
77
+ # Example:
78
+ # MyRouter =
79
+ # MuchRails::Action::Router.new {
80
+ # url :root, "/", "Root"
81
+ # }
82
+ # MyRouter.url_for(:root) # => "http://example.org/"
83
+ #
84
+ # AdminRouter =
85
+ # MuchRails::Action::Router.new(:admin) {
86
+ # base_url "/admin"
87
+ # url :users, "/users", "Users::Index"
88
+ # }
89
+ # AdminRouter.url_for(:users) # => "http://example.org/admin/users"
90
+ def url_for(name, *args)
91
+ @url_set.url_for(name, *args)
92
+ end
93
+
94
+ # Example:
95
+ # MyRouter =
96
+ # MuchRails::Action::Router.new {
97
+ # request_type(:mobile) do |request|
98
+ # mobile_user_agent?(request.user_agent)
99
+ # end
100
+ #
101
+ # url :root, "/"
102
+ # get :root, "Root::Index",
103
+ # mobile: "Root::IndexMobile"
104
+ # }
105
+ def request_type(name, &constraints_lambda)
106
+ @request_type_set.add(name, constraints_lambda)
107
+ end
108
+
109
+ # Example:
110
+ # AdminRouter =
111
+ # MuchRails::Action::Router.new(:admin) {
112
+ # base_url "/admin"
113
+ # url :users, "/users", "Users::Index"
114
+ # }
115
+ # AdminRouter.path_for(:users) # => "/admin/users"
116
+ def base_url(value = nil)
117
+ @base_url = value unless value.nil?
118
+ @base_url
119
+ end
120
+
121
+ # Example:
122
+ # MyRouter =
123
+ # MuchRails::Action::Router.new {
124
+ # url :root, "/"
125
+ # get :root, "Root::Index"
126
+ # }
127
+ def url(name, path)
128
+ unless name.is_a?(::Symbol)
129
+ raise(
130
+ ArgumentError,
131
+ "Named URLs must be defined with Symbol names, "\
132
+ "given `#{name.inspect}`.",
133
+ )
134
+ end
135
+ unless path.is_a?(::String)
136
+ raise(
137
+ ArgumentError,
138
+ "Named URLs must be defined with String paths, "\
139
+ "given `#{path.inspect}`.",
140
+ )
141
+ end
142
+ @url_set.add(name, path)
143
+ end
144
+
145
+ # Example:
146
+ # MyRouter =
147
+ # MuchRails::Action::Router.new {
148
+ # get "/", "Root::Index"
149
+ # get "/new", "Root::New"
150
+ # post "/", "Root::Create"
151
+ # get "/edit", "Root::Edit"
152
+ # put "/", "Root::Update"
153
+ # patch "/", "Root::Update"
154
+ # get "/remove", "Root::Remove"
155
+ # delete "/", "Root::Destroy"
156
+ # }
157
+ def get(
158
+ path,
159
+ default_class_name = nil,
160
+ called_from: caller,
161
+ **request_type_class_names)
162
+ route(
163
+ :get,
164
+ path,
165
+ default_class_name,
166
+ called_from: called_from,
167
+ **request_type_class_names,
168
+ )
169
+ end
170
+
171
+ def post(
172
+ path,
173
+ default_class_name = nil,
174
+ called_from: caller,
175
+ **request_type_class_names)
176
+ route(
177
+ :post,
178
+ path,
179
+ default_class_name,
180
+ called_from: called_from,
181
+ **request_type_class_names,
182
+ )
183
+ end
184
+
185
+ def put(
186
+ path,
187
+ default_class_name = nil,
188
+ called_from: caller,
189
+ **request_type_class_names)
190
+ route(
191
+ :put,
192
+ path,
193
+ default_class_name,
194
+ called_from: called_from,
195
+ **request_type_class_names,
196
+ )
197
+ end
198
+
199
+ def patch(
200
+ path,
201
+ default_class_name = nil,
202
+ called_from: caller,
203
+ **request_type_class_names)
204
+ route(
205
+ :patch,
206
+ path,
207
+ default_class_name,
208
+ called_from: called_from,
209
+ **request_type_class_names,
210
+ )
211
+ end
212
+
213
+ def delete(
214
+ path,
215
+ default_class_name = nil,
216
+ called_from: caller,
217
+ **request_type_class_names)
218
+ route(
219
+ :delete,
220
+ path,
221
+ default_class_name,
222
+ called_from: called_from,
223
+ **request_type_class_names,
224
+ )
225
+ end
226
+
227
+ private
228
+
229
+ def route(
230
+ http_method,
231
+ url_name_or_path,
232
+ default_class_name,
233
+ called_from:,
234
+ **request_type_class_names)
235
+ url =
236
+ @url_set.fetch(url_name_or_path){ url_class.for(self, url_name_or_path) }
237
+ request_type_actions =
238
+ request_type_class_names
239
+ .reduce([]) do |acc, (request_type_name, action_class_name)|
240
+ acc <<
241
+ RequestTypeAction.new(
242
+ request_type_set.get(request_type_name),
243
+ action_class_name,
244
+ )
245
+ acc
246
+ end
247
+
248
+ add_definition(
249
+ Definition.for_route(
250
+ http_method: http_method,
251
+ url: url,
252
+ default_action_class_name: default_class_name,
253
+ request_type_actions: request_type_actions,
254
+ called_from: called_from,
255
+ ),
256
+ )
257
+ end
258
+
259
+ def add_definition(definition)
260
+ @definitions << definition
261
+ @defined_urls << definition.url
262
+
263
+ definition
264
+ end
265
+
266
+ class RequestTypeSet
267
+ def initialize
268
+ @set = {}
269
+ end
270
+
271
+ def empty?
272
+ @set.empty?
273
+ end
274
+
275
+ def add(name, constraints_lambda)
276
+ request_type = RequestType.new(name.to_sym, constraints_lambda)
277
+ key = request_type.name
278
+ unless @set[key].nil?
279
+ raise(
280
+ ArgumentError,
281
+ "There is already a request type named `#{name.to_sym.inspect}`.",
282
+ )
283
+ end
284
+ @set[key] = request_type
285
+ end
286
+
287
+ def get(name)
288
+ key = name.to_sym
289
+ @set.fetch(key) do
290
+ raise(
291
+ ArgumentError,
292
+ "There is no request type named `#{name.to_sym.inspect}`.",
293
+ )
294
+ end
295
+ end
296
+ end
297
+
298
+ RequestType = Struct.new(:name, :constraints_lambda)
299
+ RequestTypeAction =
300
+ Struct.new(:request_type, :class_name) do
301
+ def constraints_lambda
302
+ request_type.constraints_lambda
303
+ end
304
+ end
305
+
306
+ class URLSet
307
+ def initialize(router)
308
+ @set = {}
309
+ @router = router
310
+ end
311
+
312
+ def empty?
313
+ @set.empty?
314
+ end
315
+
316
+ def urls
317
+ @set.values
318
+ end
319
+
320
+ def add(name, path)
321
+ url = @router.url_class.new(@router, path, name.to_sym)
322
+ key = url.name
323
+ unless @set[key].nil?
324
+ raise(
325
+ ArgumentError,
326
+ "There is already a URL named `#{name.to_sym.inspect}`.",
327
+ )
328
+ end
329
+
330
+ @set[key] = url
331
+ end
332
+
333
+ def fetch(name, default_value = nil, &block)
334
+ key = @router.url_class.url_name(@router, name.to_sym)
335
+ if default_value
336
+ @set.fetch(key, default_value)
337
+ else
338
+ @set.fetch(
339
+ key,
340
+ &(
341
+ block ||
342
+ proc{
343
+ raise(
344
+ ArgumentError,
345
+ "There is no URL named `#{name.to_sym.inspect}`.",
346
+ )
347
+ }
348
+ )
349
+ )
350
+ end
351
+ end
352
+
353
+ def path_for(name, *args)
354
+ fetch(name).path_for(*args)
355
+ end
356
+
357
+ def url_for(name, *args)
358
+ fetch(name).url_for(*args)
359
+ end
360
+ end
361
+
362
+ class BaseURL
363
+ def self.url_name(router, name)
364
+ return unless name
365
+ return name unless router&.name
366
+
367
+ "#{router.name}_#{name}".to_sym
368
+ end
369
+
370
+ def self.url_path(router, path)
371
+ return unless path
372
+ return path unless router&.base_url
373
+
374
+ File.join(router.base_url, path)
375
+ end
376
+
377
+ def self.for(router, url_or_path)
378
+ return url_or_path if url_or_path.is_a?(self)
379
+
380
+ new(router, url_or_path)
381
+ end
382
+
383
+ attr_reader :router, :url_path, :url_name
384
+
385
+ def initialize(router, url_path, url_name = nil)
386
+ @router = router
387
+ @url_path = url_path.to_s
388
+ @url_name = url_name&.to_sym
389
+ end
390
+
391
+ def name
392
+ self.class.url_name(@router, @url_name)
393
+ end
394
+
395
+ def path
396
+ self.class.url_path(@router, @url_path)
397
+ end
398
+
399
+ def path_for(*args)
400
+ raise NotImplementedError
401
+ end
402
+
403
+ def url_for(*args)
404
+ raise NotImplementedError
405
+ end
406
+
407
+ def ==(other)
408
+ return super unless other.is_a?(self.class)
409
+
410
+ @router == other.router &&
411
+ @url_path == other.url_path &&
412
+ @url_name == other.url_name
413
+ end
414
+ end
415
+
416
+ class Definition
417
+ def self.for_route(
418
+ http_method:,
419
+ url:,
420
+ default_action_class_name:,
421
+ request_type_actions:,
422
+ called_from:)
423
+ new(
424
+ http_method: http_method,
425
+ url: url,
426
+ default_action_class_name: default_action_class_name,
427
+ request_type_actions: request_type_actions,
428
+ called_from: called_from,
429
+ )
430
+ end
431
+
432
+ attr_reader :http_method, :url, :default_params
433
+ attr_reader :default_action_class_name, :request_type_actions
434
+ attr_reader :called_from
435
+
436
+ def initialize(
437
+ http_method:,
438
+ url:,
439
+ default_action_class_name:,
440
+ request_type_actions:,
441
+ called_from:,
442
+ default_params: nil)
443
+ @http_method = http_method
444
+ @url = url
445
+ @default_params = default_params || {}
446
+ @default_action_class_name = default_action_class_name
447
+ @request_type_actions = request_type_actions || []
448
+ @called_from = called_from
449
+ end
450
+
451
+ def name
452
+ @url.name
453
+ end
454
+
455
+ def path
456
+ @url.path
457
+ end
458
+
459
+ def has_default_action_class_name?
460
+ !@default_action_class_name.nil?
461
+ end
462
+
463
+ def ==(other)
464
+ return super unless other.is_a?(self.class)
465
+
466
+ @http_method == other.http_method &&
467
+ @url == other.url &&
468
+ @default_params == other.default_params &&
469
+ @default_action_class_name ==
470
+ other.default_action_class_name &&
471
+ @request_type_actions == other.request_type_actions
472
+ end
473
+ end
474
+ end