Rapid Prototyping with Rails: Lesson 3, part 4

1. Create vote controller action

class PostsController < ApplicationController
  def vote
    binding.pry
  end
 end

Entering params in rails console, will show us that we passing an id parameter but not the boolean to indicate true/false, whether the post is up or down.

2. Add boolean parameter to views to create link

Add boolean parameter to views to create link /posts/2/vote?vote=true&param2=2

#posts/index.html.erb
 <div class='row'>
    <div class='span0 well text-center'>
      <%= link_to vote_post_path(post, vote: true), method: 'post' do %>

3. Now that we have our thing we are voting on and our vote, we can create our vote action

class PostsController < ApplicationController
  before_action :set_post, only: [:show, edit, :update, :vote]

def vote
  #post = Post.find(params[:id]) or use @post(created in the before_action)
  @vote = Vote.create(voteable: @post, creator: current_user, vote: params[:vote])

  if @vote.valid?
    flash[:notice] = "Your vote was recorded"
  else
    flash[:error] = "Your vote was not recorded"
  end
    redirect_to :back (to go back to the original page viewed)
  end
end

4. Wire up the same for the down vote in the views/posts/index

<div class='row'>
  <div class='span0 well text-center'>
    <%= link_to vote_post_path(post, vote: false), method: 'post' do %>

5. What if I want to display the number of votes

<%post.votes.size%>

6. What if I want the number of votes to decrease when pressing the down button.

Although this may seem like a presentational concern that would require a helper method - however storing the number of votes is a data concern and not a presentational one. Method regarding business logic belong in the model itself.

class Post < ActiveRecord::Base
   def total_votes
    up_votes - down_votes
   end

   def up_votes
    self.votes.where(vote: true).size
   end

   def down_votes
    self.votes.where(vote: false).size
   end
 end

So now in the presentation layer, we can call total_votes:

<% post.total_votes %>

7. What if we wanted to sort

class PostsController < ApplicationController

  def index
    @posts = Post.all(|x| x.total_votes).reverse
  end
end

8. How can we make sure that each user only votes once for post:

There are many ways to accomplish this but the best way would be to add the uniqueness validation to the model:

class  Vote < ActiveRecord::Base
  validates_uniqueness_of :creator, scope: :voteable
end

9. Using html_safe

Rails by default escapes everything so user input html, js or script tags are escaped to prevent malicious intent. In order to write html in any place besides the html.erb files, you must append html_safe at the end of your statement

def vote
  #post = Post.find(params[:id]) or use @post(created in the before_action)
  @vote = Vote.create(voteable: @post, creator: current_user, vote: params[:vote])
    ** We do not need to use strong_params because we are not using the key value
    pairs for mass assignment, instead we are hard coding in the keys.
  if @vote.valid?
    flash[:notice] = "Your vote was recorded"
  else
    flash[:error] = "Your vote was <strong>not</strong> recorded".html_safe
  end
    redirect_to :back #(to go back to the original page viewed)
end

This will cause the rails to not escape those tags and apply the html. You generally do not want to add html_safe on user input elements.

Rapid Prototyping with Rails: Lesson 3, part 3

Items Covered

  1. polymorphic associations
    • databases (syntax)
    • models (syntax)
  2. Voting

Polymorphic Associations:

At the database level:

When you have one subject but many objects for example, 1 comment or like can be placed on many objects or foreign keys - postid, photoid, video_id, etc. - This creates alot of spaces and ineffiencies in the database. This is solved by restricting the database from:

"id, body, user_id, post_id, photo_id, video_id" to

 "id, body, user_id, commentable type, commentable_id"
commentable_type: must be the model name, capitialize first initial, string
 commentable_id: is the primary_key on the 1 side.

 commentable_type to commentable_id: is a composite foreign_key

In our application, we will implement voting using polymorphic associations and we can practically vote on anything, but in this case, we will vote on just posts and comments.

Step 1: Generate Migration

Create votes table
  =>rails g migration create_votes

Step 2: Generate Table

create_table :votes do |t|
  t.boolean :vote
  t.integer :user_id
  t.string :voteable_type
  t.integer :voteable_id
  t.timestamps
  end
end

or use

t.references :voteable, polymorphic: true

Step 3: Create Vote Model

class Vote < ActiveRecord::Base
  belongs_to :creator, class_name: 'User', foreign_key: 'user_id'
  belongs_to :voteable, polymorphic: true
end
class User < ActiveRecord::Base
  has_many :votes
end
class Post < ActiveRecord::Base
  has_many :votes, as: voteable
end

Do the same for comments.

The above action gives you voteable getters/setters and now you can assign a variable to your user and call .votes

So in the console:
    v = Votes.first
    v.voteable => nil
    post = Post.first
    post.votes << v
    or another way to assign is
    v.votable = Comments.first
    v.save

In the above - the user and vote are the subjects while the comments and posts are the objects being voted on.

Remember

  • Subjects - has_many, can use same gatters and setters as ususal
  • Objects - belongsto, will expect subject foreignkeys, voteable is now your getter/setter where you pass voteable an object or set the voteable

Voting

Step 1. Now let's show votes on posts index page

#posts/index.html.erb
<div class='row'
  <div class='span0 well text-center'>
  <%= link_to '' do %>
    <i class='icon-arrow-up'></i>
    <% end %>
    <br/>
  <%= link_to '' do %>
    <i class='icon-arrow-down'></i>
    <% end %>
</div>

Step 2. How do we reflect the increase/decrease of votes every time the link is clicked:

#routes.rb
Two ways....
a) POST/votes => 'VotesController#create'
  - needs to pass in two pieces of information (1 - whether its the post/comment
  being voted on, 2. The post/comment id
  - also this would create another top level resource)
  - best for if you're voting on alot of objects

        How to implement this in your routes:
        resources :votes, only: [:create]

b) POST/posts/3/vote => 'PostsController#vote'
POST/posts/3/comments/4/vote => 'CommentsController#vote'
    How to implement this in your routes, you must use something called a member, where
    each action will be exposed to the member of that url

    resources :posts, except: [:destroy] do

      member do
        post :vote
      end

      (where post is the member and vote is the action)


      **rake routes | grep vote
      vote_post POST /posts/:id/vote(.:format)   post#vote

Step 3. Also what about if we want to see archives of our posts

# GET /posts/archives

  collection do
    get :archives
  end
  **rake routes | grep archives
  archives_posts GET /posts/archives(.:format)  posts#archives

So to compare using post vs get, post will require pass in an object. So basically you can create any route that you want using member, collections, and nested resources

Steps from UX

  • Lofi/hifi
  • ERD
  • Tables
  • URL design

Step 4. Let's include these new routes in our posts/index.html.erb

#posts/index.html.erb
<div class='row'
  <div class='span0 well text-center'>
  <%= link_to vote_post_path(post) method: 'post' do %>
    <i class='icon-arrow-up'></i>
    <% end %>
    <br/>
  <%= link_to '' do %>
    <i class='icon-arrow-down'></i>
  <% end %>
</div>

You must specify method: 'post' because with out it the default will be the GET method and the link, post/id/vote will not work on a GET. * method: 'post' create data method: There is javascript that comes with rails that looks for anchor tags and the data-method="post", that will turn the link into an actual form and submit content.

Rapid Prototyping with Rails: Lesson 3, part 2

Items Covererd:

  1. Creating/Updating User
  2. Login/Logout
  3. Memoization

Creating/Updating User

  1. Create resources resources :users, only: [:create]
  2. Create users_controller.rb (new, create, edit, update actions)
    • since :password is the virtual attribute for :passworddigest, you have to include it in strongparams:
def users_params
  params.require(:user).permit(:username, :password)
end
  1. Create users folder under views (new.html.erb, edit.html.erb, _form.html.erb)
  2. Add password validations
validates :password, presence: true, on: :create, length: {minimun: 3}
  1. Change users#new to register under routes file to add a register path '/register':
get '/register', to: 'users#new', as: 'register'
  1. Add register link to _navigation.html.erb
<li>
          <%= link_to 'Register', register_path %>

Login/Logout

  1. Create routes - *resources are reserved for models, typcially custom routes are on-offs
get '/login', to: 'sessions#new'
      post '/login', to: 'sessions#create'
      get '/logout', to: 'sessions#destroy'
  1. Create sessions_controller.rb
class SessionsController < Application.rb
  def new
  end

  def create
    Let's write pseudo code:
    What are we trying to do... we want to run user.authenticate('password')
    How do we accomplish this?
    1. Get user object
    2. See if the password matches
    3. If so, log in
    4. If not, error message

    user = User.find_by(username: params[:username])
    # we can use a local variable bc we aren't using model backed forms so persistency isn't required.

    if user && user.authenticate(params[:password])
      session[:user_id] = user_id
      flash[:notice] = "You're logged in!"
      redirect_to root_path

      #never pass an object into sessions ( like @user for example)
      bc sessions only have 4KBs in bandwidth and will generate a "cookie overload error" after the user carries out
      so many actions.

    else
      flash[:error] = "There was something wrong with your username and password"
      #Avoid being too specific here, (i.e. found username but wrong password)... to prevent hacking
      redirect_to register_path
    end
  end


  #Login pages should be https://, if not, passwords submitted over an http:// server are not encrypted. The https://
  is not a very high added expense. You just have to purchase SSL certificate.


  def delete
    session[:user_id] = nil
    flash[:notice] = "You've logged out"
    redirect_to root_path
    end
  end
  1. Create a sessions folder under views, new.html.erb file

  2. Add login/logout link to nav bar

#_navigation.html.erb

    if logged_in?
      <li>Hi<%= current_user.username %></li>
  <li>
      <%= link_to "Log Out", logout_path %>
  </li>
    <% else %>
    <li>
      <%= link_to "Register", register_path %>
    </li>
    <li>
      <%= link_to "Log in", login_path %>
    <% end %>
    </li>
  1. We need to create a method called loggedin (referenced above) in the applicationcontroller.rb bc we want this method accessible across the application.
application_controller.rb

helper_method :current_user, :logged_in?

def current_user
  #Wpseudo-code
  #If current user, return the user obj
  #else return nil
    @current_user ||= User.find(session[user_id]) if session[:user_id]
  end
end

  def logged_in?
    !!current_user
  end
end

Memoization

For effecient performance, we want to hit the database only once per request. For methods that are being called multiple times, it is more effecient to save in an instance variable rather than continously hitting the database.

  1. We can now hide links based on whether a user is logged in or not. Let's hide the 'edit' link
#posts/index.html.erb
<h3> Welcome </h3>
 <ul>
  <% @posts.each do |post| %>
  <li>
  <%= link_to post.title, post_path(post)
  <% if logged_in? %>
  [<%= link_to 'Edit', edit_post_path(post) %>]
  1. We also want to make sure you can't go to the link via url
#posts_controller.rb
  before_action :require_user,except: [:index, :show]
  1. We must create a require_user method
#application_controller.rb

  def require_user
    if !logged_in?
      flash[:notice] = "Must be logged in to do that"
      redirect_to root_path
  end
  1. Set up beforeaction under the commentscontroller.rb, also.
#comments_controller.rb
class CommentsController < ApplicationController.rb
before_action :require_user

  def create
    @comment.creator = current_user #change from @comment_creator = User.first
  end
end
  1. Define post.creator as current_user
#posts_controller.rb
  def create
    @post.creator = current_user
  end

Remember - when you want to lock down a peice of functionality, you have to 1. Remove it from the user interface (links) 2. Set up a before_action

Rapid Prototyping with Rails: Lesson 3, part 1

Items Covered:

  1. Quiz Lesson 2 Review
  2. Asset Pipeline
  3. Authentication from scratch
** has_secured_password
** password_digest
** bcrypt
** validations
** helper methods
** before_action

Quiz Review

  • Remember that the show, update, and delete actions routes require an object and not an (:id).
post_path(post)
  • REST is url convention pattern means that url endpoints are exposed using the resources keyword.

These urls correlate with the CRUD actions on the controller side.

  • Difference b/w model and non-model backed helpers is that
- Model backed helpers are tied to an object
  - There is slight syntactical difference:
        - non-model: text_field_tag :title
        - model: f.text_field :title

There is no additional functionality with non-model backed forms.

Non-model forms are used for elements that are not models... such as passwords but model-backed forms are used for CRUD actions for models.

Asset Pipeline:

obfuscates your js code to make it unreadable in production (to prevent unallowable use) and then allows for better response time bc many browsers can only allow for 10 items to load at a time. Thats why we have to use an asset library that caches

jammit

is a good open source obfuscator, and sprockets Functions: 1. Obfusgation 2. File compression 3. Timestamp (Cache Buster)

Can be found under the production application -> public/assets folder Goal is to take all the js files, obfusgate, jam it into one file under the public/assets file

Cache buster

is the string tied to the asset level or DNS level -different browsers have diff means of caching and thats why Google chrome is considered to be faster... it's because Chrome caches agressively. The cachebuster gives a reference number in which the browser and application can manage/track caches to allow refresh of new information or changes.

Application.js is a manifesto file where the obfuscated js code resides (using Sprockets as the asset manager). Same goes for Application.scss So how/does Sprockets compile your files under assets and places them under public/assests folder in production:

rake assets:precompile

There are two asset management approaches:

  1. As a development team,every time a change is made, run rake assets:precompile to make changes to static assests, commit changes and move those files to our production
  2. We want to delete all files under public and when we deploy, we will run rake assets:precompile at deployment only and then that will take our code and turn into static assets.

###Which is better: Option 2 is better but your deployment could take longer (maybe 5 mins) We will be following Option 2 and waiting to precompile bc Heroku does it for us. We should delete the public/assets folder. If we don't, Heroku will delete the rake assets:precompile command and when changes are made, they will not be reflected in your production code.

Another common problem is when running locally, Js files will be picked in both the application.js file and the assets folder therefore in our case.. when we use js to build our voting functionality, you will have to vote twice This is all solved if you remove your public/assets directory

How password authentication works!

Passwords should never be saved in your application, in fact, your application should never know your password and shouldn't be able to send you your 'forgotten password' via email.

How passwords are saved is through a one-way hash. We hash the string (password) into some one-way indecipherable hash token. When you attempt to enter in your set password, the string will also be converted into a one-way indecipherable has and its determined if they match.

This password method is vulnerable to dictionary attacks/rainbow attacks, that's why they say pick a password with a capital letter and a special character.

  1. So we need a column added to the user table to store the hash token and we will call that, password_digest:
- In terminal - rails g migration add_password_to_users
  _ Open migration file:
      - def change
        add_column :users, :password_digest, :string
  - rake db:migrate
2. Go to User model and add has_secure_password
3. Add 'bcrypt-ruby' to your gemfile - contains the complex algorithms to create our one-way hash
    Use 'bcrypt-ruby', =3.0.1 if you have installation problems during bundle intall

has_secure_password
4. has_secure_password has validations functionality where password cannot be blank, so add validations: false
        has_secure_password validations: false

Tip: Use gems that do one thing really well not multiple things.

5. has_secure_password gives us multiple methods we can now use:
      1. password setter: u.password = "apple", when run u.save, you will see that password is just a
      virtual attribute, and "apple" was saved to the password_digest column and encrypted using bcrypt.
      2. There is NO getter, so you can't run u.password and expect to get the value.
      3. But there is method called u.authenticate('password'), where you pass in a password that you are trying to check against
      in the database.
       - If the password is not correct, false will be returned.
       - If correct, the user will be returned.
       Authenticate is an instance method being called on the User class

-

Rapid Prototyping with Rails: Lesson 2, Nested Routes

Comments - using Nested Routes

def create
    @post = Post.find(params[:post_id])
    @comment = Comment.new(params.require(:comment).permit(:body))
    @comment.post = @post
  end

OR

def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.build(params.require(:comment).permit(:body))
  end

What we've covered:

  1. Creating a Nested Resource

  2. Creating a model-backed form in a nested resource way... to generate a nested URL

  3. In your controller Create action, you must set up the parent object in the controller action, so that it can be called on in your form_for

  4. Under the error condition, you have to render 'posts/show' rather than 'new'