activerecord-model_inheritance 1.0.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.
Files changed (26) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +451 -0
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +21 -0
  6. data/README.md +255 -0
  7. data/Rakefile +7 -0
  8. data/lib/active_record/model_inheritance/error.rb +5 -0
  9. data/lib/active_record/model_inheritance/model.rb +169 -0
  10. data/lib/active_record/model_inheritance/version.rb +5 -0
  11. data/lib/active_record/model_inheritance/view_definition.rb +112 -0
  12. data/lib/active_record/model_inheritance.rb +57 -0
  13. data/lib/activerecord/model_inheritance.rb +1 -0
  14. data/lib/generators/active_record/model_inheritance/generators.rb +6 -0
  15. data/lib/generators/active_record/model_inheritance/model/model_generator.rb +75 -0
  16. data/lib/generators/active_record/model_inheritance/model/templates/definition.erb +1 -0
  17. data/lib/generators/active_record/model_inheritance/model/templates/model.erb +2 -0
  18. data/lib/generators/active_record/model_inheritance/model/templates/model_config.erb +3 -0
  19. data/lib/generators/active_record/model_inheritance/view/templates/create_migration.erb +13 -0
  20. data/lib/generators/active_record/model_inheritance/view/templates/update_migration.erb +5 -0
  21. data/lib/generators/active_record/model_inheritance/view/view_generator.rb +121 -0
  22. data/sig/active_record/model_inheritance/generators/model_generator.rbs +23 -0
  23. data/sig/active_record/model_inheritance/generators/view_generator.rbs +45 -0
  24. data/sig/active_record/model_inheritance/version.rbs +5 -0
  25. data/sig/active_record/model_inheritance/view_definition.rbs +20 -0
  26. metadata +127 -0
data/README.md ADDED
@@ -0,0 +1,255 @@
1
+ # Model Inheritance
2
+
3
+ An attempt at real inheritance for ActiveRecord models.
4
+
5
+ This gem leverages database views (thanks to [Scenic](https://github.com/scenic-views/scenic)) to compose models
6
+ from other models, kind of like POROs inheritance [with limitations](#limitations). Views are defined using
7
+ [Arel](https://www.rubydoc.info/gems/arel) instead of SQL, which is cleaner and allows for easier integration.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'activerecord-model_inheritance', '~> 1.0'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```bash
20
+ bundle
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ First of all, if you want to make intensive use of this gem, being familiar on how [Scenic](https://github.com/scenic-views/scenic)
26
+ works is highly recommended.
27
+
28
+ ### Quickstart
29
+
30
+ Assuming you want a new `DerivedModel` that inherits from a preexisting `BaseModel`, follow these steps:
31
+
32
+ - Generate the new model and its view definition:
33
+ ```bash
34
+ rails g active_record:model_inheritance:model DerivedModel BaseModel
35
+ ```
36
+
37
+ - Edit the generated model and definition, if needed
38
+
39
+ - Generate the SQL definition of the view and the initial migration:
40
+ ```bash
41
+ rails g active_record:model_inheritance:view DerivedModel
42
+ ```
43
+
44
+ - Edit the generated migration if needed
45
+
46
+ - Finally, run the migration:
47
+ ```bash
48
+ rails db:migrate
49
+ ```
50
+
51
+ Keep in mind that you need to generate a new version of the SQL definition whenever your view definition changes,
52
+ for example when you want to add some fields to the derived model.
53
+ To do so, just run the same generator as again:
54
+ ```bash
55
+ rails g active_record:model_inheritance:view DerivedModel
56
+ ```
57
+ This will take care of everything, including generating the migration to update the view.
58
+ It works similarly to Scenic.
59
+
60
+ ### Concepts
61
+
62
+ A database view is like a _virtual table_ where the schema, as well as the data it contains, are
63
+ defined by a plain old SQL query. Of course, since views are just query results disguised as tables, you can't write into them.
64
+ So, at the end of the day, all this gem does is enabling write operations to Scenic view backed models.
65
+
66
+ To achieve something _resembling_ real inheritance between models, the **inner model** is introduced,
67
+ which is a third entity between the **base model** (the one you want to inherit from)
68
+ and the **derived model** (the new one you're creating).
69
+
70
+ The **inner model** holds the additional pieces your **derived model** should have.
71
+
72
+ When you apply changes to a **derived model**, those changes are mapped to **inner** and **base** models.
73
+ For example, if the **derived model** has the fields `foo` and `bar`, coming respectively from **inner** and **base** models,
74
+ changes to `foo` will be saved to the **inner model**, and changes to `bar` will be saved to the **base model**.
75
+ This way, the database view backing the **derived model** is always accessed in read-only mode.
76
+
77
+ ### Configuration
78
+
79
+ If you're using Rails, the following is the code you would put inside an initializer
80
+ to configure this gem as it is configured by default. If you're ok with this defaults, then you don't need to
81
+ configure anything.
82
+ ```ruby
83
+ # config/initializers/model_inheritance.rb
84
+
85
+ ActiveRecord::ModelInheritance.configure do |config|
86
+ ## derived model options
87
+
88
+ # name of the dynamically generated inner model class
89
+ config.inner_class_name = 'Inner'
90
+
91
+ # base class of the dynamically generated inner model
92
+ config.inner_base_class = ApplicationRecord
93
+
94
+ # name of the belongs_to association from derived model to base model
95
+ config.base_reference_name = :model_inheritance_base
96
+
97
+ # name of the belongs_to association from derived model to its own inner model
98
+ config.inner_reference_name = :model_inheritance_inner
99
+
100
+ # whether to inherit enums from the base model
101
+ # only enums relevant to inherited fields will be added
102
+ config.inherit_enums = true
103
+
104
+ # whether to delegate missing methods from derived model to base model
105
+ config.delegate_missing_to_base = true
106
+
107
+ ## paths options
108
+
109
+ # these are self explanatory
110
+ config.models_path = Rails.root.join('app/models')
111
+ config.migrations_path = Rails.root.join('db/migrate')
112
+
113
+ # where to save generated SQL definitions (Scenic default)
114
+ config.views_path = Rails.root.join('db/views')
115
+
116
+ # where to save view definitions
117
+ config.definitions_path = Rails.root.join('db/views/model_inheritance')
118
+ end
119
+ ```
120
+ If you're not using Rails, the default configuration stays the same, except:
121
+ ```ruby
122
+ config.inner_base_class = ActiveRecord::Base
123
+
124
+ config.models_path = Pathname('app/models')
125
+ config.migrations_path = Pathname('db/migrate')
126
+
127
+ config.views_path = Pathname('db/views')
128
+ config.definitions_path = Pathname('db/views/model_inheritance')
129
+ ```
130
+
131
+ You can pass options to `derives_from` if you want to override the global derived models configuration on a per model basis:
132
+ ```ruby
133
+ class DerivedModel < ApplicationRecord
134
+ include ActiveRecord::ModelInheritance::Model
135
+
136
+ derives_from BaseModel,
137
+ inner_class_name: 'Inner',
138
+ inner_base_class: ApplicationRecord,
139
+ base_reference_name: :model_inheritance_base,
140
+ inner_reference_name: :model_inheritance_inner,
141
+ inherit_enums: true,
142
+ delegate_missing_to_base: true
143
+ end
144
+ ```
145
+
146
+ ### View definitions
147
+ A view definition is responsible of:
148
+ - providing a convenient way of defining views using Arel
149
+ - keeping a map of which attributes belong respectively to the base and inner model
150
+
151
+ By default, the derived model will get **all** the fields from base and inner.
152
+ If that's not what you want, you can override the default behaviour like in the following example:
153
+ ```ruby
154
+ # db/views/model_inheritance/derived_models.rb
155
+
156
+ ActiveRecord::ModelInheritance::ViewDefinition.define_derived_view DerivedModel do |inner_table, base_table|
157
+ inner_table
158
+ # all fields from inner
159
+ .project(inner_table[Arel.star])
160
+ # only some fields from base
161
+ .project(
162
+ base_table[:foo],
163
+ base_table[:bar],
164
+ base_table[:baz]
165
+ )
166
+ .join(base_table)
167
+ .on(inner_table[:model_inheritance_base_id].eq base_table[:id])
168
+ end
169
+ ```
170
+ Here, Arel is used to describe how you want the base and inner table joined.
171
+ The block parameters `inner_table` and `base_table` are both [Arel::SelectTable](https://www.rubydoc.info/gems/arel/Arel/Table)s,
172
+ representing the inner model table and base model table respectively.
173
+ The code inside the block **must** evaluate to [Arel::SelectManager](https://www.rubydoc.info/gems/arel/Arel/SelectManager).
174
+ Note that if you set the option `base_reference_name` to something different to `:model_inheritance_base`, you have to
175
+ change the join condition accordingly.
176
+
177
+ When you run the `active_record:model_inheritance:view` generator, one of the things that's done is converting that Arel::SelectManager
178
+ (the default one or your custom provided one) to SQL. In the case of the above example, the generated SQL will look something
179
+ like this:
180
+ ```sql
181
+ /* db/views/derived_models_v01.sql */
182
+
183
+ SELECT "derived_model_inners".*,
184
+ "base_models"."foo",
185
+ "base_models"."bar",
186
+ "base_models"."baz"
187
+ FROM "derived_model_inners"
188
+ INNER JOIN "base_models"
189
+ ON "derived_model_inners"."model_inheritance_base_id" = "base_models"."id"
190
+ ```
191
+ This is how the database view backing the derived model will be created.
192
+
193
+ ### Sharing code between derived and inner
194
+ Sometimes it could be useful to have code replicated in both derived and inner models.
195
+ This can be done by passing a block to `derives_from`.
196
+ ```ruby
197
+ class DerivedModel < ApplicationRecord
198
+ include ActiveRecord::ModelInheritance::Model
199
+
200
+ derives_from BaseModel do
201
+ def foo
202
+ # ...
203
+ end
204
+ end
205
+ end
206
+ ```
207
+ In the above example, `foo` you will be declared in both derived and inner models.
208
+
209
+ ### Accessing the inner model
210
+ If for some reason you want to directly access the inner model, you can:
211
+ ```ruby
212
+ DerivedModel::Inner # the inner model class
213
+
214
+ DerivedModel::Foo # in case you've set inner_class_name to 'Foo'
215
+
216
+ DerivedModel.first._model_inheritance_inner # instance of the inner model
217
+ ```
218
+
219
+ ### A few words on multiple inheritance
220
+ This gem doesn't strictly prohibit multiple inheritance, and in _in theory_ it should be possible to implement.
221
+ Currently there are no plans on this, but if you find a clean solution you can share your work with us! (see [Contributing](#contributing))
222
+
223
+ ## Limitations
224
+ - A derived model is not a subclass of its base model
225
+ - Query methods called on base models will return only base models
226
+ - Query methods called on derived models will return only derived models
227
+
228
+ ## Future developments
229
+ - Improved and more comprehensive documentation
230
+ - Some ways around current limitations
231
+ - Testing with a dummy Rails application
232
+
233
+ ## Version numbers
234
+
235
+ Model Inheritance loosely follows [Semantic Versioning](https://semver.org/), with a hard guarantee that breaking changes to the public API will always coincide with an increase to the `MAJOR` number.
236
+
237
+ Version numbers are in three parts: `MAJOR.MINOR.PATCH`.
238
+
239
+ - Breaking changes to the public API increment the `MAJOR`. There may also be changes that would otherwise increase the `MINOR` or the `PATCH`.
240
+ - Additions, deprecations, and "big" non breaking changes to the public API increment the `MINOR`. There may also be changes that would otherwise increase the `PATCH`.
241
+ - Bug fixes and "small" non breaking changes to the public API increment the `PATCH`.
242
+
243
+ Notice that any feature deprecated by a minor release can be expected to be removed by the next major release.
244
+
245
+ ## Changelog
246
+
247
+ Full list of changes in [CHANGELOG.md](CHANGELOG.md)
248
+
249
+ ## Contributing
250
+
251
+ Bug reports and pull requests are welcome on GitHub at https://github.com/moku-io/activerecord-model_inheritance.
252
+
253
+ ## License
254
+
255
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rubocop/rake_task'
4
+
5
+ RuboCop::RakeTask.new
6
+
7
+ task default: [:rubocop]
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ module ModelInheritance
3
+ class Error < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,169 @@
1
+ require 'active_support'
2
+ require 'active_record'
3
+ require_relative 'error'
4
+ require_relative 'view_definition'
5
+
6
+ module ActiveRecord
7
+ module ModelInheritance
8
+ class InheritanceError < Error; end
9
+
10
+ module Model
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ class_attribute :model_inheritance_base_model
15
+ class_attribute :model_inheritance_inner_model
16
+ class_attribute :model_inheritance_base_name
17
+ class_attribute :model_inheritance_inner_name
18
+ class_attribute :model_inheritance_view_definition
19
+ class_attribute :model_inheritance_attributes_mapping
20
+
21
+ self.primary_key = :id
22
+ self.record_timestamps = false
23
+ end
24
+
25
+ class_methods do
26
+ def derives_from(base_model,
27
+ base_reference_name: ModelInheritance.base_reference_name,
28
+ inner_reference_name: ModelInheritance.inner_reference_name,
29
+ inherit_enums: ModelInheritance.inherit_enums,
30
+ inner_base_class: ModelInheritance.inner_base_class,
31
+ inner_class_name: ModelInheritance.inner_class_name,
32
+ delegate_missing_to_base: ModelInheritance.delegate_missing_to_base,
33
+ &block)
34
+ self.model_inheritance_base_name = base_reference_name
35
+ self.model_inheritance_inner_name = inner_reference_name
36
+ self.model_inheritance_base_model = base_model
37
+
38
+ base_ref_foreign_key = "#{model_inheritance_base_model.model_name.singular}_id"
39
+ base_ref = proc do
40
+ belongs_to base_reference_name,
41
+ class_name: "::#{base_model.name}",
42
+ foreign_key: base_ref_foreign_key
43
+ end
44
+
45
+ inner_model = Class.new inner_base_class do
46
+ instance_exec(&block) if block.present?
47
+ instance_exec(&base_ref)
48
+ end
49
+ instance_exec(&block) if block.present?
50
+ instance_exec(&base_ref)
51
+
52
+ const_set inner_class_name, inner_model
53
+ belongs_to inner_reference_name, class_name: inner_class_name, foreign_key: :id
54
+
55
+ # the secret ingredient
56
+ accepts_nested_attributes_for base_reference_name
57
+ accepts_nested_attributes_for inner_reference_name
58
+
59
+ self.model_inheritance_inner_model = inner_model
60
+ self.model_inheritance_view_definition = ViewDefinition.from_model self
61
+ self.model_inheritance_attributes_mapping = model_inheritance_view_definition.attributes_mapping
62
+
63
+ # prevents attributes from being touched when updating
64
+ # not strictly necessary, but better safe than sorry
65
+ attr_readonly attribute_names.map(&:to_sym)
66
+
67
+ if inherit_enums
68
+ base_model.defined_enums.each do |attribute, enum_values|
69
+ attribute = attribute.to_sym
70
+ enum attribute, enum_values if model_inheritance_attributes_mapping[:base].include? attribute
71
+ end
72
+ end
73
+
74
+ delegate_missing_to base_reference_name if delegate_missing_to_base
75
+ end
76
+
77
+ def partition_attributes attributes
78
+ inner_attributes = attributes.select { |key| model_inheritance_attributes_mapping[:inner].include? key.to_sym }
79
+ base_attributes = attributes.select { |key| model_inheritance_attributes_mapping[:base].include? key.to_sym }
80
+
81
+ [inner_attributes, base_attributes]
82
+ end
83
+
84
+ def create(...)
85
+ super.reload
86
+ end
87
+
88
+ def create!(...)
89
+ super.reload
90
+ end
91
+
92
+ # overriding the following methods to prevent ConnectionAdapter from touching the underlying view
93
+
94
+ def _insert_record(...)
95
+ nil
96
+ end
97
+
98
+ def _update_record(...)
99
+ nil
100
+ end
101
+
102
+ def _delete_record(...)
103
+ nil
104
+ end
105
+ end
106
+
107
+ def save(**options, &)
108
+ prepare_save
109
+ super && _model_inheritance_base.save
110
+ end
111
+
112
+ def save!(**options, &)
113
+ prepare_save
114
+ super
115
+ _model_inheritance_base.save!
116
+ end
117
+
118
+ def destroy
119
+ _model_inheritance_inner.destroy
120
+ super
121
+ end
122
+
123
+ def delete
124
+ _model_inheritance_inner.delete
125
+ super
126
+ end
127
+
128
+ def _model_inheritance_base
129
+ public_send model_inheritance_base_name
130
+ end
131
+
132
+ def _model_inheritance_inner
133
+ public_send model_inheritance_inner_name
134
+ end
135
+
136
+ private
137
+
138
+ def prepare_save
139
+ inner_attributes, base_attributes = self.class.partition_attributes attributes_for_database
140
+
141
+ if new_record?
142
+ unless _model_inheritance_base.present?
143
+ raise InheritanceError, "#{model_inheritance_base_name} must be present"
144
+ end
145
+
146
+ # pass updated base attributes to base model
147
+ # this way it gets automatically updated
148
+ base_attributes.compact!
149
+ _model_inheritance_base.assign_attributes base_attributes if base_attributes.present?
150
+
151
+ attributes = {
152
+ model_inheritance_base_name => _model_inheritance_base,
153
+ "#{model_inheritance_inner_name}_attributes".to_sym => inner_attributes
154
+ }
155
+ else
156
+ inner_attributes[:id] = id
157
+ base_attributes[:id] = _model_inheritance_base.id
158
+
159
+ attributes = {
160
+ "#{model_inheritance_base_name}_attributes".to_sym => base_attributes,
161
+ "#{model_inheritance_inner_name}_attributes".to_sym => inner_attributes
162
+ }
163
+ end
164
+
165
+ assign_attributes attributes
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ module ModelInheritance
3
+ VERSION = '1.0.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,112 @@
1
+ require 'arel'
2
+ require_relative 'error'
3
+
4
+ module ActiveRecord
5
+ module ModelInheritance
6
+ class DefinitionError < Error; end
7
+
8
+ class ViewDefinition
9
+ attr_reader :definition
10
+ attr_reader :model_class
11
+
12
+ def initialize model_class, definition
13
+ @model_class = model_class
14
+ @definition = definition
15
+ end
16
+
17
+ def to_sql
18
+ @definition.to_sql
19
+ end
20
+
21
+ def attributes_mapping
22
+ definition
23
+ .projections
24
+ .each_with_object({base: [], inner: []}) do |projection, attributes_mapping|
25
+ if projection.is_a? Arel::Nodes::As
26
+ relation = projection.left.relation
27
+ name = projection.right
28
+ else
29
+ relation = projection.relation
30
+ name = projection.name
31
+ end
32
+
33
+ case relation
34
+ when model_class.model_inheritance_inner_model.arel_table
35
+ relation_type = :inner
36
+ relation_model = model_class.model_inheritance_inner_model
37
+ when model_class.model_inheritance_base_model.arel_table
38
+ relation_type = :base
39
+ relation_model = model_class.model_inheritance_base_model
40
+ else
41
+ raise DefinitionError, "Invalid \"#{relation}\" relation"
42
+ end
43
+
44
+ attributes = if name == Arel.star
45
+ relation_model.attribute_names.map(&:to_sym)
46
+ else
47
+ [name.to_sym]
48
+ end
49
+
50
+ attributes_mapping[relation_type] += attributes
51
+ end
52
+ end
53
+
54
+ def self.from_model model_class
55
+ unless model_class.include? Model
56
+ raise ArgumentError, "#{model_class.name} doesn't include ActiveRecord::ModelInheritance::Model"
57
+ end
58
+
59
+ ViewDefinition.from_name model_class.model_name.plural
60
+ end
61
+
62
+ def self.from_name name
63
+ definition_filename = Pathname(ModelInheritance.config.definitions_path).join "#{name}.rb"
64
+ raise ArgumentError, "Definition for \"#{name}\" doesn't exist" unless definition_filename.file?
65
+
66
+ eval File.read definition_filename
67
+ end
68
+
69
+ def self.define_derived_view model_class, &block
70
+ inner_model = model_class.model_inheritance_inner_model
71
+ base_model = model_class.model_inheritance_base_model
72
+
73
+ inner_table = inner_model.arel_table
74
+ base_table = base_model.arel_table
75
+
76
+ definition = if block_given?
77
+ block.call(inner_table, base_table).tap do |d|
78
+ unless d.is_a? Arel::SelectManager
79
+ raise DefinitionError, 'Defined view must evaluate to Arel::SelectManager'
80
+ end
81
+ end
82
+ else
83
+ selected_base_columns = if inner_model.primary_key == base_model.primary_key
84
+ # this is a common naming conflict problem
85
+ # makes sense to try and solve automatically
86
+
87
+ # just delete the base primary key from columns that will be selected
88
+ base_model
89
+ .column_names
90
+ .dup
91
+ .delete_if { |column_name| column_name == base_model.primary_key }
92
+ else
93
+ base_model.column_names
94
+ end.map { |column_name| base_table[column_name.to_sym] }
95
+
96
+ base_reference = model_class
97
+ .reflect_on_association(model_class.model_inheritance_base_name)
98
+ .foreign_key
99
+ .to_sym
100
+
101
+ inner_table
102
+ .project(inner_table[Arel.star])
103
+ .project(*selected_base_columns)
104
+ .join(base_table)
105
+ .on(inner_table[base_reference].eq base_table[base_model.primary_key])
106
+ end
107
+
108
+ ViewDefinition.new model_class, definition
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,57 @@
1
+ require 'scenic'
2
+ require 'active_record'
3
+ require 'active_support'
4
+ require_relative 'model_inheritance/version'
5
+
6
+ module ActiveRecord
7
+ module ModelInheritance
8
+ include ActiveSupport::Configurable
9
+
10
+ config.define_singleton_method :define_lazy_property do |key, &block|
11
+ self[key] = nil
12
+
13
+ define_singleton_method key do
14
+ self[key] || (self[key] = block.call)
15
+ end
16
+ end
17
+
18
+ config.define_singleton_method :define_lazy_path do |name, *dirs|
19
+ define_lazy_property name do
20
+ if defined?(Rails.root) && Rails.root
21
+ Rails.root.join(*dirs)
22
+ else
23
+ Pathname(dirs.join '/')
24
+ end
25
+ end
26
+
27
+ define_singleton_method "#{name}=".to_sym do |path|
28
+ self[name] = Pathname(path)
29
+ end
30
+ end
31
+
32
+ config.base_reference_name = :model_inheritance_base
33
+ config.inner_reference_name = :model_inheritance_inner
34
+ config.inner_class_name = 'Inner'
35
+ config.inherit_enums = true
36
+ config.delegate_missing_to_base = true
37
+
38
+ config.define_lazy_path :views_path, 'db', 'views'
39
+ config.define_lazy_path :models_path, 'app', 'models'
40
+ config.define_lazy_path :migrations_path, 'db', 'migrate'
41
+ config.define_lazy_path :definitions_path, 'db', 'views', 'model_inheritance'
42
+
43
+ config.define_lazy_property :inner_base_class do
44
+ if defined? ApplicationRecord
45
+ ApplicationRecord
46
+ else
47
+ ActiveRecord::Base
48
+ end
49
+ end
50
+
51
+ singleton_class.delegate(*config.keys, to: :config)
52
+ end
53
+ end
54
+
55
+ require_relative 'model_inheritance/error'
56
+ require_relative 'model_inheritance/model'
57
+ require_relative 'model_inheritance/view_definition'
@@ -0,0 +1 @@
1
+ require_relative '../active_record/model_inheritance'
@@ -0,0 +1,6 @@
1
+ module ActiveRecord
2
+ module ModelInheritance
3
+ module Generators
4
+ end
5
+ end
6
+ end