Rails 4 Error: ActiveRecord::HasManyThroughSourceAssociationNotFoundError - ruby-on-rails

Hey,
this is frustrating. I know what's wrong here, but I have no solution to fix it.
First the error, prompting when clicking on 'Favorite' (/app/views/users/index.html.haml)
ActiveRecord::HasManyThroughSourceAssociationNotFoundError in UsersController#userfavorite
Could not find the source association(s) "userfavorite" or :userfavorites in model FavoriteUser. Try 'has_many :userfavorites, :through => :favorite_users, :source => <name>'. Is it one of c_user_id or user_id?
Request: Parameters:
{"_method"=>"get",
"authenticity_token"=>"VkZF4RtOBSXLFw8mygor24Ty/Efx5uSWxto4qRWf1szu3YwFe1/F5+7QtEXXZv9eaQEQvM5O8ELX95wmPLdZYQ==",
"type"=>"favorite", # What i am doing
"id"=>"1"} # My user id
My goal is to allow Users (from the user model) to favorite other users (user model) through the favoriteuser model. The conflict here: I am not able to do the associations right cause I have only one model the data is coming from.
Let me show you the code real quick! (Conflicts user.rb)
app/models/favorite_user.rb
class FavoriteUser < ActiveRecord::Base
belongs_to :c_user_id
belongs_to :user_id
end
app/models/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 :tools
# Favorite tools of user
has_many :favorite_tools # just the 'relationships'
has_many :favorites, through: :favorite_tools, source: :tool # the actual tools the user favorites
# Favorite users of user
has_many :favorite_users # just the 'relationships'
has_many :userfavorites, through: :favorite_users, source: :user # conflicting! Can't link to userscontroller cause it belongs to the user model, the model we are at right now.
has_many :userfavorited_by, through: :favourite_users, source: :user # conflicting! Can't link to userscontroller cause it belongs to the user model, the model we are at right now.
mount_uploader :avatar_filename, AvatarUploader
end
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :find_user, only: [:show, :favorite]
# Add and remove favorite recipes
# for current_user
def userfavorite
type = params[:type]
if type == "favorite"
current_user.userfavorites << #user
redirect_to :back
elsif type == "unfavorite"
current_user.userfavorites.delete(#user)
redirect_to :back
else
# Type missing, nothing happens
redirect_to :back, notice: 'Nothing happened.'
end
end
def index
#users = User.all
end
def show
#tools = Tool.where(user_id: #user).order("created_at DESC")
#tool = Tool.find(1)
end
private
def find_user
#user = User.find(params[:id])
end
end
app/views/users/index.html.haml
- #users.each do |user|
= image_tag gravatar_for user if user.use_gravatar == true
= image_tag user.avatar_filename.url if user.use_gravatar == false
%h2= link_to user.username, user
%p= link_to "Favorite", favorite_user_path(user, type: "favorite"), method: :get
%p= link_to "Unfavorite", favorite_user_path(user, type: "unfavorite"), method: :get
app/config/routes.rb
resources :users, only: [:index, :show, :userfavorite] do
get :userfavorite, on: :member
end
Attributes of :favorite_users
c_user_id:integer user_id:integer
(=> c_user_id for currents user id - so the user who is adding favorite users)
I hope the provided data is enough, if not please tell me. I'm grateful for all Your replies.

Here is my suggestion:
user.rb
userfavorites - will list all user you have favorited
has_many :favorite_relationships, class_name: "FavoriteUser", foreign_key: "c_user_id"
has_many :userfavorites, through: :favorite_relationships, source: :user
userfavorited_by - will list all user who favorited you
has_many :favorited_relationships, class_name: "FavoriteUser", foreign_key: "user_id"
has_many :userfavorited_by, through: :favorited_relationships, source: :c_user
FavoriteUser
edited:
class FavoriteUser < ActiveRecord::Base
belongs_to :c_user, class_name: "User"
belongs_to :user, class_name: "User"
end
After you make changes, make sure to test first in your console that the relationships works. then make appropriate change in your view, controller etc...

Related

Devise_invitable, how to invite users to one hotel and not all?

In the context where a user.admin has_many hotels, is there a way to invite a user to 1 hotel only? (e.g. many-to-many relationship between user and hotel, with join UserHotel table).
More concretely, the first problem I am encountering is that I am not able to properly insert the hotel_id param in the users/invitations_controller.
Error message:Couldn't find Hotel without an ID. params sent: {"format"=>"109"}
Please find my current code below=>
views/hotels/show
<%= link_to "invite new user", new_user_invitation_path(#hotel) %>
routes
Rails.application.routes.draw do
devise_for :users, controllers: {
invitations: 'users/invitations'
}
resources :hotels do
resources :users
end
end
models
class User < ApplicationRecord
has_many :user_hotels, dependent: :destroy
has_many :hotels, through: :user_hotels
enum role: [:owner, :admin, :employee]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :admin
end
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :invitable
end
class UserHotel < ApplicationRecord
belongs_to :hotel
belongs_to :user
end
class Hotel < ApplicationRecord
has_many :user_hotels, dependent: :destroy
has_many :users, through: :user_hotels
accepts_nested_attributes_for :users, allow_destroy: true, reject_if: ->(attrs) { attrs['email'].blank? || attrs['role'].blank?}
end
controllers/users/invitations
class Users::InvitationsController < Devise::InvitationsController
def new
#hotel = Hotel.find(params[:hotel_id])
#user = User.new
How to build the join table UserHotel when inviting?
end
end
add this code to app/models/user.rb:
accepts_nested_attributes_for :user_hotels
and then:
User.new(user_hotels_attributes: [{ hotel: #hotel }])
you can add your own validations to prevent duplicate entry.

voting system for rails blog with devise user login before voting

So I have a blog I am trying to have a simple upvote/downvote feature for the posts. I have devise set up and I made all the associations between the models, votings, users, and home_blogs.
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :liked_home_blogs, through: :votings
end
class HomeBlog < ApplicationRecord
mount_uploader :image, ImageUploader
has_many :hashtaggings
has_many :hashtags, through: :hashtaggings
has_many :votings
has_many :votants, through: :votings
def all_hashes=(names)
self.hashtags = names.split(",").map do |name|
Hashtag.where(name: name.strip).first_or_create!
end
end
def all_hashes
self.hashtags.map(&:name).join(", ")
end
end
class Voting < ApplicationRecord
belongs_to :home_blog
belongs_to :user
end
and the controller looks like this at the moment:
class VotingsController < ApplicationController
before_action :authenticate_user!
def upvote
#votings = HomeBlog.find(params[:home_blog_id])
#votings.votings.build( :upvote => true, :downvote => false,
:user_id => current_user.id)
#votings.save!
redirect_to request.referrer, notice: "Thanks for the
vote!"
end
def downvote
#voting = HomeBlog.find(params[:home_blog_id])
#voting.votings.build( :downvote => true, :upvote =>
false, :user_id => current_user.id)
#voting.save!
redirect_to request.referrer, notice: "Thanks for the
vote!"
end
private
def voting_params
params.require(:voting).permit(:upvote, :downvote,
:home_blog_id, :user_id)
end
end
Sorry about the crappy copy and paste for the controller. My question is, how do I make a condition for the current_user in devise to limit them to one vote per home_blog post? Thanks!
I think you would add multicolumn unique index to the join table. Something like...
add_index :voting, [:user_id, :home_blog_id], unique: true
If im understanding your question correctly you would like there to be only one votings record for a home_blog per current_user ( user_id )

How to limit act_as_votable gem for only one model in rails

I have a rails app where I have two tables Posts & Reviews, in Posts I have added a like system made from scratch, but in Reviews I would like to use the acts_as_votable gem. I have added the gem and everything works fine, but because the User model for both Posts & Reviews is the same, the like system on Posts has stopped working, it is giving the following error:
NoMethodError (undefined method `vote_by' for nil:NilClass):
app/models/user.rb:64:in `add_like_to'
app/controllers/api/likes_controller.rb:11:in `create'
I think this error is happening because I have declared User as voter but in Posts that was not required.
Can you tell me if there is any way to declare acts_as_voter in user.rb only for one model, in my case Review?
user.rb
class User < ActiveRecord::Base
acts_as_voter
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable, :omniauth_providers => [:facebook, :twitter, :google_oauth2]
validates :username, presence: true
validate :avatar_image_size
has_many :posts, dependent: :destroy
has_many :quotes, dependent: :destroy
has_many :responses, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :liked_posts, through: :likes, source: :likeable, source_type: "Post"
has_many :liked_responses, through: :likes, source: :likeable, source_type: "Response"
has_many :bookmarks, dependent: :destroy
has_many :bookmarked_posts, through: :bookmarks, source: :bookmarkable, source_type: "Post"
has_many :bookmarked_responses, through: :bookmarks, source: :bookmarkable, source_type: "Response"
has_many :notifications, dependent: :destroy, foreign_key: :recipient_id
after_destroy :clear_notifications
after_commit :send_welcome_email, on: [:create]
mount_uploader :avatar, AvatarUploader
include UserFollowing
include TagFollowing
include SearchableUser
include OmniauthableUser
extend FriendlyId
friendly_id :username, use: [ :slugged, :finders ]
def add_like_to(likeable_obj)
likes.where(likeable: likeable_obj).first_or_create
end
def remove_like_from(likeable_obj)
likes.where(likeable: likeable_obj).destroy_all
end
def liked?(likeable_obj)
send("liked_#{downcased_class_name(likeable_obj)}_ids").include?(likeable_obj.id)
end
def add_bookmark_to(bookmarkable_obj)
bookmarks.where(bookmarkable: bookmarkable_obj).first_or_create
end
def remove_bookmark_from(bookmarkable_obj)
bookmarks.where(bookmarkable: bookmarkable_obj).destroy_all
end
def bookmarked?(bookmarkable_obj)
send("bookmarked_#{downcased_class_name(bookmarkable_obj)}_ids").include?(bookmarkable_obj.id)
end
private
# Validates the size on an uploaded image.
def avatar_image_size
if avatar.size > 5.megabytes
errors.add(:avatar, "should be less than 5MB")
end
end
# Returns a string of the objects class name downcased.
def downcased_class_name(obj)
obj.class.to_s.downcase
end
# Clears notifications where deleted user is the actor.
def clear_notifications
Notification.where(actor_id: self.id).destroy_all
end
def send_welcome_email
WelcomeEmailJob.perform_later(self.id)
end
end
likes_controller.rb
# This controller serves as a parent controller for other likes_controllers.
# Posts::LikesController for example.
# Child controller that inherit from this LikesController should implement
# before_action :set_likeable, which sets #likeable.
class API::LikesController < ApplicationController
before_action :authenticate_user!
before_action :set_likeable
skip_before_action :verify_authenticity_token
def create
current_user.add_like_to(#likeable)
notify_author
render json: { liked: true, count: #likeable.reload.likes.size, type: #likeable.class.to_s, id: #likeable.id }, status: 200
end
def destroy
current_user.remove_like_from(#likeable)
render json: { liked: false, count: #likeable.reload.likes.size, type: #likeable.class.to_s, id: #likeable.id }, status: 200
end
private
def set_likeable
raise NotImplementedError, "This #{self.class} cannot respond to 'set_likeable'"
end
def notify_author
unless current_user?(#likeable.user)
Notification.create(recipient: #likeable.user, actor: current_user, action: "liked your", notifiable: #likeable, is_new: true)
end
end
end
There's no call to vote_by in your User model so I'm not sure how you can get the error message pasted in your question.
Anyway you should probably implement theset_likeable method in your controller so that the #likeable instance variable passed as parameter to current_user.add_like_to(#likeable) is set.
I think this should fix your current problem.

Rails User has_many posts, has_many favorite_posts

first of all sorry for English
So i already have "user - posts" one to many association, which means that each post can have just ONE author, and now i want to add "favorite posts" button to user profile, and "add to favorite" button to each post, so the question is how to implement this correct way? should i rework my user - post association?
or create some another model? I,m a bit confused. Thank in advance !
Actually i want this result :
#user.posts #return all posts created by this user
#user.favorite_posts #return posts added to favorites by this user
Here is my User model:
class User < ApplicationRecord
mount_uploader :avatar, ImageUploader
validates :username, presence: true, uniqueness: true, length: {in: 3..20}
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :ratings
enum role: [ :user, :admin ]
def calculate_average
ratings.blank? ? 0 : ratings.map(&:value).inject(:+) / ratings.count.to_f
end
end
Post model:
class Post < ApplicationRecord
mount_uploader :image, ImageUploader
validates :body, presence: true
validates :title, presence: true, length: { maximum: 50}
belongs_to :user
has_many :comments, dependent: :destroy
end
EDIT
Alright look how i've done this, it works exactly the way I wanted it.
Here is my user model:
class User < ApplicationRecord
mount_uploader :avatar, ImageUploader
validates :username, presence: true, uniqueness: true, length: {in: 3..20}
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :ratings
has_many :favorites, dependent: :destroy
has_many :favorite_posts, through: :favorites, source: "post"
enum role: [ :user, :admin ]
def calculate_average
ratings.blank? ? 0 : ratings.map(&:value).inject(:+) / ratings.count.to_f
end
end
You need many-to-many relationships for the favorite post, at first run this command to create a table favorite_posts
rails g model FavoritePost user:references post:references
Then
rails db:migrate
Then add these to your model would look like this:
#=> model/user.rb
class User < ApplicationRecord
has_many :favorite_posts, dependent: :destroy # or you can use only this line except second if you will face any problem
has_many :posts, through: :favorite_posts
end
#=> model/post.rb
class Post < ApplicationRecord
has_many :favorite_posts, dependent: :destroy
has_many :users, through: :favorite_posts
end
#=> model/favorite_post.rb
class FavoritePost < ApplicationRecord
belongs_to :user
belongs_to :post
end
That was relation part, now create a favorite post part. For the fresh code you can create a controller, i.e.
rails g controller favorites
Then your routes file:
resources :favorites
An example of the new routes using rake routes:
favorites GET /favorites(.:format) favorites#index
POST /favorites(.:format) favorites#create
new_favorite GET /favorites/new(.:format) favorites#new
edit_favorite GET /favorites/:id/edit(.:format) favorites#edit
favorite GET /favorites/:id(.:format) favorites#show
PATCH /favorites/:id(.:format) favorites#update
PUT /favorites/:id(.:format) favorites#update
DELETE /favorites/:id(.:format) favorites#destroy
In your view file add something like this:
# For creating favorite
<%= link_to "Favorite", favorites_path(user: current_user, post: post.id), class: 'btn bf-save-btn', method: :post, data: {disable_with: "Saving..."}, title: "Add to favorite" %>
# For deleting favorite list
<%= link_to "Unfavorite", favorite_path(post.id), class: 'btn af-save-btn', method: :delete, data: {disable_with: "Removing...."}, title: "Remove from favorite" %>
In favorites_controller.rb:
def index
#saves = current_user.favorite_post
end
# index.html.erb
<% #saves.each do |fav| %>
<%= link_to fav.post.post_title, post_path(fav.post) %>
<% end %>
def create
#save = FavoritePost.new(post_id: params[:post], user: current_user)
respond_to do |format|
if #save.save
flash[:success] = 'Saved'
format.html { redirect_to request.referer }
format.xml { render :xml => #save, :status => :created, :location => #save }
else
format.html { redirect_to request.referer }
format.xml { render :xml => #save.errors, :status => :unprocessable_entity }
end
end
end
def destroy
post = Post.find(params[:id])
#save = FavoritePost.where(user_id: current_user.id, post_id: post.id).first
respond_to do |format|
if #save.destroy
flash[:error] = 'Unsaved'
format.html { redirect_to request.referer, status: 303 }
format.js { redirect_to request.referer, status: 303 }
# format.xml { head :ok }
end
end
end
That's it for favorite / unfavorite functionality. Now you need to create some logic for when to show Favorite and when Unfavorite.
For this requirements has many ways, at first you need to understand this then you can whatever you want.
Also, to achieve this without reloading your page you can try some Ajax.
Update
class User < ApplicationRecord
mount_uploader :avatar, ImageUploader
validates :username, presence: true, uniqueness: true, length: {in: 3..20}
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :ratings
# Newly added
has_many :favorite_posts, dependent: :destroy # or you can use only this line except second if you will face any problem
has_many :posts, through: :favorite_posts
enum role: [ :user, :admin ]
def calculate_average
ratings.blank? ? 0 : ratings.map(&:value).inject(:+) / ratings.count.to_f
end
end
Hope it will help.
Create a new model for 'UserFavoritePost' stored post_id and user_id. And create a custom association for favorite_posts
class UserFavoritePost < ApplicationRecord
belongs_to :post
belongs_to :user
end
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :user_favorite_posts
has_many :favorite_posts, throught: :user_favorite_posts, class: 'Post'
end
fool-dev's answer does not provide a direct access to favorite posts, so the index view requires a loop. Prasanna's approach solves this, but his answer has been unfairly accused of incomplete and plagiarism :-). So here is the complete approach:
You need a many to many relationship, that´s true, so you need a join model and table. But this model is auxiliary. No important logic should be there, and I don´t think it deserves a controller or views.
class User < ApplicationRecord
has_many :posts, dependent: :destroy # Posts created by this user
has_many :favs, dependent: :destroy
has_many :fav_posts, through: :favs # Favorite posts for this user
end
class Post < ApplicationRecord
belongs_to :user
has_many :favs, dependent: :destroy
has_many :fav_users, through: :favs # Users who have this post as favorite
end
class Fav < ApplicationRecord
belongs_to :user
belongs_to :post
end
This allows to access all posts created by the user and all his favorite posts using two different methods in the user class.
#posts = current_user.posts # Posts created by this user
#fav_posts = current_user.fav_posts # Favorite posts
In the view:
<h1><% current_user.name %></h1>
<h2>Your posts</h2>
<%= render #posts %>
<h2>Your favorite posts from other users</h2>
<%= render #fav_posts %>
You don't need a controller to create, view or delete favorite posts. Just handle this logic in the User or Post controllers. For example, to favorite or unfavorite a post just add fav and unfav methods in the PostsController.
def fav
current_user.fav_posts << Post.find(params[:id])
end
def unfav
current_user.favs_posts.destroy(Post.find(params[:id]))
end
In the view:
<%= link_to "Favorite", fav_post_path(id: post.id) %>
<%= link_to "Unfavorite", unfav_post_path(id: post.id) %>
You should add these methods in your routes:
post '/posts/:id/fav', to: 'posts#fav', as: 'fav_post'
post '/posts/:id/unfav', to: 'posts#unfav', as: 'unfav_post'

Couldn't find User without an ID rails error

I am new to rails and i keep getting this error
Couldn't find User without an ID
from:
class UserController < ApplicationController
def show
#user = User.find(params[:id])
end
end
this is what i have;
model/user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
validates :name, presence: true
validates :email, presence: true
has_many :listings, dependent: :destroy
has_many :purchasing, class_name: "Transaction", foreign_key: "buyer_id", dependent: :destroy
has_many :sell, class_name: "Transaction", foreign_key: "seller_id", dependent: :destroy
has_many :purchased, class_name: "Archive", foreign_key: "buyer_id", dependent: :destroy
has_many :sales, class_name: "Archive", foreign_key: "seller_id", dependent: :destroy
has_many :selling_rooms, class_name: "Room", foreign_key: "seller_id", dependent: :destroy
has_many :buying_room, class_name: "Room", foreign_key: "buyer_id", dependent: :destroy
def can_buy?(listing_price)
if self.points >= listing_price
true
else
false
end
end
def withdraw(listing_price)
self.points -= listing_price
end
def purchasing_list
purchasing.includes(:seller, :listing)
end
def purchased_list
purchased.includes(:seller, :listing)
end
def sell_list
sell.includes(:seller, :listing)
end
def sales_list
sales.includes(:seller, :listing)
end
end
resources
resources :users
I looked around but all i could find was something saying that it is looking for a resource that doesn't exist.
It seems that params[:id] not present.
first try to check with putting in
class UserController < ApplicationController
def show
logger"-----#{params[:id]}---"
#user = User.find(params[:id])
end
end
if its empty then pass id from the link from where you getting this error
ex. localhost:3000/users/1
here params[:id] == 1 must be
Some where in your form which redirects you to the users show page you should send the id or send the object itself.
<%= link_to 'Show', user_path(user) %>
or
<%= link_to 'Show', user_path(user.id) %>
Please check your params, As I know you are getting params[:id] = nil.
Try to pass :id in your params or you can check it by hard-coding it.
Check yo console!
In the parameters that are sent through in the request that is blowing up, have a look at exactly what the id your sending through is. Is it a user_id? is it an id? Your controller is expecting an id so make sure you are passing it one.
If you're passing an entire object you will need to specify. Eg. If you're passing through a user you'll need to pass through a user.id instead because your controller is expecting an ID which is typically a number. user will give the whole object, user.id will give just the number value, which is the ID in this case. Then with that number the controller action can find the relevant user in the database.

Resources