multiple_table_inheritance 0.1.0 → 0.1.1

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/.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