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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +25 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +19 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +9 -0
- data/README.md +38 -0
- data/Rakefile +6 -0
- data/appfuel.gemspec +42 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/lib/appfuel.rb +210 -0
- data/lib/appfuel/application.rb +4 -0
- data/lib/appfuel/application/app_container.rb +223 -0
- data/lib/appfuel/application/container_class_registration.rb +22 -0
- data/lib/appfuel/application/container_key.rb +201 -0
- data/lib/appfuel/application/qualify_container_key.rb +76 -0
- data/lib/appfuel/application/root.rb +140 -0
- data/lib/appfuel/cli_msg_request.rb +19 -0
- data/lib/appfuel/configuration.rb +14 -0
- data/lib/appfuel/configuration/definition_dsl.rb +175 -0
- data/lib/appfuel/configuration/file_loader.rb +61 -0
- data/lib/appfuel/configuration/populate.rb +95 -0
- data/lib/appfuel/configuration/search.rb +45 -0
- data/lib/appfuel/db_model.rb +16 -0
- data/lib/appfuel/domain.rb +7 -0
- data/lib/appfuel/domain/criteria.rb +436 -0
- data/lib/appfuel/domain/domain_name_parser.rb +44 -0
- data/lib/appfuel/domain/dsl.rb +247 -0
- data/lib/appfuel/domain/entity.rb +242 -0
- data/lib/appfuel/domain/entity_collection.rb +87 -0
- data/lib/appfuel/domain/expr.rb +127 -0
- data/lib/appfuel/domain/value_object.rb +7 -0
- data/lib/appfuel/errors.rb +104 -0
- data/lib/appfuel/feature.rb +2 -0
- data/lib/appfuel/feature/action_loader.rb +25 -0
- data/lib/appfuel/feature/initializer.rb +43 -0
- data/lib/appfuel/handler.rb +6 -0
- data/lib/appfuel/handler/action.rb +17 -0
- data/lib/appfuel/handler/base.rb +103 -0
- data/lib/appfuel/handler/command.rb +18 -0
- data/lib/appfuel/handler/inject_dsl.rb +88 -0
- data/lib/appfuel/handler/validator_dsl.rb +256 -0
- data/lib/appfuel/initialize.rb +70 -0
- data/lib/appfuel/initialize/initializer.rb +68 -0
- data/lib/appfuel/msg_request.rb +207 -0
- data/lib/appfuel/predicates.rb +10 -0
- data/lib/appfuel/presenter.rb +18 -0
- data/lib/appfuel/presenter/base.rb +7 -0
- data/lib/appfuel/repository.rb +73 -0
- data/lib/appfuel/repository/base.rb +72 -0
- data/lib/appfuel/repository/initializer.rb +19 -0
- data/lib/appfuel/repository/mapper.rb +203 -0
- data/lib/appfuel/repository/mapping_dsl.rb +210 -0
- data/lib/appfuel/repository/mapping_entry.rb +76 -0
- data/lib/appfuel/repository/mapping_registry.rb +121 -0
- data/lib/appfuel/repository_runner.rb +60 -0
- data/lib/appfuel/request.rb +53 -0
- data/lib/appfuel/response.rb +96 -0
- data/lib/appfuel/response_handler.rb +79 -0
- data/lib/appfuel/root_module.rb +31 -0
- data/lib/appfuel/run_error.rb +9 -0
- data/lib/appfuel/storage.rb +3 -0
- data/lib/appfuel/storage/db.rb +4 -0
- data/lib/appfuel/storage/db/active_record_model.rb +42 -0
- data/lib/appfuel/storage/db/mapper.rb +213 -0
- data/lib/appfuel/storage/db/migration_initializer.rb +42 -0
- data/lib/appfuel/storage/db/migration_runner.rb +15 -0
- data/lib/appfuel/storage/db/migration_tasks.rb +18 -0
- data/lib/appfuel/storage/db/repository.rb +231 -0
- data/lib/appfuel/storage/db/repository_query.rb +13 -0
- data/lib/appfuel/storage/file.rb +1 -0
- data/lib/appfuel/storage/file/base.rb +32 -0
- data/lib/appfuel/storage/memory.rb +2 -0
- data/lib/appfuel/storage/memory/mapper.rb +30 -0
- data/lib/appfuel/storage/memory/repository.rb +37 -0
- data/lib/appfuel/types.rb +53 -0
- data/lib/appfuel/validation.rb +80 -0
- data/lib/appfuel/validation/validator.rb +59 -0
- data/lib/appfuel/validation/validator_pipe.rb +47 -0
- data/lib/appfuel/version.rb +3 -0
- metadata +335 -0
@@ -0,0 +1,210 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Repository
|
3
|
+
# A mapping dsl that allows the collection of database columns to domain
|
4
|
+
# attributes. The reason this dsl is separated from the DbEntityMapper
|
5
|
+
# is due to the fact that we use method_missing for collecting column names
|
6
|
+
# and don't want any incorrect method_missing calls to be confused when
|
7
|
+
# collecting mapped values vs when defining them.
|
8
|
+
class MappingDsl
|
9
|
+
attr_reader :domain_name, :storage, :entries, :entry_class,
|
10
|
+
:container_name
|
11
|
+
|
12
|
+
STORAGE_TYPES = [:db, :file, :memory]
|
13
|
+
|
14
|
+
# 1) mapping 'feature.domain', db: true, do
|
15
|
+
# ...
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# 2) mapping 'feature.domain', db: 'foo.bar', do
|
19
|
+
# ...
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# 3) mapping 'feature.domain', db: 'global.bar' do
|
23
|
+
# ...
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# 4) mapping 'feature.domain', db: 'foo.bar.baz', key_translation: false)
|
27
|
+
# 4) mapping 'feature.domain', storage: [:db, :file] do
|
28
|
+
# ...
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# a file model requires the domain_name it represents.
|
32
|
+
#
|
33
|
+
# case1 - build a model with default settings
|
34
|
+
# file: storage.file.model
|
35
|
+
# path: <root_path>/storage/file/{key}.yaml
|
36
|
+
# adapter: storage.file.model
|
37
|
+
#
|
38
|
+
# case2 - build a model with given settings
|
39
|
+
# note: if path is absolute nothing is done
|
40
|
+
# if path is relative we will prepend root_path
|
41
|
+
# if no yaml extension the key is translated to a path
|
42
|
+
#
|
43
|
+
# path: foo/bar/baz.yml -> <root_path>/foo/bar/baz.yml
|
44
|
+
# path: /foo/bar/baz.yml -> /foo/bar/baz.yml
|
45
|
+
# path auth.user -> <root_path>/storage/features/auth/file/user.yml
|
46
|
+
# path gobal.user -> <root_path/storage/global/auth/file/user.yml
|
47
|
+
#
|
48
|
+
# case3 - build a model with adapter and path
|
49
|
+
# path: sames as above
|
50
|
+
# adapter translates key to location of adapter in storage
|
51
|
+
#
|
52
|
+
# container
|
53
|
+
# default key -> storage.file.model is default
|
54
|
+
# auth.user -> features.auth.storage.file.user
|
55
|
+
#
|
56
|
+
# file 'storage.file.model'
|
57
|
+
#
|
58
|
+
# storage db: 'foo.user_user'
|
59
|
+
# storage :file
|
60
|
+
# storage :memory
|
61
|
+
#
|
62
|
+
# storage db: 'foo.user_ath',
|
63
|
+
# file: 'storage.file.model',
|
64
|
+
# memory: 'storage.memory.model'
|
65
|
+
#
|
66
|
+
def initialize(domain_name, options = {})
|
67
|
+
fail "options must be a hash" unless options.is_a?(Hash)
|
68
|
+
|
69
|
+
@entries = []
|
70
|
+
@domain_name = domain_name.to_s
|
71
|
+
@entry_class = options[:entry_class] || MappingEntry
|
72
|
+
@container_name = options[:container] || Appfuel.default_app_name
|
73
|
+
@storage = initialize_storage(options)
|
74
|
+
|
75
|
+
fail "entity name can not be empty" if @domain_name.empty?
|
76
|
+
end
|
77
|
+
|
78
|
+
def db(key)
|
79
|
+
@storage[:db] = translate_storage_key(key)
|
80
|
+
end
|
81
|
+
|
82
|
+
# 5) mapping 'feature.domain' db: true do
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# 6) mapping 'feature.domain', db: true do
|
86
|
+
# storage :db, 'foo.bar'
|
87
|
+
# storage :file
|
88
|
+
# end
|
89
|
+
# storage(type = nil, options = {})
|
90
|
+
#
|
91
|
+
def storage(type = nil, *args)
|
92
|
+
return @storage if type.nil?
|
93
|
+
unless type.respond_to?(:to_sym)
|
94
|
+
fail "Storage type must implement :to_sym"
|
95
|
+
end
|
96
|
+
type = type.to_sym
|
97
|
+
|
98
|
+
if all_storage_symbols?(*args)
|
99
|
+
args.unshift(type)
|
100
|
+
args.each do |storage_type|
|
101
|
+
@storage[storage_type] = send("initialize_#{storage_type}_storage", true)
|
102
|
+
end
|
103
|
+
|
104
|
+
return self
|
105
|
+
end
|
106
|
+
|
107
|
+
args = [true] if args.empty?
|
108
|
+
|
109
|
+
key = args.shift
|
110
|
+
opts = args.shift
|
111
|
+
data = {type => key}
|
112
|
+
if opts.is_a?(Hash)
|
113
|
+
data.merge!(opts)
|
114
|
+
end
|
115
|
+
|
116
|
+
@storage.merge!(initialize_storage(data))
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
def all_storage_symbols?(*args)
|
121
|
+
result = args - STORAGE_TYPES
|
122
|
+
result.empty?
|
123
|
+
end
|
124
|
+
|
125
|
+
def map(name, domain_attr = nil, opts = {})
|
126
|
+
domain_attr = name if domain_attr.nil?
|
127
|
+
|
128
|
+
data = opts.merge({
|
129
|
+
domain_name: domain_name,
|
130
|
+
domain_attr: domain_attr,
|
131
|
+
storage: storage,
|
132
|
+
storage_attr: name,
|
133
|
+
container: container_name,
|
134
|
+
})
|
135
|
+
|
136
|
+
@entries << entry_class.new(data)
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def initialize_storage(data)
|
142
|
+
storage = {}
|
143
|
+
if data.key?(:db)
|
144
|
+
value = data[:db]
|
145
|
+
storage[:db] = initialize_db_storage(value, data)
|
146
|
+
elsif data.key?(:file)
|
147
|
+
value = data[:file]
|
148
|
+
storage[:file] = initialize_default_storage(value, :file)
|
149
|
+
elsif data.key?(:storage) && data[:storage].is_a?(Array)
|
150
|
+
data[:storage].each do |type|
|
151
|
+
storage[type] = send("initialize_#{type}_storage", true)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
storage
|
155
|
+
end
|
156
|
+
|
157
|
+
def initialize_db_storage(value, opts = {})
|
158
|
+
case
|
159
|
+
when value == true
|
160
|
+
translate_storage_key(:db, domain_name)
|
161
|
+
when opts.is_a?(Hash) && opts[:key_translation] == false
|
162
|
+
value
|
163
|
+
else
|
164
|
+
translate_storage_key(:db, value)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def initialize_file_storage(value, opts = {})
|
169
|
+
key = translate_storage_key(:file, domain_name)
|
170
|
+
case value
|
171
|
+
when true
|
172
|
+
{
|
173
|
+
model: 'storage.file.model',
|
174
|
+
path: "#{storage_path}/#{key.gsub(/\./,'/')}.yml"
|
175
|
+
}
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def storage_path
|
180
|
+
app_container = Appfuel.app_container(container_name)
|
181
|
+
path = app_container[:root_path]
|
182
|
+
if app_container.key?(:storage_path)
|
183
|
+
path = app_container[:storage_path]
|
184
|
+
end
|
185
|
+
path
|
186
|
+
end
|
187
|
+
|
188
|
+
#
|
189
|
+
# global.user
|
190
|
+
# global.storage.db.user
|
191
|
+
# membership.user
|
192
|
+
# features.membership.{type}.user
|
193
|
+
def translate_storage_key(type, partial_key)
|
194
|
+
fail "#{type} can not be empty" if partial_key.empty?
|
195
|
+
|
196
|
+
top, *parts = partial_key.split('.')
|
197
|
+
top = "features.#{top}" unless top == 'global'
|
198
|
+
"#{top}.storage.#{type}.#{parts.join('.')}"
|
199
|
+
end
|
200
|
+
|
201
|
+
def assign_storage(type, partial_key)
|
202
|
+
@storage[type] = translate_storage_key(partial_key)
|
203
|
+
end
|
204
|
+
|
205
|
+
def assign_default_storage(type)
|
206
|
+
@storage[type] = "#{type.to_s.underscore}.model"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Repository
|
3
|
+
class MappingEntry
|
4
|
+
attr_reader :domain_name, :domain_attr, :computed_attr, :storage_attr,
|
5
|
+
:container_name, :container_key
|
6
|
+
|
7
|
+
def initialize(data)
|
8
|
+
unless data.respond_to?(:to_h)
|
9
|
+
fail "Map entry data must respond to :to_h"
|
10
|
+
end
|
11
|
+
|
12
|
+
data = data.to_h
|
13
|
+
@domain_name = data.fetch(:domain_name) {
|
14
|
+
fail "Fully qualified domain name is required"
|
15
|
+
}.to_s
|
16
|
+
|
17
|
+
@storage = data.fetch(:storage) {
|
18
|
+
fail "Storage classes hash is required"
|
19
|
+
}
|
20
|
+
|
21
|
+
@storage_attr = data.fetch(:storage_attr) {
|
22
|
+
fail "Storage attribute is required"
|
23
|
+
}.to_s
|
24
|
+
|
25
|
+
@domain_attr = data.fetch(:domain_attr) {
|
26
|
+
fail "Domain attribute is required"
|
27
|
+
}
|
28
|
+
|
29
|
+
@skip = data[:skip] == true ? true : false
|
30
|
+
|
31
|
+
if data.key?(:computed_attr)
|
32
|
+
computed_attr_lambda(data[:computed_attr])
|
33
|
+
end
|
34
|
+
|
35
|
+
@container_name = data[:container]
|
36
|
+
@container_key = "mappings.#{domain_name}.#{domain_attr}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def storage(type)
|
40
|
+
fail "Storage #{type} is not registered" unless storage?(type)
|
41
|
+
|
42
|
+
@storage[type]
|
43
|
+
end
|
44
|
+
|
45
|
+
def storage?(type)
|
46
|
+
@storage.key?(type)
|
47
|
+
end
|
48
|
+
|
49
|
+
def skip?
|
50
|
+
@skip
|
51
|
+
end
|
52
|
+
|
53
|
+
def computed_attr?
|
54
|
+
!computed_attr.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
def compute_attr(value, domain)
|
58
|
+
fail "No lambda assigned to compute value" unless computed_attr?
|
59
|
+
@computed_attr.call(value, domain)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def computed_attr_lambda(value)
|
64
|
+
unless value.lambda?
|
65
|
+
fail "computed attributes require a lambda as a value"
|
66
|
+
end
|
67
|
+
|
68
|
+
if value.arity != 2
|
69
|
+
fail "computed attribute lambda's must accept 2 param"
|
70
|
+
end
|
71
|
+
|
72
|
+
@computed_attr = value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Repository
|
3
|
+
# The mapping registry holds all entity to db mappings. Mappings are
|
4
|
+
# contained within a DbEntityMapEntry object and are arranged by
|
5
|
+
# entity name. Each entity will hold a hash where the keys are the
|
6
|
+
# attribute names and the value is the entry
|
7
|
+
class MappingRegistry
|
8
|
+
attr_reader :map, :container_root_name
|
9
|
+
|
10
|
+
def initialize(app_name, map)
|
11
|
+
@container_root_name = app_name
|
12
|
+
@map = map
|
13
|
+
end
|
14
|
+
|
15
|
+
# Determine if an entity has been added
|
16
|
+
#
|
17
|
+
# @param entity [String]
|
18
|
+
# @return [Boolean]
|
19
|
+
def domain?(domain_name)
|
20
|
+
map.key?(domain_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Determine if an attribute is mapped for a given entity
|
24
|
+
#
|
25
|
+
# @param entity [String] name of the entity
|
26
|
+
# @param attr [String] name of the attribute
|
27
|
+
# @return [Boolean]
|
28
|
+
def domain_attr?(domain_name, domain_attr)
|
29
|
+
return false unless entity?(domain_name)
|
30
|
+
map[domain_name].key?(domain_attr)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a mapping entry for a given entity
|
34
|
+
#
|
35
|
+
# @raise [RuntimeError] when entity not found
|
36
|
+
# @raise [RuntimeError] when attr not found
|
37
|
+
#
|
38
|
+
# @param entity [String] name of the entity
|
39
|
+
# @param attr [String] name of the attribute
|
40
|
+
# @return [Boolean]
|
41
|
+
def find(domain_name, domain_attr)
|
42
|
+
validate_entity(domain_name)
|
43
|
+
|
44
|
+
unless map[domain_name].key?(domain_attr)
|
45
|
+
fail "Entity (#{domain_name}) attr (#{domain_attr}) not registered"
|
46
|
+
end
|
47
|
+
map[domain_name][domain_attr]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Iterates over all entries for a given entity
|
51
|
+
#
|
52
|
+
# @yield [attr, entry] expose the entity attr name and entry
|
53
|
+
#
|
54
|
+
# @param entity [String] name of the entity
|
55
|
+
# @return [void]
|
56
|
+
def each_domain_attr(domain_name)
|
57
|
+
validate_domain(domain_name)
|
58
|
+
|
59
|
+
map[domain_name].each do |attr, entry|
|
60
|
+
yield attr, entry
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Determine if an column is mapped for a given entity
|
65
|
+
#
|
66
|
+
# @param entity [String] name of the entity
|
67
|
+
# @param attr [String] name of the attribute
|
68
|
+
# @return [Boolean]
|
69
|
+
def persistence_attr_mapped?(domain_name, persistence_attr)
|
70
|
+
result = false
|
71
|
+
each_domain_attr(entity) do |_attr, entry|
|
72
|
+
result = true if persistence_attr == entry.persistence_attr
|
73
|
+
end
|
74
|
+
result
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns a column name for an entity's attribute
|
78
|
+
#
|
79
|
+
# @raise [RuntimeError] when entity not found
|
80
|
+
# @raise [RuntimeError] when attr not found
|
81
|
+
#
|
82
|
+
# @param entity [String] name of the entity
|
83
|
+
# @param attr [String] name of the attribute
|
84
|
+
# @return [String]
|
85
|
+
def persistence_attr(domain_name, attr)
|
86
|
+
find(entity, attr).persistence_attr
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the db model for a given entity attr
|
90
|
+
# container:
|
91
|
+
# domains:
|
92
|
+
# domain_name -> domain
|
93
|
+
# persistence
|
94
|
+
# db:
|
95
|
+
# persistence_name: -> class
|
96
|
+
#
|
97
|
+
# container[persistence.db.name]
|
98
|
+
#
|
99
|
+
# @raise [RuntimeError] when entity not found
|
100
|
+
# @raise [RuntimeError] when attr not found
|
101
|
+
# @raise [Dry::Contriner::Error] when db_class is not registered
|
102
|
+
#
|
103
|
+
# @param entity [String] name of the entity
|
104
|
+
# @param attr [String] name of the attribute
|
105
|
+
# @return [Object]
|
106
|
+
def persistence_class(type, domain_name, attr)
|
107
|
+
entry = find(domain_name, attr)
|
108
|
+
name = entry.persistence[type]
|
109
|
+
key = "persistence.#{type}.#{name}"
|
110
|
+
Appfuel.app_container(root_name)[key]
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
def validate_entity(entity)
|
115
|
+
unless entity?(entity)
|
116
|
+
fail "Entity (#{entity}) is not registered"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Appfuel
|
2
|
+
# Used in the validation system by custom predicates to ask the question if
|
3
|
+
# an entity exists in the database
|
4
|
+
class RepositoryRunner
|
5
|
+
attr_reader :repo_namespace, :criteria_class
|
6
|
+
|
7
|
+
# The call relies on the fact that we can build a criteria find the
|
8
|
+
# correct repo and call the exists? interface on that repo. The identity
|
9
|
+
# of any given repo requires its namespace + its class name.
|
10
|
+
#
|
11
|
+
# @param namespace [String] fully qualified namespace string fro repos
|
12
|
+
# @param criteria_class [Class] class used to represent the criteria
|
13
|
+
# @returns [ExistsInDbRunner]
|
14
|
+
def initialize(namespace, criteria_class)
|
15
|
+
@repo_namespace = namespace
|
16
|
+
@criteria_class = criteria_class
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_criteria(entity_key, opts = {})
|
20
|
+
criteria_class.new(entity_key, opts)
|
21
|
+
end
|
22
|
+
|
23
|
+
def query(criteria)
|
24
|
+
load_repo(criteria).query(criteria)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param entity_key [String] the type identifier for an entity
|
28
|
+
# @param opts [Hash] one attr => value pair is required
|
29
|
+
# repo => name is optional
|
30
|
+
#
|
31
|
+
# @return [Bool]
|
32
|
+
def exists?(entity_key, opts = {})
|
33
|
+
fail "opts must be a hash" unless opts.is_a?(Hash)
|
34
|
+
|
35
|
+
criteria_opts = {}
|
36
|
+
if opts.key?(:repo)
|
37
|
+
criteria_opts[:repo] = opts.delete(:repo)
|
38
|
+
end
|
39
|
+
fail "opts hash must have one attr => value pair" if opts.empty?
|
40
|
+
|
41
|
+
property, value = opts.first
|
42
|
+
criteria = create_criteria(entity_key, criteria_opts)
|
43
|
+
criteria.exists(property, value)
|
44
|
+
|
45
|
+
load_repo(criteria).exists?(criteria)
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def load_repo(criteria)
|
52
|
+
klass = "#{repo_namespace}::#{criteria.repo_name}"
|
53
|
+
unless Kernel.const_defined?(klass)
|
54
|
+
fail "RepositoryRunner: failed - repo #{klass} not defined"
|
55
|
+
end
|
56
|
+
|
57
|
+
Kernel.const_get(klass).new
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|