Time flies! Keep on learnin'
Whoa! I said I'd try to finish my current project last Wednesday (9/17), but I guess 8 days late is better than never! The review while building this simple Twitter clone has been great, and while there's so much more that I wanted to do, there comes a point in every project where you simply need to finish that last feature and move on. In this post, I'm going to skim through this project and share key points that I feel are worth reminding myself of later down the road. Hopefully I can write this up in a reasonable amount of time and get back to coding!
But first, a quick look at the transformation...
Before...

After!

I wanted to keep the design simple for now, and spend most of the time focusing on back-end familiarity. And boy, did that take some time. There are only a few models here, namely: User, Tweet, Retweet, but where I ended up spending a lot of time is trying to get fancy with ActiveRecord to use a single model to represent several other models. For instance, I wanted to use User to also represent Follower and Following ("a user has many followers, and a user can follow many other users (followings)"). There was a great RailsCast that I used to learn more about Self-Referential Associations, though it seemed to focus on a single self-referential association, and I was looking at having two (follower, following). The naming got a bit tricky, but I was able to get it working. This logic also applied to Tweet, because I wanted to use this to represent Retweets, which was easier.
Here are some code snippets and notes from Tweetito:
ActiveRecord Associations
class User < ActiveRecord::Base
# via StackOverflow
# In Rails 4, :order has been deprecated and needs to be
# replaced with lambda scope block. Note that this scope
# block needs to be passed before any other association
# options such as dependent: :destroy etc.
has_many :tweets, -> { order(created_at: :desc) }
has_many :retweets
# Self-Referential Association, via RailsCast 163
has_many :follows
has_many :followings, :through => :follows
has_many :inverse_follows, :class_name => "Follow", :foreign_key => "following_id"
has_many :followers, :through => :inverse_follows, :source => :user
# Adds methods to set and authenticate against a BCrypt password.
# This mechanism requires you to have a password_digest attribute.
# Validations for presence of password on create, confirmation
# of password (using a password_confirmation attribute) are
# automatically added
has_secure_password
end
#reminder for password_digest in table
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :username
t.string :email
t.string :password_digest
t.timestamps
end
end
end
class Follow < ActiveRecord::Base
belongs_to :user
belongs_to :following, :class_name => "User"
endSass (nesting, mixins)
.top-nav {
@include box-sizing;
width: 100%;
padding: 5px;
border-bottom: 1px solid $background-dark-2;
font-size: 20px;
background-color: $off-white;
display: inline-block;
.right {
float: right;
display: inline-block;
a {
margin-left: 20px;
}
}
}
@mixin headers-and-tweets {
background-color: $off-white;
border-radius: 15px 0;
border-bottom: 1px solid $background-dark-2;
border-right: 1px solid $background-dark-2;
}
@mixin new-session-and-user-form {
@include headers-and-tweets;
padding: 10px 0;
}
// Helpful BOX-SIZING explanation here:
// http://css-tricks.com/almanac/properties/b/box-sizing/
// Used to keep the nav bar within the page.
// Without box-sizing, the link furthest right spilt off the page
@mixin box-sizing {
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
-moz-box-sizing: border-box; /* Firefox, other Gecko */
box-sizing: border-box; /* Opera/IE 8+ */
}Helper methods and before_actions
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :require_login
helper_method :current_user, :logged_in?
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def logged_in?
!current_user.nil?
end
private
def require_login
unless logged_in?
flash[:error] = "You must log in first"
redirect_to new_session_path
end
end
endSessions
class SessionsController < ApplicationController
skip_before_action :require_login, only: [:new, :create]
def create
# The line below works, and will return either false, or the User record
# user = User.find_by(email: params[:email]).try(:authenticate, params[:password])
# Here is a solution from RailsCasts using #authenticate, which is
# an instance method provided by has_secure_password
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to user, notice: "You've successfully logged in."
else
redirect_to root_path, notice: "Log in failed."
end
end
def destroy
session[:user_id] = nil
redirect_to root_path, notice: "You've successfully logged out."
end
endRestrict Access
# TweetsController
# User can not tweet from another user's page (or more specifically w/another user's ID)
def new
@user = User.find(params[:user_id])
if @user.id != current_user.id
redirect_to user_path(current_user), notice: "Access forbidden"
end
@tweet = Tweet.new
endEager-loading (still needs some work...)
def show
# is this the correct way to eager load for a single record?
@user = User.includes(:tweets).find(params[:id])
# figure out good way to include :retweets as well!
endForms for nested resources
<div class="new-tweet-form">
<%= form_for [@user, @tweet] do |f| %>
<%= f.text_area :body, value: @tweet.body, size: "30x10", required: true %>
<%= f.submit "Tweet" %>
<% end %>
</div>CRINGE-WORTHY that nesting... :(
<!-- LIST OF TWEETS -->
<div class="tweets-list">
<% if @user.tweets.any? %>
<h3>Tweets</h3>
<% @user.tweets.each do |tweet| %>
<a href="<%= user_tweet_path(tweet.user, tweet) %>">
<div class="tweet">
<li class="timestamp"><%= tweet.created_at.strftime("%m/%d/%Y") %></li>
<li><%= tweet.body %></li>
</div>
</a>
<% end %>
</div>
<% else %>
<h3>No Tweets</h3>
<% end %>
</div>Here are a few misc. screenshots from various resources that I found useful during this project:
Destroy a generated Controller and all associated files

hasmany :through VS hasandbelongsto_many

@mixin VS @extend (Sass)

Well, that was fun (and exhausting!). I have a few ideas as to where I want to take my studying next, but for now, food and more coffee!

currently listening to Mat Zo & Porter Robinson - "Easy"