appfuel 0.4.5 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/lib/appfuel/application/app_container.rb +3 -1
- data/lib/appfuel/application/root.rb +2 -1
- data/lib/appfuel/handler/inject_dsl.rb +6 -1
- data/lib/appfuel/initialize.rb +5 -0
- data/lib/appfuel/initializers/all.rb +1 -0
- data/lib/appfuel/initializers/aws_dynamodb.rb +17 -0
- data/lib/appfuel/initializers/db.rb +2 -0
- data/lib/appfuel/initializers/web_api.rb +1 -0
- data/lib/appfuel/storage/aws_dynamodb/no_sql_model.rb +44 -0
- data/lib/appfuel/storage/aws_dynamodb/repository.rb +11 -0
- data/lib/appfuel/storage/aws_dynamodb.rb +2 -0
- data/lib/appfuel/storage/db/mapper.rb +5 -3
- data/lib/appfuel/storage/repository/base.rb +2 -2
- data/lib/appfuel/storage/repository/mapper.rb +56 -82
- data/lib/appfuel/storage/repository/mapping_collection.rb +60 -0
- data/lib/appfuel/storage/repository/mapping_dsl.rb +45 -166
- data/lib/appfuel/storage/repository/mapping_entry.rb +1 -1
- data/lib/appfuel/storage/repository/storage_map.rb +62 -0
- data/lib/appfuel/storage/repository.rb +10 -13
- data/lib/appfuel/storage/web_api/repository.rb +4 -108
- data/lib/appfuel/storage.rb +1 -2
- data/lib/appfuel/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ebc666db81c563d31cb86e1d1783187e090c9a0
|
4
|
+
data.tar.gz: 50554a3c3ddbef08ea7c453f9ab20850c8569dcd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5b5a659ce0a686e2b495d1c6f6326a991b787f62619640c9bbecd06dd9e194f4508b247cff7190b2839606e864b1083f432c429e6c1952b95050d0d8fa07f40
|
7
|
+
data.tar.gz: 8493feb719823602a34412e8b8ac18f995b523e24dada3dcd61dd9bcf6603dff6ab6b30b48a783657ce20eea1778a892516906e6334972028e8ac74f8efbf132
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. (Pending ap
|
|
5
5
|
|
6
6
|
|
7
7
|
# Releases
|
8
|
+
## [[0.5.0]] (https://github.com/rsb/appfuel/releases/tag/0.5.0) 2017-06-29
|
9
|
+
### Added
|
10
|
+
- adding new repository type of aws dynamodb
|
11
|
+
- adding initializers for aws dynamodb
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
- bootstrapping the app is not idempotent and does not throw any errors when
|
15
|
+
done twice
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
- storage mapping as simplified to a one-to-one between domain and storage model
|
19
|
+
|
8
20
|
## [[0.4.5]] (https://github.com/rsb/appfuel/releases/tag/0.4.5) 2017-06-29
|
9
21
|
### Fixed
|
10
22
|
- fixed active record migrator, was not updating config properly
|
@@ -203,7 +203,9 @@ module Appfuel
|
|
203
203
|
# @param key [String] partial key to be built into fully qualified key
|
204
204
|
# @param type_ns [String] namespace for key
|
205
205
|
# @return [String] fully qualified namespaced key
|
206
|
-
def qualify_container_key(key, type_ns)
|
206
|
+
def qualify_container_key(key, type_ns, opts = {})
|
207
|
+
return key if opts[:no_context] == true
|
208
|
+
|
207
209
|
parts = key.to_s.split('.')
|
208
210
|
namespace = "#{container_feature_key}."
|
209
211
|
if parts[0].downcase == 'global'
|
@@ -110,12 +110,13 @@ module Appfuel
|
|
110
110
|
Repository::Initializer.new
|
111
111
|
}
|
112
112
|
|
113
|
+
mappings = Repository::MappingCollection.new
|
113
114
|
root_name = root.to_s.underscore
|
114
115
|
container.register(:root, root)
|
115
116
|
container.register(:root_name, root_name)
|
116
117
|
container.register(:root_path, root_path)
|
117
118
|
container.register(:auto_register_classes, [])
|
118
|
-
container.register(:repository_mappings,
|
119
|
+
container.register(:repository_mappings, mappings)
|
119
120
|
container.register(:repository_cache, {})
|
120
121
|
container.register(:repository_initializer, repo_initializer)
|
121
122
|
container.register(:features_path, "#{root_name}/features")
|
@@ -57,7 +57,12 @@ module Appfuel
|
|
57
57
|
"container"
|
58
58
|
end
|
59
59
|
|
60
|
-
|
60
|
+
no_context = false
|
61
|
+
if opts.key?(:no_context)
|
62
|
+
no_context = !!opts.delete(:no_context)
|
63
|
+
end
|
64
|
+
|
65
|
+
namespaced_key = qualify_container_key(key, cat, no_context: no_context)
|
61
66
|
injections[namespaced_key] = opts[:as]
|
62
67
|
nil
|
63
68
|
end
|
data/lib/appfuel/initialize.rb
CHANGED
@@ -81,11 +81,16 @@ module Appfuel
|
|
81
81
|
def run(params = {})
|
82
82
|
app_name = params.fetch(:app_name) { Appfuel.default_app_name }
|
83
83
|
container = Appfuel.app_container(app_name)
|
84
|
+
if container.key?(:initialized) && container[:initialized] == true
|
85
|
+
return container
|
86
|
+
end
|
87
|
+
|
84
88
|
handle_configuration(container, params)
|
85
89
|
handle_repository_mapping(container, params)
|
86
90
|
|
87
91
|
Appfuel.run_initializers('global', container, params[:exclude] || [])
|
88
92
|
|
93
|
+
container.register(:initialized, true)
|
89
94
|
container
|
90
95
|
end
|
91
96
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Appfuel::Initialize.define('global.aws_dynamodb') do |config, container|
|
2
|
+
require 'aws-sdk'
|
3
|
+
require 'appfuel/storage/aws_dynamodb'
|
4
|
+
|
5
|
+
env = config[:env]
|
6
|
+
if ['local', 'development'].include?(env.to_s)
|
7
|
+
Aws.config.update({
|
8
|
+
region: config[:aws][:dynamodb][:region],
|
9
|
+
endpoint: config[:aws][:dynamodb][:endpoint]
|
10
|
+
})
|
11
|
+
end
|
12
|
+
key = Appfuel::AwsDynamodb::CLIENT_CONTAINER_KEY
|
13
|
+
|
14
|
+
client = Aws::DynamoDB::Client.new
|
15
|
+
|
16
|
+
container.register(key, client)
|
17
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module AwsDynamodb
|
3
|
+
CLIENT_CONTAINER_KEY = 'aws.dynamodb.client'
|
4
|
+
class NoSqlModel
|
5
|
+
include Appfuel::Application::AppContainer
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def container_class_type
|
9
|
+
'aws.dynamo_db'
|
10
|
+
end
|
11
|
+
|
12
|
+
def config_key(value = nil)
|
13
|
+
return @config_key if value.nil?
|
14
|
+
@config_key = value.to_sym
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_config
|
18
|
+
config = app_container[:config]
|
19
|
+
unless config.key?(config_key)
|
20
|
+
fail "[aws_dynamodb] config key (#{config_key}) not found - #{self}"
|
21
|
+
end
|
22
|
+
config[config_key]
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_client
|
26
|
+
app_container[CLIENT_CONTAINER_KEY]
|
27
|
+
end
|
28
|
+
|
29
|
+
def inherited(klass)
|
30
|
+
stage_class_for_registration(klass)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :config, :client
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@client = self.class.load_client
|
38
|
+
@config = self.class.load_config
|
39
|
+
@table_prefix = @config.fetch(:table_prefix) { '' }
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -16,10 +16,12 @@ module Appfuel
|
|
16
16
|
# @param expr [SpCore::Domain::Expr]
|
17
17
|
# @return [table_name, column] [Array]
|
18
18
|
def db_table_column(expr, entry = nil)
|
19
|
-
|
20
|
-
db = storage_class_from_entry(entry, :db)
|
19
|
+
storage_map = storage_map(:db, expr.domain_name)
|
21
20
|
|
22
|
-
|
21
|
+
db = fetch_storage_class(storage_map.storage_key)
|
22
|
+
column = storage_map.storage_attr(expr.domain_attr)
|
23
|
+
|
24
|
+
[db.table_name, column]
|
23
25
|
end
|
24
26
|
|
25
27
|
# Converts an entity expression into a valid active record expresion
|
@@ -195,9 +195,9 @@ module Appfuel
|
|
195
195
|
mapper.to_storage(entity, exclude)
|
196
196
|
end
|
197
197
|
|
198
|
-
def to_entity(domain_name, storage)
|
198
|
+
def to_entity(domain_name, type, storage)
|
199
199
|
key = qualify_container_key(domain_name, "domains")
|
200
|
-
hash = mapper.to_entity_hash(domain_name, storage)
|
200
|
+
hash = mapper.to_entity_hash(domain_name, type, storage)
|
201
201
|
app_container[key].new(hash)
|
202
202
|
end
|
203
203
|
|
@@ -9,8 +9,8 @@ module Appfuel
|
|
9
9
|
|
10
10
|
def initialize(app_name, map = nil)
|
11
11
|
@container_root_name = app_name
|
12
|
-
if !map.nil? && !map.
|
13
|
-
fail "repository mappings must be a
|
12
|
+
if !map.nil? && !map.instance_of?(MappingCollection)
|
13
|
+
fail "repository mappings must be a MappingCollection"
|
14
14
|
end
|
15
15
|
@map = map
|
16
16
|
end
|
@@ -39,7 +39,7 @@ module Appfuel
|
|
39
39
|
# @param entity [String]
|
40
40
|
# @return [Boolean]
|
41
41
|
def entity?(entity_name)
|
42
|
-
map.
|
42
|
+
map.entity?(entity_name)
|
43
43
|
end
|
44
44
|
|
45
45
|
# Determine if an attribute is mapped for a given entity
|
@@ -47,65 +47,32 @@ module Appfuel
|
|
47
47
|
# @param entity [String] name of the entity
|
48
48
|
# @param attr [String] name of the attribute
|
49
49
|
# @return [Boolean]
|
50
|
-
def entity_attr?(entity_name, entity_attr)
|
51
|
-
|
52
|
-
|
53
|
-
map[entity_name].key?(entity_attr)
|
50
|
+
def entity_attr?(entity_name, entity_attr, type)
|
51
|
+
map.entity_attr?(type, entity_name, entity_attr)
|
54
52
|
end
|
55
53
|
|
56
|
-
# Returns a
|
54
|
+
# Returns a column name for an entity's attribute
|
57
55
|
#
|
58
56
|
# @raise [RuntimeError] when entity not found
|
59
57
|
# @raise [RuntimeError] when attr not found
|
60
58
|
#
|
61
59
|
# @param entity_name [String] qualified entity name "<feature>.<entity>"
|
62
60
|
# @param entity_attr [String] name of the attribute
|
63
|
-
# @return [
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
unless map[entity_name].key?(entity_attr)
|
68
|
-
fail "Entity (#{entity_name}) attr (#{entity_attr}) is not registered"
|
69
|
-
end
|
70
|
-
map[entity_name][entity_attr]
|
61
|
+
# @return [String]
|
62
|
+
def storage_attr(entity_name, entity_attr, type)
|
63
|
+
map.storage_attr(type, entity_name, entity_attr)
|
71
64
|
end
|
72
65
|
|
73
|
-
|
74
|
-
|
75
|
-
# @yield [attr, entry] expose the entity attr name and entry
|
76
|
-
#
|
77
|
-
# @param entity_name [String] qualified entity name "<feature>.<entity>"
|
78
|
-
# @return [void]
|
79
|
-
def each_entity_attr(entity_name)
|
80
|
-
validate_domain(entity_name)
|
81
|
-
map[entity_name].each do |_attr, entry|
|
82
|
-
yield entry
|
83
|
-
end
|
66
|
+
def storage_key(type, entity_name)
|
67
|
+
map.storage_key(type, entity_name)
|
84
68
|
end
|
85
69
|
|
86
|
-
|
87
|
-
|
88
|
-
# @param entity_name [String] qualified entity name "<feature>.<entity>"
|
89
|
-
# @param storage_attr [String] name the persistence attr
|
90
|
-
# @return [Boolean]
|
91
|
-
def storage_attr_mapped?(entity_name, storage_attr)
|
92
|
-
each_entity_attr(entity_name) do |entry|
|
93
|
-
return true if storage_attr == entry.storage_attr
|
94
|
-
end
|
95
|
-
|
96
|
-
false
|
70
|
+
def entity_container_name(type, entity_name)
|
71
|
+
map.container_name(type, entity_name)
|
97
72
|
end
|
98
73
|
|
99
|
-
|
100
|
-
|
101
|
-
# @raise [RuntimeError] when entity not found
|
102
|
-
# @raise [RuntimeError] when attr not found
|
103
|
-
#
|
104
|
-
# @param entity_name [String] qualified entity name "<feature>.<entity>"
|
105
|
-
# @param entity_attr [String] name of the attribute
|
106
|
-
# @return [String]
|
107
|
-
def storage_attr(entity_name, entity_attr)
|
108
|
-
find(entity_name, entity_attr).storage_attr
|
74
|
+
def storage_map(type, domain_name)
|
75
|
+
map.storage_map(type, domain_name)
|
109
76
|
end
|
110
77
|
|
111
78
|
# Returns the storage class based on type
|
@@ -118,36 +85,30 @@ module Appfuel
|
|
118
85
|
# @param entity [String] name of the entity
|
119
86
|
# @param attr [String] name of the attribute
|
120
87
|
# @return [Object]
|
121
|
-
def storage_class(
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
def storage_class_from_entry(entry, type)
|
127
|
-
unless entry.storage?(type)
|
128
|
-
fail "No (#{type}) storage has been mapped"
|
129
|
-
end
|
130
|
-
|
131
|
-
container_name = entry.container_name
|
132
|
-
unless container_root_name == container_name
|
88
|
+
def storage_class(type, entity_name)
|
89
|
+
key = storage_key(type, entity_name)
|
90
|
+
domain_container = entity_container_name(type, entity_name)
|
91
|
+
unless container_root_name == domain_container
|
133
92
|
fail "You can not access a mapping outside of this container " +
|
134
|
-
"(mapper: #{container_root_name}, entry: #{
|
93
|
+
"(mapper: #{container_root_name}, entry: #{domain_container})"
|
135
94
|
end
|
136
|
-
|
137
|
-
key
|
95
|
+
|
96
|
+
fetch_storage_class(key)
|
97
|
+
end
|
98
|
+
|
99
|
+
def fetch_storage_class(key)
|
100
|
+
app_container = Appfuel.app_container(container_root_name)
|
138
101
|
app_container[key]
|
139
102
|
end
|
140
103
|
|
141
|
-
def to_entity_hash(domain_name,
|
104
|
+
def to_entity_hash(domain_name, type, storage)
|
142
105
|
entity_attrs = {}
|
143
106
|
storage_data = storage_hash(storage)
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
update_entity_hash(domain_attr, storage_data[attr_name], entity_attrs)
|
107
|
+
map.each_attr(type, domain_name) do |domain_attr, storage_attr, skip|
|
108
|
+
next unless storage_data.key?(storage_attr)
|
109
|
+
value = storage_data[storage_attr]
|
110
|
+
update_entity_hash(domain_attr, value, entity_attrs)
|
149
111
|
end
|
150
|
-
|
151
112
|
entity_attrs
|
152
113
|
end
|
153
114
|
|
@@ -156,6 +117,7 @@ module Appfuel
|
|
156
117
|
fail "storage must implement to_h" unless storage.respond_to?(:to_h)
|
157
118
|
storage.to_h
|
158
119
|
end
|
120
|
+
|
159
121
|
# Convert the domain into a hash of storage attributes that represent.
|
160
122
|
# Each storage class has its own hash of mapped attributes. A domain
|
161
123
|
# can have more than one storage class.
|
@@ -172,22 +134,30 @@ module Appfuel
|
|
172
134
|
fail "Domain entity must implement :domain_name"
|
173
135
|
end
|
174
136
|
|
175
|
-
excluded
|
176
|
-
data
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
storage_attr
|
182
|
-
storage_class = entry.storage(type)
|
183
|
-
next if excluded.include?(storage_attr) || entry.skip?
|
184
|
-
|
185
|
-
data[storage_class] = {} unless data.key?(storage_class)
|
186
|
-
data[storage_class][storage_attr] = entity_value(domain, entry)
|
137
|
+
excluded = opts[:exclude] || []
|
138
|
+
data = {}
|
139
|
+
domain_name = domain.domain_name
|
140
|
+
map.each_attr(domain_name, type) do |domain_attr, storage_attr, skip|
|
141
|
+
next if excluded.include?(storage_attr) || skip == true
|
142
|
+
|
143
|
+
data[storage_attr] = entity_value(domain, entry)
|
187
144
|
end
|
145
|
+
|
188
146
|
data
|
189
147
|
end
|
190
148
|
|
149
|
+
# user.role.id => user_role_id 99
|
150
|
+
#
|
151
|
+
# {
|
152
|
+
# user: {
|
153
|
+
# role: {
|
154
|
+
# id: 99
|
155
|
+
# }
|
156
|
+
# }
|
157
|
+
# }
|
158
|
+
#
|
159
|
+
# id
|
160
|
+
#
|
191
161
|
def update_entity_hash(domain_attr, value, hash)
|
192
162
|
if domain_attr.include?('.')
|
193
163
|
hash.deep_merge!(create_entity_hash(domain_attr, value))
|
@@ -207,6 +177,10 @@ module Appfuel
|
|
207
177
|
value
|
208
178
|
end
|
209
179
|
|
180
|
+
# user.role.id
|
181
|
+
#
|
182
|
+
# [id, role, user]
|
183
|
+
#
|
210
184
|
def create_entity_hash(domain_attr, value)
|
211
185
|
domain_attr.split('.').reverse.inject(value) do |result, nested_attr|
|
212
186
|
{nested_attr => result}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Repository
|
3
|
+
class MappingCollection
|
4
|
+
attr_reader :collection
|
5
|
+
|
6
|
+
def initialize(collection = {})
|
7
|
+
@collection = collection
|
8
|
+
fail "collection must be a hash" unless collection.is_a?(Hash)
|
9
|
+
end
|
10
|
+
|
11
|
+
# map {
|
12
|
+
# domain_name => {
|
13
|
+
# type => map
|
14
|
+
# }
|
15
|
+
# }
|
16
|
+
def load(storage_map)
|
17
|
+
domain_name = storage_map.domain_name
|
18
|
+
storage_type = storage_map.storage_type
|
19
|
+
collection[domain_name] = {} unless collection.key?(domain_name)
|
20
|
+
collection[domain_name][storage_type] = storage_map
|
21
|
+
end
|
22
|
+
|
23
|
+
def entity?(domain_name)
|
24
|
+
collection.key?(domain_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def storage_attr(type, domain_name, domain_attr)
|
28
|
+
map = storage_map(type, domain_name)
|
29
|
+
map.storage_attr(domain_attr)
|
30
|
+
end
|
31
|
+
|
32
|
+
def storage_key(type, domain_name)
|
33
|
+
map = storage_map(type, domain_name)
|
34
|
+
map.storage_key
|
35
|
+
end
|
36
|
+
|
37
|
+
def container_name(type, domain_name)
|
38
|
+
map = storage_map(type, domain_name)
|
39
|
+
map.container_name
|
40
|
+
end
|
41
|
+
|
42
|
+
def each_attr(type, domain_name, &block)
|
43
|
+
map = storage_map(type, domain_name)
|
44
|
+
map.each(&block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def storage_map(type, domain_name)
|
48
|
+
unless entity?(domain_name)
|
49
|
+
fail "#{domain_name} is not registered in map"
|
50
|
+
end
|
51
|
+
|
52
|
+
unless collection[domain_name].key?(type)
|
53
|
+
fail "#{domain_name} storage #{type} is not registered in map"
|
54
|
+
end
|
55
|
+
|
56
|
+
collection[domain_name][type]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -6,208 +6,87 @@ module Appfuel
|
|
6
6
|
# and don't want any incorrect method_missing calls to be confused when
|
7
7
|
# collecting mapped values vs when defining them.
|
8
8
|
class MappingDsl
|
9
|
-
attr_reader :domain_name, :
|
10
|
-
:
|
9
|
+
attr_reader :domain_name, :entries, :map_class, :container_name,
|
10
|
+
:storage_key, :storage_type
|
11
11
|
|
12
|
-
|
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
|
12
|
+
# 1) mapping 'feature.domain', to: :db, model: 'foo.db.bar', contextual_key: false do
|
21
13
|
#
|
22
|
-
# 3) mapping 'feature.domain', db: 'global.bar' do
|
23
|
-
# ...
|
24
14
|
# end
|
25
15
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
16
|
+
# mapping 'feature.domain' web_api: 'web_api.http.model' do
|
17
|
+
# map 'id'
|
18
|
+
# map 'foo', 'bar'
|
19
|
+
# map 'biz', 'baz'
|
29
20
|
# end
|
30
21
|
#
|
31
|
-
|
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
|
-
|
22
|
+
def initialize(domain_name, to:, model:, **opts)
|
23
|
+
opts ||= {}
|
69
24
|
@entries = []
|
70
25
|
@domain_name = domain_name.to_s
|
71
|
-
@
|
72
|
-
@container_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
|
-
@storage = initialize_storage(storage: args)
|
101
|
-
return self
|
102
|
-
end
|
26
|
+
@map_class = opts[:map_class] || StorageMap
|
27
|
+
@container_name = opts[:container] || Appfuel.default_app_name
|
103
28
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
opts = args.shift
|
108
|
-
data = {type => key}
|
109
|
-
if opts.is_a?(Hash)
|
110
|
-
data.merge!(opts)
|
29
|
+
@contextual_key = true
|
30
|
+
if opts.key?(:contextual_key) && opts[:contextual_key] == false
|
31
|
+
@contextual_key = false
|
111
32
|
end
|
33
|
+
@storage_type = to
|
34
|
+
@storage_key = translate_storage_key(to, model)
|
112
35
|
|
113
|
-
@
|
114
|
-
self
|
36
|
+
fail "entity name can not be empty" if @domain_name.empty?
|
115
37
|
end
|
116
38
|
|
117
|
-
def
|
118
|
-
|
119
|
-
|
39
|
+
def create_storage_map
|
40
|
+
map_class.new(
|
41
|
+
domain_name: domain_name,
|
42
|
+
container: container_name,
|
43
|
+
storage_type: storage_type,
|
44
|
+
storage_key: storage_key,
|
45
|
+
entries: entries
|
46
|
+
)
|
120
47
|
end
|
121
48
|
|
122
49
|
def map(name, domain_attr = nil, opts = {})
|
50
|
+
if domain_attr.is_a?(Hash)
|
51
|
+
opts = domain_attr
|
52
|
+
domain_attr = nil
|
53
|
+
end
|
54
|
+
|
123
55
|
domain_attr = name if domain_attr.nil?
|
124
56
|
|
125
57
|
data = opts.merge({
|
126
|
-
domain_name: domain_name,
|
127
58
|
domain_attr: domain_attr,
|
128
|
-
storage: storage,
|
129
59
|
storage_attr: name,
|
130
|
-
container: container_name,
|
131
60
|
})
|
132
61
|
|
133
|
-
|
134
|
-
end
|
135
|
-
|
136
|
-
private
|
137
|
-
|
138
|
-
def initialize_storage(data)
|
139
|
-
storage = {}
|
140
|
-
if data.key?(:db)
|
141
|
-
value = data[:db]
|
142
|
-
storage[:db] = initialize_general_storage(:db, value, data)
|
143
|
-
elsif data.key?(:file)
|
144
|
-
value = data[:file]
|
145
|
-
storage[:file] = initialize_file_storage(value)
|
146
|
-
elsif data.key?(:web_api)
|
147
|
-
value = data[:web_api]
|
148
|
-
storage[:web_api] = initialize_general_storage(:web_api, value, data)
|
149
|
-
elsif data.key?(:storage) && data[:storage].is_a?(Array)
|
150
|
-
data[:storage].each do |type|
|
151
|
-
storage[type] = if type.to_s.downcase == 'file'
|
152
|
-
initialize_file_storage(true)
|
153
|
-
else
|
154
|
-
initialize_general_storage(type, true)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
storage
|
159
|
-
end
|
160
|
-
|
161
|
-
def initialize_general_storage(key, value, opts = {})
|
162
|
-
case
|
163
|
-
when value == true
|
164
|
-
translate_storage_key(key, domain_name)
|
165
|
-
when opts.is_a?(Hash) && opts[:key_translation] == false
|
166
|
-
value
|
167
|
-
else
|
168
|
-
translate_storage_key(key, value)
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
def initialize_file_storage(value)
|
173
|
-
key = translate_storage_key(:file, domain_name)
|
174
|
-
case value
|
175
|
-
when true
|
176
|
-
{
|
177
|
-
model: 'file.model',
|
178
|
-
path: "#{storage_path}/#{key.tr('.', '/')}.yml"
|
179
|
-
}
|
180
|
-
end
|
62
|
+
entries << data
|
181
63
|
end
|
182
64
|
|
183
|
-
def
|
184
|
-
|
185
|
-
path = app_container[:root_path]
|
186
|
-
if app_container.key?(:storage_path)
|
187
|
-
path = app_container[:storage_path]
|
188
|
-
end
|
189
|
-
path
|
65
|
+
def contextual_key?
|
66
|
+
@contextual_key
|
190
67
|
end
|
191
68
|
|
192
|
-
#
|
193
69
|
# global.user
|
194
70
|
# global.storage.db.user
|
195
71
|
# membership.user
|
196
72
|
# features.membership.{type}.user
|
197
73
|
def translate_storage_key(type, partial_key)
|
198
|
-
|
74
|
+
return partial_key unless contextual_key?
|
75
|
+
fail "#{type} model key can not be empty" if partial_key.empty?
|
199
76
|
|
77
|
+
# take the feature or domain root unless its global
|
78
|
+
domain_top, _domain_base = domain_name.split('.')
|
200
79
|
top, *parts = partial_key.split('.')
|
201
|
-
top = "features.#{top}" unless top == 'global'
|
202
|
-
"#{top}.#{type}.#{parts.join('.')}"
|
203
|
-
end
|
204
80
|
|
205
|
-
|
206
|
-
|
207
|
-
|
81
|
+
if top == 'global'
|
82
|
+
root = top
|
83
|
+
base = parts.join('.')
|
84
|
+
else
|
85
|
+
root = "features.#{domain_top}"
|
86
|
+
base = top
|
87
|
+
end
|
208
88
|
|
209
|
-
|
210
|
-
@storage[type] = "#{type.to_s.underscore}.model"
|
89
|
+
"#{root}.#{type}.#{base}"
|
211
90
|
end
|
212
91
|
end
|
213
92
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Appfuel
|
2
|
+
module Repository
|
3
|
+
class StorageMap
|
4
|
+
attr_reader :domain_name, :container_name, :entries,
|
5
|
+
:storage_type, :storage_key
|
6
|
+
|
7
|
+
def initialize(data)
|
8
|
+
fail "Mapping data must be a hash" unless data.is_a?(Hash)
|
9
|
+
|
10
|
+
@container_name = data[:container]
|
11
|
+
@domain_name = data.fetch(:domain_name) { domain_name_failure }.to_s
|
12
|
+
@storage_type = data.fetch(:storage_type) { storage_type_failure }
|
13
|
+
@storage_key = data.fetch(:storage_key) { storage_key_failure }
|
14
|
+
@entries = data.fetch(:entries) { entries_failure }
|
15
|
+
end
|
16
|
+
|
17
|
+
def storage_attr(domain_attr)
|
18
|
+
entries.each do |data|
|
19
|
+
return data[:storage_attr] if data[:domain_attr] == domain_attr
|
20
|
+
end
|
21
|
+
|
22
|
+
fail "[storage_map #{domain_name}] #{domain_attr} not registered"
|
23
|
+
end
|
24
|
+
|
25
|
+
def domain_attr(storage_attr)
|
26
|
+
entries.each do |data|
|
27
|
+
return data[:domain_attr] if data[:storage_attr] == storage_attr
|
28
|
+
end
|
29
|
+
|
30
|
+
fail "[storage_map #{domain_name}] #{storage_attr} not registered"
|
31
|
+
end
|
32
|
+
|
33
|
+
def each
|
34
|
+
entries.each do |data|
|
35
|
+
yield data[:domain_attr], data[:storage_attr], data[:skip]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def domain_name_failure
|
42
|
+
fail "Fully qualified domain name is required"
|
43
|
+
end
|
44
|
+
|
45
|
+
def storage_type_failure
|
46
|
+
domain_failure("storage_type is required for storage map")
|
47
|
+
end
|
48
|
+
|
49
|
+
def storage_key_failure
|
50
|
+
domain_failure(":storage_key is required for storage map")
|
51
|
+
end
|
52
|
+
|
53
|
+
def entries_failure
|
54
|
+
domain_failure(":mapping_entries are required for storage map")
|
55
|
+
end
|
56
|
+
|
57
|
+
def domain_failure(msg)
|
58
|
+
fail "[#{domain_name}] #{msg}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -2,8 +2,12 @@ require 'parslet'
|
|
2
2
|
require 'parslet/convenience'
|
3
3
|
|
4
4
|
require_relative 'repository/base'
|
5
|
+
|
5
6
|
require_relative 'repository/mapping_entry'
|
7
|
+
require_relative 'repository/storage_map'
|
6
8
|
require_relative 'repository/mapping_dsl'
|
9
|
+
require_relative 'repository/mapping_collection'
|
10
|
+
|
7
11
|
require_relative 'repository/mapper'
|
8
12
|
require_relative 'repository/initializer'
|
9
13
|
require_relative 'repository/runner'
|
@@ -38,21 +42,14 @@ module Appfuel
|
|
38
42
|
# @param entity_name [String] domain name of the entity we are mapping
|
39
43
|
# @param db_class [String] name of the database class used in mapping
|
40
44
|
# @return [DbEntityMapper]
|
41
|
-
def self.mapping(domain_name,
|
42
|
-
dsl = MappingDsl.new(domain_name,
|
45
|
+
def self.mapping(domain_name, to:, model:, **opts, &block)
|
46
|
+
dsl = MappingDsl.new(domain_name, to: to, model: model, **opts)
|
43
47
|
dsl.instance_eval(&block)
|
44
48
|
|
45
|
-
dsl.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
domain_name = entry.domain_name
|
51
|
-
mappings[domain_name] = {} unless mappings.key?(domain_name)
|
52
|
-
|
53
|
-
entries = mappings[domain_name]
|
54
|
-
entries[entry.domain_attr] = entry
|
55
|
-
end
|
49
|
+
container = Appfuel.app_container(dsl.container_name)
|
50
|
+
mappings = container['repository_mappings']
|
51
|
+
storage_map = dsl.create_storage_map
|
52
|
+
mappings.load(storage_map)
|
56
53
|
end
|
57
54
|
|
58
55
|
def self.entity_builder(domain_name, type, opts = {}, &block)
|
@@ -22,116 +22,12 @@ module Appfuel
|
|
22
22
|
=end
|
23
23
|
end
|
24
24
|
|
25
|
-
def storage_class(domain_name
|
26
|
-
mapper.storage_class(
|
25
|
+
def storage_class(domain_name)
|
26
|
+
mapper.storage_class(:web_api, domain_name)
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
#
|
32
|
-
def api_class(key)
|
33
|
-
unless key.include?('.')
|
34
|
-
key = "features.#{self.class.container_feature_name}.web_api.#{key}"
|
35
|
-
end
|
36
|
-
app_container[key]
|
37
|
-
end
|
38
|
-
|
39
|
-
# Handles the treatment of the relation when the recordset is empty
|
40
|
-
# based on the criteria.
|
41
|
-
#
|
42
|
-
# @param criteria [SpCore::Criteria]
|
43
|
-
# @return [SpCore::Error, SpCore::Domain::EntityNotFound, nil]
|
44
|
-
def handle_empty_relation(criteria, relation)
|
45
|
-
return nil unless relation.blank?
|
46
|
-
|
47
|
-
if criteria.error_on_empty_dataset?
|
48
|
-
return error(criteria.domain => ["#{criteria.domain} not found"])
|
49
|
-
end
|
50
|
-
|
51
|
-
if criteria.single?
|
52
|
-
return create_entity_not_found(criteria)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# Null object used when you can not find a given entity
|
57
|
-
#
|
58
|
-
# @param criteria [SpCore::Criteria]
|
59
|
-
# @return SpCore::Domain::EntityNotFound
|
60
|
-
def create_entity_not_found(criteria)
|
61
|
-
Appfuel::Domain::EntityNotFound.new(entity_name: criteria.domain_name)
|
62
|
-
end
|
63
|
-
|
64
|
-
# Apply where, order and limit clauses to the relation based on the
|
65
|
-
# criteria.
|
66
|
-
#
|
67
|
-
# @param criteria [SpCore::Criteria]
|
68
|
-
# @param relation [mixed]
|
69
|
-
# @return relation
|
70
|
-
def apply_query_conditions(criteria, relation, _settings)
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
# Determines which query conditions to apply to the relation
|
75
|
-
#
|
76
|
-
# @param criteria [SpCore::Criteria]
|
77
|
-
# @param relation
|
78
|
-
# @return relation
|
79
|
-
def handle_query_conditions(criteria, relation, settings)
|
80
|
-
if settings.all?
|
81
|
-
return order(criteria, relation.all)
|
82
|
-
end
|
83
|
-
|
84
|
-
apply_query_conditions(criteria, relation, settings)
|
85
|
-
end
|
86
|
-
|
87
|
-
def handle_empty_relation(criteria, relation, settings)
|
88
|
-
unless relation.respond_to?(:blank?)
|
89
|
-
fail "The database relation invalid, does not implement :blank?"
|
90
|
-
end
|
91
|
-
|
92
|
-
return nil unless relation.blank?
|
93
|
-
|
94
|
-
if criteria.error_on_empty_dataset?
|
95
|
-
return domain_not_found_error(criteria)
|
96
|
-
end
|
97
|
-
|
98
|
-
if criteria.single?
|
99
|
-
return domain_not_found(criteria)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
# 1) lookup query id in cache
|
104
|
-
# if found build collection from cached query ids
|
105
|
-
#
|
106
|
-
# 2) query id not found in cache
|
107
|
-
# a) assign query id from criteria
|
108
|
-
# b) find the domain builder for that criteria
|
109
|
-
# c) loop through each item in the database relation
|
110
|
-
# d) build an domain from each record in the relation
|
111
|
-
# e) create cache id from the domain
|
112
|
-
# f) record cache id into a list that represents query
|
113
|
-
# g) assign domain into the cache with its cache id
|
114
|
-
# id is in the cache the its updated *represents a miss
|
115
|
-
# h) assign the query list into the cache with its query id
|
116
|
-
#
|
117
|
-
def build_domains(criteria, relation, settings)
|
118
|
-
result = handle_empty_relation(criteria, relation, settings)
|
119
|
-
return result if result
|
120
|
-
|
121
|
-
|
122
|
-
if settings.single?
|
123
|
-
method = settings.first? ? :first : :last
|
124
|
-
db_model = relation.send(method)
|
125
|
-
builder = domain_builder(criteria.domain_name)
|
126
|
-
domain = builder.call(db_model, criteria)
|
127
|
-
ap domain
|
128
|
-
end
|
129
|
-
|
130
|
-
end
|
131
|
-
|
132
|
-
def domain_builder(domain_name)
|
133
|
-
key = qualify_container_key(domain_name, 'domain_builders.db')
|
134
|
-
app_container[key]
|
29
|
+
def to_entity(domain_name, storage)
|
30
|
+
super(domain_name, :web_api, storage)
|
135
31
|
end
|
136
32
|
|
137
33
|
private
|
data/lib/appfuel/storage.rb
CHANGED
data/lib/appfuel/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appfuel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Scott-Buccleuch
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-07-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -316,6 +316,7 @@ files:
|
|
316
316
|
- lib/appfuel/initialize.rb
|
317
317
|
- lib/appfuel/initialize/initializer.rb
|
318
318
|
- lib/appfuel/initializers/all.rb
|
319
|
+
- lib/appfuel/initializers/aws_dynamodb.rb
|
319
320
|
- lib/appfuel/initializers/db.rb
|
320
321
|
- lib/appfuel/initializers/logging.rb
|
321
322
|
- lib/appfuel/initializers/web_api.rb
|
@@ -330,6 +331,9 @@ files:
|
|
330
331
|
- lib/appfuel/root_module.rb
|
331
332
|
- lib/appfuel/run_error.rb
|
332
333
|
- lib/appfuel/storage.rb
|
334
|
+
- lib/appfuel/storage/aws_dynamodb.rb
|
335
|
+
- lib/appfuel/storage/aws_dynamodb/no_sql_model.rb
|
336
|
+
- lib/appfuel/storage/aws_dynamodb/repository.rb
|
333
337
|
- lib/appfuel/storage/db.rb
|
334
338
|
- lib/appfuel/storage/db/active_record_model.rb
|
335
339
|
- lib/appfuel/storage/db/mapper.rb
|
@@ -352,6 +356,7 @@ files:
|
|
352
356
|
- lib/appfuel/storage/repository/expr_transform.rb
|
353
357
|
- lib/appfuel/storage/repository/initializer.rb
|
354
358
|
- lib/appfuel/storage/repository/mapper.rb
|
359
|
+
- lib/appfuel/storage/repository/mapping_collection.rb
|
355
360
|
- lib/appfuel/storage/repository/mapping_dsl.rb
|
356
361
|
- lib/appfuel/storage/repository/mapping_entry.rb
|
357
362
|
- lib/appfuel/storage/repository/order_expr.rb
|
@@ -359,6 +364,7 @@ files:
|
|
359
364
|
- lib/appfuel/storage/repository/search_parser.rb
|
360
365
|
- lib/appfuel/storage/repository/search_transform.rb
|
361
366
|
- lib/appfuel/storage/repository/settings.rb
|
367
|
+
- lib/appfuel/storage/repository/storage_map.rb
|
362
368
|
- lib/appfuel/storage/web_api.rb
|
363
369
|
- lib/appfuel/storage/web_api/http_model.rb
|
364
370
|
- lib/appfuel/storage/web_api/repository.rb
|