Skip to content

Commit 638a032

Browse files
added unit testing
1 parent 1ad7b5c commit 638a032

File tree

8 files changed

+327
-2
lines changed

8 files changed

+327
-2
lines changed

Rakefile

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
require 'rake'
2+
require 'rake/testtask'
3+
require 'rake/rdoctask'
4+
5+
desc 'Default: run unit tests.'
6+
task :default => :test
7+
8+
desc 'Test the class_table_inheritance plugin.'
9+
Rake::TestTask.new(:test) do |t|
10+
t.libs << 'lib'
11+
t.libs << 'test'
12+
t.pattern = 'test/**/*_test.rb'
13+
t.verbose = true
14+
end
15+
16+
desc 'Generate documentation for the class_table_inheritance plugin.'
17+
Rake::RDocTask.new(:rdoc) do |rdoc|
18+
rdoc.rdoc_dir = 'rdoc'
19+
rdoc.title = 'ClassTableInheritance'
20+
rdoc.options << '--line-numbers' << '--inline-source'
21+
rdoc.rdoc_files.include('README')
22+
rdoc.rdoc_files.include('lib/**/*.rb')
23+
end
24+

init.rb

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
require 'model'
2-
require 'migration'
1+
require 'cti'

lib/cti.rb

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require 'cti/model'
2+
require 'cti/migration'

lib/cti/migration.rb

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
require 'active_record'
2+
3+
module ClassTableInheritanceMigration
4+
5+
def create_cti_table(table_name, options = {})
6+
options[:id] ||= false if options[:has_parent]
7+
create_table(table_name, options) do |table_defintion|
8+
if options[:has_parent]
9+
association_type = Object.const_get(options[:has_parent].to_s.capitalize)
10+
association_instance = association_type.send(:new)
11+
attribute_column = association_instance.column_for_attribute(association_type.primary_key)
12+
field_option = {:primary_key => true, :null => false}
13+
field_option[:limit] = attribute_column.limit if attribute_column.limit
14+
table_defintion.column "#{options[:has_parent]}_id", attribute_column.type, field_option
15+
end
16+
if options[:has_children]
17+
table_defintion.column "child_type", "string"
18+
table_defintion.column "child_id", "integer"
19+
end
20+
yield table_defintion
21+
end
22+
end
23+
24+
end
25+
26+
ActiveRecord::ConnectionAdapters::AbstractAdapter::send :include, ClassTableInheritanceMigration

lib/cti/model.rb

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
require 'active_record'
2+
3+
class ActiveRecord::Base
4+
5+
attr_reader :reflection
6+
def self.has_children
7+
class << self
8+
self.class.instance_variable_set(:@cti,true)
9+
alias_method :child_find_by_sql, :find_by_sql
10+
def find_by_sql(sql)
11+
objects = child_find_by_sql(sql)
12+
if self.class.instance_variable_get(:@cti)
13+
puts "cti #{self.class.instance_variable_get(:@cti)}"
14+
objects.collect! do |obj|
15+
if obj.child_type
16+
child_class = Object.const_get(obj.child_type)
17+
obj = child_class.find(obj.child_id)
18+
end
19+
obj
20+
end
21+
end
22+
objects
23+
end
24+
def cti
25+
self.class.instance_variable_get(:@cti)
26+
end
27+
def cti=(value)
28+
self.class.instance_variable_set(:@cti,value)
29+
end
30+
end
31+
end
32+
33+
def self.has_parent(parent_class_name)
34+
35+
has_one parent_class_name, :as => :child
36+
37+
set_primary_key "#{parent_class_name}_id"
38+
39+
# Fetch or build a parent instance
40+
define_method(parent_class_name) do
41+
parent_id = "#{parent_class_name}_id"
42+
@parent ||= send("build_#{parent_class_name}") unless eval(parent_id)
43+
if !@parent
44+
eval("#{parent_class_name.capitalize}.cti=false")
45+
@parent ||= eval("#{parent_class_name.capitalize}.find(#{parent_id})")
46+
eval("#{parent_class_name.capitalize}.cti=true")
47+
end
48+
@parent
49+
end
50+
51+
define_method('parent') do
52+
send(parent_class_name)
53+
end
54+
55+
before_save :save_parent
56+
57+
define_method("save_parent") do
58+
parent.save
59+
self["#{parent_class_name}_id"] = parent.id
60+
true
61+
end
62+
63+
validate :parent_valid
64+
65+
# Assure parent is a valid class
66+
define_method("parent_valid") do
67+
unless valid = parent.valid?
68+
parent.errors.each do |attr, message|
69+
errors.add(attr, message)
70+
end
71+
end
72+
valid
73+
end
74+
75+
# Determine and save parent class
76+
reflection = create_reflection(:has_one, parent_class_name, {}, self)
77+
parent_class = Object.const_get(reflection.class_name)
78+
self.instance_variable_set(:@parent_class,parent_class)
79+
80+
# Determine inherits from parent
81+
columns = parent_class.column_names.reject { |c| self.column_names.grep(c).length > 0 || c == "child_type"}
82+
methods = parent_class.reflections.map { |key,value| key.to_s }
83+
methods = methods.reject { |c| self.reflections.map {|key, value| key.to_s }.include?(c) }
84+
inherits = columns + methods
85+
inherits.delete('id')
86+
inherits.delete('parent')
87+
88+
inherits.each do |name|
89+
define_method name do
90+
parent.send(name)
91+
end
92+
define_method "#{name}=" do |new_value|
93+
parent.send("#{name}=", new_value)
94+
end
95+
end
96+
97+
# Inherit constants
98+
constants = parent_class.constants.reject { |c| self.constants.grep(c).length > 0}
99+
constants.each do |name|
100+
const_set(name,parent_class.const_get(name))
101+
end
102+
103+
class << self
104+
alias_method :child_find, :find
105+
def find(*args)
106+
begin
107+
child_find(*args)
108+
rescue
109+
@parent_class.find(*args) if @parent_class
110+
end
111+
end
112+
end
113+
end
114+
115+
end
116+
117+
118+
class ActiveRecord::Relation
119+
120+
alias_method :child_first, :first
121+
def first(*args)
122+
begin
123+
child_first(*args)
124+
rescue
125+
klass = @klass.instance_variable_get(:@parent_class)
126+
if klass
127+
@klass = klass
128+
@table = klass.arel_table
129+
end
130+
child_first(*args)
131+
end
132+
end
133+
134+
alias_method :child_last, :last
135+
def last(*args)
136+
begin
137+
child_last(*args)
138+
rescue
139+
klass = @klass.instance_variable_get(:@parent_class)
140+
if klass
141+
@klass = klass
142+
@table = klass.arel_table
143+
end
144+
child_last(*args)
145+
end
146+
end
147+
148+
# alias_method :child_to_a, :to_a
149+
# def to_a
150+
# begin
151+
# child_to_a
152+
# rescue
153+
# klass = @klass.instance_variable_get(:@parent_class)
154+
# if klass
155+
# @klass = klass
156+
# @table = klass.arel_table
157+
# end
158+
# child_to_a
159+
# end
160+
# end
161+
#
162+
163+
alias_method :child_find, :find
164+
def find(*args)
165+
begin
166+
child_find(*args)
167+
rescue
168+
klass = @klass.instance_variable_get(:@parent_class)
169+
if klass
170+
@klass = klass
171+
@table = klass.arel_table
172+
end
173+
child_find(*args)
174+
end
175+
end
176+
177+
end
178+

test/model_test.rb

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
require 'test_helper'
2+
require 'pp'
3+
4+
class TestModel < MiniTest::Unit::TestCase
5+
6+
def setup_db
7+
return if defined?(@@db) && @@db
8+
@@db = true
9+
10+
puts "DB Setup"
11+
db_name = 'test/test.db'
12+
system("rm -f #{db_name}")
13+
SQLite3::Database.new(db_name)
14+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => db_name)
15+
require 'test_db'
16+
end
17+
18+
def setup
19+
setup_db
20+
end
21+
22+
def teardown
23+
ActiveRecord::Base.send(:descendants).each do |klass|
24+
klass.delete_all
25+
end
26+
end
27+
28+
def test_create_parent
29+
pp obj = Product.create(:title=>'GI Joe Action figure',
30+
:price=>24.99)
31+
assert_equal obj.class.name, 'Product'
32+
end
33+
34+
def test_create_child
35+
pp obj = Book.create(:title=>'War and Peace',
36+
:price=>8.50,
37+
:page_count=>700)
38+
assert_equal obj.class.name, 'Book'
39+
end
40+
41+
def test_create_grandchild
42+
pp obj = Novel.create(:title=>'Neuromancer',
43+
:price=>5.25,
44+
:page_count=>250)
45+
assert_equal obj.class.name, 'Novel'
46+
end
47+
48+
49+
50+
end

test/test_db.rb

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
ActiveRecord::Base.connection.create_cti_table :products, :has_children => true do |t|
3+
t.string :title
4+
t.decimal :price
5+
t.timestamps
6+
end
7+
8+
class Product < ActiveRecord::Base
9+
has_children
10+
end
11+
12+
ActiveRecord::Base.connection.create_cti_table :books, :has_parent => :product do |t|
13+
t.integer :page_count
14+
end
15+
16+
class Book < ActiveRecord::Base
17+
has_parent :product
18+
end
19+
20+
ActiveRecord::Base.connection.create_cti_table :novels, :has_parent => :book do |t|
21+
t.integer :chapter_count
22+
end
23+
24+
class Novel < ActiveRecord::Base
25+
has_parent :book
26+
end
27+
28+
ActiveRecord::Base.connection.create_cti_table :encyclopedias, :has_parent => :book do |t|
29+
t.integer :entry_count
30+
end
31+
32+
class Encyclopedia < ActiveRecord::Base
33+
has_parent :book
34+
end
35+
36+
ActiveRecord::Base.connection.create_cti_table :videos, :has_parent => :product do |t|
37+
t.integer :duration_in_minutes
38+
end
39+
40+
class Video < ActiveRecord::Base
41+
has_parent :product
42+
end

test/test_helper.rb

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
require 'minitest/autorun'
2+
require 'sqlite3'
3+
require 'active_record'
4+
require 'cti'

0 commit comments

Comments
 (0)