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.
- checksums.yaml +4 -4
- data/Gemfile +3 -1
- data/lib/much-rails.rb +85 -0
- data/lib/much-rails/action.rb +415 -0
- data/lib/much-rails/action/base_command_result.rb +22 -0
- data/lib/much-rails/action/base_result.rb +14 -0
- data/lib/much-rails/action/base_router.rb +474 -0
- data/lib/much-rails/action/controller.rb +100 -0
- data/lib/much-rails/action/head_result.rb +18 -0
- data/lib/much-rails/action/redirect_to_result.rb +18 -0
- data/lib/much-rails/action/render_result.rb +25 -0
- data/lib/much-rails/action/router.rb +101 -0
- data/lib/much-rails/action/send_data_result.rb +18 -0
- data/lib/much-rails/action/send_file_result.rb +18 -0
- data/lib/much-rails/action/unprocessable_entity_result.rb +37 -0
- data/lib/much-rails/assets.rb +54 -0
- data/lib/much-rails/boolean.rb +7 -0
- data/lib/much-rails/call_method.rb +27 -0
- data/lib/much-rails/call_method_callbacks.rb +122 -0
- data/lib/much-rails/change_action.rb +83 -0
- data/lib/much-rails/change_action_result.rb +59 -0
- data/lib/much-rails/config.rb +55 -0
- data/lib/much-rails/date.rb +50 -0
- data/lib/much-rails/decimal.rb +7 -0
- data/lib/much-rails/destroy_action.rb +32 -0
- data/lib/much-rails/destroy_service.rb +67 -0
- data/lib/much-rails/has_slug.rb +7 -0
- data/lib/much-rails/input_value.rb +19 -0
- data/lib/much-rails/json.rb +29 -0
- data/lib/much-rails/layout.rb +142 -0
- data/lib/much-rails/layout/helper.rb +25 -0
- data/lib/much-rails/mixin.rb +7 -0
- data/lib/much-rails/not_given.rb +7 -0
- data/lib/much-rails/rails_routes.rb +29 -0
- data/lib/much-rails/railtie.rb +39 -0
- data/lib/much-rails/records.rb +5 -0
- data/lib/much-rails/records/always_destroyable.rb +30 -0
- data/lib/much-rails/records/not_destroyable.rb +30 -0
- data/lib/much-rails/records/validate_destroy.rb +92 -0
- data/lib/much-rails/result.rb +7 -0
- data/lib/much-rails/save_action.rb +32 -0
- data/lib/much-rails/save_service.rb +68 -0
- data/lib/much-rails/service.rb +18 -0
- data/lib/much-rails/service_validation_errors.rb +41 -0
- data/lib/much-rails/time.rb +28 -0
- data/lib/much-rails/version.rb +3 -1
- data/lib/much-rails/view_models.rb +3 -0
- data/lib/much-rails/view_models/breadcrumb.rb +11 -0
- data/lib/much-rails/wrap_and_call_method.rb +41 -0
- data/lib/much-rails/wrap_method.rb +45 -0
- data/much-rails.gemspec +20 -4
- data/test/helper.rb +20 -2
- data/test/support/actions/show.rb +11 -0
- data/test/support/config/routes/test.rb +3 -0
- data/test/support/factory.rb +2 -0
- data/test/support/fake_action_controller.rb +63 -0
- data/test/unit/action/base_command_result_tests.rb +43 -0
- data/test/unit/action/base_result_tests.rb +22 -0
- data/test/unit/action/base_router_tests.rb +530 -0
- data/test/unit/action/controller_tests.rb +110 -0
- data/test/unit/action/head_result_tests.rb +24 -0
- data/test/unit/action/redirect_to_result_tests.rb +24 -0
- data/test/unit/action/render_result_tests.rb +43 -0
- data/test/unit/action/router_tests.rb +252 -0
- data/test/unit/action/send_data_result_tests.rb +24 -0
- data/test/unit/action/send_file_result_tests.rb +24 -0
- data/test/unit/action/unprocessable_entity_result_tests.rb +51 -0
- data/test/unit/action_tests.rb +400 -0
- data/test/unit/assets_tests.rb +127 -0
- data/test/unit/boolean_tests.rb +17 -0
- data/test/unit/call_method_callbacks_tests.rb +176 -0
- data/test/unit/call_method_tests.rb +62 -0
- data/test/unit/change_action_result_tests.rb +113 -0
- data/test/unit/change_action_tests.rb +260 -0
- data/test/unit/config_tests.rb +68 -0
- data/test/unit/date_tests.rb +55 -0
- data/test/unit/decimal_tests.rb +17 -0
- data/test/unit/destroy_action_tests.rb +83 -0
- data/test/unit/destroy_service_tests.rb +238 -0
- data/test/unit/has_slug_tests.rb +17 -0
- data/test/unit/input_value_tests.rb +34 -0
- data/test/unit/json_tests.rb +55 -0
- data/test/unit/layout_tests.rb +155 -0
- data/test/unit/mixin_tests.rb +17 -0
- data/test/unit/much-rails_tests.rb +82 -4
- data/test/unit/not_given_tests.rb +17 -0
- data/test/unit/rails_routes_tests.rb +28 -0
- data/test/unit/records/always_destroyable_tests.rb +43 -0
- data/test/unit/records/not_destroyable_tests.rb +40 -0
- data/test/unit/records/validate_destroy_tests.rb +252 -0
- data/test/unit/result_tests.rb +17 -0
- data/test/unit/save_action_tests.rb +83 -0
- data/test/unit/save_service_tests.rb +264 -0
- data/test/unit/service_tests.rb +33 -0
- data/test/unit/service_validation_errors_tests.rb +107 -0
- data/test/unit/time_tests.rb +58 -0
- data/test/unit/view_models/breadcrumb_tests.rb +53 -0
- data/test/unit/wrap_and_call_method_tests.rb +163 -0
- data/test/unit/wrap_method_tests.rb +112 -0
- metadata +356 -7
- 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,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,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,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
|