appfuel 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|