multiple_table_inheritance 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .rvmrc
2
+ db
3
+ Gemfile.lock
4
+ *.gem
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ 0.1.1
2
+
3
+ * Fixed bidirectional deletion of records.
4
+ * Implemented find_by_id for classes and instances that inherit from other
5
+ models.
6
+ * Added several unit tests.
7
+
8
+ 0.1.0
9
+
10
+ * Initial release.
data/README.md CHANGED
@@ -65,6 +65,13 @@ extended.
65
65
  acts_as_superclass
66
66
  end
67
67
 
68
+ As mentioned in the migration section, the name of the subtype column can be
69
+ defined here if it's something other than the default of "subtype".
70
+
71
+ class Employee < ActiveRecord::Base
72
+ acts_as_superclass, :subtype => 'employee_type'
73
+ end
74
+
68
75
  Conversely, the `inherits_from` method is used to represent that a given model
69
76
  extends another model. It takes one optional parameter, which is the symbol
70
77
  desired for referencing the relationship.
@@ -193,19 +200,19 @@ Associations will also work in the same way as other attributes.
193
200
  belongs_to :team
194
201
  end
195
202
 
196
- class ::Programmer < ActiveRecord::Base
203
+ class Programmer < ActiveRecord::Base
197
204
  inherits_from :employee
198
205
  has_many :known_languages
199
206
  has_many :languages, :through => :known_languages
200
207
  end
201
208
 
202
- class ::Language < ActiveRecord::Base
209
+ class Language < ActiveRecord::Base
203
210
  attr_accessible :name
204
211
  has_many :known_languages
205
212
  has_many :programmers, :through => :known_languages
206
213
  end
207
214
 
208
- class ::KnownLanguage < ActiveRecord::Base
215
+ class KnownLanguage < ActiveRecord::Base
209
216
  belongs_to :programmer
210
217
  belongs_to :language
211
218
  end
data/Rakefile CHANGED
@@ -4,44 +4,3 @@ RSpec::Core::RakeTask.new('spec')
4
4
 
5
5
  desc 'Default: run unit specs.'
6
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
@@ -16,6 +16,8 @@ module MultipleTableInheritance
16
16
  options = Child::default_options.merge(options.to_options.reject { |k,v| v.nil? })
17
17
  inherit_methods = options.delete(:inherit_methods)
18
18
 
19
+ extend SharedMethods
20
+ include SharedMethods
19
21
  include InstanceMethods
20
22
  include DelegateMethods if inherit_methods
21
23
 
@@ -72,6 +74,12 @@ module MultipleTableInheritance
72
74
  end
73
75
  end
74
76
 
77
+ module SharedMethods
78
+ def find_by_id(*args)
79
+ send("find_by_#{parent_association_name}_id", *args)
80
+ end
81
+ end
82
+
75
83
  module InstanceMethods
76
84
  private
77
85
 
@@ -2,18 +2,14 @@ module MultipleTableInheritance
2
2
  module ActiveRecord
3
3
  module Migration
4
4
  def self.included(base)
5
- base.class_eval do
6
- alias_method_chain :create_table, :inherits
7
- end
5
+ base.extend ClassMethods
8
6
  end
9
7
 
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
8
+ module ClassMethods
9
+ def create_table(table_name, options = {}, &block)
10
+ options[:primary_key] = "#{options[:inherits]}_id" if options[:inherits]
11
+ super(table_name, options, &block)
12
+ end
17
13
  end
18
14
  end
19
15
  end
@@ -6,7 +6,7 @@ module MultipleTableInheritance
6
6
  end
7
7
 
8
8
  def self.included(base)
9
- base.extend(ClassMethods)
9
+ base.extend ClassMethods
10
10
  base.class_attribute :subtype_column
11
11
  end
12
12
 
@@ -15,20 +15,20 @@ module MultipleTableInheritance
15
15
  options = Parent::default_options.merge(options.to_options.reject { |k,v| v.nil? })
16
16
  self.subtype_column = options[:subtype]
17
17
 
18
- if self.column_names.include?(subtype_column.to_s)
19
- class << self
20
- alias_method_chain :find_by_sql, :inherits
21
- end
18
+ if column_names.include?(subtype_column.to_s)
19
+ extend FinderMethods
20
+ include InstanceMethods
21
+ before_destroy :destroy_child_association
22
22
  end
23
23
  end
24
-
25
- private
26
-
27
- def find_by_sql_with_inherits(*args)
28
- parent_records = find_by_sql_without_inherits(*args)
24
+ end
25
+
26
+ module FinderMethods
27
+ def find_by_sql(*args)
28
+ parent_records = super(*args)
29
29
  child_records = []
30
30
 
31
- # find all child records
31
+ # Find all child records.
32
32
  ids_by_type(parent_records).each do |type, ids|
33
33
  begin
34
34
  klass = type.constantize
@@ -38,20 +38,25 @@ module MultipleTableInheritance
38
38
  end
39
39
  end
40
40
 
41
- # associate the parent records with the child records to reduce SQL calls and prevent recursion
41
+ # Associate the parent records with the child records to reduce SQL calls and prevent recursion.
42
42
  child_records.each do |child|
43
43
  association_name = to_s.demodulize.underscore
44
44
  parent = parent_records.find { |parent| child.id == parent.id }
45
45
  child.send("#{association_name}=", parent)
46
46
  end
47
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
48
+ # Order the child_records array to match the order of the parent_records array.
49
+ child_records.sort! do |a, b|
50
+ a_index = parent_records.index { |parent| parent.id == a.id }
51
+ b_index = parent_records.index { |parent| parent.id == b.id }
52
+ a_index <=> b_index
53
+ end
51
54
  end
52
55
 
56
+ private
57
+
53
58
  def ids_by_type(records)
54
- subtypes = records.collect(&self.subtype_column.to_sym).uniq
59
+ subtypes = records.collect(&subtype_column.to_sym).uniq
55
60
  subtypes = subtypes.collect do |subtype|
56
61
  subtype_records = records.select { |record| record[subtype_column.to_sym] == subtype}
57
62
  subtype_ids = subtype_records.collect { |record| record.id }
@@ -60,6 +65,21 @@ module MultipleTableInheritance
60
65
  Hash[subtypes]
61
66
  end
62
67
  end
68
+
69
+ module InstanceMethods
70
+ def destroy_child_association
71
+ child_class = send(subtype_column.to_sym).constantize
72
+ if child = child_class.find_by_id(id)
73
+ child.delete
74
+ end
75
+ rescue NameError => e
76
+ # TODO log error
77
+ end
78
+
79
+ def find_by_subtype(*args)
80
+ super || send("find_by_#{subtype_column}", *args)
81
+ end
82
+ end
63
83
  end
64
84
  end
65
85
  end
@@ -1,3 +1,3 @@
1
1
  module MultipleTableInheritance
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.extra_rdoc_files = ["README.md"]
22
22
 
23
23
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
24
- s.rubygems_version = %q{0.1.0}
24
+ s.rubygems_version = %q{1.3.6}
25
25
 
26
26
  s.add_dependency('activerecord', '>= 3.0.0')
27
27
  s.add_dependency('activesupport', '>= 3.0.0')
@@ -2,46 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe MultipleTableInheritance::ActiveRecord::Child do
4
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
5
+ pending "todo"
45
6
  end
46
7
 
47
8
  context 'creating records' do
@@ -55,4 +16,37 @@ describe MultipleTableInheritance::ActiveRecord::Child do
55
16
  context 'deleting records' do
56
17
  pending "todo"
57
18
  end
19
+
20
+ context 'when instructed to inherit parent methods' do
21
+ it 'should inherit parent methods' do
22
+ pending "todo"
23
+ end
24
+ end
25
+
26
+ context 'searching by id' do
27
+ context 'at the class level' do
28
+ before do
29
+ @employee_id = Employee.first.id
30
+ @employee = Employee.find_by_id(@employee_id)
31
+ end
32
+
33
+ it 'should return a valid record' do
34
+ @employee.should be_instance_of(@employee.subtype.constantize)
35
+ @employee.id.should be(@employee_id)
36
+ end
37
+ end
38
+
39
+ context 'at the instance level' do
40
+ before do
41
+ team = Team.first
42
+ @employee_id = team.employees.first.id
43
+ @employee = team.employees.find_by_id(@employee_id)
44
+ end
45
+
46
+ it 'should return a valid record' do
47
+ @employee.should be_instance_of(@employee.subtype.constantize)
48
+ @employee.id.should be(@employee_id)
49
+ end
50
+ end
51
+ end
58
52
  end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe MultipleTableInheritanceSpecHelper do
4
+ it 'should allow records to be found without inheritance' do
5
+ pending "find_without_inheritance is not working as intended"
6
+ employees = Employee.find_without_inheritance { Employee.all }
7
+ employees.each do |employee|
8
+ employee.should be_instance_of(Employee)
9
+ end
10
+ end
11
+ end
@@ -1,7 +1,72 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe MultipleTableInheritance::ActiveRecord::Parent do
4
- it 'should do something' do
5
- pending "test everything"
4
+ context 'retrieving records' do
5
+ it 'should only fetch child records' do
6
+ Employee.find_each do |employee|
7
+ employee.should_not be_instance_of(Employee)
8
+ ['Programmer', 'Manager'].should include(employee.class.to_s)
9
+ end
10
+ end
11
+
12
+ it 'should fetch all child records' do
13
+ pending "find_without_inheritance is not working as intended"
14
+ modified_results = Employee.all
15
+ original_results = Employee.find_without_inheritance { Employee.all }
16
+ modified_results.size.should == original_results.size
17
+ end
18
+
19
+ it 'should maintain result order' do
20
+ pending "find_without_inheritance is not working as intended"
21
+ modified_results = Employee.order("id desc").all
22
+ original_results = Employee.find_without_inheritance { Employee.order("id desc").all }
23
+ modified_results.collect(&:id).should == original_results.collect(&:id)
24
+ end
25
+
26
+ context 'an invalid subtype exists' do
27
+ before do
28
+ @employee = Employee.create!(:first_name => 'Sub', :last_name => 'Type', :salary => 50000, :team => @team) do |employee|
29
+ employee.subtype = 'DoesNotExist'
30
+ end
31
+ end
32
+
33
+ it 'should return successfully' do
34
+ @employee.should be_instance_of(Employee)
35
+ end
36
+
37
+ it 'should log an error' do
38
+ pending "logger.error should have been called"
39
+ end
40
+ end
41
+
42
+ context 'default subtype used' do
43
+ pending "test_everything"
44
+ end
45
+
46
+ context 'custom subtype used' do
47
+ pending "test_everything"
48
+ end
49
+
50
+ context 'namespaced classes are being used' do
51
+ pending "test_everything"
52
+ end
53
+ end
54
+
55
+ context 'deleting records' do
56
+ before do
57
+ programmer = Programmer.create!(:first_name => 'Billy', :last_name => 'Ray', :salary => 50000, :team => @team)
58
+ @employee = programmer.employee
59
+ @employee_id = programmer.id
60
+ end
61
+
62
+ it 'should delete the parent record' do
63
+ @employee.destroy.should be_true
64
+ Employee.find_by_id(@employee_id).should be_nil
65
+ end
66
+
67
+ it 'should delete the child record' do
68
+ @employee.destroy.should be_true
69
+ Programmer.find_by_id(@employee_id).should be_nil
70
+ end
6
71
  end
7
72
  end
data/spec/spec_helper.rb CHANGED
@@ -1,25 +1,38 @@
1
1
  require 'active_record'
2
2
  require 'database_cleaner'
3
- require File.expand_path(File.join(File.dirname(__FILE__), '../lib/multiple_table_inheritance'))
3
+ require 'multiple_table_inheritance'
4
+
5
+ MultipleTableInheritance::ActiveRecord::Parent::ClassMethods.module_eval do
6
+ def find_without_inheritance(&block)
7
+ # remove_method :find_by_sql
8
+ extend ActiveRecord::Querying
9
+ result = yield
10
+ extend MultipleTableInheritance::ActiveRecord::Parent::FinderMethods
11
+ result
12
+ end
13
+ end
14
+
15
+ MultipleTableInheritance::Railtie.insert
16
+
4
17
  require 'support/tables'
5
- require 'support/classes'
18
+ require 'support/models'
6
19
 
7
20
  module MultipleTableInheritanceSpecHelper
8
21
  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!(
22
+ team = Team.create!(:name => 'Website')
23
+ java = Language.create!(:name => 'Java')
24
+ cpp = Language.create!(:name => 'C++')
25
+ Programmer.create!(
13
26
  :first_name => 'Mario',
14
27
  :last_name => 'Mario',
15
28
  :salary => 65000,
16
- :team => @team,
17
- :languages => [@java, @cpp]) # programmer-specific relationship
18
- @manager = Manager.create!(
29
+ :team => team,
30
+ :languages => [java, cpp]) # programmer-specific relationship
31
+ Manager.create!(
19
32
  :first_name => 'King',
20
33
  :last_name => 'Koopa',
21
34
  :salary => 70000,
22
- :team => @team,
35
+ :team => team,
23
36
  :bonus => 5000) # manager-specific field
24
37
  end
25
38
  end
@@ -1,38 +1,39 @@
1
- class ::Employee < ActiveRecord::Base
1
+ class Employee < ActiveRecord::Base
2
2
  acts_as_superclass
3
- attr_accessible :first_name, :last_name, :salary
3
+ attr_accessible :first_name, :last_name, :salary, :team, :team_id
4
4
  belongs_to :team
5
5
  validates :first_name, :presence => true
6
6
  validates :last_name, :presence => true
7
7
  validates :salary, :presence => true, :numericality => { :min => 0 }
8
8
  end
9
9
 
10
- class ::Programmer < ActiveRecord::Base
10
+ class Programmer < ActiveRecord::Base
11
11
  inherits_from :employee
12
+ attr_accessible :languages, :language_ids
12
13
  has_many :known_languages
13
14
  has_many :languages, :through => :known_languages
14
15
  end
15
16
 
16
- class ::Manager < ActiveRecord::Base
17
+ class Manager < ActiveRecord::Base
17
18
  inherits_from :employee
18
19
  attr_accessible :bonus
19
20
  validates :bonus, :numericality => true
20
21
  end
21
22
 
22
- class ::Team < ActiveRecord::Base
23
+ class Team < ActiveRecord::Base
23
24
  attr_accessible :name, :description
24
25
  has_many :employees
25
26
  validates :name, :presence => true, :uniqueness => true
26
27
  end
27
28
 
28
- class ::Language < ActiveRecord::Base
29
+ class Language < ActiveRecord::Base
29
30
  attr_accessible :name
30
31
  has_many :known_languages
31
32
  has_many :programmers, :through => :known_languages
32
33
  validates :name, :presence => true, :uniqueness => true
33
34
  end
34
35
 
35
- class ::KnownLanguage < ActiveRecord::Base
36
+ class KnownLanguage < ActiveRecord::Base
36
37
  belongs_to :programmer
37
38
  belongs_to :language
38
39
  validates :programmer_id, :presence => true
@@ -1,7 +1,3 @@
1
- require 'multiple_table_inheritance/railtie'
2
-
3
- MultipleTableInheritance::Railtie.insert
4
-
5
1
  ActiveRecord::Base.establish_connection(
6
2
  :adapter => 'sqlite3',
7
3
  :database => File.expand_path(File.join(File.dirname(__FILE__), '../../db/multiple_table_inheritance.db'))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: multiple_table_inheritance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-06 00:00:00.000000000Z
12
+ date: 2012-03-08 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
16
- requirement: &2153063820 !ruby/object:Gem::Requirement
16
+ requirement: &2155943940 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 3.0.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2153063820
24
+ version_requirements: *2155943940
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: activesupport
27
- requirement: &2153063240 !ruby/object:Gem::Requirement
27
+ requirement: &2155943220 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 3.0.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2153063240
35
+ version_requirements: *2155943220
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec-rails
38
- requirement: &2153062780 !ruby/object:Gem::Requirement
38
+ requirement: &2155942320 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 2.8.0
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *2153062780
46
+ version_requirements: *2155942320
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec_tag_matchers
49
- requirement: &2153062140 !ruby/object:Gem::Requirement
49
+ requirement: &2155941500 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 1.0.0
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *2153062140
57
+ version_requirements: *2155941500
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: sqlite3-ruby
60
- requirement: &2153061480 !ruby/object:Gem::Requirement
60
+ requirement: &2155940740 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 1.3.3
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *2153061480
68
+ version_requirements: *2155940740
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: database_cleaner
71
- requirement: &2153060780 !ruby/object:Gem::Requirement
71
+ requirement: &2155939560 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,7 +76,7 @@ dependencies:
76
76
  version: 0.7.1
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *2153060780
79
+ version_requirements: *2155939560
80
80
  description: ActiveRecord plugin designed to allow simple multiple table inheritance.
81
81
  email:
82
82
  - matt@matthuggins.com
@@ -85,23 +85,25 @@ extensions: []
85
85
  extra_rdoc_files:
86
86
  - README.md
87
87
  files:
88
+ - .gitignore
88
89
  - .rspec
90
+ - CHANGELOG.md
89
91
  - Gemfile
90
92
  - README.md
91
93
  - Rakefile
92
94
  - lib/multiple_table_inheritance.rb
93
95
  - lib/multiple_table_inheritance/active_record.rb
94
96
  - lib/multiple_table_inheritance/active_record/child.rb
95
- - lib/multiple_table_inheritance/active_record/child.rb.bak
96
97
  - lib/multiple_table_inheritance/active_record/migration.rb
97
98
  - lib/multiple_table_inheritance/active_record/parent.rb
98
99
  - lib/multiple_table_inheritance/railtie.rb
99
100
  - lib/multiple_table_inheritance/version.rb
100
101
  - multiiple_table_inheritance.gemspec
101
102
  - spec/active_record/child_spec.rb
103
+ - spec/active_record/helper_spec.rb
102
104
  - spec/active_record/parent_spec.rb
103
105
  - spec/spec_helper.rb
104
- - spec/support/classes.rb
106
+ - spec/support/models.rb
105
107
  - spec/support/tables.rb
106
108
  homepage: http://github.com/mhuggins/multiple_table_inheritance
107
109
  licenses: []
@@ -130,7 +132,8 @@ specification_version: 3
130
132
  summary: ActiveRecord plugin designed to allow simple multiple table inheritance.
131
133
  test_files:
132
134
  - spec/active_record/child_spec.rb
135
+ - spec/active_record/helper_spec.rb
133
136
  - spec/active_record/parent_spec.rb
134
137
  - spec/spec_helper.rb
135
- - spec/support/classes.rb
138
+ - spec/support/models.rb
136
139
  - spec/support/tables.rb
@@ -1,132 +0,0 @@
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