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