dynamic-active-model 0.6.4 → 0.7.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 +4 -4
- data/bin/dynamic-db-explorer +6 -2
- data/lib/dynamic-active-model/associations.rb +11 -9
- data/lib/dynamic-active-model/database.rb +2 -2
- data/lib/dynamic-active-model/foreign_key.rb +3 -3
- data/lib/dynamic-active-model/setup.rb +6 -6
- data/lib/dynamic-active-model/template_class_file.rb +33 -19
- data/lib/dynamic-active-model.rb +7 -7
- metadata +11 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c613724f6f06d888232f59f4e35ec8040134d5929d4098ee404bb1c70389b1bd
|
|
4
|
+
data.tar.gz: 1c98814db2e5123bcc43357e02b1ed100ea5f03088bdf1365d03c84cb1e71359
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 537abf6be0b70bf1ac9a4ae74cec03084fe866afb66cc23d4cbf612df2e45a3dd9543c2cc231ab4b3c2c86b6be9037037eebc35517e29dca7988903382418c0c
|
|
7
|
+
data.tar.gz: fb4e92f9e2dcdc33aac80dae1058eafb996ceaaf4054f5a68f91a1733bd4702fa93446d8d259389276a7a5422ee67afad14dcc6ffdef6a80e917cc0290e25583
|
data/bin/dynamic-db-explorer
CHANGED
|
@@ -5,7 +5,7 @@ require 'dynamic-active-model'
|
|
|
5
5
|
require 'optparse'
|
|
6
6
|
require 'yaml'
|
|
7
7
|
|
|
8
|
-
DEFAULT_CONFIG_FILE = "#{
|
|
8
|
+
DEFAULT_CONFIG_FILE = "#{Dir.home}/.dynamic-db-explorer.yml".freeze
|
|
9
9
|
|
|
10
10
|
options = {
|
|
11
11
|
base_module: Object,
|
|
@@ -56,6 +56,8 @@ OptionParser.new do |opts|
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
opts.on('--module MODULE_NAME', 'module name') do |v|
|
|
59
|
+
raise ArgumentError, "Invalid module name: #{v}" unless v.match?(/\A[A-Z][A-Za-z0-9_]*\z/)
|
|
60
|
+
|
|
59
61
|
Object.const_set(v, Module.new)
|
|
60
62
|
options[:base_module] = Object.const_get(v)
|
|
61
63
|
end
|
|
@@ -86,10 +88,12 @@ DynamicActiveModel::ForeignKey.id_suffix = options[:id_suffix] if options[:id_su
|
|
|
86
88
|
if options[:config_file]
|
|
87
89
|
raise('must specify a section') unless options[:config_section]
|
|
88
90
|
|
|
89
|
-
config = YAML.
|
|
91
|
+
config = YAML.safe_load_file(options[:config_file], permitted_classes: [Symbol])[options[:config_section]]
|
|
90
92
|
options[:skip_tables] = config.delete('skip_tables') || []
|
|
91
93
|
options[:relationships] = config.delete('relationships') || {}
|
|
92
94
|
if (module_name = config.delete('module'))
|
|
95
|
+
raise ArgumentError, "Invalid module name: #{module_name}" unless module_name.match?(/\A[A-Z][A-Za-z0-9_]*\z/)
|
|
96
|
+
|
|
93
97
|
Object.const_set(module_name, Module.new)
|
|
94
98
|
options[:base_module] = Object.const_get(module_name)
|
|
95
99
|
end
|
|
@@ -20,10 +20,10 @@ module DynamicActiveModel
|
|
|
20
20
|
class Associations
|
|
21
21
|
# @return [Database] The database instance containing the models
|
|
22
22
|
attr_reader :database
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
# @return [Hash] Mapping of table names to their indexes
|
|
25
25
|
attr_reader :table_indexes
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
# @return [Array] List of detected join tables
|
|
28
28
|
attr_reader :join_tables
|
|
29
29
|
|
|
@@ -72,10 +72,10 @@ module DynamicActiveModel
|
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
@join_tables.each do |join_table_model|
|
|
75
|
-
models = join_table_model.column_names.map
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
models = join_table_model.column_names.map do |column_name|
|
|
76
|
+
foreign_key_to_models[column_name.downcase]&.first&.first
|
|
77
|
+
end.compact
|
|
78
|
+
add_has_and_belongs_to_many(join_table_model, models) if models.size == 2
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
|
|
@@ -86,8 +86,10 @@ module DynamicActiveModel
|
|
|
86
86
|
# @param models [Array<Class>] The two models to be related
|
|
87
87
|
def add_has_and_belongs_to_many(join_table_model, models)
|
|
88
88
|
model1, model2 = *models
|
|
89
|
-
model1.has_and_belongs_to_many model2.table_name.pluralize.to_sym, join_table: join_table_model.table_name,
|
|
90
|
-
|
|
89
|
+
model1.has_and_belongs_to_many model2.table_name.pluralize.to_sym, join_table: join_table_model.table_name,
|
|
90
|
+
class_name: model2.name
|
|
91
|
+
model2.has_and_belongs_to_many model1.table_name.pluralize.to_sym, join_table: join_table_model.table_name,
|
|
92
|
+
class_name: model1.name
|
|
91
93
|
end
|
|
92
94
|
|
|
93
95
|
# Adds appropriate relationships between two models
|
|
@@ -150,7 +152,7 @@ module DynamicActiveModel
|
|
|
150
152
|
# @return [Hash] Mapping of foreign key names to model and relationship name pairs
|
|
151
153
|
def create_foreign_key_to_model_map
|
|
152
154
|
@foreign_keys.values.each_with_object({}) do |foreign_key, hsh|
|
|
153
|
-
foreign_key.keys.each do |key, relationship_name|
|
|
155
|
+
foreign_key.keys.each do |key, relationship_name| # rubocop:disable Style/HashEachMethods
|
|
154
156
|
hsh[key.downcase] ||= []
|
|
155
157
|
hsh[key.downcase] << [foreign_key.model, relationship_name]
|
|
156
158
|
end
|
|
@@ -25,10 +25,10 @@ module DynamicActiveModel
|
|
|
25
25
|
class Database
|
|
26
26
|
# @return [Hash] Mapping of table names to custom class names
|
|
27
27
|
attr_reader :table_class_names
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
# @return [Factory] Factory instance used for model creation
|
|
30
30
|
attr_reader :factory
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
# @return [Array] List of created model classes
|
|
33
33
|
attr_reader :models
|
|
34
34
|
|
|
@@ -18,7 +18,7 @@ module DynamicActiveModel
|
|
|
18
18
|
class ForeignKey
|
|
19
19
|
# @return [Class] The model this foreign key belongs to
|
|
20
20
|
attr_reader :model
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
# @return [Hash] Mapping of foreign key columns to relationship names
|
|
23
23
|
attr_reader :keys
|
|
24
24
|
|
|
@@ -33,8 +33,8 @@ module DynamicActiveModel
|
|
|
33
33
|
|
|
34
34
|
# Sets a custom foreign key suffix
|
|
35
35
|
# @param val [String] The new suffix to use
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
class << self
|
|
37
|
+
attr_writer :id_suffix
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
# Initializes a new ForeignKey instance
|
|
@@ -59,12 +59,12 @@ module DynamicActiveModel
|
|
|
59
59
|
if options.is_a?(String)
|
|
60
60
|
name = options
|
|
61
61
|
options = ActiveRecord::Base
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
.configurations
|
|
63
|
+
.configs_for(
|
|
64
|
+
env_name: Rails.env,
|
|
65
|
+
name: name
|
|
66
|
+
)
|
|
67
|
+
.configuration_hash
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
if options
|
|
@@ -30,15 +30,17 @@ module DynamicActiveModel
|
|
|
30
30
|
# @param dir [String] Directory to create the file in
|
|
31
31
|
# @return [void]
|
|
32
32
|
def create_template!(dir)
|
|
33
|
-
file = dir
|
|
34
|
-
File.
|
|
33
|
+
file = "#{dir}/#{@model.name.underscore}.rb"
|
|
34
|
+
File.binwrite(file, to_s)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# Generates the Ruby source code for the model
|
|
38
38
|
# @return [String] The complete model class definition
|
|
39
39
|
def to_s
|
|
40
|
-
str = "class #{@model.name} < ActiveRecord::Base\n"
|
|
41
|
-
|
|
40
|
+
str = "class #{@model.name} < ActiveRecord::Base\n"
|
|
41
|
+
unless @model.name.underscore.pluralize == @model.table_name
|
|
42
|
+
str << " self.table_name = #{@model.table_name.to_sym.inspect}\n"
|
|
43
|
+
end
|
|
42
44
|
all_has_many_relationships.each do |assoc|
|
|
43
45
|
append_association!(str, assoc)
|
|
44
46
|
end
|
|
@@ -105,15 +107,15 @@ module DynamicActiveModel
|
|
|
105
107
|
end
|
|
106
108
|
|
|
107
109
|
association_options = case assoc_type
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
when 'has_many'
|
|
111
|
+
has_many_association_options(assoc)
|
|
112
|
+
when 'belongs_to'
|
|
113
|
+
belongs_to_association_options(assoc)
|
|
114
|
+
when 'has_one'
|
|
115
|
+
has_one_association_options(assoc)
|
|
116
|
+
when 'has_and_belongs_to_many'
|
|
117
|
+
has_and_belongs_to_many_association_options(assoc)
|
|
118
|
+
end
|
|
117
119
|
|
|
118
120
|
str << " #{assoc_type} #{assoc.name.inspect}"
|
|
119
121
|
unless association_options.empty?
|
|
@@ -129,7 +131,10 @@ module DynamicActiveModel
|
|
|
129
131
|
# @return [Hash] The association options
|
|
130
132
|
def has_many_association_options(assoc)
|
|
131
133
|
options = {}
|
|
132
|
-
|
|
134
|
+
unless assoc.options[:class_name].underscore.pluralize == assoc.name.to_s
|
|
135
|
+
options[:class_name] =
|
|
136
|
+
assoc.options[:class_name]
|
|
137
|
+
end
|
|
133
138
|
options[:foreign_key] = assoc.options[:foreign_key] unless assoc.options[:foreign_key] == default_foreign_key_name
|
|
134
139
|
options[:primary_key] = assoc.options[:primary_key] unless assoc.options[:primary_key] == 'id'
|
|
135
140
|
options
|
|
@@ -141,7 +146,10 @@ module DynamicActiveModel
|
|
|
141
146
|
def belongs_to_association_options(assoc)
|
|
142
147
|
options = {}
|
|
143
148
|
options[:class_name] = assoc.options[:class_name] unless assoc.options[:class_name] == assoc.name.to_s.classify
|
|
144
|
-
|
|
149
|
+
unless assoc.options[:foreign_key] == "#{assoc.options[:class_name].underscore}_id"
|
|
150
|
+
options[:foreign_key] =
|
|
151
|
+
assoc.options[:foreign_key]
|
|
152
|
+
end
|
|
145
153
|
options[:primary_key] = assoc.options[:primary_key] unless assoc.options[:primary_key] == 'id'
|
|
146
154
|
options
|
|
147
155
|
end
|
|
@@ -151,7 +159,10 @@ module DynamicActiveModel
|
|
|
151
159
|
# @return [Hash] The association options
|
|
152
160
|
def has_one_association_options(assoc)
|
|
153
161
|
options = {}
|
|
154
|
-
|
|
162
|
+
unless assoc.options[:class_name].underscore.singularize == assoc.name.to_s
|
|
163
|
+
options[:class_name] =
|
|
164
|
+
assoc.options[:class_name]
|
|
165
|
+
end
|
|
155
166
|
options[:foreign_key] = assoc.options[:foreign_key] unless assoc.options[:foreign_key] == default_foreign_key_name
|
|
156
167
|
options[:primary_key] = assoc.options[:primary_key] unless assoc.options[:primary_key] == 'id'
|
|
157
168
|
options
|
|
@@ -163,21 +174,24 @@ module DynamicActiveModel
|
|
|
163
174
|
def has_and_belongs_to_many_association_options(assoc)
|
|
164
175
|
options = {}
|
|
165
176
|
options[:join_table] = assoc.options[:join_table] if assoc.options[:join_table]
|
|
166
|
-
|
|
177
|
+
unless assoc.options[:class_name].underscore.singularize == assoc.name.to_s
|
|
178
|
+
options[:class_name] =
|
|
179
|
+
assoc.options[:class_name]
|
|
180
|
+
end
|
|
167
181
|
options
|
|
168
182
|
end
|
|
169
183
|
|
|
170
184
|
# Gets the default foreign key name for the model
|
|
171
185
|
# @return [String] The default foreign key name
|
|
172
186
|
def default_foreign_key_name
|
|
173
|
-
@model.table_name.underscore.singularize
|
|
187
|
+
"#{@model.table_name.underscore.singularize}_id"
|
|
174
188
|
end
|
|
175
189
|
|
|
176
190
|
# Generates the default foreign key for the associated model
|
|
177
191
|
# @param assoc [ActiveRecord::Reflection::HasAndBelongsToManyReflection] The association
|
|
178
192
|
# @return [String] The generated foreign key name
|
|
179
193
|
def generate_association_foreign_key(assoc)
|
|
180
|
-
assoc.options[:class_name].underscore.singularize
|
|
194
|
+
"#{assoc.options[:class_name].underscore.singularize}_id"
|
|
181
195
|
end
|
|
182
196
|
|
|
183
197
|
# Gets a constant by its fully qualified name
|
data/lib/dynamic-active-model.rb
CHANGED
|
@@ -22,25 +22,25 @@
|
|
|
22
22
|
module DynamicActiveModel
|
|
23
23
|
# Database class handles database connection and model creation
|
|
24
24
|
autoload :Database, 'dynamic-active-model/database'
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
# Safety feature that prevents conflicts with Ruby reserved words
|
|
27
27
|
autoload :DangerousAttributesPatch, 'dynamic-active-model/dangerous_attributes_patch'
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
# High-level interface for model discovery and relationship mapping
|
|
30
30
|
autoload :Explorer, 'dynamic-active-model/explorer'
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
# Manages the creation of model classes
|
|
33
33
|
autoload :Factory, 'dynamic-active-model/factory'
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
# Handles foreign key relationships and constraints
|
|
36
36
|
autoload :ForeignKey, 'dynamic-active-model/foreign_key'
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
# Manages automatic discovery and setup of model relationships
|
|
39
39
|
autoload :Associations, 'dynamic-active-model/associations'
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
# Handles generation of model class files
|
|
42
42
|
autoload :TemplateClassFile, 'dynamic-active-model/template_class_file'
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
# Manages the setup process and configuration
|
|
45
45
|
autoload :Setup, 'dynamic-active-model/setup'
|
|
46
46
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dynamic-active-model
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Doug Youch
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-01-31 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activerecord
|
|
@@ -37,7 +37,11 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0.2'
|
|
40
|
-
description:
|
|
40
|
+
description: Dynamic Active Model automatically discovers database schemas and creates
|
|
41
|
+
ActiveRecord models without manual class definitions. It detects and configures
|
|
42
|
+
relationships (belongs_to, has_many, has_one, has_and_belongs_to_many) based on
|
|
43
|
+
foreign keys and constraints, handles dangerous attribute names, supports model
|
|
44
|
+
extensions, and includes a CLI tool for interactive database exploration.
|
|
41
45
|
email: dougyouch@gmail.com
|
|
42
46
|
executables:
|
|
43
47
|
- dynamic-db-explorer
|
|
@@ -57,7 +61,8 @@ files:
|
|
|
57
61
|
homepage: https://github.com/dougyouch/dynamic-active-model
|
|
58
62
|
licenses:
|
|
59
63
|
- MIT
|
|
60
|
-
metadata:
|
|
64
|
+
metadata:
|
|
65
|
+
rubygems_mfa_required: 'true'
|
|
61
66
|
rdoc_options: []
|
|
62
67
|
require_paths:
|
|
63
68
|
- lib
|
|
@@ -74,5 +79,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
74
79
|
requirements: []
|
|
75
80
|
rubygems_version: 3.6.2
|
|
76
81
|
specification_version: 4
|
|
77
|
-
summary:
|
|
82
|
+
summary: Automatically discover database schemas and create ActiveRecord models with
|
|
83
|
+
relationships
|
|
78
84
|
test_files: []
|