I am having real trouble accessing the value of an additional parameter called permission on a has_many through. It is probably something simple.
My 3 Models are
class User < ActiveRecord::Base
has_many :players_users
has_many :players, through: :players_users
end
class Player < ActiveRecord::Base
has_many :players_users
has_many :users, through: :players_users
end
class PlayersUser < ActiveRecord::Base
belongs_to :user
belongs_to :player
validates :player_id, uniqueness: { scope: :user_id }
end
My controller saves the record without issue. Adding the permission value to the correct joining table.
def create
#players = Player.new(players_params)
#user= current_user
if #players.save
#player = Player.last
#user.save && #user.players_users.create(:player_id =>#player.id, :permission =>"owner")
redirect_to '/players'
else
render 'new'
end
end
However I seem unable to access it properly
I have tried
perm = User.find(current_user).players_users.includes(:Permission)
if perm == "owner"
Which gives an ActiveRecord::AssociationNotFoundError, association named 'Permission' was not found on PlayersUser; perhaps you misspelled it?
I have also tried
perm = User.players_users.where(player_id = #player.id && user_id = current_user)
perm.permission
or
perm = User.Player.where(player_id = #player.id && user_id = current_user)
or
perm = User.players.where(player_id = #player.id && user_id = current_user)
Which gives an undefined method error.
undefined method `Player'
I know this is something small in my setup but cannot figure out what it is. Any help appreciated.
players_users and players are associated with User object, so you can fetch the results as,
current_user.players_users.pluck(:permission)
I've solved this issue before with my own code.
I'll post that in a second, but first you need to refactor your controller, the current is inefficient:
#app/controllers/players_controller.rb
class PlayersController < ApplicationController
def create
#player = current_user.players.new players_params
if #player.save
current_user.player_users.find(#player.id).update(permission: "owner")
redirect_to players_path
end
end
private
def player_params
params.require(:player).permit(....)
end
end
To access the permission of the player_user, you'll need to use the following:
#permission = current_user.player_users.find(#player.id).permission
--
A much more DRY approach (if you're explicitly setting permission as owner each time) would be to introduce an enum into the Player model to act as a default:
#app/models/player.rb
class Player < ActiveRecord::Base
enum permission: [:owner, :member, :moderator] #-> defaults to :owner
end
This will do away with having to define the permission in the create method (unless of course you want to change it):
#app/controllers/players_controller.rb
class PlayersController < ApplicationController
def create
#player = current_user.players.new players_params
redirect_to players_path if #player.save
end
end
To understand this, you must remember that since player_users is a join association, ActiveRecord will automatically populate it if you create a player on the current_user object (current_user.players).
Association Extensions
In regard to pulling the permission data, I built a script which appends the permission to the player object (uses proxy_association.target etc):
#current
#player = current_user.players.find params[:player_id]
#permission = current_user.player_users.find(params[:player_id]).permission
#script
#player = current_user.players.find params[:player_id]
#permission = #player.permission
It works similarly to an SQL Alias column - so whilst you cannot manipulate the data, it will allow you to call #user.players.find(params[:player_id].permission.. except it's all done in memory:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :player_users
has_many :players, through: :player_users, -> { extending PlayerPermission }
end
#app/models/concerns/player_permission.rb
module PlayerPermission
#Load
def load
permissions.each do |permission|
proxy_association.target << permission
end
end
#Private
private
#Permissions
def permissions
return_array = []
through_collection.each_with_index do |through,i|
associate = through.send(reflection_name)
associate.assign_attributes({permission: items[i]})
return_array.concat Array.new(1).fill( associate )
end
return_array
end
#######################
# Variables #
#######################
#Association
def reflection_name
proxy_association.source_reflection.name
end
#Foreign Key
def through_source_key
proxy_association.reflection.source_reflection.foreign_key
end
#Primary Key
def through_primary_key
proxy_association.reflection.through_reflection.active_record_primary_key
end
#Through Name
def through_name
proxy_association.reflection.through_reflection.name
end
#Through
def through_collection
proxy_association.owner.send through_name
end
#Captions
def items
through_collection.map(&:permission)
end
#Target
def target_collection
#load_target
proxy_association.target
end
end
--
As an aside, convention is to keep all aspects of a model name singular (ProductUser).
Related
class Pool
has_many :memberships
end
class Membership
belongs_to :pool, presence: true
end
This is the association I have. Whenever a new pool is created, I also want to create an entry into the membership class.
class Api::PoolsController < ApplicationController
def create
#pool = Pool.new(pool_params)
member = Membership.new(user_id: current_user.id, pool_id: #pool.id)
...
end
end
The issue is that #pool is not currently saved to the database yet, so it does not have an id value.
Is there a way that I can tell member to add the id after #pool is saved? Basically linking them to each other?
class Api::PoolsController < ApplicationController
def create
#pool = Pool.new(pool_params)
member = #pool.memberships.new(user_id: current_user.id)
...
end
end
My association looks like this :
class Abc < ApplicationRecord
has_many :def
accepts_nested_attributes_for :def, allow_destroy: true
end
class AbcController < ApplicationController
def update
abc = Abc.find(params[:id])
if abc.update(abc_params)
# TODO Here update is sucessful but how to get all newly added def in database with their id?
end
end
private
def abc_params
params.require(:abc).permit(def_attributes: [:name, :title,:wordcount, :id])
end
end
We know accepts_nested attributes creates a new nested object in the database so how can I get all the newly added(not already existing) def object with their database id in AbcController update function ?
One solution is (not a direct one)..
def update
abc = Abc.find(params[:id])
number_of_defs = abc.defs.length
if abc.update(abc_params)
number_newly_added_defs = abc.defs.length - number_of_defs
newly_added_defs = abc.defs.last(number_newly_added_defs)
end
end
you will get the desired output.
I am trying to prevent users from creating duplicate chatrooms (chatroom includes 2 users). But I have no idea how to validate if chatroom with the same users already exists before save.
Thats my create method in chatroom controller:
def create
#chatroom = Chatroom.new
#friend = User.where(username: params[:friend]).last
#chatroom.chatroom_users.build(user: #friend, chatroom: #chatroom)
#chatroom.chatroom_users.build(user: current_user, chatroom: #chatroom)
if #chatroom.save
flash[:notice] = "chatrooom created"
redirect_to #chatroom
else
flash[:notice] = "chatrooom not created lol"
redirect_to authenticated_root_path
end
end
And that is how I am trying to validate if there is no chatroom with 2 users like the new one:
In my class Chatroom
after_save :duplicate?
# Checks if chatroom containing specific users exists.
def duplicate?
user = self.users.first
friend = self.users.second
# lines below work fine - they check if there is already such chatroom
(Chatroom.all - [self]).each do |chatroom|
if ((chatroom.users & [user, friend]) - [user, friend]).empty?
self.errors.add(:chatroom, "Such chatroom already exists.")
end
end
end
The problem is: if I use after_save in validating method I can get the self.users.first to set user and friend variables, but then It does not stop from creating that record and I am not sure If deleting it there is a good idea. Secondly - I use validate instead of after_save self.users.first and self.users.second returns nil, so I can't check for duplicates.
PS: I do not want to have users id as the attributes in the chatrooms table because I want to add ability to connect to chat for as many ppl as you want.
How about something like this?
def duplicate?
is_duplicate = (Chatroom.all.to_a - [self]).any? do |chatroom|
chatroom.users.map(&:id).sort == self.chatroom_users.map(&:user_id).sort
end
if is_duplicate
errors.add(:chatroom, "Such chatroom already exists.")
end
end
Here are all of the models.
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
class Chatroom < ApplicationRecord
has_many :chatroom_users, :dependent => :destroy
has_many :users, :through => :chatroom_users
before_validation :duplicate?
def duplicate?
is_duplicate = (Chatroom.all.to_a - [self]).any? do |chatroom|
chatroom.users.map(&:id).sort == self.chatroom_users.map(&:user_id).sort
end
if is_duplicate
errors.add(:chatroom, "Such chatroom already exists.")
end
end
end
class ChatroomUser < ApplicationRecord
belongs_to :chatroom
belongs_to :user
end
class User < ApplicationRecord
has_many :chatroom_users, :dependent => :destroy
has_many :chatrooms, :through => :chatroom_users
end
And here is a test
require 'test_helper'
class ChatroomTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
test 'does not allow duplicate chatrooms' do
first_user = User.create!
second_user = User.create!
chatroom = Chatroom.create!
chatroom.chatroom_users.build(user: first_user, chatroom: chatroom)
chatroom.chatroom_users.build(user: second_user, chatroom: chatroom)
chatroom.save!
duplicate_chatroom = Chatroom.create!
duplicate_chatroom.chatroom_users.build(user: first_user, chatroom: chatroom)
duplicate_chatroom.chatroom_users.build(user: second_user, chatroom: chatroom)
assert !duplicate_chatroom.valid?
end
end
Note: This code is in Rails 5.
I have 2 models in my app: Person and Review each person has many reviews and each review belongs to a person. I have an attribute called grade inside of my review model and I'd like to display the average grade of each person so I wrote this scope:
scope :average_grade, -> { self.first.review.average(:grade) }
Is there a better way for doing that? Besides the 2 queries that this method needs, I also have to run another 2 queries to get the proper Person object in my controller:
def show
#average = Person.includes(:review).where(id: params[:id]).average_grade
#person = Person.includes(:review).find(params[:id])
end
How could I avoid all of those queries?
Your scope is an instance method rather than scope, since it does not return ActiveRecord::Relation object.
I suggest you to do following:
# person.rb:
def average_grade
review.average(:grade)
end
# controller:
def show
#person = Person.find(params[:id])
#average = #person.average_grade
end
# person.rb
class Person < ActiveRecord::Base
has_many :reviews
end
# review.rb
class Review < ActiveRecord::Base
belongs_to :person
scope :by_person, ->(person) { where(person_id: person) }
end
# persons_controller
class PersonsController < ApplicationController
helper_method :person
private
def person
return #person if defined? #person
#person = Person.find(params[:id])
end
end
# show.html.haml
- present(person) do |person_presenter|
%p= person_presenter.average_grade
# person_presenter.rb
class PersonPresenter < BasePresenter
present :person
def average_grade
Review.by_person(person).average(:grade)
end
end
More on presenters you could find here Railscasts PRO #287 Presenters from Scratch
Currently in my Rails app, I have users, swatches, and bottles.
A swatch belongs to a bottle and the user that submitted it.
A bottle does not belong to a user.
class Swatch < ActiveRecord::Base
mount_uploader :swatch, SwatchUploader
belongs_to :user
belongs_to :bottle
end
class Bottle < ActiveRecord::Base
has_many :swatches
end
class User < ActiveRecord::Base
has_many :swatches
end
Currently, the user can upload new swatches for a bottle from the bottle's show page.
Here's the swatch controller:
class SwatchesController < ApplicationController
def create
#bottle = Bottle.find(params[:bottle_id])
#user = current_user
#swatch = #bottle.swatches.create(swatch_params)
redirect_to bottle_path(#bottle)
end
private
def swatch_params
params.require(:swatch).permit(:swatch, :user_id, :bottle_id)
end
end
HAML:
= form_for([#bottle, #bottle.swatches.build]) do |f|
= f.label :swatch
= f.file_field :swatch
= f.submit
With this set up, the form won't work unless I pass the current user's ID as a hidden field. But I know that is bad practice and would like to avoid that.
So my question is: How do I pass both the bottle_id and current user's user_id through the controllers?
Why do you need to pass current_user through the form? In your controller, if you want to set user_id to the current_user id, just do that as such:
app/models/swatch.rb
class Swatch < ActiveRecord::Base
mount_uploader :swatch, SwatchUploader
belongs_to :user
belongs_to :bottle
def set_user!(user)
self.user_id = user.id
self.save!
end
end
app/controllers/swatches_controller.rb
class SwatchesController < ApplicationController
def create
#bottle = Bottle.find(params[:bottle_id])
#user = current_user
#swatch = #bottle.swatches.new(swatch_params)
#swatch.set_user!(current_user)
redirect_to bottle_path(#bottle)
end
private
def swatch_params
params.require(:swatch).permit(:swatch, :bottle_id)
end
end
can't you just use current_user for the user?, for the bottle_id, I guess the best would be to have the bottle in the address, since you are creating a swatch for a bottle, you url could be something like:
/bottles/<id of bottle>/swatches
your route should be
post "/bottles/:bottle_id/swatches", to: 'swatches#create'
then, on your controller SwatchesController, you can do
def create
bottle = Bottle.find(params[:bottle_id])
swatch = current_user.swatches.create params[:swatch].merge(bottle: bottle)
if swatch.new_record?
#something if not saved
else
#something if saved
end
end