Has_many:through

I just want to make sure I’m doing this properly. I have one table that
acts as a primary key:

Table : teams
team_id
name

All of my other tables have the following criteria:
team_id
stats…
more stats…
even more stats…

The foreign key is team_id. So, I’m trying to understand how to
associate the other tables through the teams table…

I’ve normalized my tables but I’m just a bit sketchy on how to proceed
from here. Should I use simple keys or should I use something like
has_many or belongs_to in my associations?

When I display a view to another person, I will be displaying the
following information:

Name
stats…
more stats…
even more stats…
stats created by mathematical methods applied to the stats above…

Any tips would be appreciated…

Meanwhile, I will continue reading the rails enterprise book on DB
models…

I understand the foreign id has to have the same association id in the
naming… for instance,…

class PassingOffense
belongs_to :team
end

class ScoringOffense
belongs_to :team
end

class Teams
has_many :passing_offenses
has_many :scoring_offenses
end

Correct? Or, should I be doing something different here? Keep in mind
all tables have team_id but only Teams has the team’s ‘name’.

On Jun 25, 8:22 pm, “Älphä Blüë” [email protected]
wrote:

I just want to make sure I’m doing this properly. I have one table that
acts as a primary key:

No. Tables do not act as primary keys. Fields act as primary keys
for tables.

Table : teams
team_id
name

ActiveRecord expects the primary key for the table to be called simply
“id”, not “whatever_id”, although you can override this if you want.
In fact, the migrations will automatically create an id field if you
don’t specify it.

All of my other tables have the following criteria:
team_id
stats…
more stats…
even more stats…

The foreign key is team_id. So, I’m trying to understand how to
associate the other tables through the teams table…

Probably through the Team model. You know…
@team = Team.find(:some => :conditions)
@team.stats
@team.more_stats

I’ve normalized my tables but I’m just a bit sketchy on how to proceed
from here. Should I use simple keys or should I use something like
has_many or belongs_to in my associations?

That’s not an “or” question. has_many and belongs_to are the way you
tell Rails about your table associations – it’s not smart enough to
infer them from the schema, although I seem to recall the
foreign_key_associations plugin can do that. I’ve never used that
plugin, though – I generally would rather specify associations
explicitly.

Any tips would be appreciated…
See above.

Meanwhile, I will continue reading the rails enterprise book on DB
models…

I suspect it may assume a level of SQL and RDBMS knowledge that you
probably lack at present…

Best,

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

Thanks Marnen,

So basically:

Teams has

id
name

Other tables have

team_id
stats
more_stats
still_more_stats

… then the model is singular

class Team
has_many :passing_offenses
has_many :scoring_offenses
end

and the other tables use team_id as the foreign key association…

class PassingOffense
belongs_to :team
end

class ScoringOffense
belongs_to :team
end

Then I just need to define my queries. I’m better when I see them so
I’ll try some of your examples in the console and see how the output
works.

Thanks mate.

On Jun 25, 8:48 pm, “Älphä Blüë” [email protected]
wrote:

I understand the foreign id has to have the same association id in the
naming…

By default, yes. If the foreign key field has a different name, you
can specify that.

class Teams
has_many :passing_offenses
has_many :scoring_offenses
end

Correct?

Almost correct – class Teams should be class Team (remember, model
class names are always singular in Rails). This will expect a team_id
column in the passing_offenses and scoring_offenses tables, and will
create Team#passing_offenses, Team#scoring_offenses, and other methods
(see Association docs for the full list).

Or, should I be doing something different here? Keep in mind
all tables have team_id but only Teams has the team’s ‘name’.

No, other than the pluralization of Teams, this looks pretty good.

Best,

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

That was really a great guide. I understand the associations a lot
better now. However, I didn’t see any detailed examples on querying in
the associations guide. Based on what I read and looking over my
database tables, I will definitely be using a belongs_to and has_many
association.

So, for instance…

class Team
has_many :rushing_offenses
end

class RushingOffense
belongs_to :team
end

Table teams:
id
name

Table rushing_offense:
team_id
statone
stattwo
statthree
compiled_on

The name column in teams has to be unique, meaning there will always
only be 120 teams assigned to 120 ids.

So, if I’m creating both a new record in both tables, I’m using
something similar to this?

@rushing_offense = @team.rushing_offenses.create(:name => ‘Florida’,
:statone => ‘143’, :stattwo => ‘173’, :statthree => ‘194’,
:compiled_on => Time.now)

The id would automatically be created in the Teams table and because the
foreign key is team_id, that would automatically match when the record
is created? This is the part I’m leery about.

Now the other issue I have is when I do further updating. For instance,
I compiled weekly data. This means that in the rushing_offense table
there will always be 120 records for each week (matching the same number
of teams). I create new records and then view the data based on the
current week, doing something similar by referencing a named_scope:

named_scope :compiled_this_week, lambda { { :conditions =>
[‘compiled_on > ? and compiled_on < ?’, Time.now.beginning_of_week,
Time.now.end_of_week] } }

So, the question is when I go to create the new week’s data, isn’t an
error going to be thrown in the teams table because it sees that a
unique team is trying to be created? Or, it will it simply not create a
new team/id in the teams table but go ahead and create the data in the
rushing_offense table?

Again, these are specific questions that I have. I need to ensure that
I’m able to create weekly data and find weekly data. In my past model
it was easy because I just had everything duplicated in every table and
queried by a given table. However, it was an incorrect way of doing
things and the tables were not normalized. Now they are.

Please advise…

2009/6/26 Älphä Blüë [email protected]:


Then I just need to define my queries. Â I’m better when I see them so
I’ll try  some of your examples in the console and see how the output
works.

You might well find it worth while looking through some of the guides
at guides.rubyonrails.org particularly the one on Active Record
Associations. This will provide some very useful tips on how to go
about organising and accessing the data.

Colin

2009/6/26 Älphä Blüë [email protected]:

That was really a great guide. Â I understand the associations a lot
better now. Â However, I didn’t see any detailed examples on querying in
the associations guide. Â Based on what I read and looking over my
database tables, I will definitely be using a belongs_to and has_many
association.

Querying is where the magic of rails comes into play. You can use
this_team = Team.find(id) to pick up a particular team or this_team =
Team.find_by_name( ‘some team name’). Then this_team.rushing_offenses
will give you an array of all the rushing offenses for this team.

Table teams:
id
name

Table rushing_offense:

The table name should be plural - rushing_offenses

something similar to this?

@rushing_offense = @team.rushing_offenses.create(:name => ‘Florida’,
:statone => ‘143’, :stattwo => ‘173’, :statthree => ‘194’,
:compiled_on => Time.now)

I have not done it in this way as I have generally created things
using the standard methods using form submission. I would suggest you
work through some of the other guides also if you haven’t done so
already. Then I suggest you play about a bit to see how it all works.
When things don’t seem to be doing what you expect the ruby debugger
can be very useful (I think there is a guide on using ruby-debug).

Also have a look at the guide on testing and start doing tests right
from the the beginning.

Did you build your models, views etc using the scaffold generator so
all the test skeletons have been provided for you?

Colin

Hi Colin,

I created rspec_scaffolds with both the teams and rushing_offenses
table. It was pluralized - my apologies. Here’s the schema:

Teams Table:

class CreateTeams < ActiveRecord::Migration
def self.up
create_table :teams do |t|
t.string :name, :unique => true

  t.timestamps
end

end

def self.down
drop_table :teams
end
end

Rushing Offenses Table:

class CreateRushingOffenses < ActiveRecord::Migration
def self.up
create_table :rushing_offenses do |t|
t.integer :team_id
t.integer :games
t.integer :carries
t.integer :net
t.integer :tds
t.integer :wins
t.integer :losses
t.integer :ties
t.datetime :compiled_on

  t.timestamps
end

end

def self.down
drop_table :rushing_offenses
end
end

The models are:

class Teams < ActiveRecord::Base
has_many :rushing_offenses
end

Didn’t realize that the scaffold would pluralize that - strange.

class RushingOffense < ActiveRecord::Base
belongs_to :team
end

===============================================

Now, I won’t post every bit of code because it would take away from the
topic at hand. But, I use a rake task that is run by cron to call a
scraper.rb file through the rushing_offense model to parse data from
other sites which it then populates my rushing_offense table with.
However, that was before I made changes so it has to be done differently
now.

So, it looks similar to this:

Rake -> update_rushing = RushingOffense.new
–> update_rushing.scrape(arguments)

RushingOffense model houses a scrape method that calls a file called
scraper.rb and parses out the data from the site into an array of
arrays. It then creates the new data, populating the data using
something similar:

    RushingOffense.create(:rank => offensive_rushing.rows[i][0],
      :name => offensive_rushing.rows[i][1],
      :games => offensive_rushing.rows[i][2],
      :carries => offensive_rushing.rows[i][3],
      :net => offensive_rushing.rows[i][4],
      :avg => offensive_rushing.rows[i][5],
      :tds => offensive_rushing.rows[i][6],
      :ydspg => offensive_rushing.rows[i][7],
      :wins => offensive_rushing.rows[i][8],
      :losses => offensive_rushing.rows[i][9],
      :ties => offensive_rushing.rows[i][10],
      :compiled_on => Time.now)

Now, this has all changed because by normalizing my tables, I moved the
name into the Teams table. I also removed rank (because it wasn’t fully
dependent on the primary key). I also removed avg and ydspg because I
could gain these values by doing calculations later on and so they also
were not dependent on the primary key). So, by normalizing my tables it
has created issues for me in terms of how I scrape and populate the
tables.

I’m just not sure how to tie the two tables in when creating a record.

I will more than likely populate the teams table myself to create the
default 120 teams and their IDs. But, all of my stat tables need to
know that when a record is created, the team_id needs to match the id in
the teams table. Again, I just don’t know how to create this query
based on my examples above.

Keep in mind that the rake task runs weekly so it populates new data
each and every week for each of the stat tables.

In the end, my tables should look like so:

Teams
id = 1
name = Alabama
id = 2
name = Auburn
etc.
up to 120 teams

Rushing_Offenses
team_id = 1
statone = 100
stattwo = 200
statthree = 300
team_id = 2
statone = 100
stattwo = 200
statthree = 300
etc…
up to 120 teams for that week’s rake task

The next week will do the same thing for Rushing Offenses, only the
differing data will be the time it was created/compiled on (which is the
search parameter I use on my views).

So, how do I create one single row of data using the example above if
the Teams table is pre-populated with 120 unique IDs and Teams and the
Rushing Offenses table needs to know which team_id it belongs to?

When parsing the data, all information is stored in the arrays so the
name of the team is shown. I would imagine that the name of the team
would have to be matched to the team name in the teams column and that
the id would then be passed back to the team_id, or I could be
completely mistaken.

As I have a very unique parsing mechanism, and I’ve read a lot of
articles now, some of this just doesn’t make sense to me. A lot of it
has to do with the wordiness of the articles and the way my mind works.
They talk about orders and customers and I have to change the
associations to what I’m working with in order to understand it a lot of
times.

I’m just better with reading code than I am with anything. If you give
me a paragraph describing how standard deviation works for instance, it
would go over my head. If you write out a standard deviation formula, I
understand it perfectly.

Well I hate to say I took the easier way but with this scenario I did.
I went back to the older way of doing things with regards to only the
statistics tables.

The way I see it is they are being parsed from another table and so
therefore, they are just table lists for the most part. They are
individual from one another and supply distinct views of a particular
category. Therefore, there really is no association with them other
than them just being viewed as is…

The only table I will be using to work with them is my ratings table and
for that I’m using calculations only. So, I can pull the particular
piece of information I need from each of the tables I have currently and
create calculations which are then built into the ratings view.

For other parts of my site, I will be using normalization and working on
understanding these associations better. Again, like you said… I was
trying to do too much. I can’t hurt any of my views doing it the way I
am currently. If I were populating the data inwardly and not relying on
an external source/parser - maybe then I would have to normalize and
change things.

Thanks for all your help though.

I did create a viewer/category/links association using a tutorial I
found online that allowed me to create a resources links table. It did
help me understand association better.

I am afraid I am getting out of my depth with what you are describing
below as it is not the sort of application I have experience of. I
think you may need help from others.

Your Team model class should definitely be singular though.

I still think you might be better to work through some of the
tutorials working through what one might describe as a typical
application in order that you pick up the concepts, then adapt these
to your requirements. I suspect you may have a lot of difficulty
leaping in at the deep end with a non-conventional application.

Colin

2009/6/26 Älphä Blüë [email protected]:

On Jun 26, 11:55 am, “Älphä Blüë” [email protected]
wrote:
[…]
RushingOffense model houses a scrape method that calls a file called

      :tds => offensive_rushing.rows[i][6],
      :ydspg => offensive_rushing.rows[i][7],
      :wins => offensive_rushing.rows[i][8],
      :losses => offensive_rushing.rows[i][9],
      :ties => offensive_rushing.rows[i][10],
      :compiled_on => Time.now)

—begin style-Nazi digression—

Just as an aside: this is rather unidiomatic Ruby. Why didn’t you
assign offensive_rushing.rows[i] to a local variable and save some
typing (and possibly some calculation time)?

Worse, the [i] makes me think that you’re using a for loop of some
sort. Remember, Ruby is not PHP, and it has different looping
constructs. You want rows.each.

Or you could do something like this:

FIELDS = [:rank, :name, :games, …]
offensive_rushing.rows.each do |row|
values = {:compiled_on => Time.now}
FIELDS.each_with_index do |field, i|
values[field] = row[i]
end
RushingOffense.create values
end

…which is shorter and arguably easier to read.

-----end style-Nazi digression-----

Also note that you’re doing a separate DB query for each record
created, which is a terrible idea (queries don’t belong inside
loops). Investigate ar-extensions or some other bulk insertion
plugin, or if all else fails, generate a bulk insert statement
directly in SQL.

Now, this has all changed because by normalizing my tables, I moved the
name into the Teams table. I also removed rank (because it wasn’t fully
dependent on the primary key). I also removed avg and ydspg because I
could gain these values by doing calculations later on and so they also
were not dependent on the primary key). So, by normalizing my tables it
has created issues for me in terms of how I scrape and populate the
tables.

I’m just not sure how to tie the two tables in when creating a record.

Simple:

team_name = offensive_rushing.rows[i][1] # (or whatever the proper
array subscript is)
team = Team.find_by_name(team_name)

and then either:

team.rushing_offenses << RushingOffense.create(:whatever =>
‘data’, :you => ‘want’)

or:

RushingOffense.create(:team => team, :other => ‘data’)

Rails does most of the work for you.

[…]

So, how do I create one single row of data using the example above if
the Teams table is pre-populated with 120 unique IDs and Teams and the
Rushing Offenses table needs to know which team_id it belongs to?

That’s how.

When parsing the data, all information is stored in the arrays so the
name of the team is shown. I would imagine that the name of the team
would have to be matched to the team name in the teams column and that
the id would then be passed back to the team_id, or I could be
completely mistaken.

You are completely correct. That’s what the Team.find_by_name line
above does.

As I have a very unique parsing mechanism, and I’ve read a lot of
articles now, some of this just doesn’t make sense to me.

Your parsing mechanism is irrelevant. You just need to know what to
do with the data once parsed.

Best,

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

On Jun 26, 3:13 pm, “Älphä Blüë” [email protected]
wrote:

Well I hate to say I took the easier way but with this scenario I did.
I went back to the older way of doing things with regards to only the
statistics tables.

Bad idea. Proper use of associated tables is so fundamental to
efficient application design – and so well supported in Rails – that
it is worth holding up further development until you understand it. I
try not to be dogmatic about many things, but this is one of them.

See my other recent post in this thread for sample ideas. Really,
learn to use the framework and the database, don’t just kludge your
way around them.

Best,

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

Hi Marnen,

Well I have multiple copies of my project (one on 1.9.1 with mysql,
another on 1.8.6 with postgres, and another on 1.8.6 with mysql). I
also keep a full backup of each with subversion and hard-copied.

So, I played around with the parser object first, using your
suggestions. I came up with:

FIELDS = [:rank, :name, :games, :carries, :net, :avg, :tds, :ydspg,
:wins, :losses, :ties]
def scrape(url,type,name,deep)
# scraping/parsing our data
offensive_rushing = Scraper.new(url,type,name,deep)
offensive_rushing.scrape_data
offensive_rushing.clean_celldata
# checking to see if the data exists in our table for the current
week
@rushing_offenses = RushingOffense.compiled_this_week.find(:all)
if @rushing_offenses == [] # means we have an empty array so no data
exists
puts “Updating Offensive Rushing Statistics for the following
teams:”
offensive_rushing.rows.each do |row|
values = {:compiled_on => Time.now}
FIELDS.each_with_index do |field, i|
values[field] = row[i]
end
# List our teams
puts row[1]
RushingOffense.create values
end
end
if @rushing_offenses != [] # means the current week’s data is not
empty so don’t update data
puts “Current Week’s Data Is Already Populated!”
end
end

And the parser object works fine with your suggestions. Yes, you were
correct about the for loop. This bit of code you provided helped me
understand how arrays work in a much deeper detail. I’ve been using
ruby’s .each with normal arrays but was having difficulty with array of
arrays. Thanks for shedding the light.

I’ll try your suggestions with my backed up project and mull over things
a bit. It is very difficult for me to slow down though mate. I’m
patient but that’s not necessarily the same thing. I have a lot of
ideas and I like to play with those ideas. Rails to me is like a giant
candy store and I’m starving and got locked inside.

Älphä Blüë wrote:
[…]

And the parser object works fine with your suggestions.

Glad to hear it!

Yes, you were
correct about the for loop. This bit of code you provided helped me
understand how arrays work in a much deeper detail. I’ve been using
ruby’s .each with normal arrays but was having difficulty with array of
arrays. Thanks for shedding the light.

You’re welcome. The shift from loop indices to iterators can be a
little difficult, but it’s worth it.

I’ll try your suggestions with my backed up project

Remember: version control means never having to say you’re sorry. (Or
rather, it makes it easier to say you’re sorry…)

and mull over things
a bit. It is very difficult for me to slow down though mate. I’m
patient but that’s not necessarily the same thing.

I understand. The first time I used an app framework, version control,
test-first development…the first time I used each of these, it felt
like another piece to get in the way. But in each case, it turned out
to pave the way for quicker development and better code.

I have a lot of
ideas and I like to play with those ideas.

Me too.

Rails to me is like a giant
candy store and I’m starving and got locked inside.

Then please, take the time to locate the healthier snacks, or you’ll
gorge yourself on sugar and get a horrible stomachache!

Best,

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

Hrm, okay my tables are setup correctly now and populated so I’m trying
to do a find, but I must be doing it wrong…

class Team < ActiveRecord::Base
has_many :rushing_offenses
end

class RushingOffense < ActiveRecord::Base
belongs_to :team
end

I’ve tried the following in the rails console:

@team = Team.find(:all)
@rushing_offenses = @team.rushing_offenses.find(:all)

and get an undefined method rushing_offenses error

Again, I’m probably doing this wrong. Let me break it apart the way I
believe it’s reading from a code standpoint:

@team = Team.find(:all)
–> Is finding all the data in the teams table

@rushing_offenses = @team.rushing_offenses.find(:all)
–> assign an instance variable @rushing_offenses
–> utilize the associations with: @team found all data in teams
–> utilize the associations with: rushing_offenses (because it belongs
to team)
–> rushing_offenses.find(:all) would find all data in the
rushing_offenses table

Yes?.. No?..

I read up on the associations and understand the association thinking
and when to use associations. I just don’t understand the code syntax
between them and how to use the code syntax for normal querying…

Marnen,

I managed to get my scraper/parser working as intended with utilizing
both the teams table and the stats table it is updating. I did the
following:

FIELDS = [:rank, :team_id, :games, :carries, :net, :avg, :tds, :ydspg,
:wins, :losses, :ties]
def scrape(url,type,name,deep)
offensive_rushing = Scraper.new(url,type,name,deep)
offensive_rushing.scrape_data
offensive_rushing.clean_celldata
@rushing_offenses = RushingOffense.compiled_this_week.find(:all)
if @rushing_offenses == [] # means we have an empty array so no data
exists
puts “Updating Offensive Rushing Statistics for the following
teams:”
offensive_rushing.rows.each do |row|
team = Team.find_by_name(row[1])
values = {:compiled_on => Time.now}
FIELDS.each_with_index do |field, i|
if row[i] == row[1]
values[field] = team.id
else
values[field] = row[i]
end
end
# List each team we update
puts team.name + " ID = " + team.id.to_s
RushingOffense.create values
end
else
# data is already populated for the week so don’t update
puts “Current Week’s Data Is Already Populated!”
end
end

Basically, since the parser houses the name of the team in row[1] I
simply substituted :name for :team_id in my constant. I then supplied
the team variable per your helpful tidbits and then checked in the each
method to see if row[i] was the same as row[1] and if so to substitute
the values for team.id.

It lines up correctly and appears to function fine. Do you see any
possible errors with my syntax? I just want to make sure I’ve done this
part right.

So far my teams table is populated with the correct teams and
rushing_offense is populated correctly as well. I’m going to work on
the find…

I also have a question on Constants. In this case, it appears the
FIELDS constant should probably be named to something associated with
the rushing_offense model as it is just going to be used by this model.
But, if I had a constant that needed to be usable by all models would I
put that in environment.rb?

Thanks again mate. Wish me luck on the find queries.

Älphä Blüë wrote:

Hrm, okay my tables are setup correctly now and populated so I’m trying
to do a find, but I must be doing it wrong…

I think you are.

class Team < ActiveRecord::Base
has_many :rushing_offenses
end

class RushingOffense < ActiveRecord::Base
belongs_to :team
end

This is fine.

I’ve tried the following in the rails console:

@team = Team.find(:all)
@rushing_offenses = @team.rushing_offenses.find(:all)

and get an undefined method rushing_offenses error

Of course you do. ActiveRecord::Base.find(:all) returns an array of
model objects, whereas methods like rushing_offenses are defined on
each model instance, not on the array as a whole. You need to call
rushing_offenses on the particular Team instance that you’re interested
in. (And you don’t need the .find(:all) on rushing_offenses.)
[…]

I read up on the associations and understand the association thinking
and when to use associations. I just don’t understand the code syntax
between them and how to use the code syntax for normal querying…

I think you understand the syntax. You just seem to be uncertain on
what methods are defined on the individual models and what’s defined on
the array as a whole.

Best,

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

I decided to test whether or not I was getting new methods through
belongs_to and I’m not. I did:

Commented the belongs_to :team and saved the RushingOffense model

f = RushingOffense.find :all
–> Found all my records
original_methods = f.methods
–> Listed all of the current methods

Uncommented the belongs_to :team and saved the RushingOffense model
reload!
–> Reloaded the model
f = RushingOffense.find :all
–> Found all my records
new_methods = f.methods
–> Listed all of the current methods

added_methods = new_methods - original_methods

==> [] (empty)

I thought by adding belongs_to I would receive new methods due to the
association? Am I mistaken…?

Älphä Blüë wrote:

I decided to test whether or not I was getting new methods through
belongs_to and I’m not. I did:

Commented the belongs_to :team and saved the RushingOffense model

f = RushingOffense.find :all
→ Found all my records
original_methods = f.methods
→ Listed all of the current methods

Uncommented the belongs_to :team and saved the RushingOffense model
reload!
→ Reloaded the model
f = RushingOffense.find :all
→ Found all my records
new_methods = f.methods
→ Listed all of the current methods

added_methods = new_methods - original_methods

==> [] (empty)

I thought by adding belongs_to I would receive new methods due to the
association? Am I mistaken…?

Well, as I explained in another post, the methods you’ll get are defined
on the individual Team objects, not the array. But your methodology
here is problematic anyway: all you’re doing is checking the contents of
the array returned by Object#methods. Since methods can be created
dynamically or faked with method_missing trickery (which AR uses quite
often), there may be valid calls that do not appear in the methods
array. Therefore, it’s safer to use Object#respond_to? to check this
sort of thing.

Best,

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