0

I can't seem to get bidirectionality working on my Rails models. For example, in the rails console, when I execute the following:

r = Role.new
u = User.new
r.users << u
r.users
u.roles

The rails console states that role has users, but the user has no roles. If I save the role, then pull the user from the db, it has a role like it should, but the role's state doesn't stay consistent between the two objects.

I only have one db migration. Here is the code related to the two models (excluding simple data fields)

create_table :users do |t|
end

create_table :roles do |t|
end
    
create_join_table :users, :roles

Database itself also looks normal. Here are the two models

class User < ApplicationRecord
  has_and_belongs_to_many :roles
  # unrelated
  has_many :committee_enrollments
  has_many :committees, through: :committee_enrollments
end

class Role < ApplicationRecord
  has_and_belongs_to_many :users
  # unrelated
  has_and_belongs_to_many :committees
end

All my associations are doing this. I can only get relations being set automatically when I specify inverse_of:. However, specifying source: doesn't work for through: relations.

ruby 2.7.1p83, Rails 6.1.3, postgresql 12.5, Ubuntu WSL

Edit: ok so I understand the relationship won't exist until I save. However, I am still having an issue with data inconsistency. For example:

r = Role.first
r2 = r.users.first.roles.first
r.role_name == r2.role_name
 => true
r.role_name = "changed"
r.role_name == r2.role_name
 => false

according to the documentation, this second one should be true.

TheJesBoy
  • 1
  • 1
  • Did you save the role and user? `r.save u.save`. – eux Feb 23 '21 at 08:41
  • *"If I save the role, then pull the user from the db, it has a role like it should"* --- Isn't this the only thing that matters? If you retrieve/reload a record, then it will refresh the state of its newly altered associations. – Tom Lord Feb 23 '21 at 12:08
  • I thought that when I make two objects and set one association to another, it should also set the reverse automatically? It works that way for belongs_to relationships. – TheJesBoy Feb 23 '21 at 15:55
  • Ok so it doesn't set the reverse automatically in this case I guess. See my edit – TheJesBoy Feb 23 '21 at 16:54

1 Answers1

0

The association is not being saved and not even validated, you are just working with temporal data on your memory. If you save your records, you will be able to see their associations bidirectionaly.

u = User.create(YOUR_USER_MODEL_PARAMS)
r = Role.create(YOUR_ROLE_MODEL_PARAMS)
u.roles << r  # This will do the same than r.users << u

Also, with your database schema, associating records on that way allows duplicated associations (ex: one User with role 'admin' twice, you will watch this in action if you push twice the same association). In the long run, you're going to have a lot of duplicated records, you have two typical ways to avoid it:

Option 1: assign the associations using one of this ways

u.update(roles: [r]) # Array of roles
u.update(roles: Role.all)
u.roles = [r] # Array of roles
u.roles = Role.all # ActiveRecord::Relation

Option 2: prevent duplicated records on your Users-Roles join table by adding a unique index like Jeremy Lynch explains in this answer

rails g migration add_index_to_users_roles
# migration file
add_index :users_roles, [:user_id, :role_id], :unique => true
add_index :users_roles, :user_id

Once you have the unique index, attempting to add a duplicate record will raise an ActiveRecord::RecordNotUnique error. Handling this is out of the scope of this question. View this SO question.

rescue_from ActiveRecord::RecordNotUnique, :with => :some_method
anonymus_rex
  • 470
  • 4
  • 18
  • Ok so in your example, I can't do r.users and have correct information until after I save u. Got it. Still having an issue with some inconsistent states, see my edit. Also, thanks for the note about unique records. Great idea. – TheJesBoy Feb 23 '21 at 16:50
  • The problem is caused by the same reason, you are changing the value of a variable without persisting it in the database. The documentation you describe is for one-to-one and one-to-many relations, but does not mention that it works with many-to-many (`has_and_belongs_to`) relations hehe. With many to many relations things are pretty different because you have an intermediary table, not just one foreign key – anonymus_rex Feb 23 '21 at 23:24