multiple_table_inheritance 0.1.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.
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