/goodquestion

Q&A app built on Rails

Primary LanguageRuby

Awesome Q&A Rails App

goodquestion.herokuapp.com

From start to finish:

$ rails new goodquestion

$ rails g controller Questions index

  • on routes.rb:

replace

get "questions/index"

with

root :to => 'questions#index'
  • remove

public/index.html

  • test

$ rails s

  • on layouts/application.html.erb

replace <title>GoodQuestion</title> with <title><%= yield(:title) %></title>

  • on questions/index.html.erb

add <%= provide(:title, ‘GoodQuestion’) %>

  • on application.html.erb

<div id=“container”> <div id=“header”> <%= link_to ‘GoodQuestion’, root_path %> </div><!– end header –> <div id=“nav”> <ul> <li> <%= link_to ‘Home’, root_path %> </li> </ul> </div><!– end nav –>

<div id=“content”> <%= flash.each do |key, value| %> <p id=“message”><%= value %></p> <% end %> <%= yield %> </div><!– end content –>

<div id=“footer”> &copy; GoodQuestion <%= Date.today.year %> </div><!– end footer –> </div><!– end container –>

  • creating User model:

$ rails g model User username:string password_digest:string

  • On User Migration, add indexes to prevent duplicates:

class CreateUsers < ActiveRecord::Migration def change create_table :users do |t| t.string :username t.string :password_digest

t.timestamps end

add_index :users, :username, unique: true end end

  • run

$ rake db:migrate

  • on user.rb, add has_secure_password

class User < ActiveRecord::Base

attr_accessible :password_digest, :username

has_secure_password

end

  • has_secure_password requires bcrypt gem on gemfile

gem ‘bcrypt-ruby’

bundle install

  • edit user.rb to:

class User < ActiveRecord::Base attr_accessible :password, :password_confirmation, :username

has_secure_password

validates :username, presence: true, uniqueness:{ case_sensitive: false }, length: { in: 4..12 }, format: { with: /^[a-z]*$/, message: ‘can only contain lower-case letters and numbers’} validates :password, length: { in: 4..8 } validates :password_confirmation, length: { in: 4..8 } end

  • generate User controler

$ rails g controller Users new

  • on routes.rb replace get “users/new” with resources :users, only: [:new, :create]

  • Edit new action on users controller def new @user = User.new end

  • Edit users/new to:

<% provide(:title, ‘GoodQuestion - Register’) %>

<h1>Register</h1>

<%= form_for(@user) do |f| %> <%= render ‘common/form_errors’, object: @user %> <% end %>

  • create views/common folder

  • create common/_form.html.erb file

  • edit _form.html.erb

<% if object.errors.any? %> <ul id=“form-errors”> <% object.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> <% end %>

  • edit users/new.html.erb

<% provide(:title, ‘GoodQuestion - Register’) %>

<h1>Register</h1>

<%= form_for(@user) do |f| %> <%= render ‘common/form_errors’, object: @user %>

<p> <%= f.label :username %><br /> <%= f.text_field :username %> </p>

<p> <%= f.label :password, “Password” %> <%= f.password_field :password %> </p>

<p> <%= f.label :password_confirmation, “Confirm” %><br /> <%= f.password_field :password_confirmation %> </p>

<p> <%= f.submit “Register” %> </p>

<% end %>

  • edit layouts/application.html.erb

add to nav div: <li> <%= link_to ‘Register’, new_user_path %> </li>

  • add create action to users controller def create @user = User.new(params) if @user.save flash = “Thanks for registering!” redirect_to root_url else render ‘new’ end end

  • edit en.yml

en:

hello: "Hello world"
activerecord: 
  attributes:
    user:
      password_digest: 'Password'
  • Sessions controller

$ rails g controller Sessions new

  • Edit routes.rb

replace

get "sessions/new"

with resources :sessions, only: [:new, :create]

  • Edit sessions/new.html.erb

<% provide(:title, ‘GoodQuestion - Login’)%>

<h1>Login</h1>

<%= form_for(:session, url:sessions_path) do |f| %> <p> <%= f.label :username, “Username” %> <%= f.text_field :username %> </p>

<p> <%= f.label :password, “Password” %> <%= f.password_field :password %> </p>

<p> <%= f.submit “Login” %> </p> <% end %>

  • Edit nav div on layout/application.html.erb

<% if logged_in? %> <li> <%= link_to “Logout (#{current_user.username})”,‘#’ %> </li> <% else %> <li> <%= link_to ‘Register’, new_user_path %> </li> <li> <%= link_to ‘Login’, new_session_path %> </li> <% end %>

  • Edit layout/application.html.erb to

class ApplicationController < ActionController::Base

protect_from_forgery

helper_method [:current_user, :logged_in?] # this enables current_user and logged_in? methods across the the app

protected

	def current_user
		current_user ||= User.find(session[:user_id]) if session[:user_id]
	end

	def logged_in?
		!current_user.nil?
	end

end

  • Add create action to sessions_controller def create user = User.find_by_username(params[:username]) if user && user.authenticate(params[:password]) login user flash = ‘You are now logged in!’ redirect_to root_url else flash.now = ‘Your username/password combination was incorrect. ’ render ‘new’ end end

  • Add login action to sessions_controller def login(user) session = user.id end

  • Edit routes.rb

add:

match '/register', to: 'users#new'
match '/login', to: 'sessions#new'
match '/logout', to: 'sessions#destroy', via: :delete
  • Edit nav paths on application.html.erb

<% if logged_in? %> <li> <%= link_to “Logout (#{current_user.username})”, logout_path, method: ‘delete’ %> </li> <% else %> <li> <%= link_to ‘Register’, register_path %> </li> <li> <%= link_to ‘Login’, login_path %> </li> <% end %>

  • Adding destroy action to sessions controller: def destroy session = nil redirect_to root_url, notice: ‘You are now logged out!’ end

  • add login @user to create action on users controller (login user automatically after registering) def create @user = User.new(params) if @user.save login @user flash = “Thanks for registering!” redirect_to root_url else render ‘new’ end

  • Creating Question model

$ rails g model Question user:references body:string solved:boolean

end
  • Adding defaut to Question migrations def change

    create_table :questions do |t|
      t.references :user
      t.string :body
      t.boolean :solved, default: false
    
      t.timestamps
    end
    add_index :questions, :user_id
    

    end

  • run

$ rake db:migrate

  • edit user.rb

add: has_many :questions

  • add validations to questions.rb validates :body, presence: true, length: { in: 10..255 } validates :solved, inclusion: { in: [true, false] }

  • add question paths to routes.rb

    resources :questions, except: [:new]

  • edit index action on Questions Controller def index @question = Question.new end

  • edit questions/index.html.erb

<%= provide(:title, ‘GoodQuestion’) %> <div id=“ask”> <h1>Ask a Question</h1> <% if logged_in? %> <%= render ‘question_form’ %> <% else %> <p>Please login to ask or answer questions.</p> <% end %> </div><!– end task –>

  • edit questions/_question_form.html

<%= form_for(@question) do |f| %> <%= render ‘common/form_errors’, object: @question %>

<p> <%= f.label :body, “Question” %> <%= f.text_field :body %> <%= f.submit “Ask a Question” %> </p> <% end %>

  • add create action to questions controller

    def create @question = current_user.questions.build(params) if @question.save flash = “Your question has been posted!” redirect_to root_url else render ‘index’ end end

  • add auth method to application controller

    def auth
      redirect_to login_url, alert: 'You must login to access that page' unless logged_in?
    end
    
  • add before_filter to questions controller before_filter :auth, only: [:create]

  • add unsolved method to question model def self.unsolved(params) where(solved: false).paginate(page: params, order: ‘created_at DESC’, per_page: 3) end

  • edit questions controller to include unsolved method def index @question = Question.new

    @questions = Question.unsolved(params)
    

    end

    def create @question = current_user.questions.build(params) if @question.save flash = “Your question has been posted!” redirect_to root_url else

    @questions = Question.unsolved(params)
    

    render ‘index’ end end

  • add div questions to questions/index.html.erb

<div id=“questions”> <h2>Unsolved Questions</h2> <% if @questions.any? %> <ul> <%= render @questions %> <!– no need for a for loop yay! it only needs question.html.erb –> </ul> <%= will_paginate %> <% else %> <p>No questions have been asked.</p> <% end %> </div><!– end questions –>

  • create questions/_question.html.erb

<li>

<%= truncate(question.body, length: 35) %> by <%= question.user.username.capitalize %> </li>

  • add show action to questions controller def show

    @question = Question.find(params[:id])
    

    end

  • create questions/show.html.erb

<%= provide(:title, ‘View Question’) %>

<h1> <%= @question.user.username.capitalize %> asks: </h1>

<p> <%= @question.body %> </p>

  • edit questions/_question.html.erb to:

<li> <%= link_to truncate(question.body, length: 35), question %> by <%= question.user.username.capitalize %> </li>

  • edit redirect_to on questions_controller’s create action

    def create @question = current_user.questions.build(params) if @question.save flash = “Your question has been posted!” redirect_to @question else

    @questions = Question.unsolved(params)
    

    render ‘index’ end end

  • add your_questions route to routes.rb match ‘/your_questions’, to: ‘questions#your_questions’

  • add your_questions method to user.rb def your_questions(params) questions.paginate(page: params, order: ‘created_at DESC’, per_page: 3) end

  • edit before_filter on Questions controller before_filter :auth, only: [:create, :your_questions]

  • create and edit questions/your_questions.html.erb

<%= provide(:title, ‘Your Questions’) %>

<h1>Your Questions</h1>

<% if @questions.any? %> <ul> <%= render @questions %> </ul> <%= will_paginate %> <% else %> <p>You’ve not posted any questions yet.</p> <% end %>

  • add on_your_questions_page? to questions_helper

def on_your_questions_page? action_name == “your_questions” end

  • add on_your_questions_page? conditional to _question.html.erb

<% if on_your_questions_page? %> - <%= link_to ‘Edit’, edit_question_path(question) %> <% end %>

  • add edit action to questions_controller def edit

    @question = current_user.questions.find(params[:id])
    

    end

  • add edit to before_filter before_filter :auth, only: [:create, :your_questions, :edit]

  • create/edit questions/edit.html.erb

<% provide(:title, ‘Edit Your Question’) %>

<h1>Edit Your Question</h1>

<%= form_for(@question) do |f| %> <%= render ‘common/form_errors’, object: @question %>

<p> <%= f.label :body, “Body” %> <%= f.text_field :body %> </p>

<p> <%= f.label :solved, “Solved” %> <%= f.check_box :solved %> </p>

<p> <%= f.submit “Update” %> </p>

<% end %>

  • add update action to questions_controller

    def update

    @question = current_user.questions.find(params[:id])
    
    if @question.update_attributes(params[:question])
      flash[:success] = "Your question has been updated!"
      redirect_to @question
    else
      render 'edit'
    end
    

    end

  • add update to before_filter

before_filter :auth, only: [:create, :your_questions, :edit, :update]

  • add display_solved to questions_helper

def display_solved(question) (question.solved) ? ‘ - (Solved) ’ : ” end

  • create Answer model

$ rails g model Answer user:references question:references body:string

  • Run migration

$ rake db:migrate

  • add association to User model has_many :answers

  • add association to Question model has_many :answers

  • add validations to Answer model

validates :body, presence: true, length: { in: 2..255 }

  • create answers controller

$ rails g controller Answers

  • add answers routes resources :questions, except: [:new] do

    resources :answers, only: [:create]
    

    end

  • edit show action on questions_controller def show

    @question = Question.find(params[:id])
    @answer = Answer.new
    

    end

  • edit show.html.erb to:

<%= provide(:title, ‘View Question’) %>

<h1> <%= @question.user.username.capitalize %> asks: </h1>

<p> <%= @question.body %> </p>

<div id=“post-answer”> <h2>Answer this Question</h2>

<% if logged_in? %> <%= render ‘answers/answer_form’ %> <% else %> <p>Please login to post an answer for this question.</p> <% end %> </div><!– end post-answer –>

  • create/edit _answer_form.html.erb

<%= form_for([@question, @answer]) do |f| %> <%= render ‘common/form_errors’, object: @answer %>

<p> <%= f.label :body, “Answer” %> <%= f.text_field :body %> <%= f.submit “Post Answer” %> </p> <% end %>

  • add create action to answers controller

def create @question = Question.find(params) @answer = @question.answers.build(params) @answer.user = current_user

if @answer.save flash = “Your answer has been posted!” redirect_to @question else render ‘questions/show’ end end

  • add before_filter to create action on answers controller

before_filter :auth, only: [:create]

  • add answers div to show.html.erb

<div id=“answers”> <h2>Answers</h2>

<% if @question.answers.any? %> <ul> <%= render @question.answers %> </ul> <% else %> <p>This question has not been answered yet.</p> <% end %> </div><!– end answers –>

  • created _answer.html.erb

<li> <%= answer.body %> - by <%= answer.user.username.capitalize %> </li>

  • fix bug on create action of Answers Controller

def create @question = Question.find(params) @answer = @question.answers.build(params) @answer.user = current_user

if @answer.save flash = “Your answer has been posted!” redirect_to @question else @question = Question.find(params) render ‘questions/show’ end end

  • edit _question.html.erb

- (<%= pluralize(question.answers.count, ‘answer’) %>)

  • add search route to routes.rb

match ‘/search’, to: ‘questions#search’

  • add search div to layout/application.html.erb

<body> <div id=“container”> <div id=“header”> <%= link_to ‘GoodQuestion’, root_path %> </div><!– end header –> <div id=“searchbar”> <%= render ‘common/search_form’ %> </div><!– end searchbar –>

  • Add search method to Question model

    def self.search(params) where(“body LIKE ?”, “%#{params}%”).paginate(page: params, order: ‘created_at DESC’, per_page: 3) end

  • Add search action to questions controller

    def search

    @questions = Question.search(params)
    

    end

  • Create/edit questions/search.html.erb

<% provide(:title, ‘Search Results’) %> <h1>Search Results</h1>

<% if @questions.any? %> <ul> <%= render @questions %> </ul> <%= will_paginate %> <% else %> <p>Nothing found, please try a different search.</p> <% end %>

  • Create and edit assets/javscript/goodquestion.js.coffee

GoodQuestion = {}

GoodQuestion.autoSelectSearch = -> window.onload = -> keyword = document.getElementById ‘keyword’ keyword.onclick = -> @select()

GoodQuestion.autoSelectSearch()

  • add twitter bootstrap gem

gem ‘bootstrap-sass’

  • bundle

  • edit layout/application.html.erb

<!DOCTYPE html> <html> <head>

<title><%= yield(:title) %></title>
<%= stylesheet_link_tag    "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>

</head> <body> <header class = “navbar navbar-fixed-top” > <div class=“navbar-inner”> <div class=“container”> <%= link_to ‘GoodQuestion’, root_path, id: ‘logo’ %> <nav> <ul class=“nav pull-right”> <li> <%= render ‘common/search_form’ %> </li> <li> <%= link_to ‘Home’, root_path %> </li> <% if logged_in? %> <li> <%= link_to “Your Q’s”, your_questions_path %> </li> <li> <%= link_to “Logout”, logout_path, method: ‘delete’ %> </li> <% else %> <li> <%= link_to ‘Register’, register_path %> </li> <li> <%= link_to ‘Login’, login_path %> </li> <% end %> </ul> </nav><!– end nav –> </div> </div> </header>

<div class=“container”> <div class=“main-content”> <div> <% flash.each do |key, value| %> <%= content_tag(:div, value, class: “alert alert-#{key}”) %> <% end %> </div> <%= yield %> </div> <footer> &copy; GoodQuestion <%= Date.today.year %> </footer><!– end footer –> </div><!– end content –> </body> </html>

  • edit application.css.scss

/*

* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the top of the
* compiled file, but it's generally better to create a new file per style scope.
*
*= require_self
*= require_tree .
*/

@import “bootstrap”;

/* mixins, variables, etc. */

$grayMediumLight: #eaeaea;

/* universal */

html {

overflow-y: scroll;

}

body {

padding-top: 60px;
text-align: center;

}

nav { margin-top: 10px; }

section {

overflow: auto;

}

textarea {

resize: vertical;

}

ul {

list-style: none;

}

.center {

text-align: center;
h1 {
  margin-bottom: 10px;
}

}

.main-content{ margin-top: 50px; }

/* typography */

h1, h2, h3, h4, h5, h6 {

line-height: 1;

}

h1 {

font-size: 3em;
letter-spacing: -2px;
margin-bottom: 30px;
text-align: center;

}

h2 {

font-size: 1.7em;
letter-spacing: -1px;
margin-bottom: 30px;
text-align: center;
font-weight: normal;
color: $grayLight;

}

p {

font-size: 1.1em;
line-height: 1.7em;

}

/* header */

#logo {

float: left;
margin-right: 10px;
font-size: 1.7em;
color: #555;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
line-height: 1;
&:hover {
  color: #aaa;
  text-decoration: none;
}

}

/* footer */

footer {

margin-top: 45px;
padding-top: 5px;
border-top: 1px solid $grayMediumLight;
color: $grayLight;
a {
  color: $gray;
  &:hover { 
    color: $grayDarker;
  }
}  
small { 
  float: left; 
}
ul {
  float: right;
  list-style: none;
  li {
    float: left;
    margin-left: 10px;
  }
}

}

  • edit common/_form_errors.html.erb

<% if object.errors.any? %> <ul id=“form-errors”> <% object.errors.full_messages.each do |message| %> <div class=“alert alert-error”> <li><%= message %></li> </div> <% end %> </ul> <% end %>

  • Deploy to heroku

$ RAILS_ENV=production bundle exec rake assets:precompile $ heroku create goodquestion $ git add . $ git commit -m “first commit to heroku” $ git push heroku master $ heroku run rake db:migrate $ heroku open

goodquestion.herokuapp.com