dynamic-active-model 0.9.1 → 0.11.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: 7040fc598ff4554e76a3c57dcd5fc32c37fc2592f0567af929c1a5f93319313a
4
- data.tar.gz: 8601378b7117b7d612db65698025879a31a6e432952a44f2c77cab2ba027e7ca
3
+ metadata.gz: 2bd96e8f6ce3262f827cacc23998e10b8b3dca910ab1b1e9913cc04b97ab6eb0
4
+ data.tar.gz: 7491a9ec594e8e704fafdcfcc974cbcf55b09a1624774fc88879023f6799a021
5
5
  SHA512:
6
- metadata.gz: a598ae139fbe14648219525a1250061089a960978b346ff40a05f392fbd6313b69ba9a8640cb17753b7dab8086d1b2d4d7c4974f9006084d57561eabeea48f3c
7
- data.tar.gz: a98ea335292e8ea96d4c7bd9123749ed425dcc8e6a0da7eaf6bd9d8a188c08859fa4bf04b2d44c81920f75aa64e7f9282ff5698a2c6e0c0716949e0546da9927
6
+ metadata.gz: 5dcdf65ae1a4da67eb8dece874b716ac08a7bf64c6457de3e244e627620cafaf5ffe0f86055a49c806d47d53afa3365ee1873db1952d9d686da024768adb3457
7
+ data.tar.gz: f4294f0f89f4a6be4ec5e1d19fc74d721b869d7d60bf8f280e3de085df0649db89dda2bbb51e44f1f1113efb709219f65dd0badc543a80924d3be47432b49a71
@@ -16,6 +16,10 @@ module DynamicActiveModel
16
16
  # db.include_table 'users'
17
17
  # db.create_models!
18
18
  #
19
+ # @example Rebuilding After a Schema Change
20
+ # db.reset!
21
+ # db.create_models!
22
+ #
19
23
  # @example Model Updates
20
24
  # db.update_model(:users) do
21
25
  # def full_name
@@ -45,8 +49,9 @@ module DynamicActiveModel
45
49
  # @param base_module [Module] The namespace for created models
46
50
  # @param connection_options [Hash] Database connection options
47
51
  # @param base_class_name [String, nil] Optional base class name for models
48
- def initialize(base_module, connection_options, base_class_name = nil)
49
- @factory = Factory.new(base_module, connection_options, base_class_name)
52
+ # @param parent_class [Class, nil] Optional superclass for the base class (see Factory)
53
+ def initialize(base_module, connection_options, base_class_name = nil, parent_class: nil)
54
+ @factory = Factory.new(base_module, connection_options, base_class_name, parent_class: parent_class)
50
55
  @table_class_names = {}
51
56
  @skip_tables = []
52
57
  @skip_table_matchers = []
@@ -106,6 +111,15 @@ module DynamicActiveModel
106
111
  @models
107
112
  end
108
113
 
114
+ # Removes every created model constant and the factory's base class so the
115
+ # next call to #create_models! rebuilds them, e.g. after a schema change
116
+ # @return [void]
117
+ def reset!
118
+ @models.each { |model| @factory.remove(model) }
119
+ @models.clear
120
+ @factory.reset!
121
+ end
122
+
109
123
  # @return [Array] List of all skipped tables and patterns
110
124
  def skipped_tables
111
125
  @skip_tables + @skip_table_matchers
@@ -182,11 +196,22 @@ module DynamicActiveModel
182
196
  !skip_table?(table_name) && include_table?(table_name) && get_model(table_name).nil?
183
197
  end
184
198
 
185
- # Checks if a table should be skipped
199
+ # Checks if a table is one of ActiveRecord's bookkeeping tables
200
+ # (schema_migrations, ar_internal_metadata)
201
+ # @param table_name [String] Name of the table
202
+ # @return [Boolean] Whether the table is internal to ActiveRecord
203
+ def internal_table?(table_name)
204
+ base_class = @factory.base_class
205
+ [base_class.schema_migrations_table_name, base_class.internal_metadata_table_name].include?(table_name)
206
+ end
207
+
208
+ # Checks if a table should be skipped. ActiveRecord's internal tables are
209
+ # skipped unless included by exact name.
186
210
  # @param table_name [String] Name of the table
187
211
  # @return [Boolean] Whether the table should be skipped
188
212
  def skip_table?(table_name)
189
- @skip_tables.include?(table_name.to_s) ||
213
+ (internal_table?(table_name) && !@include_tables.include?(table_name)) ||
214
+ @skip_tables.include?(table_name.to_s) ||
190
215
  @skip_table_matchers.any? { |r| r.match(table_name) }
191
216
  end
192
217
 
@@ -29,10 +29,13 @@ module DynamicActiveModel
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
31
  # @param table_class_names [Hash] Custom class names by table name
32
+ # @param parent_class [Class, nil] Optional superclass for the base class (see Factory)
32
33
  # @return [Database] The configured database instance
33
34
  # @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)
35
+ def self.explore(base_module, connection_options, skip_tables = [], relationships = {}, table_class_names = {},
36
+ parent_class: nil)
37
+ database = create_models!(base_module, connection_options, skip_tables, table_class_names,
38
+ parent_class: parent_class)
36
39
  build_relationships!(database, relationships)
37
40
  database
38
41
  end
@@ -42,9 +45,10 @@ module DynamicActiveModel
42
45
  # @param connection_options [Hash] Database connection options
43
46
  # @param skip_tables [Array<String, Regexp>] Tables to exclude from model creation
44
47
  # @param table_class_names [Hash] Custom class names by table name
48
+ # @param parent_class [Class, nil] Optional superclass for the base class (see Factory)
45
49
  # @return [Database] The configured database instance
46
- def self.create_models!(base_module, connection_options, skip_tables, table_class_names = {})
47
- database = Database.new(base_module, connection_options)
50
+ def self.create_models!(base_module, connection_options, skip_tables, table_class_names = {}, parent_class: nil)
51
+ database = Database.new(base_module, connection_options, parent_class: parent_class)
48
52
  skip_tables.each { |table| database.skip_table(skip_table_matcher(table)) }
49
53
  table_class_names.each { |table_name, class_name| database.table_class_name(table_name, class_name) }
50
54
  database.create_models!
@@ -15,6 +15,10 @@ module DynamicActiveModel
15
15
  # @example With Custom Class Name
16
16
  # factory = DynamicActiveModel::Factory.new(DB, database_config)
17
17
  # model = factory.create('users', 'CustomUser')
18
+ #
19
+ # @example Sharing an Existing Connection Pool
20
+ # factory = DynamicActiveModel::Factory.new(DB, nil, parent_class: ApplicationRecord)
21
+ # model = factory.create('users')
18
22
  class Factory
19
23
  # @return [Class] The base class for all generated models
20
24
  attr_writer :base_class
@@ -23,10 +27,13 @@ module DynamicActiveModel
23
27
  # @param base_module [Module] The namespace for created models
24
28
  # @param connection_options [Hash] Database connection options
25
29
  # @param base_class_name [Symbol, nil] Optional name for the base class
26
- def initialize(base_module, connection_options, base_class_name = nil)
30
+ # @param parent_class [Class, nil] Optional superclass for the base class. When given
31
+ # without connection options, the base class inherits the parent's connection.
32
+ def initialize(base_module, connection_options, base_class_name = nil, parent_class: nil)
27
33
  @base_module = base_module
28
34
  @connection_options = connection_options
29
35
  @base_class_name = base_class_name || :DynamicAbstractBase
36
+ @parent_class = parent_class
30
37
  end
31
38
 
32
39
  # Creates a new model class for a table if it doesn't exist
@@ -56,8 +63,8 @@ module DynamicActiveModel
56
63
 
57
64
  # Gets or creates the base class for all models
58
65
  # This method:
59
- # 1. Creates an abstract ActiveRecord::Base subclass if needed
60
- # 2. Establishes the database connection
66
+ # 1. Creates an abstract subclass of the parent class if needed
67
+ # 2. Establishes the database connection, unless inheriting the parent's
61
68
  # 3. Returns the configured base class
62
69
  # @return [Class] The base class for all models
63
70
  def base_class
@@ -65,19 +72,30 @@ module DynamicActiveModel
65
72
  begin
66
73
  require 'active_record'
67
74
 
68
- unless @base_module.const_defined?(@base_class_name)
69
- new_base_class = Class.new(ActiveRecord::Base) do
70
- self.abstract_class = true
71
- end
72
- @base_module.const_set(@base_class_name, new_base_class)
73
- end
75
+ define_base_class unless @base_module.const_defined?(@base_class_name)
74
76
 
75
77
  @base_module.const_get(@base_class_name).tap do |kls|
76
- kls.establish_connection @connection_options
78
+ kls.establish_connection(@connection_options) if establish_connection?
77
79
  end
78
80
  end
79
81
  end
80
82
 
83
+ # Removes a model's constant from the base module
84
+ # @param model [Class] A model class created by this factory
85
+ # @return [void]
86
+ def remove(model)
87
+ @base_module.send(:remove_const, model.name.demodulize)
88
+ end
89
+
90
+ # Removes the base class constant if this factory defined it and forgets the
91
+ # cached base class, so the next call to #base_class builds a fresh one
92
+ # @return [void]
93
+ def reset!
94
+ @base_module.send(:remove_const, @base_class_name) if @defined_base_class
95
+ @defined_base_class = false
96
+ @base_class = nil
97
+ end
98
+
81
99
  # Generates a valid Ruby class name from a table name
82
100
  # @param table_name [String] Name of the database table
83
101
  # @return [String] A valid Ruby class name
@@ -90,6 +108,22 @@ module DynamicActiveModel
90
108
 
91
109
  private
92
110
 
111
+ # Defines the abstract base class in the base module
112
+ # @return [void]
113
+ def define_base_class
114
+ new_base_class = Class.new(@parent_class || ActiveRecord::Base) do
115
+ self.abstract_class = true
116
+ end
117
+ @base_module.const_set(@base_class_name, new_base_class)
118
+ @defined_base_class = true
119
+ end
120
+
121
+ # Checks if the base class needs its own connection
122
+ # @return [Boolean] False when inheriting the parent class's connection
123
+ def establish_connection?
124
+ @parent_class.nil? || !@connection_options.nil?
125
+ end
126
+
93
127
  # Returns the existing model for a class name if it belongs to the table
94
128
  # @param table_name [String] Name of the database table
95
129
  # @param class_name [String] Name of the existing class
@@ -18,6 +18,13 @@ module DynamicActiveModel
18
18
  # create_models!
19
19
  # end
20
20
  #
21
+ # @example Sharing ApplicationRecord's Connection
22
+ # module DB
23
+ # include DynamicActiveModel::Setup
24
+ # parent_class ApplicationRecord
25
+ # create_models!
26
+ # end
27
+ #
21
28
  # @example With Custom Relationships
22
29
  # module DB
23
30
  # include DynamicActiveModel::Setup
@@ -45,6 +52,7 @@ module DynamicActiveModel
45
52
  def dynamic_active_model_config
46
53
  {
47
54
  connection_options: nil,
55
+ parent_class: nil,
48
56
  skip_tables: [],
49
57
  relationships: {},
50
58
  table_class_names: {},
@@ -62,30 +70,31 @@ module DynamicActiveModel
62
70
  options = ActiveRecord::Base
63
71
  .configurations
64
72
  .configs_for(
65
- env_name: Rails.env,
73
+ env_name: ::Rails.env,
66
74
  name: name
67
75
  )
68
76
  .configuration_hash
69
77
  end
70
78
 
71
- if options
72
- config = dynamic_active_model_config
73
- config[:connection_options] = options
74
- redefine_class_method(:dynamic_active_model_config, config)
75
- end
79
+ update_config(:connection_options, options) if options
76
80
 
77
81
  dynamic_active_model_config[:connection_options]
78
82
  end
79
83
 
84
+ # Sets or gets the superclass for the generated base class. Without connection
85
+ # options, models inherit the parent class's connection.
86
+ # @param klass [Class, nil] Superclass, e.g. ApplicationRecord
87
+ # @return [Class, nil] The current parent class
88
+ def parent_class(klass = nil)
89
+ update_config(:parent_class, klass) if klass
90
+ dynamic_active_model_config[:parent_class]
91
+ end
92
+
80
93
  # Sets or gets the list of tables to skip
81
94
  # @param tables [Array<String>, nil] Tables to skip
82
95
  # @return [Array<String>] The current list of skipped tables
83
96
  def skip_tables(tables = nil)
84
- if tables
85
- config = dynamic_active_model_config
86
- config[:skip_tables] = tables
87
- redefine_class_method(:dynamic_active_model_config, config)
88
- end
97
+ update_config(:skip_tables, tables) if tables
89
98
  dynamic_active_model_config[:skip_tables]
90
99
  end
91
100
 
@@ -101,11 +110,7 @@ module DynamicActiveModel
101
110
  # @param all_relationships [Hash, nil] All custom relationships
102
111
  # @return [Hash] The current relationships
103
112
  def relationships(all_relationships = nil)
104
- if all_relationships
105
- config = dynamic_active_model_config
106
- config[:relationships] = all_relationships
107
- redefine_class_method(:dynamic_active_model_config, config)
108
- end
113
+ update_config(:relationships, all_relationships) if all_relationships
109
114
  dynamic_active_model_config[:relationships]
110
115
  end
111
116
 
@@ -140,11 +145,7 @@ module DynamicActiveModel
140
145
  # @param path [String, nil] Path to extension files
141
146
  # @return [String, nil] The current extensions path
142
147
  def extensions_path(path = nil)
143
- if path
144
- config = dynamic_active_model_config
145
- config[:extensions_path] = path
146
- redefine_class_method(:dynamic_active_model_config, config)
147
- end
148
+ update_config(:extensions_path, path) if path
148
149
  dynamic_active_model_config[:extensions_path]
149
150
  end
150
151
 
@@ -152,11 +153,7 @@ module DynamicActiveModel
152
153
  # @param suffix [String, nil] File extension suffix
153
154
  # @return [String] The current extensions suffix
154
155
  def extensions_suffix(suffix = nil)
155
- if suffix
156
- config = dynamic_active_model_config
157
- config[:extensions_suffix] = suffix
158
- redefine_class_method(:dynamic_active_model_config, config)
159
- end
156
+ update_config(:extensions_suffix, suffix) if suffix
160
157
  dynamic_active_model_config[:extensions_suffix]
161
158
  end
162
159
 
@@ -173,12 +170,24 @@ module DynamicActiveModel
173
170
  connection_options,
174
171
  skip_tables,
175
172
  relationships,
176
- table_class_names
173
+ table_class_names,
174
+ parent_class: parent_class
177
175
  )
178
176
  )
179
177
  database.update_all_models(extensions_path, extensions_suffix) if extensions_path
180
178
  database
181
179
  end
180
+
181
+ private
182
+
183
+ # Stores a single configuration value
184
+ # @param key [Symbol] Configuration key
185
+ # @param value [Object] Configuration value
186
+ def update_config(key, value)
187
+ config = dynamic_active_model_config
188
+ config[key] = value
189
+ redefine_class_method(:dynamic_active_model_config, config)
190
+ end
182
191
  end
183
192
  end
184
193
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module DynamicActiveModel
4
4
  # Gem version, bumped by release-please
5
- VERSION = '0.9.1'
5
+ VERSION = '0.11.0'
6
6
  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.9.1
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Doug Youch