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,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