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,22 @@
1
+ module Appfuel
2
+ module Application
3
+ module ContainerClassRegistration
4
+ # All handlers are automatically registered into the application
5
+ # container which allows them to easily be retrieved for execution.
6
+ # The ContainerKey mixin handles converting ruby class namespaces to
7
+ # container key, so we simply need to obtain the qualified namespace
8
+ # key for this class extending this, that does not belong to appfuel.
9
+ #
10
+ # @param klass [Class] the handler class that is inheriting this
11
+ # @return [Boolean]
12
+ def register_container_class(klass)
13
+ root = klass.container_root_name
14
+ return false if root == 'appfuel'
15
+
16
+ container = Appfuel.app_container(root)
17
+ container.register(klass.container_qualified_key, klass)
18
+ true
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,201 @@
1
+ module Appfuel
2
+ module Application
3
+ # Mixins to allow you to handle application container keys. The application
4
+ # container operates based on certain conventions which we take into account
5
+ # here.
6
+ module ContainerKey
7
+ # Parse the namespace assuming it is a ruby namespace and assign
8
+ # the list to container_path_list
9
+ #
10
+ # @param namespace [String]
11
+ # @return [Array]
12
+ def load_path_from_ruby_namespace(namespace)
13
+ self.container_path = parse_list_string(namespace, '::')
14
+ end
15
+
16
+ # Parse the namespace assuming it is a dry container namespace and
17
+ # assign the list to container_path_list
18
+ #
19
+ # @param namespace [String]
20
+ # @return [Array]
21
+ def load_path_from_container_namespace(namespace)
22
+ self.container_path = parse_list_string(namespace, '.')
23
+ end
24
+
25
+ # @param namespace [String] encoded string that represents a path
26
+ # @param char [String] character used to split the keys into a list
27
+ # @return [Array] an array of lower case snake cased strings
28
+ def parse_list_string(namespace, char)
29
+ fail "split char must be '.' or '::'" unless ['.', '::'].include?(char)
30
+ namespace.to_s.split(char).map {|i| i.underscore }
31
+ end
32
+
33
+ # return [Boolean]
34
+ def container_path?
35
+ !@container_path.nil?
36
+ end
37
+
38
+ # @param list [Array] list of container namespace parts including root
39
+ # @return [Array]
40
+ def container_path=(list)
41
+ fail "container path list must be an array" unless list.is_a?(Array)
42
+ @container_path = list
43
+ @container_path.freeze
44
+ end
45
+
46
+ # An array representation of the application container namespace, where
47
+ # the root is the name of the application and not part of the namespace
48
+ # and the rest is hierarchical path to features or globals
49
+ #
50
+ # @return [Array]
51
+ def container_path
52
+ load_path_from_ruby_namespace(self.to_s) unless container_path?
53
+ @container_path
54
+ end
55
+
56
+ # This is the application name used to identify the application container
57
+ # that is stored in the framework container
58
+ #
59
+ # @return string
60
+ def container_root_name
61
+ @container_root_name ||= container_path.first
62
+ end
63
+
64
+ # All root namespace for anything inside features, use this name. It is
65
+ # important to note that to avoid long namespace in ruby features are the
66
+ # name of the module directly below the root.
67
+ #
68
+ # @return [String]
69
+ def container_features_root_name
70
+ @container_features_root_name ||= 'features'
71
+ end
72
+
73
+ # The actual name of the feature
74
+ #
75
+ # @return [String]
76
+ def container_feature_name
77
+ container_path[1]
78
+ end
79
+
80
+ # The feature name is the second item in the path, that is always prexfix
81
+ # with "features"
82
+ #
83
+ # @return [String]
84
+ def container_feature_key
85
+ @container_feature_key ||= (
86
+ "#{container_features_root_name}.#{container_feature_name}"
87
+ )
88
+ end
89
+
90
+ # Container key relative from feature or global, depending on which class
91
+ # this is mixed into
92
+ #
93
+ # @return [String]
94
+ def container_relative_key
95
+ @container_relative_key ||= container_path[2..-1].join('.')
96
+ end
97
+
98
+ # This refers to either the global path or the path to a particular
99
+ # feature
100
+ #
101
+ # @return [String]
102
+ def top_container_key
103
+ container_global_path? ? container_global_name : container_feature_key
104
+ end
105
+
106
+ def container_key_basename
107
+ @container_path.last
108
+ end
109
+
110
+ # Fully qualified key, meaning you can access the class this was mixed into
111
+ # if you stored it into the container using this key
112
+ #
113
+ # @return [String]
114
+ def container_qualified_key
115
+ @container_qualified_key ||= (
116
+ "#{top_container_key}.#{container_relative_key}"
117
+ )
118
+ end
119
+
120
+ # Determines if the container path represents a global glass
121
+ #
122
+ # @return [Boolean]
123
+ def container_global_path?
124
+ container_path[1] == container_global_name
125
+ end
126
+
127
+ # @return [String]
128
+ def container_global_name
129
+ @container_global_name ||= 'global'
130
+ end
131
+
132
+ # Convert the injection key to a fully qualified namespaced key that
133
+ # is used to pull an item out of the app container.
134
+ #
135
+ # Rules:
136
+ # 1) split the injection key by '.'
137
+ # 2) use the feature_key as the initial namespace
138
+ # 3) when the first part of the key is "global" use that instead of
139
+ # the feature_key
140
+ # 4) append the type_key to the namespace unless it is "container"
141
+ # type_key like "repositories" or "commands" removes the need for
142
+ # the user to have to specify it since they already did that when
143
+ # they used the type param.
144
+ #
145
+ #
146
+ # note: feature_key in these examples will be "features.my-feature"
147
+ # @example of a feature repository named foo
148
+ #
149
+ # convert_to_qualified_container_key('foo', 'repositories')
150
+ #
151
+ # returns 'features.my-feature.repositories.foo'
152
+ #
153
+ # @example of a global command names bar
154
+ #
155
+ # convert_to_qualified_container_key('global.bar', 'commands')
156
+ #
157
+ # returns 'gloval.commands.bar'
158
+ #
159
+ # @example of a container item baz
160
+ # NOTE: feature container items are not in any namespace, they are any item
161
+ # that can resolve from the namespace given by "feature_key"
162
+ #
163
+ # convert_to_qualified_container_key('baz', 'container')
164
+ #
165
+ # returns 'features.my-feature.baz'
166
+ #
167
+ # @example of a global container item baz
168
+ # NOTE: global container items are not in any namespace, they are any item
169
+ # you can resolve from the application container.
170
+ #
171
+ # convert_to_qualified_container_key('global.baz', 'container')
172
+ #
173
+ # returns 'baz'
174
+ #
175
+ # @param key [String] partial key to be built into fully qualified key
176
+ # @param type_ns [String] namespace for key
177
+ # @return [String] fully qualified namespaced key
178
+ def qualify_container_key(key, type_ns)
179
+ parts = key.to_s.split('.')
180
+ namespace = "#{container_feature_key}."
181
+ if parts[0].downcase == 'global'
182
+ namespace = 'global.'
183
+ parts.shift
184
+ elsif parts[0] == container_feature_name
185
+ parts.shift
186
+ end
187
+
188
+ # when the key is a global container the namespace is only the path
189
+ if type_ns == "container"
190
+ namespace = '' if namespace == 'global.'
191
+ type_ns = ''
192
+ else
193
+ type_ns = "#{type_ns}."
194
+ end
195
+
196
+ path = parts.join('.')
197
+ "#{namespace}#{type_ns}#{path}"
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,76 @@
1
+ module Appfuel
2
+ module Application
3
+ # Mixins to allow you to handle application container keys. The application
4
+ # container operates based on certain conventions which we take into account
5
+ # here.
6
+ module QualifyContainerKey
7
+ # Convert the injection key to a fully qualified namespaced key that
8
+ # is used to pull an item out of the app container.
9
+ #
10
+ # Rules:
11
+ # 1) split the injection key by '.'
12
+ # 2) use the feature_key as the initial namespace
13
+ # 3) when the first part of the key is "global" use that instead of
14
+ # the feature_key
15
+ # 4) append the type_key to the namespace unless it is "container"
16
+ # type_key like "repositories" or "commands" removes the need for
17
+ # the user to have to specify it since they already did that when
18
+ # they used the type param.
19
+ #
20
+ #
21
+ # note: feature_key in these examples will be "features.my-feature"
22
+ # @example of a feature repository named foo
23
+ #
24
+ # convert_to_qualified_container_key('foo', 'repositories')
25
+ #
26
+ # returns 'features.my-feature.repositories.foo'
27
+ #
28
+ # @example of a global command names bar
29
+ #
30
+ # convert_to_qualified_container_key('global.bar', 'commands')
31
+ #
32
+ # returns 'gloval.commands.bar'
33
+ #
34
+ # @example of a container item baz
35
+ # NOTE: feature container items are not in any namespace, they are any item
36
+ # that can resolve from the namespace given by "feature_key"
37
+ #
38
+ # convert_to_qualified_container_key('baz', 'container')
39
+ #
40
+ # returns 'features.my-feature.baz'
41
+ #
42
+ # @example of a global container item baz
43
+ # NOTE: global container items are not in any namespace, they are any item
44
+ # you can resolve from the application container.
45
+ #
46
+ # convert_to_qualified_container_key('global.baz', 'container')
47
+ #
48
+ # returns 'baz'
49
+ #
50
+ # @param key [String] partial key to be built into fully qualified key
51
+ # @param type_ns [String] namespace for key
52
+ # @return [String] fully qualified namespaced key
53
+ def qualify_container_key(key, type_ns)
54
+ parts = key.to_s.split('.')
55
+ namespace = "#{container_feature_key}."
56
+ if parts[0].downcase == 'global'
57
+ namespace = 'global.'
58
+ parts.shift
59
+ elsif parts[0] == container_feature_name
60
+ parts.shift
61
+ end
62
+
63
+ # when the key is a global container the namespace is only the path
64
+ if type_ns == "container"
65
+ namespace = '' if namespace == 'global.'
66
+ type_ns = ''
67
+ else
68
+ type_ns = "#{type_ns}."
69
+ end
70
+
71
+ path = parts.join('.')
72
+ "#{namespace}#{type_ns}#{path}"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,140 @@
1
+ module Appfuel
2
+ module Application
3
+ module Root
4
+ # Initialize Appfuel by creating an application container for the
5
+ # app represented by the root module passed in. The app container is
6
+ # a dependency injection container that is used thought the app.
7
+ #
8
+ # @raises ArgumentError when root module does not exist
9
+ #
10
+ # @param params [Hash]
11
+ # @option root [Module] root module of the application
12
+ # @option app_name [String, Symbol] key to store container in appfuel
13
+ #
14
+ # @return [Dry::Container]
15
+ def setup_appfuel(params = {})
16
+ app_container = params[:app_container] || Dry::Container.new
17
+ framework_container = Appfuel.framework_container
18
+
19
+ app_container = build_app_container(params, app_container)
20
+ app_name = handle_app_name(params, app_container, framework_container)
21
+
22
+ app_container.register(:app_name, app_name)
23
+ framework_container.register(app_name, app_container)
24
+
25
+ if params.key?(:on_after_setup)
26
+ handle_after_setup(params[:on_after_setup], app_container)
27
+ end
28
+
29
+ app_container
30
+ end
31
+
32
+ def handle_after_setup(hook, container)
33
+ unless hook.respond_to?(:call)
34
+ fail ArgumentError, "After setup hook (:after_setup) must " +
35
+ "implement :call, which takes the di container as its only arg"
36
+ end
37
+ hook.call(container)
38
+ end
39
+
40
+ # The application name is determined by the root module. We use the
41
+ # lower case underscored version of the root module name
42
+ #
43
+ # @param root [Module] The root module of the application
44
+ # @param params [Hash] input params from setup
45
+ # @option app_name [String] optional
46
+ # @option default_app [Bool] force the assignment of default name
47
+ #
48
+ # @return [String]
49
+ def handle_app_name(params, app_container, framework_container)
50
+ app_name = app_container[:root].to_s.underscore
51
+
52
+ if params[:default_app] == true || !Appfuel.default_app?
53
+ framework_container.register(:default_app_name, app_name)
54
+ end
55
+
56
+ app_name
57
+ end
58
+
59
+ # Initializes the application container with:
60
+ #
61
+ # Application Container
62
+ # root: This is the root module that holds the namespaces for all
63
+ # features, actions, commands etc. This is required.
64
+ #
65
+ # root_path: This is the root path of app where the source code
66
+ # lives. We use this to autoload this features. This
67
+ # is still under design so it might not stay.
68
+ #
69
+ # config_definition: This is the definition object that we use to
70
+ # build out the configuration hash. This is optional
71
+ #
72
+ # initializers: This is an array that hold all the initializers to be
73
+ # run. This builder will handle creating the array. It is
74
+ # populated via appfuel dsl Appfuel::Initialize#define
75
+ #
76
+ # global.validators: This is a hash that holds all global validators.
77
+ # this builder will handle creating the hash. It is
78
+ # populated via appfuel dsl
79
+ # Appfuel::Validator#global_validator
80
+ #
81
+ # global.domain_builders:
82
+ # global.presenters
83
+ #
84
+ # @param root [Module] the root module of the application
85
+ # @param container [Dry::Container] dependency injection container
86
+ # @return [Dry::Container]
87
+ def build_app_container(params, container = Dry::Container.new)
88
+ root = params.fetch(:root) {
89
+ fail ArgumentError, "Root module (:root) is required"
90
+ }
91
+
92
+ root_path = params.fetch(:root_path) {
93
+ fail ArgumentError, "Root path (:root_path) is required"
94
+ }
95
+
96
+ feature_initializer = params.fetch(:feature_initializer) {
97
+ Feature::Initializer.new
98
+ }
99
+
100
+ action_loader = params.fetch(:action_loader) {
101
+ Feature::ActionLoader.new
102
+ }
103
+
104
+ repo_initializer = params.fetch(:repository_initializer) {
105
+ Repository::Initializer.new
106
+ }
107
+
108
+ root_name = root.to_s.underscore
109
+ container.register(:root, root)
110
+ container.register(:root_name, root_name)
111
+ container.register(:root_path, root_path)
112
+ container.register(:repository_mappings, {})
113
+ container.register(:repository_initializer, repo_initializer)
114
+ container.register(:features_path, "#{root_name}/features")
115
+ container.register(:feature_initializer, feature_initializer)
116
+ container.register(:action_loader, action_loader)
117
+ if params.key?(:config_definition)
118
+ container.register(:config_definition, params[:config_definition])
119
+ end
120
+
121
+ Appfuel.setup_container_dependencies('global', container)
122
+
123
+ container
124
+ end
125
+
126
+ def bootstrap(overrides: {}, env: ENV)
127
+ Initialize.run(overrides: overrides, env: env)
128
+ end
129
+
130
+ def call(route, inputs = {})
131
+ container = Appfuel.app_container
132
+ request = Request.new(route, inputs)
133
+
134
+ container[:feature_initializer].call(request.feature, container)
135
+ action = container[:action_loader].call(request.namespace, container)
136
+ action.run(inputs)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,19 @@
1
+ module Appfuel
2
+ # This represents the message delivered by RabbitMQ. We encapsulate it
3
+ # so that if you want to fire an action from the command line you can
4
+ # use a CliRequest and not worry about rabbit details
5
+ #
6
+ class CliMsgRequest < MsgRequest
7
+
8
+ def initialize (route, input)
9
+ self.inputs = input
10
+ self.service_route = route
11
+ end
12
+
13
+ private
14
+ def inputs=(data)
15
+ fail "input must be a hash" unless data.is_a?(Hash)
16
+ @inputs = data
17
+ end
18
+ end
19
+ end