activerecord-dbt 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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +921 -0
- data/Rakefile +5 -0
- data/lib/active_record/dbt/column/column.rb +84 -0
- data/lib/active_record/dbt/column/test.rb +61 -0
- data/lib/active_record/dbt/column/testable/accepted_values_testable.rb +59 -0
- data/lib/active_record/dbt/column/testable/not_null_testable.rb +23 -0
- data/lib/active_record/dbt/column/testable/relationships_testable.rb +49 -0
- data/lib/active_record/dbt/column/testable/unique_testable.rb +37 -0
- data/lib/active_record/dbt/config.rb +45 -0
- data/lib/active_record/dbt/configuration/data_sync.rb +15 -0
- data/lib/active_record/dbt/configuration/logger.rb +41 -0
- data/lib/active_record/dbt/configuration/parser.rb +15 -0
- data/lib/active_record/dbt/configuration/used_dbt_package.rb +25 -0
- data/lib/active_record/dbt/dbt_package/dbt_utils/table/testable/unique_combination_of_columns_testable.rb +42 -0
- data/lib/active_record/dbt/dbt_package/dbterd/column/testable/relationships_meta_relationship_type.rb +138 -0
- data/lib/active_record/dbt/factory/columns_factory.rb +31 -0
- data/lib/active_record/dbt/factory/model/staging_factory.rb +22 -0
- data/lib/active_record/dbt/factory/source_factory.rb +16 -0
- data/lib/active_record/dbt/factory/table_factory.rb +16 -0
- data/lib/active_record/dbt/factory/tables_factory.rb +15 -0
- data/lib/active_record/dbt/model/staging/base.rb +73 -0
- data/lib/active_record/dbt/model/staging/sql.rb +43 -0
- data/lib/active_record/dbt/model/staging/yml.rb +108 -0
- data/lib/active_record/dbt/railtie.rb +8 -0
- data/lib/active_record/dbt/source/yml.rb +37 -0
- data/lib/active_record/dbt/table/base.rb +16 -0
- data/lib/active_record/dbt/table/test.rb +19 -0
- data/lib/active_record/dbt/table/yml.rb +75 -0
- data/lib/active_record/dbt/version.rb +7 -0
- data/lib/active_record/dbt.rb +17 -0
- data/lib/generators/active_record/dbt/config/USAGE +9 -0
- data/lib/generators/active_record/dbt/config/config_generator.rb +30 -0
- data/lib/generators/active_record/dbt/config/templates/source_config.yml.tt +68 -0
- data/lib/generators/active_record/dbt/initializer/USAGE +8 -0
- data/lib/generators/active_record/dbt/initializer/initializer_generator.rb +15 -0
- data/lib/generators/active_record/dbt/initializer/templates/dbt.rb +10 -0
- data/lib/generators/active_record/dbt/source/USAGE +8 -0
- data/lib/generators/active_record/dbt/source/source_generator.rb +22 -0
- data/lib/generators/active_record/dbt/staging_model/USAGE +9 -0
- data/lib/generators/active_record/dbt/staging_model/staging_model_generator.rb +38 -0
- data/lib/generators/active_record/dbt/staging_model/templates/staging_model.sql.tt +29 -0
- data/lib/tasks/active_record/dbt_tasks.rake +6 -0
- metadata +133 -0
data/Rakefile
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Dbt
|
5
|
+
module Column
|
6
|
+
class Column
|
7
|
+
attr_reader :table_name, :column, :column_test, :primary_keys
|
8
|
+
|
9
|
+
delegate :name, :comment, to: :column, prefix: true
|
10
|
+
delegate :source_config, to: :@config
|
11
|
+
|
12
|
+
def initialize(table_name, column, column_test, primary_keys: [])
|
13
|
+
@table_name = table_name
|
14
|
+
@column = column
|
15
|
+
@column_test = column_test
|
16
|
+
@primary_keys = primary_keys
|
17
|
+
@config = ActiveRecord::Dbt::Config.instance
|
18
|
+
end
|
19
|
+
|
20
|
+
def config
|
21
|
+
{
|
22
|
+
'name' => column_name,
|
23
|
+
'description' => description,
|
24
|
+
'meta' => { 'column_type' => column.type.to_s },
|
25
|
+
**column_overrides.except(:tests),
|
26
|
+
'tests' => column_test.config
|
27
|
+
}.compact
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def description
|
33
|
+
@description ||=
|
34
|
+
column_description ||
|
35
|
+
translated_attribute_name ||
|
36
|
+
column_comment ||
|
37
|
+
key_column_name ||
|
38
|
+
default_column_description ||
|
39
|
+
"Write a description of the '#{table_name}.#{column_name}' column."
|
40
|
+
end
|
41
|
+
|
42
|
+
def column_description
|
43
|
+
source_config.dig(:table_descriptions, table_name, :columns, column_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def translated_attribute_name
|
47
|
+
translated_column_name || translated_default_column_name
|
48
|
+
end
|
49
|
+
|
50
|
+
def translated_column_name
|
51
|
+
I18n.t("activerecord.attributes.#{table_name.singularize}.#{column_name}", default: nil)
|
52
|
+
end
|
53
|
+
|
54
|
+
def translated_default_column_name
|
55
|
+
I18n.t("attributes.#{column_name}", default: nil)
|
56
|
+
end
|
57
|
+
|
58
|
+
def key_column_name
|
59
|
+
column_name if primary_key? || foreign_key?
|
60
|
+
end
|
61
|
+
|
62
|
+
def primary_key?
|
63
|
+
primary_keys.include?(column_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def foreign_key?
|
67
|
+
ActiveRecord::Base.connection.foreign_key_exists?(table_name, column: column_name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def default_column_description
|
71
|
+
source_config.dig(:defaults, :table_descriptions, :columns, :description)
|
72
|
+
&.gsub(/{{\s*table_name\s*}}/, table_name)
|
73
|
+
&.gsub(/{{\s*column_name\s*}}/, column_name)
|
74
|
+
end
|
75
|
+
|
76
|
+
def column_overrides
|
77
|
+
@column_overrides ||=
|
78
|
+
source_config.dig(:table_overrides, table_name, :columns, column_name) ||
|
79
|
+
{}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Dbt
|
5
|
+
module Column
|
6
|
+
class Test
|
7
|
+
include ActiveRecord::Dbt::Column::Testable::AcceptedValuesTestable
|
8
|
+
include ActiveRecord::Dbt::Column::Testable::NotNullTestable
|
9
|
+
include ActiveRecord::Dbt::Column::Testable::RelationshipsTestable
|
10
|
+
include ActiveRecord::Dbt::Column::Testable::UniqueTestable
|
11
|
+
|
12
|
+
attr_reader :table_name, :column, :primary_keys, :foreign_keys
|
13
|
+
|
14
|
+
delegate :name, to: :column, prefix: true
|
15
|
+
delegate :source_config, to: :@config
|
16
|
+
|
17
|
+
def initialize(table_name, column, primary_keys: [], foreign_keys: [{}])
|
18
|
+
@table_name = table_name
|
19
|
+
@column = column
|
20
|
+
@primary_keys = primary_keys
|
21
|
+
@foreign_keys = foreign_keys
|
22
|
+
@config = ActiveRecord::Dbt::Config.instance
|
23
|
+
end
|
24
|
+
|
25
|
+
def config
|
26
|
+
(tests.keys | tests_overrides_hash.keys).map do |key|
|
27
|
+
tests_overrides_hash[key] || tests[key]
|
28
|
+
end.presence
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def tests
|
34
|
+
{
|
35
|
+
'unique_test' => unique_test,
|
36
|
+
'not_null_test' => not_null_test,
|
37
|
+
'relationships_test' => relationships_test,
|
38
|
+
'accepted_values_test' => accepted_values_test
|
39
|
+
}.compact
|
40
|
+
end
|
41
|
+
|
42
|
+
def tests_overrides_hash
|
43
|
+
@tests_overrides_hash ||=
|
44
|
+
tests_overrides.index_by do |tests_override|
|
45
|
+
"#{extract_key(tests_override)}_test"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def extract_key(item)
|
50
|
+
item.is_a?(Hash) ? item.keys.first : item
|
51
|
+
end
|
52
|
+
|
53
|
+
def tests_overrides
|
54
|
+
@tests_overrides ||=
|
55
|
+
source_config.dig(:table_overrides, table_name, :columns, column_name, :tests) ||
|
56
|
+
[]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Dbt
|
5
|
+
module Column
|
6
|
+
module Testable
|
7
|
+
module AcceptedValuesTestable
|
8
|
+
REQUIRED_ACCEPTED_VALUES_TESTABLE_METHODS = %i[@config column table_name column_name].freeze
|
9
|
+
|
10
|
+
delegate :type, to: :column, prefix: true
|
11
|
+
delegate :add_log, to: :@config
|
12
|
+
|
13
|
+
REQUIRED_ACCEPTED_VALUES_TESTABLE_METHODS.each do |method_name|
|
14
|
+
define_method(method_name) do
|
15
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def accepted_values_test
|
20
|
+
return nil unless column_type == :boolean || enum_values.present?
|
21
|
+
|
22
|
+
{
|
23
|
+
'accepted_values' => {
|
24
|
+
'values' => values,
|
25
|
+
'quote' => quote?
|
26
|
+
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def values
|
33
|
+
column_type == :boolean ? [true, false] : enum_accepted_values
|
34
|
+
end
|
35
|
+
|
36
|
+
def enum_accepted_values
|
37
|
+
enum_values.map { |key| quote? ? key.to_s : key }
|
38
|
+
end
|
39
|
+
|
40
|
+
def enum_values
|
41
|
+
@enum_values ||= enums[column_name]&.values
|
42
|
+
end
|
43
|
+
|
44
|
+
def enums
|
45
|
+
table_name.singularize.classify.constantize.defined_enums
|
46
|
+
rescue NameError => e
|
47
|
+
add_log(self.class, e)
|
48
|
+
|
49
|
+
{}
|
50
|
+
end
|
51
|
+
|
52
|
+
def quote?
|
53
|
+
@quote ||= %i[integer boolean].exclude?(column_type)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Dbt
|
5
|
+
module Column
|
6
|
+
module Testable
|
7
|
+
module NotNullTestable
|
8
|
+
REQUIRED_NOT_NULL_TESTABLE_METHODS = %i[column].freeze
|
9
|
+
|
10
|
+
REQUIRED_NOT_NULL_TESTABLE_METHODS.each do |method_name|
|
11
|
+
define_method(method_name) do
|
12
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def not_null_test
|
17
|
+
column.null == true ? nil : 'not_null'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Dbt
|
5
|
+
module Column
|
6
|
+
module Testable
|
7
|
+
module RelationshipsTestable
|
8
|
+
REQUIRED_RELATIONSHIPS_TESTABLE_METHODS = %i[@config foreign_keys column_name].freeze
|
9
|
+
|
10
|
+
include ActiveRecord::Dbt::DbtPackage::Dbterd::Column::Testable::RelationshipsMetaRelationshipType
|
11
|
+
|
12
|
+
delegate :source_name, :data_sync_delayed?, to: :@config
|
13
|
+
delegate :to_table, to: :foreign_key
|
14
|
+
|
15
|
+
REQUIRED_RELATIONSHIPS_TESTABLE_METHODS.each do |method_name|
|
16
|
+
define_method(method_name) do
|
17
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def relationships_test
|
22
|
+
return nil if foreign_key.blank?
|
23
|
+
|
24
|
+
{
|
25
|
+
'relationships' => {
|
26
|
+
'severity' => data_sync_delayed? ? 'warn' : nil,
|
27
|
+
'to' => "source('#{source_name}', '#{to_table}')",
|
28
|
+
'field' => primary_key,
|
29
|
+
'meta' => relationships_meta_relationship_type
|
30
|
+
}.compact
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def primary_key
|
37
|
+
foreign_key.dig(:options, :primary_key)
|
38
|
+
end
|
39
|
+
|
40
|
+
def foreign_key
|
41
|
+
@foreign_key ||= foreign_keys.find do |fk|
|
42
|
+
fk.dig(:options, :column) == column_name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Dbt
|
5
|
+
module Column
|
6
|
+
module Testable
|
7
|
+
module UniqueTestable
|
8
|
+
REQUIRED_UNIQUE_TESTABLE_METHODS = %i[table_name column_name primary_keys].freeze
|
9
|
+
|
10
|
+
REQUIRED_UNIQUE_TESTABLE_METHODS.each do |method_name|
|
11
|
+
define_method(method_name) do
|
12
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def unique_test
|
17
|
+
unique? ? 'unique' : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def unique?
|
23
|
+
primary_keys.include?(column_name) || unique_columns.include?(column_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def unique_columns
|
27
|
+
ActiveRecord::Base.connection.indexes(table_name).each_with_object([]) do |index, array|
|
28
|
+
if index.unique == true && (unique_indexes = index.columns).size == 1
|
29
|
+
array << unique_indexes.first
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Dbt
|
7
|
+
class Config
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
include ActiveRecord::Dbt::Configuration::DataSync
|
11
|
+
include ActiveRecord::Dbt::Configuration::Logger
|
12
|
+
include ActiveRecord::Dbt::Configuration::Parser
|
13
|
+
include ActiveRecord::Dbt::Configuration::UsedDbtPackage
|
14
|
+
|
15
|
+
DEFAULT_CONFIG_DIRECTORY_PATH = 'lib/dbt'
|
16
|
+
DEFAULT_EXPORT_DIRECTORY_PATH = 'doc/dbt'
|
17
|
+
|
18
|
+
attr_writer :config_directory_path, :export_directory_path
|
19
|
+
|
20
|
+
def source_config_path
|
21
|
+
@source_config_path ||= "#{config_directory_path}/source_config.yml"
|
22
|
+
end
|
23
|
+
|
24
|
+
def source_config
|
25
|
+
@source_config ||= parse_yaml(source_config_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def source_name
|
29
|
+
@source_name ||= source_config.dig(:sources, :name).tap do |source_name|
|
30
|
+
raise SourceNameIsNullError, "'sources.name' is required in '#{source_config_path}'." if source_name.nil?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def config_directory_path
|
35
|
+
@config_directory_path ||= DEFAULT_CONFIG_DIRECTORY_PATH
|
36
|
+
end
|
37
|
+
|
38
|
+
def export_directory_path
|
39
|
+
@export_directory_path ||= DEFAULT_EXPORT_DIRECTORY_PATH
|
40
|
+
end
|
41
|
+
|
42
|
+
class SourceNameIsNullError < StandardError; end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Dbt
|
5
|
+
module Configuration
|
6
|
+
module Logger
|
7
|
+
DEFAULT_LOG_FILE_PATH = './log/active_record_dbt.log'
|
8
|
+
EXCLUDE_EXCEPTION_CLASS_NAMES = %w[ArInternalMetadatum SchemaMigration].freeze
|
9
|
+
|
10
|
+
attr_writer :logger
|
11
|
+
|
12
|
+
def logger
|
13
|
+
@logger ||= ::Logger.new(DEFAULT_LOG_FILE_PATH)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_log(class_name, exception)
|
17
|
+
return if include_exception_class_names?(exception)
|
18
|
+
|
19
|
+
logger.info(class_name) { format_log(exception) }
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def include_exception_class_names?(exception)
|
25
|
+
exception.instance_of?(NameError) &&
|
26
|
+
EXCLUDE_EXCEPTION_CLASS_NAMES.include?(exception.name.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
def format_log(exception)
|
30
|
+
{
|
31
|
+
exception: [
|
32
|
+
exception.class,
|
33
|
+
exception.message
|
34
|
+
],
|
35
|
+
exception_backtrace: exception.backtrace.first(5)
|
36
|
+
}.to_json
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Dbt
|
5
|
+
module Configuration
|
6
|
+
module UsedDbtPackage
|
7
|
+
attr_writer :used_dbt_package_names
|
8
|
+
|
9
|
+
def used_dbt_utils?
|
10
|
+
used_dbt_package_names.include?('dbt-labs/dbt_utils')
|
11
|
+
end
|
12
|
+
|
13
|
+
def used_dbterd?
|
14
|
+
used_dbt_package_names.include?('datnguye/dbterd')
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def used_dbt_package_names
|
20
|
+
@used_dbt_package_names ||= []
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Dbt
|
5
|
+
module DbtPackage
|
6
|
+
module DbtUtils
|
7
|
+
module Table
|
8
|
+
module Testable
|
9
|
+
module UniqueCombinationOfColumnsTestable
|
10
|
+
REQUIRED_UNIQUE_COMBINATION_OF_COLUMNS_TESTABLE_METHODS = %i[table_name @config].freeze
|
11
|
+
|
12
|
+
delegate :used_dbt_utils?, to: :@config
|
13
|
+
|
14
|
+
REQUIRED_UNIQUE_COMBINATION_OF_COLUMNS_TESTABLE_METHODS.each do |method_name|
|
15
|
+
define_method(method_name) do
|
16
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def unique_combination_of_columns_test
|
21
|
+
return nil unless used_dbt_utils?
|
22
|
+
|
23
|
+
ActiveRecord::Base.connection.indexes(table_name).each_with_object([]) do |index, array|
|
24
|
+
next if index.unique == false
|
25
|
+
next if (unique_indexes = index.columns).size == 1
|
26
|
+
|
27
|
+
array.push(
|
28
|
+
{
|
29
|
+
'dbt_utils.unique_combination_of_columns' => {
|
30
|
+
'combination_of_columns' => unique_indexes
|
31
|
+
}
|
32
|
+
}
|
33
|
+
)
|
34
|
+
end.presence
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Dbt
|
5
|
+
module DbtPackage
|
6
|
+
module Dbterd
|
7
|
+
module Column
|
8
|
+
module Testable
|
9
|
+
module RelationshipsMetaRelationshipType
|
10
|
+
REQUIRED_RELATIONSHIP_TYPE_TESTABLE_METHODS = %i[@config foreign_key].freeze
|
11
|
+
|
12
|
+
delegate :used_dbterd?, :add_log, to: :@config
|
13
|
+
|
14
|
+
REQUIRED_RELATIONSHIP_TYPE_TESTABLE_METHODS.each do |method_name|
|
15
|
+
define_method(method_name) do
|
16
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def relationships_meta_relationship_type
|
21
|
+
return nil unless used_dbterd?
|
22
|
+
return nil if foreign_key.nil? || relationship_type.blank?
|
23
|
+
|
24
|
+
{
|
25
|
+
'relationship_type' => relationship_type
|
26
|
+
}
|
27
|
+
rescue NotSpecifiedOrNotInvalidIdError, StandardError => e
|
28
|
+
add_log(self.class, e)
|
29
|
+
|
30
|
+
{
|
31
|
+
'relationship_type' => 'many-to-one',
|
32
|
+
'active_record_dbt_error' => {
|
33
|
+
'class' => e.class.to_s,
|
34
|
+
'message' => e.message.to_s
|
35
|
+
}
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# MEMO: It seems to be a good idea to set only 'many-to-one', 'one-to-one', and 'many-to-one'.
|
42
|
+
# * [Relationship Types - DaaC from dbt artifacts](https://dbterd.datnguyen.de/1.13/nav/metadata/relationship_type.html)
|
43
|
+
# * [Syntax | DBML](https://dbml.dbdiagram.io/docs/#relationships--foreign-key-definitions)
|
44
|
+
# * DBML supports 'one-to-many', 'many-to-one', 'one-to-one', and 'many-to-many'.
|
45
|
+
def relationship_type
|
46
|
+
# if one_to_many?
|
47
|
+
# 'one-to-many'
|
48
|
+
# elsif zero_to_many?
|
49
|
+
# 'zero-to-many'
|
50
|
+
# elsif many_to_many?
|
51
|
+
# 'many-to-many'
|
52
|
+
# elsif one_to_one?
|
53
|
+
if one_to_one?
|
54
|
+
'one-to-one'
|
55
|
+
elsif many_to_one?
|
56
|
+
'many-to-one'
|
57
|
+
else
|
58
|
+
raise NotSpecifiedOrNotInvalidIdError, 'Not specified/Invalid value'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# MEMO: If 'many-to-one' is specified, 'one-to-many' should not be necessary.
|
63
|
+
# def one_to_many?
|
64
|
+
# end
|
65
|
+
|
66
|
+
# MEMO:
|
67
|
+
# * It seems that `zero-to-many` cannot be specified in a dbt relationship.
|
68
|
+
# * The reverse may be possible, but cannot be specified in dbterd.
|
69
|
+
# * It doesn't look like it can be configured with dbml.
|
70
|
+
# * [Syntax | DBML](https://dbml.dbdiagram.io/docs/#relationships--foreign-key-definitions)
|
71
|
+
# def zero_to_many?
|
72
|
+
# end
|
73
|
+
|
74
|
+
# # TODO: Usually, there is always an intermediate table, so there is no `many-to-many`.
|
75
|
+
# def many_to_many?
|
76
|
+
# from_model_find_association_to_model?(:has_and_belongs_to_many) &&
|
77
|
+
# to_model_find_association_from_model?(:has_and_belongs_to_many)
|
78
|
+
# end
|
79
|
+
|
80
|
+
def one_to_one?
|
81
|
+
from_model_find_association_to_model?(:belongs_to) &&
|
82
|
+
to_model_find_association_from_model?(:has_one)
|
83
|
+
end
|
84
|
+
|
85
|
+
def many_to_one?
|
86
|
+
from_model_find_association_to_model?(:belongs_to) &&
|
87
|
+
to_model_find_association_from_model?(:has_many)
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_model_find_association_from_model?(association_type)
|
91
|
+
to_model.reflect_on_all_associations(association_type).any? do |association|
|
92
|
+
association_klass(association) == from_model &&
|
93
|
+
association_foreign_key(association) == foreign_key_column
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def from_model_find_association_to_model?(association_type)
|
98
|
+
from_model.reflect_on_all_associations(association_type).any? do |association|
|
99
|
+
association_klass(association) == to_model &&
|
100
|
+
association_foreign_key(association) == foreign_key_column
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def association_klass(association)
|
105
|
+
association.klass
|
106
|
+
rescue NoMethodError
|
107
|
+
association.options.fetch(:through).to_s.classify.constantize
|
108
|
+
end
|
109
|
+
|
110
|
+
def association_foreign_key(association)
|
111
|
+
association.foreign_key
|
112
|
+
rescue NoMethodError
|
113
|
+
association.options.fetch(
|
114
|
+
:foreign_key,
|
115
|
+
"#{association.active_record.to_s.underscore}_id"
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
119
|
+
def foreign_key_column
|
120
|
+
foreign_key.dig(:options, :column)
|
121
|
+
end
|
122
|
+
|
123
|
+
def from_model
|
124
|
+
@from_model ||= foreign_key.from_table.classify.constantize
|
125
|
+
end
|
126
|
+
|
127
|
+
def to_model
|
128
|
+
@to_model ||= foreign_key.to_table.classify.constantize
|
129
|
+
end
|
130
|
+
|
131
|
+
class NotSpecifiedOrNotInvalidIdError < StandardError; end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Dbt
|
5
|
+
module Factory
|
6
|
+
module ColumnsFactory
|
7
|
+
def self.build(
|
8
|
+
table_name,
|
9
|
+
primary_keys: ActiveRecord::Base.connection.primary_keys(table_name),
|
10
|
+
foreign_keys: ActiveRecord::Base.connection.foreign_keys(table_name)
|
11
|
+
)
|
12
|
+
ActiveRecord::Base.connection.columns(table_name).map do |column|
|
13
|
+
column_test = ActiveRecord::Dbt::Column::Test.new(
|
14
|
+
table_name,
|
15
|
+
column,
|
16
|
+
primary_keys: primary_keys,
|
17
|
+
foreign_keys: foreign_keys
|
18
|
+
)
|
19
|
+
|
20
|
+
ActiveRecord::Dbt::Column::Column.new(
|
21
|
+
table_name,
|
22
|
+
column,
|
23
|
+
column_test,
|
24
|
+
primary_keys: primary_keys
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|