luna_park 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.overcommit.yml +18 -0
- data/.rspec +3 -0
- data/.rubocop.yml +106 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +308 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +182 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +54 -0
- data/Rakefile +30 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/docs/.nojekyll +0 -0
- data/docs/CNAME +1 -0
- data/docs/README.md +190 -0
- data/docs/_coverpage.md +18 -0
- data/docs/_imgs/adapter.png +0 -0
- data/docs/_imgs/bender.jpeg +0 -0
- data/docs/_imgs/bender_header.jpeg +0 -0
- data/docs/_imgs/collecting.png +0 -0
- data/docs/_imgs/conductor_schema.png +0 -0
- data/docs/_imgs/ddd_header.jpeg +0 -0
- data/docs/_imgs/ddd_map.png +0 -0
- data/docs/_imgs/domain_context.jpeg +0 -0
- data/docs/_imgs/drunk_master.jpg +0 -0
- data/docs/_imgs/full_map.png +0 -0
- data/docs/_imgs/full_map_hight_res.png +0 -0
- data/docs/_imgs/graph_1.png +0 -0
- data/docs/_imgs/graph_2.jpg +0 -0
- data/docs/_imgs/graph_3.png +0 -0
- data/docs/_imgs/graph_4.jpg +0 -0
- data/docs/_imgs/graph_5.jpg +0 -0
- data/docs/_imgs/graph_5.png +0 -0
- data/docs/_imgs/processing.png +0 -0
- data/docs/_imgs/representation.png +0 -0
- data/docs/_imgs/storage.png +0 -0
- data/docs/_imgs/tourtle_context_map.png +0 -0
- data/docs/_imgs/tree.png +0 -0
- data/docs/_imgs/wm.jpeg +0 -0
- data/docs/_media/bender.jpg +0 -0
- data/docs/_media/black_cover.jpg +0 -0
- data/docs/_media/logo.svg +7 -0
- data/docs/_sidebar.md +9 -0
- data/docs/architecture.md +214 -0
- data/docs/google48f1e6f5c35eae5f.html +1 -0
- data/docs/index.html +32 -0
- data/docs/methodology.md +376 -0
- data/docs/patterns/entity.md +193 -0
- data/docs/patterns/sequence.md +332 -0
- data/docs/patterns/service.md +280 -0
- data/docs/patterns/value.md +197 -0
- data/docs/way.md +66 -0
- data/lib/luna_park.rb +72 -0
- data/lib/luna_park/callable.rb +7 -0
- data/lib/luna_park/entities/attributable.rb +18 -0
- data/lib/luna_park/entities/nested.rb +28 -0
- data/lib/luna_park/entities/simple.rb +39 -0
- data/lib/luna_park/errors.rb +16 -0
- data/lib/luna_park/errors/base.rb +244 -0
- data/lib/luna_park/errors/business.rb +9 -0
- data/lib/luna_park/errors/http.rb +90 -0
- data/lib/luna_park/errors/json_parse.rb +11 -0
- data/lib/luna_park/errors/system.rb +9 -0
- data/lib/luna_park/extensions/attributable.rb +26 -0
- data/lib/luna_park/extensions/callable.rb +44 -0
- data/lib/luna_park/extensions/comparable.rb +90 -0
- data/lib/luna_park/extensions/comparable_debug.rb +96 -0
- data/lib/luna_park/extensions/data_mapper.rb +195 -0
- data/lib/luna_park/extensions/dsl/attributes.rb +135 -0
- data/lib/luna_park/extensions/dsl/foreign_key.rb +97 -0
- data/lib/luna_park/extensions/exceptions/substitutive.rb +83 -0
- data/lib/luna_park/extensions/has_errors.rb +125 -0
- data/lib/luna_park/extensions/injector.rb +189 -0
- data/lib/luna_park/extensions/injector/dependencies.rb +74 -0
- data/lib/luna_park/extensions/predicate_attr_accessor.rb +23 -0
- data/lib/luna_park/extensions/repositories/postgres/create.rb +20 -0
- data/lib/luna_park/extensions/repositories/postgres/delete.rb +15 -0
- data/lib/luna_park/extensions/repositories/postgres/read.rb +63 -0
- data/lib/luna_park/extensions/repositories/postgres/update.rb +21 -0
- data/lib/luna_park/extensions/serializable.rb +99 -0
- data/lib/luna_park/extensions/severity_levels.rb +120 -0
- data/lib/luna_park/extensions/typed_attr_accessor.rb +26 -0
- data/lib/luna_park/extensions/validatable.rb +80 -0
- data/lib/luna_park/extensions/validatable/dry.rb +24 -0
- data/lib/luna_park/extensions/wrappable.rb +43 -0
- data/lib/luna_park/forms/simple.rb +63 -0
- data/lib/luna_park/forms/single_item.rb +74 -0
- data/lib/luna_park/handlers/simple.rb +17 -0
- data/lib/luna_park/http/client.rb +328 -0
- data/lib/luna_park/http/request.rb +225 -0
- data/lib/luna_park/http/response.rb +381 -0
- data/lib/luna_park/http/send.rb +103 -0
- data/lib/luna_park/mappers/simple.rb +92 -0
- data/lib/luna_park/notifiers/bugsnag.rb +48 -0
- data/lib/luna_park/notifiers/log.rb +174 -0
- data/lib/luna_park/notifiers/sentry.rb +50 -0
- data/lib/luna_park/repositories/postgres.rb +38 -0
- data/lib/luna_park/repositories/sequel.rb +11 -0
- data/lib/luna_park/repository.rb +9 -0
- data/lib/luna_park/serializers/simple.rb +28 -0
- data/lib/luna_park/tools.rb +19 -0
- data/lib/luna_park/use_cases/scenario.rb +325 -0
- data/lib/luna_park/use_cases/service.rb +13 -0
- data/lib/luna_park/validators/dry.rb +67 -0
- data/lib/luna_park/values/attributable.rb +21 -0
- data/lib/luna_park/values/compound.rb +26 -0
- data/lib/luna_park/values/single.rb +35 -0
- data/lib/luna_park/version.rb +5 -0
- data/luna_park.gemspec +54 -0
- data/node_modules/.yarn-integrity +12 -0
- data/package-lock.json +3 -0
- data/yarn.lock +4 -0
- metadata +414 -0
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'luna_park/extensions/comparable'
|
4
|
+
require 'luna_park/extensions/serializable'
|
5
|
+
require 'luna_park/extensions/predicate_attr_accessor'
|
6
|
+
require 'luna_park/extensions/typed_attr_accessor'
|
7
|
+
|
8
|
+
module LunaPark
|
9
|
+
module Extensions
|
10
|
+
module Dsl
|
11
|
+
##
|
12
|
+
# class-level mixin
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# class Elephant
|
16
|
+
# include LunaPark::Extensions::Comparable # required for Dsl::Attributes
|
17
|
+
# include LunaPark::Extensions::Serializable # required for Dsl::Attributes
|
18
|
+
# extend LunaPark::Extensions::Dsl::Attributes
|
19
|
+
#
|
20
|
+
# # ...
|
21
|
+
#
|
22
|
+
# attr :eyes_count # simple accessor registered as serializable and comparable attribute
|
23
|
+
# attr :ears, Ears, :new # :new or any other class method that will construct Ear object
|
24
|
+
# attr :defeat_enemies, comparable: false # will not be used in compasrion via `#==``
|
25
|
+
# attr? :alive # will add predicate? method
|
26
|
+
#
|
27
|
+
# attr_accessor :smuggler
|
28
|
+
#
|
29
|
+
# # ...
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# e1 = Elephant.new(eyes_count: 1, ears: { left: true, right: true }, defeat_enemies: 228, alive: true, smuggler: true)
|
33
|
+
# e2 = Elephant.new(eyes_count: 1, ears: { left: true, right: true }, defeat_enemies: 0, alive: true, smuggler: false)
|
34
|
+
# e3 = Elephant.new(eyes_count: 2, ears: { left: true, right: true }, defeat_enemies: 0, alive: true, smuggler: true)
|
35
|
+
#
|
36
|
+
# e1 == e2 # => true (comparsion disabled for #defeat_enemies and not registered for #smuggler)
|
37
|
+
# e1 == e3 # => false (missmatch of #eyes_count)
|
38
|
+
#
|
39
|
+
# e1.to_h # => { eyes_count: 1, ears: { left: true, right: true }, defeat_enemies: 228, alive: true }
|
40
|
+
# # (omit #smuggler cause it's not registered for serialization)
|
41
|
+
#
|
42
|
+
# e1.ears # => #<Ears left=true right=true>
|
43
|
+
# e1.ears = { left: true, right: false }
|
44
|
+
# e1.ears # => #<Ears left=true right=false>
|
45
|
+
#
|
46
|
+
# e1.alive? # => true
|
47
|
+
module Attributes
|
48
|
+
DEFAULT_TYPE_METH = :call
|
49
|
+
private_constant :DEFAULT_TYPE_METH
|
50
|
+
|
51
|
+
include PredicateAttrAccessor
|
52
|
+
include TypedAttrAccessor
|
53
|
+
|
54
|
+
##
|
55
|
+
# .attr that also adds `reader?`
|
56
|
+
#
|
57
|
+
# @return [Array of Hash(Symbol => Symbol)] Hash of defined methods
|
58
|
+
def attrs?(*args, **options)
|
59
|
+
defined_methods_arr = attrs(*args, **options)
|
60
|
+
getter_names = defined_methods_arr.map { |r| r[:getter] }
|
61
|
+
|
62
|
+
protected(*getter_names)
|
63
|
+
attr_reader?(*getter_names)
|
64
|
+
|
65
|
+
defined_methods_arr.map { |r| r.merge(predicate: :"#{r[:getter]}?") }
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# .attrs that also adds `reader?`
|
70
|
+
# return Hash of defined methods `{ getter: :foo, setter: :foo=, predicate }`
|
71
|
+
#
|
72
|
+
# @return [Hash(Symbol => Symbol)] Hash of defined methods
|
73
|
+
def attr?(*args, **options)
|
74
|
+
defined_methods = attr(*args, **options)
|
75
|
+
getter_name = defined_methods[:getter]
|
76
|
+
|
77
|
+
protected(getter_name)
|
78
|
+
attr_reader?(getter_name)
|
79
|
+
|
80
|
+
defined_methods.merge(predicate: :"#{getter_name}?")
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# .attr with mass defining
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# attrs name1, name2, name3, **attr_options
|
88
|
+
# attrs name1, name2, name3, CallableType, **attr_options
|
89
|
+
# attrs name1, name2, name3, Type, :type_method, **attr_options
|
90
|
+
#
|
91
|
+
# @return [Array of Hash(Symbol => Symbol)] Hash of defined methods
|
92
|
+
def attrs(*args, **options) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
93
|
+
*names, type, type_meth = if args.all? { |arg| arg.is_a?(Symbol) }
|
94
|
+
[*args, nil, nil]
|
95
|
+
elsif args[0..-2].all? { |arg| arg.is_a?(Symbol) }
|
96
|
+
[*args, DEFAULT_TYPE_METH]
|
97
|
+
elsif args[0..-3].all? { |arg| arg.is_a?(Symbol) } && args.last.is_a?(Symbol)
|
98
|
+
args
|
99
|
+
else
|
100
|
+
raise ArgumentError, 'must be (*names) | ' \
|
101
|
+
'(*names, type) | (*names, type, type_meth)'
|
102
|
+
end
|
103
|
+
|
104
|
+
names.map { |name| attr name, type, type_meth, **options }
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# define typed attr_accessor, register it for Extenions::Comparable, Extenions::Serializable
|
109
|
+
# so it will be comparable using `#==`, `#eql?` and serializable using `#to_h`, `#serialize`
|
110
|
+
# return Hash of defined methods `{ getter: :foo, setter: :foo= }`
|
111
|
+
#
|
112
|
+
# @param name [Symbol]
|
113
|
+
# @param type [Object] any object that responds to method described in next param. Skip if you dont need stypification
|
114
|
+
# @param type_meth [Symbol] (call)
|
115
|
+
# @option options [Bool] comparable (true)
|
116
|
+
# @option options [Bool] array (false)
|
117
|
+
# @option options [Bool] private_setter (false)
|
118
|
+
#
|
119
|
+
# @return [Hash(Symbol => Symbol)]
|
120
|
+
# Hash of defined methods { :method_role => :method_name }; `{ getter: :foo }`
|
121
|
+
def attr(name, type = nil, type_meth = nil, comparable: true, array: false)
|
122
|
+
type_meth ||= DEFAULT_TYPE_METH
|
123
|
+
attr_reader(name)
|
124
|
+
|
125
|
+
serializable_attributes(name) if include?(Serializable)
|
126
|
+
comparable_attributes(name) if comparable && include?(Comparable)
|
127
|
+
|
128
|
+
typed_attr_writer(name, type&.method(type_meth), is_array: array)
|
129
|
+
|
130
|
+
{ getter: name, setter: :"#{name}=" }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'luna_park/extensions/comparable'
|
4
|
+
require 'luna_park/extensions/serializable'
|
5
|
+
|
6
|
+
module LunaPark
|
7
|
+
module Extensions
|
8
|
+
module Dsl
|
9
|
+
# @example
|
10
|
+
# class Transaction
|
11
|
+
# # ...
|
12
|
+
# include LunaPark::Extensions::Dsl::ForeignKey
|
13
|
+
#
|
14
|
+
# foreign_key :user_uid, :user, primary_key: :uid # `primary_key:` default is `:id`
|
15
|
+
# # foreign_key points to primary_key
|
16
|
+
#
|
17
|
+
# # OR alias:
|
18
|
+
# fk :user_uid, :user, pk: :uid
|
19
|
+
#
|
20
|
+
# # ...
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# t = Transaction.new
|
24
|
+
# t1.user # => nil
|
25
|
+
# t1.user_uid # => nil
|
26
|
+
#
|
27
|
+
# t1.user = User.new(uid: 42)
|
28
|
+
# t1.user # => #<User uid=42>
|
29
|
+
# t1.user_uid # => 42 (changed)
|
30
|
+
#
|
31
|
+
# t1.user # => #<User uid=42>
|
32
|
+
# t1.user = nil
|
33
|
+
# t1.user_uid # => nil (removed)
|
34
|
+
#
|
35
|
+
# t1.user # => #<User uid=42>
|
36
|
+
# t1.user_uid = nil
|
37
|
+
# t1.user # => nil (removed)
|
38
|
+
#
|
39
|
+
# t1.user # => #<User uid=42>
|
40
|
+
# t1.user = User.new(uid: 666)
|
41
|
+
# t1.user_uid # => 666 (changed)
|
42
|
+
#
|
43
|
+
# t1.user # => #<User uid=42>
|
44
|
+
# t1.user_uid = 666
|
45
|
+
# t1.user # => nil (removed cause uid missmatch)
|
46
|
+
module ForeignKey
|
47
|
+
def self.extended(base)
|
48
|
+
base.extend ClassMethods
|
49
|
+
base.include InstanceMethods
|
50
|
+
end
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
def fk(fk_name, assoc_name, pk: :id)
|
54
|
+
foreign_key(fk_name, assoc_name, primary_key: pk)
|
55
|
+
end
|
56
|
+
|
57
|
+
def foreign_key(fk_name, assoc_name, primary_key: :id) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
58
|
+
pk_name = primary_key
|
59
|
+
serializable_attributes(fk_name) if include?(Serializable)
|
60
|
+
comparable_attributes(fk_name) if include?(Comparable)
|
61
|
+
|
62
|
+
attr_reader fk_name, assoc_name
|
63
|
+
|
64
|
+
anonym_mixin = Module.new do
|
65
|
+
define_method(:"#{assoc_name}=") do |new_assoc|
|
66
|
+
new_assoc_pk = extract_pk_value_from_object__(new_assoc, pk_name)
|
67
|
+
instance_variable_set(:"@#{fk_name}", new_assoc_pk)
|
68
|
+
instance_variable_set(:"@#{assoc_name}", new_assoc)
|
69
|
+
end
|
70
|
+
|
71
|
+
define_method(:"#{fk_name}=") do |new_fk|
|
72
|
+
assoc = public_send(assoc_name)
|
73
|
+
instance_variable_set(:"@#{fk_name}", new_fk)
|
74
|
+
return new_fk if assoc.nil?
|
75
|
+
|
76
|
+
current_assoc_pk = extract_pk_value_from_object__(assoc, pk_name)
|
77
|
+
instance_variable_set(:"@#{assoc_name}", nil) unless new_fk == current_assoc_pk
|
78
|
+
new_fk
|
79
|
+
end
|
80
|
+
end
|
81
|
+
include anonym_mixin
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module InstanceMethods
|
86
|
+
private
|
87
|
+
|
88
|
+
def extract_pk_value_from_object__(object, pk_name)
|
89
|
+
object.respond_to?(:[]) && object[pk_name] ||
|
90
|
+
object.respond_to?(pk_name) && object.public_send(pk_name) ||
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LunaPark
|
4
|
+
module Extensions
|
5
|
+
module Exceptions
|
6
|
+
# class-level mixin
|
7
|
+
|
8
|
+
module Substitutive
|
9
|
+
def self.extended(base)
|
10
|
+
base.extend ClassMethods
|
11
|
+
base.include InstanceMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
# Substitute original exception with save original backtrace.
|
16
|
+
#
|
17
|
+
# @example bad case
|
18
|
+
# class MyException < StandardError
|
19
|
+
# extend LunaPark::Extensions::Exceptions::Substitutive
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# call_exceptional_lib!
|
23
|
+
# rescue ExceptionalLib::SomeException => e
|
24
|
+
# raise MyException # => raised MyException with backtrace started from `raise MyException`
|
25
|
+
# # and not contained origin exception backtrace
|
26
|
+
# # that can be very painfull for debug
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @example resolve
|
30
|
+
# begin
|
31
|
+
# call_exceptional_lib!
|
32
|
+
# rescue ExceptionalLib::SomeException => e
|
33
|
+
# raise MyException.substitute(e) # => raised MyException with backtrace started
|
34
|
+
# # from library `raise ExceptionalLib::SomeException`
|
35
|
+
# # so you can easily find out where exception starts
|
36
|
+
# end
|
37
|
+
def substitute(origin)
|
38
|
+
new = new(origin.message)
|
39
|
+
new.backtrace = origin.backtrace
|
40
|
+
new.origin = origin
|
41
|
+
new
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module InstanceMethods
|
46
|
+
attr_accessor :origin
|
47
|
+
attr_writer :backtrace
|
48
|
+
|
49
|
+
def backtrace
|
50
|
+
super || @backtrace
|
51
|
+
end
|
52
|
+
|
53
|
+
# Cover up trace for current exception
|
54
|
+
#
|
55
|
+
# @example bad case
|
56
|
+
# begin
|
57
|
+
# call_exceptional_lib!
|
58
|
+
# rescue ExceptionalLib::SomeException => e
|
59
|
+
# send_alert_to_developer
|
60
|
+
# raise e # => raised `ExceptionalLib::SomeException` with backtrace started
|
61
|
+
# # from current line and not contained origin exception backtrace
|
62
|
+
# # that can be very painful for debug
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# @example resolve
|
66
|
+
# begin
|
67
|
+
# call_exceptional_lib!
|
68
|
+
# rescue ExceptionalLib::SomeException => e
|
69
|
+
# send_alert_to_developer
|
70
|
+
# raise e.cover_up_backtrace # => raised `ExceptionalLib::SomeException` with original backtrace
|
71
|
+
# # so you can easily find out where exception starts
|
72
|
+
# end
|
73
|
+
def cover_up_backtrace
|
74
|
+
new = dup
|
75
|
+
new.backtrace = backtrace
|
76
|
+
new.origin = self
|
77
|
+
new
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'luna_park/errors/business'
|
4
|
+
require 'luna_park/errors/system'
|
5
|
+
|
6
|
+
module LunaPark
|
7
|
+
module Extensions
|
8
|
+
##
|
9
|
+
# This is syntax sugar for define exception class
|
10
|
+
# in UseCase layer
|
11
|
+
#
|
12
|
+
# @example without sugar
|
13
|
+
# class Service
|
14
|
+
# class UserNotExists < LunaPark::Errors::Business
|
15
|
+
# message 'Sorry but user does not exists'
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# def call
|
19
|
+
# raise UserNotExists if something_wrong
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example with sugar
|
24
|
+
# class Service
|
25
|
+
# include LunaPark::Extensions::HasErrors
|
26
|
+
# error :user_not_exists, 'Sorry but user does not exists'
|
27
|
+
#
|
28
|
+
# def call
|
29
|
+
# error :user_not_exists if something_wrong
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
module HasErrors
|
33
|
+
def self.included(base)
|
34
|
+
base.extend ClassMethods
|
35
|
+
base.include InstanceMethods
|
36
|
+
end
|
37
|
+
|
38
|
+
module InstanceMethods
|
39
|
+
##
|
40
|
+
# Raise error defined in class
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# class Service
|
44
|
+
# include LunaPark::Extensions::HasErrors
|
45
|
+
#
|
46
|
+
# class CustomError < LunaPark::Errors::Business; end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# Service.new.error :custom_error # => raise CustomError
|
50
|
+
#
|
51
|
+
# @param title [Symbol|String] - Title of error
|
52
|
+
# @param msg [String] - Message of error
|
53
|
+
# @param **attrs - See @LunaPark::Errors::Base#new
|
54
|
+
def error(title, msg = nil, **attrs)
|
55
|
+
class_name = self.class.error_class_name(title)
|
56
|
+
raise self.class.const_get(class_name).new msg, **attrs
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module ClassMethods
|
61
|
+
##
|
62
|
+
# Define business error
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# class Service
|
66
|
+
# include LunaPark::Extensions::HasErrors
|
67
|
+
#
|
68
|
+
# business_error :logic_error, { (1 + 1).to_s }
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# logic_error = Service::LogicError.new
|
72
|
+
# logic_error.is_a? LunaPark::Errors::Business # => true
|
73
|
+
# logic_error.message # => '2'
|
74
|
+
def business_error(title, txt = nil, i18n_key: nil, notify: nil, &default_message_block)
|
75
|
+
error_class = Class.new(Errors::Business)
|
76
|
+
error_class.message(txt, i18n_key: i18n_key, &default_message_block)
|
77
|
+
error_class.notify(notify)
|
78
|
+
const_set(error_class_name(title), error_class)
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Alias for business error
|
83
|
+
alias error business_error
|
84
|
+
|
85
|
+
##
|
86
|
+
# Define business error
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# class Service
|
90
|
+
# include LunaPark::Extensions::HasErrors
|
91
|
+
#
|
92
|
+
# system_error :tech_error, 'Custom message'
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# tech_error = Service::TechError.new
|
96
|
+
# tech_error.is_a? LunaPark::Errors::System # => true
|
97
|
+
# tech_error.message # => 'Custom message'
|
98
|
+
def system_error(title, txt = nil, i18n_key: nil, notify: nil, &default_message_block)
|
99
|
+
error_class = Class.new(Errors::System)
|
100
|
+
error_class.message(txt, i18n_key: i18n_key, &default_message_block)
|
101
|
+
error_class.notify(notify)
|
102
|
+
const_set(error_class_name(title), error_class)
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Get error class name
|
107
|
+
#
|
108
|
+
# @example when title is string
|
109
|
+
# error_class_name('CamelCase') # => 'CamelCase'
|
110
|
+
#
|
111
|
+
# @example when title is symbol
|
112
|
+
# error_class_name(:snake_case) # => 'SnakeCase'
|
113
|
+
#
|
114
|
+
# @param [String|Symbol] title - short alias for error
|
115
|
+
def error_class_name(title)
|
116
|
+
case title
|
117
|
+
when String then title
|
118
|
+
when Symbol then title.to_s.split('_').collect!(&:capitalize).join
|
119
|
+
else raise ArgumentError, "Unknown type `#{title}` for error title"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'luna_park/extensions/injector/dependencies'
|
4
|
+
|
5
|
+
module LunaPark
|
6
|
+
module Extensions
|
7
|
+
# The main goal of the injector is a help developer with
|
8
|
+
# dependency injection technique.
|
9
|
+
#
|
10
|
+
# @example Dependency injection
|
11
|
+
# class Messenger
|
12
|
+
# def self.post(to:, msg:); end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# module Users
|
16
|
+
# class Entity
|
17
|
+
# def active!; end
|
18
|
+
# def full_name;end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# class PgRepo
|
22
|
+
# def find(id); end
|
23
|
+
# def save(user); end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# # In this example, you can see the relationship between
|
27
|
+
# # the business layer and the data layer, and between the
|
28
|
+
# # business layer and external libraries.
|
29
|
+
#
|
30
|
+
# class SetActive < LunaPark::UseCases::Scenario
|
31
|
+
# include LunaPark::Extensions::Injector
|
32
|
+
#
|
33
|
+
# attr_accessor :user_id
|
34
|
+
#
|
35
|
+
# def call!
|
36
|
+
# repo = Users::PgRepo.new # <- dependency from data layer
|
37
|
+
# user = repo.find(user_id)
|
38
|
+
# user.active!
|
39
|
+
# repo.save user
|
40
|
+
#
|
41
|
+
# Messenger.post(to: :admin, msg:"User #{user.full_name} is active_now") # <- dependency from external libs
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# # Here, Injector can help remove the dependency technical details from
|
47
|
+
# # the business layer.
|
48
|
+
#
|
49
|
+
# class SetActive < LunaPark::Interactors::Scenario
|
50
|
+
# include LunaPark::Extensions::Injector
|
51
|
+
#
|
52
|
+
# dependency(:repo) { Users::PgRepo.new } # You should define dependency in block - like rspec `let`
|
53
|
+
# # method. That should initialize value only if that needed.
|
54
|
+
# dependency(:messenger) { Messenger }
|
55
|
+
#
|
56
|
+
# attr_accessor :user_id
|
57
|
+
#
|
58
|
+
# def call!
|
59
|
+
# user = repo.find(user_id)
|
60
|
+
# user.active!
|
61
|
+
# repo.save user
|
62
|
+
#
|
63
|
+
# messenger.post(to: :admin, msg:"User #{user.full_name} is active_now")
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
#
|
68
|
+
# @example rspec test
|
69
|
+
# module Users
|
70
|
+
# RSpec.describe SetActive do
|
71
|
+
# # We highly dont recommended inject dependencies witch does not call exteranal resources
|
72
|
+
# let(:user) { Entity.new id: 1, first_name: 'John', last_name: 'Doe'}
|
73
|
+
# let(:use_case) { described_class.new(user_id: 1) }
|
74
|
+
#
|
75
|
+
# before do
|
76
|
+
# use_case.dependencies = {
|
77
|
+
# repo: -> { instance_double PgRepo, find: user, save: true },
|
78
|
+
# messenger: -> { class_double Messenger, post: true }
|
79
|
+
# }
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# describe '#call!' do
|
83
|
+
# subject(:set_active!) { use_case.call! }
|
84
|
+
#
|
85
|
+
# it 'should set user is active' do
|
86
|
+
# expect{ set_active! }.to change{ user.active? }.from(false).to(true)
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# it 'should save user' do
|
90
|
+
# expect(use_case.repo).to receive(:save).with(user)
|
91
|
+
# set_active!
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# it 'should send expected message to admin' do
|
95
|
+
# text = 'User John Doe is active_now'
|
96
|
+
# expect(use_case.messenger).to receive(:post).with(to: :admin, msg: text)
|
97
|
+
# set_active!
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
# end
|
101
|
+
# end
|
102
|
+
module Injector
|
103
|
+
# @!parse include Injector::ClassMethods
|
104
|
+
# @!parse extend Injector::InstanceMethods
|
105
|
+
def self.included(base)
|
106
|
+
base.extend ClassMethods
|
107
|
+
base.include InstanceMethods
|
108
|
+
end
|
109
|
+
|
110
|
+
module ClassMethods
|
111
|
+
##
|
112
|
+
# Set dependency
|
113
|
+
#
|
114
|
+
# @example Set dependency
|
115
|
+
# class Foo
|
116
|
+
# include LunaPark::Extensions::Injector
|
117
|
+
#
|
118
|
+
# dependency(:example) { Bar.new }
|
119
|
+
# end
|
120
|
+
def dependency(name, &block)
|
121
|
+
dependencies[name] = block
|
122
|
+
|
123
|
+
define_method(name) do
|
124
|
+
dependencies.call_with_cache(name)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# List class defined dependencies
|
130
|
+
#
|
131
|
+
# @example get dependency
|
132
|
+
# class Foo
|
133
|
+
# include LunaPark::Extensions::Injector
|
134
|
+
#
|
135
|
+
# dependency(:example) { Bar.new }
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# Foo.dependencies # => {:example=>#<Proc:0x0000560a4fb48fc0@t.rb:77>}
|
139
|
+
def dependencies
|
140
|
+
@dependencies ||= {}
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
module InstanceMethods
|
145
|
+
##
|
146
|
+
# List instance defined dependencies, in default it defined in class
|
147
|
+
# methods.
|
148
|
+
#
|
149
|
+
# class SetActive
|
150
|
+
# dependency(:repo) { Repo.new(CONFIG[:db_connect]) }
|
151
|
+
# dependency(:messenger) { Messenger }
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
# use_case = SetActive.new(user_id: 1)
|
155
|
+
#
|
156
|
+
# # All dependencies
|
157
|
+
# use_case.dependencies # => {
|
158
|
+
# # :repo=>#<Proc:0x0000564a0d90d668@t.rb:33>,
|
159
|
+
# # :messenger=>#<Proc:0x0000564a0d90d438@t.rb:34>
|
160
|
+
# # }
|
161
|
+
#
|
162
|
+
# # Single dependency
|
163
|
+
# use_case.dependencies[:messenger] # => #<Proc:0x0000564a0d90d438@t.rb:34>
|
164
|
+
#
|
165
|
+
# # Dependency value
|
166
|
+
# use_case.messenger # => Messenger
|
167
|
+
def dependencies
|
168
|
+
@dependencies ||= Dependencies.wrap(self.class.dependencies)
|
169
|
+
end
|
170
|
+
|
171
|
+
##
|
172
|
+
# Setter - highly recommended for use in specs so you don't forget
|
173
|
+
# to override <b>all</b> dependencies.
|
174
|
+
#
|
175
|
+
# use_case.dependencies = {
|
176
|
+
# repo: -> { Fake::Repo.new }
|
177
|
+
# }
|
178
|
+
# use_case.messenger # => Dependency `messenger` is undefined (LunaPark::Errors::DependencyUndefined)
|
179
|
+
# use_case.call # => Dependency `messenger` is undefined (LunaPark::Errors::DependencyUndefined)
|
180
|
+
#
|
181
|
+
# # Redefine single dependency still possible
|
182
|
+
# use_case.dependencies[:messenger] = -> { Messenger }
|
183
|
+
def dependencies=(value)
|
184
|
+
@dependencies = Dependencies.wrap(value)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|