appfuel 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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