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,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuchRails; end
4
+ module MuchRails::Layout; end
5
+
6
+ module MuchRails::Layout::Helper
7
+ # This is used to render layouts. It is designed to be used in
8
+ # the Rails layout template to render the nested layouts.
9
+ def much_rails_render_layouts(view_model, &content)
10
+ unless view_model.is_a?(MuchRails::Layout)
11
+ raise(
12
+ TypeError,
13
+ "A View Model that mixes in MuchRails::Layout expected; "\
14
+ "got #{view_model.class}.",
15
+ )
16
+ end
17
+ view_model
18
+ .layouts
19
+ .reverse
20
+ .reduce(content){ |render_proc, template_path|
21
+ ->{ render(File.join("layouts", template_path), &render_proc) }
22
+ }
23
+ .call
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-mixin"
4
+
5
+ module MuchRails
6
+ Mixin = MuchMixin
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-not-given"
4
+
5
+ module MuchRails
6
+ NotGiven = MuchNotGiven
7
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module MuchRails; end
6
+
7
+ # MuchRails::RailsRoutes is a Singleton object that provides Rails' URL helpers
8
+ # and path/URL generation.
9
+ class MuchRails::RailsRoutes
10
+ include Singleton
11
+ include ::Rails.application.routes.url_helpers
12
+
13
+ # These methods support stubbing #method_missing in tests but have no real
14
+ # effect or behavior.
15
+
16
+ def method_missing(name, *args, &block)
17
+ super
18
+ end
19
+
20
+ def respond_to_missing?(*args)
21
+ super
22
+ end
23
+
24
+ private
25
+
26
+ def default_url_options
27
+ ::Rails.application.config.action_mailer.default_url_options
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuchRails; end
4
+
5
+ class MuchRails::Railtie < Rails::Railtie
6
+ config.action_mailer.default_url_options ||= {}
7
+
8
+ initializer "much-rails-gem" do |app|
9
+ require "much-rails/rails_routes"
10
+
11
+ # Helpers
12
+ ActionView::Base.include(MuchRails::Layout::Helper)
13
+
14
+ require "much-rails/assets"
15
+ MuchRails::Assets.configure_for_rails(::Rails)
16
+ app.middleware.use MuchRails::Assets::Server
17
+
18
+ # See https://github.com/ohler55/oj/blob/master/pages/Rails.md.
19
+ Oj.optimize_rails
20
+
21
+ MuchResult.default_transaction_receiver = ActiveRecord::Base
22
+
23
+ MuchRails.configure do |config|
24
+ # This should be `true` in development so things fail fast and give the
25
+ # developers rich error information for debugging purposes.
26
+ #
27
+ # This should be `false` in all other envs so proper HTTP response
28
+ # statuses are returned.
29
+ config.action.raise_response_exceptions = Rails.env.development?
30
+
31
+ config.save_service_validation_error_exception_classes = [
32
+ ActiveRecord::RecordInvalid,
33
+ ]
34
+ config.destroy_service_validation_error_exception_classes = [
35
+ MuchRails::Records::DestructionInvalid,
36
+ ]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails/records/always_destroyable"
4
+ require "much-rails/records/not_destroyable"
5
+ require "much-rails/records/validate_destroy"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails/mixin"
4
+ require "much-rails/records/validate_destroy"
5
+
6
+ module MuchRails; end
7
+ module MuchRails::Records; end
8
+
9
+ # MuchRails::Records::AlwaysDestroyable is a mix-in to always enable destroying
10
+ # a record. It mixes-in MuchRails::Records::ValidateDestroy and hard-codes
11
+ # never adding destruction error messages.
12
+ module MuchRails::Records::AlwaysDestroyable
13
+ include MuchRails::Mixin
14
+
15
+ mixin_included do
16
+ include MuchRails::Records::ValidateDestroy
17
+ end
18
+
19
+ mixin_instance_methods do
20
+ def destruction_error_messages
21
+ []
22
+ end
23
+
24
+ private
25
+
26
+ def validate_destroy
27
+ # Do nothing on purpose.
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails/mixin"
4
+ require "much-rails/records/validate_destroy"
5
+
6
+ module MuchRails; end
7
+ module MuchRails::Records; end
8
+
9
+ # MuchRails::Records::NotDestroyable is a mix-in to disable destroying a
10
+ # record. It mixes-in MuchRails::Records::ValidateDestroy and hard-codes
11
+ # a permanent destruction error message.
12
+ module MuchRails::Records::NotDestroyable
13
+ include MuchRails::Mixin
14
+
15
+ mixin_included do
16
+ include MuchRails::Records::ValidateDestroy
17
+ end
18
+
19
+ mixin_instance_methods do
20
+ def destruction_error_messages
21
+ ["#{self.class.name} records can't be deleted."]
22
+ end
23
+
24
+ private
25
+
26
+ def validate_destroy
27
+ # Do nothing on purpose.
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/error"
4
+ require "much-rails/mixin"
5
+
6
+ module MuchRails; end
7
+ module MuchRails::Records; end
8
+
9
+ # MuchRails::Records::ValidateDestroy is used to mix in custom validation
10
+ # logic and handling in destroying records.
11
+ #
12
+ # Include this module and define the #validate_destroy private method. Any
13
+ # calls to #destroy or #destroy! will first check if the record is
14
+ # #destroyable?. This check runs the custom #validate_destroy logic. If the
15
+ # record is not destroyable, the #validate_destroy method should add
16
+ # #destruction_error_messages.
17
+ module MuchRails::Records::ValidateDestroy
18
+ include MuchRails::Mixin
19
+
20
+ mixin_instance_methods do
21
+ def destruction_error_messages
22
+ @destruction_error_messages ||= []
23
+ end
24
+
25
+ def destroy(validate: true)
26
+ return false if validate && !destroyable?
27
+
28
+ super()
29
+ end
30
+
31
+ def destroy_without_validation
32
+ destroy(validate: false)
33
+ end
34
+
35
+ def destroy!(as: :base, validate: true)
36
+ if validate && !destroyable?
37
+ raise MuchRails::Records::DestructionInvalid.new(self, field_name: as)
38
+ end
39
+
40
+ # `_raise_record_not_destroyed` is from ActiveRecord. This logic was
41
+ # copied from Rails `destroy!` implementation.
42
+ destroy(validate: validate) || _raise_record_not_destroyed
43
+ end
44
+
45
+ def destroy_without_validation!(as: :base)
46
+ destroy!(as: as, validate: false)
47
+ end
48
+
49
+ def destroyable?
50
+ destruction_error_messages.clear
51
+ validate_destroy
52
+ destruction_error_messages.none?
53
+ end
54
+
55
+ def not_destroyable?
56
+ !destroyable?
57
+ end
58
+
59
+ private
60
+
61
+ def validate_destroy
62
+ raise NotImplementedError
63
+ end
64
+ end
65
+ end
66
+
67
+ class MuchRails::Records::DestructionInvalid < StandardError
68
+ attr_reader :record, :errors, :error_full_messages
69
+
70
+ def initialize(record = nil, field_name: :base)
71
+ super(record&.destruction_error_messages.to_a.join("\n"))
72
+
73
+ @record = record
74
+
75
+ messages = record&.destruction_error_messages.to_a
76
+ @errors =
77
+ if messages.any?
78
+ { field_name.to_sym => messages }
79
+ else
80
+ {}
81
+ end
82
+
83
+ @error_full_messages =
84
+ if field_name == :base
85
+ messages
86
+ else
87
+ messages.map do |m|
88
+ ActiveModel::Error.new(@record, field_name, m).full_message
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-result"
4
+
5
+ module MuchRails
6
+ Result = MuchResult
7
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails/mixin"
4
+ require "much-rails/change_action"
5
+
6
+ module MuchRails; end
7
+
8
+ module MuchRails::SaveAction
9
+ include MuchRails::Mixin
10
+
11
+ mixin_included do
12
+ include MuchRails::ChangeAction
13
+ end
14
+
15
+ mixin_class_methods do
16
+ def save_result(&block)
17
+ change_result(&block)
18
+ end
19
+ end
20
+
21
+ mixin_instance_methods do
22
+ def save_result
23
+ change_result
24
+ end
25
+
26
+ private
27
+
28
+ def undefined_change_result_block_error_message
29
+ "A `save_result` block must be defined."
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require "much-rails/mixin"
5
+ require "much-rails/result"
6
+ require "much-rails/service"
7
+ require "much-rails/service_validation_errors"
8
+
9
+ module MuchRails; end
10
+
11
+ # MuchRails::SaveService is a common mix-in for all service objects that
12
+ # save (e.g. create/update) records.
13
+ module MuchRails::SaveService
14
+ include MuchRails::Mixin
15
+
16
+ mixin_included do
17
+ include MuchRails::Service
18
+
19
+ around_call do |receiver|
20
+ receiver.call
21
+ rescue *MuchRails::SaveService::ValidationErrors.exception_classes => ex
22
+ set_the_return_value_for_the_call_method(
23
+ MuchRails::SaveService::ValidationErrors.result_for(ex),
24
+ )
25
+ end
26
+ end
27
+
28
+ module ValidationErrors
29
+ def self.add(exception_class, &block)
30
+ service_validation_errors.add(exception_class, &block)
31
+ end
32
+
33
+ def self.exception_classes
34
+ service_validation_errors.exception_classes
35
+ end
36
+
37
+ def self.result_for(ex)
38
+ service_validation_errors.result_for(ex)
39
+ end
40
+
41
+ def self.service_validation_errors
42
+ @service_validation_errors ||=
43
+ MuchRails::ServiceValidationErrors
44
+ .new
45
+ .tap do |e|
46
+ e.add(ActiveRecord::RecordInvalid) do |ex|
47
+ MuchRails::SaveService::FailureResult.new(
48
+ record: ex.record,
49
+ exception: ex,
50
+ validation_errors: ex.record&.errors.to_h,
51
+ validation_error_messages:
52
+ ex.record&.errors&.full_messages.to_a,
53
+ )
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ module FailureResult
60
+ def self.new(exception:, validation_errors:, **kargs)
61
+ MuchResult.failure(
62
+ exception: exception,
63
+ validation_errors: validation_errors,
64
+ **kargs,
65
+ )
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails/call_method_callbacks"
4
+ require "much-rails/mixin"
5
+ require "much-rails/wrap_and_call_method"
6
+
7
+ module MuchRails; end
8
+
9
+ # MuchRails::Service is a common mix-in for service objects. It supports
10
+ # the single `.call` method API with before/after callback support.
11
+ module MuchRails::Service
12
+ include MuchRails::Mixin
13
+
14
+ mixin_included do
15
+ include MuchRails::CallMethodCallbacks
16
+ include MuchRails::WrapAndCallMethod
17
+ end
18
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuchRails; end
4
+
5
+ class MuchRails::ServiceValidationErrors
6
+ attr_reader :hash
7
+
8
+ def initialize
9
+ @hash = {}
10
+ end
11
+
12
+ def add(exception_class, &block)
13
+ unless exception_class < Exception
14
+ raise(ArgumentError, "#{exception_class} is not an Exception")
15
+ end
16
+
17
+ @hash[exception_class] = block
18
+ end
19
+
20
+ def exception_classes
21
+ @hash.keys
22
+ end
23
+
24
+ def result_for(ex)
25
+ result_proc = nil
26
+ exception_class = ex.class
27
+ loop do
28
+ result_proc = @hash[exception_class]
29
+ break unless result_proc.nil?
30
+
31
+ exception_class =
32
+ if exception_class.superclass.nil?
33
+ raise ArgumentError, "#{ex.class} hasn't been configured"
34
+ else
35
+ exception_class.superclass
36
+ end
37
+ end
38
+
39
+ result_proc.call(ex)
40
+ end
41
+ end