Supertracer
Supertracer

Reputation: 472

Deleting Record does not destroy association

I have a Task model and User model, I have a join table b/w Task & User named Tag. Task.rb

class Task < ActiveRecord::Base
  belongs_to :user
  has_many :comments, dependent: :destroy
  has_many :tags, dependent: :destroy
  validates :description, presence: true

  def tagged_user_ids
    tags.map{|tag| tag.user_id}
  end

  def tag_exists_for(user)
    tagged_user_ids.include?user.id.to_s
  end

  def tag(user)
    tags.create(user_id: user.id)
  end

  def untag(user)
    tags.find_by(user_id: user.id).destroy
  end
end

User.rb

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  has_many :tasks
  has_many :tags
  has_many :comments,through: :tasks

  def tagged_tasks
    tags.map{|tag| tag.task}
  end

  def all_tasks
    self.tasks + self.tagged_tasks
  end

end

Tag.rb

class Tag < ActiveRecord::Base

  belongs_to :user
  belongs_to :task

end

I wrote the following test case for the untag method

describe "untag" do
    it "should untag user from task" do
      create_task_and_tag_user
      p @task2.tags
      @task2.untag(@user)
      p @task2.tags
      expect(@task2.tag_exists_for(@user)).to be false 
    end  
  end

def create_task_and_tag_user
    @user = User.create(email:"[email protected]",password: 123456,name: "user1")
    @task1 = @user.tasks.create(description: "some description")
    @user2 = User.create(email:"[email protected]",password: 123456,name: "user2")
    @task2 = @user2.tasks.create(description: "some other task")
    @task2.tag(@user)
  end

but this test fails

Task untag should untag user from task
     Failure/Error: expect(@task2.tag_exists_for(@user)).to be false

       expected false
            got true

on further investigation, i found out that untag(tag) deletes tag record,but when i call task.tags it shows in the collectionproxy

Tag.find_by(id: 39)
  Tag Load (0.3ms)  SELECT  `tags`.* FROM `tags` WHERE `tags`.`id` = 71 LIMIT 1
 => nil
2.4.1 :042 > t1.tags
 => #<ActiveRecord::Associations::CollectionProxy [#<Tag id: 39, user_id: "3", task_id: "37", created_at: "2018-01-18 11:13:23", updated_at: "2018-01-18 11:13:23">, #<Tag id: 43, user_id: "9", task_id: "37", created_at: "2018-01-22 04:56:13", updated_at: "2018-01-22 04:56:13">, #<Tag id: 44, user_id: "5", task_id: "37", created_at: "2018-01-22 05:31:16", updated_at: "2018-01-22 05:31:16">]>
2.4.1 :043 >

I come from a Mongodb,nosql background so never experienced this kind of behaviour before. Is there a way i can deleted associations also(without using any gem, some suggested to use paranoia gem and replace destroy by really_destroy!)

Upvotes: 2

Views: 600

Answers (1)

Kkulikovskis
Kkulikovskis

Reputation: 2088

This happened because Tag objects that you destroyed are still cached in ruby. That is why reload solved the issue, because it re-fetches tags from the database.

To have a little performance boost and not have to call reload in tests, I would suggest rewriting the method tagged_user_ids this way:

def tagged_user_ids
  tags.pluck(:user_id)
end

First of all, this will call an SQL query to only fetch user_id column instead of all columns. Secondly each call to this method will execute a new SQL query, in which case if you delete some tags, this method will reflect the result without calling reload on task object.

Another unrelated improvement would be rewriting tagged_tasks into:

has_many :tagged_tasks, through: :tags

Upvotes: 2

Related Questions