Hi, I think this question comes up every so often, but I checked everything I could think of and couldn''t find a good solution. The question is how to properly do a has_and_belongs_to_many relationship with checkboxes. The wiki entry at http://wiki.rubyonrails.com/rails/show/MultipleSelectOptionsHelper, particularly the text at the bottom helped me somewhat, but there are a couple problems with it, as far as I can see. Here''s the view code in the wiki example, where user habtm groups: <% Group.find(:all).each do |g| %> <input type="checkbox" id="<% g.id %>" name="user[groups_ids][]" value="<% g.id %>" <% if @user.groups.find(g.id) %> checked="checked" <% end %> /> <%= g.name %> <% end %> Two problems, I think: 1) @user.groups.find(g.id) will fail if g is not in @user.groups, throwing an exception 2) If no Group checkboxes are checked, none of the checkboxes get submitted via POST, and so User#update_attributes doesn''t bother to update the User''s Groups. The controller in the example was the standard scaffold''d UsersController#update method as far as I can see. The model code had: class User << ActiveRecord::Base has_and_belongs_to_many :groups def groups_ids=(list) groups.clear groups << Group.find(list) end end Which is a good idea, UsersController#update gets a POST''d value "user[groups_ids][the checkboxes values]" which the User model can handle, thanks to User#groups_ids. But! the controller gets (I think) "user[groups_ids][{"1" => "1", "2" => "1", "4" => "1"}]" (assuming that the first, second, and fourth (with ids 1, 2, and 4) Group checkboxes were checked). Which is a hash of group ids to a ''yes'' value. So when that hash is passed in to User#groups_ids, Groups#find(that_hash) fails. Does that analysis seem right? So....... Here''s what I did to fix the example given: 1) Added User#has_group?(id) that checks to see if a given id corresponds to one of the User''s Groups. 2) Modified User#groups_ids so that "Group.find(list)" is now "Group.find(list.keys)", so now Group#find gets an array of Group ids. 3) Modified the view code like (from memory) <% Group.find(:all).each do |g| %> <%= check_box_tag("users[groups_ids][#{g.id}]", "1", @user.has_group?(g.id)) %> <%= g.name %> <% end %> 4) In UsersController#update, added a check before @user.update_attributes() to see if @params[:user][:groups_ids] was nil, and if so, clear @users.groups. That seemed to work as far as I can see, and I couldn''t figure out a cleaner way of doing it. Comments are very much appreciated. Thanks, Joe
I wrote the stuff on the wiki in question, so I''ll respond inline. Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> writes:> Hi, > > I think this question comes up every so often, but I checked > everything I could think of and couldn''t find a good solution. The > question is how to properly do a has_and_belongs_to_many relationship > with checkboxes. > > The wiki entry at > http://wiki.rubyonrails.com/rails/show/MultipleSelectOptionsHelper, > particularly the text at the bottom helped me somewhat, but there are > a couple problems with it, as far as I can see.Yes, this seems like a common problem that should have a common solution. I came up with mine with some help from #rubyonrails. I threw it on the wiki to generate discussion.> Here''s the view code in the wiki example, where user habtm groups: > <% Group.find(:all).each do |g| %> > <input type="checkbox" id="<% g.id %>" name="user[groups_ids][]" > value="<% g.id %>" > <% if @user.groups.find(g.id) %> checked="checked" <% end %> /> > <%= g.name %> > <% end %> > > Two problems, I think: > 1) @user.groups.find(g.id) will fail if g is not in @user.groups, > throwing an exceptionThat''s true. My actual code also has accessor methods on the model like @user.group?(name) that returns true when a user is in the group with ''name''. So, my code posted to the wiki was untested. I think there''s another solution, but I''m not sure what it is.> 2) If no Group checkboxes are checked, none of the checkboxes get > submitted via POST, and so User#update_attributes doesn''t bother to > update the User''s Groups.one solution might be to have a hidden input like <input hidden id="0" name="user[groups_ids][] value="0" /> knowing there are no groups with id 0. However, i think there''s some voodoo with the id attribute on the input that will cause an empty list to be set in the @params. Not sure.> class User << ActiveRecord::Base > has_and_belongs_to_many :groups > > def groups_ids=(list) > groups.clear > groups << Group.find(list) > end > end > > Which is a good idea, UsersController#update gets a POST''d value > "user[groups_ids][the checkboxes values]" which the User model can > handle, thanks to User#groups_ids.yes, that''s the fun part.> But! the controller gets (I think) "user[groups_ids][{"1" => "1", "2" > => "1", "4" => "1"}]" (assuming that the first, second, and fourth > (with ids 1, 2, and 4) Group checkboxes were checked). Which is a > hash of group ids to a ''yes'' value. So when that hash is passed in to > User#groups_ids, Groups#find(that_hash) fails. > > Does that analysis seem right?No, the controller gets @params[''user''][''groups_ids''] = [ ''1'', ''2'', ''4''] assuming groups 1, 2, and 4 were checked.> Here''s what I did to fix the example given: > > 1) Added User#has_group?(id) that checks to see if a given id > corresponds to one of the User''s Groups.This is essentially what I have too. It''s tedious given many associations though. Ripe for a module_eval I think...> > 2) Modified User#groups_ids so that "Group.find(list)" is now > "Group.find(list.keys)", so now Group#find gets an array of Group > ids.I''m really surprised this is necessary. My User#groups_ids= method works as coded on the wiki.> 3) Modified the view code like (from memory) > <% Group.find(:all).each do |g| %> > <%= check_box_tag("users[groups_ids][#{g.id}]", > "1", > @user.has_group?(g.id)) %>To each their own, this check_box_tag doesn''t seem any more attractive than mine. Note however, naming the checkbox with a final ''[]'' (that''s an empty square bracket) causes @params to get the list of the values checked. That may be why your User#groups_ids= method needed changing.> <%= g.name %> > <% end %> > > 4) In UsersController#update, added a check before > @user.update_attributes() to see if @params[:user][:groups_ids] was > nil, and if so, clear @users.groups.I''ll need to add a test for my working code to see if this is necessary or not. I suspect the magic of ''[]'' on the input name may mean I don''t need that check. -- doug-jGAhs73c5XxeoWH0uzbU5w@public.gmane.org
On 18-mei-2005, at 22:09, Doug Alcorn wrote:> I wrote the stuff on the wiki in question, so I''ll respond inline.>> Here''s what I did to fix the example given: >> >> 1) Added User#has_group?(id) that checks to see if a given id >> corresponds to one of the User''s Groups. >> > > This is essentially what I have too. It''s tedious given many > associations though. Ripe for a module_eval I think...What I tend to do (it''s also oh-so-human but might be hard on the DB) is just Object.subobjects.include?(Subobject)>> >> 2) Modified User#groups_ids so that "Group.find(list)" is now >> "Group.find(list.keys)", so now Group#find gets an array of Group >> ids. >> > > I''m really surprised this is necessary. My User#groups_ids= method > works as coded on the wiki. > Note however, naming the checkbox with a final ''[]'' > (that''s an empty square bracket) causes @params to get the list of the > values checked. That may be why your User#groups_ids= method needed > changing. >Actually I don''t understand why there is no collection= method generated automatically with AR assotiation magic. Maybe because not everyone has transaction support (and this kind of batch assignment basically requires transactions for safety because you fire many queries in one call). What I currently do is this: def types=(ntypes) self.types.clear self.types.concat(ntypes) end I suppose with something more safe it would be so: def types=(ntypes) old_types = self.types.collect { type.id } begin self.types.clear self.types.concat(ntypes) rescue #if we get errors or validation problems - rollback, or reinsert old pairs self.types.concat(Type.find(old_types) end end or wrapped in transaction do.... or some other kind of exception-fu. I am not good enough in Ruby yet (reaching out for Pickaxe). The thing is this method automatically gets used by ActiveRecord::Base#update_attributes, so my controller stays pristine clean (just pass those @params[:hash]). After that I use this: multiselect (model_name, model_field, members_array, id_field, name_field, existing_collection = [], options = []) and a checkbox_array with the same signature.> >> <%= g.name %> >> <% end %> >> >> 4) In UsersController#update, added a check before >> @user.update_attributes() to see if @params[:user][:groups_ids] was >> nil, and if so, clear @users.groups. >> > > I''ll need to add a test for my working code to see if this is > necessary or not. I suspect the magic of ''[]'' on the input name may > mean I don''t need that check.I think it''s there so that the browser send _something_ when nothing is checked/selected. Basically, a question remains - shouldn''t this be in AR and helpers by-default? HABTM is a constant pain and for many cases can be just handled as one to many. -- Julian "Julik" Tarkhanov
On 19-mei-2005, at 1:47, Julian ''Julik'' Tarkhanov wrote:> > def types=(ntypes) > self.types.clear > self.types.concat(ntypes) > end >Oops, missed the most juicy bit def types=(ntypes) self.types.clear ntypes.collect! { |t| t = Type.find(t) unless t.is_a?(Type); t} #grab related objects by ID self.types.concat(ntypes) end -- Julian "Julik" Tarkhanov
On 5/18/05, Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> Hi, > > I think this question comes up every so often, but I checked > everything I could think of and couldn''t find a good solution. The > question is how to properly do a has_and_belongs_to_many relationship > with checkboxes.Final solution that seems to work fairly well. This comes from my actual code, where Houses have many Amenities (an Amenity is like a "Hardwood Floor" or "Fireplace"). There''s also an example of how I''m doing a one-to-many relationship with a select box (Community has_many Houses, House belongs_to Community). ====== MODEL =======class House < ActiveRecord::Base has_and_belongs_to_many :amenities belongs_to :community def amenities_ids=(list) amenities.clear amenities << Amenity.find(list) end def amenity_exists?(id) amenities.to_a.find {|a| a.id == id} end end ===== View ======= <p><label for="house_community_id">Community<br/> <%= select("house", "community_id", Community.find_all.collect {|c| [c.name, c.id]}) %> </label></p> <% for amenity in Amenity.find_all do %> <label for="<%=amenity.name%>"> <%= check_box_tag("house[amenities_ids][]", amenity.id, @house.has_amenity?(amenity.id))%> <%=amenity.name%> </label> <br/> <% end %> </p> ===== CONTROLLER ====== def update @house = House.find(@params[:id]) if @house.update_attributes(@params[:house]) # Addition to default scaffold code: if @params[:house][:amenities_ids].nil?; @house.amenities.clear; end flash[''notice''] = ''House was successfully updated.'' redirect_to :action => ''show'', :id => @house else render_action ''edit'' end end Any way I can improve on that? Thanks, Joe
On 5/19/05, Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> Any way I can improve on that?I''d like to come up with something better, as adding those functions to the model for every habtm relationship seems sorta tedious. Ideas are welcome!
On 5/19/05, Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> On 5/19/05, Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > Any way I can improve on that? > > I''d like to come up with something better, as adding those functions > to the model for every habtm relationship seems sorta tedious. Ideas > are welcome! >Also, I can''t figure out how to force a user to check one checkbox in a habtm group using validates_presence_of. Is that possible?
On 5/19/05, Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> On 5/19/05, Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > On 5/19/05, Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > Any way I can improve on that? > > > > I''d like to come up with something better, as adding those functions > > to the model for every habtm relationship seems sorta tedious. Ideas > > are welcome! > > > > Also, I can''t figure out how to force a user to check one checkbox in > a habtm group using validates_presence_of. Is that possible?Nevermind on that last question. It''s really straighforward, the problem was elsewhere.
> On 5/19/05, Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: >> Any way I can improve on that? > > I''d like to come up with something better, as adding those functions > to the model for every habtm relationship seems sorta tedious.config/environment.rb: require_dependency ''my_active_record'' lib/my_active_record.rg: class ActiveRecord::Base def self.define_habtm_setter(list) list.each do |a| module_eval <<-CODE def #{a}_ids=(list) #{a}.clear #{a} << #{a.classify}.find(list) end validates_associated :#{a} CODE end end end app/model/foo.rb: class Foo < ActiveRecord::Base has_and_belongs_to_many :bars has_and_belongs_to_many :bazes define_habtm_setter([''bars'', ''bazes'']) end There might be a more elegant way to do this as well. However, it works for me. -- doug-jGAhs73c5XxeoWH0uzbU5w@public.gmane.org
Julian ''Julik'' Tarkhanov <listbox-RY+snkucC20@public.gmane.org> writes:> def types=(ntypes) > self.types.clear > ntypes.collect! { |t| t = Type.find(t) unless t.is_a?(Type); > t} #grab related objects by ID > self.types.concat(ntypes) > endYou lost me here. Can you explain a bit further? -- doug-jGAhs73c5XxeoWH0uzbU5w@public.gmane.org
Julian ''Julik'' Tarkhanov <listbox-RY+snkucC20@public.gmane.org> writes:> self.types.concat(ntypes)OK, I''m not seeing collection.concat in the docs for has_and_belongs_to_many. What am I missing?> multiselect (model_name, model_field, members_array, id_field, > name_field, existing_collection = [], options = []) > > and a checkbox_array with the same signature.Likewise, I don''t see docs for these ActionView helpers. -- doug-jGAhs73c5XxeoWH0uzbU5w@public.gmane.org
On 5/19/05, Doug Alcorn <doug-jGAhs73c5XxeoWH0uzbU5w@public.gmane.org> wrote:> > On 5/19/05, Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > >> Any way I can improve on that? > > > > I''d like to come up with something better, as adding those functions > > to the model for every habtm relationship seems sorta tedious. > > config/environment.rb: > > require_dependency ''my_active_record'' > > lib/my_active_record.rg: > > class ActiveRecord::Base > def self.define_habtm_setter(list) > list.each do |a| > module_eval <<-CODE > def #{a}_ids=(list) > #{a}.clear > #{a} << #{a.classify}.find(list) > end > validates_associated :#{a} > CODE > end > end > endNice. I assume #classify returns the class for a? Is that a Ruby or Rails function? The list var in the CODE section isn''t related to the list being passed to #define_habtm_setter, right? That was a little confusing. How does validates_associated help? I read the documentation for the method, but it''s still not clear to me on what it does.... the docs say the method "validates whether the associated object or objects are all themselves valid.", but that doesn''t make much sense to me.> > app/model/foo.rb: > > class Foo < ActiveRecord::Base > has_and_belongs_to_many :bars > has_and_belongs_to_many :bazes > define_habtm_setter([''bars'', ''bazes'']) > end > > There might be a more elegant way to do this as well. However, it > works for me.That looks great! I''ll try it.
Joe Van Dyk wrote:>On 5/18/05, Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > >>I think this question comes up every so often, but I checked >>everything I could think of and couldn''t find a good solution. The >>question is how to properly do a has_and_belongs_to_many relationship >>with checkboxes. >> >> > >Final solution that seems to work fairly well. This comes from my >actual code, where Houses have many Amenities (an Amenity is like a >"Hardwood Floor" or "Fireplace"). There''s also an example of how I''m >doing a one-to-many relationship with a select box (Community has_many >Houses, House belongs_to Community). > >====== MODEL =======>class House < ActiveRecord::Base > has_and_belongs_to_many :amenities > belongs_to :community > > def amenities_ids=(list) > amenities.clear > amenities << Amenity.find(list) > end > >I usually use something like this in my model: def vk_category_ids=(ids) new_cats = VkCategory.find(ids) vk_categories.each{|cat| vk_categories.delete(cat) unless new_cats.include?(cat)} new_cats.each{|cat| vk_categories << cat unless vk_categories.include?(cat)} end (replace vk_category with amenities) It has the advantage of not deleting everything in the database just to reinsert it rightaway (especially useful if you do not have transaction support). I''m aware that the code is O(n^2) which could be an issue for bigger collections. One could easily bring this down to O(n log n) using set or some sorting, but it would certainly have higher constants. I think, we should patch AR to automatically generate a "relation=" method that performs this "diff"-strategy. I volunteer to create such a patch if nobody else is already working on this. Sebastian
On 5/19/05, Sebastian Kanthak <sebastian.kanthak-ZS8b95Whz3sUSW6y5lq3GQ@public.gmane.org> wrote:> Joe Van Dyk wrote: > > >On 5/18/05, Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > > > >>I think this question comes up every so often, but I checked > >>everything I could think of and couldn''t find a good solution. The > >>question is how to properly do a has_and_belongs_to_many relationship > >>with checkboxes. > >> > >> > > > >Final solution that seems to work fairly well. This comes from my > >actual code, where Houses have many Amenities (an Amenity is like a > >"Hardwood Floor" or "Fireplace"). There''s also an example of how I''m > >doing a one-to-many relationship with a select box (Community has_many > >Houses, House belongs_to Community). > > > >====== MODEL =======> >class House < ActiveRecord::Base > > has_and_belongs_to_many :amenities > > belongs_to :community > > > > def amenities_ids=(list) > > amenities.clear > > amenities << Amenity.find(list) > > end > > > > > I usually use something like this in my model: > > def vk_category_ids=(ids) > new_cats = VkCategory.find(ids) > vk_categories.each{|cat| vk_categories.delete(cat) unless > new_cats.include?(cat)} > new_cats.each{|cat| vk_categories << cat unless > vk_categories.include?(cat)} > end > > (replace vk_category with amenities) > > It has the advantage of not deleting everything in the database just to > reinsert it rightaway (especially useful if you do not have transaction > support). I''m aware that the code is O(n^2) which could be an issue for > bigger collections. One could easily bring this down to O(n log n) using > set or some sorting, but it would certainly have higher constants. > > I think, we should patch AR to automatically generate a "relation=" > method that performs this "diff"-strategy. I volunteer to create such a > patch if nobody else is already working on this.So, a combination between what you have above and what Doug is doing? Sounds good to me. That would really help this particular problem.
Joe Van Dyk <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> writes:>> class ActiveRecord::Base >> def self.define_habtm_setter(list) >> list.each do |a| >> module_eval <<-CODE >> def #{a}_ids=(list) >> #{a}.clear >> #{a} << #{a.classify}.find(list) >> end >> validates_associated :#{a} >> CODE >> end >> end >> end > > I assume #classify returns the class for a? Is that a Ruby or Rails > function?classify is one of the Inflection (sorry if that''s not the right name, I can''t look up it''s class right now) methods. It returns the class name that Rails assumes given a collection name.> The list var in the CODE section isn''t related to the list being > passed to #define_habtm_setter, right? That was a little confusing.Sorry, I shouldn''t have used the argument twice. It would be more correct to name the parameter for the #{a}_ids= method id_list and then call #{a.classify}.find(id_list)> How does validates_associated help? I read the documentation for the > method, but it''s still not clear to me on what it does.... the docs > say the method "validates whether the associated object or objects are > all themselves valid.", but that doesn''t make much sense to me.For example, let''s take class Foo has_and_belongs_to_many :bars. My assumption is that when the validate function is called for a Foo object, it doesn''t automatically call Bar#validate for each of the associated :bars. Specifying validate_associated seems to be the hook in Foo#validate to call Bar#validate for each of the associated Foo#bars. -- doug-jGAhs73c5XxeoWH0uzbU5w@public.gmane.org
On 5/23/05, Robert Mannl <ro-x0ewNDjvrP1m7dl9lghbdg@public.gmane.org> wrote:> Hello! > > This works well, thanks for the approach. > > But does anyone know how to, for example, search for (using this > example) Houses that have certain amenities? > > Would love to hear your ideas, > > Thanks, > RobMaybe (off the top of my head) (and this is really slow and bad and I''m not sure if it''ll work): (in House model) def self.search_by_amenities(amenities) houses = [] for house in House.find(:all) for amenity in amenities if house.amenities.include? amenity houses << house break end end end houses end end> > > > >Final solution that seems to work fairly well. This comes from my > >actual code, where Houses have many Amenities (an Amenity is like a > >"Hardwood Floor" or "Fireplace"). There''s also an example of how I''m > >doing a one-to-many relationship with a select box (Community has_many > >Houses, House belongs_to Community). > > > >====== MODEL =======> >class House < ActiveRecord::Base > > has_and_belongs_to_many :amenities > > belongs_to :community > > > > def amenities_ids=(list) > > amenities.clear > > amenities << Amenity.find(list) > > end > > > > def amenity_exists?(id) > > amenities.to_a.find {|a| a.id == id} > > end > >end > >. > >. > >. > >. > > > > > > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >
Hello! This works well, thanks for the approach. But does anyone know how to, for example, search for (using this example) Houses that have certain amenities? Would love to hear your ideas, Thanks, Rob> >Final solution that seems to work fairly well. This comes from my >actual code, where Houses have many Amenities (an Amenity is like a >"Hardwood Floor" or "Fireplace"). There''s also an example of how I''m >doing a one-to-many relationship with a select box (Community has_many >Houses, House belongs_to Community). > >====== MODEL =======>class House < ActiveRecord::Base > has_and_belongs_to_many :amenities > belongs_to :community > > def amenities_ids=(list) > amenities.clear > amenities << Amenity.find(list) > end > > def amenity_exists?(id) > amenities.to_a.find {|a| a.id == id} > end >end >. >. >. >. > >
Hello Joe, yes something like that would work, thanks alot :) If I find any better way to do it (as this solution is pretty heavy on the DB =D), I''ll post it here :) Cheers, Rob>I''m not sure if it''ll work): > >(in House model) > >def self.search_by_amenities(amenities) > houses = [] > for house in House.find(:all) > for amenity in amenities > if house.amenities.include? amenity > houses << house > break > end > end > end > houses >end > >end > > > > Maybe (off the top of my head) (and this is really slow and bad and > >>>Final solution that seems to work fairly well. This comes from my >>>actual code, where Houses have many Amenities (an Amenity is like a >>>"Hardwood Floor" or "Fireplace"). There''s also an example of how I''m >>>doing a one-to-many relationship with a select box (Community has_many >>>Houses, House belongs_to Community). >>> >>>====== MODEL =======>>>class House < ActiveRecord::Base >>> has_and_belongs_to_many :amenities >>> belongs_to :community >>> >>> def amenities_ids=(list) >>> amenities.clear >>> amenities << Amenity.find(list) >>> end >>> >>> def amenity_exists?(id) >>> amenities.to_a.find {|a| a.id == id} >>> end >>>end >>>. >>>. >>>. >>>. >>> >>> >>> >>> >>_______________________________________________ >>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 > >. > > >
On 19-mei-2005, at 17:10, Doug Alcorn wrote:> Julian ''Julik'' Tarkhanov <listbox-RY+snkucC20@public.gmane.org> writes: > > >> def types=(ntypes) >> self.types.clear >> ntypes.collect! { |t| t = Type.find(t) unless t.is_a?(Type); >> t} #grab related objects by ID >> self.types.concat(ntypes) >> end >> > > You lost me here. Can you explain a bit further?Sorry, I am basically drowning in my exams so didn''t have time to answer. Yes. Basically AR generates a getter for your collection in AR, which looks like a Ruby method: def types self.grab_complicated_relationship_from_db_or_whatever end but you don''t think about it - you just know that there''s a method in your AR object that gets you an association collection (which works sorta like an Array). It also generates "setters" for every attribute of your model. A Ruby setter looks like a method with "=" in the end: class Foo def bar=(newbar) @bar = newbar end Foo.bar = "string" #this calls a setter However, AR doesn''t (and I cant understand why) create setters for associations. When you create one (like the one I made above) all built-in methods of ActiveRecord will use this setter, so when you call update_attributes(hash_of_new_attribs) it will call your setter. Then I use standard Ruby methods for collections to wipe the collection (it supports "clear" - see AR docs), then I "collect" the IDs replacing them by real AR objects, and then I use "concat" (also supported by collections) to push the new records into the collection by calling standard AR functionality. After that I can do: Work.find(2).types = Type.find_by_client_id(3) #quickie etc. I don''t understand why this is not built into AR. Then I only have to recieve an array of IDs from my form and send it directly to the model for updating. The code that "collects" IDs into objects (if they are not objects already) is exactly for that - otherwise you get an AssociationTypeMismatch error. But Joe is right - you should basically use the "diff" approach, which will make your setter, uhm, 1 line longer :-) -- Julian "Julik" Tarkhanov
Robert Mannl
2005-May-26 19:39 UTC
Re: Re: habtm + checkboxes - searching in a many-to-many relationship
Just wanted to post this here like I promised. It *is* possible to search the db in a many-to-many relationship with one query (e.g. to find houses that meet certain criteria, i.e. houses that have certain amenities). See this thread at sitepoint: http://www.sitepoint.com/forums/showthread.php?t=255492&highlight=sim Cheers, Rob