# Changing types in single inheritance

**URL:** <https://www.ruby-forum.com/t/changing-types-in-single-inheritance/138704>\
**Category:** Rails\
**Created:** [May 27, 2008, 12:06am UTC](https://www.ruby-forum.com/t/changing-types-in-single-inheritance/138704 "2008-05-27T00:06:49Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![taro](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/df705f/32.png) [@taro](https://www.ruby-forum.com/u/taro)\
**Post date:** [May 27, 2008, 12:06am UTC](https://www.ruby-forum.com/t/changing-types-in-single-inheritance/138704/1 "2008-05-27T00:06:49Z")

</div>

I’m trying to write an application that involves with three kinds of  
inherited models:

The lowest is Person, a model merely used as records, and nothing else  
of importance.  
Officer inherits from Person, and unlike Person, stores a password for  
verification and logging in.  
Admin inherits from Officer, and is the same as Officers except there  
must be at least one of them.

I want to make an internal method that allows me to turn a Person into  
an Officer, an Officer to Admin, Admin to Person, etc. What’s a clean  
method of upgrading/downgrading within this single-inheritance?

Here’s the source file, as well as a quick template for what I want to  
see happen:  
=======Person.rb========  
class Person \< ActiveRecord::Base  
#first\_name is required: must start with a capital  
validates\_presence\_of :first\_name  
validates\_format\_of :first\_name,  
:with =\> /^[A-Z][a-zA-Z0-9, .]+$/

#last\_name is required: must start with a capital  
validates\_presence\_of :last\_name  
validates\_format\_of :last\_name,  
:with =\> /^[A-Z][a-zA-Z0-9, .]+$/

#rin is required: must be unique; also, must all be lowercases  
#with 0 to 2 numbers following the letters.  
validates\_presence\_of :rin  
validates\_format\_of :rin,  
:with =\> /^[a-z]+[0-9]{0,2}$/  
validates\_uniqueness\_of :rin

#email must be unique, and it is required.  
validates\_presence\_of :email  
validates\_format\_of :email,  
:with =\> /^[a-zA-Z0-9\_]+@[a-zA-Z0-9\_]+(.[a-z]{2,3}){1,2}$/  
validates\_uniqueness\_of :email

#year is required, and must be a 4 digit number greater than 2000  
validates\_presence\_of :year  
validates\_numericality\_of :year,  
:only\_integer =\> true  
validates\_length\_of :year,  
:is =\> 4

#turns a Person or Officer to Admin  
def turn\_to\_admin  
self[:type] = ‘Admin’  
#somehow update with Admin-based validations  
self  
end

#turns a Person or Admin to Officer  
def turn\_to\_officer  
self[:type] = ‘Officer’  
#somehow update with Officer-based validations  
self  
end

#technically, this does nothing  
def turn\_to\_person  
nil  
end

protected

def validate  
errors.add(:year, “should be between 2000 and 3000”) if  
( year.to\_i \< 2000 or year.to\_i \> 3000 )  
end

#completely ignore the passwords  
end  
=======Officer.rb========  
class Officer \< Person  
#take care of password thingy  
validates\_length\_of :password, :minimum =\> 5

attr\_accessor :password\_confirmation  
validates\_confirmation\_of :password

#some functions  
def validate  
errors.add\_to\_base(“Missing password”) if hashed\_password.blank?  
end

def self.authenticate(name, password)  
person = self.find\_by\_rin(name)  
if person  
expected\_password = encrypted\_password(password, person.salt)  
if person.hashed\_password!=expected\_password  
person = nil  
end  
end  
person  
end

def password  
@password  
end

def password=(pwd)  
@password = pwd  
create\_new\_salt  
self.hashed\_password = Officer.encrypted\_password(self.password,  
self.salt)  
end

#turn\_to\_admin is inherited from Person

#This does nothing  
def turn\_to\_officer  
nil  
end

#turns an Officer or Admin  
def turn\_to\_person  
self[:type] = ‘Person’  
#somehow update with Officer-based validations  
self  
end

#careful of private statements  
private

def self.encrypted\_password(password, salt)  
string\_to\_hash = password + “Japan is an island of interest” +  
salt  
Digest::SHA1.hexdigest(string\_to\_hash)  
end

def create\_new\_salt  
self.salt = (self.object\_id \* rand).to\_s + rand.to\_s  
end  
end  
=======Admin.rb========  
class Admin \< Officer

#Make sure there’s at least one admin  
def after\_destroy  
if Admin.count.zero?  
raise “Can’t delete the last admin”  
end  
end

#turn\_to\_person is inherited by Officer

#Return nil, again  
def turn\_to\_admin  
nil  
end

#turns an Admin to Officer  
def turn\_to\_officer  
self[:type] = ‘Officer’  
#somehow update with Officer-based validations  
self  
end  
end

---

<div class="post-metadata">

**Author:** ![taro](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/df705f/32.png) [@taro](https://www.ruby-forum.com/u/taro)\
**Post date:** [May 27, 2008, 4:59am UTC](https://www.ruby-forum.com/t/changing-types-in-single-inheritance/138704/2 "2008-05-27T04:59:29Z")

</div>

Single table inheritance applies perfectly to this need,  
basically you have to add a type column,  
and build the people table with all the columns that any of the three  
classes will do,  
then both admin and officer have to inherit from person,  
(I never tested double inheritance)  
so some repetition at admin and officer model may happen,  
(you can use a mixin if you want to avoid that)

---

<div class="post-metadata">

**Author:** ![taro](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/df705f/32.png) [@taro](https://www.ruby-forum.com/u/taro)\
**Post date:** [May 27, 2008, 6:26am UTC](https://www.ruby-forum.com/t/changing-types-in-single-inheritance/138704/3 "2008-05-27T06:26:41Z")

</div>

I think he’s got this already.

What he’s trying to do is assign a person to be an Admin or an Officer.  
I  
think all you do is self.type = "Admin and then self.reload and it might  
work.

On Tue, May 27, 2008 at 12:28 PM, Andres [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> > validates\_format\_of :first\_name,  
> > validates\_format\_of :rin,  
> > validates\_presence\_of :year  
> > end  
> > nil  
> > end  
> > errors.add\_to\_base(“Missing password”) if hashed\_password.blank?  
> > person  
> > self.salt)  
> > def turn\_to\_person  
> > salt  
> > #Make sure there’s at least one admin  
> > nil

–  
Appreciated my help?  
Recommend me on Working With Rails  
[http://workingwithrails.com/person/11030-ryan-bigg](http://workingwithrails.com/person/11030-ryan-bigg)

---

<div class="post-metadata">

**Author:** ![taro](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/df705f/32.png) [@taro](https://www.ruby-forum.com/u/taro)\
**Post date:** [May 28, 2008, 2:17am UTC](https://www.ruby-forum.com/t/changing-types-in-single-inheritance/138704/4 "2008-05-28T02:17:53Z")

</div>

## Yeah, I did already know. It’s is why I was asking something else :-)! Anyway, I tried the reload method:

## def test\_changing\_officer\_to\_member officer = Officer.find(:first) assert officer assert officer.update\_attributes(:type =\> ‘Person’, :password =\> ‘Ha, ha, you cannot login!!!’, :password\_confirmation =\> ‘Ha, ha, you cannot login!!!’) officer.reload assert\_equal officer.class.to\_s, ‘Person’ end

## It failed miserably:

1. Failure:  
test\_changing\_officer\_to\_member(OfficerTest)  
[officer\_test.rb:89:in `test_changing_officer_to_member' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:in `run’]:  
\<“Officer”\> expected but was  
\<“Person”\>.

## 9 tests, 22 assertions, 1 failures, 0 errors Process ruby exited with code 1

Any other solutions?

## In addition, looking back at my test code, I noticed that:

## def test\_defective\_new officer = Officer.new( :first\_name =\> ‘John’, :last\_name =\> ‘Doe’, :rin =\> ‘doej’, :email =\> ‘removed\_email\_address@domain.invalid’, :year =\> 2011, :password =\> ‘forka’) assert officer.valid? assert\_equal officer.class.to\_s, ‘Officer’ end

## Passes. It shouldn’t. It should compare whether password and confirmation password is the same thing, then give out an error. Why by only giving a password, the model passes? After all, this test passes correctly:

## def test\_unequal\_password\_confirmation officer = Officer.new( :first\_name =\> ‘John’, :last\_name =\> ‘Doe’, :rin =\> ‘doej’, :email =\> ‘removed\_email\_address@domain.invalid’, :year =\> 2011, :password =\> ‘jojoj’, :password\_confirmation =\> ‘kkkkk’) assert !officer.valid? assert officer.errors.invalid?(:password) end

Finally, random, but I’ll also like to test in “rake script/console  
testing”. Unfortunately, the console doesn’t seem to realize I’ve  
downloaded the plugin “acts\_as\_list”, nor does it seem to connect the  
Database really well. What can I do to fix this? I’m using  
InstantRails, by the way: does this have to do with anything?

On May 27, 12:26 am, “Ryan B. (Radar)” [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)

---

<div class="post-metadata">

**Author:** ![taro](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/df705f/32.png) [@taro](https://www.ruby-forum.com/u/taro)\
**Post date:** [May 28, 2008, 2:19am UTC](https://www.ruby-forum.com/t/changing-types-in-single-inheritance/138704/5 "2008-05-28T02:19:39Z")

</div>

Reinitialize the variable seems to be the only option then, switch the  
type  
over to Officer and then do Officer.find(@person.id) and it may work  
that  
way.

On Wed, May 28, 2008 at 9:47 AM, Taro [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> ```
> :password_confirmation => 'Ha, ha, you cannot login!!!')
> 
> ```
> 
> ## active\_support/testing/default.rb:7:in `run’]: def test\_defective\_new by only giving a password, the model passes? After all, this test
> 
> > What he’s trying to do is assign a person to be an Admin or an Officer. I
> > 
> > > classes will do,  
> > > else  
> > > clean
> > > 
> > > > ```
> > > > :with => /^[A-Z][a-zA-Z0-9, .]+$/
> > > > :with => /^[a-z]+[0-9]{0,2}$/
> > > > 
> > > > ```
> > > > 
> > > > validates\_numericality\_of :year,
> > 
> > > > end  
> > > > =======Officer.rb========  
> > > > end  
> > > > end  
> > > > end  
> > > > self[:type] = ‘Person’  
> > > > Digest::SHA1.hexdigest(string\_to\_hash)  
> > > > def after\_destroy  
> > > > end  
> > > > Appreciated my help?  
> > > > Recommend me on Working With Railshttp://  
> > > > [workingwithrails.com/person/11030-ryan-bigg](http://workingwithrails.com/person/11030-ryan-bigg)

–  
Appreciated my help?  
Recommend me on Working With Rails  
[http://workingwithrails.com/person/11030-ryan-bigg](http://workingwithrails.com/person/11030-ryan-bigg)

---

<div class="post-metadata">

**Author:** ![taro](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/df705f/32.png) [@taro](https://www.ruby-forum.com/u/taro)\
**Post date:** [May 28, 2008, 2:26am UTC](https://www.ruby-forum.com/t/changing-types-in-single-inheritance/138704/6 "2008-05-28T02:26:40Z")

</div>

Behold!!!

* * *

def test\_changing\_officer\_to\_member  
officer = Officer.find(:first)  
assert officer  
assert officer.update\_attributes(:type =\> ‘Person’,  
:password =\> ‘Ha, ha, you cannot login!!!’,  
:password\_confirmation =\> ‘Ha, ha, you cannot login!!!’)  
officer.reload  
assert\_equal officer.class.to\_s, ‘Person’  
end

def test\_changing\_officer\_to\_member2  
officer = Officer.find(:first)  
assert officer  
officer.type = ‘Person’  
officer.reload  
assert\_equal officer.class.to\_s, ‘Person’  
end

def test\_changing\_officer\_to\_member3  
officer = Officer.find(:first)  
assert officer  
officer[:type] = ‘Person’  
officer.reload  
assert\_equal officer.class.to\_s, ‘Person’  
end

def test\_changing\_officer\_to\_member4  
officer = Officer.find(:first)  
assert officer  
officer[:type] = ‘Person’  
officer = Officer.find(officer.id)  
assert\_equal officer.class.to\_s, ‘Person’  
end

def test\_changing\_officer\_to\_member5  
officer = Officer.find(:first)  
assert officer  
officer[:type] = ‘Person’  
officer = Person.find(officer.id)  
assert\_equal officer.class.to\_s, ‘Person’  
end

## def test\_changing\_officer\_to\_member6 officer = Officer.find(:first) assert officer officer.type = ‘Person’ officer = Person.find(officer.id) assert\_equal officer.class.to\_s, ‘Person’ end

1. Failure:  
test\_changing\_officer\_to\_member(OfficerTest)  
[officer\_test.rb:90:in `test_changing_officer_to_member' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:in `run’]:  
\<“Officer”\> expected but was  
\<“Person”\>.

2. Failure:  
test\_changing\_officer\_to\_member2(OfficerTest)  
[officer\_test.rb:98:in `test_changing_officer_to_member2' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:in `run’]:  
\<“Officer”\> expected but was  
\<“Person”\>.

3. Failure:  
test\_changing\_officer\_to\_member3(OfficerTest)  
[officer\_test.rb:106:in `test_changing_officer_to_member3' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:in `run’]:  
\<“Officer”\> expected but was  
\<“Person”\>.

4. Failure:  
test\_changing\_officer\_to\_member4(OfficerTest)  
[officer\_test.rb:114:in `test_changing_officer_to_member4' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:in `run’]:  
\<“Officer”\> expected but was  
\<“Person”\>.

5. Failure:  
test\_changing\_officer\_to\_member5(OfficerTest)  
[officer\_test.rb:122:in `test_changing_officer_to_member5' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:in `run’]:  
\<“Officer”\> expected but was  
\<“Person”\>.

6. Failure:  
test\_changing\_officer\_to\_member6(OfficerTest)  
[officer\_test.rb:130:in `test_changing_officer_to_member6' c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2-/lib/ active_support/testing/default.rb:7:in `run’]:  
\<“Officer”\> expected but was  
\<“Person”\>.

On May 27, 8:19 pm, “Ryan B. (Radar)” [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)

---

<div class="post-metadata">

**Author:** ![taro](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/df705f/32.png) [@taro](https://www.ruby-forum.com/u/taro)\
**Post date:** [May 28, 2008, 3:34am UTC](https://www.ruby-forum.com/t/changing-types-in-single-inheritance/138704/7 "2008-05-28T03:34:03Z")

</div>

Your test is wrong. You should be doing:

person = Person.find(id)  
person.type = “Officer”  
person.save  
officer = Officer.find(id)  
officer.class == Officer

And that will return true.

On Wed, May 28, 2008 at 9:55 AM, Taro [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> officer.reload
> 
> assert officer  
> assert\_equal officer.class.to\_s, ‘Person’
> 
> test\_changing\_officer\_to\_member2(OfficerTest)  
> active\_support/testing/default.rb:7:in `run’]:
> 
> [officer\_test.rb:130:in `test\_changing\_officer\_to\_member6’  
> that
> 
> > > ## def test\_changing\_officer\_to\_member
> > > 
> > > * * *
> > > 
> > > assert\_equal officer.class.to\_s, ‘Officer’  
> > > :email =\> ‘removed\_email\_address@domain.invalid’, :year =\> 2011,
> > 
> > > > On Tue, May 27, 2008 at 12:28 PM, Andres [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:
> > 
> > > for
> > 
> > > > > > validates\_uniqueness\_of :rin  
> > > > > > validates\_numericality\_of :year,
> > 
> > > > > > end  
> > > > > > =======Officer.rb========  
> > > > > > hashed\_password.blank?  
> > > > > > end  
> > > > > > self.hashed\_password =
> > 
> > > > > > def self.encrypted\_password(password, salt)  
> > > > > > =======Admin.rb========
> > 
> > > > > > end  
> > > > > > [workingwithrails.com/person/11030-ryan-bigg](http://workingwithrails.com/person/11030-ryan-bigg)

–  
Appreciated my help?  
Recommend me on Working With Rails  
[http://workingwithrails.com/person/11030-ryan-bigg](http://workingwithrails.com/person/11030-ryan-bigg)

---

<div class="post-metadata">

**Author:** ![taro](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/df705f/32.png) [@taro](https://www.ruby-forum.com/u/taro)\
**Post date:** [May 29, 2008, 2:05pm UTC](https://www.ruby-forum.com/t/changing-types-in-single-inheritance/138704/8 "2008-05-29T14:05:28Z")

</div>

Oh, whoops.

Anyway, it did work. I’m curious about why "person.type = " works. I  
read somewhere that the correct syntax should be "person[:type] = ",  
due to the fact that “.type” is a (deprecated) class method.

On May 27, 9:33 pm, “Ryan B. (Radar)” [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)
