How To Dynamically Add and Remove Items with Rails
I previously wrote about a web application I created using Ruby on Rails. For this app, which I created with hui wang, we made a user show page which had all the events a user had added to their events.
When a user found an event they wanted to attend, they clicked the “Add to my events” button and them BOOM it appears on your show page. Then if you decide to not attend the event any more, click “Remove from my events” and it’s gone!
I spoke a little bit about how using Event Listeners in JavaScript can simplify this process, however that method doesn’t utilize the database at all. In my example here, adding events is not just client side but also stored in the database as one of the user’s events.
To have this functionality we needed to have a way for the buttons to add the specific event instance to the user’s events.
We achieved this functionality through class associations
In the case of our app, we used the following relationships for the events and users classes
class User < ApplicationRecord
has_many :user_events
has_many :events, through: :user_events
[...]
endclass Event < ApplicationRecord
has_many :user_events
has_many :users, through: :user_events
[...]
end
This establishes a classic has_many through relationship between Users and Events. Now a User doesn’t need to worry about keeping track of their Events and Events don’t need to keep track of all their Users. Instead the UserEvent class handles that:
class UserEvent < ApplicationRecord
belongs_to :event
belongs_to :user
end
Now, we can access a Users events by typing User.events
and you can see all the events attached to the User. Similarly you can do Event.users
and see all the Users attending. This is great and we definitely used it to our advantage.
Next we created methods that could add or delete events from a User.
These methods were added to the User class since they would be added and deleted from the User and not adding or deleting the actual Event instance. The code for this was fairly simple:
def add_event(event)
if !self.events.include?(event)
self.events << event
end
end def delete_event(event)
if self.events.include?(event)
self.events.delete(event)
end
end
Here we are taking the specific Event instance and shoveling it into the User.events array but ONLY if it does not already exist in their events. This is to prevent duplicate events from being in the User’s events.
After the methods are working, add actions to the controllers
This part was a little tricky. We had to Macgyver our actions a bit, so try not to judge. In our Events controller we added the addevent
action:
def addevent
event = Event.find(params[:id])
current_user.add_event(event)
current_user.save
redirect_to user_path(current_user)
end
Then in our Users controller we added the deleteevent
action:
def deleteevent
event = Event.find(params[:id])
current_user.delete_event(event)
current_user.save
redirect_to user_path(current_user)
end
Why were the responsibilities separated like this? Because of our routes 🙄
post '/events/:id', to: 'events#addevent', as: 'addevent'
post '/users/:id', to: 'users#deleteevent', as: 'deleteevent'
Since we were doing POST actions to the same URL (the User), add and delete were not functioning. So we channeled the add event action to the Events controller and delete to the Users controller. This made it so events could be added and deleted. Not ideal but it worked. Suggestions for improvement welcome!
With all the backend functionality up and running, we need to make it visible for the User
This was done through our Views and in a number of different ways. Like I said before, this wasn’t the most DRY code but it did what we wanted!
In the code, I included the “All Events” show page which put every event in every city on the page. To get our buttons to actually utilize the methods and actions we’d written we had to use a little Rails automagic. The most important part of the code is here:
<%= link_to 'Add to my events', addevent_path(event), :method => :post, class: "btn btn-outline-primary"%>
Here we created a link that utilized the addevent_path
of this specific event
(specified above in the loop through all events) through the :post
method. Bootstrap was used for styling to that’s what the class:
is.
Now, when the button is clicked the post request is sent through the addevent_path
in the Events controller. In the controller the add_event
method is called which adds the event to the User’s events array.
With this, a user could add an event! Hallelujah!
For removing an event, we followed a similar structure. Here’s the code from our User show page where you can either ADD or REMOVE events:
<% if current_user == @user %> <%= link_to 'Remove From My Events', deleteevent_path(event), :method => :post, class: "btn btn-outline-danger"%><% else %> <%= link_to 'Add to my events', addevent_path(event), :method => :post, class: "btn btn-outline-primary"%> <%end%>
Again, when the button is clicked the post request is sent through the deleteevent_path
in the Users controller. In the controller the delete_event
method is called which deletes the event from the User’s events array.
And with that, you can dynamically add and remove items from a Users database 🎉