Created as part of the Odin Project curriculum. View live page.
For full functionality sign up or login with odin@example.com and password.
Logged in users can create, edit, and invite/uninvite other users to their own events. Users can change their RSVPs to upcoming invitations, but not to past ones.
Event hosts can only uninvite users who have not yet confirmed their attendance and can only update the invitation list for upcoming events.
One of the main challenges I ran into was deciding how to allow event hosts to invite guests. With my current form-related skillset a checkbox seemed to be the best option, but ideally there would be a text field that autocompletes from the database of users. I struggled for a long time to try for an efficient way of prechecking the box of any user who was already invited:
User.order(:username).includes(:invitations).each do |user|
{ checked: user.invitations.any? { |inv| inv.event_id == @event.id } } # Ruby; no index
User.order(:username).includes(:invitations).each do |user|
{ checked: user.invitations.exists?(event: @event) } # n+1 query
User.all.order(:username)
.includes(:invitations)
.where('invitations.event_id = ?', @event.id)
.references(:invitations).each #where clause filters users, not invitations
{ checked: @event.invitations.exists?(recipient_id: user.id) }) # n+1 query
{ checked: @event.invitations.any? { |inv| inv.recipient_id == user.id } } # Ruby; no index
temp_invitations = @event.invitations.pluck(:recipient_id)
{ checked: !!temp_invitations.delete(user.id) } # much faster than anything else I tried
My eventual solution has the advantages of only making one query, storing a relatively lightweight array of recipient_id
integers, and diminishing average search times as found elements are deleted from the array. It was much faster than any alternative I tried. Still, it doesn't feel like the most elegant solution.
Another big disadvantage of the checkbox approach for invitations was that I need to handle every invitation for an event any time its invitation list is updated, since there's no way to know which ones changed and target only those. For a large database of users this implementation would need to improve.
I had to use user.username
instead of f.label(user.id, user.username)
for the checkbox labels in order to get them to display inline.
I used a scaffold generator for the Event
model, which proved helpful and educational.
For this project I was a little pickier about the styling, and decided I didn't want the field-with-error
class applied on label
elements. To avoid that particular piece of Rails magic I just went with the raw html <label for='event_name'>Name</label>
instead of <%#= form.label :name %>
.
I wrote tests for spec/controllers/events_controller_spec.rb
, and spec/models/*
using the shoulda-matchers
, rails-controller-testing
, and factory-bot-rails
gems. In projects where I wrote my own authorization I had been using this helper method to stub methods related to authentication:
def login(user)
allow_any_instance_of(ApplicationController).to receive(:current_user) { user }
allow_any_instance_of(ApplicationController).to receive(:authenticate_user!).and_return(true)
end
With Devise, however, I just needed to add these lines to spec/rails_helper.rb
:
config.include Devise::Test::IntegrationHelpers, type: :request
config.include Devise::Test::ControllerHelpers, type: :view
config.include Devise::Test::ControllerHelpers, type: :controller
Then I could use the simple method Devise provides: sign_in user
.
Because rspec-rails
was already bundled when I generated the Event
scaffolding, a bunch of tests were generated in the spec
folder. I decided to look into these and make them pass as well since they contained techniques I hadn't seen before.
I didn't encounter too many snags here, but getting syntax that worked for stuff like assert_select "div", /Nov 11 2022 12:00am/, count: 1
in the view specs took me a while.
I was also getting this quirky error with the views:
ActionView::Template::Error:
Error: Function rgb is missing argument $green.
on line 465 of stdin
>> background-color: rgb(216 216 216);
The issue was the missing alpha value in application.css
(which was not actually an issue outside the view tests), but I was confused about line 465 and mentions of stdin, which didn't correspond to anything in my project folder but were probably results of the Asset Pipeline.
Things you may want to cover:
A project completed as part of The Odin Ruby on Rails Learning Track to dive into ActiveRecord’s associations. The project involves building a private website with similar functionality to the well known event organization and management platform Eventbrite.
You can try it out here
HEADS UP: Heroku server may need up to 30 sec to fire up a dyno. Be patient! :)
You can use 'Fred Flintstone', 'Alice', 'Mickey Mouse', 'Homer Simpson' or 'SpongeBob' as test users to see full functionality.
For those who are not patient, here are a couple of screenshots of what it looks like
As far as this is a training app with focus on ActiveRecord's associations, User authentication and authorization are extremely barebone with no validations or real security of access: no need for a password, anyone is able to sign in/sign up through a basic hand-rolled authentication by their name. After the registration/login they're able to create events, invite other users as well as to enroll for events organized by others. Always because it's an exercise, users can create and enroll for the events with the past dates to practice rails' scopes. In a similar vein, just strictly necessary RESTful actions were implemented in the controllers: for example, you can't edit/delete users/events. Nevertheless, the styling was not requested, I built a minimalistic design using bulma
gem, a CSS framework based on Flexbox.
To get started with the app, make sure you have Rails and Git installed on your machine
Get instructions
Clone the repo to your local machine:
$ git clone https://github.com/Pandenok/private-events.git
Then, install the needed gems:
$ bundle install
Next, migrate the database:
$ rails db:migrate
If you want to load sample users and events, use seeds:
$ rails db:seed
Finally, on root path run a local server:
$ rails server
Open browser to view application:
localhost:3000
This was an awesome rundown practice and I had a really joyful fun playing with associations, until I bumped into extra credit on allowance to invite other users.
Spoiler alert! Click to continue reading...
In the beginning, I added an Invitations
model to my app, because in real world invitation
and enrollment
are two conceptually different things. And I made it work with this setup. IMHO, two join models looked very similar:
class Enrollment < ApplicationRecord
belongs_to :event
belongs_to :user
end
class Invitation < ApplicationRecord
belongs_to :event
belongs_to :host, class_name: "User"
belongs_to :invitee, class_name: "User"
end
So, I decided to go with one model, got rid off Invitations
and put everything in Enrollment
.
class Enrollment < ApplicationRecord
belongs_to :event
belongs_to :user
belongs_to :host, class_name: "User"
belongs_to :invitee, class_name: "User"
end
As far as User
invites other User
, it felt right to apply self joins, so I created something like
class User < ApplicationRecord
has_many :events
has_many :enrollments
has_many :attended_events, through: :enrollments, source: :event
has_many :invited_users, foreign_key: "host_id", class_name: 'Enrollment'
has_many :invitees, through: :invited_users
has_many :hosting_users, foreign_key: "invitee_id", class_name: 'Enrollment'
has_many :hosts, through: :hosting_users
end
And to my surprise associations were working! As far I got rid off Invitation
controller I had no idea on how to split enrollment
and invitation
creation, what routes to use, etc. At this point I tried different things and finished asking support to the forum. @GopherJackets was really helpful in the understanding of my model set up and cleaning it up. Then the conversation with @nalyk inspired me to find an interesting solution: when a creator of the event sends an invitation, it triggers the creation of a new enrollment
joining only invitee_id
and event_id
(I had to set user_id
as optional
to make it work). When the invited user accepts the invite by enrolling to the event, it was updating an existing enrollment
with his id
in user_id
column. The only issue was on how to drop an enrollment without canceling the invitation. And here I had an eye-opening talk with @Roli who not only explained how to fix the bug, but helped me polish completely the app and adviced to use enum status
, which makes the extra task super easy. You're seeing the final result of Data model.
[Collaborative project]
This assignment consists of using the Devise gem to have a first approach at authentication in rails. I was able to build an application that allows users to create posts and the authors of the posts are displayed only if as a User you are logged in. More like an exclusive clubhouse where your members can write anonymous posts. Inside the clubhouse, members can see who the author of a post is but, outside, they can only see the story and wonder who wrote it.. Find project specifications here
- Ruby
- Devise
- Ruby on Rails
- webpack
- Heroku
- Sqlite
- MVC pattern
- Node.js -Yarn
To get a local copy up and running follow these simple example steps.
- Vscode
- Heroku CLI
- Terminal
- Linters Test
- Rubocop style guide
- Open your terminal and locate the folder you want to clone the repository and follow the steps below to install
Run the following command into your terminal:
git clone https://github.com/errea/member_only-rails_app.git
gem bundle install --without production
run rails db:migrate to migrate files
├── README.md
├── bundle
│ └── main.rb
└── .github\workflows
└── linters.yml
└── app
└── assets
└── channels
└── controllers
└── helpers
└── jobs
└── mailers
└── models
└── views
└── bin
└── config
└── db
└──log
└── bin
└── public
└── storage
└──test
- Git clone this repo and cd the to the
member_only
directory. - Run
rails server
in command line to open the application server in your browser via http://localhost:3000 or something similar - Run
heroku start
. - heroku run
- heroku run rails db:migrate
- git push heroku main
- heroku run console
👤 Eri
- Github: @errea
- Twitter: @Erreakay
- Linkedin: Eri Okereafor
Contributions, issues and feature requests are welcome!
Feel free to check the issues page.
Give a ⭐️ if you like this project!
- Microverse
This project is MIT licensed.