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.
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 have nested relationships and built them according to the Rails Guide.
A User has many Collections that have many Sections each containing many Links. When creating a new Link though, the user_id is not being assigned but is always nil. The section_id and collection_id are being set correctly.
Controller
class Api::V1::LinksController < Api::V1::BaseController
acts_as_token_authentication_handler_for User, only: [:create]
def create
#link = Link.new(link_params)
#link.user_id = current_user
authorize #link
if #link.save
render :show, status: :created
else
render_error
end
end
private
def link_params
params.require(:resource).permit(:title, :description, :category, :image, :type, :url, :collection_id, :user_id, :section_id)
end
def render_error
render json: { errors: #resource.errors.full_messages },
status: :unprocessable_entity
end
end
Models
User
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
acts_as_token_authenticatable
has_many :collections, dependent: :destroy
has_many :sections, through: :collections, dependent: :destroy
has_many :links, through: :sections, dependent: :destroy
mount_uploader :image, PhotoUploader
end
Collection
class Collection < ApplicationRecord
belongs_to :user
has_many :sections, dependent: :destroy
has_many :links, through: :sections, dependent: :destroy
mount_uploader :image, PhotoUploader
end
Section
class Section < ApplicationRecord
belongs_to :collection
has_many :links, dependent: :destroy
end
Link
class Link < ApplicationRecord
belongs_to :section
end
Is this the correct way to set up the relationships and can someone help me understand what I am missing?
You can't do
#link.user_id = current_user
You could (instead) do...
#link.user_id = current_user.id
Or more elegantly...
#link.user = current_user
Which assumes you will define the relationship in the model
class Link < ApplicationRecord
belongs_to :section
belongs_to :user
end
But as Andrew Schwartz points out in the comments, it may have been a design mistake to add the field user_id to the links table. You have in the User model has_many :links, through: :sections, dependent: :destroy which does not use any user_id field in the link record. It uses the user_id field in the collections table
Just adding user_id to the links table will NOT mean that link will be returned when you do my_user.links ... it won't be.
Since you're passing a section_id in the link_params that is enough to create the link to the user, so just write a migration to remove the user_id field. If you want to be able to see the associated user from the link, do...
class Link < ApplicationRecord
belongs_to :section
has_one :collection, through: :section
has_one :user, through: :collection
end
and that will let you do my_link.user to retrieve the link's user.
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 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
Hi I have a many to many association where 'posts' have many 'feeling', I'd like to figure out how to find all the posts with a specific feeling by the user. My Feeling model has a 'name' attribute.
class User < ActiveRecord::Base
has_many :posts, :dependent => :destroy
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
class Post < ActiveRecord::Base
has_many :feelingships
has_many :feelings, :through => :feelingships
belongs_to :user
end
class Feeling < ActiveRecord::Base
has_many :feelingships
has_many :posts, :through => :feelingships
end
class Feelingship < ActiveRecord::Base
belongs_to :post
belongs_to :feeling
attr_accessible :post_id, :feeling_id
end
I tried this but it says I have the wrong association: "ActiveRecord::ConfigurationError: Association named 'feeling' was not found; perhaps you misspelled it?"
def feeling
#user = User.find(params[:id])
#feed_items= #user.posts.includes(:feeling).where(
['`feelings`.name = ?', params[:feeling]])
#feed_items = #feed_items.paginate(:per_page => "10", :page => params[:page])
render 'shared/_feed', :layout => 'head_layout'
end
The includes argument should be :feelings - notice the plural, which is what your association is named.
So it should be:
#user.posts.includes(:feelings)