dynamic-active-model 1.0.0 → 1.2.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: 471bcb2966f59d8f8f4f7df9f32432cf6ff215685a770e2a99956844e0f2d8d5
4
- data.tar.gz: 69941d0598425aaf866cfdc29a6e8252d38df291d61523e6edae6243d7f049c0
3
+ metadata.gz: b13a9d41753ab707e9a414bb36ffd043c4362d09764717f4b43770ba8c0e0731
4
+ data.tar.gz: 2ed3a9b28b4e9a0825b19acf97f9b95deb8cd99bf312ed6006112f8bbd2573d1
5
5
  SHA512:
6
- metadata.gz: a7b6cd8138519d8879df5fab6349771b2cc586d340752d566431a985aac152f102c10654dd60098695f303f3fd35eec5b85f109d269ac24a489b51de0a6f2e05
7
- data.tar.gz: 3402b23d14abd1b92525642021a5586a202d83aa09683cfe14ae576d9ead974fdccd72374bb7d4f47b77ffc56ca4ba8c8fbb48f3c8cdc371ba283b2f15f24a9f
6
+ metadata.gz: 86c16133679c95f1e79a24745f2d474c5b5645aadcd6609dcddfe0a347e0545ef618a24ee96b9d674f742b75d03fa0cc90c964400c4ca3e8c89019de8988214a
7
+ data.tar.gz: acb55a31da056e1f4d5b5d816a61cd45464262b7b6bf4df84b9f56e3400e4d0767561c933e8bce43bc3718e4de7088bd108fa5124a6ecb8f66ec4ef03d9b3134
@@ -66,6 +66,14 @@ OptionParser.new do |opts|
66
66
  options[:id_suffix] = v
67
67
  end
68
68
 
69
+ opts.on('--foreign-key-constraints', 'Also relate columns through foreign key constraints') do
70
+ options[:foreign_key_constraints] = true
71
+ end
72
+
73
+ opts.on('--has-many-through', 'Also add has_many :through across join models') do
74
+ options[:has_many_through] = true
75
+ end
76
+
69
77
  opts.on('-c', '--config FILE', "config file, default #{DEFAULT_CONFIG_FILE}") do |v|
70
78
  options[:config_file] = v
71
79
  end
@@ -91,6 +99,8 @@ if options[:config_file]
91
99
  config = YAML.safe_load_file(options[:config_file], permitted_classes: [Symbol])[options[:config_section]]
92
100
  options[:skip_tables] = config.delete('skip_tables') || []
93
101
  options[:relationships] = config.delete('relationships') || {}
102
+ options[:foreign_key_constraints] ||= config.delete('foreign_key_constraints') || false
103
+ options[:has_many_through] ||= config.delete('has_many_through') || false
94
104
  if (module_name = config.delete('module'))
95
105
  raise ArgumentError, "Invalid module name: #{module_name}" unless module_name.match?(/\A[A-Z][A-Za-z0-9_]*\z/)
96
106
 
@@ -104,7 +114,9 @@ DYNAMIC_DATABASE = DynamicActiveModel::Explorer.explore(
104
114
  options[:base_module],
105
115
  options[:connection_options],
106
116
  options[:skip_tables],
107
- options[:relationships]
117
+ options[:relationships],
118
+ foreign_key_constraints: options[:foreign_key_constraints] || false,
119
+ has_many_through: options[:has_many_through] || false
108
120
  )
109
121
 
110
122
  if options[:class_files_dir]
@@ -53,6 +53,19 @@ module DynamicActiveModel
53
53
  .add(foreign_key, relationship_name)
54
54
  end
55
55
 
56
+ # Relates columns through the database's foreign key constraints as well as by
57
+ # naming convention. A column with a constraint follows the constraint.
58
+ # @return [void]
59
+ def use_foreign_key_constraints!
60
+ @constraints = ForeignKeyConstraints.new(@database)
61
+ end
62
+
63
+ # Also adds has_many :through associations across join models (see HasManyThrough)
64
+ # @return [void]
65
+ def use_has_many_through!
66
+ @has_many_through = true
67
+ end
68
+
56
69
  # Builds all relationships between models based on foreign keys and constraints
57
70
  # This method:
58
71
  # 1. Maps foreign keys to their corresponding models
@@ -64,15 +77,7 @@ module DynamicActiveModel
64
77
  foreign_key_to_models = create_foreign_key_to_model_map
65
78
 
66
79
  @database.models.each do |model|
67
- model.column_names.each do |column_name|
68
- next unless foreign_key_to_models[column_name.downcase]
69
-
70
- foreign_key_to_models[column_name.downcase].each do |foreign_model, relationship_name|
71
- next if foreign_model == model
72
-
73
- add_relationships(relationship_name, model, foreign_model, column_name)
74
- end
75
- end
80
+ model.column_names.each { |column_name| add_column_relationships(model, column_name, foreign_key_to_models) }
76
81
  end
77
82
 
78
83
  @join_tables.each do |join_table_model|
@@ -81,10 +86,36 @@ module DynamicActiveModel
81
86
  end.compact
82
87
  add_has_and_belongs_to_many(join_table_model, models) if models.size == 2
83
88
  end
89
+
90
+ HasManyThrough.new(@database.models, table_indexes).build! if @has_many_through
84
91
  end
85
92
 
86
93
  private
87
94
 
95
+ # Relates a column through its foreign key constraint when it has one (and
96
+ # constraints are in use), otherwise by naming convention
97
+ # @param model [Class] The model with the column
98
+ # @param column_name [String]
99
+ # @param foreign_key_to_models [Hash] See #create_foreign_key_to_model_map
100
+ def add_column_relationships(model, column_name, foreign_key_to_models)
101
+ constraint = @constraints&.find(model, column_name)
102
+ return add_constraint_relationships(constraint) if constraint
103
+
104
+ foreign_key_to_models.fetch(column_name.downcase, []).each do |foreign_model, relationship_name|
105
+ next if foreign_model == model
106
+
107
+ add_relationships(relationship_name, model, foreign_model, column_name)
108
+ end
109
+ end
110
+
111
+ # Adds the relationships a foreign key constraint describes; unlike the naming
112
+ # convention, these may reference the same model (employees.manager_id)
113
+ # @param constraint [ForeignKeyConstraints::Constraint]
114
+ def add_constraint_relationships(constraint)
115
+ add_relationships(constraint.relationship_name, constraint.model, constraint.referenced_model,
116
+ constraint.column, constraint.primary_key)
117
+ end
118
+
88
119
  # Adds has_and_belongs_to_many relationships between two models
89
120
  # @param join_table_model [Class] The join table model
90
121
  # @param models [Array<Class>] The two models to be related
@@ -101,12 +132,14 @@ module DynamicActiveModel
101
132
  # @param model [Class] The model with the foreign key
102
133
  # @param belongs_to_model [Class] The model being referenced
103
134
  # @param foreign_key [String] The foreign key column name
104
- def add_relationships(relationship_name, model, belongs_to_model, foreign_key)
105
- add_belongs_to(relationship_name, model, belongs_to_model, foreign_key)
135
+ # @param primary_key [String] The referenced column, normally the primary key
136
+ def add_relationships(relationship_name, model, belongs_to_model, foreign_key,
137
+ primary_key = belongs_to_model.primary_key)
138
+ add_belongs_to(relationship_name, model, belongs_to_model, foreign_key, primary_key)
106
139
  if unique_index?(model, foreign_key)
107
- add_has_one(relationship_name, belongs_to_model, model, foreign_key)
140
+ add_has_one(relationship_name, belongs_to_model, model, foreign_key, primary_key)
108
141
  else
109
- add_has_many(relationship_name, belongs_to_model, model, foreign_key)
142
+ add_has_many(relationship_name, belongs_to_model, model, foreign_key, primary_key)
110
143
  end
111
144
  end
112
145
 
@@ -115,12 +148,13 @@ module DynamicActiveModel
115
148
  # @param model [Class] The model with the foreign key
116
149
  # @param belongs_to_model [Class] The model being referenced
117
150
  # @param foreign_key [String] The foreign key column name
118
- def add_belongs_to(relationship_name, model, belongs_to_model, foreign_key)
151
+ # @param primary_key [String] The referenced column
152
+ def add_belongs_to(relationship_name, model, belongs_to_model, foreign_key, primary_key)
119
153
  model.belongs_to(
120
154
  relationship_name.singularize.to_sym,
121
155
  class_name: belongs_to_model.name,
122
156
  foreign_key: foreign_key,
123
- primary_key: belongs_to_model.primary_key
157
+ primary_key: primary_key
124
158
  )
125
159
  end
126
160
 
@@ -129,12 +163,13 @@ module DynamicActiveModel
129
163
  # @param model [Class] The referenced (parent) model that gets the association
130
164
  # @param has_many_model [Class] The model with the foreign key
131
165
  # @param foreign_key [String] The foreign key column name
132
- def add_has_many(relationship_name, model, has_many_model, foreign_key)
166
+ # @param primary_key [String] The referenced column on the parent model
167
+ def add_has_many(relationship_name, model, has_many_model, foreign_key, primary_key)
133
168
  model.has_many(
134
169
  generate_has_many_association_name(relationship_name, model, has_many_model),
135
170
  class_name: has_many_model.name,
136
171
  foreign_key: foreign_key,
137
- primary_key: model.primary_key
172
+ primary_key: primary_key
138
173
  )
139
174
  end
140
175
 
@@ -143,12 +178,13 @@ module DynamicActiveModel
143
178
  # @param model [Class] The referenced (parent) model that gets the association
144
179
  # @param has_one_model [Class] The model with the foreign key
145
180
  # @param foreign_key [String] The foreign key column name
146
- def add_has_one(relationship_name, model, has_one_model, foreign_key)
181
+ # @param primary_key [String] The referenced column on the parent model
182
+ def add_has_one(relationship_name, model, has_one_model, foreign_key, primary_key)
147
183
  model.has_one(
148
184
  generate_has_one_association_name(relationship_name, model, has_one_model),
149
185
  class_name: has_one_model.name,
150
186
  foreign_key: foreign_key,
151
- primary_key: model.primary_key
187
+ primary_key: primary_key
152
188
  )
153
189
  end
154
190
 
@@ -14,6 +14,10 @@ module DynamicActiveModel
14
14
  # skip_tables = ['temporary_data', 'audit_logs']
15
15
  # DynamicActiveModel::Explorer.explore(DB, database_config, skip_tables)
16
16
  #
17
+ # @example With Foreign Key Constraints and has_many :through
18
+ # DynamicActiveModel::Explorer.explore(DB, database_config, foreign_key_constraints: true,
19
+ # has_many_through: true)
20
+ #
17
21
  # @example With Custom Relationships
18
22
  # relationships = {
19
23
  # 'users' => {
@@ -30,13 +34,18 @@ module DynamicActiveModel
30
34
  # @param relationships [Hash] Custom foreign key relationships to add
31
35
  # @param table_class_names [Hash] Custom class names by table name
32
36
  # @param parent_class [Class, nil] Optional superclass for the base class (see Factory)
37
+ # @param foreign_key_constraints [Boolean] Also relate columns through the database's
38
+ # foreign key constraints (see ForeignKeyConstraints)
39
+ # @param has_many_through [Boolean] Also add has_many :through across join models
40
+ # (see HasManyThrough)
33
41
  # @return [Database] The configured database instance
34
42
  # @raise [ClassNameConflict] If two tables map to the same class name
35
43
  def self.explore(base_module, connection_options, skip_tables = [], relationships = {}, table_class_names = {},
36
- parent_class: nil)
44
+ parent_class: nil, foreign_key_constraints: false, has_many_through: false)
37
45
  database = create_models!(base_module, connection_options, skip_tables, table_class_names,
38
46
  parent_class: parent_class)
39
- build_relationships!(database, relationships)
47
+ build_relationships!(database, relationships, foreign_key_constraints: foreign_key_constraints,
48
+ has_many_through: has_many_through)
40
49
  database
41
50
  end
42
51
 
@@ -67,9 +76,13 @@ module DynamicActiveModel
67
76
  # Sets up relationships between created models
68
77
  # @param database [Database] The database instance containing the models
69
78
  # @param relationships [Hash] Custom foreign key relationships to add
79
+ # @param foreign_key_constraints [Boolean] Also relate columns through foreign key constraints
80
+ # @param has_many_through [Boolean] Also add has_many :through across join models
70
81
  # @return [void]
71
- def self.build_relationships!(database, relationships)
82
+ def self.build_relationships!(database, relationships, foreign_key_constraints: false, has_many_through: false)
72
83
  relations = Associations.new(database)
84
+ relations.use_foreign_key_constraints! if foreign_key_constraints
85
+ relations.use_has_many_through! if has_many_through
73
86
  relationships.each do |table_name, foreign_keys|
74
87
  foreign_keys.each do |foreign_key, relationship_name|
75
88
  relations.add_foreign_key(table_name, foreign_key, relationship_name)
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DynamicActiveModel
4
+ # Reads the database's foreign key constraints and resolves them to models, so
5
+ # Associations can relate columns that don't follow the <table>_id naming
6
+ # convention (posts.author_id => users) and self-references (employees.manager_id).
7
+ #
8
+ # Skipped: composite constraints, columns without the id suffix (a belongs_to
9
+ # named after the column itself would hide it), and constraints whose table or
10
+ # referenced table has no model.
11
+ #
12
+ # @example
13
+ # constraints = DynamicActiveModel::ForeignKeyConstraints.new(database)
14
+ # constraints.find(DB::Post, 'author_id') # => #<struct referenced_model=DB::User, ...>
15
+ class ForeignKeyConstraints
16
+ # A constraint resolved to models
17
+ Constraint = Struct.new(:model, :column, :referenced_model, :primary_key, :relationship_name)
18
+
19
+ # @param database [Database] The database whose models' constraints to read
20
+ def initialize(database)
21
+ @database = database
22
+ @constraints = database.models.flat_map { |model| constraints_for(model) }
23
+ .to_h { |constraint| [[constraint.model, constraint.column], constraint] }
24
+ end
25
+
26
+ # @param model [Class] The model with the column
27
+ # @param column [String] The column name
28
+ # @return [Constraint, nil] The column's constraint, if it has one
29
+ def find(model, column)
30
+ @constraints[[model, column]]
31
+ end
32
+
33
+ # @return [Integer] Number of usable constraints
34
+ def size
35
+ @constraints.size
36
+ end
37
+
38
+ private
39
+
40
+ # @param model [Class]
41
+ # @return [Array<Constraint>] The model's usable constraints
42
+ def constraints_for(model)
43
+ model.connection.foreign_keys(model.table_name).filter_map do |foreign_key|
44
+ column = foreign_key.column
45
+ next unless column.is_a?(String) && column.end_with?(ForeignKey.id_suffix)
46
+
47
+ referenced_model = @database.get_model(foreign_key.to_table)
48
+ next unless referenced_model
49
+
50
+ Constraint.new(model, column, referenced_model, foreign_key.primary_key,
51
+ relationship_name(column, referenced_model))
52
+ end
53
+ end
54
+
55
+ # Names the relationship like the naming convention does: author_id => "author",
56
+ # but user_id referencing users => "users", so its has_many stays :posts
57
+ # @param column [String]
58
+ # @param referenced_model [Class]
59
+ # @return [String]
60
+ def relationship_name(column, referenced_model)
61
+ name = column.delete_suffix(ForeignKey.id_suffix)
62
+ table_name = referenced_model.table_name.underscore
63
+ name == table_name.singularize ? table_name : name
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DynamicActiveModel
4
+ # Adds has_many :through associations across join models. A join model is a
5
+ # table with a primary key and a unique index on exactly two columns, each a
6
+ # belongs_to to a different model; other columns (timestamps, audit columns,
7
+ # extra data) don't matter. For user_roles with a unique (user_id, role_id):
8
+ #
9
+ # User has_many :roles, through: :user_roles, source: :role
10
+ # Role has_many :users, through: :user_roles, source: :user
11
+ #
12
+ # A name already taken on the model (association, method or column) is skipped.
13
+ # Tables without a primary key are has_and_belongs_to_many join tables instead.
14
+ class HasManyThrough
15
+ # @param models [Array<Class>] Models with their belongs_to/has_many already built
16
+ # @param table_indexes [Hash] Indexes by table name (see Associations#table_indexes)
17
+ def initialize(models, table_indexes)
18
+ @models = models
19
+ @table_indexes = table_indexes
20
+ end
21
+
22
+ # @return [void]
23
+ def build!
24
+ @models.each { |model| add_through_associations(model) }
25
+ end
26
+
27
+ private
28
+
29
+ # @param join_model [Class]
30
+ def add_through_associations(join_model)
31
+ pair = join_belongs_to(join_model)
32
+ return unless pair
33
+
34
+ first, second = pair
35
+ add_through(join_model, first, second)
36
+ add_through(join_model, second, first)
37
+ end
38
+
39
+ # @param model [Class]
40
+ # @return [Array<ActiveRecord::Reflection::BelongsToReflection>, nil] The two
41
+ # belongs_to a unique two-column index covers, if the model is a join model
42
+ def join_belongs_to(model)
43
+ return unless model.primary_key.is_a?(String)
44
+
45
+ @table_indexes.fetch(model.table_name, []).each do |index|
46
+ next unless index.unique && index.columns.is_a?(Array) && index.columns.size == 2
47
+
48
+ reflections = index.columns.map { |column| belongs_to_for(model, column) }
49
+ return reflections if reflections.all? && reflections.map(&:klass).uniq.size == 2
50
+ end
51
+ nil
52
+ end
53
+
54
+ # @return [ActiveRecord::Reflection::BelongsToReflection, nil]
55
+ def belongs_to_for(model, column)
56
+ model.reflect_on_all_associations(:belongs_to).find { |reflection| reflection.foreign_key.to_s == column }
57
+ end
58
+
59
+ # Gives the owner side of the join a has_many :through to the target side
60
+ # @param join_model [Class]
61
+ # @param owner [ActiveRecord::Reflection::BelongsToReflection] e.g. user_roles.user
62
+ # @param target [ActiveRecord::Reflection::BelongsToReflection] e.g. user_roles.role
63
+ def add_through(join_model, owner, target)
64
+ through = join_association(owner.klass, join_model, owner.foreign_key.to_s)
65
+ name = target.name.to_s.pluralize.to_sym
66
+ return if through.nil? || taken?(owner.klass, name)
67
+
68
+ owner.klass.has_many(name, through: through.name, source: target.name)
69
+ end
70
+
71
+ # @return [ActiveRecord::Reflection::HasManyReflection, nil] The owner's has_many
72
+ # to the join model over that foreign key, e.g. User has_many :user_roles
73
+ def join_association(owner_model, join_model, foreign_key)
74
+ owner_model.reflect_on_all_associations(:has_many).find do |reflection|
75
+ reflection.options[:class_name] == join_model.name && reflection.options[:foreign_key].to_s == foreign_key
76
+ end
77
+ end
78
+
79
+ # @return [Boolean] Whether the name is already an association, method or column
80
+ def taken?(model, name)
81
+ !model.reflect_on_association(name).nil? || model.method_defined?(name) ||
82
+ model.column_names.include?(name.to_s)
83
+ end
84
+ end
85
+ end
@@ -53,6 +53,8 @@ module DynamicActiveModel
53
53
  {
54
54
  connection_options: nil,
55
55
  parent_class: nil,
56
+ foreign_key_constraints: false,
57
+ has_many_through: false,
56
58
  skip_tables: [],
57
59
  relationships: {},
58
60
  table_class_names: {},
@@ -84,6 +86,24 @@ module DynamicActiveModel
84
86
  dynamic_active_model_config[:parent_class]
85
87
  end
86
88
 
89
+ # Sets or gets whether columns are also related through the database's foreign
90
+ # key constraints (see ForeignKeyConstraints)
91
+ # @param enabled [Boolean, nil]
92
+ # @return [Boolean] The current setting
93
+ def foreign_key_constraints(enabled = nil)
94
+ update_config(:foreign_key_constraints, enabled) unless enabled.nil?
95
+ dynamic_active_model_config[:foreign_key_constraints]
96
+ end
97
+
98
+ # Sets or gets whether has_many :through associations are added across join
99
+ # models (see HasManyThrough)
100
+ # @param enabled [Boolean, nil]
101
+ # @return [Boolean] The current setting
102
+ def has_many_through(enabled = nil) # rubocop:disable Naming/PredicatePrefix -- DSL setter/getter
103
+ update_config(:has_many_through, enabled) unless enabled.nil?
104
+ dynamic_active_model_config[:has_many_through]
105
+ end
106
+
87
107
  # Sets or gets the list of tables to skip
88
108
  # @param tables [Array<String>, nil] Tables to skip
89
109
  # @return [Array<String>] The current list of skipped tables
@@ -165,7 +185,9 @@ module DynamicActiveModel
165
185
  skip_tables,
166
186
  relationships,
167
187
  table_class_names,
168
- parent_class: parent_class
188
+ parent_class: parent_class,
189
+ foreign_key_constraints: foreign_key_constraints,
190
+ has_many_through: has_many_through
169
191
  )
170
192
  )
171
193
  database.update_all_models(extensions_path, extensions_suffix) if extensions_path
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fileutils'
4
+
3
5
  module DynamicActiveModel
4
6
  # The TemplateClassFile class generates Ruby source files for ActiveRecord models.
5
7
  # It creates properly formatted class definitions that include:
@@ -26,11 +28,13 @@ module DynamicActiveModel
26
28
  @model = model
27
29
  end
28
30
 
29
- # Creates a Ruby source file for the model
31
+ # Creates a Ruby source file for the model, at dir/<namespace>/<model>.rb
30
32
  # @param dir [String] Directory to create the file in
31
33
  # @return [void]
32
34
  def create_template!(dir)
33
35
  file = "#{dir}/#{@model.name.underscore}.rb"
36
+ # the namespace directory (e.g. db/ for DB::User) may not exist yet
37
+ FileUtils.mkdir_p(File.dirname(file))
34
38
  File.binwrite(file, to_s)
35
39
  end
36
40
 
@@ -44,6 +48,7 @@ module DynamicActiveModel
44
48
  all_has_many_relationships.each do |assoc|
45
49
  append_association!(str, 'has_many', assoc, has_many_association_options(assoc))
46
50
  end
51
+ all_has_many_through_relationships.each { |assoc| append_through_association!(str, assoc) }
47
52
  all_belongs_to_relationships.each do |assoc|
48
53
  append_association!(str, 'belongs_to', assoc, belongs_to_association_options(assoc))
49
54
  end
@@ -66,6 +71,12 @@ module DynamicActiveModel
66
71
  @model.reflect_on_all_associations.grep(ActiveRecord::Reflection::HasManyReflection)
67
72
  end
68
73
 
74
+ # Gets all has_many :through relationships for the model
75
+ # @return [Array] List of has_many :through associations
76
+ def all_has_many_through_relationships
77
+ @model.reflect_on_all_associations(:has_many).grep(ActiveRecord::Reflection::ThroughReflection)
78
+ end
79
+
69
80
  # Gets all belongs_to relationships for the model
70
81
  # @return [Array<ActiveRecord::Reflection::BelongsToReflection>]
71
82
  def all_belongs_to_relationships
@@ -84,6 +95,14 @@ module DynamicActiveModel
84
95
  @model.reflect_on_all_associations.grep(ActiveRecord::Reflection::HasAndBelongsToManyReflection)
85
96
  end
86
97
 
98
+ # Appends a has_many :through declaration; through and source are symbols
99
+ # @param str [String] The source string being built
100
+ # @param assoc [ActiveRecord::Reflection::ThroughReflection] The association to add
101
+ def append_through_association!(str, assoc)
102
+ str << " has_many #{assoc.name.inspect}, through: #{assoc.options[:through].to_sym.inspect}, " \
103
+ "source: #{assoc.options[:source].to_sym.inspect}\n"
104
+ end
105
+
87
106
  # Appends an association definition to the source string
88
107
  # @param str [String] The source string being built
89
108
  # @param assoc_type [String] The association macro, e.g. 'has_many'
@@ -2,5 +2,5 @@
2
2
 
3
3
  module DynamicActiveModel
4
4
  # Gem version, bumped by release-please
5
- VERSION = '1.0.0'
5
+ VERSION = '1.2.0'
6
6
  end
@@ -37,6 +37,12 @@ module DynamicActiveModel
37
37
  # Handles foreign key relationships and constraints
38
38
  autoload :ForeignKey, 'dynamic-active-model/foreign_key'
39
39
 
40
+ # Resolves the database's foreign key constraints to models
41
+ autoload :ForeignKeyConstraints, 'dynamic-active-model/foreign_key_constraints'
42
+
43
+ # Adds has_many :through associations across join models
44
+ autoload :HasManyThrough, 'dynamic-active-model/has_many_through'
45
+
40
46
  # Manages automatic discovery and setup of model relationships
41
47
  autoload :Associations, 'dynamic-active-model/associations'
42
48
 
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: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Doug Youch
@@ -56,6 +56,8 @@ files:
56
56
  - lib/dynamic-active-model/explorer.rb
57
57
  - lib/dynamic-active-model/factory.rb
58
58
  - lib/dynamic-active-model/foreign_key.rb
59
+ - lib/dynamic-active-model/foreign_key_constraints.rb
60
+ - lib/dynamic-active-model/has_many_through.rb
59
61
  - lib/dynamic-active-model/setup.rb
60
62
  - lib/dynamic-active-model/template_class_file.rb
61
63
  - lib/dynamic-active-model/version.rb