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 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