Liz's Blog

12 in 12 Challenges #1:如何利用Rails4打造出Reddit或Hacker News類型的網站

| Comments

很久很久之前就看過【Inside】刊登的這篇文章〈我是這樣學會 Ruby on Rails:12 個星期打造 12 個網頁 APP〉,當時還很認真買了一本Ruby on Rails的書,但可想而知,當然是翻了兩三頁就放棄了。今年要來重啟挑戰,雖然作者關掉了步驟詳解,只留下GitHub和線上影片,看一集看到眼睛要瞎掉,但今年一定要完成這件事(握拳)!

第一集作者Mackenzie Child的GitHub,影片請看這裡。很推薦大家去看他影片,聲音很好聽,英文不用很好也沒關係:)

以下是因應不同版本的gem,而修改部分程式碼,大部份都與課程相同,並且添加一些筆記。我自己的GitHub在此,請參考。

【建立新專案】
1.終端機:rails new Reddit
——建立新專案Reddit。
2.終端機:cd Reddit
——移到新專案目錄下。
3.終端機:ls
——可看出此專案下的目錄列表。
4.用sublime打開Reddit專案。
5.終端機:rails s
——讓專案程式跑起來。
6.用瀏覽器打開http://localhost:3000
——成功建立,會顯示歡迎畫面。
7.終端機:git init
——建立新的Git的Repository(存放處),第一次才需使用此指令。
8.終端機:git status
——查詢目前Git狀態,可看出檔案改變有沒有被追蹤。
9.終端機:git add .
——一次加入所有沒被追蹤的檔案,或者也可一次追蹤一個檔案,如:git add test.rb
10.終端機:git commit -am “Initial Commit”
——將已更動的檔案,按下一個存檔的動作。
註:新手也可使用Source Tree來學習Git,可參考Growth School提供的免費教學課程

【蓋出一個可以新增網址的網頁】
1.終端機:git checkout -b link_scaffold
——新增一個link_scaffold的分支,並從本來的Master跳到link_scaffold的分支下工作。
2.終端機:rails g scaffold link title:string url:string
——快速利用鷹架來蓋出首頁是有連結的網頁,並新增title及url兩個字串欄位。
3.終端機:rake db:migrate
——確實執行上面的指令。
4.利用另外視窗重啟程式:rails s
——按ctrl+C可停止程式。
5.使用瀏覽器開啟http://localhost:3000/links
——可進去新增title(名稱)和url(網址)欄位。
6.終端機:git status
7.終端機:git add .
8.終端機:git commit -am “Generate Link Scaffold”
9.終端機:git checkout master
——跳回Master工作主線。
10.終端機:git merge Link_scaffold
——將剛剛在Link_scaffold分支下做的事情,與主線合併。

【新增會員系統 Devise
1.終端機:git checkout -b add_users
2.安裝devise,目前版本已到4.0以上,作者當時是安裝3系列,後續安裝差異請見這篇

Gemfile
gem 'devise', '~> 4.3'

3.終端機:bundle install
——執行安裝gem。
4.利用另外視窗重啟程式:rails s
5.終端機:rails g devise:install
——安裝Devise檔案。
6.在development.rb為Devise mailer配置預設的網址。

config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

7.將首頁改成指向剛剛建立的連結網頁。

routes.rb
Rails.application.routes.draw do
  root to: “links#index”
end

8.加入訊息顯示程式碼。

app/views/layouts/application.html
...
<body>
<% flash.each do |name, msg| %>
  <% content_tag(:div, msg, class: "alert alert-#{name}") %>
<% end %>

<%= yield %>
....

9.終端機:rails g devise:views
——為了之後要修改views,先把可修改的選項跑起來。
10.終端機:rails g devise user
——產生devise的user model。
11.終端機:rake db:migrate
——建立user資料庫欄位。
12.利用另外視窗重啟程式:rails s
13.使用瀏覽器重啟:http://localhost:3000//users/sign_up
——可進去註冊會員並登入,再新增網址。
14.終端機:rails c
——用指令直接和程式互動,又不會影響到網站運作。
15.終端機:User.count
——顯示有多少個使用者。
16.終端機:@user = User.first
——顯示第一個使用者是誰。
17.按ctrl+D則可離開console。
18.終端機:git status
19.終端機:git add .
20.終端機:git commit -am “Add devise and add user model”

【建立user和link的資料庫連結】
1.會員登入後,顯示Submit link、Account、Sign out。未登入則顯示Sign up及Sign in。

app/views/layouts/application.html
....
<% if user_signed_in? %>
  <ul>
    <li><%= link_to 'Submit link', new_link_path %></li>
    <li><%= link_to 'Account', edit_user_registration_path %></li>
    <li><%= link_to 'Sign out', destroy_user_session_path, :method => :delete %></li>
  </ul>
<% else %>
  <ul>
    <li><%= link_to 'Sign up', new_user_registration_path %></li>
    <li><%= link_to 'Sign in', new_user_session_path %></li>
  </ul>
<% end %>

  <%= yield %>
</body>

2.在資料庫中建立連結,user有許多links。

app/models/user.rb
has_many :links

3.link則屬於user。

app/models/link.rb
belongs_to :user

4.終端機:rails c
5.終端機:@link = Link.first
6.終端機:@link.user
7.按ctrl+D離開console。
8.終端機:rails g migration add_user_id_to_links user_id:integer:index
新增user id欄位到Link資料庫。
9.終端機:rake db:migrate
10.終端機:git status
11.終端機:git add .
12.終端機:git commit -am “Add association between link and user”

【會員才能新增link】
1.在新增連結時,會記錄登入會員。將Link.new取代成current_user.links.build。

app/controller/links_controller.rb
def new
  @link = current_user.links.build
end

def create
  @link = current_user.links.build(link_params)
....

2.終端機:rails c
3.終端機:@link = Link.last
4.終端機:@link.user
5.終端機:@link.user.email
6.按ctrl+D離開console。
7.在controller建立會員才能建立連結,但純觀看則不需登入會員。

app/controllers/links_controller.rb
before_filter :authenticate_user!, except: [:index, :show]

8.只有同時是登入會員又是連結建立者,才能看到Edit及Destroy。

app/views/links/index.html.erb
<% if link.user == current_user %>
  <td><%= link_to 'Edit', edit_link_path(link) %></td>
  <td><%= link_to 'Destroy', link, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% end %>

9.終端機:@link = Link.first
10.終端機:@link.user = User.first
11.終端機:@link
12.終端機:@link = Link.find(2)
13.終端機:@link.user = User.first
14.終端機:@link.save
15.去除下列這行。

app/views/links/index.html.erb
<%= link_to 'New Link', new_link_path %>

16.終端機:git status
17.終端機:git add .
18.終端機:git commit -am “Authorization on links”
19.終端機:git checkout master
20.終端機:git merge add_users

【安裝bootstrap】可參考我之前寫過的Bootstrap安裝步驟。
1.終端機:git checkout -b add_bootstrap
2.安裝bootstrap。

Gemfile
gem 'bootstrap-sass', '~> 3.3', '>= 3.3.7'

3.終端機:bundle install
4.終端機:mv app/assets/stylesheets/application.css app/assets/stylesheets/application.css.scss
5.新增以下兩行,並移除*= require_self and *= require_tree .

app/assets/stylesheets/application.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";

6.確認有以下這兩行:

app/assets/javascripts/application.js
//= require jquery
//= require bootstrap-sprockets

7.移除app/assets/stylesheets/scaffolds.scss,因為不需要用到。
8.讓導覽列標題靠左,登入登出在最右邊。導覽列練習可參考我之前的文章

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>Reddit</title>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body>
  <header class="navbar navbar-default" role="navigation">
    <div class='navbar-inner'
      <div class="container">
        <div id="logo" class="navbar-brand"><%= link_to "Reddit", root_path %></div>
        <nav class="collapse navbar-collapse navbar-ex1-collapse">
          <% if user_signed_in? %>
            <ul class="nav navbar-nav" navbar-right>
              <li><%= link_to 'Submit link', new_link_path %></li>
              <li><%= link_to 'Account', edit_user_registration_path %></li>
              <li><%= link_to 'Sign out', destroy_user_session_path, :method => :delete %></li>
            </ul>
          <% else %>
            <ul class="nav navbar-nav pull-right">
              <li><%= link_to 'Sign up', new_user_registration_path %></li>
              <li><%= link_to 'Sign in', new_user_session_path %></li>
            </ul>
          <% end %>
        </nav>
      </div>
    </div>
  </header>

  <div id="main_content" class="container">
    <% flash.each do |name, msg| %>
      <% content_tag(:div, msg, class: "alert alert-#{name}") %>
    <% end %>

    <div id=“content" class="col-md-9 center-black">
      <%= yield %>
    </div>

</body>
</html>

9.使用bootstrap及加入logo、main_content、comments設計。

app/assets/stylesheets/application.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";

#logo {
  font-size: 26px;
  font-weight: 700;
  text-transform:uppercase;
  letter-spacing: -1px;
  padding: 15 px 0;
  a {
    color: #2F363E;
  }
}

#main_content {
  #content {
    float: none;
  }
  padding-bottom: 100px;
  .link {
    padding: 2em 1em;
    border-bottom: 1px solid #e9e9e9;
    .title {

      a {
        color: #FF4500;
      }
    }
  }
  .comments_title {
    margin-top: 2em;
  }
  #comments {
    .comments {
      padding: 1em 0;
      border-top: 1px solid #E9E9E9;
      .lead {
        margin-bottom: 0;
      }
    }
  }
}

10.建立連結目錄頁顯示方式。

app/views/links/index.html.erb
<% @links.each do |link| %>
  <div class="link row clearfix">
    <h2>
      <%= link_to link.title, link %><br>
      <small class="author">Submitted <%=time_ago_in_words(link.created_at) %> by <%= link.user.email %></small>
    </h2>
  </div>
<% end %>

11.建立點入連結的顯示頁的顯示方式。

app/views/links/show.html.erb
<div class="page-header">
  <h1><a href="<%= @link.url %>"><%= @link.title %></a><br> <small>Submitted by <%= @link.user.email %></small></h1>
</div>

<div class="btn-group">
  <%= link_to 'Visit URL', @link.url, class: "btn btn-primary" %>
</div>

<% if @link.user == current_user -%>
  <div class="btn-group">
  <%= link_to 'Edit', edit_link_path(@link), class: "btn btn-default" %>
  <%= link_to 'Destroy', @link, method: :delete, data: {confirm: 'Are you sure?' }, class: "btn btn-default" %>
  </div>
<% end %>

12.新增連結頁面套入bootstrap設計。

app/views/links/_form.html.erb
<%= form_for(@link) do |f| %>
  <% if @link.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@link.errors.count, "error") %> prohibited this link from being saved:</h2>

      <ul>
      <% @link.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="form-group">
    <%= f.label :title %><br>
    <%= f.text_field :title, class: "form-control" %>
  </div>
  <div class="form-group">
    <%= f.label :url %><br>
    <%= f.text_field :url, class: "form-control" %>
  </div>
  <div class="form-group">
    <%= f.submit "Submit", class: "btn btn-lg btn-primary" %>
  </div>
<% end %>

13.將Account修改頁面,套入bootstrap設計。

app/views/devise/registrations/edit.html.erb
<h2>Edit <%= resource_name.to_s.humanize %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
  <%= devise_error_messages! %>

  <div class="panel panel-default">
    <div class="panel-body">

      <div class="form-inputs">

      <div class="form-group">
        <%= f.label :email %><br />
        <%= f.email_field :email, class: "form-control", autofocus: true %>
      </div>

      <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
        <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
      <% end %>

      <div class="form-group">
        <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
        <%= f.password_field :password, class: "form-control", autocomplete: "off" %>
      </div>

      <div class="form-group">
        <%= f.label :password_confirmation %><br />
        <%= f.password_field :password_confirmation, autocomplete: "off" %>
      </div>

      <div class="form-group">
        <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
        <%= f.password_field :current_password, class: "form-control", autocomplete: "off" %>
      </div>

      <div class="form-group">
        <%= f.submit "Update", class: "btn btn-primary" %>
      </div>
    <% end %>
  </div>
  <div class="panel-footer">

    <h3>Cancel my account</h3>

    <p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete, class: "btn btn-default" %></p>
  </div>

14.將Sign up修改頁面,套入bootstrap設計。

app/views/devise/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>

  <div class="form-group">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, class: "form-control", required: true %>
  </div>

  <div class="form-group">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "off", class: "form-control", required: true %>
  </div>

  <div class="form-group">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control", required: true %>
  </div>

  <div class="form-group">
    <%= f.submit "Sign up", class: "btn btn-lg btn-primary" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

15.將Log in修改頁面,套入bootstrap設計。

app/views/devise/session/new.html.erb
<h2>Log in</h2>

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
  <div class="form-group">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, class: "form-control", required: false %>
  </div>

  <div class="form-group">
    <%= f.label :password %><br />
    <%= f.password_field :password, autocomplete: "off", class: "form-control", required: false %>
  </div>

  <% if devise_mapping.rememberable? -%>
    <div class="form-group">
      <%= f.check_box :remember_me %>
      <%= f.label :remember_me %>
    </div>
  <% end -%>

  <div class="form-group">
    <%= f.submit "Log in", class: "btn btn-primary" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

16.終端機:git status
17.終端機:git add .
18.終端機:git commit -am “Add structure and basic styling”
19.終端機:git checkout master
20.終端機:git merge add_bootstrap

【安裝acts_as_votable的投票系統】
1.終端機:git checkout -b add_acts_as_votable
2.安裝acts_as_votable。

Gemfile
gem 'acts_as_votable', '~> 0.10.0'

3.終端機:bundle install
4.終端機:rails g acts_as_votable:migration
5.終端機:rake db:migrate
6.建立link和acts_as_votable資料庫關係。

app/models/link.rb
class Link < ActiveRecord::Base
  acts_as_votable
  belongs_to :user
end

7.終端機:rails c
8.終端機:@link = Link.first
9.終端機:@user = User.first
10.終端機:@link.liked_by @user
11.終端機:@link.votes_for.size
12.終端機:@link.save
13.按ctrl+D離開console。
14.會員按下like則會執行links下upvote,按下dislike則會執行downvote。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  resources :links do
    member do
      put "like", to: "links#upvote"
      put "dislike", to: "links#downvote"
    end
  end

15.設定如何執行upvote和downvote。

app/controllers/links_controller.rb
def upvote
  @link = Link.find(params[:id])
  @link.upvote_by current_user
  redirect_to :back
end

def downvote
  @link = Link.find(params[:id])
  @link.downvote_by current_user
  redirect_to :back
end

16.設定連結目錄頁的顯示畫面。

app/views/links/index.html.erb
<% @links.each do |link| %>
  <div class="link row clearfix">
    <h2>
      <%= link_to link.title, link %><br>
      <small class="author">Submitted <%=time_ago_in_words(link.created_at) %> by <%= link.user.email %></small>
    </h2>

    <div class="btn-group">
      <a class="btn btn-default btn-sm" href="<%= link.url %>">Visit Link</a>
        <%= link_to like_link_path(link), method: :put, class: "btn btn-default btn-sm" do %>
          <span class="glyphicon glyphicon-chevron-up"></span>
          Upvote
          <%= link.get_upvotes.size %>
        <% end %>
        <%= link_to dislike_link_path(link), method: :put, class: "btn btn-default btn-sm" do %>
          <span class="glyphicon glyphicon-chevron-down"></span>
          Downvote
          <%= link.get_downvotes.size %>
        <% end %>
    </div>
  </div>
<% end %>

17.設定點進每個連結內容,也可以看到投票系統。

app/views/links/show.html.erb
<div class="page-header">
  <h1><a href="<%= @link.url %>"><%= @link.title %></a><br> <small>Submitted by <%= @link.user.email %></small></h1>
</div>

<div class="btn-group">
  <%= link_to 'Visit URL', @link.url, class: "btn btn-primary" %>
</div>

<% if @link.user == current_user -%>
  <div class="btn-group">
  <%= link_to 'Edit', edit_link_path(@link), class: "btn btn-default" %>
  <%= link_to 'Destroy', @link, method: :delete, data: {confirm: 'Are you sure?' }, class: "btn btn-default" %>
  </div>
<% end %>

<div class="btn-group pull-right">
  <%= link_to like_link_path(@link), method: :put, class: "btn btn-default btn-sm" do %>
    <span class="glyphicon glyphicon-chevron-up"></span>
    Upvote
    <%= @link.get_upvotes.size %>
  <% end %>
  <%= link_to dislike_link_path(@link), method: :put, class: "btn btn-default btn-sm" do %>
    <span class="glyphicon glyphicon-chevron-down"></span>
    Upvote
    <%= @link.get_downvotes.size %>
  <% end %>
</div>

18.終端機:git status
19.終端機:git add .
20.終端機:git commit -am “Added and setup acts_as_votable”
21.終端機:git checkout master
22.終端機:git merge add_acts_as_votable

【安裝simple_form及增加comment評論功能】
1.終端機:git checkout -b add_comments
2.終端機:rails g scaffold Comment link_id:integer:index body:text user:references --skip-stylesheets
3.終端機:rake db:migrate
4.安裝simpleform。

Gemfile
gem 'simple_form', '~> 3.2'

5.終端機:bundle install
6.終端機:rails generate simple_form:install —bootstrap
7.建立資料庫連結,link有很多comments。

app/models/link.rb
has_many :comments

8.建立資料庫連結,comment屬於link。

app/models/comment.rb
belongs_to :link

9.新增comments連結。

config/routes.rb
resources :links do
  member do
    put "like", to: "links#upvote"
    put "dislike", to: "links#downvote"
  end
    resources :comments
end

10.將new、edit、update刪除,將create及destroy修改如下:

app/controllers/comments_controller.rb
def create
  @link = Link.find(params[:link_id])
  @comment = @link.comments.new(comment_params)
  @comment.user = current_user

  respond_to do |format|
    if @comment.save
      format.html { redirect_to @link, notice: 'Comment was successfully created.' }
      format.json { render json: @comment, status: :created, location: @comment }
    else
      format.html { render action: "new" }
      format.json { render json: @comment.errors, status: :unprocessable_entity }
    end
  end
end

# DELETE /comments/1
# DELETE /comments/1.json
def destroy
  @comment.destroy
  respond_to do |format|
    format.html { redirect_to :back, notice: 'Comment was successfully destroyed.' }
    format.json { head :no_content }
  end
end

11.在每一連結的顯示頁中,加入評論。

app/views/links/show.html.erb
<h3 class="comments_title">
  <%= @link.comments.count %> Comments
</h3>

<div id="comments">
  <%= render :partial => @link.comments %>
</div>
<%= simple_form_for [@link, Comment.new]  do |f| %>
  <div class="field">
    <%= f.text_area :body, class: "form-control" %>
  </div>
  <br>
  <%= f.submit "Add Comment", class: "btn btn-primary" %>
<% end %>

12.新增檔案_comment.html.erb。partial可參考Rails 101的運用。

app/views/comments/_comment.html.erb
<%= div_for(comment) do %>
  <div class="comments_wrapper clearfix">
  <div class="pull-left">
   <p class="lead"><%= comment.body %></p>
   <p><small>Submitted <strong><%= time_ago_in_words(comment.created_at) %> ago</strong> by <%= comment.user.email %></small></p>
  </div>

  <div class="btn-group pull-right">
   <% if comment.user == current_user -%>
     <%= link_to 'Destroy', comment, method: :delete, date: { confirm: 'Are you sure?' }, class: "btn btn-sm btn-default" %>
   <% end %>
  </div>
  </div>
<% end %>

13.git status
14.git add .
15.git commit -am “Add comments”
16.git checkout master
17.git merge add_comments

【新增user有name欄位】
1.git checkout -b add_name_to_users
2.rails g migration add_name_to_users name:string
3.rake db:migrate
4.新增Account修改頁有name的欄位。

app/views/devise/registrations/edit.html.erb
<div class="form-group">
   <%= f.label :name %><br />
   <%= f.text_field :name, class: "form-control", autofocus: true %>
</div>

5.此為修改Devise 4.0版本的方式,作者原為版本3,差異請看這裡

app/controllers/application.rb
before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up) do |u|
      u.permit(:name, :email, :password, :password_confirmation)
    end
    devise_parameter_sanitizer.permit(:account_update) do |u|
      u.permit(:name, :email, :password, :password_confirmation, :current_password)
    end
  end

6.原顯示email,改為name。

app/views/links/index.html.erb
<small class="author">Submitted <%=time_ago_in_words(link.created_at) %> by <%= link.user.name %></small>

7.原顯示email,改為name。

app/views/links/show.html.erb
<div class="page-header">
  <h1><a href="<%= @link.url %>"><%= @link.title %></a><br> <small>Submitted by <%= @link.user.name %></small></h1>
</div>

8.原顯示email,改為name。

app/views/comments/_comments.html.erb
<p><small>Submitted <strong><%= time_ago_in_words(comment.created_at) %> ago</strong> by <%= comment.user.name %></small></p>

9.註冊頁面,新增name欄位。

app/views/devise/registrations/new.html.erb
<div class="form-group">
   <%= f.label :name %><br />
   <%= f.text_field :name, class: "form-control", autofocus: true %>
</div>

10.終端機:git status
11.終端機:git add .
12.終端機:git commit -am “Add name to users”
13.終端機:git checkout master
14.終端機:git merge add_name_to_users

然後就完成囉!基本上算是複習之前上過的初探 RailsRails 101

【延伸閱讀】
1.12 in 12 Challenges #1:如何利用Rails4打造出Reddit或Hacker News類型的網站
2.12 in 12 Challenges #2:如何用Rails4做出部落格
3.12 in 12 challenges #3:如何用Rails4打造一個食譜網站
4.12 in 12 challenges #4:如何用Rails4打造Pinterest
5.12 in 12 challenges #5:如何用Rails4打造電影評論網
6.12 in 12 challenges #6:如何用Rails4打造待做清單
7.12 in 12 Challenges #7:如何利用Rails4打造出求職網站
8.12 in 12 Challenges #8:如何用Rails4做出健身紀錄
9.12 in 12 Challenges #9:如何用Rails4做出維基百科
10.12 in 12 Challenges #10:如何用Rails4做出論壇
11.12 in 12 Challenges #11:如何用Rails4做出Notebook
12.12 in 12 Challenges #12:如何用Rails4做出Dribbble

Comments

comments powered by Disqus