Complex Associations

© 2012 - 2017 thoughtbot, inc. Upcase, the design of a robot, and thoughtbot are registered trademarks of thoughtbot, inc.


This is a companion discussion topic for the original entry at https://thoughtbot.com/upcase/videos/intermediate-rails-6

In the video about single table inheritance, at 7:28, he misses the migration for creating following relationships. This is what the migration file looks like. Once you migrate that, you should be good to continue.

class CreateFollowingRelationships < ActiveRecord::Migration[5.1]
def change
create_table :following_relationships do |t|
t.belongs_to :follower
t.belongs_to :followed_user
t.timestamps
end
end
end

Sorry about the intendation. Not sure how to fix that in the comments

1 Like

In the user model, why are we creating a
has_many :followers, through: :follower_relationships
as well as
has_many :follower_relationships, foreign_key: :followed_user_id, class_name: "FollowingRelationship", dependent: :destroy ?

Wouldn’t it make more sense to just do
has_many :followers, foreign_key: :followed_user_id, class_name: "FollowingRelationship", dependent: :destroy ?

Same applies to followed_users and followed_user_relationships.

My assumption is that we want to give the user a helper of user.followers and user.follower_relationships. But is that it? Isn’t there a better way?

I could be a little late to the party, but I want to know why the author is using: @_user in FollowedUsersController instead of plain @user that I have been using so far.

What about the other constraints?

I wrote it like this:

create_table :following_relationships do |t|
  t.integer :follower_id,          foreign_key: true, null: false
  t.integer :followed_user_id,     foreign_key: true, null: false
end

add_index :users, [:follower_id, :followed_user_id], unique: true, name: 'some_name'

Do pass the option in add_index for name. Else, the index name will be too long for PostgreSQL.

If you do it like:

has_many :followers, foreign_key: :followed_user_id, class_name: "FollowingRelationship", dependent: :destroy

Well, if we do it like this:

user = User.first
user.followers

user.followers will give us instances of FollowingRelaionship class, and the table associated with class has the following two columns, among other columns:

follower_id | followed_user_id

So if you are asked to print all the usernames of all the users a particular user is following:

user = User.first
User.find(user.followed_user_relationships.map(&:followed_id))

Surely, you wouldn’t like doing: User.find(user.foll...

I use the code below:

rails generate Model FollowingRelationship follower_id:integer followed_user_id:integer

class CreateFollowingRelationships < ActiveRecord::Migration[5.1]
  def change
    create_table :following_relationships do |t|
      t.integer :follower_id, foreign_key: true, null: false
      t.integer :followed_user_id, foreign_key: true, null: false

      t.timestamps
    end

    add_index :following_relationships, :follower_id
    add_index :following_relationships, :followed_user_id
    add_index :following_relationships, %i[follower_id followed_user_id], name: :relationship_index
  end
end

why Matthew does not make a official reply?

I came up with this solution that ensures the foreign_keys align with the users table. If you skip ahead to 11:55 in the video you can see the migration that is being added to source control.

class CreateFollowingRelationships < ActiveRecord::Migration[5.0]
  def change
    create_table :following_relationships do |t|
      t.integer :follower_id, null: false
      t.integer :followed_user_id, null: false

      t.timestamps
    end

    add_foreign_key :following_relationships, :users, column: :follower_id
    add_foreign_key :following_relationships, :users, column: :followed_user_id

    add_index :following_relationships, [:follower_id, :followed_user_id],
              name: :index_follower_following_relationship,
              unique: true
  end
end

The add_index at the end is not required but an extra validation the database can perform.

Thanks @joshmosh for the pointer to the git diff at 11:55! (And sorry to revive this old thread again)

I wanted to use references to keep it consistent with the previous migrations.

Eventually I got it to work and generate the same schema as @halogenandtoast (less some minor Rails 5.1 differences such as bigint instead of int for reference columns) using the following migration:

class CreateFollowingRelationships < ActiveRecord::Migration[5.1]
  def change
    create_table :following_relationships do |t|
      t.references :follower, index: true, foreign_key: { to_table: :users }, null: false
      t.references :followed_user, index: true, foreign_key: { to_table: :users }, null: false
      t.timestamps
    end
  end
end

Hey! I believe he is using @_user during assignment as a way to communicate to future collaborates/coders/etc that the method of assignment used in that line isn’t designed to be used for other things. In other words, the lines that all make conditional assignments with @_user are specific to that intent… it shouldn’t be used as a generalized way to make assignments. One reason for this, for example, is if the assignments are made with conditions that wouldn’t give the same results if used elsewhere. I might be wrong on this, though.