Using :through

Sorry, cause this is the second post in trying to solve the same
problem. Since that other post hasn’t received any replies and I have
a slightly better understanding I’m trying it again.

In my Position model I have the method company_id
In my Cdetail model I have a method company_id and another company_name.

When I’m pulling up queries on the Position table I want to translate
the company_id in Position to the company_name in Cdetail.

Is this something to use the :through option with or is there another
way ?

Stuart

I assume that Cdetail belongs_to Position?

If so, you could redefine the company_id method

def company_id
self.Cdetail.company_name
end

Would this work?

Also, if you wanted to use :select in your Find call, you could do

Position.find(:all, :select =>'[other fields..],

cdetails.company_name as company_id’, :include => ‘cdetails’)

But now that I’m thinking about it, do you really want to use the field
used for the foreign key to do this? That could cause problems.

Jason

I can’t quite say I followed all of that. I’d recommend posting a copy
of the fixture data for both models or a class definition of each model
listing the attributes.

On 9/24/06, Jason N. [email protected] wrote:

I assume that Cdetail belongs_to Position?

The other way around , Position belong_to Cdetail

Position.find(:all, :select =>'[other fields..],

cdetails.company_name as company_id’, :include => ‘cdetails’)

It might work, what is the [other fields] mean ? or can i just exclude
that option ?

But now that I’m thinking about it, do you really want to use the field
used for the foreign key to do this? That could cause problems.

Jason

I’m not sure , but in looking through my options I thought perhaps I
could use it to define the correct association.

Stuart

Maybe this will help, because it seems I like my explanations are not
conveying my issue .
I created a sql query in mysql to extract the correct information -
this query works , so now I need to figure out a way for Rails to
perform it.

SELECT
cdetails.company_name
FROM
positions
Inner Join cdetails ON positions.company_id = cdetails.company_id

Hope this helps

Stuart

On 9/24/06, Dark A. [email protected] wrote:

end

But now that I’m thinking about it, do you really want to use the field
used for the foreign key to do this? That could cause problems.

Jason

I’m not sure , but in looking through my options I thought perhaps I
could use it to define the correct association.

Stuart

Model: (showing only relevant lines)

class Position < ActiveRecord::Base
belongs_to :cdetail

attribute: company_id

class Cdetail < ActiveRecord::Base

has_many :positions

attributes: company_id, company_name

Stuart

On 9/24/06, 5MileRadius [email protected] wrote:

a slightly better understanding I’m trying it again.


Dark ambient - Wikipedia

Like 5MileRadius said, posting your models and possible some controller
code would help.
But.
To get rails to do that query, you could do

Position.find(:all, :select => ‘cdetails.company_name as whatever’,
:include => ‘cdetails’)
that will return an array of Position objects.

When you use :select, the string you pass is what is used in Rails’
“SELECT” statement, so only those fields will be queried. If you want
other things to be retrieved, you must include them in your :select

Jason

Could someone explain what posting the models means ? I’m not trying
to be funny, but what I posted was pretty much them. Anyway I’ll post
them here again.

class Position < ActiveRecord::Base
belongs_to :state
belongs_to :category
belongs_to :edureq
belongs_to :expreq
belongs_to :pay
belongs_to :postlength
belongs_to :securityclear
belongs_to :term
belongs_to :travreq
belongs_to :wage
belongs_to :user
belongs_to :cdetail

def self.current_openings
find(:all)
end
end

class Cdetail < ActiveRecord::Base
belongs_to :user
has_many :positions

end

I thought I could put something into the Position associations like
this:

belongs_to:comp_name, :class_name=> “Cdetail”, :foreign_key =>
"company_name
and although it wasn’t returning an error it wasn’t giving me the name.

Stuart

On 9/24/06, Jason N. [email protected] wrote:

When you use :select, the string you pass is what is used in Rails’

this query works , so now I need to figure out a way for Rails to

The other way around , Position belong_to Cdetail

Also, if you wanted to use :select in your Find call, you could do

This is one messy thread. Anyway , I worked out this problem using
find_by_sql. Why I can’t do it through the AR associations is beyond
me. I don’t know if I feel right using find_by_sql, as it doesn’t
really take advantage of the associations that are in place. Does it
have a downside ? I’ve posted my models as requested below anyway.

Stuart

On 9/24/06, Dark A. [email protected] wrote:

belongs_to  :postlength

end
and although it wasn’t returning an error it wasn’t giving me the name.

Position.find(:all, :select => ‘cdetails.company_name as whatever’,
Dark A. wrote:

Inner Join cdetails ON positions.company_id = cdetails.company_id

On 9/24/06, Jason N. [email protected] wrote:

end

Stuart


Dark ambient - Wikipedia

On 9/25/06, ASE [email protected] wrote:

I think your original question was, how do I get the the cdetails’s
company_name, given a position. Why can’t you just do:

position.cdetail.company_name

?

No, that is just giving me “undefined method `cdetail’ for
#Position:0x4c56748

“belongs_to :cdetail” adds an accessor method to Position which results
in the Cdetail referenced by the cdetail_id field in Position (you can
change the name of the field with :foreign_key, if your field is named
something other than cdetail_id).

Well , I’ve been trying this and it seems to have no effect -
continuing to get undefined method - “cdetail”

Here is the relationship defined in Position model
belongs_to :cdetail, :foreign_key => :company_id

So, in the Position model , the attribute is company_id
and in the Cdetail model, the attribute is company_id , perhaps having
both columns with the same name is confusing the issue.

if you wanted to access users directly from cdetail (use :foreign_key
and :source if the field names are weird).

I haven’t gotten a good feel for the :source option. Reading through
the doc it seems to reference another model ?

Tangent: Is there a reason you’re using an unintuitive reference like
“cdetail”? Why not just call it a “company”, with fields “id” and
“name”? Or even “company_detail”?

Because I didn’t think it through and I’m being hard headed :slight_smile:
Actually company_detail maybe a better idea, if that would truly help
the association problem. The c in cdetail obviously stands for
company. Just in case it seemed my model naming is totally
irrelevant.

For the time being I solved the issue by adding the :join option to
the find. It’s not totally ugly and seems to give me the results.
However I’d like to do it, as said above
position.cdetail.company_name.

Stuart

Hi –

On Mon, 25 Sep 2006, Dark A. wrote:

No, that is just giving me "undefined method `cdetail’ for

I haven’t gotten a good feel for the :source option. Reading through
irrelevant.

For the time being I solved the issue by adding the :join option to
the find. It’s not totally ugly and seems to give me the results.
However I’d like to do it, as said above
position.cdetail.company_name.

It seems a little over-engineered, perhaps. Since you have a
companies table, why not just put the details of the companies in that
table? Then each position could have a company, which in turn would
have a name:

position.company.name

David


David A. Black | [email protected]
Author of “Ruby for Rails” [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB’s Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] Ruby for Rails | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org

Stuart Fellowes wrote:

class Position < ActiveRecord::Base
belongs_to :cdetail
belongs_to :user
end
class Cdetail < ActiveRecord::Base
has_many :positions
end

I think your original question was, how do I get the the cdetails’s
company_name, given a position. Why can’t you just do:

position.cdetail.company_name

?

“belongs_to :cdetail” adds an accessor method to Position which results
in the Cdetail referenced by the cdetail_id field in Position (you can
change the name of the field with :foreign_key, if your field is named
something other than cdetail_id).

There’s no reason to use :through here. :through is used to connect two
tables with another table between them (as an alternative to habtm). You
might want to say

class Cdetail < ActiveRecord::Base
has_many :positions
has_many :users, :through => :positions
end

if you wanted to access users directly from cdetail (use :foreign_key
and :source if the field names are weird).

When people were asking for your models, maybe they really wanted your
schema, so they could see what your field names were called to decide if
you needed foreign_keys, etc.

Tangent: Is there a reason you’re using an unintuitive reference like
“cdetail”? Why not just call it a “company”, with fields “id” and
“name”? Or even “company_detail”?

Stuart Fellowes wrote:

So, in the Position model , the attribute is company_id
and in the Cdetail model, the attribute is company_id , perhaps having
both columns with the same name is confusing the issue.

Is company_id the primary key attribute for cdetail? That could be the
problem, since Rails assumes the primary key attribute will be id. If
you’re using a weird primary key, make sure you use ActiveRecord::Base’s
primary_key method in the model to tell Rails about it. Having a weird
primary key might cause belongs_to to fail and not add the cdetail
method to Position. I’d seriously recommend your primary key just be
‘id’. If I’m not just misunderstanding you.

David wrote:

It seems a little over-engineered, perhaps. Since you have a
companies table, why not just put the details of the companies in that
table? Then each position could have a company, which in turn would
have a name:

Stuart, just to clarify: Do you have a companies table, which Position
and Cdetail reference with their company_id field? Or is
Position.company_id referencing a Cdetail record identified by it’s
company_id field? Or is company_id not a record id at all, and some
other id (like, from another database)?

On 9/25/06, ASE [email protected] wrote:

company_id field? Or is company_id not a record id at all, and some
other id (like, from another database)?

So many choices :slight_smile: … It is Position.company_id is referencing a
Cdetail record with a company_id field. For sanity sake here are the
tables:

cdetails

| id | company_id | company_name | other fields for company information
±----±---------------------±------------------------±----------------------------------------------------

positions
| id | company_id | other fields for position information
±–±-----------------------±-------------------------------------------------

You should see from above that (unfortunately cdetails id is not the
actual company id. Maybe it would make sense to have that though,
eliminate the company_id and make the id non incremental and place the
company_id there ?
That would probably make it available for reference as
position.cdetail.company_name.

What’s happening here and possibly was born out of adding to the
schema as I move along -
users do a “light” registration first - which is a login name and
password, afterwards to move up in the site’s food chain for
permissions they fill out a details profile.

Stuart

If company_id holds a value you’re getting from somewhere else, then
don’t make it your primary key.

The problem is that you have two “unique” ids for a cdetail, a record id
and a company id. Rails uses record ids to build relationship accessor
methods, but you have a valid reason to want to use the company id in
creating positions, I’m guessing, that the user who creates new
positions knows and can input the company id to which the position
should belong, but not its record id in the database (and why should
they?)

To keep your database normalized, I would remove the company_id field
from Position and replace it with a cdetail_id, so you can use all of
Rails’ relationship goodness. Then override Position’s initialize method
to get the correct cdetail_id from a company_id:

if cdetail_id.nil? and company_id
cdetail = Cdetail.find_by_company_id company_id
cdetail_id = cdetail.id unless cdetail.nil?
end

Then when you save, the position will refer to the correct Cdetail
record (the save will ignore the company_id property in the Position if
there’s no corresponding field in the table). Maybe add a validation for
cdetail_id which complains about company_id?

Then when you need the company_id (or, silimlarly, company_name) for a
position, you do:

position.cdetail.company_id

I would also recommend that you rename company_id to something that
doesn’t have such associations for other Rails coders, like
company_identifier. Another rails coder who saw a field named company_id
would assume you have a table named companies and might get confused.

Again, for clarity, where does the content for the company_id field come
from? If it’s entirely internal, I would get rid of it and just use the
cdetails’ id field as the unique identifier and present the user with a
menu of company names (or something).

Stuart Fellowes wrote:

I haven’t gotten a good feel for the :source option. Reading through
the doc it seems to reference another model ?

It describes how to get at the other model in a many-to-many
relationship with weird fieldnames.

For a many-to-many “has_many :through” relationship, Rails assumes you
have a schema like the following (habtm wouldn’t have a Second.id):

First:
id
Second:
id
first_id
third_id
Third
id

and models like

class First < ActiveRecord::Base
has_many :thirds, :through => :seconds
end
class Second < ActiveRecord::Base
belongs_to :first
belongs_to :third
end
class Third < ActiveRecord::Base
has_many :firsts, :through => :seconds
end

This sets up a many-to-many relationship between First and Third. Now if
your Second schema was like this:

Second:
id
weird_first_id
weird_third_id

…then Rails’s assumption about what your fields will be called will be
wrong and you have to tell it, like this:

class First < ActiveRecord::Base
has_many :thirds, :through => :seconds,
:foreign_key => :weird_first_id, :source => :weird_third_id
end
class Second < ActiveRecord::Base
belongs_to :first, :foreign_key => :weird_first_id
belongs_to :third, :foreign_key => :weird_third_id
end
class Third < ActiveRecord::Base
has_many :firsts, :through => :seconds,
:foreign_key => :weird_third_id, :source => weird_first_id
end

I hope I got that all right.

has_many tell Rails that (or actually, tells Rails to set up accessor
methods based on the assumption that) there is another table out there
which references this model and has field which stores the id of a
record in this model. So has_many’s :foreign_key is talking about the
field in the other table.

belongs_to tells Rails that this model references a record in another
table, and that this model stores the id of a record in the other table.
So belongs_to’s :foreign_key is talking about the field in this table.

has_many :through tell Rails that this table is joined to another (far)
table by a middle table which has fields which store the ids of a record
in this table and a record in the far table. So has_many :through’s
:foreign_key talks about a field in the middle table referencing this
table, and has_many :through’s :source talks about a field in the middle
table referencing the far table.

:source is ONLY used for :through associations, so it doesn’t apply to
your Cdetail/Person relationship (which is one-to-many, not
many-to-many, which is what :through is primarily for).

Just saw your last post, will follow up.

Stuart Fellowes wrote:

Again, company_id in both the positions table and the cdetails table
comes from the user id in the session information.

Wow, okay.

So if I understand you, each user can have one cdetail and many
positions.

Forget what I said before. I would set up a has_one/belongs_to
relationship between User and Cdetail, and a has_many/belongs_to
relationship between User and Position. That’s it. Then to get the
Cdetail from a Position, I would do this:

position.user.cdetail

And seriously, I would rename cdetails to companies, and company_id to
user_id. It’s a lot easier to read and maintain if the methods and
properties describe what they’re referring to.

On 9/25/06, ASE [email protected] wrote:

they?)
Actually without sounding too screwey, I’m grabbing it out of the
session information.

To keep your database normalized, I would remove the company_id field
from Position and replace it with a cdetail_id, so you can use all of
Rails’ relationship goodness. Then override Position’s initialize method
to get the correct cdetail_id from a company_id:

if cdetail_id.nil? and company_id
cdetail = Cdetail.find_by_company_id company_id
cdetail_id = cdetail.id unless cdetail.nil?
end

I’m thinking all this wouldn’t be necessary. All I’m really doing is
taking the user_id out of the session information and calling that the
company_id. Renaming the position.company_id to cdetail_id shouldn’t
effect the creation of the position. btw, if it means anything, I’m
using a hidden field to input this id. You did say the magic words
though “normalize the database”. Yes that is important.

Then when you save, the position will refer to the correct Cdetail
record (the save will ignore the company_id property in the Position if
there’s no corresponding field in the table). Maybe add a validation for
cdetail_id which complains about company_id?

Then when you need the company_id (or, silimlarly, company_name) for a
position, you do:

position.cdetail.company_id

K. that makes sense. I’ll be trying it shortly and let you know how it
works out.

Again, for clarity, where does the content for the company_id field come
from? If it’s entirely internal, I would get rid of it and just use the
cdetails’ id field as the unique identifier and present the user with a
menu of company names (or something).

Again, company_id in both the positions table and the cdetails table
comes from the user id in the session information.

Stuart

On 9/25/06, ASE [email protected] wrote:

Followed through a was returned
You have a nil object when you didn’t expect it!
The error occurred while evaluating nil.cdetail

Forget what I said before. I would set up a has_one/belongs_to
relationship between User and Cdetail, and a has_many/belongs_to
relationship between User and Position. That’s it. Then to get the
Cdetail from a Position, I would do this:

position.user.cdetail

So here is the models just to show that i followed above correctly:
Actually all these relationships existed already.

class User < ActiveRecord::Base
has_one :cdetail
has_many :positions
before_create :make_activation_code

class Position < ActiveRecord::Base

belongs_to  :user
belongs_to  :cdetail

class Cdetail < ActiveRecord::Base
belongs_to :user
has_many :positions

Maybe I’m doing something wrong here .

stuart