Carl Youngblood
2005-Oct-26 18:47 UTC
habtm with additional attributes that need to be changed after association is made
Hello, I''m writing an app with an habtm relationship like so: class User < ActiveRecord::Base has_and_belongs_to_many :resumes end class Resume < ActiveRecord::Base has_and_belongs_to_many :users def mark_as_viewed(user_id) # what to put here? end end The joining table has an extra attribute that holds a 1 or a 0 indicating whether or not this resume has been viewed already by this user: CREATE TABLE resumes_users ( resume_id INT UNSIGNED NOT NULL DEFAULT 0, user_id INT UNSIGNED NOT NULL DEFAULT 0, viewed INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (resume_id, user_id) ); I want to be able to associate resumes with users beforehand and then mark them as view at a later time. I saw the method push_with_attributes for creating an association and adding information at the same time. What is the best way to do this after an association has already been created? Thanks, Carl
Jamie Macey
2005-Oct-26 21:08 UTC
Re: habtm with additional attributes that need to be changed after association is made
The consensus from when I had this question a few months ago (July/August) was to make a separate model for your habtm relation. In my case I had a Subscriber and a List, and wanted to track extra data about their relation, and the best way to do it was to create a Subscription model, which has_a :subscriber and has_a :list. Both Subscriber and List then has_many :subscriptions. For your data, I don''t know what I''d call it, but breaking the relation out into a separate class makes the metadata management much more palatable. - Jamie On Wed, 2005-26-10 at 11:47 -0700, Carl Youngblood wrote:> Hello, I''m writing an app with an habtm relationship like so: > > class User < ActiveRecord::Base > has_and_belongs_to_many :resumes > end > > class Resume < ActiveRecord::Base > has_and_belongs_to_many :users > > def mark_as_viewed(user_id) > # what to put here? > end > end > > The joining table has an extra attribute that holds a 1 or a 0 > indicating whether or not this resume has been viewed already by this > user: > > CREATE TABLE resumes_users > ( > resume_id INT UNSIGNED NOT NULL DEFAULT 0, > user_id INT UNSIGNED NOT NULL DEFAULT 0, > viewed INT UNSIGNED NOT NULL DEFAULT 0, > PRIMARY KEY (resume_id, user_id) > ); > > I want to be able to associate resumes with users beforehand and then > mark them as view at a later time. I saw the method > push_with_attributes for creating an association and adding > information at the same time. What is the best way to do this after > an association has already been created? > > Thanks, > > Carl > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails
Carl Youngblood
2005-Oct-26 21:43 UTC
Re: habtm with additional attributes that need to be changed after association is made
Thanks for the tip, but do think this is a good idea even if my model will only contain one single boolean variable? In that case, don''t you think it makes more sense to keep it in the association table? On 10/26/05, Jamie Macey <jamie-7g3wz9A/6AxWk0Htik3J/w@public.gmane.org> wrote:> The consensus from when I had this question a few months ago > (July/August) was to make a separate model for your habtm relation. > > In my case I had a Subscriber and a List, and wanted to track extra data > about their relation, and the best way to do it was to create a > Subscription model, which has_a :subscriber and has_a :list. Both > Subscriber and List then has_many :subscriptions. > > For your data, I don''t know what I''d call it, but breaking the relation > out into a separate class makes the metadata management much more > palatable.
Michael Smedberg
2005-Oct-26 22:22 UTC
Re: habtm with additional attributes that need to be changed after association is made
Here''s a copy of an email I sent earlier about this problem: I had the same problem, and added a function to habtm to handle this. I submitted it to the rails team as ticket 2462. The source code was: module ActiveRecord module Associations class HasAndBelongsToManyAssociation def update_attributes(record, join_attributes = {}) # Did they pass in an ID or an object? if record.is_a? ActiveRecord::Base # Check the type of the passed in record raise_on_type_mismatch(record) # Find the actual record in @target, if @target is loaded if loaded? record_in_arr = @target.find { | item | item == record } raise ActiveRecord::RecordNotFound, "#{record.class} #{record.id<http://record.id/>} not found in collection" unless !record_in_arr.nil? record = record_in_arr record_id = record.id <http://record.id/> else record_id = record.id <http://record.id/> record = nil end else # The record isn''t an ActiveRecord, assume it''s an ID record_id = record.to_i # If the target is loaded, find the record in the target if loaded? # Find the actual record in @target record_in_arr = target.find { | item | item.id <http://item.id/> == record } raise ActiveRecord::RecordNotFound, "Item with ID #{record} not found in collection" unless !record_in_arr.nil? record = record_in_arr else # Not loaded- for performance, don''t load it, just do an update based on the ID record = nil end end # Break the join_attributes into columns and values for those columns cols_and_vals = join_attributes.to_a.transpose # Join the columns together with '' = ?, '', so the result for [a, b] # would be ''a = ?, b'' NOTE: We will have to add a trailing '' = ?'' # in the SQL col_string = cols_and_vals[0].join('' = ?, '') #NOTE: :before_update doesn''t do anything right now- this is "future proofing" callback(:before_update, record) if !record.nil? # Do the SQL, passing in the args @owner.connection().update(sanitize_sql(["UPDATE #{@join_table} SET #{col_string} = ? WHERE #{@association_class_primary _key_name} = ? AND #{@association_foreign_key} = ?", cols_and_vals[1], @owner.id<http://owner.id/>, record_id].flatten), "Update Attributes") # Fix up @target, the array of items IF it''s loaded join_attributes.each { | att, att_val | record[att] = att_val } if !record.nil? #NOTE: :after_update doesn''t do anything right now- this is "future proofing" callback(:after_update, record) if !record.nil? end end end end which I added to application_helper.rb in my app. Also see the thread with subject "*[Q] habtm with extra attributes*" on this forum, which discusses this a bit. I''ve updated the ticket a couple times, adding functionality, etc. On 10/26/05, Carl Youngblood <carl-MJzSGySFh6ZUfOvSQQQpYw@public.gmane.org> wrote:> > Thanks for the tip, but do think this is a good idea even if my model > will only contain one single boolean variable? In that case, don''t > you think it makes more sense to keep it in the association table? > > On 10/26/05, Jamie Macey <jamie-7g3wz9A/6AxWk0Htik3J/w@public.gmane.org> wrote: > > The consensus from when I had this question a few months ago > > (July/August) was to make a separate model for your habtm relation. > > > > In my case I had a Subscriber and a List, and wanted to track extra data > > about their relation, and the best way to do it was to create a > > Subscription model, which has_a :subscriber and has_a :list. Both > > Subscriber and List then has_many :subscriptions. > > > > For your data, I don''t know what I''d call it, but breaking the relation > > out into a separate class makes the metadata management much more > > palatable. > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >_______________________________________________ Rails mailing list Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails