activerecord-model_inheritance 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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