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,44 @@
1
+ module Appfuel
2
+ module Domain
3
+
4
+ module DomainNameParser
5
+
6
+ # This parse the domain name string or object with domain_name method
7
+ # and returns an array with feature, domain and name.
8
+ # @example
9
+ # parse_domain_name('Foo.Bar.Baz')
10
+ # ['Foo', 'Bar', 'Foo.Bar.Baz']
11
+ # @param name [String, Object] domain name
12
+ # @return [Array] parsed Array from domain name
13
+ def parse_domain_name(name)
14
+ if !name.is_a?(String) && !name.respond_to?(:domain_name)
15
+ fail 'domain name must be a string or implement method :domain_name'
16
+ end
17
+
18
+ name = name.domain_name if name.respond_to?(:domain_name)
19
+ feature, domain = name.split('.')
20
+ if domain.nil?
21
+ domain, feature = feature, nil
22
+ end
23
+
24
+ [feature, domain, name]
25
+ end
26
+
27
+ # This parse the domain attributes string and returns an array with
28
+ # two elements.
29
+ # @example
30
+ # parse_domain_attr('Foo.Bar.Baz')
31
+ # ['Foo.Bar', 'Baz']
32
+ # @param name [String] domain name attributes
33
+ # @return [Array] parsed Array from domain attributes
34
+ def parse_domain_attr(name)
35
+ unless name.is_a?(String)
36
+ fail 'domain attribute name must be a string'
37
+ end
38
+
39
+ *first, last = name.split('.')
40
+ [first.join('.'), last]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,247 @@
1
+ module Appfuel
2
+ # Mixin used to add attributes to an object. Attributes are given an name
3
+ # and have a type declaration which is used to ensure it is correct. Types
4
+ # are handled by Dry::Types which are mixed into the Types module. When an
5
+ # object is instaniated dymamic getters and setters are created depending
6
+ # on if the a value_object flag has been toggled
7
+ #
8
+ # @example
9
+ # attribute :foo, 'strict.string', default: 'bar', min_size: 3
10
+ #
11
+ module Domain
12
+ module Dsl
13
+ # Class macro dsl used to implement attributes
14
+ attr_accessor :equalizer
15
+ attr_reader :schema, :defaults
16
+ protected :equalizer=
17
+
18
+ def self.extended(base)
19
+ base.instance_variable_set(:@schema, {})
20
+ base.instance_variable_set(:@value_object, false)
21
+ end
22
+
23
+ def inherited(klass)
24
+ super
25
+ klass.instance_variable_set(:@value_object, false)
26
+ klass.instance_variable_set(:@schema, {})
27
+ klass.equalizer = Dry::Equalizer.new(*schema.keys)
28
+ klass.send(:include, klass.equalizer)
29
+
30
+ register_container_class(klass)
31
+ Types.register_domain(klass)
32
+ end
33
+
34
+ def default?
35
+ false
36
+ end
37
+
38
+ def valid?(value)
39
+ self === value
40
+ end
41
+
42
+ def value_object?
43
+ @value_object
44
+ end
45
+
46
+ def enable_value_object
47
+ @value_object = true
48
+ end
49
+
50
+ def disable_value_object
51
+ @value_object = false
52
+ end
53
+
54
+ def type(str)
55
+ Types[str]
56
+ end
57
+
58
+ def strict_enum(*args)
59
+ type('strict.string').enum(*args)
60
+ end
61
+
62
+ def enum(*args)
63
+ type('coercible.string').enum(*args)
64
+ end
65
+
66
+ def attribute_names
67
+ schema.keys
68
+ end
69
+
70
+ def attribute(name, type_str, **options)
71
+ unless type_str.is_a?(String)
72
+ return handle_manual_type(name, type_str, options)
73
+ end
74
+
75
+ name = name.to_sym
76
+ type = build_type(type_str, options)
77
+ schema[name] = type unless attribute_exists?(name, type)
78
+ nil
79
+ end
80
+
81
+ def build_type(type_str, **options)
82
+ base = type_str.split('.').last
83
+ type = Types[type_str]
84
+ type = apply_defaults(type, options)
85
+ type = apply_optional(type, options)
86
+
87
+ nil_is_allowed = allow_nil?(options)
88
+
89
+ type = case base
90
+ when 'hash' then handle_hash(type, options)
91
+ when 'array' then handle_array(type, options)
92
+ else
93
+ type
94
+ end
95
+
96
+ type = apply_constraints(type, options)
97
+
98
+ # You have to apply all the contraints before summing nil
99
+ type = sum_nil(type) if nil_is_allowed
100
+
101
+ type
102
+ end
103
+
104
+ def attribute_exists?(name, type)
105
+ schema[name.to_sym] === type
106
+ end
107
+
108
+ def attribute_conflict?(name, type)
109
+ name = name.to_sym
110
+ schema.key?(name) && schema[name] != type
111
+ end
112
+
113
+ def create(inputs = {})
114
+ self.new(inputs)
115
+ end
116
+
117
+ alias_method :call, :create
118
+ alias_method :[], :create
119
+
120
+ def try(input)
121
+ Dry::Types::Result::Success.new(self[input])
122
+ rescue => e
123
+ failure = Dry::Types::Result::Failure.new(input, e.message)
124
+ block_given? ? yield(failure) : failure
125
+ end
126
+
127
+ def domain_name
128
+ @domain_name ||= build_domain_name
129
+ end
130
+
131
+ def domain_basename
132
+ domain_name.split('.').last
133
+ end
134
+
135
+ def empty_hash(undefined_as_nil = false)
136
+ data = {}
137
+ value = undefined_as_nil == true ? nil : Types::Undefined
138
+ schema.keys.each do |key|
139
+ data[key] = value
140
+ end
141
+ data
142
+ end
143
+
144
+ private
145
+
146
+ def parse_class_name
147
+ return ["anonmous_#{generate_code(6)}"] if name.nil?
148
+ name.underscore.split('/')
149
+ end
150
+
151
+ def build_domain_name
152
+ "#{container_feature_name}.#{container_key_basename}"
153
+ end
154
+
155
+ def generate_code(nbr)
156
+ charset = Array('A' .. 'Z') + Array('a' .. 'z')
157
+ Array.new(nbr) { charset.sample }.join
158
+ end
159
+
160
+ def handle_manual_type(name, type, options)
161
+ name = name.to_sym
162
+ type = apply_defaults(type, options)
163
+ if attribute_conflict?(name, type)
164
+ fail RuntimeError, "Attribute :#{name} has already been defined " +
165
+ "as another type"
166
+ end
167
+
168
+ schema[name] = type unless attribute_exists?(name, type)
169
+ end
170
+
171
+ def handle_hash(type, options)
172
+ return type unless options.key?(:hash)
173
+ constructor = options.fetch(:constructor) { :schema }
174
+ options.delete(:constructor)
175
+ valid = [
176
+ :schema,
177
+ :weak,
178
+ :permissive,
179
+ :strict,
180
+ :strict_with_defaults,
181
+ :symbolized
182
+ ]
183
+
184
+ unless valid.include?(constructor)
185
+ fail "the :constructor of the hash must be one of the " +
186
+ "following (#{valid.join(' ')})"
187
+ end
188
+
189
+ unless options[:hash].is_a?(Hash)
190
+ fail ":hash params must be a hash"
191
+ end
192
+
193
+ if options[:hash].empty?
194
+ fail ":hash params that are empty don't make sense you probably " +
195
+ "want to exclude the params and use the constructor alone"
196
+ end
197
+
198
+ hash = options.delete(:hash)
199
+ params = {}
200
+ hash.each do |key, value|
201
+ params[key] = value.is_a?(String) ? Types[value] : value
202
+ end
203
+ type.send(constructor, params)
204
+ end
205
+
206
+ def handle_array(type, options)
207
+ if options.key?(:member)
208
+ member = options.delete(:member)
209
+ member = member.is_a?(String) ? Types[member] : member
210
+ type = type.member(member)
211
+ end
212
+ type
213
+ end
214
+
215
+ def apply_defaults(type, options)
216
+ return type unless options.key?(:default)
217
+
218
+ type.default(options.delete(:default))
219
+ end
220
+
221
+ def apply_optional(type, options)
222
+ return type unless options.key?(:optional)
223
+
224
+ options.delete(:optional)
225
+ type.optional
226
+ end
227
+
228
+ def apply_constraints(type, options)
229
+ return type if options.empty?
230
+ type.constrained(options)
231
+ end
232
+
233
+ def allow_nil?(options)
234
+ result = false
235
+ if options.key?(:allow_nil)
236
+ options.delete(:allow_nil)
237
+ result = true
238
+ end
239
+ result
240
+ end
241
+
242
+ def sum_nil(type)
243
+ type | Types['strict.nil']
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,242 @@
1
+ module Appfuel
2
+ # Mixin used to add attributes to an object. Attributes are given an name
3
+ # and have a type declaration which is used to ensure it is correct. Types
4
+ # are handled by Dry::Types which are mixed into the Types module. When an
5
+ # object is instaniated dymamic getters and setters are created depending
6
+ # on if the a value_object flag has been toggled
7
+ #
8
+ # @example
9
+ # attribute :foo, 'strict.string', default: 'bar', min_size: 3
10
+ #
11
+ module Domain
12
+ class Entity
13
+ extend Appfuel::Application::ContainerKey
14
+ extend Appfuel::Application::ContainerClassRegistration
15
+ extend Dsl
16
+
17
+ def initialize(inputs = {})
18
+ setup_attributes(inputs)
19
+ enable_undefined
20
+ @is_global = domain_name.count('.') == 0
21
+
22
+ rescue Dry::Types::ConstraintError => e
23
+ msg = "#{self.class.name} could not initialize: #{e.message}"
24
+ error = RuntimeError.new(msg)
25
+ error.set_backtrace(e.backtrace)
26
+ raise error
27
+ end
28
+
29
+ def global?
30
+ @is_global
31
+ end
32
+
33
+ def collection?
34
+ false
35
+ end
36
+
37
+ def hide_undefined?
38
+ @hide_undefined
39
+ end
40
+
41
+ def hide_undefined
42
+ @hide_undefined = true
43
+ end
44
+
45
+ def show_undefined
46
+ @hide_undefined = false
47
+ end
48
+
49
+ def enable_undefined
50
+ show_undefined
51
+ @undefined_as_nil = false
52
+ each_entity do |entity|
53
+ entity.enable_undefined
54
+ end
55
+ end
56
+
57
+ def undefined_as_nil
58
+ @undefined_as_nil = true
59
+ each_entity do |entity|
60
+ entity.undefined_as_nil
61
+ end
62
+ end
63
+
64
+ def undefined_as_nil?
65
+ @undefined_as_nil
66
+ end
67
+
68
+ def attr_typed!(name, value)
69
+ self.class.schema[name][value]
70
+ end
71
+
72
+ def data_typed!(type_name, value)
73
+ data_type(type_name)[value]
74
+ end
75
+
76
+ def data_type(type_name)
77
+ self.class.type(type_name)
78
+ end
79
+
80
+ def validate_type!(value, type_str, **options)
81
+ type = self.class.build_type(type_str, **options)
82
+ type[value]
83
+ end
84
+
85
+ def domain_name
86
+ self.class.domain_name
87
+ end
88
+
89
+ def domain_basename
90
+ self.class.domain_basename
91
+ end
92
+
93
+ def to_hash
94
+ data = {}
95
+ self.class.schema.each do |key, type|
96
+ fail "no getter defined for #{key}" unless respond_to?(key)
97
+ value = send(key)
98
+ case
99
+ when value.is_a?(Array)
100
+ list = []
101
+ value.each do |item|
102
+ list << (item.is_a?(Entity) ? item.to_hash : item)
103
+ end
104
+ value = list
105
+ when value.is_a?(Hash)
106
+ dict = {}
107
+ value.each do |value_key, item|
108
+ dict[value_key] = item.is_a?(Entity) ? item.to_hash : item
109
+ end
110
+ value = dict
111
+ when value.respond_to?(:to_hash)
112
+ value = value.to_hash
113
+ when value == Types::Undefined
114
+ if type.respond_to?(:domain_name)
115
+ value = type.empty_hash(undefined_as_nil? ? true : false)
116
+ end
117
+ next if hide_undefined?
118
+
119
+ end
120
+ data[key] = value
121
+ end
122
+ data.deep_symbolize_keys
123
+ end
124
+
125
+ def to_h
126
+ to_hash
127
+ end
128
+
129
+ def has?(key)
130
+ value = send(key)
131
+ !value.nil? && value != Types::Undefined
132
+ end
133
+
134
+ private
135
+
136
+ def each_attr_schema
137
+ self.class.schema.each do |key, type|
138
+ yield key, type
139
+ end
140
+ end
141
+
142
+ def each_entity
143
+ each_attr_schema do |key, type|
144
+ if type.respond_to?(:<) && type < Dsl && has?(key)
145
+ yield send(key)
146
+ end
147
+ end
148
+ end
149
+
150
+ def each_attr
151
+ each_attr_schema do |key, type|
152
+ yield key, send(key)
153
+ end
154
+ end
155
+
156
+ def value_object?
157
+ self.class.value_object?
158
+ end
159
+
160
+ def setup_attributes(inputs = {})
161
+ inputs = {} if inputs.nil?
162
+ inputs = inputs.to_h if inputs == self
163
+
164
+ unless inputs.is_a?(Hash)
165
+ fail "Can not create #{self} entity inputs must be a Hash"
166
+ end
167
+
168
+ inputs.deep_symbolize_keys!
169
+ self.class.schema.each do |key, type|
170
+ value = Types::Undefined
171
+ value = inputs[key] if inputs.key?(key)
172
+ if value_object?
173
+ setup_value_object(key, type, value)
174
+ next
175
+ end
176
+ setup_entity(key, type, value)
177
+ end
178
+ end
179
+
180
+ def setup_value_object(key, type, input)
181
+ define_getter(key)
182
+ initialize_value(key, type, input)
183
+ freeze_instance_var(key)
184
+ end
185
+
186
+ def setup_entity(key, type, input)
187
+ define_getter(key)
188
+ initialize_value(key, type, input)
189
+ define_setter(key, type)
190
+ end
191
+
192
+ def initialize_value(key, type, input)
193
+ if input == Types::Undefined && type.default?
194
+ input = type[nil]
195
+ end
196
+
197
+ # manual overrides have to manually type check themselves
198
+ setter = "#{key}="
199
+ return send(setter, input) if respond_to?(setter)
200
+
201
+ if input != Types::Undefined && input != nil
202
+ input = type[input]
203
+ end
204
+
205
+ instance_variable_set("@#{key}", input)
206
+ rescue => e
207
+ msg = "#{domain_name} could not assign :#{key} #{e.message}"
208
+ error = RuntimeError.new(msg)
209
+ error.set_backtrace(e.backtrace)
210
+ raise error
211
+ end
212
+
213
+ def define_getter(key)
214
+ return if respond_to?(key)
215
+ define_singleton_method(key) do
216
+ value = instance_variable_get("@#{key}")
217
+ value = nil if value == Types::Undefined && undefined_as_nil?
218
+ value
219
+ end
220
+ end
221
+
222
+ def define_setter(key, type)
223
+ setter = "#{key}="
224
+ return if respond_to?(setter)
225
+
226
+ define_singleton_method(setter) do |input|
227
+ value = is_entity?(input, type) ? input : type[input]
228
+ instance_variable_set("@#{key}", value)
229
+ end
230
+ end
231
+
232
+ def freeze_instance_var(key)
233
+ instance_variable_get("@#{key}").freeze
234
+ end
235
+
236
+
237
+ def is_entity?(value, type)
238
+ value.respond_to?(:domain_name) && value.domain_name == type.domain_name
239
+ end
240
+ end
241
+ end
242
+ end