Liz's Blog

12 in 12 Challenges #10:如何用Rails4做出論壇

| Comments

作者:Mackenzie Child
影片:How to build a forum in Rails 4
Github

我練習的Github

【開啟新專案forum】
1.終端機:rails new forum
2.終端機:cd forum
3.用另一視窗開啟終端機:rails s
4.瀏覽器:localhost:3000
5.終端機:git init
6.終端機:git status
7.終端機:git add .
8.終端機:git commit -am "Initial Commit"

【新增post model & controller】
1.終端機:rails g model post title:string content:text
2.終端機:rake db:migrate
3.終端機:rails g controller posts
4.

app/controllers/posts_controller.rb
....
def index
edn
....

5.

config/routes.rb
....
  resources :posts

  root "posts#index"
end

6.終端機:rake routes
7.終端機:git status
8.終端機:git add .
9.終端機:git commit -am "Add post model & controller"

【安裝gem】
1.安裝hamlsimple_formdevise

Gemfile
....
gem 'haml', '~> 5.0', '>= 5.0.1'
gem 'simple_form', '~> 3.5'
gem 'devise', '~> 4.3'
....

2.終端機:bundle install
3.另一視窗重啟終端機:rails s
4.新增index.html.haml檔案。

app/views/posts/index.html.haml
%h1 This is posts#index placeholder

5.終端機:rails g simple_form:install --bootstrap
6.終端機:git status
7.終端機:git add .
8.終端機:git commit -am "Add Application Gems & Post index view"

【新增文章觀看、修改及刪除功能】
1.

app/controllers/posts_controller.rb
....
    def new
        @post = Post.new
    end

    def create
        @post = Post.new(post_params)

        if @post.save
            redirect_to @post
        else
            render 'new'
        end
    end
....
    private

    def post_params
        params.require(:post).permit(:title, :content)
    end
....

2.新增new.html.haml檔案。

app/views/posts/new.html.haml
%h1 New Post

= render 'form'

3.新增_form.html.haml檔案。

app/views/posts/_form.html.haml
= simple_form_for @post do |f|
    = f.input :title
    = f.input :content
    = f.submit

4.

app/controllers/posts_controller.rb
....
    def show
    @post = Post.find(params[:id])
    end
...

5.新增show.html.haml檔案。

app/views/posts/show.html.haml
%h1= @post.title
%p= @post.content

= link_to "Home", root_path

6.

app/controllers/posts_controller.rb
class PostsController < ApplicationController
    before_action :find_post, only: [:show, :edit, :update, :destroy]

    def index
    end

    def show
    end

    def new
        @post = Post.new
    end

    def create
        @post = Post.new(post_params)

        if @post.save
            redirect_to @post
        else
            render 'new'
        end
    end

    def edit
    end

    def update
        if @post.update(post_params)
            redirect_to @post
        else
            render 'edit'
        end
    end
  
  def destroy
        @post.destroy
        redirect_to root_path
  end

    private

    def find_post
        @post = Post.find(params[:id])
    end

    def post_params
        params.require(:post).permit(:title, :content)
    end
end

7.新增edit.html.haml檔案。

app/views/posts/edit.html.haml
%h1 Edit Post

= render 'form'

= link_to "Cancel", post_path

8.

app/views/posts/show.html.haml
....
= link_to "Edit", edit_post_path(@post)
= link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure you want to do this?" }
= link_to "Home", root_path

9.終端機:git status
10.終端機:git add .
11.終端機:git commit -am "Add view files and edit / destroy ability to posts"

【修改首頁文章顯示】
1.

app/controllers/posts_controller.rb
....
  def index
    @posts = Post.all.order("created_at DESC")
  end
....

2.

app/views/posts/index.html.haml
- @posts.each do |post|
    %h2= post.title
    %p
        Published at
        = time_ago_in_words(post.created_at)

= link_to "New Post", new_post_path

3.終端機:git status
4.終端機:git add .
5.終端機:git commit -am "Loop through all the posts on index"

【安裝Devise】
1.終端機:rails g devise:install
2.

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

3.

app/views/layouts/application.html.erb
....
<body>

    <p class="notice"><%= notice %></p>
    <p class="alert"><%= alert %></p>

    <%= yield %>

</body>
....

4.終端機:rails g devise user
5.終端機:rake db:migrate
6.終端機:rails g devise:views
7.終端機:git status
8.終端機:git add .
9.終端機:git commit -am "Add and setup Devise"

【建立文章和使用者的關聯性】
1.終端機:rails g migration add_user_id_to_posts user_id:integer
2.終端機:rake db:migrate
3.

app/models/post.rb
....
  belongs_to :user
....

4.

app/models/user.rb
....
  has_many :posts
....

5.

app/controllers/posts_controller.rb
def new
    @post = current_user.posts.build
end

def create
    @post = current_user.posts.build(post_params)

    if @post.save
        redirect_to @post
    else
        render 'new'
    end
end

6.終端機:rails c
7.終端機:@posts = Post.first
8.終端機:@posts.user_id = 1
9.終端機:@posts.save
10.終端機:@posts = Post.last
11.

app/views/posts/index.html.haml
- @posts.each do |post|
    %h2= link_to post.title, post
    %p
        Published at
        = time_ago_in_words(post.created_at)
        by
        = post.user.email

= link_to "New Post", new_post_path

12.終端機:git status
13.終端機:git add .
14.終端機:git commit -am "Add association between post and user"

【修改版型】
1.改成haml檔。

app/views/layouts/application.html.haml
!!!
%html
%head
    %title Liz's Rails Forum
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true
    %link{ rel: "stylesheet", href: "http://fonts.googleapis.com/css?family=Lato:300,400,700", type: "text/css"}
    %link{:rel => "stylesheet", :href => "http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.min.css"}/
    = javascript_include_tag 'application', 'data-turbolinks-track' => true
    = csrf_meta_tags
%body
    %header.main_header.clearfix
        .wrapper
            #logo
                %p Liz's Rails Forum
            #buttons
                = link_to "New Post", new_post_path
    .wrapper
        %p.notice= notice
        %p.alert= alert
    .wrapper
        = yield

2.application.css改成application.css.scss。

app/assets/stylesheets/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 bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any styles
 * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
 * file per style scope.
 *
 *= require_tree .
 *= require_self
 */

 body {
    font-family: 'Lato', sans-serif;
    background: #EDEFF0;
 }

.wrapper {
    width: 60%;
    max-width: 1140px;
    margin: 0 auto;
}

.clearfix:before, .clearfix:after {
    content: " ";
    display: table;
}

.clearfix:after {
    clear: both;
}

.main_header {
    width: 100%;
    margin: 0 auto;
    background: white;
    #logo {
        float: left;
        a {
            text-transform: uppercase;
            font-weight: 700;
            letter-spacing: -1px;
            font-size: 1.2rem;
        }
    }
    #buttons {
        float: right;
        a {
            line-height: 60px;
            background: #397CAC;
            padding: .5em 1em;
            border-radius: 0.2em;
            color: white;
            text-decoration: none;
            font-weight: 100;
        }
    }
}

#posts {
    background: white;
    padding: 2em 5%;
    border-radius: .5em;
    .post {
        margin: 1em 0;
        padding: 1em 0;
        border-bottom: 1px solid #D1d1d1;
        .title {
            margin: 0;
            a {
                color: #397CAC;
                text-decoration: none;
                font-weight: 100;
                font-size: 1.25rem;
            }
        }
        .date {
            margin-top: .25rem;
            font-size: 0.9rem;
            color: #B2BAC2;
        }
    }
}

3.

app/views/posts/index.html.haml
#posts
    - @posts.each do |post|
        .post
            %h2.title= link_to post.title, post
            %p.date
                Published at
                = time_ago_in_words(post.created_at)
                by
                = post.user.email

4.終端機:git status
5.終端機:git add .
6.終端機:git commit -am "Basic styles & structure"

【修改顯示頁面】
1.

app/views/posts/show.html.haml
#post_content
    %h1= @post.title
    %p= @post.content

    = link_to "Edit", edit_post_path(@post)
    = link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure you want to do this?" }
    = link_to "Home", root_path

2.

app/assets/stylesheets/application.css.scss
....
#post_content {
    background: white;
    padding: 2em 5%;
    border-radius: .5em;
    h1 {
        font-weight: 100;
        font-size: 2em;
        color: #397CAC;
        margin-top: 0;
    }
    p {
        color: #B2BAC2;
        font-size: 0.9rem;
        font-weight: 100;
        line-height: 1.5;
    }
}

3.git status
4.git add .
5.git commit -am "Style Show Page"

【新增認證功能】
1.

app/controllers/posts_controller.rb
....
  before_action :authenticate_user!, except: [:index, :show]
....

2.

app/views/layouts/application.html.haml
!!!
%html
%head
    %title Mackenzie's Rails Forum
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true
    %link{ rel: "stylesheet", href: "http://fonts.googleapis.com/css?family=Lato:300,400,700", type: "text/css"}
    %link{:rel => "stylesheet", :href => "http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.min.css"}/
    = javascript_include_tag 'application', 'data-turbolinks-track' => true
    = csrf_meta_tags
%body
    %header.main_header.clearfix
        .wrapper
            #logo
                %p= link_to "Liz's Rails Forum", root_path
            #buttons
                - if user_signed_in?
                    = link_to "New Post", new_post_path
                - else
                    = link_to "Sign Up", new_user_registration_path
                    = link_to "Sign In", new_user_session_path
    .wrapper
        %p.notice= notice
        %p.alert= alert
    .wrapper
        = yield

3.終端機:git status
4.終端機:git add .
5.終端機:git commit -am "Add authentication to application"

【新增文章中有評論功能】
1.終端機:rails g model Comment comment:text post:references user:references
2.終端機:rake db:migrate
3.

app/models/post.rb
....
  has_many :comments
....

4.

app/models/user.rb
....
  has_many :comments
....

5.

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  resources :posts do
    resources :comments
  end

  root 'posts#index'
end

6.終端機:rails g controller comments
7.

app/controllers/comments_controlller.rb
....
    def create
        @post = Post.find(params[:post_id])
        @comment = @post.comments.create(params[:comment].permit(:comment))
        @comment.user_id = current_user.id if current_user
        @comment.save

        if @comment.save
            redirect_to post_path(@post)
        else
            render 'new'
        end
    end
....

8.新增_comments.html.haml檔案。

app/views/comments/_comments.html.haml
.comment
    %p= comment.comment
    %p= comment.user.email

9.新增_form.html.haml檔案。

app/views/comments/_form.html.haml
= simple_form_for([@post, @post.comments.build]) do |f|
    = f.input :comment
    = f.submit

10.

app/views/posts/show.html.haml
#post_content
    %h1= @post.title
    %p= @post.content

    #comments
        %h2= @post.comments.count
        = render @post.comments

        %h3 Reply to thread
        = render "comments/form"

    = link_to "Edit", edit_post_path(@post)
    = link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure you want to do this?" }
    = link_to "Home", root_path

11.

app/controller/comments_controller.rb
....
    def edit
        @post = Post.find(params[:post_id])
        @comment = @post.comments.find(params[:id])
    end

    def update
        @post = Post.find(params[:post_id])
        @comment = @post.comments.find(params[:id])

        if @comment.update(params[:comment].permit(:comment))
            redirect_to post_path(@post)
        else
            render 'edit'
        end
    end
....

12.

app/views/comments/edit.html.haml
%h1 Edit Reply

= simple_form_for([@post, @comment]) do |f|
    = f.input :comment
    = f.submit

13.

app/controller/comments_controller.rb
....
    def destroy
        @post = Post.find(params[:post_id])
        @comment = @post.comments.find(params[:id])
        @comment.destroy
        redirect_to post_path(@post)
    end
....

14.

app/views/comments/_comments.html.haml
.comment
    %p= comment.comment
    %p= comment.user.email
    = link_to "Edit", edit_post_comment_path(comment.post, comment)
    = link_to "Delete", [comment.post, comment], method: :delete, data: { confirm: "Are you sure?" }

15.終端機:git status
16.終端機:git add .
17.終端機:git commit -am "Add comments to posts"

【修改版型】
1.

app/assets/stylesheets/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 bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any styles
 * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
 * file per style scope.
 *
 *= require_tree .
 *= require_self
 */

 body {
    font-family: 'Lato', sans-serif;
    background: #EDEFF0;
 }

.wrapper {
    width: 60%;
    max-width: 1140px;
    margin: 0 auto;
}

.clearfix:before, .clearfix:after {
    content: " ";
    display: table;
}

.clearfix:after {
    clear: both;
}

.main_header {
    width: 100%;
    margin: 0 auto;
    background: white;
    #logo {
        float: left;
        a {
            text-transform: uppercase;
            font-weight: 700;
            letter-spacing: -1px;
            font-size: 1.2rem;
            text-decoration: none;
            color:#397CAC;
        }
    }
    #buttons {
        float: right;
        a {
            line-height: 60px;
            background: #397CAC;
            padding: .5em 1em;
            border-radius: 0.2em;
            color: white;
            text-decoration: none;
            font-weight: 100;
        }
    }
}

#posts {
    background: white;
    padding: 2em 5%;
    border-radius: .5em;
    .post {
        margin: 1em 0;
        padding: 1em 0;
        border-bottom: 1px solid #D1d1d1;
        .title {
            margin: 0;
            a {
                color: #397CAC;
                text-decoration: none;
                font-weight: 100;
                font-size: 1.25rem;
            }
        }
        .date {
            margin-top: .25rem;
            font-size: 0.9rem;
            color: #B2BAC2;
        }
    }
}

.button {
    color: #397CAC;
    border: 1px solid #397CAC;
    padding: .5em 1em;
    border-radius: 0.2em;
    text-decoration: none;
    margin-right: 2%;
}

#post_content {
    background: white;
    padding: 2em 5%;
    border-radius: .5em;
    h1 {
        font-weight: 100;
        font-size: 2em;
        color: #397CAC;
        margin-top: 0;
    }
    p {
        color: #B2BAC2;
        font-size: 0.9rem;
        font-weight: 100;
        line-height: 1.5;
    }
    #comments {
        .comment {
            border-bottom: 1px solid #d1d1d1;
            padding-bottom: 1em;
            margin-bottom: 1em;
            .content {
                width: 75%;
                float: left;
            }
            .buttons {
                width: 25%;
                float: left;
                font-size: .8em;
                text-align: right;
                padding-top: 1.5em;
                a {
                    color: #397CAC;
                    border: 1px solid #397CAC;
                    padding: .5em 1em;
                    border-radius: 0.2em;
                    text-decoration: none;
                    margin-right: 2%;
                }
            }
            .comment_content {
                margin: 0;
                padding: 0;
            }
            .comment_author {
                color: #397CAC;
                margin-top: .5rem;
                font-size: 0.6em;
                font-weight: 700;
            }
        }
        input[type="submit"] {
            background: #397CAC;
            border: none;
            color: white;
            font-weight: 100;
            padding: .5em 1em;
            border-radius: .2em;
        }
        textarea {
            width: 100%;
            min-height: 200px;
            border: 1px solid #d1d1d1;
            border-radius: .2em;
            margin: 1em 0;
        }
    }
}

2.

app/views/comments/_comment.html.haml
.comment.clearfix
    .content
        %p.comment_content= comment.comment
        %p.comment_author= comment.user.email
    .buttons
        = link_to "Edit", edit_post_comment_path(comment.post, comment)
        = link_to "Delete", [comment.post, comment], method: :delete, data: { confirm: "Are you sure?" }

3.

app/views/posts/show.html.haml
#post_content
    %h1= @post.title
    %p= @post.content

    #comments
        %h2
            = @post.comments.count
            Comments
        = render @post.comments

        %h3 Reply to thread
        = render "comments/form"

    %br/
    %hr/
    %br/

    = link_to "Edit", edit_post_path(@post), class: "button"
    = link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure you want to do this?" }, class: "button"

4.終端機:git status
5.終端機:git add .
6.終端機:git commit -am "Finish up (most) styles"

【延伸閱讀】
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