First, a quick introduction
I struggle with these blogs. For those not following along at home, I'm doing a development bootcamp; one of the requirements is to do a technical blog after each milestone project. It's a struggle for me because I've been coding in earnest for all of 3 months now; what could I have to say that anyone would, could, or should care about?
I reached out to some of my friends in the program and asked them to have a look at my project, OpenStage (be patient if it takes a moment to spin up, it's hosted on heroku) and tell me if there was anything they wanted to know about. My friend Bella said she'd like to know more about how I managed the invitations to bands, so that's what this blog will be about. At least I know there's one person who will think it's interesting.
Invites?
Yeah, so my app has a few goals.
- Help musicians form bands
- Help bands find open stage time to play casual gigs
- Help venues draw traffic by having bands play
To have bands, which are made of multiple Muscians (users of class User in my models), you need to be have a join table. There are a number of questions to be asked about how to handle who has privileges in a band, but for this demonstration I decided the user that created the band (the band leader) would have the unilateral power to request gigs and invite members. We'll need a join table so that users can have many bands and bands can have many users, and we've called this join table band_members.
class BandMember < ApplicationRecord
belongs_to :user
belongs_to :band
validates_uniqueness_of :user_id, scope: :band_id
end
class User < ApplicationRecord
has_secure_password
has_many :lead_bands, class_name: "Band", foreign_key: :leader_id
has_many :band_members
has_many :bands, through: :band_members
has_many :gigs, through: :bands
validates :name, presence: true
validates :email, presence: true
validates :email, uniqueness: true
end
class Band < ApplicationRecord
belongs_to :leader, class_name: "User", foreign_key: :leader_id
has_many :band_members
has_many :users, through: :band_members
has_many :gigs
validates :name, presence: true
def empty_roster
self.band_members.each do |band_member|
band_member.delete
end
end
def unclaim_all_gigs
self.gigs.each do |gig|
gig.band_id = nil
gig.status = nil
gig.save
end
end
def clear_associations_before_deletion
self.empty_roster
self.unclaim_all_gigs
end
end
Wait, I thought you were going to talk about your invite model?
There's no invite model. Let me explain.
create_table "band_members", force: :cascade do |t|
t.integer "band_id"
t.integer "user_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "status"
end
There's no invite model, because a band_member entry with status= "Pending" is an invite.
class UsersController < ApplicationController
...
def invite_member
user = User.find(params[:user_id])
invite = BandMember.new(user_id: params[:user_id], band_id: params[:band_id], status: "pending")
if invite.save
redirect_to user, notice: "Invite sent."
else
redirect_to user, notice: "Either something went wrong, or this user is already in that band. Please try again later."
end
end
def invites
end
def accept_invite
invite = BandMember.find(params[:id])
invite.status = "Accepted"
if invite.save
redirect_to invite.band, notice: "You've joined #{invite.band.name}."
else
redirect_to invites_path, notice: "An error occured, please try again later."
end
end
...
end
"Invites" are just BandMember records with status "Pending", which, when initially created, all BandMember records are (with the exception of the record that is created for the band leader, which happens when the band is created). A user has to post to the accept invite path to change the status of the record to "Accepted"
Users interact with these controller actions from the views:
views/users/show.html.erb
...
<% if current_user_is_band_leader? && current_user.id != @user.id %>
<div class='invite-wrapper'>
<%= form_with(url: 'bands/band_members/new', method: 'post') do |form| %>
<%= form.hidden_field :user_id, value: @user.id %>
<%= form.label "Invite to:" %>
<br>
<%= form.collection_select(:band_id, current_user.lead_bands, :id, :name) %>
<br>
<%= form.submit "Invite", class: 'button' %>
<% end %>
</div>
<% end %>
...
When viewing a musicians show page, if the viewing user is a band leader, they get a form that's just a drop-down with the bands they lead and a button that says "Invite".
The user then has an "invite" so when they view the invites path, they see this:
If they accept, the form is submitted to the accept invite path, and the entry for the associated BandMember record is updated, and those updates are persisted.
The user is redirected to the page for the band that they've joined, and a button to leave the band is now present, so that they have the ability to un-join, as it where.
And that is, more or less, the life-cycle of an invite in this model. A user can't decline an invite, they can only let it sit unaccepted, because frankly figuring out how to build a protection against invite-again-on-rejection-abuse seemed like a big hassle to include in the MVP.
Top comments (0)