My Daily Coding Blog

June

June 29 2017

The Odin Project Blog Post 21

June 27 2017

Week 25 - “Has_many :through” Associations

These past couple weeks have been spent building the most complex project to-date. It is a Private Events application where a user can sign up, create events, and attend other events. Sounds simple on the surface, but at the database level is where it gave me a lot of trouble. Up until now, I’d only dealt with two related tables in my model. A User has_many :posts or a Post has_many :users etc. For this project, a User can create(or host) many events, a User can attend many events, and an Event can be attended by many users. This specific kind of association is called a has_many :through relationship because it requires a third database table to relate the users and events through. Then when I finally got it working in my development environment, it didn’t work on Heroku so I had to almost start all over for the production environment. Needless to say this was a time-consuming and frustrating, but rewarding learning experience. I encountered a problem at every stage of the process from development, to version control, to Heroku deployment, but persisted until the job was complete. Here is my attempted explanation of has_many :through:

First and foremost, I know I need a different type of association for this application because my Events are going to have different types of User (creator and attendee) associations, and my Users are going to have different types of Event (event and attended_event) associations. Therefore, I need to let Rails know which one I am asking for via my models. That's where the through table comes in to play. It acts as a join table and creates new columns to be referenced. In this case, an attendee_id column for the User class and an attended_event_id column for the Event class.

/app/models/user.rb
class User < ApplicationRecord
    has_many :events, :foreign_key => "creator_id"
    has_many :attendances, :foreign_key => :attendee_id
    has_many :attended_events, :through => :attendances, :source => :attended_event

/app/models/event.rb
class Event < ApplicationRecord
    belongs_to :creator, :class_name => "User"
    has_many :attendances, :foreign_key => :attended_event_id
    has_many :attendees, :through => :attendances, source: :attendee

/app/models/attendance.rb
class Attendance < ApplicationRecord
    belongs_to :attendee, :class_name => "User"
    belongs_to :attended_event, :class_name => "Event"
end

Looking at the code above, let's begin with the Attendance model. We associate it with the User model with a belongs_to :attendee reference. Up in the User model, we accept this relationship with the has_many :attendances, :foreign_key => :attendee_id reference. The foreign_key is essential because without it Rails would be looking for a column named "attendance_id". The same associations are made for Attendance and Event but with attended_event and attended_event_id. Now that our original tables are connected to the new through table, they need a connection to each other. User gets has_many :attended_events, :through => :attendances, :source => :attended_event while Event gets has_many :attendees, :through => :attendances, source: :attendee because remember - attended_event's class_name is Event within the attendance model while attendee's class_name is User. The source option at the end of these commands is just like class_name but for irregularly named associations in the through table.

With these associations properly set up, I was able to use constructs like @event.attendees in my controllers and views, make model methods using syntax such as self.attendeded_events and self.attendances.create, and generate an attendances controller that used this method current_user.attendances.build.

June 26 2017

June 20 2017

June 14-16, 19 2017

June 13 2017

The Odin Project Blog Post 20

June 12 2017

Week 23 - Rolling My Own Authentication

After a busy couple of weeks with illness and traveling and Hartl tutorials, I’m back to writing a blog post about rolling my own authentication system with Ruby on Rails. This past week I created a full-fledged app from scratch with the most autonomy that TOP has given it’s users as of yet. It was challenging, but not overly, and segments are still puzzling me, but there is still a ways to go in the Rails path so I expect those gray areas to become clearer with more practice. There is a lot to get into here so let’s not waste time:

After I created my app, the first step was to set up the User model. That means validations. For my Members Only app, I wanted a site that allows users to see and create posts, as well as see who the authors of the posts are, but does not allow non-users to create posts or see the authors of posts. So I used the basic model from a previous exercise and the same validations for name, email, and password_digest.

 /app/models/user.rb

class User < ApplicationRecord
    before_save { email.downcase! }
    validates :name, presence: true
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
    validates :email, presence: true, length: { maximum: 25 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
    has_secure_password
    validates :password, presence: true, length: { minimum: 6 }

Next, I set up the sessions controller for signing in users. Things started to get more complex here. I had two methods within the sessions controller - new and create - and my form looked like this:

 /app/views/sessions/new.html.erb

<h1>Sign In</h1>

<div class="row">
 <div class="col-md-6 col-md-offset-3">

    <%= form_for(:session, url: signin_path) do |f| %>

     <%= f.label :name %>
     <%= f.text_field :name, class: 'form-control' %>

     <%= f.label :email %>
     <%= f.email_field :email, class: 'form-control' %>

     <%= f.label :password %>
     <%= f.password_field :password, class: 'form-control' %>


     <%= f.submit "Sign in", class: "btn btn-primary" %>

     <% end %>

    </div>
</div>

First off, sessions#new routes to /signin; that's why form_for needs to be directed there. Now for the first tricky part: setting up the user’s remember_token in ActiveRecord. I needed to add a remember_digest migration, create a remember_token attribute accessor, create a remember method to digest the token, and save it for my user. Luckily, I had already done something almost identical in the Hartl tutorial. This is what it looks like in the end:

 /app/models/user.rb

class User < ApplicationRecord
    attr_accessor :remember_token
    before_save { email.downcase! }
    validates :name, presence: true
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
    validates :email, presence: true, length: { maximum: 25 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
    has_secure_password
    validates :password, presence: true, length: { minimum: 6 }

    def User.digest(string)
        Digest::SHA1.hexdigest(string)
    end

    def User.new_token
        SecureRandom.urlsafe_base64
    end

    def remember
       self.remember_token = User.new_token
       update_attribute(:remember_digest, User.digest(remember_token))
    end

end

The remember method takes the remember_token attr_accessor and passes it to my User.new_token method where Secure.Random_urlsafe_base64 turns it into a random string. Remember then updates the :remember_digest attributes by passing this random string to the User.digest method and encrypting it with the Digest::SHA1.hexdigest method.

With my users' remember_token safely encrypted in the database, I moved onto the sessions creation function. In order to authenticate the user, the params method checks the email from the form, and compares it to the hashed password that’s been stored in the database for that user. If we have a match, a method within the application_controller signs them in. This sign_in function uses Rails’ session method to place a temporary cookie on the browser containing an encrypted version of their user id, which allows that user to traverse subsequent pages of the site and remain logged in. However, the session expires immediately when the browser is closed, and we want a permanent cookie that lasts until the user signs out. Thus, a second remember function was necessary to store the user id in a cookie. The end result can be seen here:

 /app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  # Logs in given user
  def sign_in(user)
      session[:user_id] = user.id
  end


  # Remembers given user in persistent session
  def remember(user)
     user.remember
     cookies.permanent.signed[:user_id] = user.id
     cookies.permanent[:remember_token] = user.remember_token
  end

Now for the hardest part for me to wrap my head around: the current_user methods. These are necessary for retrieving your logged in user on subsequent pages and referencing the current user in other methods which we’ll see later. The first method retrieves a @current_user instance variable from the database by finding the logged in user’s encrypted user id.

# Returns current signed-in user
def current_user
  if session[:user_id]
    @current_user ||= User.find_by(id: session[:user_id])
  elsif cookies.signed[:user_id]
    user = User.find_by(id: cookies.signed[:user_id])
    if user && user.authenticated?(:remember, cookies[:remember_token])
      sign_in user
      @current_user = user
    end
  end
end

And the second method just returns true if the given user is equal to the current user

# Returns true if the current user is the given user
def current_user?(user)
      user == current_user
  end

With sign_in, remember, and the current_user methods defined, my creation method is now complete. Let's take a look at my session controller:

 /app/controllers/sessions_controller.rb

class SessionsController < ApplicationController

  def new
  end

  def create
      @user = User.find_by(email: params[:session][:email])
      if @user && @user.authenticate(params[:session][:password])
          sign_in @user
          remember @user
          flash[:success] = "You're signed in!"
          redirect_to root_url
      else
          flash.now[:error] = "Invalid username/password. Try again."
          render 'new'
      end
  end

  def delete
     sign_out
     flash[:success] = "You're signed out!"
     redirect_to root_url
  end
end

We can see the first step within create is finding the @user by comparing the email params with the hashed password as explained earlier. But what is within the delete method? In order to successfully sign out a user, there are a few things that need to be "undone." Their sessions and cookies I created need to be deleted. So in other words, my site needs to forget who they are, and @current_user needs to be set to nil. I broke the sign_out method into two functions in the application controller:

  # End session and forget user
  def sign_out
      forget(current_user)
      session.delete(:user_id)
      @current_user = nil
  end

  # Delete cookies
  def forget(user)
      cookies.delete(:user_id)
      cookies.delete(:remember_token)
  end

Finally, to get the delete method to work on the site, I needed to use the code <%= link_to "Sign out", signout_path, method: :delete %>. The method: :delete being the unique part here. This is actually JavaScript within the Rails code because web browsers cannot issue DELETE requests.

And with that, my users can sign in, be remembered with a cookie, and sign out. The other half of the app was creating posts, but for length's sake I'll stop here. I now have the ability to "roll my own auth", but honestly I look forward learning to some of the gems that will do that lifting for me.

June 8 2017

  • Struggled to but eventually got sample data working on heroku for members-only app Members Only
  • Leetcode Integer Palindrome problem
  • def is_a_palindrome?(x)
      x == x.to_s.reverse.to_i
    end
    
    is_a_palindrome?(13231) => true
    is_a_palindrome?(123) => false
    

June 7 2017

June 6 2017

  • Read Rails Guide Active Record Querying
  • Read TOP's section on Advanced Active Record querying and polymorphism
  • Leetcode Reverse Integer problem
  • def reverse(x)
      arr = x.to_s.split("")
      if arr[0] == "-"
        arr[1..-1].reverse.join.to_i * -1
      else
        arr.reverse.join.to_i
      end
    end
    
    reverse(321) => 321
    reverse(-54321) => -12345
    

June 2, 5 2017

  • Began work on members-only app
  • Weekly goals
  • Leetcode Two Sum problem
  • def sums(arr, target)
      arr.each_with_index do |outer, x|
        arr.each_with_index do |inner, y|
          if arr[x] + arr[y] == target
            return [x, y]
          end
        end
      end
    end
    
    sums([2,7,11,18], 9) => [0,1]
    sums([2,7,11,18], 18) => [1,2]
    

June 1 2017