luna_park 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.overcommit.yml +18 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +106 -0
  6. data/.ruby-gemset +1 -0
  7. data/.ruby-version +1 -0
  8. data/.travis.yml +6 -0
  9. data/CHANGELOG.md +308 -0
  10. data/Gemfile +8 -0
  11. data/Gemfile.lock +182 -0
  12. data/LICENSE +21 -0
  13. data/LICENSE.txt +21 -0
  14. data/README.md +54 -0
  15. data/Rakefile +30 -0
  16. data/bin/console +15 -0
  17. data/bin/setup +8 -0
  18. data/docs/.nojekyll +0 -0
  19. data/docs/CNAME +1 -0
  20. data/docs/README.md +190 -0
  21. data/docs/_coverpage.md +18 -0
  22. data/docs/_imgs/adapter.png +0 -0
  23. data/docs/_imgs/bender.jpeg +0 -0
  24. data/docs/_imgs/bender_header.jpeg +0 -0
  25. data/docs/_imgs/collecting.png +0 -0
  26. data/docs/_imgs/conductor_schema.png +0 -0
  27. data/docs/_imgs/ddd_header.jpeg +0 -0
  28. data/docs/_imgs/ddd_map.png +0 -0
  29. data/docs/_imgs/domain_context.jpeg +0 -0
  30. data/docs/_imgs/drunk_master.jpg +0 -0
  31. data/docs/_imgs/full_map.png +0 -0
  32. data/docs/_imgs/full_map_hight_res.png +0 -0
  33. data/docs/_imgs/graph_1.png +0 -0
  34. data/docs/_imgs/graph_2.jpg +0 -0
  35. data/docs/_imgs/graph_3.png +0 -0
  36. data/docs/_imgs/graph_4.jpg +0 -0
  37. data/docs/_imgs/graph_5.jpg +0 -0
  38. data/docs/_imgs/graph_5.png +0 -0
  39. data/docs/_imgs/processing.png +0 -0
  40. data/docs/_imgs/representation.png +0 -0
  41. data/docs/_imgs/storage.png +0 -0
  42. data/docs/_imgs/tourtle_context_map.png +0 -0
  43. data/docs/_imgs/tree.png +0 -0
  44. data/docs/_imgs/wm.jpeg +0 -0
  45. data/docs/_media/bender.jpg +0 -0
  46. data/docs/_media/black_cover.jpg +0 -0
  47. data/docs/_media/logo.svg +7 -0
  48. data/docs/_sidebar.md +9 -0
  49. data/docs/architecture.md +214 -0
  50. data/docs/google48f1e6f5c35eae5f.html +1 -0
  51. data/docs/index.html +32 -0
  52. data/docs/methodology.md +376 -0
  53. data/docs/patterns/entity.md +193 -0
  54. data/docs/patterns/sequence.md +332 -0
  55. data/docs/patterns/service.md +280 -0
  56. data/docs/patterns/value.md +197 -0
  57. data/docs/way.md +66 -0
  58. data/lib/luna_park.rb +72 -0
  59. data/lib/luna_park/callable.rb +7 -0
  60. data/lib/luna_park/entities/attributable.rb +18 -0
  61. data/lib/luna_park/entities/nested.rb +28 -0
  62. data/lib/luna_park/entities/simple.rb +39 -0
  63. data/lib/luna_park/errors.rb +16 -0
  64. data/lib/luna_park/errors/base.rb +244 -0
  65. data/lib/luna_park/errors/business.rb +9 -0
  66. data/lib/luna_park/errors/http.rb +90 -0
  67. data/lib/luna_park/errors/json_parse.rb +11 -0
  68. data/lib/luna_park/errors/system.rb +9 -0
  69. data/lib/luna_park/extensions/attributable.rb +26 -0
  70. data/lib/luna_park/extensions/callable.rb +44 -0
  71. data/lib/luna_park/extensions/comparable.rb +90 -0
  72. data/lib/luna_park/extensions/comparable_debug.rb +96 -0
  73. data/lib/luna_park/extensions/data_mapper.rb +195 -0
  74. data/lib/luna_park/extensions/dsl/attributes.rb +135 -0
  75. data/lib/luna_park/extensions/dsl/foreign_key.rb +97 -0
  76. data/lib/luna_park/extensions/exceptions/substitutive.rb +83 -0
  77. data/lib/luna_park/extensions/has_errors.rb +125 -0
  78. data/lib/luna_park/extensions/injector.rb +189 -0
  79. data/lib/luna_park/extensions/injector/dependencies.rb +74 -0
  80. data/lib/luna_park/extensions/predicate_attr_accessor.rb +23 -0
  81. data/lib/luna_park/extensions/repositories/postgres/create.rb +20 -0
  82. data/lib/luna_park/extensions/repositories/postgres/delete.rb +15 -0
  83. data/lib/luna_park/extensions/repositories/postgres/read.rb +63 -0
  84. data/lib/luna_park/extensions/repositories/postgres/update.rb +21 -0
  85. data/lib/luna_park/extensions/serializable.rb +99 -0
  86. data/lib/luna_park/extensions/severity_levels.rb +120 -0
  87. data/lib/luna_park/extensions/typed_attr_accessor.rb +26 -0
  88. data/lib/luna_park/extensions/validatable.rb +80 -0
  89. data/lib/luna_park/extensions/validatable/dry.rb +24 -0
  90. data/lib/luna_park/extensions/wrappable.rb +43 -0
  91. data/lib/luna_park/forms/simple.rb +63 -0
  92. data/lib/luna_park/forms/single_item.rb +74 -0
  93. data/lib/luna_park/handlers/simple.rb +17 -0
  94. data/lib/luna_park/http/client.rb +328 -0
  95. data/lib/luna_park/http/request.rb +225 -0
  96. data/lib/luna_park/http/response.rb +381 -0
  97. data/lib/luna_park/http/send.rb +103 -0
  98. data/lib/luna_park/mappers/simple.rb +92 -0
  99. data/lib/luna_park/notifiers/bugsnag.rb +48 -0
  100. data/lib/luna_park/notifiers/log.rb +174 -0
  101. data/lib/luna_park/notifiers/sentry.rb +50 -0
  102. data/lib/luna_park/repositories/postgres.rb +38 -0
  103. data/lib/luna_park/repositories/sequel.rb +11 -0
  104. data/lib/luna_park/repository.rb +9 -0
  105. data/lib/luna_park/serializers/simple.rb +28 -0
  106. data/lib/luna_park/tools.rb +19 -0
  107. data/lib/luna_park/use_cases/scenario.rb +325 -0
  108. data/lib/luna_park/use_cases/service.rb +13 -0
  109. data/lib/luna_park/validators/dry.rb +67 -0
  110. data/lib/luna_park/values/attributable.rb +21 -0
  111. data/lib/luna_park/values/compound.rb +26 -0
  112. data/lib/luna_park/values/single.rb +35 -0
  113. data/lib/luna_park/version.rb +5 -0
  114. data/luna_park.gemspec +54 -0
  115. data/node_modules/.yarn-integrity +12 -0
  116. data/package-lock.json +3 -0
  117. data/yarn.lock +4 -0
  118. 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