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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d3a177c11d50f541f3190be60b12e703604d1154
4
- data.tar.gz: aec5544881946e4293a347721cb3275bffa27109
3
+ metadata.gz: 2ebc666db81c563d31cb86e1d1783187e090c9a0
4
+ data.tar.gz: 50554a3c3ddbef08ea7c453f9ab20850c8569dcd
5
5
  SHA512:
6
- metadata.gz: e229df4c4375776527a8091f8b7e493d741cd103fd2c2b2d8406e0d78db954f3b2e767428adc7d4b0f5041dcdf2541bef4922169b8ad6e01f001bfe50d533d3c
7
- data.tar.gz: ea420d94728c15cf578bcd8f20535c21354662550c6ad761fa0b3e4920447e212b05dfdf4ca5f9ea01ecc094b45fad523d215e471f3e72553bde4db38813b94f
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
- namespaced_key = qualify_container_key(key, cat)
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
@@ -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
@@ -1,3 +1,4 @@
1
1
  require_relative 'logging'
2
2
  require_relative 'db'
3
3
  require_relative 'web_api'
4
+ require_relative 'aws_dynamodb'
@@ -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
@@ -4,6 +4,8 @@ Appfuel::Initialize.define('global.db') do |config, container|
4
4
 
5
5
  require 'pg'
6
6
  require 'active_record'
7
+ require 'appfuel/storage/db'
8
+
7
9
  config[:db][:main] = config[:db][:main].with_indifferent_access
8
10
 
9
11
  ActiveSupport.on_load(:active_record) do
@@ -1,4 +1,5 @@
1
1
  Appfuel::Initialize.define('global.web_api') do |config, container|
2
2
  require 'rest-client'
3
+ require 'appfuel/storage/web_api'
3
4
  container.register('web_api.http_adapter', RestClient)
4
5
  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
@@ -0,0 +1,11 @@
1
+ module Appfuel
2
+ module AwsDynamodb
3
+ class Repository < Appfuel::Repository::Base
4
+ class << self
5
+ def container_class_type
6
+ "#{super}.aws.dynamo_db"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'aws_dynamodb/repository'
2
+ require_relative 'aws_dynamodb/no_sql_model'
@@ -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
- entry ||= find(expr.domain_name, expr.domain_attr)
20
- db = storage_class_from_entry(entry, :db)
19
+ storage_map = storage_map(:db, expr.domain_name)
21
20
 
22
- [db.table_name, entry.storage_attr]
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.is_a?(Hash)
13
- fail "repository mappings must be a hash"
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.key?(entity_name)
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
- return false unless entity?(entity_name)
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 mapping entry for a given entity
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 [Boolean]
64
- def find(entity_name, entity_attr)
65
- validate_domain(entity_name)
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
- # Iterates over all entries for a given entity
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
- # Determine if an column is mapped for a given entity
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
- # Returns a column name for an entity's attribute
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(entity_name, entity_attr, type)
122
- entry = find(entity_name, entity_attr)
123
- storage_class_from_entry(entry, type)
124
- end
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: #{container_name})"
93
+ "(mapper: #{container_root_name}, entry: #{domain_container})"
135
94
  end
136
- app_container = Appfuel.app_container(entry.container_name)
137
- key = entry.storage(type)
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, storage)
104
+ def to_entity_hash(domain_name, type, storage)
142
105
  entity_attrs = {}
143
106
  storage_data = storage_hash(storage)
144
- each_entity_attr(domain_name) do |entry|
145
- attr_name = entry.storage_attr
146
- domain_attr = entry.domain_attr
147
- next unless storage_data.key?(attr_name)
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 = opts[:exclude] || []
176
- data = {}
177
- each_entity_attr(domain.domain_name) do |entry|
178
- unless entry.storage?(type)
179
- "storage type (#{type}) is not support for (#{domain.domain_name})"
180
- end
181
- storage_attr = entry.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, :storage, :entries, :entry_class,
10
- :container_name
9
+ attr_reader :domain_name, :entries, :map_class, :container_name,
10
+ :storage_key, :storage_type
11
11
 
12
- STORAGE_TYPES = [:db, :file, :memory, :web_api]
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
- # 4) mapping 'feature.domain', db: 'foo.bar.baz', key_translation: false)
27
- # 4) mapping 'feature.domain', storage: [:db, :file] do
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
- # a file model requires the domain_name it represents.
32
- #
33
- # case1 - build a model with default settings
34
- # file: storage.file.model
35
- # path: <root_path>/storage/file/{key}.yaml
36
- # adapter: storage.file.model
37
- #
38
- # case2 - build a model with given settings
39
- # note: if path is absolute nothing is done
40
- # if path is relative we will prepend root_path
41
- # if no yaml extension the key is translated to a path
42
- #
43
- # path: foo/bar/baz.yml -> <root_path>/foo/bar/baz.yml
44
- # path: /foo/bar/baz.yml -> /foo/bar/baz.yml
45
- # path auth.user -> <root_path>/storage/features/auth/file/user.yml
46
- # path gobal.user -> <root_path/storage/global/auth/file/user.yml
47
- #
48
- # case3 - build a model with adapter and path
49
- # path: sames as above
50
- # adapter translates key to location of adapter in storage
51
- #
52
- # container
53
- # default key -> storage.file.model is default
54
- # auth.user -> features.auth.storage.file.user
55
- #
56
- # file 'storage.file.model'
57
- #
58
- # storage db: 'foo.user_user'
59
- # storage :file
60
- # storage :memory
61
- #
62
- # storage db: 'foo.user_ath',
63
- # file: 'storage.file.model',
64
- # memory: 'storage.memory.model'
65
- #
66
- def initialize(domain_name, options = {})
67
- fail "options must be a hash" unless options.is_a?(Hash)
68
-
22
+ def initialize(domain_name, to:, model:, **opts)
23
+ opts ||= {}
69
24
  @entries = []
70
25
  @domain_name = domain_name.to_s
71
- @entry_class = options[:entry_class] || MappingEntry
72
- @container_name = options[:container] || Appfuel.default_app_name
73
- @storage = initialize_storage(options)
74
-
75
- fail "entity name can not be empty" if @domain_name.empty?
76
- end
77
-
78
- def db(key)
79
- @storage[:db] = translate_storage_key(key)
80
- end
81
-
82
- # 5) mapping 'feature.domain' db: true do
83
- # end
84
- #
85
- # 6) mapping 'feature.domain', db: true do
86
- # storage :db, 'foo.bar'
87
- # storage :file
88
- # end
89
- # storage(type = nil, options = {})
90
- #
91
- def storage(type = nil, *args)
92
- return @storage if type.nil?
93
- unless type.respond_to?(:to_sym)
94
- fail "Storage type must implement :to_sym"
95
- end
96
- type = type.to_sym
97
-
98
- if all_storage_symbols?(*args)
99
- args.unshift(type)
100
- @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
- args = [true] if args.empty?
105
-
106
- key = args.shift
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
- @storage.merge!(initialize_storage(data))
114
- self
36
+ fail "entity name can not be empty" if @domain_name.empty?
115
37
  end
116
38
 
117
- def all_storage_symbols?(*args)
118
- result = args - STORAGE_TYPES
119
- result.empty?
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
- @entries << entry_class.new(data)
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 storage_path
184
- app_container = Appfuel.app_container(container_name)
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
- fail "#{type} can not be empty" if partial_key.empty?
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
- def assign_storage(type, partial_key)
206
- @storage[type] = translate_storage_key(partial_key)
207
- end
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
- def assign_default_storage(type)
210
- @storage[type] = "#{type.to_s.underscore}.model"
89
+ "#{root}.#{type}.#{base}"
211
90
  end
212
91
  end
213
92
  end
@@ -1,7 +1,7 @@
1
1
  module Appfuel
2
2
  module Repository
3
3
  class MappingEntry
4
- attr_reader :domain_name, :domain_attr, :computed_attr, :storage_attr,
4
+ attr_reader :domain_name, :domain_attr, :storage_attr,
5
5
  :container_name, :container_key
6
6
 
7
7
  def initialize(data)
@@ -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, options = {}, &block)
42
- dsl = MappingDsl.new(domain_name, options)
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.entries.each do |entry|
46
- root = entry.container_name || Appfuel.default_app_name
47
- container = Appfuel.app_container(root)
48
- mappings = container['repository_mappings']
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, domain_attr)
26
- mapper.storage_class(domain_name, domain_attr, :web_api)
25
+ def storage_class(domain_name)
26
+ mapper.storage_class(:web_api, domain_name)
27
27
  end
28
28
 
29
- # when key has no . assume the feature of the repository
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
@@ -1,5 +1,4 @@
1
1
  require_relative 'storage/repository'
2
2
  require_relative 'storage/file'
3
3
  require_relative 'storage/memory'
4
- require_relative 'storage/db'
5
- require_relative 'storage/web_api'
4
+
@@ -1,3 +1,3 @@
1
1
  module Appfuel
2
- VERSION = "0.4.5"
2
+ VERSION = "0.5.0"
3
3
  end
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.5
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-06-29 00:00:00.000000000 Z
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