multiple_table_inheritance 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,215 @@
1
+ Multiple Table Inheritance
2
+ ==========================
3
+
4
+ Multiple Table Inheritance is a plugin designed to allow for multiple table
5
+ inheritance between your database tables and your ActiveRecord models.
6
+
7
+ This plugin is a derivative of the original class-table-inheritance gem, which
8
+ can be found at http://github.com/brunofrank/class-table-inheritance
9
+
10
+
11
+ Compatibility
12
+ =============
13
+
14
+ Multiple Table Inheritance is Rails 3.x compatible.
15
+
16
+
17
+ How to Install
18
+ ==============
19
+
20
+ From the command line:
21
+
22
+ gem install multiple-table-inheritance
23
+
24
+ From your Gemfile:
25
+
26
+ gem 'multiple_table_inheritance', '~> 0.1.0'
27
+
28
+ Usage
29
+ =====
30
+
31
+ Running Migrations
32
+ ------------------
33
+
34
+ When creating your tables, the table representing the superclass must include a
35
+ `subtype` string column. (Optionally, you can provide a custom name for this
36
+ field and provide the custom name in your model options.) It is recommended
37
+ that this column be non-null for sanity.
38
+
39
+ create_table :employees do |t|
40
+ t.string :subtype, :null => false
41
+ t.string :first_name, :null => false
42
+ t.string :last_name, :null => false
43
+ t.integer :salary, :null => false
44
+ t.timestamps
45
+ end
46
+
47
+ When creating tables that are derived from your superclass table, simple
48
+ provide the `:inherits` hash option to your `create_table` call.
49
+
50
+ create_table :programmers, :inherits => :employee do |t|
51
+ t.datetime :training_completed_at
52
+ end
53
+
54
+ create_table :managers, :inherits => :employee do |t|
55
+ t.integer :bonus, :null => false
56
+ end
57
+
58
+ Creating Models
59
+ ---------------
60
+
61
+ The `acts_as_superclass` method is used to represent that a model can be
62
+ extended.
63
+
64
+ class Employee < ActiveRecord::Base
65
+ acts_as_superclass
66
+ end
67
+
68
+ Conversely, the `inherits_from` method is used to represent that a given model
69
+ extends another model. It takes one optional parameter, which is the symbol
70
+ desired for referencing the relationship.
71
+
72
+ class Programmer < ActiveRecord::Base
73
+ inherits_from :employee
74
+ end
75
+
76
+ class Manager < ActiveRecord::Base
77
+ inherits_from :employee
78
+ end
79
+
80
+ Additional options can be passed to represent the exact relationship structure.
81
+ Specifically, any option that can be provided to `belongs_to` can be provided
82
+ to `inherits_from`. (Presently, this has only be tested to work with the
83
+ `:class_name` option.)
84
+
85
+ class Resources::Manager < ActiveRecord::Base
86
+ inherits_from :employee, :class_name => 'Resources::Employee'
87
+ end
88
+
89
+ Creating Records
90
+ ----------------
91
+
92
+ Programmer.create(
93
+ :first_name => 'Bob',
94
+ :last_name => 'Smith',
95
+ :salary => 65000,
96
+ :training_completed_at => 3.years.ago)
97
+
98
+ Manager.create(
99
+ :first_name => 'Joe'
100
+ :last_name => 'Schmoe',
101
+ :salary => 75000,
102
+ :bonus => 5000)
103
+
104
+ Retrieving Records
105
+ ------------------
106
+
107
+ Records can be retrieved explicitly by their own type.
108
+
109
+ programmer = Programmer.first # <Programmer employee_id: 1 training_completed_at: "2009-03-06 00:30:00">
110
+ programmer.id # 1
111
+ programmer.first_name # "Bob"
112
+ programmer.last_name # "Smith"
113
+ programmer.salary # 65000
114
+
115
+ Records can be retrieved implicitly by the superclass type.
116
+
117
+ employees = Employee.limit(2) # [<Programmer employee_id: 1 training_completed_at: "2009-03-06 00:30:00">,
118
+ <Manager employee_id: 2 bonus: 5000>]
119
+ employees.first.class # Programmer
120
+ employees.last.class # Manager
121
+ employees.first.bonus # undefined method `bonus`
122
+ employees.last.bonus # 5000
123
+
124
+ Deleting Records
125
+ ----------------
126
+
127
+ Records can be deleted by either the parent or child reference.
128
+
129
+ Manager.first.destroy # destroys associated Employee reference as well
130
+ Employee.first.destroy # destroys associated Manager reference as well
131
+
132
+ Validation
133
+ ----------
134
+
135
+ When creating a new record that inherits from another model, validation is
136
+ taken into consideration across both models.
137
+
138
+ class ::Employee < ActiveRecord::Base
139
+ acts_as_superclass
140
+ validates :first_name, :presence => true
141
+ validates :last_name, :presence => true
142
+ validates :salary, :presence => true, :numericality => { :min => 0 }
143
+ end
144
+
145
+ class ::Programmer < ActiveRecord::Base
146
+ inherits_from :employee
147
+ end
148
+
149
+ class ::Manager < ActiveRecord::Base
150
+ inherits_from :employee
151
+ validates :bonus, :presence => true, :numericality => true
152
+ end
153
+
154
+ Programmer.create(:first_name => 'Bob', :last_name => 'Jones', :salary => -50)
155
+ # fails because :salary must be >= 0
156
+
157
+ Manager.create!(:first_name => 'Bob', :last_name => 'Jones', :salary => 75000)
158
+ # fails because :bonus is not present
159
+
160
+ Mass Assignment Security
161
+ ------------------------
162
+
163
+ Mass assignment security can optionally be used in the same manner you would
164
+ for a normal ActiveRecord model.
165
+
166
+ class Employee < ActiveRecord::Base
167
+ acts_as_superclass
168
+ attr_accessible :first_name, :last_name, :salary
169
+ end
170
+
171
+ class Manager < ActiveRecord::Base
172
+ inherits_from :employee
173
+ attr_accessible :bonus
174
+ end
175
+
176
+ **NOTE:** When an ActiveRecord model does not make a call to `attr_accessible`,
177
+ all its fields are presumed to be accessible. Currently, when using
178
+ MultipleTableInheritance, if a parent class does not call `attr_accesible` and
179
+ one of its children does, then the parent's attributes cannot properly be
180
+ stored. This will be fixed in a future release.
181
+
182
+ Associations
183
+ ------------
184
+
185
+ Associations will also work in the same way as other attributes.
186
+
187
+ class Team < ActiveRecord::Base
188
+ attr_accessible :name
189
+ end
190
+
191
+ class Employee < ActiveRecord::Base
192
+ acts_as_superclass
193
+ belongs_to :team
194
+ end
195
+
196
+ class ::Programmer < ActiveRecord::Base
197
+ inherits_from :employee
198
+ has_many :known_languages
199
+ has_many :languages, :through => :known_languages
200
+ end
201
+
202
+ class ::Language < ActiveRecord::Base
203
+ attr_accessible :name
204
+ has_many :known_languages
205
+ has_many :programmers, :through => :known_languages
206
+ end
207
+
208
+ class ::KnownLanguage < ActiveRecord::Base
209
+ belongs_to :programmer
210
+ belongs_to :language
211
+ end
212
+
213
+ programmer = Programmer.first # <Programmer employee_id: 1 training_completed_at: "2009-03-06 00:30:00">
214
+ programmer.languages.collect(&:name) # ['Java', 'C++']
215
+ programmer.team.name # 'Website Front-End'
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new('spec')
4
+
5
+ desc 'Default: run unit specs.'
6
+ task :default => :spec
7
+
8
+ # require 'rake'
9
+ # require 'rake/testtask'
10
+ # require 'rake/rdoctask'
11
+ #
12
+ # desc 'Default: run unit tests.'
13
+ # task :default => :test
14
+ #
15
+ # desc 'Test the multiple_table_inheritance plugin.'
16
+ # Rake::TestTask.new(:test) do |t|
17
+ # t.libs << 'lib'
18
+ # t.libs << 'test'
19
+ # t.pattern = 'test/**/*_test.rb'
20
+ # t.verbose = true
21
+ # end
22
+ #
23
+ # desc 'Generate documentation for the multiple_table_inheritance plugin.'
24
+ # Rake::RDocTask.new(:rdoc) do |rdoc|
25
+ # rdoc.rdoc_dir = 'rdoc'
26
+ # rdoc.title = 'MultipleTableInheritance'
27
+ # rdoc.options << '--line-numbers' << '--inline-source'
28
+ # rdoc.rdoc_files.include('README')
29
+ # rdoc.rdoc_files.include('lib/**/*.rb')
30
+ # end
31
+ #
32
+ # begin
33
+ # require 'jeweler'
34
+ # Jeweler::Tasks.new do |gem|
35
+ # gem.name = "multiple_table_inheritance"
36
+ # gem.summary = "ActiveRecord plugin designed to allow simple multiple table inheritance."
37
+ # gem.description = "ActiveRecord plugin designed to allow simple multiple table inheritance."
38
+ # gem.email = "tvdeyen@gmail.com"
39
+ # gem.homepage = "http://github.com/tvdeyen/multiple_table_inheritance"
40
+ # gem.authors = `git log --pretty=format:"%an"`.split("\n").uniq.sort
41
+ # gem.add_dependency "activerecord", ">=3.0.0"
42
+ # end
43
+ # Jeweler::GemcutterTasks.new
44
+ # rescue LoadError
45
+ # puts "Jeweler (or a dependency) not available."
46
+ # puts "Install it with: gem install jeweler"
47
+ # end
@@ -0,0 +1,125 @@
1
+ module MultipleTableInheritance
2
+ module ActiveRecord
3
+ module Child
4
+ def self.default_options
5
+ { :dependent => :destroy, :inherit_methods => false }
6
+ end
7
+
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ base.class_attribute :parent_association_name
11
+ end
12
+
13
+ module ClassMethods
14
+ def inherits_from(association_name, options={})
15
+ # Standardize options, and remove those that should not affect the belongs_to relationship
16
+ options = Child::default_options.merge(options.to_options.reject { |k,v| v.nil? })
17
+ inherit_methods = options.delete(:inherit_methods)
18
+
19
+ include InstanceMethods
20
+ include DelegateMethods if inherit_methods
21
+
22
+ # Set association references.
23
+ self.parent_association_name = association_name.to_sym
24
+ self.primary_key = "#{parent_association_name}_id"
25
+
26
+ # Ensure parent association is always returned.
27
+ define_method("#{parent_association_name}_with_autobuild") do
28
+ send("#{parent_association_name}_without_autobuild") || send("build_#{parent_association_name}")
29
+ end
30
+
31
+ # Allow getting and setting of parent attributes and relationships.
32
+ inherited_columns_and_associations.each do |name|
33
+ delegate name, "#{name}=", :to => parent_association_name
34
+ end
35
+
36
+ # Ensure parent's accessible attributes are accessible in child.
37
+ parent_association_class.accessible_attributes.each do |attr|
38
+ attr_accessible attr.to_sym
39
+ end
40
+
41
+ # Bind relationship, handle validation, and save properly.
42
+ belongs_to parent_association_name, options
43
+ alias_method_chain parent_association_name, :autobuild
44
+ before_validation :set_association_subtype
45
+ validate :parent_association_must_be_valid
46
+ before_save :parent_association_must_be_saved
47
+ end
48
+
49
+ private
50
+
51
+ def parent_association_class
52
+ reflection = create_reflection(:belongs_to, parent_association_name, {}, self)
53
+ reflection.class_name.constantize
54
+ end
55
+
56
+ def inherited_columns_and_associations
57
+ # Get the associated columns and relationship names
58
+ inherited_columns = parent_association_class.column_names
59
+ inherited_methods = parent_association_class.reflections.map { |key, value| key.to_s }
60
+
61
+ # Filter out columns that the class already has
62
+ # inherited_columns = inherited_columns.reject do |column|
63
+ # (self.column_names.grep(column).length > 0) || (column == 'type') || (column == parent_association_class.subtype_column)
64
+ # end
65
+
66
+ # Filter out methods that the class already has
67
+ inherited_methods = inherited_methods.reject do |method|
68
+ self.reflections.map { |key, value| key.to_s }.include?(method)
69
+ end
70
+
71
+ inherited_columns + inherited_methods - ['id']
72
+ end
73
+ end
74
+
75
+ module InstanceMethods
76
+ private
77
+
78
+ def parent_association
79
+ send(self.class.parent_association_name)
80
+ end
81
+
82
+ def set_association_subtype
83
+ association = parent_association
84
+ if association.attribute_names.include?(association.class.subtype_column)
85
+ association[association.class.subtype_column] = self.class.to_s
86
+ end
87
+ end
88
+
89
+ def parent_association_must_be_valid
90
+ association = parent_association
91
+
92
+ unless valid = association.valid?
93
+ association.errors.each do |attr, message|
94
+ errors.add(attr, message)
95
+ end
96
+ end
97
+
98
+ valid
99
+ end
100
+
101
+ def parent_association_must_be_saved
102
+ association = parent_association
103
+ association.save(:validate => false)
104
+ self.id = association.id
105
+ end
106
+ end
107
+
108
+ module DelegateMethods
109
+ private
110
+
111
+ def method_missing(name, *args)
112
+ if parent_association.respond_to?(name)
113
+ parent_association.public_send(name, *args)
114
+ else
115
+ super(name, *args)
116
+ end
117
+ end
118
+
119
+ def respond_to?(name)
120
+ parent_association.respond_to?(name) || super
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,132 @@
1
+ module MultipleTableInheritance
2
+ module ActiveRecord
3
+ module Child
4
+ def self.default_options
5
+ { :dependent => :destroy, :inherit_methods => false }
6
+ end
7
+
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def inherits_from(association_name, options={})
14
+ # Standardize parameters.
15
+ association_name = association_name.to_sym
16
+ options = Child::default_options.merge(options.to_options.reject { |k,v| v.nil? })
17
+
18
+ # Remove options that should not affect the belongs_to relationship.
19
+ inherit_methods = options.delete(:inherit_methods)
20
+
21
+ # Add an association, and set the foreign key.
22
+ belongs_to association_name, options
23
+
24
+ # Set the primary key since the inheriting table includes no `id` column.
25
+ self.primary_key = "#{association_name}_id"
26
+
27
+ # Always return an instance of the parent class, whether it be an existing or new instance.
28
+ always_return_association(association_name)
29
+
30
+ # Ensure the parent specifies the current class as the subtype.
31
+ associate_before_validation(association_name)
32
+
33
+ # Bind the validation of association.
34
+ validate_association(association_name)
35
+
36
+ # Ensure both the parent and the child records are saved.
37
+ save_association(association_name)
38
+
39
+ # Create proxy methods for instances of this class.
40
+ create_instance_methods(association_name)
41
+
42
+ # Delegate all missing method calls to the parent association if preferred.
43
+ delegate_parent_methods(association_name) if inherit_methods
44
+ end
45
+
46
+ private
47
+
48
+ def always_return_association(association_name)
49
+ define_method("#{association_name}_with_autobuild") do
50
+ send("#{association_name}_without_autobuild") || send("build_#{association_name}")
51
+ end
52
+
53
+ alias_method_chain association_name, :autobuild
54
+ end
55
+
56
+ def associate_before_validation(association_name)
57
+ define_method(:set_subtype) do
58
+ association = send(association_name)
59
+ if association.attribute_names.include?(association.class.subtype_column)
60
+ association[association.class.subtype_column] = self.class.to_s
61
+ end
62
+ end
63
+
64
+ before_validation :set_subtype
65
+ end
66
+
67
+ def validate_association(association_name)
68
+ define_method(:parent_association_must_be_valid) do
69
+ association = send(association_name)
70
+
71
+ unless valid = association.valid?
72
+ association.errors.each do |attr, message|
73
+ errors.add(attr, message)
74
+ end
75
+ end
76
+
77
+ valid
78
+ end
79
+
80
+ validate :parent_association_must_be_valid
81
+ end
82
+
83
+ def save_association(association_name)
84
+ define_method(:parent_association_must_be_saved) do
85
+ association = send(association_name)
86
+ association.save(:validate => false)
87
+ self.id = association.id
88
+ end
89
+
90
+ before_save :parent_association_must_be_saved
91
+ end
92
+
93
+ def create_instance_methods(association_name)
94
+ inherited_columns_and_methods(association_name).each do |name|
95
+ delegate name, "#{name}=", :to => association_name
96
+ end
97
+ end
98
+
99
+ def inherited_columns_and_methods(association_name)
100
+ # Get the class of association by reflection
101
+ reflection = create_reflection(:belongs_to, association_name, {}, self)
102
+ association_class = reflection.class_name.constantize
103
+ inherited_columns = association_class.column_names
104
+ inherited_methods = association_class.reflections.map { |key, value| key.to_s }
105
+
106
+ # Filter out columns that the class already has
107
+ inherited_columns = inherited_columns.reject do |column|
108
+ (self.column_names.grep(column).length > 0) || (column == 'type') || (column == association_class.subtype_column)
109
+ end
110
+
111
+ # Filter out columns that the class already has
112
+ inherited_methods = inherited_methods.reject do |method|
113
+ self.reflections.map { |key, value| key.to_s }.include?(method)
114
+ end
115
+
116
+ inherited_columns + inherited_methods - ['id']
117
+ end
118
+
119
+ def delegate_parent_methods(association_name)
120
+ define_method("method_missing") do |name, *args|
121
+ association = self.public_send(association_name)
122
+ if association.present? && association.respond_to?(name)
123
+ association.send(name, *args)
124
+ else
125
+ super(name, *args)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,20 @@
1
+ module MultipleTableInheritance
2
+ module ActiveRecord
3
+ module Migration
4
+ def self.included(base)
5
+ base.class_eval do
6
+ alias_method_chain :create_table, :inherits
7
+ end
8
+ end
9
+
10
+ # Generate the association field.
11
+ def create_table_with_inherits(table_name, options = {}, &block)
12
+ options[:primary_key] = "#{options[:inherits]}_id" if options[:inherits]
13
+
14
+ create_table_without_inherits(table_name, options) do |table_defintion|
15
+ yield table_defintion
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,65 @@
1
+ module MultipleTableInheritance
2
+ module ActiveRecord
3
+ module Parent
4
+ def self.default_options
5
+ { :subtype => 'subtype' }
6
+ end
7
+
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ base.class_attribute :subtype_column
11
+ end
12
+
13
+ module ClassMethods
14
+ def acts_as_superclass(options={})
15
+ options = Parent::default_options.merge(options.to_options.reject { |k,v| v.nil? })
16
+ self.subtype_column = options[:subtype]
17
+
18
+ if self.column_names.include?(subtype_column.to_s)
19
+ class << self
20
+ alias_method_chain :find_by_sql, :inherits
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def find_by_sql_with_inherits(*args)
28
+ parent_records = find_by_sql_without_inherits(*args)
29
+ child_records = []
30
+
31
+ # find all child records
32
+ ids_by_type(parent_records).each do |type, ids|
33
+ begin
34
+ klass = type.constantize
35
+ child_records += klass.find(ids)
36
+ rescue NameError => e
37
+ # TODO log error
38
+ end
39
+ end
40
+
41
+ # associate the parent records with the child records to reduce SQL calls and prevent recursion
42
+ child_records.each do |child|
43
+ association_name = to_s.demodulize.underscore
44
+ parent = parent_records.find { |parent| child.id == parent.id }
45
+ child.send("#{association_name}=", parent)
46
+ end
47
+
48
+ # TODO order the child_records array to match the order of the parent_records array (by comparing id's)
49
+
50
+ child_records
51
+ end
52
+
53
+ def ids_by_type(records)
54
+ subtypes = records.collect(&self.subtype_column.to_sym).uniq
55
+ subtypes = subtypes.collect do |subtype|
56
+ subtype_records = records.select { |record| record[subtype_column.to_sym] == subtype}
57
+ subtype_ids = subtype_records.collect { |record| record.id }
58
+ [subtype, subtype_ids]
59
+ end
60
+ Hash[subtypes]
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ module MultipleTableInheritance
2
+ module ActiveRecord
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Child
6
+ autoload :Parent
7
+ autoload :Migration
8
+ end
9
+ end
@@ -0,0 +1,22 @@
1
+ module MultipleTableInheritance
2
+ if defined?(Rails::Railtie)
3
+ class Railtie < Rails::Railtie
4
+ initializer 'multiple_table_inheritance.insert_into_active_record' do
5
+ ::ActiveSupport.on_load(:active_record) { Railtie.insert }
6
+ end
7
+ end
8
+ end
9
+
10
+ class Railtie
11
+ def self.insert
12
+ ::ActiveRecord::Base.module_eval do
13
+ include MultipleTableInheritance::ActiveRecord::Child
14
+ include MultipleTableInheritance::ActiveRecord::Parent
15
+ end
16
+
17
+ ::ActiveRecord::ConnectionAdapters::SchemaStatements.module_eval do
18
+ include MultipleTableInheritance::ActiveRecord::Migration
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module MultipleTableInheritance
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'multiple_table_inheritance/railtie'
2
+
3
+ module MultipleTableInheritance
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :ActiveRecord
7
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "multiple_table_inheritance/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "multiple_table_inheritance"
7
+ s.version = MultipleTableInheritance::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Matt Huggins"]
10
+ s.email = ["matt@matthuggins.com"]
11
+ s.homepage = "http://github.com/mhuggins/multiple_table_inheritance"
12
+ s.summary = "ActiveRecord plugin designed to allow simple multiple table inheritance."
13
+ s.description = "ActiveRecord plugin designed to allow simple multiple table inheritance."
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.rdoc_options = ["--charset=UTF-8"]
21
+ s.extra_rdoc_files = ["README.md"]
22
+
23
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
24
+ s.rubygems_version = %q{0.1.0}
25
+
26
+ s.add_dependency('activerecord', '>= 3.0.0')
27
+ s.add_dependency('activesupport', '>= 3.0.0')
28
+ s.add_development_dependency('rspec-rails', '~> 2.8.0')
29
+ s.add_development_dependency('rspec_tag_matchers', '>= 1.0.0')
30
+ s.add_development_dependency('sqlite3-ruby', '>= 1.3.3')
31
+ s.add_development_dependency('database_cleaner', '>= 0.7.1')
32
+ # s.add_development_dependency('appraisal')
33
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe MultipleTableInheritance::ActiveRecord::Child do
4
+ context 'retrieving records' do
5
+ it 'should only fetch subtypes' do
6
+ subtypes = Employee.all.collect(&:subtype).uniq
7
+ subtypes.each do |subtype|
8
+ ['Programmer', 'Manager'].should include(subtype)
9
+ end
10
+ end
11
+
12
+ it 'should fetch all child records' do
13
+ pending "find_by_sql_without_inherit.size.should == find_by_sql_with_inherit.size"
14
+ end
15
+
16
+ context 'an invalid subtype is included' do
17
+ it 'should return successfully' do
18
+ pending "error should not be thrown"
19
+ end
20
+
21
+ it 'should generate log entry' do
22
+ pending "logger.error should have been called"
23
+ end
24
+ end
25
+
26
+ it 'should maintain result order' do
27
+ pending "find_by_sql_without_inherit.collect(&:id).should == find_by_sql_with_inherit.collect(&:id)"
28
+ end
29
+
30
+ context 'default subtype used' do
31
+ pending "test_everything"
32
+ end
33
+
34
+ context 'custom subtype used' do
35
+ pending "test_everything"
36
+ end
37
+
38
+ it 'should work with namespaced classes' do
39
+ pending "test_everything"
40
+ end
41
+
42
+ it 'should inherit parent methods' do
43
+ pending 'todo'
44
+ end
45
+ end
46
+
47
+ context 'creating records' do
48
+ pending "todo"
49
+ end
50
+
51
+ context 'updating records' do
52
+ pending "todo"
53
+ end
54
+
55
+ context 'deleting records' do
56
+ pending "todo"
57
+ end
58
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe MultipleTableInheritance::ActiveRecord::Parent do
4
+ it 'should do something' do
5
+ pending "test everything"
6
+ end
7
+ end
@@ -0,0 +1,48 @@
1
+ require 'active_record'
2
+ require 'database_cleaner'
3
+ require File.expand_path(File.join(File.dirname(__FILE__), '../lib/multiple_table_inheritance'))
4
+ require 'support/tables'
5
+ require 'support/classes'
6
+
7
+ module MultipleTableInheritanceSpecHelper
8
+ def mock_everything
9
+ @team = Team.create!(:name => 'Website')
10
+ @java = Language.create!(:name => 'Java')
11
+ @cpp = Language.create!(:name => 'C++')
12
+ @programmer = Programmer.create!(
13
+ :first_name => 'Mario',
14
+ :last_name => 'Mario',
15
+ :salary => 65000,
16
+ :team => @team,
17
+ :languages => [@java, @cpp]) # programmer-specific relationship
18
+ @manager = Manager.create!(
19
+ :first_name => 'King',
20
+ :last_name => 'Koopa',
21
+ :salary => 70000,
22
+ :team => @team,
23
+ :bonus => 5000) # manager-specific field
24
+ end
25
+ end
26
+
27
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
28
+ RSpec.configure do |config|
29
+ include MultipleTableInheritanceSpecHelper
30
+
31
+ config.treat_symbols_as_metadata_keys_with_true_values = true
32
+ config.run_all_when_everything_filtered = true
33
+ config.filter_run :focus
34
+
35
+ config.before(:suite) do
36
+ DatabaseCleaner.strategy = :transaction
37
+ DatabaseCleaner.clean_with(:truncation)
38
+ end
39
+
40
+ config.before(:each) do
41
+ DatabaseCleaner.start
42
+ mock_everything
43
+ end
44
+
45
+ config.after(:each) do
46
+ DatabaseCleaner.clean
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ class ::Employee < ActiveRecord::Base
2
+ acts_as_superclass
3
+ attr_accessible :first_name, :last_name, :salary
4
+ belongs_to :team
5
+ validates :first_name, :presence => true
6
+ validates :last_name, :presence => true
7
+ validates :salary, :presence => true, :numericality => { :min => 0 }
8
+ end
9
+
10
+ class ::Programmer < ActiveRecord::Base
11
+ inherits_from :employee
12
+ has_many :known_languages
13
+ has_many :languages, :through => :known_languages
14
+ end
15
+
16
+ class ::Manager < ActiveRecord::Base
17
+ inherits_from :employee
18
+ attr_accessible :bonus
19
+ validates :bonus, :numericality => true
20
+ end
21
+
22
+ class ::Team < ActiveRecord::Base
23
+ attr_accessible :name, :description
24
+ has_many :employees
25
+ validates :name, :presence => true, :uniqueness => true
26
+ end
27
+
28
+ class ::Language < ActiveRecord::Base
29
+ attr_accessible :name
30
+ has_many :known_languages
31
+ has_many :programmers, :through => :known_languages
32
+ validates :name, :presence => true, :uniqueness => true
33
+ end
34
+
35
+ class ::KnownLanguage < ActiveRecord::Base
36
+ belongs_to :programmer
37
+ belongs_to :language
38
+ validates :programmer_id, :presence => true
39
+ validates :language_id, :presence => true, :uniqueness => { :scope => :programmer_id }
40
+ end
@@ -0,0 +1,42 @@
1
+ require 'multiple_table_inheritance/railtie'
2
+
3
+ MultipleTableInheritance::Railtie.insert
4
+
5
+ ActiveRecord::Base.establish_connection(
6
+ :adapter => 'sqlite3',
7
+ :database => File.expand_path(File.join(File.dirname(__FILE__), '../../db/multiple_table_inheritance.db'))
8
+ )
9
+
10
+ ActiveRecord::Base.connection do
11
+ ['employees', 'programmers', 'managers', 'teams', 'languages', 'known_languages'].each do |table|
12
+ execute "DROP TABLE IF EXISTS '#{table}'"
13
+ end
14
+
15
+ create_table :employees do |t|
16
+ t.string :subtype, :null => false
17
+ t.string :first_name, :null => false
18
+ t.string :last_name, :null => false
19
+ t.integer :salary, :null => false
20
+ t.integer :team_id
21
+ end
22
+
23
+ create_table :programmers, :inherits => :employee do |t|
24
+ end
25
+
26
+ create_table :managers, :inherits => :employee do |t|
27
+ t.integer :bonus
28
+ end
29
+
30
+ create_table :teams do |t|
31
+ t.string :name, :null => false
32
+ end
33
+
34
+ create_table :languages do |t|
35
+ t.string :name, :null => false
36
+ end
37
+
38
+ create_table :known_languages do |t|
39
+ t.integer :programmer_id, :null => false
40
+ t.integer :language_id, :null => false
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multiple_table_inheritance
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matt Huggins
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-06 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: &2153063820 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2153063820
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &2153063240 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 3.0.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2153063240
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec-rails
38
+ requirement: &2153062780 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.8.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2153062780
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec_tag_matchers
49
+ requirement: &2153062140 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2153062140
58
+ - !ruby/object:Gem::Dependency
59
+ name: sqlite3-ruby
60
+ requirement: &2153061480 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: 1.3.3
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *2153061480
69
+ - !ruby/object:Gem::Dependency
70
+ name: database_cleaner
71
+ requirement: &2153060780 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: 0.7.1
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *2153060780
80
+ description: ActiveRecord plugin designed to allow simple multiple table inheritance.
81
+ email:
82
+ - matt@matthuggins.com
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files:
86
+ - README.md
87
+ files:
88
+ - .rspec
89
+ - Gemfile
90
+ - README.md
91
+ - Rakefile
92
+ - lib/multiple_table_inheritance.rb
93
+ - lib/multiple_table_inheritance/active_record.rb
94
+ - lib/multiple_table_inheritance/active_record/child.rb
95
+ - lib/multiple_table_inheritance/active_record/child.rb.bak
96
+ - lib/multiple_table_inheritance/active_record/migration.rb
97
+ - lib/multiple_table_inheritance/active_record/parent.rb
98
+ - lib/multiple_table_inheritance/railtie.rb
99
+ - lib/multiple_table_inheritance/version.rb
100
+ - multiiple_table_inheritance.gemspec
101
+ - spec/active_record/child_spec.rb
102
+ - spec/active_record/parent_spec.rb
103
+ - spec/spec_helper.rb
104
+ - spec/support/classes.rb
105
+ - spec/support/tables.rb
106
+ homepage: http://github.com/mhuggins/multiple_table_inheritance
107
+ licenses: []
108
+ post_install_message:
109
+ rdoc_options:
110
+ - --charset=UTF-8
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 1.8.10
128
+ signing_key:
129
+ specification_version: 3
130
+ summary: ActiveRecord plugin designed to allow simple multiple table inheritance.
131
+ test_files:
132
+ - spec/active_record/child_spec.rb
133
+ - spec/active_record/parent_spec.rb
134
+ - spec/spec_helper.rb
135
+ - spec/support/classes.rb
136
+ - spec/support/tables.rb