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