How could I update an intermediary table in rails using checkobxes? - ruby-on-rails-3

I am not sure how to figure this one out, because I dont know how I would update my intermediary table, without having to do delete_all. Or where to do inserts and deletes, etc.
I have three classes:
class User < ActiveRecord::Base
has_many :players
has_many :teams, :through => :players
end
class Team < ActiveRecord::Base
has_many :players
end
class Player < ActiveRecord::Base
belongs_to :team
belongs_to :user
(contains other columns like, is_captain, has_paid, etc.)
end
I am displaying a page with checkboxes and you can select which 'users' you would like to be on your team (from your AddressBook). So imagine 25 names listed, and some of them are already checked and others are not. How could I save the data of checkboxes, cause they may have 3 different states: 1) insert brand new player (insert row into player) 2) remove player from team (delete existing player) or 3)do nothing (a player you dont want on your team)
The issue i am running into is that when you 'uncheck' a box, then the front-end does not send that to back-end? Any ideas how this can be done nicely?

Not sure if this is the best way to do this but it works, I am open to recommendations in improving this code
params[:team][:user_ids] ||= []
#team = Team.find(params[:team][:id])
current_players = #team.players
current_players_user_ids = current_players.collect { |player| player.user_id }
user_ids = params[:team][:user_ids]
puts "input ids #{user_ids}"
#add current_user because checkbox does not pass them in since its 'disabled'
user_ids << current_user.id
#delete the current_players that are not in the the received 'checkboxes'
current_players_user_ids.each do |current_player_user_id|
unless user_ids.include?(current_player_user_id)
Player.delete_all(:team_id => #team.id, :user_id => current_player_user_id)
end
end
#iterate through input ids and run 'inserts' on those who are not current-players
user_ids.each do |user_id|
unless current_players_user_ids.include?(user_id)
player = Player.new(:user_id=>user_id, :team_id=>#team.id)
#team.players << player
end
end

Related

Select the correct record in an <ActiveRecord::Associations::CollectionProxy>

I'm trying to understand what I'm doing wrong here - if someone can point me in the right direction, that would be great!
I have the following associations in my application:
class Notification < ApplicationRecord
belongs_to :notificationable, polymorphic: true
end
class AccessPermission < ApplicationRecord
belongs_to :project
has_many :notifications, as: :notificationable, dependent: :destroy
end
In one of my controllers, I have a call that finds all Access Permissions that belong to a project, and then select all the notifications for that collection of Access Permissions. But I'm having a bit of trouble. I thought a call like AccessPermission.where(project_id: 1).notifications would work, but it doesn't. So I resorted to doing this in ruby using .map
if AccessPermission.where(project_id: 1).map(&:notifications).include?(Notification.where(recipient_id: 1))
#Here is where I need help. Find the notification record in the ActiveRecord::Associations that made the call above "True." I tried:
notification = AccessPermission.where(project_id: 1).map(&:notifications).find(Notification.where(recipient_id: 1)).first
## That did not work, it returns an enumerator and then selects the very first object of the array no matter what the recipient_id is
else
#create the notification if no notification exists
access_permission.create_notification(recipient_id: 1)
end
You want a has_many through relationship on your Project model.
Something like:
has_many :notifications, through: :access_permissions
Then you can just do
project.notifications
to get what you want. Should scale pretty well, it will join the tables for the query.
After you have that you can do something like this:
project = Project.find(1)
if (notification = project.notifications.find_by(recipient_id: 1))
# Do something with your notification here
else
access_permission.create_notification(recipient_id: 1)
end

Querying relationships of relationships in Rails

I am trying to create a query in Rails but am having some trouble creating the correct one. Below is my models with their relationships.
class User < ActiveRecord::Base
has_and_belongs_to_many :rsvps, class_name: 'Event'
has_many :albums
end
class Event < ActiveRecord::Base
has_many :albums
has_and_belongs_to_many :attendees, class_name: 'User'
end
class Album < ActiveRecord::Base
belongs_to :user
belongs_to :event
end
I need to get all events a user has "rsvp'ed" to that they haven't uploaded an album to yet. I can find out if a user has uploaded an album to a particular event using the following:
u = User.find(1)
e = Event.find(1)
e.albums.where(user_id: u.id)
I want to be able to run this query on each of the user's rsvp'ed albums. I know I could do something like this:
u.rsvps.delete_if { |e| !e.albums.where(user_id: u.id).blank? }
However, I want to do this all in one query instead of getting the rsvps and then iterating over them and deleting them when necessary.
In order to get all events a user has rsvp'ed to but haven't uploaded an album to yet, you can use the following, which (UPDATE) now also works when a user has not uploaded any albums.
#event_ids = Album.where(user_id: u.id).pluck(:event_id))
#event_ids.empty? ? u.rsvps : u.rsvps.where("id not in (?)", #event_ids)
In addition, this query should work as well.
u.rsvps.where.not(id: Album.where(user_id: u.id).pluck(:event_id))

How can I query a has_many through relationship which contains an additional attribute in the join table?

Situation
In my application a user can create a plan. Once the plan is created, the user can define the stakeholders/team members of the plan. Each team member becomes a responsibility assigned. There are many plans and users can be stakeholders of multiple plans and in each plan they have a different responsibility.
Example
Admin creates a plan and assigns 10 users as stakeholders. 1 is accountable, 2 are responsible, 7 just need to be informed
What I did so far
I set up a has_many through relationship between two models:
class User < ActiveRecord::Base
has_many :assignments
has_many :plans, through: :assignments
end
class Plan < ActiveRecord::Base
has_many :assignments
has_many :users, through: :assignments
end
The assignment table looks like this:
create_table :assignments do |t|
t.belongs_to :user
t.belongs_to :plan
t.string :responsibility
end
add_index :assignments, [:user_id, :plan_id]
the column responsibility contains one of 4 different values (responsible, accountable, informed, consulted.)
What I am looking for
I know how I can query all users that have been assigned to the plan (#plan.users.to_a) but I do not know how I can additionally supplement the user information with the responsibility they have in this plan.
The query I need is something along the lines of:
Select users which belong to plan X by looking at the assignment table. Do not just use the assignment table to identify the user, but also take the value from the responsibility column in the assignment table and return an array which contains:
user.first_name
user.last_name
user.responsibility (for this specific plan)
We had this exact requirement, and solved it in 2 ways:
Use an SQL Alias Column
The first way is to use an SQL Alias Column & append it to your has_many association, like this:
Class User < ActiveRecord::Base
has_many :assignments
has_many :plans, -> { select("#{User.table_name}.*, #{Plan.table_name}.responsibility AS responsibility") }, through: :assignments, dependent: :destroy
end
This will allow you to call #user.plans.first.responsibility, and will fail gracefully if no record exists
Use ActiveRecord Association Extensions
This is the best, but more complicated, way, as it uses the proxy_association object in memory (instead of performing another DB request). This script took us 2 weeks to create, so we're very proud of it!! Not tested with Rails 4.1:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :assignments
has_many :plans, through: :assignments, extend: Responsibility
end
#app/models/plan.rb
Class Plan < ActiveRecord::Base
attr_accessor :responsibility
end
#app/models/concerns/Responsibility.rb
module Responsibility
#Load
def load
captions.each do |caption|
proxy_association.target << responsibility
end
end
#Private
private
#Captions
def captions
return_array = []
through_collection.each_with_index do |through,i|
associate = through.send(reflection_name)
associate.assign_attributes({responsibility: items[i]}) if items[i].present?
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
#Responsibilities
def items
through_collection.map(&:responsibility)
end
#Target
def target_collection
#load_target
proxy_association.target
end
end
Query the appointement table directly fitering for all the users in the current plan:
Appointement.select(:id).where(user_id: Plan.find(params[:id]).users.pluck(:user_id), plan_id: params[:id]).group(:id).having('count(*) = ?', Plan.find(params[:id]).users.count)

has_many :through and build

I have three models, Account, User and Contact:
class User < ActiveRecord::Base
has_one :account
has_many :contacts, :through => :account
end
class Account < ActiveRecord::Base
belongs_to :owner, :class_name => 'User'
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :account
end
I'm trying to scope build a new contact through the user record, like this in my contacts controller.
def create
#contact = current_user.contacts.build(params[:contact])
respond_to do |format|
if #contact.save
...
else
...
end
end
end
When I do this, I don't receive any errors, the contact record is saved to the database however the account_id column is not set on the contact, and it is not added to the collection so calling #current_user.contacts returns an empty collection.
Any suggestions?
Using build makes a new instance of Contact in memory, but you would need to manually set the account_id on the record (e.g. #contact.account_id = current_user.account.id), or perhaps set it in a hidden field in the new form used to display the contact for creation such that it is picked up in the params array passed to the build method.
You might also want to consider whether accepts_nested_attributes_for may be helpful in this case. Another option may be to use delegate, although in both cases, your use may be sort of the opposite of what these are intended for (typically defined on the "parent").
Update:
In your case, the build method is added to both the User instance and to the Account (maybe "Owner") instance, because you have both a many-to-many relationship between User and Contact, as well as a one-to-many relationship between Account and Contact. So to get the account_id I think you would need to call Account's build, like
#contact = current_user.accounts.contacts.build(params[:contact])
Does this work?

Simple has_many :through association

Pretty simple setup. I want to make sure my understanding of the ORM is correct.
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, through => memberships
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, through => memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
Now when a user creates a group I want the membership record in the link table to get populated. It should be an atomic(transaction).
class GroupsController < ApplicationController
def create
#group = current_user.groups.build(params[:group])
if #group.save
flash[:notice] = "Group has been created."
redirect_to #group
else
flash[:alert] = "Group has not been created."
render :action => "new"
end
end
end
This doesn't work. The group gets saved but no membership record created in the link table. However using a create vs build works. Is that how it's supposed to work?
What's the best approach here?
This behaviour is by design. As you mentioned, you can either do #group = current_user.groups.create(params[:group]).
Or you can add an additional statement to create a record in the join model's table as :
#group = current_user.groups.build(params[:group])
if #group.save
#group.memberships.create(:user_id => current_user)
# redirect and notify
Well, the reason being simply building #group and saving it does not add an additional record in the join table.
Infact, in this case, #group = current_user.groups.build(params[:group]) is somewhat similar to #group = Group.new(params[:group]). The difference being, in the former case, current_user.groups will contain #group (you can try that in Groups#create before redirect) but doing current_user.reload followed by current_user.groups will yield [].
The best way to do this is somewhat similar to your approach. Have a simple create action as :
def create
#group = Group.new(params[:group])
# if else for save and redirect
However, for this to work the params hash submitted to Groups#create should include user_ids as :
"group"=>{"name"=>"new group", "user_ids"=>["1", "2", "3"]}, "commit"=>"Create Group"
May be that was the reason why #bruno077 was asking you to paste your view's code, so as to get an idea on user_ids params being passed.
So, if the new group form contains fields to select multiple users, then its simple create action as shown right above (because of the user_ids params). But if have a new group form with no options to select users, then you are better off using the first option (one using create).