appfuel 0.4.5 → 0.5.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 +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
|