Rails - Issue with controllers for creating a user group - mysql

I currently have a Rails app that allows users to create a group and allows other users to join the group. The group "creator" is the owner of the group and any that join ON REQUEST are the members. I want a user to be able to create only one group, but belong to many (I think that I've captured that relationship, but I'm a little uncertain). I need a little help understanding what I need to do to show the group associations on the User's page. How should I go about creating a group "show" page and how do I show the group memberships on the User "show" page? I got help from SO and followed the Railscast on self-referential association to help guide me through setting up the relationships.
In this example groups are called Cliqs and membership is controlled by a has_many :through. I used Devise for the User model.
To clarify my question: Am I capturing the relationship that I'm trying to set up? How would I go about allowing the user to view groups that they belong to?
As an aside, I'm not sure if the group creator is being associated as a member of the group. How do I represent that in my model/controller?
Here is my code:
Group Model:
class Cliq < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
has_many :members, through: :cliq_memberships, source: :user
has_many :cliq_memberships
end
Membership Model:
class CliqMembership < ActiveRecord::Base
belongs_to :cliq
belongs_to :user
end
User Model:
class User < ActiveRecord::Base
has_one :owned_group, foreign_key: 'owner_id', class_name: 'Group'
has_many :cliqs, through: :cliq_memberships
has_many :cliq_memberships
.
.
.
end
Group Controller:
class CliqsController < ApplicationController
def show
#cliq = Cliq.find(params[:id])
end
def new
#cliq = Cliq.new(params[:id])
end
def create
#cliq = Cliq.create(cliq_params)
if #cliq.save
redirect_to current_user
else
redirect_to new_cliq_path
end
end
def destroy
end
def cliq_params
params.require(:cliq).permit(:name, :cliq_id)
end
end
Group Membership Controller:
class CliqMembershipsController < ApplicationController
def create
#cliq = cliq.find(params[:cliq_id])
if #cliq_membership.save = current_user.cliq_memberships.build(:cliq_id => params[:cliq_id])
flash[:notice] = "Joined #{#cliq.name}"
else
#Set up multiple error message handler for rejections/already a member
flash[:notice] = "Not able to join Cliq."
end
redirect_to cliq_url
end
def destroy
#cliq = Cliq.find(params[:id])
#cliq_memberships = current_user.cliq_memberships.find(params[cliq_memberships: :cliq_id]).destroy
redirect_to user_path(current_user)
end
end
And my User Show Page:
<h1> <%= #user.username %> </h1>
<h2>Cliqs</h2>
<%= link_to "Create Cliq", new_cliq_path %>
<ul>
<% for cliq_membership in #user.cliq_memberships %>
<li>
<%= cliq_membership.cliq.name %>
(<%= link_to "Leave Cliq", cliq_membership, :method => :delete %>)
</li>
<% end %>
</ul>
<h3>Title:</h3>
<% #uploads.each do |upload| %>
<div>
<%= link_to upload.title, upload_url %>
</div>
<% end %>
And my Migrations:
Cliq:
class CreateCliqs < ActiveRecord::Migration
def change
create_table :cliqs do |t|
t.string :name
t.references :owner
t.integer :cliq_id
t.timestamps null: false
end
end
end
CliqMemberships:
class CreateCliqMemberships < ActiveRecord::Migration
def change
create_table :cliq_memberships do |t|
t.references :user
t.references :cliq
t.timestamps null: false
end
end
end
FULL SOLUTION OF WHAT WORKED BELOW.

Try the following:
Your revised models. Fixed the following issues:
In User model, for has_one :owned_group, you set class_name as Group instead of Cliq.
Declare has_many before has_many :through. It may work otherwise, but it is a good practice and easy for readability.
class User < ActiveRecord::Base
has_one :owned_group, foreign_key: 'owner_id', class_name: 'Cliq'
has_many :cliq_memberships
has_many :cliqs, through: :cliq_memberships
end
class CliqMembership < ActiveRecord::Base
belongs_to :cliq
belongs_to :user
end
class Cliq < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
has_many :cliq_memberships
has_many :members, through: :cliq_memberships, source: :user
end
Your revised controllers. Fixed the following issues:
In the CliqsController, as it is relates to Cliq, you won't get cliq_id while creating it. So removed the cliq_id from the cliq_params. You could add other cliq related attributes in there.
In create, you forgot to assign the current_user as the owner of the cliq. This is addressed by the next note.
As the user is the owner of the cliq, built the cliq using build_owned_group which automatically sets the current_user as the owner.
Try not to do multiple things in the same statement. Like assigning it to a variable as well as doing some operation on the newly assigned variable. For example: In create action of CliqMembershipsController, you were assigning the #cliq_membership as well as calling save on it. Separated those two into two steps.
In destroy of CliqMembershipsController, there is no need to load the #cliq and also fixed the way you are finding the #cliq_membership.
class CliqsController < ApplicationController
def show
#cliq = Cliq.find(params[:id])
end
def new
#cliq = Cliq.new(params[:id])
end
def create
#cliq = current_user.build_owned_group(cliq_params)
if #cliq.save
redirect_to current_user
else
redirect_to new_cliq_path
end
end
private
def cliq_params
params.require(:cliq).permit(:name)
end
end
class CliqMembershipsController < ApplicationController
def create
#cliq = Cliq.find(params[:cliq_id])
#cliq_membership = current_user.cliq_memberships.build(cliq: #cliq)
if #cliq_membership.save
flash[:notice] = "Joined #{#cliq.name}"
else
#Set up multiple error message handler for rejections/already a member
flash[:notice] = "Not able to join Cliq."
redirect_to cliq_url
end
def destroy
#cliq_membership = current_user.cliq_memberships.find(params[:id])
if #cliq_membership.destroy
redirect_to user_path(current_user)
end
end
end
And finally your revised view:
Fixed few things.
Try to use each on the collection to iteration through. This is more ruby way, instead of for loop.
Based on your CliqMemberhipsController code, I assumed you are using nested resources as below. So fixed the link_to to use cliq_cliq_memberhip_path instead of cliq_membership_path.
<h1><%= #user.username %></h1>
<h2>Cliqs</h2>
<%= link_to "Create Cliq", new_cliq_path %>
<ul>
<% #user.cliq_memberships.each do |cliq_membership| %>
<li><%= cliq_membership.cliq.name %>(<%= link_to "Leave Cliq", cliq_cliq_membership_path([cliq, cliq_membership]), method: :delete %>)</li>
<% end %>
</ul>
This assumes you have a routes file with the following:
resources :cliqs do
resources :cliq_memberships
end

Going along the lines of my comment above it seems to me the best thing to do is implement some kind of role attribute in the bridge table.
The Rails docs say this:
You should use has_many :through if you need validations, callbacks, or extra attributes on the join model.
So you might try this in your models:
class Cliq < ActiveRecord::Base
has_many :cliq_memberships
has_many :members, through: :cliq_memberships
def owner
cliq_memberships.where(role: 'owner').user
end
end
# this model is used to access attributes on the bridge table
class CliqMembership < ActiveRecord::Base
belongs_to :cliq
belongs_to :user
attr_accessor :role
end
class User < ActiveRecord::Base
has_many :cliq_memberships
has_many :cliqs, through: :cliq_memberships
# something like this would make it easy to grab the owned cliq
def ownedCliq
cliq_memberships.where(role: 'owner').cliq
end
end
so the bridge table stores role which would be an enum or a string representing 'member', 'owner', and maybe 'admin' or something.
Some example usage:
# say I have a user
u = User.find(1)
# and I want the cliq that he/she owns
owned_cliq = u.ownedCliq
# maybe I have a group:
g = Cliq.find(1)
# and I want the user that owns it:
my_owner = g.owner
# now let's get all the members of the cliq (including the 'owner')
my_members = g.members
More example usage:
# inside the controller...
# say I have a user:
u = User.find(1)
# this user is trying to create a cliq
# pretend we fill it in with its data here...
c = Cliq.new
c.save!
# we'll need to hook the two together:
cm = CliqMembership.new(role: 'owner', user_id: u.id, cliq_id: c.id)
cm.save!
# or we might try something like this:
#cm = CliqMembership.find_or_create_by #...
Also, I found this SO answer which does a good job of explaining things.

So I started with the code in my question above and then worked inward to my answer (through many additional trials). This may help someone in the future so here is what worked. (Taking advice from both answers):
class Cliq < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
has_many :cliq_memberships
has_many :members, through: :cliq_memberships, source: :user
end
class CliqMembership < ActiveRecord::Base
belongs_to :cliq
belongs_to :user
end
class User < ActiveRecord::Base
has_one :owned_cliq, foreign_key: 'owner_id', class_name: 'Cliq'
has_many :cliq_memberships
has_many :cliqs, through: :cliq_memberships
.
.
.
end
class CliqsController < ApplicationController
def show
#cliq = Cliq.find(params[:id])
end
def new
#cliq = Cliq.new(params[:id])
end
def create
#cliq = current_user.build_owned_cliq(cliq_params)
#cliq.members << current_user
if #cliq.save
redirect_to current_user
else
redirect_to new_cliq_path
end
end
def destroy
end
def cliq_params
params.require(:cliq).permit(:name, :cliq_id)
end
end
class UsersController < ApplicationController
def show
#find way to use username instead of id (vanity url?)
#user = User.find(params[:id])
#uploads = Upload.all
#cliq_memberships = CliqMembership.all
#cliqs = Cliq.all
end
end
class CliqMembershipsController < ApplicationController
def show
end
def create
#cliq = Cliq.find(params[:cliq_id])
#cliq_membership = current_user.cliq_memberships.build(cliq: #cliq)
if #cliq_membership.save
flash[:notice] = "Joined #{#cliq.name}"
else
#Set up multiple error message handler for rejections/already a member
flash[:notice] = "Not able to join Cliq."
end
redirect_to cliq_url
end
def destroy
#cliq_membership = current_user.cliq_membership.find(params[:id])
if #cliq_membership.destroy
redirect_to user_path(current_user)
end
end
class CreateCliqs < ActiveRecord::Migration
def change
create_table :cliqs do |t|
t.string :name
t.references :owner
t.timestamps null: false
end
end
end
class CreateCliqMemberships < ActiveRecord::Migration
def change
create_table :cliq_memberships do |t|
t.references :user
t.references :cliq
t.timestamps null: false
end
end
end
Thanks so much for all of the incredible help on this thread!

Related

How to send notifications to all commentors on polymorphic comments in rails?

I am using polymorphic association for comments.
I want to send notification to all user who comments on post.
Suppose user1 posted a post.
User2 comments on it.
User1 gets
notification (users = User.joins(:posts).where(:posts => {id: params[:post_id]})) that "User2 posted a comment"
Again, User1 comments.
Now, User2 should get a notification (users= ????)
If user2, user3, user4 comments then all the related users should get notifications.
comment.rb
class Comment < ApplicationRecord
belongs_to :user
belongs_to :commentable, polymorphic: true
has_many :comments, as: :commentable
validates :comment, presence: true
after_create :notifications
def notifications
#Create Notification
users = ????
(users-[#current_user]).each do |user|
Resque.enqueue(NotifyJob, Notification.create(
recipient: user,
actor: #current_user,
action: "posted",
notifiable: #comment))
end
end
end
user.rb
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, as: :commentable, dependent: :destroy
has_many :notifications, foreign_key: :recipient_id, dependent: :destroy
end
post.rb
class Post < ApplicationRecord
belongs_to :user
validates :comment, presence: true
validates :user_id, presence: true
has_many :comments, as: :commentable
end
comments_controller.rb
module Api
module V1
class CommentsController < ApplicationController
skip_before_action :verify_authenticity_token
before_action :authorize_request
before_action :find_commentable
def new
#comment = Comment.new
end
def create
#comment = #commentable.comments.new(comment_params)
#comment.user = #current_user
if #comment.save
render json: #comment, status: :created
else
render json: { errors: #comment.errors.full_messages },
status: :unprocessable_entity
end
end
def show
#comment = Comment.find(params[:id])
if !#comment.nil?
render json: #comment, status: :ok
else
render json: {errors: #comment.errors.full_messages}, status: :unprocessable_entity
end
end
private
def comment_params
params.permit(:comment)
end
def find_commentable
#commentable = Comment.find_by_id(params[:comment_id]) if params[:comment_id]
#commentable = Post.find_by_id(params[:post_id]) if params[:post_id]
end
end
end
end
Your first problem is in User model. You have
has_many :comments, as: :commentable, dependent: :destroy
but Comment model belongs_to :user and have user_id, it means that in User should be
has_many :comments, dependent: :destroy
In this case you can get all user comments easily with User.first.comments
The second one is callback. Vyacheslav Loginov is right about bad practice to put this complex logic inside the controller action, but callbacks are not good practice too. Not sure which of them is worse. You will create notification on every comment creation, even from console. Notifications will be created in every test setup where you create commnets. Not sure if that it is what you really want.
Better option is to create separate ServiceObject to handle notifications
class CommentsController < ApplicationController
def create
#comment = #commentable.comments.new(comment_params)
#comment.user = #current_user
if #comment.save
NotificationSender.new(#comment).comment_notification
render json: #comment, status: :created
else
render json: { errors: #comment.errors.full_messages },
status: :unprocessable_entity
end
end
end
class NotificationSender
def initialize(resource)
#resource = resource
end
# you can add here all other notifications as methods
def comment_notification
users = User.where(id: #resource.commentable.comments.pluck(:user_id)).
where.not(id: #resource.user_id)
users.each do |user|
Resque.enqueue(
NotifyJob,
Notification.create(
recipient: user,
actor: #resource.user,
action: "posted",
notifiable: #resource
)
)
end
end
end
I didn't make much changes. I just made changes like this on:
comment.rb
class Comment < ApplicationRecord
belongs_to :user
belongs_to :commentable, polymorphic: true
has_many :comments, as: :commentable
validates :comment, presence: true
private
after_commit :create_notifications, on: [:create]
#Create Notification
def create_notifications
commentable = self.commentable
commentors= Comment.where(commentable: commentable).pluck(:user_id)
users = User.find(commentors)-[self.user]
users.each do |user|
Resque.enqueue(NotifyJob, Notification.create(
actor: self.user,
recipient: user,
action: "posted",
notifiable: self.commentable))
end
end
end

Many to many join table not updated in Active admin

I have two models Destination and Package. A package may have multiple Destinations and Destination may contain multiple Packages. In order to maintain the relationship, I have a table called packages_in_destinations. While I tried to insert destination from package form in active admin, it doesn't show up any errors but the packages_in_destinations table is not updated. I am using MySQL for the database.
# app/admin/package.rb
ActiveAdmin.register Package do
permit_params :package_id, :package_title, :description, :featured_image, :duration, :meta_description, :meta_keywords, :published, :package_categories_id, :destinations_id
form do |f|
f.semantic_errors *f.object.errors.keys
f.inputs "Package Details" do
f.input :destination_id, :as => :check_boxes, :collection => Destination.all.collect {|destination| [destination.destination_title, destination.id]}
end
f.actions
end
end
# app/models/package.rb file
class Package < ApplicationRecord
validates_uniqueness_of :package_title, scope: :package_title
validates :package_title, :description, :package_categories_id, :presence => true
belongs_to :package_category
has_and_belongs_to_many :packages_in_destinations
has_many :destinations, through: :packages_in_destinations
end
# app/models/destination.rb file
class Destination < ApplicationRecord
validates_uniqueness_of :destination_title, scope: :destination_title
validates :destination_title, :description, :altitude, :geolocation, :presence=>true
has_and_belongs_to_many :packages_in_destinations
has_many :packages, through: :packages_in_destinations
end
# app/models/packages_in_destination.rb
class PackagesInDestination < ApplicationRecord
has_and_belongs_to_many :destinations, foreign_key: :destination_id, class_name: 'Destination'
has_and_belongs_to_many :packages, foreign_key: :package_id, class_name: 'Package'
end
the relationship between two table has been created from migration file
class CreatePackagesInDestinations < ActiveRecord::Migration[5.0]
def up
create_join_table :packages, :destinations, table_name: :packages_in_destinations do |t|
t.index :package_id
t.index :destination_id
t.timestamps
end
def down
drop_table :packages_in_destinations
end
end
another migration file is created to add a primary key to the table
class AddingPrimaryInPackaesInDestination < ActiveRecord::Migration[5.0]
def change
add_column :packages_in_destinations, :id, :primary_key
end
end
so from all these things no error has been shown but packages saves in packages table but not inserted relationship in the packages_in_destinations table. Please, somebody, suggest what's missing and what's wrong with these?
You need to either use has_and_belongs_to_many or has_many :through. Don't use both at the same time.
1) Example with has_and_belongs_to_many. In this case, you can remove PackagesInDestination model:
# app/models/package.rb file
class Package < ApplicationRecord
# ...
has_and_belongs_to_many :destinations, join_table: :packages_in_destinations
end
# app/models/destination.rb file
class Destination < ApplicationRecord
# ...
has_and_belongs_to_many :packages, join_table: :packages_in_destinations
end
# app/models/packages_in_destination.rb
# removed
2) Example with has_many :through:
# app/models/package.rb file
class Package < ApplicationRecord
# ...
has_many :packages_in_destinations
has_many :destinations, through: :packages_in_destinations
end
# app/models/destination.rb file
class Destination < ApplicationRecord
# ...
has_many :packages_in_destinations
has_many :packages, through: :packages_in_destinations
end
# app/models/packages_in_destination.rb
class PackagesInDestination < ApplicationRecord
belongs_to :destination
belongs_to :package
end

Delete an element from a collection

I'm facing to a stupid problem. I have created a collection select which is creating elements into a join table "staffs_task" to reference an association between the model staff and task.
And now I would like two things: (1) a button delete this association (2) and a little bit of code for my model staffs_task to avoid duplication, so with the task_id and staff_id. And last info, task is a model built by ranch
my code:
(the collection in new_task)
<%= select_tag "staffs_task", options_from_collection_for_select(#staffs, 'id', 'name') , :multiple => true %>
(task_controller)
skip_before_action :configure_sign_up_params
before_action :set_ranch
before_action :set_task, except: [:create]
def create
#task = #ranch.tasks.create(task_params)
#staffs = Staff.where(:id => params[:staffs_task])
#task.staffs << #staffs
if #task.save
#task.update(done: false)
#task.update(star: false)
flash[:success] = "The task was created "
else
flash[:success] = "The task was not created "
end
redirect_to #ranch
end
private
def task_params
params.require(:task).permit(:content, :deadline, :row_order, :date, :assigned_to)
end
def set_ranch
#ranch = Ranch.find(params[:ranch_id])
end
def set_task
#task = #ranch.tasks.find(params[:id])
end
So if you have any idea about one of this two things, your help would be welcome
Thanks in advance !!
Lets say you have the following many to many setup with a join model:
class Staff
has_many :assignments
has_many :tasks, through: :assignments
end
class Task
has_many :assignments
has_many :staff, through: :assignments
end
class Assignment
belongs_to :task
belongs_to :staff
end
Note that the plural of staff is staff - unless you are talking about the sticks carried by wizards.
ActiveRecord creates "magical" _ids setters for all has_many relationships. When used with a has_many through: relationship rails is smart enough to just remove the rows from the join table.
You can use this with the collection_select and collection_checkboxes methods:
<%= form_for([#task.ranch, #task]) do |f| %>
<%= f.collection_select(:staff_ids, Staff.all, :id, :name, multiple: true) %>
<% end %>
You would then set your controller up like so:
def create
#task = #ranch.tasks.new(task_params) do |t|
# this should really be done by setting default values
# for the DB columns
t.done = false
t.star = false
end
if #task.save
redirect_to #ranch, success: "The task was created"
else
render :new, error: "The task was not created"
end
end
private
def task_params
params.require(:task)
.permit(:content, :deadline, :row_order, :date, :assigned_to, staff_ids: [])
end
staff_ids: [] will allow an array of scalar values. Also not that .new and .create are not the same thing! You where saving the record 4 times if it was valid so the user has to wait for 4 expensive write queries when one will do.

rails - association params selected, but params "changing" on persist

I really am not sure of how to describe this problem and I've never had an issue like this before. I am trying to set up a "has_many :through" association that handles membership requests to a group. The other actions (destroy, update) seem to be working fine. Whenever I initiate the create action in my controller, the object shows that it is being passed the correct params and the association is created. However, it is creating an association only to the group with the "1" id. I have no idea how to clearly explain this problem or solve it. I'll post my log and code below.
To clarify my question: why is the data "changing" when it persists to the database (on create)?
Cliqs = Groups
Log:
Started POST "/cliqs/2/cliq_requests" for ::1 at 2016-03-31 20:35:32 -0500
Processing by CliqRequestsController#create as HTML
Parameters: {"authenticity_token"=>"uaVHFgB7digMywl2a/n2GKMtwi691WA/dw2F2mzdkSCK69C46TZICiSp90xldj3hosFwSOPEi3fSOvOSkIVMjA==", "cliq_id"=>"2"}
Cliq Load (0.0ms) SELECT `cliqs`.* FROM `cliqs` WHERE (2) LIMIT 1
User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 ORDER BY `users`.`id` ASC LIMIT 1
(0.0ms) BEGIN
SQL (1.0ms) INSERT INTO `cliq_requests` (`cliq_id`, `user_id`, `created_at`, `updated_at`) VALUES (1, 1, '2016-04-01 01:35:32', '2016-04-01 01:35:32')
(198.0ms) COMMIT
Redirected to http://localhost:3000/cliqs
Completed 302 Found in 237ms (ActiveRecord: 199.0ms)
Controller Action:
def create
#cliq = Cliq.find_by(params[:cliq_id])
#cliq_request = current_user.cliq_requests.new(cliq: #cliq)
if #cliq_request.save
redirect_to cliqs_path
else
redirect_to current_user
end
end
Other Actions (just in case):
def update
#cliq = Cliq.find_by(params[:cliq_id])
#cliq_request = CliqRequest.find(params[:id])
#cliq_request.accept
end
def destroy
#cliq = Cliq.find_by(params[:cliq_id])
#cliq_request = CliqRequest.find(params[:id])
#cliq_request.destroy
if #cliq_request.destroy
redirect_to cliqs_path
else
redirect_to current_user
end
end
And the Models:
class User < ActiveRecord::Base
has_one :owned_cliq, foreign_key: 'owner_id', class_name: 'Cliq', dependent: :destroy
has_many :cliq_memberships, dependent: :destroy
has_many :cliqs, through: :cliq_memberships
has_many :cliq_requests, dependent: :destroy
end
class Cliq < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
has_many :cliq_memberships, dependent: :destroy
has_many :members, through: :cliq_memberships, source: :user
has_many :cliq_requests, dependent: :destroy
has_many :pending_members, through: :cliq_requests, source: :user, foreign_key: 'user_id'
end
class CliqRequest < ActiveRecord::Base
#from
belongs_to :user
#to
belongs_to :cliq
def accept
cliq.members << cliq.pending_members.find(user_id)
destroy
end
end
Finally my View:
<h1><%= #cliq.name %></h1>
<%= link_to 'Request to join Cliq', cliq_cliq_requests_path(#cliq, #cliq_request), :method => :post %>
<% #cliq_members.each do |cliq_member| %>
<ul><%= link_to cliq_member.username, user_path(cliq_member) %></ul>
<% end %>
<h3>Cliq Requests:</h3>
<ul>
<% #cliq.pending_members.each do |pending_member| %>
<%= link_to pending_member.username, user_path(pending_member) %>
<% end %>
<% #cliq.cliq_requests.each do |cliq_request| %>
<%= link_to "Accept", cliq_cliq_request_path(#cliq, cliq_request), :method => :put %>
<%= link_to "Deny", cliq_cliq_request_path(#cliq, cliq_request), :method => :delete %>
</ul>
<% end %>
As you found out yourself, using #cliq = Cliq.find_by(id: params[:cliq_id]) works, and #cliq = Cliq.find_by(params[:cliq_id]) does not. So why is this the case?
The find_by method matches on conditions. Using find_by, you can match on any attribute. For example, this would also work:
#cliq = Cliq.find_by(some_attribute: "foo")
So using find_by, you must specify the id attribute in order for the query to return the correct record. Your find_by query was actually running SQL that looked something like this:
SELECT `cliqs`.* FROM `cliqs` WHERE (2) LIMIT 1
That select statement will return the whole table, and the LIMIT 1 just grabs the first record.
As a bonus, the preferred rails way to assign #cliq based on an params[:cliq_id] would be using just find, which searches for the record using its primary key:
#cliq = Cliq.find(params[:cliq_id])

Join table in rails; many-to-many; trying to call join method on instance

I just did this a few hours ago and now I can't getting it working on a second attempt. There are many Users and many EventGoals. Users have many EventGoals and EventGoals have many Users, both through the join table called EventGoalClaims. The #event_goal is getting passed properly but I get a `
undefined method `event_goal_claims' for #<EventGoal:0x007fd11b0ce178>`
Here's the view (the first line is hitting an error):
<%= form_for([#event_goal, #event_goal.event_goal_claims.build]) do |f| %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.hidden_field :user_last_name, :value => current_user.last_name %>
Controller
def create
#event_goal = EventGoal.find(params[:event_goal_id])
#event_goal_claim = #event_goal.event_goal_claims.build(eventgoalclaim_params)
#event_goal_claim.event_goal_id = #event_goal.id
#event_goal_claim.user_id = current_user.id
#event_goal_claim.user_last_name = current_user.last_name
...
private
def eventgoalclaim_params
params.require(:event_goal_claim).permit(:event_goal_id, :user_id, :user_last_name, :approved)
end
EDIT
class EventGoal < ActiveRecord::Base
belongs_to :event
has_many :users, through: :event_goal_claims
end
The answer likely lies in the associations of one of the three respective models. Make sure they look like this:
user.rb
Class User < ActiveRecord::Base
has_many :event_goal_claims
has_many :event_goals, through: :event_goal_claims
event_goal.rb
Class EventGoal < ActiveRecord::Base
has_many :event_goal_claims
has_many :users, through: :event_goal_claims
event_goal_claim.rb
Class EventGoalClaim < ActiveRecord::Base
belongs_to :user
belongs_to :event_goal
Also worth noting: This is all assuming your db has the foreign keys on the event_goal_claims table in your schema.rb labeled as user_id and event_goal_id. If they are different than that, you will need to explicitly state the name of the foreign key in the model's associations.