dynamic-active-model 0.8.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df4da0b0a5a5026754e0624dd508604ac4796ac3f60f37fa82018691fa596f3f
4
- data.tar.gz: d42ea5435c3ad98e7bb6e9c4489df62db14b7b6199df3e0247989ad7b8a6a725
3
+ metadata.gz: a9f8e2e549471211eb31b49e3e20b8a72e1c47eb2e9c857bcb4a85c11e470558
4
+ data.tar.gz: 70b7edd5ecf3dbe9c164593f1866eb2bd12a9cb89b5c609898052b8ad3f5eee2
5
5
  SHA512:
6
- metadata.gz: 986938faee17bc087e8260f468bf6c346244e291527c0f827b0ce0e227d079b22c082848eadb202a93d51798124787e496f343c9c4221eec0db652b3d3cb8218
7
- data.tar.gz: f0d2af7a8da7d120dc82b3600ed8b3daef0a5d0eb7c581a2fc60dbc205b0440cb27cf01d792ffb0aeaa481756db5d63e603e2548ea3e5153b007c6ebffdce4b8
6
+ metadata.gz: 90b12783def828937bef5898a020c6e8639fbf72c83a8649f5227077fce087d1fc619bd25f472b70b83dcf6a551bc97386f57c86b76f4fdc401fbe8c92336c11
7
+ data.tar.gz: c3316c0eb2a01b52e3b2628c913ce1d1d53c6ba7f67a34cbfb879caa820509d5ab676e3a2f78c771ca40a3405f960e65bea4ca9674540fca3192808d32053926
@@ -45,8 +45,11 @@ module DynamicActiveModel
45
45
  # @param table_name [String] Name of the table with the foreign key
46
46
  # @param foreign_key [String] Name of the foreign key column
47
47
  # @param relationship_name [String, nil] Custom name for the relationship
48
+ # @raise [ModelNotFound] If no model exists for the table
48
49
  def add_foreign_key(table_name, foreign_key, relationship_name = nil)
49
- @foreign_keys[table_name].add(foreign_key, relationship_name)
50
+ @foreign_keys
51
+ .fetch(table_name.to_s) { raise ModelNotFound, "no model found for table #{table_name}" }
52
+ .add(foreign_key, relationship_name)
50
53
  end
51
54
 
52
55
  # Builds all relationships between models based on foreign keys and constraints
@@ -122,29 +125,29 @@ module DynamicActiveModel
122
125
 
123
126
  # Adds a has_many relationship to a model
124
127
  # @param relationship_name [String] Name of the relationship
125
- # @param model [Class] The model with the foreign key
126
- # @param has_many_model [Class] The model being referenced
128
+ # @param model [Class] The referenced (parent) model that gets the association
129
+ # @param has_many_model [Class] The model with the foreign key
127
130
  # @param foreign_key [String] The foreign key column name
128
131
  def add_has_many(relationship_name, model, has_many_model, foreign_key)
129
132
  model.has_many(
130
133
  generate_has_many_association_name(relationship_name, model, has_many_model),
131
134
  class_name: has_many_model.name,
132
135
  foreign_key: foreign_key,
133
- primary_key: has_many_model.primary_key
136
+ primary_key: model.primary_key
134
137
  )
135
138
  end
136
139
 
137
140
  # Adds a has_one relationship to a model
138
141
  # @param relationship_name [String] Name of the relationship
139
- # @param model [Class] The model with the foreign key
140
- # @param has_one_model [Class] The model being referenced
142
+ # @param model [Class] The referenced (parent) model that gets the association
143
+ # @param has_one_model [Class] The model with the foreign key
141
144
  # @param foreign_key [String] The foreign key column name
142
145
  def add_has_one(relationship_name, model, has_one_model, foreign_key)
143
146
  model.has_one(
144
147
  generate_has_one_association_name(relationship_name, model, has_one_model),
145
148
  class_name: has_one_model.name,
146
149
  foreign_key: foreign_key,
147
- primary_key: has_one_model.primary_key
150
+ primary_key: model.primary_key
148
151
  )
149
152
  end
150
153
 
@@ -161,8 +164,8 @@ module DynamicActiveModel
161
164
 
162
165
  # Generates an appropriate name for a has_many association
163
166
  # @param relationship_name [String] Original relationship name
164
- # @param model [Class] The model with the foreign key
165
- # @param has_many_model [Class] The model being referenced
167
+ # @param model [Class] The referenced (parent) model that gets the association
168
+ # @param has_many_model [Class] The model with the foreign key
166
169
  # @return [Symbol] The generated association name
167
170
  def generate_has_many_association_name(relationship_name, model, has_many_model)
168
171
  name =
@@ -176,8 +179,8 @@ module DynamicActiveModel
176
179
 
177
180
  # Generates an appropriate name for a has_one association
178
181
  # @param relationship_name [String] Original relationship name
179
- # @param model [Class] The model with the foreign key
180
- # @param has_one_model [Class] The model being referenced
182
+ # @param model [Class] The referenced (parent) model that gets the association
183
+ # @param has_one_model [Class] The model with the foreign key
181
184
  # @return [Symbol] The generated association name
182
185
  def generate_has_one_association_name(relationship_name, model, has_one_model)
183
186
  name =
@@ -208,7 +211,7 @@ module DynamicActiveModel
208
211
  def join_table?(model)
209
212
  model.primary_key.nil? &&
210
213
  model.columns.size == 2 &&
211
- model.columns.all? { |column| column.name =~ /#{ForeignKey.id_suffix}$/ }
214
+ model.columns.all? { |column| column.name.end_with?(ForeignKey.id_suffix) }
212
215
  end
213
216
  end
214
217
  end
@@ -94,15 +94,16 @@ module DynamicActiveModel
94
94
  @table_class_names[table_name.to_s] = class_name
95
95
  end
96
96
 
97
- # Creates ActiveRecord models for all included tables
98
- # @return [Array] List of created model classes
97
+ # Creates ActiveRecord models for all included tables that don't have one yet
98
+ # @return [Array] List of model classes
99
+ # @raise [ClassNameConflict] If two tables map to the same class name
99
100
  def create_models!
100
101
  @factory.base_class.connection.tables.each do |table_name|
101
- next if skip_table?(table_name)
102
- next unless include_table?(table_name)
102
+ next unless create_model?(table_name)
103
103
 
104
104
  @models << @factory.create(table_name, @table_class_names[table_name])
105
105
  end
106
+ @models
106
107
  end
107
108
 
108
109
  # @return [Array] List of all skipped tables and patterns
@@ -174,6 +175,13 @@ module DynamicActiveModel
174
175
 
175
176
  private
176
177
 
178
+ # Checks if a model should be created for a table
179
+ # @param table_name [String] Name of the table
180
+ # @return [Boolean] Whether the table is included and has no model yet
181
+ def create_model?(table_name)
182
+ !skip_table?(table_name) && include_table?(table_name) && get_model(table_name).nil?
183
+ end
184
+
177
185
  # Checks if a table should be skipped
178
186
  # @param table_name [String] Name of the table
179
187
  # @return [Boolean] Whether the table should be skipped
@@ -28,9 +28,11 @@ module DynamicActiveModel
28
28
  # @param connection_options [Hash] Database connection options
29
29
  # @param skip_tables [Array<String, Regexp>] Tables to exclude from model creation
30
30
  # @param relationships [Hash] Custom foreign key relationships to add
31
+ # @param table_class_names [Hash] Custom class names by table name
31
32
  # @return [Database] The configured database instance
32
- def self.explore(base_module, connection_options, skip_tables = [], relationships = {})
33
- database = create_models!(base_module, connection_options, skip_tables)
33
+ # @raise [ClassNameConflict] If two tables map to the same class name
34
+ def self.explore(base_module, connection_options, skip_tables = [], relationships = {}, table_class_names = {})
35
+ database = create_models!(base_module, connection_options, skip_tables, table_class_names)
34
36
  build_relationships!(database, relationships)
35
37
  database
36
38
  end
@@ -39,17 +41,25 @@ module DynamicActiveModel
39
41
  # @param base_module [Module] The namespace for created models
40
42
  # @param connection_options [Hash] Database connection options
41
43
  # @param skip_tables [Array<String, Regexp>] Tables to exclude from model creation
44
+ # @param table_class_names [Hash] Custom class names by table name
42
45
  # @return [Database] The configured database instance
43
- def self.create_models!(base_module, connection_options, skip_tables)
46
+ def self.create_models!(base_module, connection_options, skip_tables, table_class_names = {})
44
47
  database = Database.new(base_module, connection_options)
45
- skip_tables.each do |table_name|
46
- table_name = Regexp.new("^#{table_name}") if table_name.include?('*')
47
- database.skip_table(table_name)
48
- end
48
+ skip_tables.each { |table| database.skip_table(skip_table_matcher(table)) }
49
+ table_class_names.each { |table_name, class_name| database.table_class_name(table_name, class_name) }
49
50
  database.create_models!
50
51
  database
51
52
  end
52
53
 
54
+ # Converts a table name containing * wildcards into an anchored Regexp
55
+ # @param table [String, Regexp] Table name, wildcard pattern, or Regexp
56
+ # @return [String, Regexp] The table name or matching Regexp
57
+ def self.skip_table_matcher(table)
58
+ return table unless table.is_a?(String) && table.include?('*')
59
+
60
+ Regexp.new("\\A#{Regexp.escape(table).gsub('\\*', '.*')}\\z")
61
+ end
62
+
53
63
  # Sets up relationships between created models
54
64
  # @param database [Database] The database instance containing the models
55
65
  # @param relationships [Hash] Custom foreign key relationships to add
@@ -33,10 +33,12 @@ module DynamicActiveModel
33
33
  # @param table_name [String] Name of the database table
34
34
  # @param class_name [String, nil] Optional custom class name
35
35
  # @return [Class] The model class
36
+ # @raise [ClassNameConflict] If the class name is already used by a different table
36
37
  def create(table_name, class_name = nil)
37
38
  class_name ||= generate_class_name(table_name)
38
- create!(table_name, class_name) unless @base_module.const_defined?(class_name)
39
- @base_module.const_get(class_name)
39
+ return create!(table_name, class_name) unless @base_module.const_defined?(class_name, false)
40
+
41
+ existing_model!(table_name, class_name)
40
42
  end
41
43
 
42
44
  # Creates a new model class for a table, overwriting if it exists
@@ -49,7 +51,7 @@ module DynamicActiveModel
49
51
  include DynamicActiveModel::DangerousAttributesPatch
50
52
  end
51
53
  @base_module.const_set(class_name, kls)
52
- @base_module.const_get(class_name)
54
+ @base_module.const_get(class_name, false)
53
55
  end
54
56
 
55
57
  # Gets or creates the base class for all models
@@ -85,5 +87,22 @@ module DynamicActiveModel
85
87
 
86
88
  class_name
87
89
  end
90
+
91
+ private
92
+
93
+ # Returns the existing model for a class name if it belongs to the table
94
+ # @param table_name [String] Name of the database table
95
+ # @param class_name [String] Name of the existing class
96
+ # @return [Class] The existing model class
97
+ # @raise [ClassNameConflict] If the class belongs to a different table
98
+ def existing_model!(table_name, class_name)
99
+ model = @base_module.const_get(class_name, false)
100
+ return model if model.respond_to?(:table_name) && model.table_name == table_name.to_s
101
+
102
+ raise ClassNameConflict,
103
+ "table #{table_name} maps to class #{model.name}, which is already used by " \
104
+ "#{model.respond_to?(:table_name) ? "table #{model.table_name}" : 'another constant'}; " \
105
+ 'set a different class name with table_class_name'
106
+ end
88
107
  end
89
108
  end
@@ -47,6 +47,7 @@ module DynamicActiveModel
47
47
  connection_options: nil,
48
48
  skip_tables: [],
49
49
  relationships: {},
50
+ table_class_names: {},
50
51
  extensions_path: nil,
51
52
  extensions_suffix: '.ext.rb'
52
53
  }
@@ -120,6 +121,21 @@ module DynamicActiveModel
120
121
  redefine_class_method(:dynamic_active_model_config, config)
121
122
  end
122
123
 
124
+ # Sets a custom class name for a table
125
+ # @param table_name [String] Name of the table
126
+ # @param class_name [String] Class name to use for the table's model
127
+ def table_class_name(table_name, class_name)
128
+ config = dynamic_active_model_config
129
+ config[:table_class_names][table_name.to_s] = class_name
130
+ redefine_class_method(:dynamic_active_model_config, config)
131
+ end
132
+
133
+ # Gets the custom class names by table name
134
+ # @return [Hash] The current table class names
135
+ def table_class_names
136
+ dynamic_active_model_config[:table_class_names]
137
+ end
138
+
123
139
  # Sets or gets the path for model extensions
124
140
  # @param path [String, nil] Path to extension files
125
141
  # @return [String, nil] The current extensions path
@@ -156,7 +172,8 @@ module DynamicActiveModel
156
172
  self,
157
173
  connection_options,
158
174
  skip_tables,
159
- relationships
175
+ relationships,
176
+ table_class_names
160
177
  )
161
178
  )
162
179
  database.update_all_models(extensions_path, extensions_suffix) if extensions_path
@@ -2,5 +2,5 @@
2
2
 
3
3
  module DynamicActiveModel
4
4
  # Gem version, bumped by release-please
5
- VERSION = '0.8.0'
5
+ VERSION = '0.9.0'
6
6
  end
@@ -46,6 +46,12 @@ module DynamicActiveModel
46
46
  # Manages the setup process and configuration
47
47
  autoload :Setup, 'dynamic-active-model/setup'
48
48
 
49
+ # Base class for errors raised by DynamicActiveModel
50
+ class Error < StandardError; end
51
+
49
52
  # Raised when a requested model cannot be found
50
- class ModelNotFound < StandardError; end
53
+ class ModelNotFound < Error; end
54
+
55
+ # Raised when two tables map to the same model class name
56
+ class ClassNameConflict < Error; end
51
57
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamic-active-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Doug Youch