ruby-on-railsrails-activerecordrails-models

Model.find() calling a different model


I have 3 models user, micropost, like

I followed Ruby on Rails Tutorial by Michael Hartl https://rails-4-0.railstutorial.org/book

Now I am adding new features and was trying to add a like button and after some changes now my micropost delete button doesn't work.

#micropost_controller   
def destroy
        Micropost.find(params[:id]).destroy
        redirect_to root_url
      end

#_micropost.html.erb
<%= link_to "delete", micropost, method: :delete,
                                   data: { confirm: "You sure?" },
                                   title: micropost.content %>

Even though i am calling Micropost.find Active Record searches in likes table and gives the error as

ActiveRecord::StatementInvalid in MicropostsController#destroy
SQLite3::SQLException: no such column: likes.micropost_id: SELECT "likes".* FROM "likes" WHERE "likes"."micropost_id" = ?

Similar thing happens when i try to delete a user. So basically whichever destroy i am trying to call it is redirected to like model

PS: the problem started after i executed

ActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;")

as i was getting BusyException: database is locked: commit transaction

models

#like.rb
class Like < ApplicationRecord
    belongs_to :microposts, optional: true
    belongs_to :users, optional: true
    validates :src_user_id, presence: true
    validates :des_user_id, presence: true
    validates :post_id, presence: true
end


#micropost.rb
class Micropost < ActiveRecord::Base
  belongs_to :user
  default_scope -> { order('created_at DESC') }
  validates :content, presence: true, length: { maximum: 140 }
  validates :user_id, presence: true
  has_many :likes, dependent: :destroy

  # Returns microposts from the users being followed by the given user.
  def self.from_users_followed_by(user)
    followed_user_ids = "SELECT followed_id FROM relationships
                         WHERE follower_id = :user_id"
    where("user_id IN (#{followed_user_ids}) OR user_id = :user_id",
          user_id: user.id)
  end

  def self.search(search)
    where("content LIKE ?", "%#{search}%")
  end

end


#user.rb
class User < ActiveRecord::Base

  has_many :microposts, dependent: :destroy

  has_many :relationships, foreign_key: "follower_id", dependent: :destroy
  has_many :followed_users, through: :relationships, source: :followed
  has_many :reverse_relationships, foreign_key: "followed_id",
                                 class_name:  "Relationship",
                                 dependent:   :destroy
  has_many :followers, through: :reverse_relationships, source: :follower

  has_many :likes, foreign_key: "src_user_id", dependent: :destroy
  has_many :liked_by_users, through: :likes, source: :likes

  scope :starts_with, -> (name) { where("name like ?", "#{Example User}%")}

  before_save { self.email = email.downcase }
  before_create :create_remember_token
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }, unless: Proc.new { |user| user.password.nil? }
  validates :password_confirmation, presence: true, unless: Proc.new { |user| user.password.nil? }
  validates :birthday, presence: true
  validate :email_verification, on: :update

end

Solution

  • class User
      has_many :microposts, dependent: :destroy
       # You never have to specify foreign key for indirect assocations
      has_many :likes, through: :microposts
    end
    
    class Micropost
      belongs_to :user
      has_many :likes, dependent: :destroy, foreign_key: "post_id"
    end
    
    
    class Like
      # You must specify the foreign key if it cannot be derived from the name of the association
      # `belongs_to :micropost` will use micropost_id
      belongs_to :micropost, optional: true, foreign_key: "post_id"
      belongs_to :user # you have to add a `likes.user_id` column
    end
    

    Although I would really question if you want to use the same table/model for user likes and micropost likes. It really just makes things a lot more complicated and you end up with a table with a large number of null values. If this is part of the book I really wonder what the author was thinking as its a really bad design.

    You can just set it up as:

    # app/models/users/like.rb
    # table name is users_likes
    module Users
      class Like
        belongs_to :src_user, class_name: 'User', optional: false
        belongs_to :des_user, class_name: 'User', optional: false
      end
    end
    
    class User
       has_many :likes_as_src, 
         class_name: 'Users::Like',
         foreign_key: :src_user
       has_many :likes_as_des, 
         class_name: 'Users::Like',
         foreign_key: :src_user
    end
    

    # app/models/users/like.rb
    # table name is microposts_likes
    module Microposts
      class Like
        belongs_to :user, optional: false
        belongs_to :micropost, optional: false, foreign_key: 'post_id'
      end
    end
    
    class User
      has_many :micropost_likes, class_name: 'Microposts::Like'
      has_many :liked_microposts, through: :micropost_likes, source: :micropost
    end
    

    That gives you two simple join tables with non-nullable foreign keys and good indices. It also makes the validations very straight forward.