Liz's Blog

12 in 12 Challenges #12:如何用Rails4做出Dribbble

| Comments

作者:Mackenzie Child
影片:How to build a dribbble type app in Rails 4
Github:muse

因應gem版本不同,可參考我練習的Github

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

【新增資料庫欄位及controller】
1.終端機:rails g model post title:string link:string description:text
2.終端機:rake db:migrate
3.終端機:rails g controller Posts
4.

config/routes.rb
....
  resources :posts
  
  root "posts#index"
end

5.

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

6.終端機:git status
7.終端機:git add .
8.終端機:git commit -am "Generated Post model and controller and added routes"

【安裝gems】
1.hamlsimple_formdevisepaperclip

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

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

app/views/posts/index.html.haml
%h1 This is the placeholder ;)

6.終端機:git status
7.終端機:git add .
8.終端機:git commit -am "Add application gems and post view files"

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

app/controllers/posts_controller.rb
....
    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
    end

    def destroy
    end

    private

    def find_post
    end

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

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

app/views/posts/new.html.haml
%h1 Add New Inspiration

= render 'form'

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

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

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

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

5.

app/controllers/posts_controller.rb
....
before_action :find_post, only: [:show, :edit, :update, :destroy]
....
    def find_post
    @post = Post.find(params[:id])
  end
....

6.

app/views/posts/index.html.haml
- @posts.each do |post|
  %h2= link_to post.title, post

7.

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

8.

app/views/posts/index.html.haml
....
= link_to "Add New Inspiration", new_post_path

9.

app/views/posts/show.html.haml
....
= link_to "Home", root_path

10.

app/controllers/posts_controller.rb
def update
    if @post.update(post_params)
        redirect_to @post
    else
        render 'edit'
    end
end

def destroy
    @post.destroy
    redirect_to root_path
end

11.

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

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

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

= render 'form'

13.終端機:git status
14.終端機:git add .
15.終端機:git commit -am "Add create update destroy ability for posts"

【安裝會員功能】
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:views
5.終端機:rails g devise User
6.終端機:rake db:migrate
7.終端機:rails g migration add_user_id_to_post user_id:integer
8.終端機:rake db:migrate
9.

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

10.

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

11.

app/controllers/posts_controller.rb
....
  before_action :authenticate_user!, except: [:index, :show]
....
    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
....

12.終端機:rails c
13.終端機:@post = Post.last
14.終端機:@post.user_id = 1
15.終端機:@post.save
16.ctrl+D
17.終端機:git status
18.終端機:git add .
19.終端機:git commit -am "Added and setup devise"

【新增使用者姓名】
1.終端機:rails g migration add_name_to_user name:string
2.終端機:rake db:migrate
3.

app/views/devise/registrations/new.html.erb
....
<div class="form-inputs">
  <%= f.input :name, required: true, autofocus: true %>
  <%= f.input :email, required: true %>
....

4.

app/views/devise/registrations/edit.html.erb
....
<div class="form-inputs">
  <%= f.input :name, required: true, autofocus: true %>
  <%= f.input :email, required: true %>
....

5.安裝Devise3,請參考此篇

app/controllers/application_controller.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.

app/views/posts/show.html.haml
....
%p= @post.description
%p= @post.user.name
....

7.終端機:git status
8.終端機:git add .
9.終端機:git commit -am "Add name to users"

【安裝paperclip】
1.

app/models/post.rb
....
  has_attached_file :image, styles: { medium: "700x500#", small: "350x250#" }
  validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
end

2.終端機:rails g paperclip post image
3.終端機:rake db:migrate
3.

app/views/posts/_form.html.haml
= simple_form_for @post do |f|
    = f.input :image
    = f.input :title
    = f.input :link
    = f.input :description

    = f.button :submit

4.

app/controllers/posts_controller.rb
....
  def post_params
    params.require(:post).permit(:title, :link, :description, :image)
  end
....

5.

app/views/posts/show.html.haml
= image_tag @post.image.url(:medium)
....

6.

app/views/posts/index.html.haml
- @posts.each do |post|
    = link_to (image_tag post.image.url(:small))
....

7.

.gitignore
/public/system/posts/images/

8.終端機:git status
9.終端機:git add .
10.終端機:git commit -am "Setup paperclip for image uploading"

【新增評論功能】
1.終端機:rails g model comment content: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
....
  resources :posts do
    resources :comments
    end
....

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

app/controllers/comments_controller.rb
....
  before_action :authenticate_user!
    
  def create
        @post = Post.find(params[:post_id])
        @comment = Comment.create(params[:comment].permit(:content))
        @comment.user_id = current_user.id
        @comment.post_id = @post.id

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

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

app/views/comments/_form.html.haml
= simple_form_for([@post, @post.comments.build]) do |f|
    = f.input :content, label: "Reply to thread"
    = f.button :submit, class: "button"

9.

app/views/posts/show.html.haml
= image_tag @post.image.url(:medium)
%h1= @post.title
%p= @post.link
%p= @post.description
%p= @post.user.name

#comments
    %h2.comment_count= pluralize(@post.comments.count, "Comment")
    - @comments.each do |comment|
        .comment
            %p.username= comment.user.name
            %p.content= comment.content

    = render "comments/form"

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

10.

app/controllers/posts_controller.rb
....
  def show
    @comments = Comment.where(post_id: @post)
  end
....

11.

app/views/posts/index.html.haml
- @posts.each do |post|
    = link_to (image_tag post.image.url(:small)), post
    %h2= link_to post.title, post
    %p
        = post.comments.count
        Comments
    
= link_to "Add New Inspiration", new_post_path

12.終端機:git status
13.終端機:git add .
14.終端機:git commit -am "Add comments to posts"

【安裝acts_as_votable】
1.acts_as_votable

Gemfile
gem 'acts_as_votable', '~> 0.10.0'

2.終端機:bundle install
3.用另一視窗重啟終端機:rails s
4.終端機:rails g acts_as_votable:migration
5.終端機:rake db:migrate
6.

app/models/post.rb
acts_as_votable
....

7.

config/routes.rb
....
  resources :posts do
    member do
        get "like", to: "posts#upvote"
        get "dislike", to: "posts#downvote"
    end
    resources :comments
    end
  root 'posts#index'
end

8.

app/controllers/posts_controller.rb
....
  before_action :find_post, only: [:show, :edit, :update, :destroy, :upvote, :downvote]
....
    def upvote
        @post.upvote_by current_user
        redirect_to :back
    end

    def downvote
        @post.downvote_from current_user
        redirect_to :back
    end
....

9.

app/views/posts/show.html.haml
....
%p= @post.user.name
%p
    = @post.get_upvotes.size
    Likes
%p
    = @post.get_downvotes.size
    Dislikes
= link_to "Like", like_post_path(@post), method: :get
= link_to "Dislike", dislike_post_path(@post), method: :get
....

10.

app/views/posts/index.html.haml
....
    Comments
    %p
    = post.get_likes.size
    Likes
....

11.終端機:git status
12.終端機:git add .
13.終端機:git commit -am "Add and setup voting"

【改成haml】
1.改成haml檔。

app/views/layouts/application.html.haml
!!!
%html
%head
    %title Muse
    = stylesheet_link_tag    'application', media: 'all'
    = javascript_include_tag 'application'
    %link{:rel => "stylesheet", :href => "http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.min.css"}
    %link{:rel => "stylesheet", :href => "http://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css"}
    = csrf_meta_tags
%body
    %header
        .wrapper.clearfix
            #logo= link_to "Muse", root_path
            %nav
                - if user_signed_in?
                    = link_to current_user.name, edit_user_registration_path
                    = link_to "Add New Inspiration", new_post_path, class: "button"
                - else
                    = link_to "Sign in", new_user_session_path
                    = link_to "Sign Up", new_user_registration_path, class: "button"
    %p.notice= notice
    %p.alert= alert
    .wrapper
        = yield

2.終端機:git status
3.終端機:git add .
4.終端機:git commit -am "Convert application layout to haml and added header"

【修改版型】
1.將css改成scss檔。

app/assets/stylesheets/application.css.scss
/*
 *= require_self
 */

body {
    font-family: "Proxima Nova";
    font-weight: 100;
}

h1, h2, h3, h4, h5, h6 {
    font-weight: 100;
}

.wrapper {
    width: 80%;
    margin: 0 auto;
}

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

.clearfix:after {
    clear: both;
}

.button {
    border: 1px solid #9B9B9B;
    padding: .7rem 1.25rem;
    border-radius: .3rem;
    outline: none;
    background: white;
    color: #9B9B9B;
}

header {
    width: 100%;
    padding: 2rem 0;
    border-bottom: 1px solid #E4E4E4;
    #logo {
        float: left;
        font-size: 1.75rem;
        a {
            color: #333233;
            text-decoration: none;
            &:hover {
                color: #50A7C7;
            }
        }
    }
    nav {
        float: right;
        a {
            margin-left: 1.5rem;
            line-height: 2;
            color: #9B9B9B;
            text-decoration: none;
            &:hover {
                color: #50A7C7;
            }
        }
    }
}

@import 'home.css.scss';
@import 'posts.scss';

2.新增home.css.scss檔案。

/app/assets/stylesheets/home.css.scss
#intro {
    margin: 2.75rem 0 1.75rem 0;
    text-align: center;
    font-size: 1.75rem;
    line-height: 1;
    span {
        font-size: 1.25rem;
        color: #9B9B9B;
    }
}
#posts {
    .post {
        background: #F8F9FA;
        width: 30%;
        float: left;
        margin: 1rem 1.5%;
        border: 1px solid #E4E4E4;
        border-radius: 0.3rem;
        .post_image {
            height: 200px;
            overflow: hidden;
            img {
                width: 100%;
                border-radius: .3rem .3rem 0 0;
            }
        }
        .post_content {
            h2 {
                margin: 0;
                font-weight: 100;
                padding: 1rem 5%;
                border-bottom: 1px solid #E4E4E4;
                font-size: 1.25rem;
                a {
                    text-decoration: none;
                    color: #333233;
                    &:hover {
                        color: #50A7C7;
                    }
                }
            }
            .data {
                padding: .75rem 5%;
                color: #969696;
                .username, .buttons {
                    margin: 0;
                    font-size: .8rem;
                }
                .username {
                    float: left;
                }
                .buttons {
                    float: right;
                    span {
                        margin-left: .5rem;
                    }
                }
            }
        }
    }
}

3.新增posts.scss檔案。

app/assets/stylesheets/posts.scss
#post_show {
    padding-top: 2rem;
    .post_image_description {
        width: 50%;
        float: left;
        margin-right: 5%;
    }
    .post_data {
        width: 15%;
        float: left;
        .button {
            width: 100%;
            display: inline-block;
            padding: .75rem 0;
            margin-bottom: 1rem;
            text-align: center;
            text-decoration: none;
            color: #969696;
            &:hover {
                color: #50A7C7;
                border: 1px solid #50A7C7;
            }
        }
        .data {
            width: 100%;
            display: block;
            padding: .75rem 0;
            border-bottom: 1px solid #E9E9E9;
            text-decoration: none;
            color: #969696;
            margin: 0;
        }
    }
    #random_post {
        width: 25%;
        margin-left: 5%;
        margin-top: -3.2rem;
        float: left;
        h3 {
            font-size: 1rem;
        }
        .post {
            background: #F8F9FA;
            border: 1px solid #E4E4E4;
            border-radius: 0.3rem;
            .post_image {
                height: 200px;
                overflow: hidden;
                img {
                    width: 100%;
                    border-radius: .3rem .3rem 0 0;
                }
            }
            .post_content {
                h2 {
                    margin: 0;
                    font-weight: 100;
                    padding: 1rem 5%;
                    border-bottom: 1px solid #E4E4E4;
                    font-size: 1.25rem;
                    a {
                        text-decoration: none;
                        color: #333233;
                        &:hover {
                            color: #50A7C7;
                        }
                    }
                }
                .data {
                    padding: .75rem 5%;
                    color: #969696;
                    .username, .buttons {
                        margin: 0;
                        font-size: .8rem;
                    }
                    .username {
                        float: left;
                    }
                    .buttons {
                        float: right;
                        span {
                            margin-left: .5rem;
                        }
                    }
                }
            }
        }
    }
    h1 {
        margin: 0;
        color: #333233;
    }
    img {
        width: 100%;
        border-radius: .3rem;
    }
    .username {
        color: #969696;
        margin: 0 0 1.5rem 0;
    }
    .description {
        margin: 2rem 0;
        font-size: 1.1rem;
        line-height: 1.5;
        color: #969696;
    }
}

#comments {
    padding-bottom: 4rem;
    width: 50%;
    .comment_count {
        border-bottom: 1px solid #E9E9E9;
        padding-bottom: .5rem;
        margin-bottom: 0;
    }
    .comment {
        padding: 2rem 0;
        border-bottom: 1px solid #E9E9E9;
        padding-left: 5%;
        .username {
            font-size: 1.3rem;
            color: #333233;
            margin: 0 0 .5rem 0;
        }
        .content {
            margin: 0;
            color: #969696;
        }
    }
    .comment_content {
        margin-top: 2.5rem;
        textarea {
            width: 100%;
            margin: 1rem 0;
            min-height: 150px;
            border: 1px solid #E4E4E4;
            background: #F8F9FA;
            border-radius: 0.3rem;
        }
    }
}

4.

app/views/posts/index.html.haml
- if user_signed_in?
    %p#intro
        Hello
        = current_user.name
        %br/
        %span
            Share your inspiration and see what's inspiring others.
- else
    %p#intro
        What's your muse?
        %br/
        %span
            Share your inspiration and see what's inspiring others.

#posts
    - @posts.each do |post|
        .post
            .post_image
                = link_to (image_tag post.image.url(:small)), post
            .post_content
                .title
                    %h2= link_to post.title, post
                .data.clearfix
                    %p.username
                        Shared by
                        = post.user.name
                    %p.buttons
                        %span
                            %i.fa.fa-comments-o
                            = post.comments.count
                        %span
                            %i.fa.fa-thumbs-o-up
                            = post.get_likes.size

5.

app/views/posts/show.html.haml
#post_show
    %h1= @post.title
    %p.username
        Shared by
        = @post.user.name
        about
        = time_ago_in_words(@post.created_at)
    .clearfix
        .post_image_description
            = image_tag @post.image.url(:medium)
            .description= simple_format(@post.description)
        .post_data
            = link_to "Visit Link", @post.link, class: "button"
            = link_to like_post_path(@post), method: :get, class: "data" do
                %i.fa.fa-thumbs-o-up
                = pluralize(@post.get_upvotes.size, "Like")
            = link_to dislike_post_path(@post), method: :get, class: "data" do
                %i.fa.fa-thumbs-o-down
                = pluralize(@post.get_downvotes.size, "Dislike")
            %p.data
                %i.fa.fa-comments-o
                = pluralize(@post.comments.count, "Comment")
            - if @post.user == current_user
                = link_to "Edit", edit_post_path(@post), class: "data"
                = link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure?" }, class: "data"
        
#comments
    %h2.comment_count= pluralize(@post.comments.count, "Comment")
    - @comments.each do |comment|
        .comment
            %p.username= comment.user.name
            %p.content= comment.content

    = render "comments/form"

6.終端機:git status
7.終端機:git add .
8.終端機:git commit -am "Added structure and styling"

【移除turbolinks】
1.刪除turbolinks部分。

app/views/layouts/application.html.erb
....
  = stylesheet_link_tag    'application', media: 'all'
  = javascript_include_tag 'application'
....

2.移除turbolinks部分。

app/assets/javascripts/application.js
-//= require turbolinks

3.移除turbolinks部分。

Gemfile
gem 'turbolinks'

4.終端機:bundle
5.終端機:git status
6.終端機:git add .
7.終端機:git commit -am "Remove turbolinks"

【新增random post】
1.

app/views/posts/show.html.haml
#post_show
    %h1= @post.title
    %p.username
        Shared by
        = @post.user.name
        about
        = time_ago_in_words(@post.created_at)
    .clearfix
        .post_image_description
            = image_tag @post.image.url(:medium)
            .description= simple_format(@post.description)
        .post_data
            = link_to "Visit Link", @post.link, class: "button"
            = link_to like_post_path(@post), method: :get, class: "data" do
                %i.fa.fa-thumbs-o-up
                = pluralize(@post.get_upvotes.size, "Like")
            = link_to dislike_post_path(@post), method: :get, class: "data" do
                %i.fa.fa-thumbs-o-down
                = pluralize(@post.get_downvotes.size, "Dislike")
            %p.data
                %i.fa.fa-comments-o
                = pluralize(@post.comments.count, "Comment")
            - if @post.user == current_user
                = link_to "Edit", edit_post_path(@post), class: "data"
                = link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure?" }, class: "data"
        #random_post
            %h3 Random Inspiration
            .post
                .post_image
                    = link_to (image_tag @random_post.image.url(:medium)), post_path(@random_post)
                .post_content
                    .title
                        %h2= link_to @random_post.title, post_path(@random_post)
                    .data.clearfix
                        %p.username
                            Shared by
                            = @random_post.user.name
                        %p.buttons
                            %span
                                %i.fa.fa-comments-o
                                = @random_post.comments.count
                            %span
                                %i.fa.fa-thumbs-o-up
                                = @random_post.get_likes.size
​
#comments
    %h2.comment_count= pluralize(@post.comments.count, "Comment")
    - @comments.each do |comment|
        .comment
            %p.username= comment.user.name
            %p.content= comment.content

    = render "comments/form"

2.

app/controllers/posts_controller.rb
....
  def show
    @comments = Comment.where(post_id: @post)
    @random_post = Post.where.not(id: @post).order("RANDOM()").first
  end
....

3.終端機:git status
4.終端機:git add .
5.終端機:git commit -am "Add random post to post show"

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