Database - Switching Tables

I have a mailing-list database that consists of several tables (e.g.,
clients table, prospects table, etc.). How can I switch between these
tables within my mailing-list Rails application?

I have a feeling that if I better understood the role of the model I
might be able to answer my own question. I’m hoping that getting this
question answered will help me in that regard.

Thanks for any input.

     ... doug

Hi–
On May 4, 2009, at 2:38 PM, doug wrote:

I have a mailing-list database that consists of several tables (e.g.,
clients table, prospects table, etc.). How can I switch between these
tables within my mailing-list Rails application?

I have a feeling that if I better understood the role of the model I
might be able to answer my own question. I’m hoping that getting this
question answered will help me in that regard.

In Rails, a model normally maps to a table. So if you have a clients
table, you have a Client model and if your have a prospects table, you
have a Prospect model. Rails infers the table name and columns from
the name of the model class. So you can do:

send_email_to :clients
send_email_to :prospects

def send_email_to(whom)
model = whom.to_s.camelize.constantize.all.each do |recipient|
# recipient is an object of the specified type. Send email at will!
end
end

In Rails, a model normally maps to a table.

Yes; but, it appears that in this case it would be really nice if I
could have a single model and then somehow simply switch between
associated tables. (I’m not sure that I’m going to be able to do it.)

Rails infers the table name and columns from
the name of the model class.

I understand that default inference. Obviously, if I am able to
succeed at what I am trying to do, I would be overriding that default.

Thanks for the input.

     ... doug

I’ve been working on something like this myself. Depending on how
different the two are, it may also just be easier to use Single Table
Inheritance?

if you decide that abstract classes are better, then I suggest that you
do something like this to make your life easier:

class Person < ActiveRecord::Base

TypesofPeople = [“Client”, “Prospect”]

def self.abstract_find (person_type, *args)
if TypesofPeople.include?(person_type)
person_type.constantize.find(*args)
else
nil
end
end

cheers,
-Gabe

Marnen Laibow-Koser wrote:

Doug J. wrote:

In Rails, a model normally maps to a table.

Yes; but, it appears that in this case it would be really nice if I
could have a single model and then somehow simply switch between
associated tables. (I’m not sure that I’m going to be able to do it.)

Why not just subclass?

class Person < ActiveRecord::Base
self.abstract_class = true # this will prevent Rails from expecting a
“people” table

common features go here

end

class Client < Person

end

class Prospect < Person

end

That should do the trick!

Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

Why not just subclass?

I like that approach. It was essentially what I was playing with when
I submitted my original post. The problem with it seems to be that I
can’t reference the subclass. Extending your example, I would like to
say something like:

clients=Client.find(:all)

Rails doesn’t like that. It complains that Client is an uninitialized
constant. It appears that Rails wants me to say something like:

clients=Person.find(:all)

IOW, it appears that Rails wants the name of the class to correspond
the base name of the model file (i.e., since the name of the model
file is person.rb, it wants the class to be Person). Without that
correlation, I get the uninitialized constant error. I’m not sure how
to get around the problem. If I could; then, I think your suggestion
would work just fine.

Thanks.

   ... doug

Doug J. wrote:

In Rails, a model normally maps to a table.

Yes; but, it appears that in this case it would be really nice if I
could have a single model and then somehow simply switch between
associated tables. (I’m not sure that I’m going to be able to do it.)

Why not just subclass?

class Person < ActiveRecord::Base
self.abstract_class = true # this will prevent Rails from expecting a
“people” table

common features go here

end

class Client < Person

end

class Prospect < Person

end

That should do the trick!

Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

6: else
7: nil
8: end
9: end

I really wish that I understood your suggestion especially because it
may be what works. I have taken the liberty of adding line numbers.
Basically, I understand the code except for line 5. I have no idea
what you are doing there. I’m not sure what the person_type and *args
are that you are passing to abstract_find. Part of my confusion may
stem from the fact that there does not seem to be and ‘end’ for your
class definition. So, I’m not altogether clear on whether lines 3
through 9 are suggested model code or suggested controller code.
Could you please clarify? I do appreciate your suggestion and I have
high hopes for it being my salvation. Thanks.

   ... doug

Put each class definition in a separate file

By “separate file” you mean “separate model”, right? I guess I could
do that. My concern is that if I do I may be opening and closing the
connection to the database? Maybe I am and maybe I am not. If I am,
maybe I don’t need to worry about it; but, I do. That’s why I wanted
to let Rails be in charge of handling the connection to the database.
I just want to swap tables.

Maybe I should ask a couple of more basic questions: What, if any,
are the adverse affects associated with switching models from with an
application? If I swap models from within an application, does that
result in closing and re-opening the database?

Thanks.

  ... doug

Doug J. wrote:

Put each class definition in a separate file

By “separate file” you mean “separate model”, right? I guess I could
do that. My concern is that if I do I may be opening and closing the
connection to the database? Maybe I am and maybe I am not. If I am,
maybe I don’t need to worry about it; but, I do. That’s why I wanted
to let Rails be in charge of handling the connection to the database.
I just want to swap tables.

Maybe I should ask a couple of more basic questions: What, if any,
are the adverse affects associated with switching models from with an
application? If I swap models from within an application, does that
result in closing and re-opening the database?

Thanks.

  ... doug

yes, you’ll need to have client.rb (containing the Client class) and
prospect.rb (containing the Prospect class) files in app/models .

No, the amount of models is not related to connections/disconnections
from the database. No matter the amount of models, Rails is handling the
connection to the database. The way to swap tables is using one model
per table.

There are no adverse effects of switching models, the database
connection is created when you start the app server (webrick, mongrel,
phusion) and closed when you end the server.

hope it helps,

Maximiliano G.

hope it helps,

Great answer. Love it! :slight_smile: I think that I’m a happy camper.

Thanks to all who contributed.

   ... doug

Doug J. wrote:
[…]

IOW, it appears that Rails wants the name of the class to correspond
the base name of the model file (i.e., since the name of the model
file is person.rb, it wants the class to be Person).

Yes, this is how Rails works. Put each class definition in a separate
file (generally best), or use explicit require statements.

[…]

   ... doug

Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

Hey Doug,

i believe this is the line that was confusing you?

person_type.constantize.find(*args)

first, all of what i wrote was model code, and yes, you should break up
the two classes into two separate model files, or really, three:
Person.rb, Client.rb, and Prospect.rb.

Ok, so in that one line, person_type is a string. rails mixes in a
function to the String class called “constantize” that changes a string
into an actual program token,

so, if person_type is “Client” then person_type.constantize is the
actual Programming token Client. As long as you define the Client
class, rails will then interpret the above line as

Client.find(*args)

OR

Prospect.find(*args)

depending on the string passed into abstract_find.

The reason i have *args is because it is what the ActiveRecord API
specificies as the argument to find - (basically all arguments passed to
ActiveRecord find are rolled up into an array, which it then figures out
what to do with)

so, the above code would allow you to, in your controller, say something
like,

@person = Person.abstract_find(“Client”, [normal find arguments go
here])

or

@person = Person.abstract_find(“Prospect”, [normal find arguments go
here])

the real power comes when you basically let the actual type be
dynamically determined by the program/user at run time:

@person = Person.abstract_find(specified_person_type, [normal find
arguments go here])

I know you had said that you had pretty much figured it out, but i
thought this might still be useful. hope it is!

-Gabe

Doug J. wrote:

6: � else
7: � � nil
8: � end
9: end

I really wish that I understood your suggestion especially because it
may be what works. I have taken the liberty of adding line numbers.
Basically, I understand the code except for line 5. I have no idea
what you are doing there. I’m not sure what the person_type and *args
are that you are passing to abstract_find. Part of my confusion may
stem from the fact that there does not seem to be and ‘end’ for your
class definition. So, I’m not altogether clear on whether lines 3
through 9 are suggested model code or suggested controller code.
Could you please clarify? I do appreciate your suggestion and I have
high hopes for it being my salvation. Thanks.

   ... doug

i believe this is the line that was confusing you?

person_type.constantize.find(*args)

Thanks, Gabe, for the very thorough explanation.

 ... doug