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,42 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
module Appfuel
|
4
|
+
module Db
|
5
|
+
module MigrationsInitializer
|
6
|
+
|
7
|
+
def self.call(settings = {})
|
8
|
+
root_name = settings[:root_name] || Appfuel.default_app_name
|
9
|
+
container = Appfuel.app_container(root_name)
|
10
|
+
|
11
|
+
config = container[:config]
|
12
|
+
root_path = container[:root_path]
|
13
|
+
env = container[:env]
|
14
|
+
db_path = config[:db][:path]
|
15
|
+
migrations_paths = config[:db][:migrations_path]
|
16
|
+
db_config = config[:db][:main]
|
17
|
+
|
18
|
+
db_tasks = settings.fetch(:db_tasks) {
|
19
|
+
ActiveRecord::Tasks::DatabaseTasks
|
20
|
+
}
|
21
|
+
|
22
|
+
db_migrator = settings.fetch(:db_migrator) {
|
23
|
+
ActiveRecord::Migrator
|
24
|
+
}
|
25
|
+
|
26
|
+
active_record_base = settings.fetch(:active_record_base) {
|
27
|
+
ActiveRecord::Base
|
28
|
+
}
|
29
|
+
|
30
|
+
active_record_base.configurations = {env => db_config}
|
31
|
+
|
32
|
+
db_tasks.root = root_path
|
33
|
+
db_tasks.env = env
|
34
|
+
db_tasks.db_dir = db_path
|
35
|
+
db_tasks.migrations_paths = migrations_paths
|
36
|
+
db_tasks.database_configuration = db_config
|
37
|
+
|
38
|
+
db_migrator.migrations_paths = migrations_paths
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require_relative 'migration_initializer'
|
3
|
+
require_relative 'migration_tasks'
|
4
|
+
|
5
|
+
module Appfuel
|
6
|
+
module Db
|
7
|
+
module MigrationRunner
|
8
|
+
def self.call(cmd, data = {})
|
9
|
+
tasks = MigrationTasks.new
|
10
|
+
tasks.install_tasks
|
11
|
+
Rake::Task[cmd].invoke
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rake'
|
2
|
+
module Appfuel
|
3
|
+
module Db
|
4
|
+
class MigrationTasks
|
5
|
+
include Rake::DSL
|
6
|
+
|
7
|
+
def install_tasks
|
8
|
+
load "active_record/railties/databases.rake"
|
9
|
+
|
10
|
+
namespace :db do
|
11
|
+
task :environment do
|
12
|
+
MigrationsInitializer.call
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Db
|
3
|
+
class Repository < Appfuel::Repository::Base
|
4
|
+
class << self
|
5
|
+
def create_mapper(maps = nil)
|
6
|
+
Mapper.new(maps)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :response_handler
|
11
|
+
|
12
|
+
def create(entity, exclude = [])
|
13
|
+
data = to_storage(entity, exclude: ['id'])
|
14
|
+
db_results = []
|
15
|
+
data.each do |db_class_key, mapped|
|
16
|
+
db_model = db_class(db_class_key)
|
17
|
+
db_results << db_model.create(mapped)
|
18
|
+
end
|
19
|
+
|
20
|
+
build(name: entity.domain_name, storage: db_results, type: :db)
|
21
|
+
end
|
22
|
+
|
23
|
+
def db_class(key)
|
24
|
+
app_container[key]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Used when the automated query methods don't suite your use case. It
|
28
|
+
# is assumed that the method executed will honor the same interfaces as
|
29
|
+
# query does
|
30
|
+
#
|
31
|
+
# @param criteria [SpCore::Criteria]
|
32
|
+
# @return [Dataset]
|
33
|
+
def execute_criteria(criteria)
|
34
|
+
query_method = "#{criteria.exec}_manual_query"
|
35
|
+
validate_query_method(query_method)
|
36
|
+
|
37
|
+
public_send(query_method, criteria)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Use the criteria entity's basename as a convention to find a method
|
41
|
+
# on the repository that returns the necessary relation (db scope) for
|
42
|
+
# which to add conditions that will be used to complete the query.
|
43
|
+
#
|
44
|
+
#
|
45
|
+
# @param criteria [SpCore::Criteria]
|
46
|
+
# @return [ActiveRecord::Relation]
|
47
|
+
def query_relation(criteria)
|
48
|
+
query_method = "#{criteria.domain}_query"
|
49
|
+
validate_query_method(query_method)
|
50
|
+
|
51
|
+
public_send(query_method)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Handles the treatment of the relation when the recordset is empty
|
55
|
+
# based on the criteria.
|
56
|
+
#
|
57
|
+
# @param criteria [SpCore::Criteria]
|
58
|
+
# @return [SpCore::Error, SpCore::Domain::EntityNotFound, nil]
|
59
|
+
def handle_empty_relation(criteria, relation)
|
60
|
+
return nil unless relation.blank?
|
61
|
+
|
62
|
+
if criteria.error_on_empty_dataset?
|
63
|
+
return error(criteria.domain => ["#{criteria.domain} not found"])
|
64
|
+
end
|
65
|
+
|
66
|
+
if criteria.single?
|
67
|
+
return create_entity_not_found(criteria)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Null object used when you can not find a given entity
|
72
|
+
#
|
73
|
+
# @param criteria [SpCore::Criteria]
|
74
|
+
# @return SpCore::Domain::EntityNotFound
|
75
|
+
def create_entity_not_found(criteria)
|
76
|
+
Appfuel::Domain::EntityNotFound.new(entity_name: criteria.domain_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Apply where, order and limit clauses to the relation based on the
|
80
|
+
# criteria.
|
81
|
+
#
|
82
|
+
# @param criteria [SpCore::Criteria]
|
83
|
+
# @param relation [mixed]
|
84
|
+
# @return relation
|
85
|
+
def apply_query_conditions(criteria, relation)
|
86
|
+
relation = where(criteria, relation)
|
87
|
+
relation = order(criteria, relation)
|
88
|
+
relation = limit(criteria, relation)
|
89
|
+
relation
|
90
|
+
end
|
91
|
+
|
92
|
+
# We have an interface for getting all recordsets separately because
|
93
|
+
# this is usually done with no filters or limits.
|
94
|
+
#
|
95
|
+
# @param criteria [SpCore::Criteria]
|
96
|
+
# @param relation
|
97
|
+
# @return relation
|
98
|
+
def apply_query_all(criteria, relation)
|
99
|
+
unless criteria.all?
|
100
|
+
fail "This interface can only be used when the criteria :all is used"
|
101
|
+
end
|
102
|
+
|
103
|
+
order(criteria, relation.all)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Determines which query conditions to apply to the relation
|
107
|
+
#
|
108
|
+
# @param criteria [SpCore::Criteria]
|
109
|
+
# @param relation
|
110
|
+
# @return relation
|
111
|
+
def handle_query_conditions(criteria, relation)
|
112
|
+
if criteria.all?
|
113
|
+
return apply_query_all(criteria, relation)
|
114
|
+
end
|
115
|
+
|
116
|
+
apply_query_conditions(criteria, relation)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Factory method to create a pagination result
|
120
|
+
#
|
121
|
+
# @param data [Hash]
|
122
|
+
# @return [SpCore::Pagination::Result]
|
123
|
+
def create_pager_result(data)
|
124
|
+
Appfuel::Pagination::Result.new(data)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Factory method to create a domain entity
|
128
|
+
#
|
129
|
+
# @param domain_name [String]
|
130
|
+
# @return [SpCore::Domain::EntityCollection]
|
131
|
+
def create_entity_collection(domain_name)
|
132
|
+
Appfuel::Domain::EntityCollection.new(domain_name)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Creates a lambda to used with the entity collection
|
136
|
+
#
|
137
|
+
# @param criteria [SpCore::Criteria]
|
138
|
+
# @param relation [Object]
|
139
|
+
# @param builder [Object]
|
140
|
+
# @return lambda
|
141
|
+
def entity_loader(criteria, relation, builder)
|
142
|
+
-> { load_collection(criteria, relation, builder) }
|
143
|
+
end
|
144
|
+
|
145
|
+
# A collection is usually loaded within an entity collection via
|
146
|
+
# a lambda. It setups up pagination results and builds an entity
|
147
|
+
# foreach record in the list
|
148
|
+
#
|
149
|
+
# @param criteria [SpCore::Criteria]
|
150
|
+
# @param relation [Object]
|
151
|
+
# @param builder [Object]
|
152
|
+
# @return [Hash]
|
153
|
+
def load_collection(criteria, relation, builder)
|
154
|
+
data = { items: [] }
|
155
|
+
unless criteria.disable_pagination?
|
156
|
+
relation = relation.page(criteria.page).per(criteria.per_page)
|
157
|
+
data[:pager] = create_pager_result(
|
158
|
+
total_pages: relation.total_pages,
|
159
|
+
current_page: relation.current_page,
|
160
|
+
total_count: relation.total_count,
|
161
|
+
page_limit: relation.limit_value,
|
162
|
+
page_size: relation.size
|
163
|
+
)
|
164
|
+
end
|
165
|
+
|
166
|
+
relation.each do |db_item|
|
167
|
+
data[:items] << builder.call(db_item)
|
168
|
+
end
|
169
|
+
data
|
170
|
+
end
|
171
|
+
|
172
|
+
# Create an entity collection and assign the entity loader with
|
173
|
+
# the entity builder class.
|
174
|
+
#
|
175
|
+
# @param criteria [SpCore::Criteria]
|
176
|
+
# @param relation relation
|
177
|
+
# @return SpCore::Domain::EntityCollection
|
178
|
+
def build_criteria_entities(criteria, relation)
|
179
|
+
builder = create_entity_builder(criteria.domain_name)
|
180
|
+
result = handle_empty_relation(criteria, relation)
|
181
|
+
# when this has a result it means an empty relation has been
|
182
|
+
# handled and ready for return otherwise it was a no op
|
183
|
+
return result if result
|
184
|
+
|
185
|
+
if criteria.single?
|
186
|
+
relation_method = criteria.first? ? :first : :last
|
187
|
+
return builder.call(relation.send(relation_method))
|
188
|
+
end
|
189
|
+
|
190
|
+
collection = create_entity_collection(criteria.domain_name)
|
191
|
+
collection.entity_loader = entity_loader(criteria, relation, builder)
|
192
|
+
collection
|
193
|
+
end
|
194
|
+
|
195
|
+
# Query will use the database model to build a query based on the
|
196
|
+
# given criteria. It supports where, order and limit conditions.
|
197
|
+
#
|
198
|
+
# @param criteria [SpCore::Criteria]
|
199
|
+
# @return [SpCore::Domain::Entity, SpCore::Domain::EntityCollection]
|
200
|
+
def query(criteria)
|
201
|
+
return execute_criteria(criteria) if criteria.exec?
|
202
|
+
|
203
|
+
begin
|
204
|
+
relation = query_relation(criteria)
|
205
|
+
relation = handle_query_conditions(criteria, relation)
|
206
|
+
build_criteria_entities(criteria, relation)
|
207
|
+
rescue => e
|
208
|
+
msg = "query failed for #{criteria.domain}: #{e.class} #{e.message}"
|
209
|
+
err = RuntimeError.new(msg)
|
210
|
+
err.set_backtrace(e.backtrace)
|
211
|
+
raise err
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
private
|
216
|
+
|
217
|
+
def raise_error(err, message)
|
218
|
+
error = RuntimeError.new(message)
|
219
|
+
error.set_backtrace(err.backtrace)
|
220
|
+
raise error
|
221
|
+
end
|
222
|
+
|
223
|
+
def validate_entity_id(entity)
|
224
|
+
if entity.id == Types::Undefined
|
225
|
+
fail("entity id is #{entity.id}")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'file/base'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module File
|
3
|
+
class Base
|
4
|
+
attr_reader :filepath
|
5
|
+
|
6
|
+
def initialize(name, path)
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
def attributes
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
def exsits?(data)
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def where(data)
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def limit(nbr)
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def order(data)
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Memory
|
3
|
+
class Mapper < Appfuel::Repository::Mapper
|
4
|
+
|
5
|
+
def to_storage(domain, opts = {})
|
6
|
+
excluded = opts[:exclude] || []
|
7
|
+
|
8
|
+
data = {}
|
9
|
+
each_entity_attr(domain.domain_name) do |entry|
|
10
|
+
attr_name = entry.storage_attr
|
11
|
+
next if excluded.include?(attr_name) || entry.skip?
|
12
|
+
|
13
|
+
data[attr_name] = entity_value(domain, entry)
|
14
|
+
end
|
15
|
+
data
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def exists?(criteria)
|
20
|
+
domain_expr = criteria.exists_expr
|
21
|
+
domain_name = domain_expr.domain_name
|
22
|
+
domain_attr = domain_expr.domain_attr
|
23
|
+
|
24
|
+
db_expr = create_db_expr(domain_name, domain_attr)
|
25
|
+
db_model = db_class_mapped(domain_name, domain_attr)
|
26
|
+
db_model.exists?([db_expr.string, db_expr.values])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Memory
|
3
|
+
class Repository < Appfuel::Repository::Base
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def create_mapper(maps = nil)
|
7
|
+
Mapper.new(maps)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :items, :sequence
|
12
|
+
def initialize
|
13
|
+
@items = {}
|
14
|
+
@sequence = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def create(entity)
|
19
|
+
id = sequence_id
|
20
|
+
entity.id = id
|
21
|
+
data = to_storage(entity)
|
22
|
+
items[id] = data
|
23
|
+
|
24
|
+
|
25
|
+
build(name: entity.domain_name, storage: data)
|
26
|
+
end
|
27
|
+
|
28
|
+
def build(name:, storage:, **inputs)
|
29
|
+
super(type: :memory, name: name, storage: storage, **inputs)
|
30
|
+
end
|
31
|
+
|
32
|
+
def sequence_id
|
33
|
+
@sequence + 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'dry/core/constants'
|
2
|
+
require 'dry-equalizer'
|
3
|
+
require 'dry-container'
|
4
|
+
require 'dry-types'
|
5
|
+
|
6
|
+
Dry::Types.load_extensions(:maybe)
|
7
|
+
|
8
|
+
module Types
|
9
|
+
include Dry::Types.module
|
10
|
+
include Dry::Core::Constants
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
# @param key [String, Symbol]
|
15
|
+
# @return [Class]
|
16
|
+
def [](key)
|
17
|
+
Dry::Types[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param key [String, Symbol]
|
21
|
+
# @return [Class]
|
22
|
+
def key?(key)
|
23
|
+
Dry::Types.container.key?(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Container used to store validation types and domain entities
|
27
|
+
#
|
28
|
+
# @return Dry::Container
|
29
|
+
def container
|
30
|
+
Dry::Types.container
|
31
|
+
end
|
32
|
+
|
33
|
+
# Register a dependency that can be used for injection
|
34
|
+
#
|
35
|
+
# @param key [String, Symbol]
|
36
|
+
# @param klass [Class]
|
37
|
+
# @return
|
38
|
+
def register(key, klass)
|
39
|
+
container.register(key, klass)
|
40
|
+
end
|
41
|
+
|
42
|
+
def register_domain(klass, opts = {})
|
43
|
+
unless klass.respond_to?(:domain_name)
|
44
|
+
fail "Domain must be a Appfuel::Entity or respond to :domain_name"
|
45
|
+
end
|
46
|
+
name = opts.key?(:as) ? opt[:as] : klass.domain_name
|
47
|
+
p name
|
48
|
+
return if key?(name) && self[name] == klass
|
49
|
+
|
50
|
+
register(name, klass)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|