Kickflip is a web app built for for skateboarders to use to track and find local skateparks, show their friends when they get a new trick, and encourage return use with a “checkin” feature. Kickflip is built using Sinatra and AciveRecord.
Project Architecture
Kickflip is primarily built with the sinatra
ruby gem and uses the “MVC” app architecture (Model, View, Controller) inside the app
directory. Kickflip also makes use of the activerecord
gem to persist data in sqlite3
.
Most models are connected to their corresponding table using ActiveRecord::Base
, with the exception of the Search
class. Parks
and Users
have a many-to-many relationship through SkateSessions
, while Users
relate to Tricks
with a has-many relationship through UserTricks
Gems
Kickflip also takes advantage of the geocoder gem, which extends a Class and returns latitude
and longitude
from street addresses. With the geocoder
gem, a user can find a Park
near a location without the instance containing the specific string in it’s address.
This gem becomes especially useful when searching for parks in a geographical area that would otherwise not return any results. For example, there are no Parks
that are located in “Bountiful, UT”. Instead of returning no results, the query “Bountiful, UT” is transformed into latitude and longitude coordinates (69.0933, -111.8805) and returns results of “Fairmont Skate Park” located in “Salt Lake City, UT”
The application also uses sinatra-flash
to display error and success messages when the user modifies data or attempts to access pages they are not authorized to use.
RESTful Conventions
All controllers adhere to RESTful conventions (Representational state transfer) with a few exceptions:
- The
get '/signup'
route is used instead ofget '/user/new'
- SkateSessions use the
get '/users/:id/skate-sessions'
route route as aSkateSession
always belongs-to aUser
. Tricks
are not represented by a route because they can not be modified by aUser
.UserTricks
do not have a route because they are displayed on on aUser
page.
Models
Because Parks
and Users
have a many-to-many relationship, both models have similar instance methods to return either the top users or top parks related to each other. An additional method is included in each class, top_x_users(x)
and top_x_parks(x)
where the argument of x can be changed to return the desired number of results.
This method is especially useful to front end users in displaying “King of the Park” where the top User
at a Park
gains a special badge displayed on their profile and the Park
page.
The User
class contains additional instance methods: most_recent_skate_session
, minutes_since_most_recent_skate_session
, and can_record_skate_session
. These methods are used together with the SkateSession.timeout
to determine if enough time has passed for a User
to record a new SkateSession
.
If not enough time has passed, the checkin button is removed and replaced with a message saying “Wait 5 minutes before you can checkin again”.
Sessions
Kickflip allows users to log in and out by enabling sessions
in the Application Controller
. Together with several helper methods, buttons and content are dynamically generated on a number of variables.
When not logged in or viewing another User
’s page, there are no edit buttons present.
However, when viewing a user is viewing their own page and the user is logged in, they are presented with both “Edit My Info”, and “Edit My Checkins” buttons.
Further, if a user attempts to access a route they are not authorized to, they are rerouted and presented with an error message using sinatra-flash
.
The redirect_if_user_not_authorized
method checks if the passed in user.id
has the same session[:user_id]
.
def redirect_if_user_not_authorized(user)
if user.id != current_user.id
flash[:error] = "You can not edit someone else's data."
redirect "/"
end
end
##Security and Password Encryption
Passwords are salted and hashed through has_secure_password
and stored in the database in their encrypted form in the password_digest
column. When logging in, the password
param is encrypted with the .authenticate
method and compared against the encrypted password stored in the database. This way, the user’s password is never exposed to the browser.
post '/login' do
user = User.find_by(email: params["email"])&.authenticate(params['password'])
if user != nil
session[:user_id] = user.id
flash[:success] = "Welcome back to Kickflip, #{user.username}!"
redirect "/users/#{user.id}"
else
flash[:error] = "Invalid login."
redirect "login"
end
end
Resources
Feel free to check out Kickflip on my Github or give me a follow on Twitter to continue following my coding journey.
Kickflip is licensed with a BSD 2-Clause License.