appfuel 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +25 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1156 -0
  6. data/.travis.yml +19 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +9 -0
  9. data/README.md +38 -0
  10. data/Rakefile +6 -0
  11. data/appfuel.gemspec +42 -0
  12. data/bin/console +7 -0
  13. data/bin/setup +8 -0
  14. data/lib/appfuel.rb +210 -0
  15. data/lib/appfuel/application.rb +4 -0
  16. data/lib/appfuel/application/app_container.rb +223 -0
  17. data/lib/appfuel/application/container_class_registration.rb +22 -0
  18. data/lib/appfuel/application/container_key.rb +201 -0
  19. data/lib/appfuel/application/qualify_container_key.rb +76 -0
  20. data/lib/appfuel/application/root.rb +140 -0
  21. data/lib/appfuel/cli_msg_request.rb +19 -0
  22. data/lib/appfuel/configuration.rb +14 -0
  23. data/lib/appfuel/configuration/definition_dsl.rb +175 -0
  24. data/lib/appfuel/configuration/file_loader.rb +61 -0
  25. data/lib/appfuel/configuration/populate.rb +95 -0
  26. data/lib/appfuel/configuration/search.rb +45 -0
  27. data/lib/appfuel/db_model.rb +16 -0
  28. data/lib/appfuel/domain.rb +7 -0
  29. data/lib/appfuel/domain/criteria.rb +436 -0
  30. data/lib/appfuel/domain/domain_name_parser.rb +44 -0
  31. data/lib/appfuel/domain/dsl.rb +247 -0
  32. data/lib/appfuel/domain/entity.rb +242 -0
  33. data/lib/appfuel/domain/entity_collection.rb +87 -0
  34. data/lib/appfuel/domain/expr.rb +127 -0
  35. data/lib/appfuel/domain/value_object.rb +7 -0
  36. data/lib/appfuel/errors.rb +104 -0
  37. data/lib/appfuel/feature.rb +2 -0
  38. data/lib/appfuel/feature/action_loader.rb +25 -0
  39. data/lib/appfuel/feature/initializer.rb +43 -0
  40. data/lib/appfuel/handler.rb +6 -0
  41. data/lib/appfuel/handler/action.rb +17 -0
  42. data/lib/appfuel/handler/base.rb +103 -0
  43. data/lib/appfuel/handler/command.rb +18 -0
  44. data/lib/appfuel/handler/inject_dsl.rb +88 -0
  45. data/lib/appfuel/handler/validator_dsl.rb +256 -0
  46. data/lib/appfuel/initialize.rb +70 -0
  47. data/lib/appfuel/initialize/initializer.rb +68 -0
  48. data/lib/appfuel/msg_request.rb +207 -0
  49. data/lib/appfuel/predicates.rb +10 -0
  50. data/lib/appfuel/presenter.rb +18 -0
  51. data/lib/appfuel/presenter/base.rb +7 -0
  52. data/lib/appfuel/repository.rb +73 -0
  53. data/lib/appfuel/repository/base.rb +72 -0
  54. data/lib/appfuel/repository/initializer.rb +19 -0
  55. data/lib/appfuel/repository/mapper.rb +203 -0
  56. data/lib/appfuel/repository/mapping_dsl.rb +210 -0
  57. data/lib/appfuel/repository/mapping_entry.rb +76 -0
  58. data/lib/appfuel/repository/mapping_registry.rb +121 -0
  59. data/lib/appfuel/repository_runner.rb +60 -0
  60. data/lib/appfuel/request.rb +53 -0
  61. data/lib/appfuel/response.rb +96 -0
  62. data/lib/appfuel/response_handler.rb +79 -0
  63. data/lib/appfuel/root_module.rb +31 -0
  64. data/lib/appfuel/run_error.rb +9 -0
  65. data/lib/appfuel/storage.rb +3 -0
  66. data/lib/appfuel/storage/db.rb +4 -0
  67. data/lib/appfuel/storage/db/active_record_model.rb +42 -0
  68. data/lib/appfuel/storage/db/mapper.rb +213 -0
  69. data/lib/appfuel/storage/db/migration_initializer.rb +42 -0
  70. data/lib/appfuel/storage/db/migration_runner.rb +15 -0
  71. data/lib/appfuel/storage/db/migration_tasks.rb +18 -0
  72. data/lib/appfuel/storage/db/repository.rb +231 -0
  73. data/lib/appfuel/storage/db/repository_query.rb +13 -0
  74. data/lib/appfuel/storage/file.rb +1 -0
  75. data/lib/appfuel/storage/file/base.rb +32 -0
  76. data/lib/appfuel/storage/memory.rb +2 -0
  77. data/lib/appfuel/storage/memory/mapper.rb +30 -0
  78. data/lib/appfuel/storage/memory/repository.rb +37 -0
  79. data/lib/appfuel/types.rb +53 -0
  80. data/lib/appfuel/validation.rb +80 -0
  81. data/lib/appfuel/validation/validator.rb +59 -0
  82. data/lib/appfuel/validation/validator_pipe.rb +47 -0
  83. data/lib/appfuel/version.rb +3 -0
  84. metadata +335 -0
@@ -0,0 +1,18 @@
1
+ module Appfuel
2
+ module Handler
3
+ class Command < Base
4
+ class << self
5
+ def resolve_dependencies(results = Dry::Container.new)
6
+ =begin
7
+ super
8
+ resolve_container(results)
9
+ resolve_domains(results)
10
+ resolve_db_models(results)
11
+ resolve_repos(results)
12
+ results
13
+ =end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,88 @@
1
+ module Appfuel
2
+ module Handler
3
+ # Allow handlers to inject domains, commands, repositories or anything
4
+ # that was initialized in the app container. Injections are done in two
5
+ # steps, first, when the class is loaded in memory the declarations are
6
+ # converted into fully qualified container keys and second, when the
7
+ # handler is run they plucked from the app container into a container
8
+ # dedicated to that one execution.
9
+ module InjectDsl
10
+ TYPES = [:domain, :cmd, :repo, :container]
11
+
12
+ # Holds a dictionary where the key is the container key and the value
13
+ # is an an optional alias for the name of the injection to be saved
14
+ # as. Injections a separated into two categories because domains are
15
+ # part of the type system (Dry::Types) and therefore kept in its own
16
+ # container "Types", meaning are fetched differently.
17
+ #
18
+ # @return [Hash]
19
+ def injections
20
+ @injections ||= {}
21
+ end
22
+
23
+ # Dsl to declare a dependency injection. You can inject one of four
24
+ # types, which are:
25
+ # domain: these are domains or value object
26
+ # cmd: commands to be run
27
+ # repo: repositories to query the persistence layer
28
+ # container: any initialized container item
29
+ #
30
+ # since names an collide you can rename your injection with the
31
+ # :as option.
32
+ #
33
+ # @example of using an alias to rename a domain injection
34
+ # inject :domain, 'member.user', as: :current_user
35
+ #
36
+ # @example of normal domain injection. In this case the name of the
37
+ # domain with me the base name "user" not "member.user"
38
+ #
39
+ # inject :domain, 'member.user'
40
+ #
41
+ #
42
+ # @param type [Symbol] type of injection
43
+ # @param key [String, Symbol] container key
44
+ # @param opts [Hash]
45
+ # @option as [String, Symbol] alternate name for injection
46
+ # @return nil
47
+ def inject(type, key, opts = {})
48
+ unless inject_type?(type)
49
+ fail "inject type must be #{TYPES.join(",")} #{type} given"
50
+ end
51
+
52
+ cat = case type
53
+ when :repo then 'repositories'
54
+ when :cmd then 'commands'
55
+ when :domain then 'domains'
56
+ else
57
+ "container"
58
+ end
59
+
60
+ namespaced_key = qualify_container_key(key, cat)
61
+ injections[namespaced_key] = opts[:as]
62
+ nil
63
+ end
64
+
65
+ def resolve_dependencies(container = Dry::Container.new)
66
+ app_container = Appfuel.app_container
67
+ injections.each do |key, alias_name|
68
+ unless app_container.key?(key)
69
+ fail "Could not inject (#{key}): not registered in app container"
70
+ end
71
+
72
+ basename = key.split('.').last
73
+ item = app_container[key]
74
+ container_key = alias_name || basename
75
+ container.register(container_key, item)
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # @param type [Symbol]
82
+ # @return [Bool]
83
+ def inject_type?(type)
84
+ TYPES.include?(type)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,256 @@
1
+ module Appfuel
2
+ module Handler
3
+ #
4
+ #
5
+ # 1) single block validator. A basic validator that is only used by
6
+ # that particular interactor
7
+ #
8
+ #
9
+ # validator('foo', fail_fast: true) do
10
+ #
11
+ # end
12
+ #
13
+ # 2) single validator from the features validators located in the app
14
+ # container under the key "features.<feature-name>.validators.<validator-name>"
15
+ #
16
+ # validator 'foo'
17
+ #
18
+ # 3) single validator from the global validators located in the app
19
+ # container under the key "global.validators.<validator-name>"
20
+ #
21
+ # validator 'global.foo'
22
+ #
23
+ # 4) muliple validators, all are located in the feature namespace
24
+ # validators 'foo', 'bar', 'baz'
25
+ #
26
+ # 5) multiple validators, some in features other in global
27
+ # validators 'foo', 'global.bar', 'baz'
28
+ #
29
+ # 6) a pipe is a closure that lives before a given validator in order
30
+ # to manipulate the inputs to fit the next validator. it does not validate
31
+ #
32
+ # validator_pipe do |inputs, data|
33
+ #
34
+ # end
35
+ #
36
+ # 7) using a pipe when using muliple validators
37
+ #
38
+ # validators 'foo', 'pipe.bar', 'base'
39
+ #
40
+ # 8) using global pipe in multiple validator declaration
41
+ # validators 'foo', 'global-pipe.bar', 'base'
42
+ #
43
+ # 9) validator_schema is used to use Dry::Validations with out our block
44
+ # processing
45
+ #
46
+ # validation_schema 'foo', Dry::Validation.Schama do
47
+ #
48
+ # end
49
+ #
50
+ # validation_schema Dry::Validation.Schema do
51
+ #
52
+ # end
53
+ #
54
+ # validation_schema 'foo', fail_fast: true, Dry::Validation.Schema do
55
+ #
56
+ # end
57
+ #
58
+ # validator
59
+ # name: to identify it in errors and as a key to register it with container
60
+ # fail_fast: when true runner will bail on first error
61
+ # pipe?: false
62
+ # code: validator schema to run
63
+ # call: run the validator schema
64
+ #
65
+ # validator-pipe
66
+ # name: to identify the pipe in errors and register it with container
67
+ # code: lambda to run
68
+ # call: run the lamda
69
+ module ValidatorDsl
70
+
71
+ def validators(*args)
72
+ @validators ||= []
73
+ return @validators if args.empty?
74
+
75
+ args.each do |arg|
76
+ @validators << load_validator(arg)
77
+ end
78
+ end
79
+
80
+ # When no name for a validator is given then the default name will
81
+ # be the name of the concrete handler class
82
+ #
83
+ # @return [String]
84
+ def default_validator_name
85
+ self.to_s.split('::').last.underscore
86
+ end
87
+
88
+ # Converts a given block to a validator or load the validator from
89
+ # either global or feature validators
90
+ #
91
+ # @param key [String] name of the validator
92
+ # @param opts [Hash] options for creating a validator
93
+ # @option fail_fast [Bool] allows that validator to fail immediately
94
+ # @return [Nil]
95
+ def validator(key = nil, opts = {}, &block)
96
+ key = default_validator_name if key.nil?
97
+ validators << build_validator(key, opts, &block)
98
+ nil
99
+ end
100
+
101
+ # load a validator with the given key from the app container.
102
+ #
103
+ # @note the key is encode and will be decoded first
104
+ # @see ValidatorDsl#convert_to_container_key for details
105
+ #
106
+ # @param key [String]
107
+ # @param opts [Hash]
108
+ # @return Appfuel::Validation::Validator
109
+ def load_validator(key, opts = {})
110
+ fail "validator must have a key" if key.nil?
111
+
112
+ container_key = convert_to_container_key(key)
113
+ container = Appfuel.app_container
114
+ unless container.key?(container_key)
115
+ fail "Could not locate validator with (#{container_key})"
116
+ end
117
+
118
+ container[container_key]
119
+ end
120
+
121
+ # return [Bool]
122
+ def validators?
123
+ !validators.empty?
124
+ end
125
+
126
+ # Used when resolving inputs to determine if we should apply any
127
+ # validation
128
+ #
129
+ # return [Bool]
130
+ def skip_validation?
131
+ @skip_validation == true
132
+ end
133
+
134
+ # Dsl method to allow a handler to tell the system not to validate
135
+ # anything and use the raw inputs
136
+ #
137
+ # return [Bool]
138
+ def skip_validation!
139
+ @skip_validation = true
140
+ end
141
+
142
+ # Validate all inputs using the list of validators that were assigned
143
+ # using the dsl methods.
144
+ #
145
+ # @param inputs [Hash]
146
+ # @return Appfuel::Response
147
+ def resolve_inputs(inputs = {})
148
+ return ok(inputs) if skip_validation?
149
+ return ok({}) unless validators?
150
+
151
+ response = nil
152
+ has_failed = false
153
+ validators.each do |validator|
154
+ if validator.pipe?
155
+ result = handle_validator_pipe(validator, inputs)
156
+ inputs = result unless result == false
157
+ next
158
+ end
159
+
160
+ result = validator.call(inputs)
161
+ if result.success? && !has_failed
162
+ response = handle_successful_inputs(result, response)
163
+ next
164
+ end
165
+
166
+ return error(result.errors(full: true)) if validator.fail_fast?
167
+ has_failed = true
168
+ response = handle_error_inputs(result, response)
169
+ end
170
+
171
+ fail "multi validators can not be only Procs" if response.nil?
172
+
173
+ response
174
+ end
175
+
176
+ private
177
+
178
+ # Decodes the given key into a dependency injection namespace that is
179
+ # used to find a validator or pipe in the app container. It decodes
180
+ # to a global or feature namespaces.
181
+ #
182
+ # @param key [String]
183
+ # #return [String]
184
+ def convert_to_container_key(key)
185
+ parts = key.to_s.split('.')
186
+ last = parts.last
187
+ first = parts.first
188
+ case first
189
+ when 'global'
190
+ "global.validators.#{last}"
191
+ when 'global-pipe'
192
+ "global.validator-pipes.#{last}"
193
+ when 'pipe'
194
+ "#{container_feature_key}.validator-pipes.#{last}"
195
+ else
196
+ "#{container_feature_key}.validators.#{first}"
197
+ end
198
+ end
199
+
200
+ # Create a validator for the handler or load it from the container
201
+ # depending on if a block is given
202
+ #
203
+ # @param key [String] key used to identify the item
204
+ # @param opts [Hash]
205
+ # @return [
206
+ # Appfuel::Validation::Validator,
207
+ # Appfuel::Validation::ValidatorPipe
208
+ # ]
209
+ def build_validator(key, opts = {}, &block)
210
+ return load_validator(key, opts) unless block_given?
211
+
212
+ Appfuel::Validation.build_validator(key, opts, &block)
213
+ end
214
+
215
+ # Creates a response the first time otherwise it merges the results
216
+ # from the last validator into the response
217
+ #
218
+ # @param results [Hash] successful valid inputs
219
+ # @param response [Appfuel::Response]
220
+ def handle_successful_inputs(result, response)
221
+ if response.nil?
222
+ ok(result.output)
223
+ else
224
+ ok(response.ok.merge(result.output))
225
+ end
226
+ end
227
+
228
+ # Creates a response the first time otherwise it merges the error
229
+ # results from the last validator into the response
230
+ #
231
+ # @param results [Hash] successful valid inputs
232
+ # @param response [Appfuel::Response]
233
+ def handle_error_inputs(result, response)
234
+ if response.nil?
235
+ error(result.errors(full: true))
236
+ else
237
+ error(result.errors(full: true).merge(response.error_messages))
238
+ end
239
+ end
240
+
241
+ # Delegates call to the validator pipe
242
+ #
243
+ # @param pipe [Appfuel::Validation::ValidatorPipe]
244
+ # @param inputs [Hash]
245
+ # @return [Hash]
246
+ def handle_validator_pipe(pipe, inputs)
247
+ result = pipe.call(inputs, Dry::Container.new)
248
+ return false unless result
249
+ unless result.is_a?(Hash)
250
+ fail "multi validator proc must return a Hash"
251
+ end
252
+ result
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,70 @@
1
+ require_relative 'initialize/initializer'
2
+
3
+ module Appfuel
4
+ module Initialize
5
+ class << self
6
+
7
+ # Dsl used to add an initializer into to the application container. This
8
+ # will add an initializer into the default app unless another name is
9
+ # given.
10
+ #
11
+ # @param name [String] name of the initializer
12
+ # @param envs [String, Symbol, Array] A env,list of envs this can run in
13
+ # @param app_name [String] name of app for this initializer
14
+ def define(namespace_key, name, envs = [], app_name = nil, &block)
15
+ initializers = Appfuel.resolve("#{namespace_key}.initializers", app_name)
16
+ initializers << Initializer.new(name, envs, &block)
17
+ end
18
+
19
+ # Populate configuration definition that is in the container and
20
+ # add its results to the container. It also adds the environment from
21
+ # the config to the container for easier access.
22
+ #
23
+ # @raises RuntimeError when :env is not in the config
24
+ #
25
+ #
26
+ # @param container [Dry::Container]
27
+ # @param params [Hash]
28
+ # @option overrides [Hash] used to override config values
29
+ # @option env [ENV] used to collect environment variables
30
+ # @return [Dry::Container] that was passed in
31
+ def handle_configuration(container, params = {})
32
+ overrides = params[:overrides] || {}
33
+ env = params[:env] || ENV
34
+ definition = container['config_definition']
35
+
36
+ config = definition.populate(env: env, overrides: overrides)
37
+ env = config.fetch(:env) { fail "key (:env) is missing from config" }
38
+
39
+ container.register(:config, config)
40
+ container.register(:env, env)
41
+
42
+ container
43
+ end
44
+
45
+ def handle_repository_mapping(container, params = {})
46
+ initializer = container[:repository_initializer]
47
+ initializer.call(container)
48
+ end
49
+
50
+ # This will initialize the app by handling configuration and running
51
+ # all the initilizers, which will result in an app container that has
52
+ # registered the config, env, and anything else the initializers
53
+ # decide to add.
54
+ #
55
+ # @param params [Hash]
56
+ # @option app_name [String] name of the app to initialize, (optional)
57
+ # @return [Dry::Container]
58
+ def run(params = {})
59
+ app_name = params.fetch(:app_name) { Appfuel.default_app_name }
60
+ container = Appfuel.app_container(app_name)
61
+ handle_configuration(container, params)
62
+ handle_repository_mapping(container, params)
63
+
64
+ Appfuel.run_initializers('global', container, params[:exclude] || [])
65
+
66
+ container
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,68 @@
1
+ module Appfuel
2
+ module Initialize
3
+ # The client application will declare a series of initializer blocks.
4
+ # Each of these blocks are represented as this class. This allows us
5
+ # to save the block to be later executed along with info about which
6
+ # environments this can run on
7
+ class Initializer
8
+ attr_reader :name, :envs, :code
9
+
10
+ # Ensure each environment is stored as a lowercased string, convert
11
+ # the name to a string as save the block to be executed later
12
+ #
13
+ # @raises ArgumentError, when env is not an Array
14
+ # @raises ArgumentError, when block is not given
15
+ #
16
+ # @param name [String, Symbol] name to identify this initializer
17
+ # @param env [String, Symbol, Array] env or list of envs to execute on
18
+ # @param blk [Proc] the code to be excuted
19
+ # @return [Initializer]
20
+ def initialize(name, env = [], &block)
21
+ @name = name.to_s
22
+ @envs = []
23
+
24
+ env = [env] if env.is_a?(String) || env.is_a?(Symbol)
25
+ env = [] if env.nil?
26
+
27
+ unless env.is_a?(Array)
28
+ fail ArgumentError, "environments must be a string, symbol or array"
29
+ end
30
+ env.each {|item| add_env(item) }
31
+
32
+ fail ArgumentError, "initializer requires a block" unless block_given?
33
+ @code = block
34
+ end
35
+
36
+ # Determines which env this is allowed to execute on. No enironment means
37
+ # it it is allow to execute on all
38
+ #
39
+ # @param env [String, Symbol]
40
+ # @return [Bool]
41
+ def env_allowed?(env)
42
+ return true if envs.empty?
43
+
44
+ envs.include?(env.to_s.downcase)
45
+ end
46
+
47
+ # @raises RuntimeError, when env already exists
48
+ #
49
+ # @param name [String, Symbol] name of the environment
50
+ # @return [Array]
51
+ def add_env(name)
52
+ name = name.to_s.downcase
53
+ fail "env already exists" if envs.include?(name)
54
+ envs << name.to_s.downcase
55
+ end
56
+
57
+ # Delegate to executing the stored code
58
+ #
59
+ # @param config [Hash]
60
+ # @param app_container [Dry::Container]
61
+ # @return nil
62
+ def call(config, container)
63
+ code.call(config, container)
64
+ nil
65
+ end
66
+ end
67
+ end
68
+ end