steroids 1.0.0 → 1.6.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/README.md +854 -0
- data/Rakefile +27 -0
- data/app/jobs/steroids/async_service_job.rb +10 -0
- data/app/serializers/steroids/error_serializer.rb +35 -0
- data/lib/resources/quotes.yml +114 -0
- data/lib/steroids/controllers/methods.rb +18 -0
- data/lib/{concerns/controller.rb → steroids/controllers/responders_helper.rb} +20 -21
- data/lib/steroids/controllers/serializers_helper.rb +15 -0
- data/lib/steroids/engine.rb +6 -0
- data/lib/steroids/errors/base.rb +57 -0
- data/lib/steroids/errors/context.rb +70 -0
- data/lib/steroids/errors/quotes.rb +29 -0
- data/lib/steroids/errors.rb +89 -0
- data/lib/steroids/extensions/array_extension.rb +25 -0
- data/lib/steroids/extensions/class_extension.rb +141 -0
- data/lib/steroids/extensions/hash_extension.rb +14 -0
- data/lib/steroids/extensions/method_extension.rb +63 -0
- data/lib/steroids/extensions/module_extension.rb +32 -0
- data/lib/steroids/extensions/object_extension.rb +122 -0
- data/lib/steroids/extensions/proc_extension.rb +9 -0
- data/lib/steroids/logger.rb +162 -0
- data/lib/steroids/railtie.rb +60 -0
- data/lib/steroids/serializers/base.rb +7 -0
- data/lib/{concerns/serializer.rb → steroids/serializers/methods.rb} +3 -3
- data/lib/steroids/services/base.rb +181 -0
- data/lib/steroids/support/magic_class.rb +17 -0
- data/lib/steroids/support/noticable_methods.rb +134 -0
- data/lib/steroids/support/servicable_methods.rb +34 -0
- data/lib/{base/type.rb → steroids/types/base.rb} +3 -3
- data/lib/{base/model.rb → steroids/types/serializable_type.rb} +2 -2
- data/lib/steroids/version.rb +4 -0
- data/lib/steroids.rb +12 -0
- metadata +75 -34
- data/lib/base/class.rb +0 -15
- data/lib/base/error.rb +0 -87
- data/lib/base/hash.rb +0 -49
- data/lib/base/list.rb +0 -51
- data/lib/base/service.rb +0 -104
- data/lib/concern.rb +0 -130
- data/lib/concerns/error.rb +0 -20
- data/lib/concerns/model.rb +0 -9
- data/lib/errors/bad_request_error.rb +0 -15
- data/lib/errors/conflict_error.rb +0 -15
- data/lib/errors/forbidden_error.rb +0 -15
- data/lib/errors/generic_error.rb +0 -14
- data/lib/errors/internal_server_error.rb +0 -15
- data/lib/errors/not_found_error.rb +0 -15
- data/lib/errors/not_implemented_error.rb +0 -15
- data/lib/errors/unauthorized_error.rb +0 -15
- data/lib/errors/unprocessable_entity_error.rb +0 -15
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
module Steroids
|
|
2
|
+
module Extensions
|
|
3
|
+
module ClassExtension
|
|
4
|
+
# --------------------------------------------------------------------------------------------
|
|
5
|
+
# Methods
|
|
6
|
+
# --------------------------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
def runtime_methods(include_modules = true)
|
|
9
|
+
methods = if include_modules
|
|
10
|
+
self.methods(true)
|
|
11
|
+
else
|
|
12
|
+
methods = []
|
|
13
|
+
klass = self.class
|
|
14
|
+
while klass
|
|
15
|
+
methods += klass.methods
|
|
16
|
+
klass = klass.superclass
|
|
17
|
+
end
|
|
18
|
+
methods
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
methods - Object.methods
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def runtime_instance_methods(include_modules = true)
|
|
25
|
+
methods = if include_modules
|
|
26
|
+
self.instance_methods(true)
|
|
27
|
+
else
|
|
28
|
+
methods = []
|
|
29
|
+
klass = self.class
|
|
30
|
+
while klass
|
|
31
|
+
methods += klass.methods
|
|
32
|
+
klass = klass.superclass
|
|
33
|
+
end
|
|
34
|
+
methods
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
methods - Object.instance_methods
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# --------------------------------------------------------------------------------------------
|
|
41
|
+
# Delegate, proxy and so on
|
|
42
|
+
# --------------------------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
def delegate_alias(alias_name, to:, method:)
|
|
45
|
+
define_method(alias_name) do |*arguments, **options, &block|
|
|
46
|
+
delegate = send(to)
|
|
47
|
+
delegate.send_apply(method, *arguments, **options, &block)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def forward_methods_to(method_name, **options)
|
|
52
|
+
respond_to_hander = options.fetch(:if)
|
|
53
|
+
return unless self.instance_methods.include?(method_name) && respond_to_hander.present?
|
|
54
|
+
|
|
55
|
+
define_method(:method_missing) do |missing_method_name, *arguments, **options, &block|
|
|
56
|
+
if self.send_apply(respond_to_hander, missing_method_name)
|
|
57
|
+
self.send_apply(method_name, missing_method_name)
|
|
58
|
+
else
|
|
59
|
+
super(missing_method_name, *arguments, **options, &block)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def try_delegate(*method_names, **options)
|
|
65
|
+
# condition = options.fetch(:if, nil)
|
|
66
|
+
delegate_name = options.fetch(:to)
|
|
67
|
+
method_names.each do |method_name|
|
|
68
|
+
self.define_method(method_name) do |*arguments, **options, &block|
|
|
69
|
+
delegate_object = self.try(delegate_name) rescue false
|
|
70
|
+
if delegate_object.present? && delegate_object.try_method(method_name)
|
|
71
|
+
delegate_object.send_apply(method_name, *arguments, **options, &block)
|
|
72
|
+
else
|
|
73
|
+
super(*arguments, **options, &block)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# --------------------------------------------------------------------------------------------
|
|
80
|
+
# Naming
|
|
81
|
+
# --------------------------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
def own_klass_name
|
|
84
|
+
self.name.split('::').last
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# --------------------------------------------------------------------------------------------
|
|
88
|
+
# Anonymous class building
|
|
89
|
+
# --------------------------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
def proxy(instance, &block)
|
|
92
|
+
name = "#{instance.class.name}Proxy"
|
|
93
|
+
Class.build_anonymous(name) do
|
|
94
|
+
include Module.new(&block)
|
|
95
|
+
|
|
96
|
+
define_method(:klass) do
|
|
97
|
+
instance.class
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
define_method(:method_missing) do |missing_method_name, *arguments, **options, &block|
|
|
101
|
+
if instance.respond_to?(missing_method_name, true)
|
|
102
|
+
instance.send_apply(missing_method_name, *arguments, **options, &block)
|
|
103
|
+
else
|
|
104
|
+
super(missing_method_name, *arguments, **options, &block)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
define_method(:respond_to_missing?) do |missing_method_name, *arguments, **options, &block|
|
|
109
|
+
!!instance.respond_to?(missing_method_name, true)
|
|
110
|
+
end
|
|
111
|
+
end.new
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def build_anonymous(name, **options, &block)
|
|
115
|
+
parent_class = options.fetch(:inherit, nil) || Class.new
|
|
116
|
+
class_name = name.camelize
|
|
117
|
+
self.new(parent_class) do
|
|
118
|
+
include Module.new(&block)
|
|
119
|
+
|
|
120
|
+
define_singleton_method(:name) do
|
|
121
|
+
class_name
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
define_singleton_method(:inspect) do
|
|
125
|
+
super().gsub("#<Class:", "#<Anonymous:#{class_name}:")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
define_singleton_method(:to_s) do
|
|
129
|
+
self.inspect
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
define_singleton_method(:anonymous?) do
|
|
133
|
+
true
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
Class.include(Steroids::Extensions::ClassExtension)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Steroids
|
|
2
|
+
module Extensions
|
|
3
|
+
module HashExtension
|
|
4
|
+
def fetch_any(*values)
|
|
5
|
+
matching_key = self.keys.find do |key|
|
|
6
|
+
!!values.include?(key)
|
|
7
|
+
end
|
|
8
|
+
self[matching_key]
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
Hash.include(Steroids::Extensions::HashExtension)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Steroids
|
|
2
|
+
module Extensions
|
|
3
|
+
module MethodExtension
|
|
4
|
+
# --------------------------------------------------------------------------------------------
|
|
5
|
+
# Calling
|
|
6
|
+
# --------------------------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
# TODO: Use *arguments.extract_options instead!
|
|
9
|
+
def apply(*given_arguments, **given_options, &block)
|
|
10
|
+
applied_arguments = dynamic_arguments_for(given_arguments, given_options)
|
|
11
|
+
applied_options = dynamic_options_for(given_options)
|
|
12
|
+
self.yield(*applied_arguments, **applied_options, &block)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def dynamic_arguments_for(given_arguments, given_options)
|
|
16
|
+
return given_arguments if self.rest?
|
|
17
|
+
|
|
18
|
+
expected_arguments_count = self.least_arguments.count
|
|
19
|
+
non_nil_arguments_count = given_arguments.take_while(&:present?).count
|
|
20
|
+
applied_arguments = given_arguments.first([expected_arguments_count, non_nil_arguments_count].min)
|
|
21
|
+
return applied_arguments if self.spread? && self.options.any?
|
|
22
|
+
|
|
23
|
+
applied_arguments << given_options if applied_arguments.count < self.arguments.count
|
|
24
|
+
applied_arguments
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def dynamic_options_for(given_options)
|
|
28
|
+
return given_options if self.spread?
|
|
29
|
+
|
|
30
|
+
given_options.select { |key| self.options.include?(key) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private def least_arguments
|
|
34
|
+
all_arguments = self.parameters.select { |key,v| key == :req || key == :opt }
|
|
35
|
+
required_arguments = all_arguments.reverse.take_while { |element| element.first == :opt }
|
|
36
|
+
required_count = arguments.size - required_arguments.size
|
|
37
|
+
arguments.first(required_count)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# --------------------------------------------------------------------------------------------
|
|
41
|
+
# Parameters
|
|
42
|
+
# --------------------------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
def arguments
|
|
45
|
+
self.parameters.select { |key,v| key == :req || key == :opt }.map { |argument| argument.second }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def options
|
|
49
|
+
self.parameters.select { |key,v| key == :key || key == :keyreq }.map { |option| option.second }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def spread?
|
|
53
|
+
!!self.parameters.find { |parameter| parameter.first == :keyrest }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def rest?
|
|
57
|
+
!!self.parameters.find { |parameter| parameter.first == :rest }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
Method.include(Steroids::Extensions::MethodExtension)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Steroids
|
|
2
|
+
module Extensions
|
|
3
|
+
module ModuleExtension
|
|
4
|
+
def grundclass
|
|
5
|
+
self.singleton_class? ? ObjectSpace.each_object(self).to_a.last : self
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def mixin(method_name)
|
|
11
|
+
if grundclass.methods.include?(method_name)
|
|
12
|
+
grundclass.define_method(method_name) do |*args|
|
|
13
|
+
self.class.send(method_name, *args)
|
|
14
|
+
end
|
|
15
|
+
else
|
|
16
|
+
raise ArgumentError.new("Mixin expects a class method")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def mixin_alias(alias_name, method_name)
|
|
21
|
+
grundclass.define_method(alias_name) do |*args|
|
|
22
|
+
self.class.send(method_name, *args)
|
|
23
|
+
end
|
|
24
|
+
grundclass.define_singleton_method(alias_name) do |*args|
|
|
25
|
+
self.send(method_name, *args)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Module.include(Steroids::Extensions::ModuleExtension)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module Steroids
|
|
2
|
+
module Extensions
|
|
3
|
+
module ObjectExtension
|
|
4
|
+
# --------------------------------------------------------------------------------------------
|
|
5
|
+
# Apply and send
|
|
6
|
+
# --------------------------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
def instance_apply(*given_arguments, **given_options, &block)
|
|
9
|
+
applied_arguments = dynamic_arguments_for(block, given_arguments)
|
|
10
|
+
applied_options = dynamic_options_for(block, given_options)
|
|
11
|
+
self.instance_exec(*applied_arguments, **applied_options, &block)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# TODO: Rename to try_apply and implement/alias try_send
|
|
15
|
+
def send_apply(method_name, *given_arguments, **given_options, &block)
|
|
16
|
+
return unless respond_to?(method_name, true)
|
|
17
|
+
method = method(method_name)
|
|
18
|
+
applied_arguments = method.dynamic_arguments_for(given_arguments, given_options)
|
|
19
|
+
applied_options = method.dynamic_options_for(given_options)
|
|
20
|
+
self.send(method_name, *applied_arguments, **applied_options, &block)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# TODO: Rename to send_apply
|
|
24
|
+
def send_apply!(method_name, *given_arguments, **given_options, &block)
|
|
25
|
+
return NoMethodError.new("Send apply", method_name) unless self.respond_to?(method_name, true)
|
|
26
|
+
|
|
27
|
+
send_apply(method_name, *given_arguments, **given_options, &block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# --------------------------------------------------------------------------------------------
|
|
31
|
+
# Marshall dump
|
|
32
|
+
# --------------------------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
def marshallable?
|
|
35
|
+
!!Marshal.dump(self)
|
|
36
|
+
rescue TypeError
|
|
37
|
+
false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# --------------------------------------------------------------------------------------------
|
|
41
|
+
# Try method
|
|
42
|
+
# --------------------------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
def try_method(method_name)
|
|
45
|
+
if self.respond_to?(method_name, true)
|
|
46
|
+
self.method(method_name)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# --------------------------------------------------------------------------------------------
|
|
51
|
+
# Type
|
|
52
|
+
# --------------------------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
def typed(expected_type)
|
|
55
|
+
return itself if instance_of?(expected_type) || itself == nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def typed!(expected_type)
|
|
59
|
+
typed_itself = typed(expected_type)
|
|
60
|
+
return typed_itself if typed_itself == itself
|
|
61
|
+
|
|
62
|
+
message = "Expected #{self.inspect} to be an instance of #{expected_type.inspect}"
|
|
63
|
+
TypeError.new(message).tap do |exception|
|
|
64
|
+
exception.set_backtrace(caller)
|
|
65
|
+
raise exception
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# --------------------------------------------------------------------------------------------
|
|
70
|
+
# If nil default
|
|
71
|
+
# --------------------------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def ifnil(default)
|
|
74
|
+
itself == nil ? default : itself
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# --------------------------------------------------------------------------------------------
|
|
78
|
+
# Serialization
|
|
79
|
+
# --------------------------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
def serializable?(include_object = true)
|
|
82
|
+
if self.is_a?(Hash)
|
|
83
|
+
self.all? do |key, value|
|
|
84
|
+
key.serializable?(include_object) && value.serializable?(include_object)
|
|
85
|
+
end
|
|
86
|
+
elsif self.is_a?(Array)
|
|
87
|
+
self.all? do |value|
|
|
88
|
+
value.serializable?(include_object)
|
|
89
|
+
end
|
|
90
|
+
elsif self.is_a?(String) || self.is_a?(Symbol) || self.is_a?(Numeric) ||
|
|
91
|
+
self.is_a?(TrueClass) || self.is_a?(FalseClass) || self.is_a?(NilClass)
|
|
92
|
+
true
|
|
93
|
+
elsif include_object == true
|
|
94
|
+
self.respond_to?(:as_json) && self.as_json.serializable?(include_object)
|
|
95
|
+
else
|
|
96
|
+
false
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def deep_serialize(include_object = true)
|
|
101
|
+
raise TypeError, "Cannot serialize object of type #{self.class}" unless serializable?(include_object)
|
|
102
|
+
|
|
103
|
+
case self
|
|
104
|
+
when Hash
|
|
105
|
+
self.to_h.transform_values { |value| value.deep_serialize(include_object) }
|
|
106
|
+
when Array
|
|
107
|
+
self.map { |value| value.deep_serialize(include_object) }
|
|
108
|
+
when String, Symbol, Numeric, TrueClass, FalseClass, NilClass
|
|
109
|
+
self
|
|
110
|
+
else
|
|
111
|
+
if include_object && self.respond_to?(:to_h)
|
|
112
|
+
self.to_h.deep_serialize(include_object)
|
|
113
|
+
elsif include_object && self.respond_to?(:as_json)
|
|
114
|
+
self.as_json.deep_serialize(include_object)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
Object.include(Steroids::Extensions::ObjectExtension)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'rainbow'
|
|
3
|
+
|
|
4
|
+
module Steroids
|
|
5
|
+
class Logger
|
|
6
|
+
@notifier = false
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
attr_accessor :notifier
|
|
10
|
+
|
|
11
|
+
def print(input = nil, verbosity: nil, format: :decorated)
|
|
12
|
+
assert_format(format)
|
|
13
|
+
assert_backtrace(input, verbosity)
|
|
14
|
+
if input.is_a?(Steroids::Errors::Base) && input.logged == true
|
|
15
|
+
false
|
|
16
|
+
else
|
|
17
|
+
level = assert_level(input)
|
|
18
|
+
output = format_input(level, input)
|
|
19
|
+
Rails.logger.send(level, output)
|
|
20
|
+
notify(level, output)
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def assert_level(input)
|
|
28
|
+
return :info unless input.is_a?(Exception)
|
|
29
|
+
|
|
30
|
+
if input.is_a?(Steroids::Errors::InternalServerError) || input.is_a?(Steroids::Errors::GenericError)
|
|
31
|
+
:error
|
|
32
|
+
elsif input.is_a?(Steroids::Errors::Base)
|
|
33
|
+
:warn
|
|
34
|
+
else
|
|
35
|
+
:error
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def assert_color(level)
|
|
40
|
+
case level
|
|
41
|
+
when :error
|
|
42
|
+
:red
|
|
43
|
+
when :warn
|
|
44
|
+
:yellow
|
|
45
|
+
when :info
|
|
46
|
+
:green
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def assert_backtrace(input, verbosity)
|
|
51
|
+
@backtrace = input.is_a?(Exception) ? input.backtrace : caller
|
|
52
|
+
@backtrace_verbosity = begin
|
|
53
|
+
if [:full, :concise, :none].include?(verbosity)
|
|
54
|
+
verbosity
|
|
55
|
+
elsif input.is_a?(Exception)
|
|
56
|
+
:full
|
|
57
|
+
else
|
|
58
|
+
:none
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def clean_path(input_path)
|
|
64
|
+
root_path_array = Rails.root.to_s.split("/")
|
|
65
|
+
root_path_array.slice!(root_path_array.size-1..)
|
|
66
|
+
input_path_array = input_path.split("/")
|
|
67
|
+
zipped_array = root_path_array.zip(input_path_array)
|
|
68
|
+
matchs = zipped_array.take_while { |root_path, input_path| root_path == input_path }
|
|
69
|
+
output_path = matchs.map(&:first)
|
|
70
|
+
common_path = output_path.join("/")
|
|
71
|
+
input_path.sub(common_path, '').sub(/^\//, '')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def assert_format(format)
|
|
75
|
+
@format = [:raw, :decorated].include?(format) ? format : :decorated
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def notify(level, input)
|
|
79
|
+
if @notifier.respond_to?(:call) && input.is_a?(Exception) && [:error, :warn].include?(level)
|
|
80
|
+
@notifier.call(input)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def backtrace_origin
|
|
85
|
+
@backtrace.find do |line|
|
|
86
|
+
!line.include?(".bundle/gems/ruby") && !line.include?("steroids/lib/steroids")
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def format_timestamp(input)
|
|
91
|
+
if input.respond_to?(:timestamp) && input.timestamp.is_a?(DateTime)
|
|
92
|
+
"(at #{input.timestamp.to_time.to_s})"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def format_message(input)
|
|
97
|
+
[
|
|
98
|
+
"\n#{Rainbow("▶").magenta} #{Rainbow(input.class.to_s).red} -- #{Rainbow(input.message.to_s.upcase_first).magenta}",
|
|
99
|
+
input.respond_to?(:id) && "[ID: #{input.id.to_s}]",
|
|
100
|
+
format_timestamp(input),
|
|
101
|
+
].compact_blank.join(" ")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def format_origin
|
|
105
|
+
" ↳ #{clean_path(backtrace_origin.to_s)}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def format_cause(input)
|
|
109
|
+
cause_message = assert_attribute(input, :cause_message) || assert_attribute(input.cause, :message) || "Unknown error"
|
|
110
|
+
[
|
|
111
|
+
Rainbow("\n ➤ Cause: #{input.cause.class.name}").cyan + " -- #{cause_message.to_s}",
|
|
112
|
+
input.cause.respond_to?(:record) && input.cause.record && "(#{input.cause.record.class.name})"
|
|
113
|
+
].compact_blank.join(" ")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def format_backtrace(input)
|
|
117
|
+
if @backtrace_verbosity == :full
|
|
118
|
+
" " + @backtrace.map do |path|
|
|
119
|
+
clean_path(path.to_s)
|
|
120
|
+
end.join("\n ") if @backtrace.any?
|
|
121
|
+
elsif @backtrace_verbosity == :concise
|
|
122
|
+
format_origin
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def format_errors(input)
|
|
127
|
+
" • " + input.errors.map do |error|
|
|
128
|
+
error_class = input.try(:record) || input.is_a?(Exception) ? input.class.name : "Error"
|
|
129
|
+
"#{error_class}: #{error}"
|
|
130
|
+
end.join("\n • ") if input.errors.any?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def format_context(input)
|
|
134
|
+
Rainbow(" ➤ Context: ").cyan + Rainbow(input.context.to_s).blue
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def format_input(level, input)
|
|
138
|
+
color = assert_color(level)
|
|
139
|
+
if input.is_a?(Exception)
|
|
140
|
+
[
|
|
141
|
+
format_message(input),
|
|
142
|
+
assert_attribute(input, :errors) && format_errors(input),
|
|
143
|
+
assert_attribute(input, :context) && format_context(input),
|
|
144
|
+
[:full, :concise].include?(@backtrace_verbosity) && format_backtrace(input),
|
|
145
|
+
assert_attribute(input, :cause) && format_cause(input)
|
|
146
|
+
].compact_blank.join("\n") + "\n"
|
|
147
|
+
else
|
|
148
|
+
decorator = "\n#{Rainbow("▶").magenta} #{Rainbow("Steroids::Logger").send(color)} -- #{Rainbow(level.to_s).send(color)}:"
|
|
149
|
+
[
|
|
150
|
+
@format == :decorated && decorator,
|
|
151
|
+
input,
|
|
152
|
+
[:full, :concise].include?(@backtrace_verbosity) && format_backtrace(input)
|
|
153
|
+
].compact_blank.join("\n") + "\n"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def assert_attribute(instance, key)
|
|
158
|
+
instance.respond_to?(key) && instance.public_send(key)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "zeitwerk"
|
|
3
|
+
require "rails"
|
|
4
|
+
require "active_model_serializers"
|
|
5
|
+
|
|
6
|
+
module Steroids
|
|
7
|
+
# ------------------------------------------------------------------------------------------------
|
|
8
|
+
# Custom loader (Zeitwerk)
|
|
9
|
+
# ------------------------------------------------------------------------------------------------
|
|
10
|
+
class Loader
|
|
11
|
+
def load_extensions!
|
|
12
|
+
core_extensions = File.expand_path("#{gem_path}/steroids/extensions/**/**/*.rb", __dir__)
|
|
13
|
+
extensions_dir = Dir.glob(core_extensions)
|
|
14
|
+
Dir.glob(extensions_dir).sort.each do |path|
|
|
15
|
+
require path
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def file_update_checker(&loader)
|
|
20
|
+
watch_list = Dir["#{gem_path}/**/*.rb"]
|
|
21
|
+
@file_update_checker ||= ActiveSupport::FileUpdateChecker.new(watch_list, &loader)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def zeitwerk
|
|
25
|
+
@loader ||= Zeitwerk::Loader.new.tap do |loader|
|
|
26
|
+
loader.tag = "steroids"
|
|
27
|
+
loader.enable_reloading
|
|
28
|
+
loader.push_dir(gem_path)
|
|
29
|
+
loader.inflector = Zeitwerk::GemInflector.new("#{gem_path}/steroids.rb")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def gem_path
|
|
36
|
+
@gem_path ||= File.expand_path("..", __dir__)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# ------------------------------------------------------------------------------------------------
|
|
41
|
+
# Rails hooks (Railtie)
|
|
42
|
+
# ------------------------------------------------------------------------------------------------
|
|
43
|
+
class Railtie < ::Rails::Railtie
|
|
44
|
+
config.to_prepare do
|
|
45
|
+
loader = Steroids.loader
|
|
46
|
+
if loader.file_update_checker.updated?
|
|
47
|
+
loader.file_update_checker.execute
|
|
48
|
+
loader.zeitwerk.reload
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
initializer "steroids.add_reloader" do |app|
|
|
53
|
+
loader = Steroids.loader
|
|
54
|
+
app.reloaders << loader.file_update_checker do
|
|
55
|
+
loader.zeitwerk.reload
|
|
56
|
+
loader.load_extensions!
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Steroids
|
|
2
|
-
module
|
|
3
|
-
module
|
|
2
|
+
module Serializers
|
|
3
|
+
module Methods
|
|
4
4
|
extend ActiveSupport::Concern
|
|
5
5
|
included do
|
|
6
6
|
def initialize(object, options = {})
|
|
@@ -34,7 +34,7 @@ module Steroids
|
|
|
34
34
|
when /^[-+]?[1-9]([0-9]*)?$/
|
|
35
35
|
options[:params][key] = Integer(value)
|
|
36
36
|
end
|
|
37
|
-
rescue
|
|
37
|
+
rescue
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
options
|