Update only the join table in a HABTM - how?


#1

I have a photographers app that requires giving access to galleries for
users. in one part, I want to be able to define gallery access for a
user seperate from updating any of the user info. I have this function:

def edit_access
@user = User.find(params[:id])
@user.galleries = Gallery.find(params[:gallery_ids]) if
params[:gallery_ids]
if @user.update_attributes(params[:user])
flash[:notice] = ‘Account was successfully updated.’
redirect_to :action => ‘list’
else
@galleries = Gallery.find(:all)
render :action => ‘manage_access’
end
end

The function works and updates the correct fields in the galleries_users
table, but the page errors out on validation errors on @user. I’m
hoping to make a function that only reaches to update @user.galleries
and nothing in the user table.

How do I do it? I’ve tried update_attributes(params[:user][:galleries])
and a few similar things, but really I’m just hacking and would like to
know the proper way.

Thanks,

Jason


#2

Jason P. wrote:

The function works and updates the correct fields in the galleries_users
table, but the page errors out on validation errors on @user. I’m
hoping to make a function that only reaches to update @user.galleries
and nothing in the user table.

How do I do it? I’ve tried update_attributes(params[:user][:galleries])
and a few similar things, but really I’m just hacking and would like to
know the proper way.

It’s hard to updated attributes in a join table. Try using has_many
:through
instead.

http://blog.hasmanythrough.com/articles/2006/04/20/many-to-many-dance-off


Josh S.
http://blog.hasmanythrough.com


#3

Hi Jason,

your user model will have a method called gallery_ids= that has been
created by the has_and_belongs_to_many macro.

So you could say @user.gallery_ids = [1,2,3,4,5]

For models where new_record? is false (i.e. existing records) the
change to the database is immediate and doesn’t require you to call
save().

Seeing as you’re working on something related to security, and
because you are using mass-assignment from params (i.e.
update_attributes(params[:blah]) or new(params[:blah]) or attributes
= params[:blah]) I think you should probably take a look at a two-
part article on my blog:

http://tinyurl.com/prxdl

It applies to you if you have an action anywhere that allows a user
to alter their associated User object’s attributes (like their name
or address etc) and you use mass-assignment from params to do that
(i.e. update_attributes(:user)). If that’s the case then you MUST do:

attr_protected :gallery_ids

in your User model.

This doesn’t stop you from saying @user.gallery_ids = [1,2,3,4,5] or
@user.gallery_ids = params[:gallery_ids] in your edit_access() action
(which I assume is only available to privileged users) but it will
ensure any spurious :gallery_ids param will be discarded if passed in
to a new(), attributes= or update_attributes() call in some other
unrelated action.

HTH,
Trevor

Trevor S.
http://somethinglearned.com


#4

On 8-Jun-06, at 10:56 AM, Jason P. wrote:

end

end

Is there not a way in the code above, where I can just say something
like:

if @user.galleries.update_attributes
or if @user.update_attributes(params[:gallery_ids])

Jason,

take another look at my previous email on this subject.

@user.gallery_ids = params[:gallery_ids]

For existing records, there is no update_attributes() or save()
required, it does it immediately. Try it out in the console if
you’re skeptical.

If you really want to use an update_attributes style call then:

@user.update_attribute(:gallery_ids, params[:gallery_ids])

will work because update_attribute (not plural) will do the
database update without first running validations. But to be honest,
the heavy lifting here is being done by the underlying call to
gallery_ids= that’s issued by update_attribute().

And also in reference to my previous mail, if you haven’t added the
following line to your User model:

attr_protected :gallery_ids

then you had better really know why not. See http://tinyurl.com/
prxdl for more information.

HTH,
Trevor


Trevor S.
http://somethinglearned.com


#5

def edit_access
@user = User.find(params[:id])
@user.galleries = Gallery.find(params[:gallery_ids]) if
params[:gallery_ids]
if @user.update_attributes(params[:user])
flash[:notice] = ‘Account was successfully updated.’
redirect_to :action => ‘list’
else
@galleries = Gallery.find(:all)
render :action => ‘manage_access’
end
end

Is there not a way in the code above, where I can just say something
like:

if @user.galleries.update_attributes
or if @user.update_attributes(params[:gallery_ids])

or something that just bypasses trying to update the User model? It
really only needs to update the join table ‘galleries_users’ and nothing
else.

The initial code actually works for assigning access which is great - I
watch it update the tables, but the page gives validation errors for the
(non-existent in the view) password field being blank. Even if I could
just bypass that validation error everything would work fine.

Jason


#6

For existing records, there is no update_attributes() or save()
required, it does it immediately. Try it out in the console if
you’re skeptical.

Awesome thanks! I guess I didn’t understand that things are being
updated in the database without a call to save or an update method.
That’s so easy. I’ve put the attr_protected in there as well.

Cheers!

Jason