user1957774
user1957774

Reputation: 23

Accessing additional values on has_many through Rails

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.

Upvotes: 2

Views: 400

Answers (2)

Sasidaran
Sasidaran

Reputation: 454

players_users and players are associated with User object, so you can fetch the results as,

current_user.players_users.pluck(:permission)

Upvotes: 1

Richard Peck
Richard Peck

Reputation: 76774

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).

Upvotes: 1

Related Questions