why is ActiveRecord tying to select nonex ID column?

I've loaded (and updated) thousands of these MeteredUsage records. In
the middle of a run, I suddenly get:

ActiveRecord::StatementInvalid: Mysql::Error: Unknown column 'id' in
'where clause': UPDATE `metered_usages` SET `cost` = 12603.46 WHERE `id`
= NULL

I can't see anything that's different about this particular record
compared to its brethren. (Note that I'm invoking this at the console
-- this is not a question about views or controllers.) So the schema
and the model:

It is a pretty fundamental assumption of Active Record that tables
have a primary key. There is (or at least there used to be) a plugin
out there that added support for composite primary keys

Fred

Frederick Cheung wrote:

It is a pretty fundamental assumption of Active Record that tables
have a primary key. There is (or at least there used to be) a plugin
out there that added support for composite primary keys

Fred

@fred: You may be right, but in that case, the documentation is wrong.
From

http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html

we have:

:id - Whether to automatically add a primary key column. Defaults
to true. Join tables for has_and_belongs_to_many should set
:id => false.

And, as I hinted at, this IS a join table.

What's interesting is that I cleared the table and rebuilt it -- no
error. When I tried to update the entire table (with identical values),
it gave the same error on the same record. Curiouser and curiouser.

- ff

It doesn't look like one to me - they tend to just have two fields;
one for each foreign key.

If it's a join table using a :through relationship, it still exists as
a model in its own right, and needs an id.

And, as I hinted at, this IS a join table.

Alas, it is not. At least in RoR terms.

What's interesting is that I cleared the table and rebuilt it -- no
error. When I tried to update the entire table (with identical values),
it gave the same error on the same record. Curiouser and curiouser.

There is nothing courious about it. Rails try to locate particular entry
by means of id, and if it is not there…

Regards,
Rimantas

Join tables of the habtm variety are just join tables and typically
have no associated model - the assumption is that they are only
manipulated behind the scenes by active record. If you're going do be
doing foo.save! (or foo.update_attributes which calls save) then you
need a primary key, it's just how active record works

Fred

My error in terminology -- it's not a join table, but rather a "fact"
table in the Dimensional Database sense. Would it be any different if
this was a HABTM table?

Frederick Cheung wrote:

...If you're going do be
doing foo.save! (or foo.update_attributes which calls save) then you
need a primary key, it's just how active record works

Fred, with all respect, I can't accept your assertion at face value:
I've made thousands of calls to foo.update_attributes on this table, and
only this particular update has triggered the error. There must be
something else going on.

- ff

My error in terminology -- it's not a join table, but rather a "fact"
table in the Dimensional Database sense. Would it be any different if
this was a HABTM table?

Frederick Cheung wrote:
> ...If you're going do be
> doing foo.save! (or foo.update_attributes which calls save) then you
> need a primary key, it's just how active record works

Fred, with all respect, I can't accept your assertion at face value:
I've made thousands of calls to foo.update_attributes on this table, and
only this particular update has triggered the error. There must be
something else going on.

I wouldn't expect you to. Read the source:

activerecord/lib/base.rb

def update(attribute_names = @attributes.keys)
  quoted_attributes = attributes_with_quotes(false, false,
attribute_names)
  return 0 if quoted_attributes.empty?
  connection.update(
    "UPDATE #{self.class.quoted_table_name} " +
     "SET #{quoted_comma_pair_list(connection, quoted_attributes)} " +
     "WHERE #{connection.quote_column_name(self.class.primary_key)} =
#{quote_value(id)}",
      "#{self.class.name} Update"
      )
end

So (apologies to Fred) this may be deeper than I thought: How does a
HABTM table EVER get updated? From the Rails 1.9.1
active_record/base.rb sources, update_attributes => save =>
create_or_update => update. The sources for update():

      # Updates the associated record with values matching those of the
instance attributes.
      # Returns the number of affected rows.
      def update(attribute_names = @attributes.keys)
        quoted_attributes = attributes_with_quotes(false, false,
attribute_names)
        return 0 if quoted_attributes.empty?
        connection.update(
          "UPDATE #{self.class.quoted_table_name} " +
          "SET #{quoted_comma_pair_list(connection, quoted_attributes)}
" +
          "WHERE #{connection.quote_column_name(self.class.primary_key)}
= #{quote_value(id)}",
          "#{self.class.name} Update"
        )
      end

Sure enough, it appears to depend on some sort of 'id' -- what is the
primary_key column name for HABTM tables? Or am off in the weeds?

Fred, as I said: apologies!

But since you clearly understand better than I do, I'm hoping you can
enlighten me as to how HABTM tables ever get updated? Or how a single
entry in a HABTM table gets deleted (since the delete method, like
update, seems to depend on the existence of an id field)?

Thanks again.

So (apologies to Fred) this may be deeper than I thought: How does a
HABTM table EVER get updated? From the Rails 1.9.1
active_record/base.rb sources, update_attributes => save =>
create_or_update => update. The sources for update():

[snip]

Sure enough, it appears to depend on some sort of 'id' -- what is the
primary_key column name for HABTM tables? Or am off in the weeds?

for a normal HABTM there is no corresponding model, so rows never get
updated by this code path. The habtm association generates sql
fragments directly and runs them
Furthermore, because habtm is usually used as a 'dumb' join table,
it's only ever a question of deleting or inserting rows

Fred

Frederick Cheung wrote:

for a normal HABTM there is no corresponding model, so rows never get
updated by this code path. The habtm association generates sql
fragments directly and runs them
Furthermore, because habtm is usually used as a 'dumb' join table,
it's only ever a question of deleting or inserting rows

Fred

Enlightenment is a slow process, at least for me.

So I think what's going on is that I've created a table that *should* be
declared as HABTM. And now that I think about it in those terms, I'm
not sure why I didn't to that in the first place.

Time for another "script/generate migration"...

Thanks for your patience.

- ff

Actually, I think what you want is to use has_many :through

1. add an id to metered_usages

2. class MeterUsage
       belongs_to :service_address
       belongs_to :metered_service
    end

    class ServiceAddress
      has_many :metered_usages
      has_many :metered_services, :through => :metered_usages
    end

    class MeteredService
      has_many :metered_usages
      has_many :service_addresses, :through => :metered_usages
    end

Rick Denatale wrote:

Actually, I think what you want is to use has_many :through

1. add an id to metered_usages
...

Rick -- bingo -- that's what I ended up doing. You probably already
understood that metered_usage needs to carry additional data, so it
couldn't be a pure HABTM table.

Thanks for the reassurance!

- ff