Liz's Blog

12 in 12 challenges #4:如何用Rails4打造Pinterest

| Comments

第四週是挑戰Pinterest,除了使用之前練習過的haml、bootstrap、simple_form、acts_as_votable、devise、paperclip,這週還加入mansory-rails等的練習。如果有跟著前面三週的挑戰做下來,應該會越來越熟悉。

請配合作者Mackenzie Child影片Github使用。我的Github在此。

【開啟新專案pin_board,並擁有新增、編輯、觀看及刪除的功能】
1.終端機:rails new pin_board
2.終端機:cd pin_board
3.終端機:rails s
4.安裝hamlbootstrap-sasssimple_form

Gemfile
gem 'haml', '~> 5.0', '>= 5.0.1'
gem 'bootstrap-sass', '~> 3.3', '>= 3.3.7'
gem 'simple_form', '~> 3.5'

5.終端機:bundle install
6.終端機:rails s
7.終端機:rails g model Pin title:string description:text
8.終端機:rake db:migrate
9.終端機:rails g controller Pins
10.

app/controllers/pins_controller.rb
….
def index
end
….

11.

config/routes.rb
….
resources :pins

root "pins#index"
….

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

app/views/pins/index.html.haml
%h1 This is the index placeholder

13.

app/controllers/pins_controller.rb
….
def new
  @pin = Pin.new
end

def create
  @pin = Pin.new(pin_params)
end

private

def pin_params
  params.require(:pin).permit(:title, :description)
end
….

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

app/views/pins/new.html.haml
%h1 New Form

= render 'form'

= link_to "Back", root_path

15.終端機:rails generate simple_form:install
16.終端機:rails g simple_form:install —bootstrap
17.新增_form.html.haml檔案。

app/views/pins/_form.html.haml
= simple_form_for @pin, html: { multipart: true } do |f|
  - if @pin.errors.any?
    #errors
      %h2
        = pluralize(@pin.errors.count, "error")
        prevented this Pin from saving
      %ul
        - @pin.errors.full_messages.each do |msg|
          %li= msg

  .form-group
    = f.input :title, input_html: { class: 'form-control' }

  .form-group
    = f.input :description, input_html: { class: 'form-control' }

  = f.button :submit, class: "btn btn-primary"

18.

app/controllers/pins_controller.rb
….
def create
  @pin = Pin.new(pin_params)

  if @pin.save
    redirect_to @pin, notice: "Pin was successfully created"
  else
    render 'new'
  end
end
….

19.改成haml檔。

app/views/layouts/application.html.haml
!!! 5
%html
%head
  %title Pin Board
  = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true
  = javascript_include_tag 'application', 'data-turbolinks-track' => true
  = csrf_meta_tags

%body
  - flash.each do |name, msg|
    = content_tag :div, msg, class: "alert alert-info"

  = yield

20.

app/controllers/pins_controller.rb
before_action :find_pin, only: [:show, :edit, :update, :destroy]
….
def show
end
….
private

  def pin_params
    params.require(:pin).permit(:title, :description)
  end

  def find_pin
   @pin = Pin.find(params[:id])
  end

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

app/views/pins/show.html.haml
%h1= @pin.title
%p.description= @pin.description

= link_to "Back", root_path

21.

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

22.

app/controllers/pins_controller.rb
….
def index
  @pins = Pin.all.order("created_at DESC")
end
….

23.

app/controllers/pins_controller.rb
….
def edit
  end

def update
  if @pin.update(pin_params)
    redirect_to @pin, notice: "Pin was successfully updated"
  else
    render 'edit'
  end
end
….

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

app/views/pins/edit.html.haml
%h1 Edit Your Pin

= render 'form'

= link_to "Cancel", pin_path

25.

app/views/pins/show.html.haml
….
= link_to "Edit", edit_pin_path

26.

app/controllers/pins_controller.rb
def destroy
  @pin.destroy
  redirect_to root_path
end

27.

app/views/pins/show.html.haml
….
= link_to "Delete", pin_path, method: :delete, data: { confirm: "Are you sure?" }

28.

app/views/pins/index.html.haml
....
= link_to "New Pin", new_pin_path

29.終端機:git init
30.終端機:git status
31.終端機:git add .
32.終端機:git commit -am “Pin CRUD ability”

【會員功能】
1.

Gemfile
gem 'devise', '~> 4.3'

2.終端機:bundle install
3.終端機:rails g devise:install
4.

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

5.終端機:rails g devise:views
6.終端機:rails g devise user
7.終端機:rake db:migrate
8.重啟另一視窗終端機:rails s
9.

app/models/user.rb
has_many :pins

10.

app/models/pin.rb
belongs_to :user

11.終端機:rails g migration add_user_id_to_pins user_id:integer:index
12.終端機:rake db:migrate
13.終端機:rails c
14.終端機:@pin = Pin.first
15.終端機:@user = User.first
16.終端機:@pin.user = @user
17.終端機:@pin
18.終端機:@pin.save
19.

app/views/pins/show.html.haml
%h1= @pin.title
%p= @pin.description
%p
  Submitted by
  = @pin.user.email

%br/
….

20.終端機:@pin = Pin.last
21.

app/controllers/pins_controller.rb
before_action :authenticate_user!, except: [:index, :show]
....
def new
  @pin = current_user.pins.build
end

def create
  @pin = current_user.pins.build(pin_params)

  if @pin.save
    redirect_to @pin, notice: "Pin was successfully created"
  else
    render 'new'
  end
end

22.終端機:@pin = Pin.find(3)
23.終端機:@user = User.first
24.終端機:@pin.user = @user
25.終端機:@pin.save
26.離開console,按ctrl+D。
27.終端機:git status
28.終端機:git add .
29.終端機:git commit -am “Add devise and pin / user asssociation”

【安裝bootstrap-sass】
1.詳細安裝參考此篇

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

2.

app/views/layouts/application.html.haml
!!! 5
%html
%head
    %title Pin Board
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true
    = javascript_include_tag 'application', 'data-turbolinks-track' => true
    = csrf_meta_tags

%body
    %nav.navbar.navbar-default
        .container
            .navbar-brand= link_to "Pin Board", root_path

            - if user_signed_in?
                %ul.nav.navbar-nav.navbar-right
                    %li= link_to "New Pin", new_pin_path
                    %li= link_to "Account", edit_user_registration_path
                    %li= link_to "Sign Out", destroy_user_session_path, method: :delete
            - else
                %ul.nav.navbar-nav.navbar-right
                    %li= link_to "Sign Up", new_user_registration_path
                    %li= link_to "Sign In", new_user_session_path
    .container
        - flash.each do |name, msg|
            = content_tag :div, msg, class: "alert alert-info"
        = yield

3.刪除以下這行。

app/views/pins/index.html.haml
= link_to “New Pin”, new_pin_path
….

4.

app/views/pins/new.html.haml
.col-md-6.col-md-offset-3
  %h1 New Form
  = render 'form'
  = link_to "Back", root_path

5.

app/views/pins/edit.html.haml
.col-md-6.col-md-offset-3
  %h1 Edit Pin
  = render 'form'
  = link_to "Cancel", pin_path

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

【安裝paperclip】
1.安裝paperclip

Gemfile
gem 'paperclip', '~> 5.1'

2.終端機:bundle install
3.在另一視窗重啟終端機:rails s
4.

app/models/pin.rb
....
has_attached_file :image, :styles => { :medium => "300x300>" }
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/

5.終端機:rails g paperclip pin image
6.終端機:rake db:migrate
7.

app/views/pins/_form.html.haml
= simple_form_for @pin, html: { multipart: true } do |f|
    - if @pin.errors.any?
        #errors
            %h2
                = pluralize(@pin.errors.count, "error")
                prevented this Pin from saving
            %ul
                - @pin.errors.full_messages.each do |msg|
                    %li= msg

    .form-group
        = f.input :image, input_html: { class: 'form-control' }

    .form-group
        = f.input :title, input_html: { class: 'form-control' }

    .form-group
        = f.input :description, input_html: { class: 'form-control' }

    = f.button :submit, class: "btn btn-primary"

8.

app/controllers/pins_controller.rb
….
def pin_params
  params.require(:pin).permit(:title, :description, :image)
end
….

9.

app/views/pins/show.html.haml
= image_tag @pin.image.url(:medium)
….

10.

app/views/pins/index.html.haml
- @pins.each do |pin|
  = link_to (image_tag pin.image.url(:medium)), pin
  %h2= link_to pin.title, pin

11.

app/views/pins/edit.html.haml
.col-md-6.col-md-offset-3
  %h1 Edit Pin
  = image_tag @pin.image.url(:medium)
  = render 'form'
  = link_to "Cancel", pin_path

12.終端機:git status
13.終端機:git add .
14.終端機:git commit -am “Add image uploading with paperclip”

【安裝masonry-rails】
1.安裝masonry-rails

Gemfile
gem 'masonry-rails', '~> 0.2.4'

2.終端機:bundle install
3.在另一視窗重啟終端機:rails s
4.在jquery_ujs以下加入。

app/assets/javascripts/application.js
//= require masonry/jquery.masonry

5.

app/assets/javascripts/pins.coffee
....
$ ->
  $('#pins').imagesLoaded ->
    $('#pins').masonry
      itemSelector: '.box'
      isFitWidth: true

6.

app/views/pins/index.html.haml
#pins.transitions-enabled
    - @pins.each do |pin|
        .box.panel.panel-default
            = link_to (image_tag pin.image.url), pin
            %h2= link_to pin.title, pin
            %p.user
                Submitted by
                = pin.user.email

7.在另一視窗重啟終端機:rails s
8.

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 'masonry/transitions'
 */

@import "bootstrap-sprockets";
@import "bootstrap";

body {
    background: #E9E9E9;
}

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

nav {
    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.22);
    .navbar-brand {
        a {
            color: #BD1E23;
            font-weight: bold;
            &:hover {
                text-decoration: none;
            }
        }
    }
}

#pins {
  margin: 0 auto;
  width: 100%;
  .box {
      margin: 10px;
      width: 350px;
      box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.22);
      border-radius: 7px;
      text-align: center;
      img {
        max-width: 100%;
        height: auto;
      }
      h2 {
        font-size: 22px;
        margin: 0;
        padding: 25px 10px;
        a {
                color: #474747;
        }
      }
      .user {
        font-size: 12px;
        border-top: 1px solid #EAEAEA;
            padding: 15px;
            margin: 0;
      }
    }
}

#edit_page {
    .current_image {
        img {
            display: block;
            margin: 20px 0;
        }
    }
}

#pin_show {
    .panel-heading {
        padding: 0;
    }
    .pin_image {
        img {
            max-width: 100%;
            width: 100%;
            display: block;
            margin: 0 auto;
        }
    }
    .panel-body {
        padding: 35px;
        h1 {
            margin: 0 0 10px 0;
        }
        .description {
            color: #868686;
            line-height: 1.75;
            margin: 0;
        }
    }
    .panel-footer {
        padding: 20px 35px;
        p {
            margin: 0;
        }
        .user {
            padding-top: 8px;
        }
    }
}

textarea {
    min-height: 250px;
}

9.

app/views/pins/show.html.haml
#pin_show.row
    .col-md-8.col-md-offset-2
        .panel.panel-default
            .panel-heading.pin_image
                = image_tag @pin.image.url
            .panel-body
                %h1= @pin.title
                %p.description= @pin.description
            .panel-footer
                .row
                    .col-md-6
                        %p.user
                            Submitted by
                            = @pin.user.email
                    .col-md-6
                        .btn-group.pull-right
                            = link_to "Edit", edit_pin_path, class: "btn btn-default"
                            = link_to "Delete", pin_path, method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-default"

10.終端機:git status
11.終端機:git add .
11.終端機:git commit -am “Add jquery and styles”

【安裝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/pin.rb
….
acts_as_votable
….

7.

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

  root "pins#index"
end

8.

app/controllers/pins_controller.rb
before_action :find_pin, only: [:show, :edit, :update, :destroy, :upvote]
before_action :authenticate_user!, except: [:index, :show]
….
def upvote
  @pin.upvote_by current_user
  redirect_to :back
end
….

9.

app/views/pins/show.html.haml
#pin_show.row
    .col-md-8.col-md-offset-2
        .panel.panel-default
            .panel-heading.pin_image
                = image_tag @pin.image.url
            .panel-body
                %h1= @pin.title
                %p.description= @pin.description
            .panel-footer
                .row
                    .col-md-6
                        %p.user
                            Submitted by
                            = @pin.user.email
                    .col-md-6
                        .btn-group.pull-right
                            = link_to like_pin_path(@pin), method: :put, class: "btn btn-default" do
                                %span.glyphicon.glyphicon-heart
                                = @pin.get_upvotes.size
                            - if user_signed_in?
                                = link_to "Edit", edit_pin_path, class: "btn btn-default"
                                = link_to "Delete", pin_path, method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-default"

10.終端機:git status
11.終端機:git add .
12.終端機:git commit -am "Add and configure acts_as_votable"

1.【修改樣式】

app/views/pins/new.html.haml
.col-md-6.col-md-offset-3
    .row
        .panel.panel-default
            .panel-heading
                %h1 New Form
            .panel-body
                = render 'form'

2.

app/views/devise/registrations/edit.html.erb
<div class="col-md-8 col-md-offset-2">
  <div class="row">
    <div class="panel panel-default">
      <div class="panel-heading">
        <h2>Edit Your Account</h2>
      </div>

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

          <div class="form-group">
            <%= f.label :email %><br />
            <%= f.email_field :email, autofocus: true, class: "form-control" %>
          </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, autocomplete: "off", class: "form-control" %>
          </div>

          <div class="form-group">
            <%= f.label :password_confirmation %><br />
            <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %>
          </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, autocomplete: "off", class: "form-control" %>
          </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>
        <br>
        <%= link_to "Back", :back, class: "btn btn-default" %>
      </div>
    </div>
  </div>
</div>

3.

app/views/pins/edit.html.haml
#edit_page.col-md-6.col-md-offset-3
    .row
        .panel.panel-default
            .panel-heading
                %h1 Edit Your Pin
            .panel-body
                .current_image
                    %strong.center Current Image
                    = image_tag @pin.image.url(:medium)
                = render 'form'

4.

app/views/devise/registrations/new.html.erb
<div class="col-md-8 col-md-offset-2">
  <div class="row">
    <div class="panel panel-default">
      <div class="panel-heading">
        <h2>Sign up</h2>
      </div>
      <div class="panel-body">
        <%= 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" %>
                  </div>

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

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

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

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

5.

app/views/devise/sessions/new.html.erb
<div class="col-md-8 col-md-offset-2">
  <div class="row">
    <div class="panel panel-default">
      <div class="panel-heading">
                <h2>Sign In</h2>
            </div>

            <div class="panel-body">
                <%= 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" %>
                </div>

                <div class="form-group">
                    <%= f.label :password %><br />
                    <%= f.password_field :password, autocomplete: "off", class: "form-control" %>
                </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" %>
            </div>
        </div>
    </div>
</div>

6.終端機:git status
7.終端機:git add .
8.終端機:git commit -am "Finish up styling"

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