简体   繁体   中英

Rails check if HABTM association is changed

I have a roles table

select * from roles;

 id |   name
----+----------
  1 | admin
  2 | user
  3 | author
  4 | guest
  5 | manager

and another table user_roles

select * from user_roles;

 role_id | user_id
---------+---------
       3 |       1
       3 |       2
       3 |       3
       4 |       5
       3 |       6
       5 |       7
       5 |       8
       1 |       9
       1 |      11



#role.rb

class Role < ActiveRecord::Base
  has_and_belongs_to_many :users, join_table: 'user_roles', class_name: user_class.to_s
end

I am trying to do some actions when a user's role is updated say, from guest to author

#user.rb

class Use < ActiveRecord::Base
  after_update :print_role_updated if: :user_roles_changed?
  .
  .

  private

  def user_roles_changed?
    user_roles.any? { |role| role.changed? }
  end

  def print_role_updated
    puts "User role changed from #{old_role} to #{new_role}"
  end
end

But it is not working as expected ( .changed? is checking whether the value in role table is updated ?).

How do I run print_role_updated method whenever user roles get updated to a diffrent one?

edit

I tried Doctor's answer but role_updated? is returning false even though the record is being updated

class Use < ActiveRecord::Base
  has_many :user_roles
  after_update :print_role_updated if: role_updated?
  .
  .


  private

  def role_updated?
    user_roles.any? {|a| a.changed?} 
  end

  def print_role_updated
    puts "User role changed from #{old_role} to #{new_role}"
  end
end

try below code in model:

user.rb

class User < ActiveRecord::Base
  after_update :print_role_updated if: :user_roles_changed?
  .
  .

  private

  def print_role_updated
    puts "User role changed"
  end

  def user_roles_changed?
    u = User.find(self.id)
    u.role != self.role # it check existing role and role saved in db 
  end
end

I think the _changed? does only work for attributes not classes. And since you have a many to many association you can't check on one object. I would try something in this direction:

if user_roles.any? {|a| a.changed?} 

if a user only has a single role (sorry it is not clear from your code) then the following should work, or doesn't it ?

if user_roles.changed?

Update since you specified that a user can have many roles. There should be a few corrections you should consider:

class Use < ActiveRecord::Base
  has_many :user_roles
  after_update :print_role_updated if: user_roles.any? {|a| a.changed?} 
  .
  .


  private

  def print_role_updated
    puts "User role changed from #{old_role} to #{new_role}"
  end
end

Update 2: As it turns out, changed? is false on newly created objects, so my previous update is not working.

Try to change the changed? to the following:

user_roles.any? { |a| a.new_record? || a.marked_for_destruction? || a.changed? }

To be honest it is a bit a guessing I do here, but can you please try it? I would love to see if that works

I found a solution. the problem is that the HABTM relationship is updated in the db as soon as the new values are set

here is my workaround for your example to implement an HABTM changed method. may not work for your exact case but knowing this you'll get the idea

class User < ActiveRecord::Base

  ...
  def user_role_ids=(ids)
    @user_roles_changed = ids != user_role_ids
    super(ids)
  end

  def user_roles_changed?
    !!@user_roles_changed
  end
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM