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.
Related
My goal is to display in the notifications modal that a follower has started following the current user whenever the user clicks the follow button.
I am also using the noticed gem, but it seems a bit complicated for me to implement with my relationship model (Which is the follower/following model).
Whenever I follow someone, I see in the console that it is inserting the notification, but when I click unfollow I get an error that there are "too many has_many associations". And when I log in as the user that gets followed the notification does not appear. I am assuming because I have implemented the notify recipient function wrong.And I cannot seem to find any resources only for follow notifications.
Here is my code:
FollowNotification.rb
def message
#user = User.find(follower_id: params[:user_id])
"#{#user.username} has started following you"
end
#
def url
show_user_path(#user)
end
Relationships Controller
class RelationshipsController < ApplicationController
before_action :authenticate_user!
def create
#user = User.find(params[:user_id])
# if statement prevents user from forcing both users to follow each other after accepting request
if current_user.Is_private? && !#user.pending_requests
following = #user.relationships.build(follower_id: current_user.id)
following.save
redirect_to request.referrer || root_path
else
following = current_user.relationships.build(follower_id: params[:user_id])
following.save
redirect_to request.referrer || root_path
end
end
def destroy
following = current_user.relationships.find_by(follower_id: params[:user_id])
following.destroy
redirect_to request.referrer || root_path
end
end
Relationship model
class Relationship < ApplicationRecord
belongs_to :following, class_name: 'User'
belongs_to :follower, class_name: 'User'
has_noticed_notifications model_name: 'Notification'
has_many :notifications, through: :user, dependent: :destroy
after_create_commit :notify_recipient
before_destroy :cleanup_notifications
private
def notify_recipient
FollowNotification.with(follower: self).deliver_later(following.id)
end
def cleanup_notifications
notifications_as_follow.destroy_all
end
end
User model
class User < ApplicationRecord
has_merit
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one_attached :avatar
validates :avatar, file_size: { less_than_or_equal_to: 5.megabytes },
file_content_type: { allow: ['image/jpg', 'image/png', 'image/jpeg'] }
has_many(
:posts,
class_name: 'Post',
foreign_key: 'user_id',
inverse_of: :user
)
has_many :likes
has_many :comments
validates :username, presence: true, length: {maximum: 30}
validates_uniqueness_of :username
has_many :relationships, foreign_key: :following_id
has_many :followings, through: :relationships, source: :follower
has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: :follower_id
has_many :followers, through: :reverse_of_relationships, source: :following
def is_followed?(user)
reverse_of_relationships.find_by(following_id: user.id).present?
end
has_many :notifications, as: :recipient, dependent: :destroy
end
I'm currently implementing pundit, where I am trying to identify whether or not a user has an admin role.
Issue
I'm trying to avoid creating a join_table between discounts and users, by leveraging the relationship between
discounts and attraction (a discount belongs to an attraction)
attractions and park (a park has_many attractions)
parks and users (many to many relationship, via a join_table).
--> However, I get the error message: undefined local variable or method `attraction' for #<DiscountPolicy::Scope:0x00007fa012ec6b70>
Question
I was wondering:
if it's even possible what I'm trying to do and if so
how will I be able to access the user?
Code
discount controller
def index
#user = current_user
if params[:attraction_id]
#attraction = Attraction.find(params[:attraction_id])
#discounts = #attraction.discounts
#discounts = policy_scope(#discounts)
else
#discounts = []
end
end
discount policy
class DiscountPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin?
# scope.where(user: user)
scope.joins(attraction: :discounts).where(discounts: { attraction_id: attraction.id }).joins(park: :attractions).where(attractions: { park_id: park.id }).joins(park: :user_parks).where(user_parks: { user_id: user.id })
else
raise Pundit::NotAuthorizedError
end
end
end
def index?
user.admin?
end
end
models
class Discount < ApplicationRecord
belongs_to :attraction
has_many :reservations
end
class Attraction < ApplicationRecord
belongs_to :park
has_many :discounts, dependent: :destroy
accepts_nested_attributes_for :discounts, allow_destroy: true
end
class Park < ApplicationRecord
has_many :attractions, dependent: :destroy
has_many :discounts, through: :attractions
has_many :user_parks, dependent: :destroy
has_many :users, through: :user_parks
accepts_nested_attributes_for :users, allow_destroy: true, reject_if: ->(attrs) { attrs['email'].blank? || attrs['role'].blank?}
end
class UserPark < ApplicationRecord
belongs_to :park
belongs_to :user
end
class User < ApplicationRecord
has_many :user_parks, dependent: :destroy
has_many :parks, through: :user_parks
enum role: [:owner, :admin, :employee, :accountant]
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
You need to have nested association joins. Here's what your scope should look like:
scope.joins(attraction: [park: :user_parks]).where(user_parks: { user_id: user.id })
You can go through the documentation to understand better.
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.
i have a problem to solve in my application here a little brief:
My app is something like AirBnb so i have Users and Houses, any user can create a house i already have this, i need a watch list, is a list of houses who user liked like a Bookmark or Favorite system, i have the house list and the idea is have button like "watch this" when user clicks this house go to their watch lists.
I've seen many solutions and i tried them, i understand the relationship but i don't know how do get pieces in.
here is my code currently:
watch.rb:
class Watch < ActiveRecord::Base
belongs_to :user
belongs_to :house
end
user.rb:
class User < ActiveRecord::Base
has_many :houses, :dependent => :destroy
has_many :watches, :dependent => :destroy
has_many :watch_houses, :through => :watches, :source => :houses
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
house.rb:
class House < ActiveRecord::Base
belongs_to :user
has_many :watches, :dependent => :destroy
has_many :watches, :through => :watches, :source => :user
end
routes.rb:
Rails.application.routes.draw do
resources :houses
devise_for :users
resources :users, :only => [:show] do
resources :watches
end
resources :houses
root 'home#index'
end
How can i create a link to assing the user and the house in the watchlist cliking in the house list?
Here's how to do it:
#config/routes.rb
resources :houses do
post :watch #-> url.com/houses/:house_id/watch
end
#app/controllers/houses_controller.rb
class HousesController < ApplicationController
def watch
#house = House.find params[:house_id]
current_user.watched_houses << #house
redirect_to #house, notice: "Added to Watch List"
end
end
Here are the models:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :houses, dependent: :destroy
has_many :watching, class_name: "Watch", foreign_key: :user_id, dependent: :destroy
has_many :watched_houses, through: :watching
end
#app/models/house.rb
class House < ActiveRecord::Base
belongs_to :user
has_many :watches, dependent: :destroy
has_many :watchers, through: :watches, foreign_key: :user_id
end
UPDATE
I have an action in my Miniatures model called set_gold_and_silver.
I want my Users model to run it when a User is destroyed, so I have before_destroy :set_gold_and_silver in my User model.
A User has many Imagevotes. Before destroy I need to delete those Imagevotes and then run set_gold_and_silver on all the Miniatures that those imagevotes pertained to.
This is what I've got so far and I'm currently getting undefined method 'miniatures'.
It's not clear to me whether I am caching self.imagevotes or whether they are just deleted and then I get the error because they no longer exist?
def set_gold_and_silver
votes = self.imagevotes
self.imagevotes.destroy
votes.miniatures.uniq.each(&:set_gold_and_silver)
end
My models
User
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_many :collections, dependent: :destroy
has_many :miniatures, through: :collections
has_many :imagevotes, foreign_key: "voted_id", dependent: :destroy
has_many :imagevotes, foreign_key: "voter_id", dependent: :destroy
before_destroy :set_gold_and_silver
def set_gold_and_silver
my_collections = self.collections.each
their_miniatures = collection.miniature.uniq
my_collections.their_miniatures.each(&:set_gold_and_silver)
end
end
Miniature
class Miniature < ActiveRecord::Base
has_many :collections, dependent: :destroy
has_many :users, :through => :collections
has_many :imagevotes, dependent: :destroy
def set_gold_and_silver
wipe = self.collections.all
wipe.each {|s| s.update_attributes :is_gold => false, :is_silver => false}
top_collections = self.collections.limit(4)
gold = top_collections.shift
gold.update_attribute :is_gold, true if gold
top_collections.each {|s| s.update_attribute :is_silver, true}
end
end
Collection
class Collection < ActiveRecord::Base
default_scope order('imagevotes_count DESC')
belongs_to :miniature
belongs_to :user
has_many :imagevotes, dependent: :destroy
end
Imagevote
class Imagevote < ActiveRecord::Base
belongs_to :collection, :counter_cache => true
belongs_to :voter, class_name: "User", :counter_cache => "voted_count"
belongs_to :voted, class_name: "User", :counter_cache => "vote_count"
belongs_to :miniature
after_create :set_gold_and_silver
after_update :set_gold_and_silver
def set_gold_and_silver
self.miniature.set_gold_and_silver
end
end
You need to make your code simpler:
class Miniature < ActiveRecord::Base
def set_gold_and_silver
self.collections.update_all("is_gold = false, is_silver = false")
top_collections = self.collections.limit(4)
gold = top_collections.shift
gold.update_attribute :is_gold, true if gold
top_collections.each {|s| s.update_attribute :is_silver, true}
end
end
class User < ActiveRecord::Base
def set_gold_and_silver
self.miniatures.uniq.each(&:set_gold_and_silver)
end
end
you have has_many :miniatures, through: :collections so you don't need to work with collections to get minuatures.
And for now your code not working because everything still there before destroy. It need to be done after, when everything depended to user removed. And also as it seems for me you need to remove imagevotes in user destroy and set_gold_and_silver only after that. For now it's not done, so gold and silver stays.