pakyow-data 1.0.0.rc1

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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +0 -0
  3. data/LICENSE +4 -0
  4. data/README.md +29 -0
  5. data/lib/pakyow/data/adapters/abstract.rb +58 -0
  6. data/lib/pakyow/data/adapters/sql/commands.rb +58 -0
  7. data/lib/pakyow/data/adapters/sql/dataset_methods.rb +29 -0
  8. data/lib/pakyow/data/adapters/sql/differ.rb +76 -0
  9. data/lib/pakyow/data/adapters/sql/migrator/adapter_methods.rb +95 -0
  10. data/lib/pakyow/data/adapters/sql/migrator.rb +181 -0
  11. data/lib/pakyow/data/adapters/sql/migrators/automator.rb +49 -0
  12. data/lib/pakyow/data/adapters/sql/migrators/finalizer.rb +96 -0
  13. data/lib/pakyow/data/adapters/sql/runner.rb +49 -0
  14. data/lib/pakyow/data/adapters/sql/source_extension.rb +31 -0
  15. data/lib/pakyow/data/adapters/sql/types.rb +50 -0
  16. data/lib/pakyow/data/adapters/sql.rb +247 -0
  17. data/lib/pakyow/data/behavior/config.rb +28 -0
  18. data/lib/pakyow/data/behavior/lookup.rb +75 -0
  19. data/lib/pakyow/data/behavior/serialization.rb +40 -0
  20. data/lib/pakyow/data/connection.rb +103 -0
  21. data/lib/pakyow/data/container.rb +273 -0
  22. data/lib/pakyow/data/errors.rb +169 -0
  23. data/lib/pakyow/data/framework.rb +42 -0
  24. data/lib/pakyow/data/helpers.rb +11 -0
  25. data/lib/pakyow/data/lookup.rb +85 -0
  26. data/lib/pakyow/data/migrator.rb +182 -0
  27. data/lib/pakyow/data/object.rb +98 -0
  28. data/lib/pakyow/data/proxy.rb +262 -0
  29. data/lib/pakyow/data/result.rb +53 -0
  30. data/lib/pakyow/data/sources/abstract.rb +82 -0
  31. data/lib/pakyow/data/sources/ephemeral.rb +72 -0
  32. data/lib/pakyow/data/sources/relational/association.rb +43 -0
  33. data/lib/pakyow/data/sources/relational/associations/belongs_to.rb +47 -0
  34. data/lib/pakyow/data/sources/relational/associations/has_many.rb +54 -0
  35. data/lib/pakyow/data/sources/relational/associations/has_one.rb +54 -0
  36. data/lib/pakyow/data/sources/relational/associations/through.rb +67 -0
  37. data/lib/pakyow/data/sources/relational/command.rb +531 -0
  38. data/lib/pakyow/data/sources/relational/migrator.rb +101 -0
  39. data/lib/pakyow/data/sources/relational.rb +587 -0
  40. data/lib/pakyow/data/subscribers/adapters/memory.rb +153 -0
  41. data/lib/pakyow/data/subscribers/adapters/redis/pipeliner.rb +45 -0
  42. data/lib/pakyow/data/subscribers/adapters/redis/scripts/_shared.lua +73 -0
  43. data/lib/pakyow/data/subscribers/adapters/redis/scripts/expire.lua +16 -0
  44. data/lib/pakyow/data/subscribers/adapters/redis/scripts/persist.lua +15 -0
  45. data/lib/pakyow/data/subscribers/adapters/redis/scripts/register.lua +37 -0
  46. data/lib/pakyow/data/subscribers/adapters/redis.rb +209 -0
  47. data/lib/pakyow/data/subscribers.rb +148 -0
  48. data/lib/pakyow/data/tasks/bootstrap.rake +18 -0
  49. data/lib/pakyow/data/tasks/create.rake +22 -0
  50. data/lib/pakyow/data/tasks/drop.rake +32 -0
  51. data/lib/pakyow/data/tasks/finalize.rake +56 -0
  52. data/lib/pakyow/data/tasks/migrate.rake +24 -0
  53. data/lib/pakyow/data/tasks/reset.rake +18 -0
  54. data/lib/pakyow/data/types.rb +37 -0
  55. data/lib/pakyow/data.rb +27 -0
  56. data/lib/pakyow/environment/data/auto_migrate.rb +31 -0
  57. data/lib/pakyow/environment/data/config.rb +54 -0
  58. data/lib/pakyow/environment/data/connections.rb +76 -0
  59. data/lib/pakyow/environment/data/memory_db.rb +23 -0
  60. data/lib/pakyow/validations/unique.rb +26 -0
  61. metadata +186 -0
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pakyow
4
+ module Data
5
+ module Adapters
6
+ class Sql
7
+ class Runner
8
+ def initialize(connection, migration_path)
9
+ @connection, @migration_path = connection, migration_path
10
+ end
11
+
12
+ def disconnect!
13
+ @connection.disconnect
14
+ end
15
+
16
+ def run!
17
+ Pakyow.module_eval do
18
+ unless singleton_class.instance_methods.include?(:migration)
19
+ def self.migration(&block)
20
+ Sequel.migration(&block)
21
+ end
22
+ end
23
+ end
24
+
25
+ # Allows migrations to be defined with the nice mapping, then executed with the Sequel type.
26
+ #
27
+ local_types = @connection.types
28
+ @connection.adapter.connection.define_singleton_method :type_literal do |column|
29
+ if column[:type].is_a?(Symbol)
30
+ begin
31
+ column[:type] = Data::Types.type_for(column[:type], local_types).meta[:database_type]
32
+ rescue Pakyow::UnknownType
33
+ end
34
+ end
35
+
36
+ super(column)
37
+ end
38
+
39
+ Sequel.extension :migration
40
+ Sequel::Migrator.run(
41
+ @connection.adapter.connection, @migration_path,
42
+ allow_missing_migration_files: true
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pakyow
4
+ module Data
5
+ module Adapters
6
+ class Sql
7
+ module SourceExtension
8
+ extend Support::Extension
9
+
10
+ private def build(string, *args)
11
+ Sequel.lit(string, *args)
12
+ end
13
+
14
+ apply_extension do
15
+ class_state :dataset_table, default: self.__object_name.name
16
+
17
+ class << self
18
+ def table(table_name)
19
+ @dataset_table = table_name
20
+ end
21
+
22
+ def default_primary_key_type
23
+ :bignum
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pakyow
4
+ module Data
5
+ module Adapters
6
+ class Sql
7
+ module Types
8
+ module Postgres
9
+ TYPES = {
10
+ bignum: Sql::TYPES[:bignum].meta(native_type: "bigint"),
11
+ decimal: Sql::TYPES[:decimal].meta(column_type: :decimal, native_type: ->(meta) { "numeric(#{meta[:size][0]},#{meta[:size][1]})" }),
12
+ integer: Sql::TYPES[:integer].meta(native_type: "integer"),
13
+ string: Sql::TYPES[:string].meta(native_type: "text"),
14
+ text: Sql::TYPES[:text].meta(column_type: :string),
15
+
16
+ json: Pakyow::Data::Types.Constructor(:json) { |value|
17
+ Sequel.pg_json(value)
18
+ }.meta(mapping: :json, database_type: :json)
19
+ }.freeze
20
+ end
21
+
22
+ module SQLite
23
+ TYPES = {
24
+ bignum: Sql::TYPES[:bignum].meta(native_type: "bigint"),
25
+ decimal: Sql::TYPES[:decimal].meta(column_type: :decimal, native_type: ->(meta) { "numeric(#{meta[:size][0]}, #{meta[:size][1]})" }),
26
+ integer: Sql::TYPES[:integer].meta(native_type: "integer"),
27
+ string: Sql::TYPES[:string].meta(native_type: "varchar(255)"),
28
+ text: Sql::TYPES[:text].meta(column_type: :string),
29
+
30
+ # Used indirectly for migrations to override the column type (since
31
+ # sqlite doesn't support bignum as a primary key).
32
+ #
33
+ pk_bignum: Sql::TYPES[:bignum].meta(column_type: :integer)
34
+ }.freeze
35
+ end
36
+
37
+ module MySQL
38
+ TYPES = {
39
+ bignum: Sql::TYPES[:bignum].meta(native_type: "bigint(20)"),
40
+ decimal: Sql::TYPES[:decimal].meta(column_type: :decimal, native_type: ->(meta) { "decimal(#{meta[:size][0]},#{meta[:size][1]})" }),
41
+ integer: Sql::TYPES[:integer].meta(native_type: "int(11)"),
42
+ string: Sql::TYPES[:string].meta(native_type: "varchar(255)"),
43
+ text: Sql::TYPES[:text].meta(column_type: :string)
44
+ }.freeze
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sequel"
4
+
5
+ require "pakyow/support/extension"
6
+
7
+ require "pakyow/support/core_refinements/string/normalization"
8
+
9
+ require "pakyow/data/adapters/abstract"
10
+
11
+ module Pakyow
12
+ module Data
13
+ module Adapters
14
+ class Sql < Abstract
15
+ require "pakyow/data/adapters/sql/commands"
16
+ require "pakyow/data/adapters/sql/dataset_methods"
17
+ require "pakyow/data/adapters/sql/migrator"
18
+ require "pakyow/data/adapters/sql/runner"
19
+ require "pakyow/data/adapters/sql/source_extension"
20
+
21
+ TYPES = {
22
+ # overrides for default types
23
+ boolean: Data::Types::MAPPING[:boolean].meta(default: false, database_type: :boolean, column_type: :boolean),
24
+ date: Data::Types::MAPPING[:date].meta(database_type: :date),
25
+ datetime: Data::Types::MAPPING[:datetime].meta(database_type: DateTime),
26
+ decimal: Data::Types::MAPPING[:decimal].meta(database_type: BigDecimal, size: [10, 2]),
27
+ float: Data::Types::MAPPING[:float].meta(database_type: :float),
28
+ integer: Data::Types::MAPPING[:integer].meta(database_type: Integer),
29
+ string: Data::Types::MAPPING[:string].meta(database_type: String),
30
+ time: Data::Types::MAPPING[:time].meta(database_type: Time, column_type: :datetime),
31
+
32
+ # sql-specific types
33
+ file: Types.Constructor(Sequel::SQL::Blob).meta(mapping: :file, database_type: File, column_type: :blob),
34
+ text: Types::Coercible::String.meta(mapping: :text, database_type: :text, column_type: :text, native_type: "text"),
35
+ bignum: Types::Coercible::Integer.meta(mapping: :bignum, database_type: :Bignum)
36
+ }.freeze
37
+
38
+ require "pakyow/data/adapters/sql/types"
39
+
40
+ attr_reader :connection
41
+
42
+ DEFAULT_EXTENSIONS = %i(
43
+ connection_validator
44
+ ).freeze
45
+
46
+ DEFAULT_ADAPTER_EXTENSIONS = {
47
+ postgres: %i(
48
+ pg_json
49
+ ).freeze
50
+ }.freeze
51
+
52
+ def initialize(opts, logger: nil)
53
+ @opts, @logger = opts, logger
54
+ connect
55
+ end
56
+
57
+ def qualify_attribute(attribute, source)
58
+ Sequel.qualify(source.class.dataset_table, attribute)
59
+ end
60
+
61
+ def alias_attribute(attribute, as)
62
+ Sequel.as(attribute, as)
63
+ end
64
+
65
+ def dataset_for_source(source)
66
+ @connection[source.dataset_table]
67
+ end
68
+
69
+ def result_for_attribute_value(attribute, value, source)
70
+ source.where(attribute => value)
71
+ end
72
+
73
+ def restrict_to_source(restrict_to_source, source, *additional_fields)
74
+ source.select.qualify(
75
+ restrict_to_source.class.dataset_table
76
+ ).select_append(
77
+ *additional_fields
78
+ )
79
+ end
80
+
81
+ def restrict_to_attribute(attribute, source)
82
+ source.select(*attribute)
83
+ end
84
+
85
+ def merge_results(left_column_name, right_column_name, mergeable_source, merge_into_source)
86
+ merge_into_source.tap do
87
+ merge_into_source.__setobj__(
88
+ merge_into_source.join(
89
+ mergeable_source.class.dataset_table, left_column_name => right_column_name
90
+ )
91
+ )
92
+ end
93
+ end
94
+
95
+ def transaction(&block)
96
+ @connection.transaction do
97
+ begin
98
+ block.call
99
+ rescue Rollback
100
+ raise Sequel::Rollback
101
+ end
102
+ end
103
+ end
104
+
105
+ def connect
106
+ @connection = Sequel.connect(
107
+ adapter: @opts[:adapter],
108
+ database: @opts[:path],
109
+ host: @opts[:host],
110
+ port: @opts[:port],
111
+ user: @opts[:user],
112
+ password: @opts[:password],
113
+ logger: @logger
114
+ )
115
+
116
+ (DEFAULT_EXTENSIONS + DEFAULT_ADAPTER_EXTENSIONS[@opts[:adapter].to_s.to_sym].to_a).each do |extension|
117
+ @connection.extension extension
118
+ end
119
+
120
+ if @opts.include?(:timeout)
121
+ @connection.pool.connection_validation_timeout = @opts[:timeout].to_i
122
+ end
123
+ rescue Sequel::AdapterNotFound => error
124
+ raise MissingAdapter.build(error)
125
+ rescue Sequel::DatabaseConnectionError => error
126
+ raise ConnectionError.build(error)
127
+ end
128
+
129
+ def disconnect
130
+ if connected?
131
+ @connection.disconnect
132
+ end
133
+ end
134
+
135
+ def connected?
136
+ @connection.opts[:adapter] == "sqlite" || @connection.test_connection
137
+ rescue
138
+ false
139
+ end
140
+
141
+ def migratable?
142
+ true
143
+ end
144
+
145
+ def auto_migratable?
146
+ true
147
+ end
148
+
149
+ def finalized_attribute(attribute)
150
+ if attribute.meta[:primary_key] || attribute.meta[:foreign_key]
151
+ begin
152
+ finalized_attribute = Data::Types.type_for(:"pk_#{attribute.meta[:mapping]}", Sql.types_for_adapter(@connection.opts[:adapter].to_sym)).dup
153
+
154
+ if attribute.meta[:primary_key]
155
+ finalized_attribute = finalized_attribute.meta(primary_key: attribute.meta[:primary_key])
156
+ end
157
+
158
+ if attribute.meta[:foreign_key]
159
+ finalized_attribute = finalized_attribute.meta(foreign_key: attribute.meta[:foreign_key])
160
+ end
161
+ rescue Pakyow::UnknownType
162
+ finalized_attribute = attribute.dup
163
+ end
164
+ else
165
+ finalized_attribute = attribute.dup
166
+ end
167
+
168
+ finalized_meta = finalized_attribute.meta.dup
169
+
170
+ if finalized_meta.include?(:mapping)
171
+ finalized_meta[:migration_type] = finalized_meta[:mapping]
172
+ end
173
+
174
+ unless finalized_meta.include?(:migration_type)
175
+ finalized_meta[:migration_type] = migration_type_for_attribute(attribute)
176
+ end
177
+
178
+ unless finalized_meta.include?(:column_type)
179
+ finalized_meta[:column_type] = column_type_for_attribute(attribute)
180
+ end
181
+
182
+ unless finalized_meta.include?(:database_type)
183
+ finalized_meta[:database_type] = database_type_for_attribute(attribute)
184
+ end
185
+
186
+ finalized_meta.each do |key, value|
187
+ finalized_meta[key] = case value
188
+ when Proc
189
+ if value.arity == 1
190
+ value.call(finalized_meta)
191
+ else
192
+ value.call
193
+ end
194
+ else
195
+ value
196
+ end
197
+ end
198
+
199
+ finalized_attribute.meta(finalized_meta)
200
+ end
201
+
202
+ private
203
+
204
+ def migration_type_for_attribute(attribute)
205
+ attribute.meta[:database_type] || attribute.primitive
206
+ end
207
+
208
+ def column_type_for_attribute(attribute)
209
+ attribute.primitive.to_s.downcase.to_sym
210
+ end
211
+
212
+ def database_type_for_attribute(attribute)
213
+ attribute.primitive
214
+ end
215
+
216
+ class << self
217
+ CONNECTION_TYPES = {
218
+ postgres: "Types::Postgres",
219
+ sqlite: "Types::SQLite",
220
+ mysql2: "Types::MySQL"
221
+ }.freeze
222
+
223
+ def types_for_adapter(adapter)
224
+ TYPES.dup.merge(const_get(CONNECTION_TYPES[adapter.to_sym])::TYPES)
225
+ end
226
+
227
+ using Support::Refinements::String::Normalization
228
+
229
+ def build_opts(opts)
230
+ database = if opts[:adapter] == "sqlite"
231
+ if opts[:host]
232
+ File.join(opts[:host], opts[:path])
233
+ else
234
+ opts[:path]
235
+ end
236
+ else
237
+ String.normalize_path(opts[:path])[1..-1]
238
+ end
239
+
240
+ opts[:path] = database
241
+ opts
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ module Pakyow
6
+ module Data
7
+ module Behavior
8
+ module Config
9
+ extend Support::Extension
10
+
11
+ apply_extension do
12
+ configurable :data do
13
+ configurable :subscriptions do
14
+ setting :adapter_settings, {}
15
+ setting :version
16
+
17
+ defaults :production do
18
+ setting :adapter_settings do
19
+ { key_prefix: [Pakyow.config.redis.key_prefix, config.name].join("/") }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ require "pakyow/data/container"
6
+ require "pakyow/data/lookup"
7
+ require "pakyow/data/subscribers"
8
+
9
+ module Pakyow
10
+ module Data
11
+ module Behavior
12
+ module Lookup
13
+ extend Support::Extension
14
+
15
+ # Data container object.
16
+ #
17
+ attr_reader :data
18
+
19
+ apply_extension do
20
+ after "boot", "initialize.data", priority: :high do
21
+ # Validate that each source connection exists.
22
+ #
23
+ state(:source).each do |source|
24
+ Pakyow.connection(source.adapter, source.connection)
25
+ end
26
+
27
+ subscribers = if is_a?(Plugin)
28
+ # Plugins should use the same subscribers object as their parent app.
29
+ #
30
+ parent.data.subscribers
31
+ else
32
+ Subscribers.new(
33
+ self,
34
+ Pakyow.config.data.subscriptions.adapter,
35
+ Pakyow.config.data.subscriptions.adapter_settings
36
+ )
37
+ end
38
+
39
+ containers = Pakyow.data_connections.values.each_with_object([]) { |connections, arr|
40
+ connections.values.each do |connection|
41
+ arr << Container.new(
42
+ connection: connection,
43
+ sources: state(:source).select { |source|
44
+ connection.name == source.connection && connection.type == source.adapter
45
+ },
46
+ objects: state(:object)
47
+ )
48
+ end
49
+ }
50
+
51
+ containers.each do |container|
52
+ container.finalize_associations!(containers - [container])
53
+ end
54
+
55
+ containers.each do |container|
56
+ container.finalize_sources!(containers - [container])
57
+ end
58
+
59
+ @data = Data::Lookup.new(
60
+ app: self,
61
+ containers: containers,
62
+ subscribers: subscribers
63
+ )
64
+ end
65
+
66
+ on "shutdown" do
67
+ if instance_variable_defined?(:@data)
68
+ @data.subscribers.shutdown
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+ require "pakyow/support/serializer"
5
+
6
+ module Pakyow
7
+ module Data
8
+ module Behavior
9
+ # Persists in-memory subscribers across restarts.
10
+ #
11
+ module Serialization
12
+ extend Support::Extension
13
+
14
+ apply_extension do
15
+ on "shutdown", priority: :high do
16
+ if Pakyow.config.data.subscriptions.adapter == :memory && data
17
+ subscriber_serializer.serialize
18
+ end
19
+ end
20
+
21
+ after "boot" do
22
+ if Pakyow.config.data.subscriptions.adapter == :memory && data
23
+ subscriber_serializer.deserialize
24
+ end
25
+ end
26
+ end
27
+
28
+ private def subscriber_serializer
29
+ Support::Serializer.new(
30
+ data.subscribers.adapter,
31
+ name: "#{config.name}-subscribers",
32
+ path: File.join(
33
+ Pakyow.config.root, "tmp", "state"
34
+ )
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "forwardable"
5
+
6
+ require "pakyow/support/class_state"
7
+ require "pakyow/support/deep_freeze"
8
+ require "pakyow/support/indifferentize"
9
+ require "pakyow/support/inflector"
10
+
11
+ module Pakyow
12
+ module Data
13
+ class Connection
14
+ extend Forwardable
15
+ def_delegators :@adapter, :dataset_for_source, :transaction
16
+
17
+ attr_reader :type, :name, :opts, :adapter, :failure
18
+
19
+ extend Support::DeepFreeze
20
+ unfreezable :logger, :adapter
21
+
22
+ def initialize(type:, name:, string: nil, opts: nil, logger: nil)
23
+ @type, @name, @logger, @failure = type, name, logger, nil
24
+
25
+ @opts = self.class.adapter(type).build_opts(
26
+ opts.is_a?(Hash) ? opts : self.class.parse_connection_string(string)
27
+ )
28
+
29
+ @adapter = self.class.adapter(@type).new(@opts, logger: @logger)
30
+ rescue ConnectionError, MissingAdapter => error
31
+ error.context = self
32
+ @failure = error
33
+ end
34
+
35
+ def connected?
36
+ !failed? && @adapter.connected?
37
+ end
38
+
39
+ def failed?
40
+ !@failure.nil?
41
+ end
42
+
43
+ def auto_migrate?
44
+ migratable? && @adapter.auto_migratable?
45
+ end
46
+
47
+ def migratable?
48
+ connected? && @adapter.migratable?
49
+ end
50
+
51
+ def connect
52
+ @adapter.connect
53
+ end
54
+
55
+ def disconnect
56
+ @adapter.disconnect
57
+ end
58
+
59
+ def types
60
+ if @adapter.class.const_defined?("TYPES")
61
+ @adapter.class.types_for_adapter(adapter.connection.opts[:adapter])
62
+ else
63
+ {}
64
+ end
65
+ end
66
+
67
+ extend Support::ClassState
68
+ class_state :adapter_types, default: []
69
+
70
+ using Support::Indifferentize
71
+
72
+ class << self
73
+ def parse_connection_string(connection_string)
74
+ uri = URI(connection_string)
75
+
76
+ {
77
+ adapter: uri.scheme,
78
+ path: uri.path,
79
+ host: uri.host,
80
+ port: uri.port,
81
+ user: uri.user,
82
+ password: uri.password
83
+ }.merge(
84
+ CGI::parse(uri.query.to_s).transform_values(&:first).indifferentize
85
+ )
86
+ end
87
+
88
+ def register_adapter(type)
89
+ (@adapter_types << type).uniq!
90
+ end
91
+
92
+ def adapter(type)
93
+ if @adapter_types.include?(type.to_sym)
94
+ require "pakyow/data/adapters/#{type}"
95
+ Adapters.const_get(Support.inflector.camelize(type))
96
+ else
97
+ raise UnknownAdapter.new_with_message(type: type)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end