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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE +4 -0
- data/README.md +29 -0
- data/lib/pakyow/data/adapters/abstract.rb +58 -0
- data/lib/pakyow/data/adapters/sql/commands.rb +58 -0
- data/lib/pakyow/data/adapters/sql/dataset_methods.rb +29 -0
- data/lib/pakyow/data/adapters/sql/differ.rb +76 -0
- data/lib/pakyow/data/adapters/sql/migrator/adapter_methods.rb +95 -0
- data/lib/pakyow/data/adapters/sql/migrator.rb +181 -0
- data/lib/pakyow/data/adapters/sql/migrators/automator.rb +49 -0
- data/lib/pakyow/data/adapters/sql/migrators/finalizer.rb +96 -0
- data/lib/pakyow/data/adapters/sql/runner.rb +49 -0
- data/lib/pakyow/data/adapters/sql/source_extension.rb +31 -0
- data/lib/pakyow/data/adapters/sql/types.rb +50 -0
- data/lib/pakyow/data/adapters/sql.rb +247 -0
- data/lib/pakyow/data/behavior/config.rb +28 -0
- data/lib/pakyow/data/behavior/lookup.rb +75 -0
- data/lib/pakyow/data/behavior/serialization.rb +40 -0
- data/lib/pakyow/data/connection.rb +103 -0
- data/lib/pakyow/data/container.rb +273 -0
- data/lib/pakyow/data/errors.rb +169 -0
- data/lib/pakyow/data/framework.rb +42 -0
- data/lib/pakyow/data/helpers.rb +11 -0
- data/lib/pakyow/data/lookup.rb +85 -0
- data/lib/pakyow/data/migrator.rb +182 -0
- data/lib/pakyow/data/object.rb +98 -0
- data/lib/pakyow/data/proxy.rb +262 -0
- data/lib/pakyow/data/result.rb +53 -0
- data/lib/pakyow/data/sources/abstract.rb +82 -0
- data/lib/pakyow/data/sources/ephemeral.rb +72 -0
- data/lib/pakyow/data/sources/relational/association.rb +43 -0
- data/lib/pakyow/data/sources/relational/associations/belongs_to.rb +47 -0
- data/lib/pakyow/data/sources/relational/associations/has_many.rb +54 -0
- data/lib/pakyow/data/sources/relational/associations/has_one.rb +54 -0
- data/lib/pakyow/data/sources/relational/associations/through.rb +67 -0
- data/lib/pakyow/data/sources/relational/command.rb +531 -0
- data/lib/pakyow/data/sources/relational/migrator.rb +101 -0
- data/lib/pakyow/data/sources/relational.rb +587 -0
- data/lib/pakyow/data/subscribers/adapters/memory.rb +153 -0
- data/lib/pakyow/data/subscribers/adapters/redis/pipeliner.rb +45 -0
- data/lib/pakyow/data/subscribers/adapters/redis/scripts/_shared.lua +73 -0
- data/lib/pakyow/data/subscribers/adapters/redis/scripts/expire.lua +16 -0
- data/lib/pakyow/data/subscribers/adapters/redis/scripts/persist.lua +15 -0
- data/lib/pakyow/data/subscribers/adapters/redis/scripts/register.lua +37 -0
- data/lib/pakyow/data/subscribers/adapters/redis.rb +209 -0
- data/lib/pakyow/data/subscribers.rb +148 -0
- data/lib/pakyow/data/tasks/bootstrap.rake +18 -0
- data/lib/pakyow/data/tasks/create.rake +22 -0
- data/lib/pakyow/data/tasks/drop.rake +32 -0
- data/lib/pakyow/data/tasks/finalize.rake +56 -0
- data/lib/pakyow/data/tasks/migrate.rake +24 -0
- data/lib/pakyow/data/tasks/reset.rake +18 -0
- data/lib/pakyow/data/types.rb +37 -0
- data/lib/pakyow/data.rb +27 -0
- data/lib/pakyow/environment/data/auto_migrate.rb +31 -0
- data/lib/pakyow/environment/data/config.rb +54 -0
- data/lib/pakyow/environment/data/connections.rb +76 -0
- data/lib/pakyow/environment/data/memory_db.rb +23 -0
- data/lib/pakyow/validations/unique.rb +26 -0
- metadata +186 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 48569dc1d1d2f24fdf3a94b7b642ac33ecf1f80ed5fd2ae336300815eccc59ff
|
4
|
+
data.tar.gz: b4fae401a84c092cdd0fa5df7564016a2f9209b6c53c81d408b8f5a3ae96900e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 683e102bdc37a1806c78f181cdca846015d82277e3580ade5347f91a5f2182ba1e12f6cd48d1e2bd0184ce3fa7a96b9862a067924e0515f61319a4e2228f1efa
|
7
|
+
data.tar.gz: feeafc96cc49182ad4ad8819c628429fae848818fd284ade6906cdd34c32f0f8e1883cdcb72c23f037ebd86a70081a4f4f506ce430a7b5bd086cdab74077e0e7
|
data/CHANGELOG.md
ADDED
File without changes
|
data/LICENSE
ADDED
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# pakyow-data
|
2
|
+
|
3
|
+
Data layer for Pakyow.
|
4
|
+
|
5
|
+
# Download
|
6
|
+
|
7
|
+
The latest version of Pakyow Data can be installed with RubyGems:
|
8
|
+
|
9
|
+
```
|
10
|
+
gem install pakyow-data
|
11
|
+
```
|
12
|
+
|
13
|
+
Source code can be downloaded as part of the Pakyow project on Github:
|
14
|
+
|
15
|
+
- https://github.com/pakyow/pakyow/tree/master/pakyow-data
|
16
|
+
|
17
|
+
# License
|
18
|
+
|
19
|
+
Pakyow Data is free and open-source under the [LGPLv3 license](https://choosealicense.com/licenses/lgpl-3.0/).
|
20
|
+
|
21
|
+
# Support
|
22
|
+
|
23
|
+
Found a bug? Tell us about it here:
|
24
|
+
|
25
|
+
- https://github.com/pakyow/pakyow/issues
|
26
|
+
|
27
|
+
We'd love to have you in the community:
|
28
|
+
|
29
|
+
- http://pakyow.org/get-involved
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Data
|
5
|
+
module Adapters
|
6
|
+
class Abstract
|
7
|
+
def initialize(opts, logger: nil)
|
8
|
+
@opts, @logger = opts, logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def dataset_for_source(_source)
|
12
|
+
raise "dataset_for_source is not implemented on #{self}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def result_for_attribute_value(_attribute, _value, _source)
|
16
|
+
raise "result_for_attribute_value is not implemented on #{self}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def transaction
|
20
|
+
raise "transactions are not supported by #{self}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def connected?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def migratable?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def types_for_adapter(_adapter)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module SourceExtension
|
37
|
+
end
|
38
|
+
|
39
|
+
module Commands
|
40
|
+
end
|
41
|
+
|
42
|
+
module DatasetMethods
|
43
|
+
def to_a(_dataset)
|
44
|
+
raise "to_a is not implemented on #{self}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def one(_dataset)
|
48
|
+
raise "one is not implemented on #{self}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def count(_dataset)
|
52
|
+
raise "count is not implemented on #{self}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Data
|
5
|
+
module Adapters
|
6
|
+
class Sql
|
7
|
+
module Commands
|
8
|
+
extend Support::Extension
|
9
|
+
|
10
|
+
apply_extension do
|
11
|
+
command :create, performs_create: true do |values|
|
12
|
+
begin
|
13
|
+
inserted_return_value = insert(values)
|
14
|
+
if self.class.primary_key_field
|
15
|
+
if Migrator::AUTO_INCREMENTING_TYPES.include?(self.class.primary_key_type)
|
16
|
+
where(self.class.primary_key_field => inserted_return_value)
|
17
|
+
else
|
18
|
+
where(self.class.primary_key_field => values[self.class.primary_key_field])
|
19
|
+
end
|
20
|
+
else
|
21
|
+
where(values)
|
22
|
+
end
|
23
|
+
rescue Sequel::UniqueConstraintViolation => error
|
24
|
+
raise UniqueViolation.build(error)
|
25
|
+
rescue Sequel::ForeignKeyConstraintViolation => error
|
26
|
+
raise ConstraintViolation.build(error)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
command :update, performs_update: true do |values|
|
31
|
+
__getobj__.select(self.class.primary_key_field).map { |result|
|
32
|
+
result[self.class.primary_key_field]
|
33
|
+
}.tap do
|
34
|
+
begin
|
35
|
+
unless values.empty?
|
36
|
+
update(values)
|
37
|
+
end
|
38
|
+
rescue Sequel::UniqueConstraintViolation => error
|
39
|
+
raise UniqueViolation.build(error)
|
40
|
+
rescue Sequel::ForeignKeyConstraintViolation => error
|
41
|
+
raise ConstraintViolation.build(error)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
command :delete, provides_dataset: false, performs_delete: true do
|
47
|
+
begin
|
48
|
+
delete
|
49
|
+
rescue Sequel::ForeignKeyConstraintViolation => error
|
50
|
+
raise ConstraintViolation.build(error)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Data
|
5
|
+
module Adapters
|
6
|
+
class Sql
|
7
|
+
module DatasetMethods
|
8
|
+
def to_a(dataset)
|
9
|
+
dataset.qualify.all
|
10
|
+
rescue Sequel::Error => error
|
11
|
+
raise QueryError.build(error)
|
12
|
+
end
|
13
|
+
|
14
|
+
def one(dataset)
|
15
|
+
dataset.qualify.first
|
16
|
+
rescue Sequel::Error => error
|
17
|
+
raise QueryError.build(error)
|
18
|
+
end
|
19
|
+
|
20
|
+
def count(dataset)
|
21
|
+
dataset.qualify.count
|
22
|
+
rescue Sequel::Error => error
|
23
|
+
raise QueryError.build(error)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Data
|
5
|
+
module Adapters
|
6
|
+
class Sql
|
7
|
+
class Differ
|
8
|
+
def initialize(connection:, source:, attributes: source.attributes)
|
9
|
+
@connection, @source, @attributes = connection, source, attributes
|
10
|
+
end
|
11
|
+
|
12
|
+
def exists?
|
13
|
+
raw_connection.table_exists?(table_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def changes?
|
17
|
+
attributes_to_add.any? || columns_to_remove.any? || column_types_to_change.any?
|
18
|
+
end
|
19
|
+
|
20
|
+
def table_name
|
21
|
+
@source.dataset_table
|
22
|
+
end
|
23
|
+
|
24
|
+
def attributes
|
25
|
+
Hash[@attributes.map { |attribute_name, attribute|
|
26
|
+
[attribute_name, @connection.adapter.finalized_attribute(attribute)]
|
27
|
+
}]
|
28
|
+
end
|
29
|
+
|
30
|
+
def attributes_to_add
|
31
|
+
{}.tap { |attributes|
|
32
|
+
self.attributes.each do |attribute_name, attribute_type|
|
33
|
+
unless schema.find { |column| column[0] == attribute_name }
|
34
|
+
attributes[attribute_name] = attribute_type
|
35
|
+
end
|
36
|
+
end
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def columns_to_remove
|
41
|
+
{}.tap { |columns|
|
42
|
+
schema.each do |column_name, column_info|
|
43
|
+
unless @source.attributes.keys.find { |attribute_name| attribute_name == column_name }
|
44
|
+
columns[column_name] = column_info
|
45
|
+
end
|
46
|
+
end
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def column_types_to_change
|
51
|
+
{}.tap { |attributes|
|
52
|
+
self.attributes.each do |attribute_name, attribute_type|
|
53
|
+
if found_column = schema.find { |column| column[0] == attribute_name }
|
54
|
+
column_name, column_info = found_column
|
55
|
+
unless column_info[:type] == attribute_type.meta[:column_type] && (!attribute_type.meta.include?(:native_type) || column_info[:db_type] == attribute_type.meta[:native_type])
|
56
|
+
attributes[column_name] = attribute_type.meta[:migration_type]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def raw_connection
|
66
|
+
@connection.adapter.connection
|
67
|
+
end
|
68
|
+
|
69
|
+
def schema
|
70
|
+
raw_connection.schema(table_name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Data
|
5
|
+
module Adapters
|
6
|
+
class Sql
|
7
|
+
class Migrator
|
8
|
+
module AdapterMethods
|
9
|
+
module Mysql
|
10
|
+
def create!
|
11
|
+
handle_error do
|
12
|
+
@connection.adapter.connection.run("CREATE DATABASE `#{database}`")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def drop!
|
17
|
+
handle_error do
|
18
|
+
@connection.adapter.connection.run("DROP DATABASE `#{database}`")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.globalize_connection_opts!(connection_opts)
|
23
|
+
connection_opts[:initial] = Sql.build_opts(path: connection_opts[:path])
|
24
|
+
connection_opts[:path] = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
private def database
|
28
|
+
if @connection.opts.key?(:initial)
|
29
|
+
@connection.opts[:initial][:path]
|
30
|
+
else
|
31
|
+
@connection.opts[:path]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module Postgres
|
37
|
+
def create!
|
38
|
+
handle_error do
|
39
|
+
@connection.adapter.connection.run("CREATE DATABASE \"#{database}\"")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def drop!
|
44
|
+
handle_error do
|
45
|
+
@connection.adapter.connection.run <<~SQL
|
46
|
+
SELECT
|
47
|
+
pg_terminate_backend(pid)
|
48
|
+
FROM
|
49
|
+
pg_stat_activity
|
50
|
+
WHERE
|
51
|
+
-- don't kill my own connection!
|
52
|
+
pid <> pg_backend_pid()
|
53
|
+
-- don't kill the connections to other databases
|
54
|
+
AND datname = '#{@connection.opts[:path]}';
|
55
|
+
SQL
|
56
|
+
|
57
|
+
@connection.adapter.connection.run("DROP DATABASE \"#{database}\"")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.globalize_connection_opts!(connection_opts)
|
62
|
+
connection_opts[:initial] = Sql.build_opts(path: connection_opts[:path])
|
63
|
+
connection_opts[:path] = "template1"
|
64
|
+
end
|
65
|
+
|
66
|
+
private def database
|
67
|
+
if @connection.opts.key?(:initial)
|
68
|
+
@connection.opts[:initial][:path]
|
69
|
+
else
|
70
|
+
@connection.opts[:path]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module Sqlite
|
76
|
+
def create!
|
77
|
+
# intentionally empty; automatically created on connect
|
78
|
+
end
|
79
|
+
|
80
|
+
def drop!
|
81
|
+
if File.exist?(@connection.opts[:path])
|
82
|
+
FileUtils.rm(@connection.opts[:path])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.globalize_connection_opts!(connection_opts)
|
87
|
+
# nothing to do here
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/data/adapters/sql/differ"
|
4
|
+
require "pakyow/data/sources/relational/migrator"
|
5
|
+
|
6
|
+
module Pakyow
|
7
|
+
module Data
|
8
|
+
module Adapters
|
9
|
+
class Sql
|
10
|
+
class Migrator < Sources::Relational::Migrator
|
11
|
+
require "pakyow/data/adapters/sql/migrator/adapter_methods"
|
12
|
+
|
13
|
+
def initialize(*)
|
14
|
+
super
|
15
|
+
|
16
|
+
extend self.class.adapter_methods_for_adapter(
|
17
|
+
@connection.opts[:adapter]
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def disconnect!
|
22
|
+
@connection.disconnect
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_source?(source)
|
26
|
+
!differ(source).exists?
|
27
|
+
end
|
28
|
+
|
29
|
+
def change_source?(source, attributes = source.attributes)
|
30
|
+
create_source?(source) || differ(source, attributes).changes?
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_source!(source, attributes)
|
34
|
+
local_context = self
|
35
|
+
differ = differ(source, attributes)
|
36
|
+
create_table differ.table_name do
|
37
|
+
differ.attributes.each do |attribute_name, attribute|
|
38
|
+
local_context.send(:add_column_for_attribute, attribute_name, attribute, self, source)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def reassociate_source!(source, foreign_keys)
|
44
|
+
foreign_keys.each do |foreign_key_name, foreign_key|
|
45
|
+
differ = differ(source, foreign_key_name => foreign_key)
|
46
|
+
|
47
|
+
if create_source?(source) || differ.changes?
|
48
|
+
local_context = self
|
49
|
+
|
50
|
+
associate_table differ.table_name, with: foreign_key.meta[:foreign_key] do
|
51
|
+
attributes_to_add = if local_context.send(:create_source?, source)
|
52
|
+
differ.attributes
|
53
|
+
else
|
54
|
+
differ.attributes_to_add
|
55
|
+
end
|
56
|
+
|
57
|
+
attributes_to_add.each do |attribute_name, attribute|
|
58
|
+
local_context.send(:add_column_for_attribute, attribute_name, attribute, self, source, method_prefix: "add_")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def change_source!(source, attributes)
|
66
|
+
differ = differ(source, attributes)
|
67
|
+
|
68
|
+
if differ.changes?
|
69
|
+
local_context = self
|
70
|
+
|
71
|
+
alter_table differ.table_name do
|
72
|
+
differ.attributes_to_add.each do |attribute_name, attribute|
|
73
|
+
local_context.send(:add_column_for_attribute, attribute_name, attribute, self, source, method_prefix: "add_")
|
74
|
+
end
|
75
|
+
|
76
|
+
differ.column_types_to_change.each do |column_name, _column_type|
|
77
|
+
local_context.send(:change_column_type_for_attribute, column_name, differ.attributes[column_name], self)
|
78
|
+
end
|
79
|
+
|
80
|
+
# TODO: revisit when we're ready to tackle foreign key removal
|
81
|
+
#
|
82
|
+
# differ.columns_to_remove.keys.each do |column_name|
|
83
|
+
# local_context.send(:remove_column_by_name, column_name, self)
|
84
|
+
# end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def automator
|
92
|
+
require "pakyow/data/adapters/sql/migrators/automator"
|
93
|
+
Migrators::Automator.new(@connection, sources: @sources)
|
94
|
+
end
|
95
|
+
|
96
|
+
def finalizer
|
97
|
+
require "pakyow/data/adapters/sql/migrators/finalizer"
|
98
|
+
Migrators::Finalizer.new(@connection, sources: @sources)
|
99
|
+
end
|
100
|
+
|
101
|
+
def differ(source, attributes = source.attributes)
|
102
|
+
Differ.new(connection: @connection, source: source, attributes: attributes)
|
103
|
+
end
|
104
|
+
|
105
|
+
AUTO_INCREMENTING_TYPES = %i(integer bignum).freeze
|
106
|
+
def add_column_for_attribute(attribute_name, attribute, context, source, method_prefix: "")
|
107
|
+
if attribute.meta[:primary_key]
|
108
|
+
if AUTO_INCREMENTING_TYPES.include?(attribute.meta[:migration_type])
|
109
|
+
context.send(:"#{method_prefix}primary_key", attribute_name, type: type_for_attribute(attribute))
|
110
|
+
else
|
111
|
+
context.send(:"#{method_prefix}column", attribute_name, type_for_attribute(attribute), primary_key: true, **column_opts_for_attribute(attribute))
|
112
|
+
end
|
113
|
+
elsif attribute.meta[:foreign_key] && source.container.sources.any? { |potential_foreign_source| potential_foreign_source.plural_name == attribute.meta[:foreign_key] }
|
114
|
+
context.send(:"#{method_prefix}foreign_key", attribute_name, attribute.meta[:foreign_key], type: type_for_attribute(attribute))
|
115
|
+
else
|
116
|
+
context.send(:"#{method_prefix}column", attribute_name, type_for_attribute(attribute), **column_opts_for_attribute(attribute))
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def change_column_type_for_attribute(attribute_name, attribute, context)
|
121
|
+
context.set_column_type(attribute_name, type_for_attribute(attribute), **column_opts_for_attribute(attribute))
|
122
|
+
end
|
123
|
+
|
124
|
+
def remove_column_by_name(column_name, context)
|
125
|
+
context.drop_column(column_name)
|
126
|
+
end
|
127
|
+
|
128
|
+
ALLOWED_COLUMN_OPTS = %i(size text)
|
129
|
+
def column_opts_for_attribute(attribute)
|
130
|
+
{}.tap do |opts|
|
131
|
+
ALLOWED_COLUMN_OPTS.each do |opt|
|
132
|
+
if attribute.meta.include?(opt)
|
133
|
+
opts[opt] = attribute.meta[opt]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def column_opts_string_for_attribute(attribute)
|
140
|
+
opts = column_opts_for_attribute(attribute)
|
141
|
+
|
142
|
+
if opts.any?
|
143
|
+
opts.each_with_object(String.new) { |(key, value), opts_string|
|
144
|
+
opts_string << ", #{key}: #{value.inspect}"
|
145
|
+
}
|
146
|
+
else
|
147
|
+
""
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def handle_error
|
152
|
+
yield
|
153
|
+
rescue Sequel::Error => error
|
154
|
+
Pakyow.logger.warn "#{error}"
|
155
|
+
end
|
156
|
+
|
157
|
+
class << self
|
158
|
+
def adapter_methods_for_adapter(adapter)
|
159
|
+
case adapter
|
160
|
+
when "mysql", "mysql2"
|
161
|
+
AdapterMethods::Mysql
|
162
|
+
when "postgres"
|
163
|
+
AdapterMethods::Postgres
|
164
|
+
when "sqlite"
|
165
|
+
AdapterMethods::Sqlite
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def globalize_connection_opts!(connection_opts)
|
170
|
+
adapter_methods_for_adapter(
|
171
|
+
connection_opts[:adapter]
|
172
|
+
).globalize_connection_opts!(
|
173
|
+
connection_opts
|
174
|
+
)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/data/adapters/sql/migrator"
|
4
|
+
|
5
|
+
module Pakyow
|
6
|
+
module Data
|
7
|
+
module Adapters
|
8
|
+
class Sql
|
9
|
+
module Migrators
|
10
|
+
class Automator < Migrator
|
11
|
+
def associate_table(name, **, &block)
|
12
|
+
alter_table(name, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def alter_table(name, &block)
|
16
|
+
@connection.adapter.connection.alter_table name do
|
17
|
+
AlterTable.new(self).instance_exec(&block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(name, *args, &block)
|
22
|
+
@connection.adapter.connection.public_send(name, *args, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def type_for_attribute(attribute)
|
28
|
+
attribute.meta[:database_type]
|
29
|
+
end
|
30
|
+
|
31
|
+
class AlterTable
|
32
|
+
def initialize(table)
|
33
|
+
@table = table
|
34
|
+
end
|
35
|
+
|
36
|
+
def drop_column(*)
|
37
|
+
# Prevent columns from being dropped during auto migrate.
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(name, *args, &block)
|
41
|
+
@table.public_send(name, *args, &block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/data/adapters/sql/migrator"
|
4
|
+
|
5
|
+
module Pakyow
|
6
|
+
module Data
|
7
|
+
module Adapters
|
8
|
+
class Sql
|
9
|
+
module Migrators
|
10
|
+
class Finalizer < Migrator
|
11
|
+
attr_reader :migrations
|
12
|
+
|
13
|
+
def initialize(*)
|
14
|
+
super
|
15
|
+
@migrations = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_table(name, &block)
|
19
|
+
writer = Writer.new(root: true)
|
20
|
+
writer.create_table(name, &block)
|
21
|
+
@migrations << ["create_#{name}", writer]
|
22
|
+
end
|
23
|
+
|
24
|
+
def associate_table(name, with:, &block)
|
25
|
+
writer = Writer.new(root: true)
|
26
|
+
writer.alter_table(name, &block)
|
27
|
+
@migrations << ["associate_#{name}_with_#{with}", writer]
|
28
|
+
end
|
29
|
+
|
30
|
+
def alter_table(name, &block)
|
31
|
+
writer = Writer.new(root: true)
|
32
|
+
writer.alter_table(name, &block)
|
33
|
+
@migrations << ["change_#{name}", writer]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def type_for_attribute(attribute)
|
39
|
+
attribute.meta[:migration_type]
|
40
|
+
end
|
41
|
+
|
42
|
+
class Writer
|
43
|
+
def initialize(root: false)
|
44
|
+
@root, @content = root, String.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
if @root
|
49
|
+
<<~CONTENT
|
50
|
+
change do
|
51
|
+
#{indent(@content.strip)}
|
52
|
+
end
|
53
|
+
CONTENT
|
54
|
+
else
|
55
|
+
@content.strip
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_ary
|
60
|
+
[to_s]
|
61
|
+
end
|
62
|
+
|
63
|
+
def method_missing(name, *args, **kwargs, &block)
|
64
|
+
method_call = "#{name} #{args_to_string(args, kwargs)}"
|
65
|
+
|
66
|
+
if block_given?
|
67
|
+
@content << <<~CONTENT
|
68
|
+
#{method_call} do
|
69
|
+
#{indent(Writer.new.tap { |writer| writer.instance_exec(&block) }.to_s)}
|
70
|
+
end
|
71
|
+
CONTENT
|
72
|
+
else
|
73
|
+
@content << <<~CONTENT
|
74
|
+
#{method_call}
|
75
|
+
CONTENT
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def args_to_string(args, kwargs)
|
82
|
+
kwargs.each_with_object(args.map(&:inspect).join(", ")) { |(key, value), string|
|
83
|
+
string << ", #{key}: #{value.inspect}"
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def indent(content)
|
88
|
+
content.split("\n").map { |line| " #{line}" }.join("\n")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|