wasmify-rails 0.1.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +7 -0
  3. data/LICENSE.txt +23 -0
  4. data/README.md +168 -0
  5. data/lib/active_record/connection_adapters/nulldb_adapter/checkpoint.rb +13 -0
  6. data/lib/active_record/connection_adapters/nulldb_adapter/column.rb +4 -0
  7. data/lib/active_record/connection_adapters/nulldb_adapter/configuration.rb +5 -0
  8. data/lib/active_record/connection_adapters/nulldb_adapter/core.rb +391 -0
  9. data/lib/active_record/connection_adapters/nulldb_adapter/empty_result.rb +41 -0
  10. data/lib/active_record/connection_adapters/nulldb_adapter/index_definition.rb +5 -0
  11. data/lib/active_record/connection_adapters/nulldb_adapter/null_object.rb +13 -0
  12. data/lib/active_record/connection_adapters/nulldb_adapter/quoting.rb +3 -0
  13. data/lib/active_record/connection_adapters/nulldb_adapter/statement.rb +15 -0
  14. data/lib/active_record/connection_adapters/nulldb_adapter/table_definition.rb +23 -0
  15. data/lib/active_record/connection_adapters/nulldb_adapter.rb +67 -0
  16. data/lib/active_record/connection_adapters/sqlite3_wasm_adapter.rb +163 -0
  17. data/lib/active_storage/null_delivery.rb +15 -0
  18. data/lib/generators/wasmify/install/USAGE +5 -0
  19. data/lib/generators/wasmify/install/install_generator.rb +73 -0
  20. data/lib/generators/wasmify/install/templates/config/environments/wasm.rb +30 -0
  21. data/lib/generators/wasmify/install/templates/config/wasmify.yml +17 -0
  22. data/lib/generators/wasmify/pwa/USAGE +5 -0
  23. data/lib/generators/wasmify/pwa/pwa_generator.rb +13 -0
  24. data/lib/generators/wasmify/pwa/templates/pwa/README.md +40 -0
  25. data/lib/generators/wasmify/pwa/templates/pwa/boot.html +102 -0
  26. data/lib/generators/wasmify/pwa/templates/pwa/boot.js +96 -0
  27. data/lib/generators/wasmify/pwa/templates/pwa/database.js +18 -0
  28. data/lib/generators/wasmify/pwa/templates/pwa/index.html +2 -0
  29. data/lib/generators/wasmify/pwa/templates/pwa/package.json.tt +20 -0
  30. data/lib/generators/wasmify/pwa/templates/pwa/rails.sw.js +115 -0
  31. data/lib/generators/wasmify/pwa/templates/pwa/vite.config.js +29 -0
  32. data/lib/image_processing/null.rb +22 -0
  33. data/lib/wasmify/rails/builder.rb +45 -0
  34. data/lib/wasmify/rails/configuration.rb +41 -0
  35. data/lib/wasmify/rails/packer.rb +60 -0
  36. data/lib/wasmify/rails/railtie.rb +11 -0
  37. data/lib/wasmify/rails/shim.rb +65 -0
  38. data/lib/wasmify/rails/shims/io/console/size.rb +0 -0
  39. data/lib/wasmify/rails/shims/io/console.rb +11 -0
  40. data/lib/wasmify/rails/shims/io/wait.rb +0 -0
  41. data/lib/wasmify/rails/shims/ipaddr.rb +7 -0
  42. data/lib/wasmify/rails/shims/nio.rb +0 -0
  43. data/lib/wasmify/rails/shims/nokogiri/nokogiri.rb +0 -0
  44. data/lib/wasmify/rails/shims/nokogiri.rb +0 -0
  45. data/lib/wasmify/rails/shims/rails-html-sanitizer.rb +163 -0
  46. data/lib/wasmify/rails/shims/socket.rb +21 -0
  47. data/lib/wasmify/rails/shims/sqlite3.rb +0 -0
  48. data/lib/wasmify/rails/tasks.rake +120 -0
  49. data/lib/wasmify/rails/version.rb +7 -0
  50. data/lib/wasmify/rails/wasmtimer.rb +31 -0
  51. data/lib/wasmify-rails.rb +25 -0
  52. metadata +200 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 442a1c699d7e906546d63b1d5c6c451652d65ecd651bd16fa72d98841a845acf
4
+ data.tar.gz: d8dac2abf684945cc8c9fbd10d3ed6c6236c9535c3f7c3974501638b7be6f607
5
+ SHA512:
6
+ metadata.gz: 14b9d7c161e577b80d5d9b92d6cb6a7294203f535f938423b194e4adf204541f0d93c4efa466847d2546bf8a919aa96c82037613fb31c7114f3fe1c4debaa5ee
7
+ data.tar.gz: 61c8ee0c9f26376f53e22bff246bba40c9d038094d846c17cc7b022f600344c70142777901d28d60eb41a5c8471c4dacbf6c188166a0aa76e1b88554c52900e9
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Change log
2
+
3
+ ## master
4
+
5
+ ## 0.1.0
6
+
7
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2024 Vladimir Dementyev
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
data/README.md ADDED
@@ -0,0 +1,168 @@
1
+ [![Gem Version](https://badge.fury.io/rb/wasmify-rails.svg)](https://rubygems.org/gems/wasmify-rails)
2
+
3
+ # Wasmify Rails
4
+
5
+ This gem provides tools and extensions to compile Rails applications to WebAssembly.
6
+
7
+ ## Installation
8
+
9
+ Adding to your Gemfile:
10
+
11
+ ```ruby
12
+ # Gemfile
13
+ gem "wasmify-rails", group: [:development, :wasm]
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ This gem comes with a few commands (Rake tasks) to help you get started with packing your Rails app into a Wasm module.
19
+
20
+ ### Step 1: `bin/rails wasmify:install`
21
+
22
+ Run the following command to preconfigure your project for _wasmification_:
23
+
24
+ ```sh
25
+ bin/rails wasmify:install
26
+ ```
27
+
28
+ The script will tweak you configuration files and create a couple of new ones:
29
+
30
+ - `config/environments/wasm.rb` — a dedicated Rails environment based on the `production` environment to be used in Wasm builds.
31
+
32
+ - `config/wasmify.yml` — this is a configuration file for different _wasmify_ commands (see below).
33
+
34
+ ### Step 2: `bin/rails wasmify:build:core`
35
+
36
+ Now, it's time to build a ruby.wasm with all your project dependencies (from the Gemfile). This is gonna be a base Wasm module
37
+ for your project but not including the project files.
38
+
39
+ Prior to running the build command, you MUST update your Gemfile to mark the gems that you need in the Wasm environment
40
+ with the `:wasm` group. For example:
41
+
42
+ ```ruby
43
+ # Rails is required.
44
+ # NOTE: don't forget about the default group — you need it for the regular Rails environments.
45
+ gem "rails", group: [:default, :wasm]
46
+
47
+ # We don't need Puma though
48
+ gem "puma"
49
+
50
+ # You can also use group ... do ... end syntax
51
+ group :default, :wasm do
52
+ gem "propshaft"
53
+ gem "importmap-rails"
54
+ gem "turbo-rails"
55
+ gem "stimulus-rails"
56
+ end
57
+ ...
58
+ ```
59
+
60
+ **NOTE:** If you use `ruby file: ".ruby-version"` in your Gemfile, you should probably configure the Ruby version for Wasm platform
61
+ a bit differently (since patch versions might not match). For example:
62
+
63
+ ```ruby
64
+ if RUBY_PLATFORM =~ /wasm/
65
+ ruby "3.3.3"
66
+ else
67
+ ruby file: ".ruby-version"
68
+ end
69
+ ```
70
+
71
+ Now, try to run the following command:
72
+
73
+ ```sh
74
+ $ bin/rails wasmify:build:core
75
+
76
+ ...
77
+ INFO: Packaging gem: rails-7.2.1
78
+ ...
79
+ INFO: Size: 77.92 MB
80
+ ```
81
+
82
+ If it succeeds then you're good to go further. However, 99% that it will fail with some compilation error.
83
+ You must identify the gem that caused the problem (usually, it happens when we failed to compile a C extension to Wasm—not every C extension is Wasm-compilable).
84
+ Then, you can either update your Gemfile to exclude the problematic gem from the Wasm group or you can update your `config/wasmify.yml` and
85
+ add the gem to the `exclude_gems` list.
86
+
87
+ Repeat until the `bin/rails wasmify:build:core` succeeds.
88
+
89
+ You can also verify that the resulting Wasm module works by running:
90
+
91
+ ```sh
92
+ $ bin/rails wasmify:build:core:verify
93
+
94
+ Your Rails version is: 7.2.1 [wasm32-wasi]
95
+ ```
96
+
97
+ If this command fails, try to iterate on gem exclusions and rebuild the core module.
98
+
99
+ ### Step 3: `bin/rails wasmify:pack:core`
100
+
101
+ Now, we're ready to pack the whole application into a single Wasm module.
102
+ For that, run the following command:
103
+
104
+ ```sh
105
+ $ bin/rails wasmify:pack:core
106
+
107
+ Packed the application to tmp/wasmify/app-core.wasm
108
+ Size: 103 MB
109
+ ```
110
+
111
+ That should succeeds given that the previous step was successful.
112
+
113
+ Now, let's try to boot the application and see if it works:
114
+
115
+ ```sh
116
+ $ bin/rails wasmify:pack:core:verify
117
+
118
+ Initializing Rails application...
119
+ Rails application initialized!
120
+ ```
121
+
122
+ If the verification passes, you can proceed to the final step — building the Wasm module to be used on the web.
123
+ If it fails, check out the error message and try to fix the issues (usually, configuration related).
124
+
125
+ ### Step 4: `bin/rails wasmify:pwa`
126
+
127
+ We're ready to launch our Rails application fully within a browser!
128
+
129
+ For that, you can use our starter Vite PWA application that can be generated via the following command:
130
+
131
+ ```sh
132
+ bin/rails wasmify:pwa
133
+ ```
134
+
135
+ Then, update your `config/wasmify.yml` to specify the path to the PWA app as the output:
136
+
137
+ ```yml
138
+ output_dir: "pwa"
139
+ # ...
140
+ ```
141
+
142
+ Now, create the final Wasm module:
143
+
144
+ ```sh
145
+ bin/rails wasmify:pack
146
+ ```
147
+
148
+ And go to the `pwa` for the instructions on how to launch the application.
149
+
150
+ ## Roadmap
151
+
152
+ - PGLite support (see [this example](https://github.com/kateinoigakukun/mastodon/blob/fff2e4a626a20a616c546ddf4f91766abaf1133a/pwa/dist/pglite.rb#L1))
153
+ - Active Storage OPFS service
154
+ - File uploads support (multipart/form-data)
155
+ - Background jobs support
156
+ - WASI Preview 2 support (also [this](https://github.com/kateinoigakukun/mastodon/tree/katei/wasmify))
157
+
158
+ ## Contributing
159
+
160
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/](https://github.com/).
161
+
162
+ ## Credits
163
+
164
+ The `nulldb` adapter for Active Record (used for tests) is adopted from [this project](https://github.com/nulldb/nulldb).
165
+
166
+ ## License
167
+
168
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,13 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+
3
+ class Checkpoint < Statement
4
+ def initialize
5
+ super(:checkpoint, "")
6
+ end
7
+
8
+ def ==(other)
9
+ self.class == other.class
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,4 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+ class Column < ::ActiveRecord::ConnectionAdapters::Column
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+
3
+ class Configuration < Struct.new(:project_root); end
4
+
5
+ end
@@ -0,0 +1,391 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
2
+
3
+ # A convenience method for integratinginto RSpec. See README for example of
4
+ # use.
5
+ def self.insinuate_into_spec(config)
6
+ config.before :all do
7
+ ActiveRecord::Base.establish_connection(:adapter => :nulldb)
8
+ end
9
+
10
+ config.after :all do
11
+ ActiveRecord::Base.establish_connection(:test)
12
+ end
13
+ end
14
+
15
+ # Recognized options:
16
+ #
17
+ # [+:schema+] path to the schema file, relative to Rails.root
18
+ # [+:table_definition_class_name+] table definition class
19
+ # (e.g. ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition for Postgres) or nil.
20
+ def initialize(config={})
21
+ @log = StringIO.new
22
+ @logger = Logger.new(@log)
23
+ @last_unique_id = 0
24
+ @tables = {'schema_info' => new_table_definition(nil)}
25
+ @indexes = Hash.new { |hash, key| hash[key] = [] }
26
+ @schema_path = config.fetch(:schema){ "db/schema.rb" }
27
+ @config = config.merge(:adapter => :nulldb)
28
+ super *initialize_args
29
+ @visitor ||= Arel::Visitors::ToSql.new self if defined?(Arel::Visitors::ToSql)
30
+
31
+ if config[:table_definition_class_name]
32
+ ActiveRecord::ConnectionAdapters::NullDBAdapter.send(:remove_const, 'TableDefinition')
33
+ ActiveRecord::ConnectionAdapters::NullDBAdapter.const_set('TableDefinition',
34
+ self.class.const_get(config[:table_definition_class_name]))
35
+ end
36
+
37
+ register_types
38
+ end
39
+
40
+ # A log of every statement that has been "executed" by this connection adapter
41
+ # instance.
42
+ def execution_log
43
+ (@execution_log ||= [])
44
+ end
45
+
46
+ # A log of every statement that has been "executed" since the last time
47
+ # #checkpoint! was called, or since the connection was created.
48
+ def execution_log_since_checkpoint
49
+ checkpoint_index = @execution_log.rindex(Checkpoint.new)
50
+ checkpoint_index = checkpoint_index ? checkpoint_index + 1 : 0
51
+ @execution_log[(checkpoint_index..-1)]
52
+ end
53
+
54
+ # Inserts a checkpoint in the log. See also #execution_log_since_checkpoint.
55
+ def checkpoint!
56
+ self.execution_log << Checkpoint.new
57
+ end
58
+
59
+ def adapter_name
60
+ "NullDB"
61
+ end
62
+
63
+ def supports_migrations?
64
+ true
65
+ end
66
+
67
+ def connect
68
+ end
69
+
70
+ def reconnect
71
+ end
72
+
73
+ def create_table(table_name, options = {})
74
+ table_definition = new_table_definition(self, table_name, options.delete(:temporary), options)
75
+
76
+ unless options[:id] == false
77
+ table_definition.primary_key(options[:primary_key] || "id")
78
+ end
79
+
80
+ yield table_definition if block_given?
81
+
82
+ @tables[table_name.to_s] = table_definition
83
+ end
84
+
85
+ def rename_table(table_name, new_name)
86
+ table_definition = @tables.delete(table_name.to_s)
87
+
88
+ table_definition.name = new_name.to_s
89
+ @tables[new_name.to_s] = table_definition
90
+ end
91
+
92
+ def add_index(table_name, column_names, **options)
93
+ options[:unique] = false unless options.key?(:unique)
94
+ column_names = Array.wrap(column_names).map(&:to_s)
95
+
96
+ index, index_type, ignore = add_index_options(table_name, column_names, **options)
97
+
98
+ if index.is_a?(ActiveRecord::ConnectionAdapters::IndexDefinition)
99
+ @indexes[table_name] << index
100
+ else
101
+ # Rails < 6.1
102
+ @indexes[table_name] << IndexDefinition.new(table_name, index, (index_type == 'UNIQUE'), column_names, [], [])
103
+ end
104
+ end
105
+
106
+ # Rails 6.1+
107
+ if ActiveRecord::VERSION::MAJOR >= 7 || (ActiveRecord::VERSION::MAJOR >= 6 and ActiveRecord::VERSION::MINOR > 0)
108
+ def remove_index(table_name, column_name = nil, **options )
109
+ index_name = index_name_for_remove(table_name, column_name, options)
110
+ index = @indexes[table_name].reject! { |index| index.name == index_name }
111
+ end
112
+ else
113
+ def remove_index(table_name, options = {} )
114
+ index_name = index_name_for_remove(table_name, options)
115
+ index = @indexes[table_name].reject! { |index| index.name == index_name }
116
+ end
117
+ end
118
+
119
+ def add_fk_constraint(*args)
120
+ # NOOP
121
+ end
122
+
123
+ def add_pk_constraint(*args)
124
+ # NOOP
125
+ end
126
+
127
+ def enable_extension(*)
128
+ # NOOP
129
+ end
130
+
131
+ # Retrieve the table names defined by the schema
132
+ def tables
133
+ @tables.keys.map(&:to_s)
134
+ end
135
+
136
+ def views
137
+ [] # TODO: Implement properly if needed - This is new method in rails
138
+ end
139
+
140
+ # Retrieve table columns as defined by the schema
141
+ def columns(table_name, name = nil)
142
+ if @tables.size <= 1
143
+ ActiveRecord::Migration.verbose = false
144
+ schema_path = if Pathname(@schema_path).absolute?
145
+ @schema_path
146
+ else
147
+ File.join(NullDB.configuration.project_root, @schema_path)
148
+ end
149
+ Kernel.load(schema_path)
150
+ end
151
+
152
+ if table = @tables[table_name]
153
+ table.columns.map do |col_def|
154
+ col_args = default_column_arguments(col_def)
155
+ ActiveRecord::ConnectionAdapters::NullDBAdapter::Column.new(*col_args)
156
+ end
157
+ else
158
+ []
159
+ end
160
+ end
161
+
162
+ # Retrieve table indexes as defined by the schema
163
+ def indexes(table_name, name = nil)
164
+ @indexes[table_name]
165
+ end
166
+
167
+ def execute(statement, name = nil)
168
+ self.execution_log << Statement.new(entry_point, statement)
169
+ NullObject.new
170
+ end
171
+
172
+ def exec_query(statement, name = 'SQL', binds = [], options = {})
173
+ internal_exec_query(statement, name, binds, **options)
174
+ end
175
+
176
+ def internal_exec_query(statement, name = 'SQL', binds = [], prepare: false, async: false)
177
+ self.execution_log << Statement.new(entry_point, statement)
178
+ EmptyResult.new
179
+ end
180
+
181
+ def select_rows(statement, name = nil, binds = [], async: false)
182
+ [].tap do
183
+ self.execution_log << Statement.new(entry_point, statement)
184
+ end
185
+ end
186
+
187
+ def insert(statement, name = nil, primary_key = nil, object_id = nil, sequence_name = nil, binds = [], returning: nil)
188
+ with_entry_point(:insert) do
189
+ super(statement, name, primary_key, object_id, sequence_name)
190
+ end
191
+
192
+ result = object_id || next_unique_id
193
+
194
+ returning ? [result] : result
195
+ end
196
+ alias :create :insert
197
+
198
+ def update(statement, name=nil, binds = [])
199
+ with_entry_point(:update) do
200
+ super(statement, name)
201
+ end
202
+ end
203
+
204
+ def delete(statement, name=nil, binds = [])
205
+ with_entry_point(:delete) do
206
+ super(statement, name).size
207
+ end
208
+ end
209
+
210
+ def select_all(statement, name=nil, binds = [], options = {})
211
+ with_entry_point(:select_all) do
212
+ super(statement, name)
213
+ end
214
+ end
215
+
216
+ def select_one(statement, name=nil, binds = [])
217
+ with_entry_point(:select_one) do
218
+ super(statement, name)
219
+ end
220
+ end
221
+
222
+ def select_value(statement, name=nil, binds = [])
223
+ with_entry_point(:select_value) do
224
+ super(statement, name)
225
+ end
226
+ end
227
+
228
+ def select_values(statement, name=nil)
229
+ with_entry_point(:select_values) do
230
+ super(statement, name)
231
+ end
232
+ end
233
+
234
+ def primary_key(table_name)
235
+ columns(table_name).detect { |col| col.type == :primary_key }.try(:name)
236
+ end
237
+
238
+ def add_column(table_name, column_name, type, **options)
239
+ super
240
+
241
+ table_meta = @tables[table_name.to_s]
242
+ return unless table_meta
243
+
244
+ table_meta.column column_name, type, **options
245
+ end
246
+
247
+ def change_column(table_name, column_name, type, options = {})
248
+ table_meta = @tables[table_name.to_s]
249
+ column = table_meta.columns.find { |column| column.name == column_name.to_s }
250
+ return unless column
251
+
252
+ column.type = type
253
+ column.options = options if options
254
+ end
255
+
256
+ def rename_column(table_name, column_name, new_column_name)
257
+ table_meta = @tables[table_name.to_s]
258
+ column = table_meta.columns.find { |column| column.name == column_name.to_s }
259
+ return unless column
260
+
261
+ column.name = new_column_name
262
+ end
263
+
264
+ def change_column_default(table_name, column_name, default_or_changes)
265
+ table_meta = @tables[table_name.to_s]
266
+ column = table_meta.columns.find { |column| column.name == column_name.to_s }
267
+
268
+ return unless column
269
+
270
+ if default_or_changes.kind_of? Hash
271
+ column.default = default_or_changes[:to]
272
+ else
273
+ column.default = default_or_changes
274
+ end
275
+ end
276
+
277
+ protected
278
+
279
+ def select(statement, name = nil, binds = [], prepare: nil, async: nil, **)
280
+ EmptyResult.new.tap do |r|
281
+ r.bind_column_meta(columns_for(name))
282
+ self.execution_log << Statement.new(entry_point, statement)
283
+ end
284
+ end
285
+
286
+ private
287
+
288
+ def columns_for(table_name)
289
+ table_meta = @tables[table_name]
290
+ return [] unless table_meta
291
+ table_meta.columns
292
+ end
293
+
294
+ def next_unique_id
295
+ @last_unique_id += 1
296
+ end
297
+
298
+ def with_entry_point(method)
299
+ if entry_point.nil?
300
+ with_thread_local_variable(:entry_point, method) do
301
+ yield
302
+ end
303
+ else
304
+ yield
305
+ end
306
+ end
307
+
308
+ def entry_point
309
+ Thread.current[:entry_point]
310
+ end
311
+
312
+ def with_thread_local_variable(name, value)
313
+ old_value = Thread.current[name]
314
+ Thread.current[name] = value
315
+ begin
316
+ yield
317
+ ensure
318
+ Thread.current[name] = old_value
319
+ end
320
+ end
321
+
322
+ def includes_column?
323
+ false
324
+ end
325
+
326
+ def new_table_definition(adapter = nil, table_name = nil, is_temporary = nil, options = {})
327
+ case ::ActiveRecord::VERSION::MAJOR
328
+ when 6, 7
329
+ TableDefinition.new(self, table_name, temporary: is_temporary, options: options.except(:id))
330
+ when 5
331
+ TableDefinition.new(table_name, is_temporary, options.except(:id), nil)
332
+ else
333
+ raise "Unsupported ActiveRecord version #{::ActiveRecord::VERSION::STRING}"
334
+ end
335
+ end
336
+
337
+ def default_column_arguments(col_def)
338
+ [
339
+ col_def.name.to_s,
340
+ col_def.default.present? ? col_def.default.to_s : nil,
341
+ sql_type_definition(col_def),
342
+ col_def.null.nil? || col_def.null
343
+ ]
344
+ end
345
+
346
+ def sql_type_definition(col_def)
347
+ ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(
348
+ type: col_def.type,
349
+ sql_type: col_def.type.to_s,
350
+ limit: col_def.limit
351
+ )
352
+ end
353
+
354
+ def initialize_args
355
+ [nil, @logger, @config]
356
+ end
357
+
358
+ # Register types only once to avoid ActiveRecord::TypeConflictError
359
+ # in ActiveRecord::Type::Registration#<=>
360
+ REGISTRATION_MUTEX = Mutex.new
361
+
362
+ def register_types
363
+ REGISTRATION_MUTEX.synchronize do
364
+ return if self.class.types_registered
365
+
366
+ self.class.types_registered = true
367
+ end
368
+
369
+ ActiveRecord::Type.register(
370
+ :primary_key,
371
+ ActiveModel::Type::Integer,
372
+ adapter: adapter_name,
373
+ override: true
374
+ )
375
+
376
+ ActiveRecord::Type.add_modifier({ array: true }, DummyOID, adapter: :nulldb)
377
+ ActiveRecord::Type.add_modifier({ range: true }, DummyOID, adapter: :nulldb)
378
+ end
379
+
380
+ class << self
381
+ attr_accessor :types_registered
382
+ end
383
+
384
+ class DummyOID < ActiveModel::Type::Value
385
+ attr_reader :subtype
386
+
387
+ def initialize(*args)
388
+ @subtype = args.first
389
+ end
390
+ end
391
+ end
@@ -0,0 +1,41 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+
3
+ class EmptyResult < Array
4
+ attr_reader :column_types
5
+
6
+ def bind_column_meta(columns)
7
+ @columns = columns
8
+ return if columns.empty?
9
+
10
+ @column_types = columns.reduce({}) do |ctypes, col|
11
+ ctypes[col.name] = ActiveRecord::Type.lookup(col.type)
12
+ ctypes
13
+ end
14
+ end
15
+
16
+ def columns
17
+ @columns ||= []
18
+ end
19
+
20
+ def column_types
21
+ @column_types ||= {}
22
+ end
23
+
24
+ def cast_values(type_overrides = nil)
25
+ rows
26
+ end
27
+
28
+ def rows
29
+ []
30
+ end
31
+
32
+ def >(num)
33
+ rows.size > num
34
+ end
35
+
36
+ def includes_column?(name)
37
+ false
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,5 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+
3
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders); end
4
+
5
+ end
@@ -0,0 +1,13 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+
3
+ class NullObject
4
+ def method_missing(*args, &block)
5
+ nil
6
+ end
7
+
8
+ def to_a
9
+ []
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,3 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+ def self.quote_column_name(name) = %Q("#{name.to_s.gsub('"', '""')}")
3
+ end