[Ruby]《Ruby on Rails Tutorial》的搬运工之三

[Ruby]《Ruby on Rails Tutorial》的搬运工之三_第1张图片
ruby-on-rails-tutorials.jpg

背景:

  1. 最近比较闲,想学习ruby on rails
  2. 于是找到了https://www.railstutorial.org 上的首推教程《Ruby on Rails Tutorial》
    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第2张图片
    屏幕快照 2016-05-29 上午11.04.20.png

    这本书第一章和第二章讲了2个基本demo,实在没啥意思,姑且略过. 从第三章开始到第十二章是从0到1实现了一个类似Twitter的简单社交网站(首页,登录注册,发布推文,关注等功能). 怎么样是不是很棒?
    但是这个本书实在讲得过于详细,对于我这种本身没有那么多时间(也没那么多耐心)去一点一点看下来的童鞋,看着实在太着急了,于是准备快速整理下(把里面的干货和代码提取出来),方便大家可以分分钟coding出这个demo出来.
    当然真正学习还是要看原教程,我这个只是"扒皮版本".


原文链接

RUBY ON RAILS TUTORIAL
https://www.railstutorial.org/book/static_pages

他们的github:

railstutorial/sample_app_rails_4
https://github.com/railstutorial/sample_app_rails_4


ruby学习框架图

[Ruby]《Ruby on Rails Tutorial》的搬运工之三_第3张图片
ruby on rails is hard?

第3-7章节见:

[Ruby]RUBY ON RAILS TUTORIAL 的搬运工之一

第8-10章节见:

[Ruby]《Ruby on Rails Tutorial》的搬运工之二


下面是第11章开始


11. User microposts

用户的推文功能实现:

11.1 A Micropost model

a). 首先我们需要新建一个基本model

rails generate model Micropost content:text user:references
[Ruby]《Ruby on Rails Tutorial》的搬运工之三_第4张图片
屏幕快照 2016-05-31 下午3.30.16.png

b). 数据增加index

//db/migrate/[timestamp]_create_microposts.rb
class CreateMicroposts < ActiveRecord::Migration
  def change
    create_table :microposts do |t|
      t.text :content
      t.references :user, index: true, foreign_key: true

      t.timestamps null: false
    end
    add_index :microposts, [:user_id, :created_at]
  end
end

11.1.3 User/Micropost associations

做数据关联:


[Ruby]《Ruby on Rails Tutorial》的搬运工之三_第5张图片
User/Micropost associations

polen:
这个是ruby的一个特性.ruby希望我们的数据库原则上不要做强关联(当然你非要这么干也没人拦着你),但是它提供了一种模型关联的方法,这种关联方法有什么用? 就是数据之间的操作可以更简单也更直观一些:
举例:我们有2个model,customer和order.
如果不做关联,新建个订单需要:

@order = Order.create(order_date: Time.now, customer_id: @customer.id)

但二者如果做了belongs_to和has_many关联,那么就可以写成:

@order = @customer.orders.create(order_date: Time.now)

比较常见的几种关联关系有:

belongs_to  # 一对多,与 has_many,has_one 套用        
has_one      # 一对一          
has_many   # 一对多的另外一方            
has_and_belongs_to_many # 多对多

参照:Rubyonrails.org:Active Record 关联

11.1.4 Micropost refinements

这里是对11.1.3的完善
a). 用户的推文信息很多的时候,我们希望默认是按照"最近发布的排在最前面"的原则(即:时间倒序)
怎么实现呢? 很简单,设置个default_scope.

class Micropost < ActiveRecord::Base
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end

b). 用户和自己的推文是绑定的,如果用户被删除了,那么他对应的推文也需要全部删除,这个总不能来个for循环删除吧,那怎么办呢?
刚在不是做了关联么,加一句话即可dependent: :destroy

class User < ActiveRecord::Base
  has_many :microposts, dependent: :destroy
  attr_accessor :remember_token, :activation_token, :reset_token
...

polen:
dependent用于:设置销毁拥有者时要怎么处理关联对象
包含以下几种参数:

  • destroy:也销毁关联对象;
  • delete:直接把关联对象对数据库中删除,因此不会执行回调;
  • nullify:把外键设为 NULL,不会执行回调;
  • restrict_with_exception:有关联的对象时抛出异常;
  • restrict_with_error:有关联的对象时,向拥有者添加一个错误;

如果在数据库层设置了 NOT NULL约束,就不能使用 :nullify
选项。如果 :dependent选项没有销毁关联,就无法修改关联对象,因为关联对象的外键设置为不接受 NULL.

11.2 Showing microposts

数据层做好了,开始做UI层展示了:

[Ruby]《Ruby on Rails Tutorial》的搬运工之三_第6张图片
产品经理给的草图

11.2.1 Rendering micro posts

a). 新建controller,新建html

//新建一个controller
rails generate controller Microposts

//新建_micropost.html.erb,参照之前的_user.html.erb代码模式
touch app/views/microposts/_micropost.html.erb

b). 画基础UI (相当于iOS的tableviewCell )

//app/views/microposts/_micropost.html.erb
  • <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <%= link_to micropost.user.name, micropost.user %> <%= micropost.content %> Posted <%= time_ago_in_words(micropost.created_at) %> ago.
  • c). 界面展示controller和show.html.erb:

    //app/controllers/users_controller.rb
    ...
      def show
        @user = User.find(params[:id])
        @micropost = @user.miscropost.paginate(page: params[:page])
      end
    ...
    
    //app/views/users/show.html.erb
    <% provide(:title, @user.name) %>
    
    <% if @user.micropost.any? %>

    Miscroposts (<%= @user.micropost.count %>)

      <%= render @microposts %>
    <%= will_paginate @microposts %> <% end %>

    d). 布局调整一下,不然目前是这样的


    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第7张图片
    屏幕快照 2016-05-31 下午5.05.32.png
    //app/assets/stylesheets/custom.css.scss
    ...
    /* microposts */
    
    .microposts {
      list-style: none;
      padding: 0;
      li {
        padding: 10px 0;
        border-top: 1px solid #e8e8e8;
      }
      .user {
        margin-top: 5em;
        padding-top: 0;
      }
      .content {
        display: block;
        margin-left: 60px;
        img {
          display: block;
          padding: 5px 0;
        }
      }
      .timestamp {
        color: $gray-light;
        display: block;
        margin-left: 60px;
      }
      .gravatar {
        float: left;
        margin-right: 10px;
        margin-top: 5px;
      }
    }
    
    aside {
      textarea {
        height: 100px;
        margin-bottom: 5px;
      }
    }
    
    span.picture {
      margin-top: 10px;
      input {
        border: 0;
      }
    }
    
    

    调整之后是这样子:


    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第8张图片
    屏幕快照 2016-05-31 下午5.07.47.png

    11.3 Manipulating microposts

    接下来就是对推文的增删操作了.
    做之前先增加路由:

    //config/routes.rb
    ...
     resources :microposts, only: [:create, :destroy]
    

    11.3.1 Micropost access control

    a). 首先要查看推文的话,需要检查是否登录,之前是因为只是用户信息界面需要,所以我们的logged_in_user方法只写在了users_controller里面,现在作为公用,我们需要移到application_controller里面

    //app/controllers/application_controller.rb
    class ApplicationController < ActionController::Base
      protect_from_forgery with: :exception
      include  SessionsHelper
    
      private
    
      # Confirms a logged-in user.
      def logged_in_user
        unless logged_in?
          store_location
          flash[:danger] = "Please log in."
          redirect_to login_url
        end
      end
    end
    

    b). 作为公用之后,我们就可以用起来了:
    MicropostsController增加action,以及对应的登录状态检查

    //app/controllers/microposts_controller.rb
    class MicropostsController < ApplicationController
      before_action :logged_in_user, only: [:create, :destroy]
    
      def create 
      end 
    
      def destroy
      end
    end
    

    11.3.2 Creating microposts

    a). controller 中添加create action

    //app/controllers/microposts_controller.rb
    class MicropostsController < ApplicationController
      before_action :logged_in_user, only: [:create, :destroy]
    
      def create
        @micropost = current_user.microposts.build(micropost_params)
        if @micropost.save
          flash[:success] = "Micropost created!"
          redirect_to root_url
        else
          render 'static_pages/home'
        end
      end
    
      def destroy
      end
    
      private
    
        def micropost_params
          params.require(:micropost).permit(:content)
        end
    end
    
    

    b). home 页添加发布消息的UI

    //app/views/static_pages/home.html.erb
    <% if logged_in? %>
      
    <% else %>

    Welcome to the Sample App

    This is the home page for the Ruby on Rails Tutorial sample application.

    <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
    <%= link_to image_tag("rails.png", alt: "Rails logo"), 'http://rubyonrails.org/' %> <% end %>

    c). 新增_user_info.html.erb和_micropost_form.html.erb

    //app/views/shared/_user_info.html.erb
    <%= link_to gravatar_for(current_user, size: 50), current_user %>
    

    <%= current_user.name %>

    <%= link_to "view my profile", current_user %> <%= pluralize(current_user.microposts.count, "micropost") %>
    //app/views/shared/_micropost_form.html.erb
    <%= form_for(@micropost) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>
      
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
    <%= f.submit "Post", class: "btn btn-primary" %> <% end %>

    d). StaticPagesController添加一个变量micropost

    //app/controllers/static_pages_controller.rb
    class StaticPagesController < ApplicationController
      def home
        @micropost = current_user.microposts.build if logged_in?
      end
    

    e) .错误提示之前只是用于user,现在需要修改为通用型,所以将@user修改为object:

    //app/views/shared/_error_messages.html.erb
    <% if object.errors.any? %>
      
    The form contains <%= pluralize(object.errors.count, "error") %>.
      <% object.errors.full_messages.each do |msg| %>
    • <%= msg %>
    • <% end %>
    <% end %>

    然后将

    • users/new.html.erb,
    • users/edit.html.erb,
    • password_resets/edit.html.erb

    中的错误处理:

     <%= render 'shared/error_messages' %>
    
    

    修改为:

     <%= render 'shared/error_messages', object: f.object %>
    

    11.3.3 A porto-feed

    polen:
    feed这个东西是什么鬼?也想不出具体的定义,其实就是个list .想深入了解的童鞋可以参考:
    知乎:Feed 除了 timeline 形式,还有没有更好的内容展示方式...

    a). user model 增加def feed

    //app/models/user.rb
    class User < ActiveRecord::Base
    ...
      # Defines a proto-feed.
      # See "Following users" for the full implementation.
      def feed
        Micropost.where("user_id = ?", id)
      end
    
      private
    ...
    

    b). static_pages_controller 增加@feed_items

    //app/controllers/static_pages_controller.rb
    class StaticPagesController < ApplicationController
      def home
        if logged_in?
          @micropost  = current_user.microposts.build
          @feed_items = current_user.feed.paginate(page: params[:page])
        end
    
      end
    ...
    

    c). 写个可复用的_feed.html.erb

    //app/views/shared/_feed.html.erb
    <% if @feed_items.any? %>
      
      <%= render @feed_items %>
    <%= will_paginate @feed_items %> <% end %>

    d). home页把feed加进去

    //app/views/static_pages/home.html.erb
    <% if logged_in? %>
      

    Micropost Feed

    <%= render 'shared/feed' %>
    <% else %> ...

    e). 针对提交失败,@feed_items找不到,所以要打个预防针:

    //app/controllers/microposts_controller.rb
      def create
        @micropost = current_user.microposts.build(micropost_params)
        if @micropost.save
          flash[:success] = "Micropost created!"
          redirect_to root_url
        else
          @feed_items = []
          render 'static_pages/home'
        end
      end
    

    11.3.4 Destroying microposts

    这块开始搞删除啦...

    a). 增加个删除的按钮

    //app/views/microposts/_micropost.html.erb
    
  • <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <%= link_to micropost.user.name, micropost.user %> <%= micropost.content %> Posted <%= time_ago_in_words(micropost.created_at) %> ago. <% if current_user?(micropost.user) %> <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %> <% end %>
  • b). controller 中增加def destroydef correct_user

    //app/controllers/microposts_controller.rb
    class MicropostsController < ApplicationController
      before_action :logged_in_user, only: [:create, :destroy]
      before_action :correct_user,   only: :destroy
    
      def create
        @micropost = current_user.microposts.build(micropost_params)
        if @micropost.save
          flash[:success] = "Micropost created!"
          redirect_to root_url
        else
          @feed_items = []
          render 'static_pages/home'
        end
      end
    
      def destroy
        @micropost.destroy
        flash[:success] = "Micropost deleted"
        redirect_to request.referrer || root_url
      end
    
      def destroy
      end
    
      private
    
        def micropost_params
          params.require(:micropost).permit(:content)
        end
        
        def correct_user
          @micropost = current_user.microposts.find_by(id: params[:id])
          redirect_to root_url if @micropost.nil?
        end
    end
    
    

    11.4 Micropost images

    没有图像,用户还是怎么"装高冷"?...

    11.4.1 Basic image upload

    a). 首先引入几个库

    gem 'carrierwave',             '0.10.0'
    gem 'mini_magick',             '3.8.0'
    gem 'fog',                     '1.36.0'
    

    b). 建一个uploader以及数据库加字段picture:string

    rails generate uploader Picture
    
    rails generate migration add_picture_to_microposts picture:string
    

    c). model 中加image

    //app/models/micropost.rb
    class Micropost < ActiveRecord::Base
      belongs_to :user
      default_scope -> { order(created_at: :desc) }
      mount_uploader :picture, PictureUploader
      validates :user_id, presence: true
      validates :content, presence: true, length: { maximum: 140 }
    end
    

    polen:
    这里用到了mount_uploader这个方法,这个方法用于将model的属性和上传者绑定.(其实就是绑定数据库的某一列)
    官方解释看这里:/CarrierWave/Mount

    d). UI可以改起来啦,在"写新推文"的界面,添加上传照片的button

    //app/views/shared/_micropost_form.html.erb
    <%= form_for(@micropost, html: { multipart: true }) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>
      
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
    <%= f.submit "Post", class: "btn btn-primary" %> <%= f.file_field :picture %> <% end %>

    e). MicropostsController中参数也要把picture带进去

    //app/controllers/microposts_controller.rb
       def micropost_params
          params.require(:micropost).permit(:content, :picture)
        end
    

    f). 继续改UI,找个展示图片的地方

    //app/views/microposts/_micropost.html.erb
    ...
      
        <%= micropost.content %>
        <%= image_tag micropost.picture.url if micropost.picture? %>
      
    ...
    

    11.4.2 Image validation

    以防用户乱传各种苍老师的照片,我们需要加一些限制和校验...

    a). 格式限制:picture_uploader.rb中关于格式限制的代码解注

    //app/uploaders/picture_uploader.rb
     def extension_white_list
        %w(jpg jpeg gif png)
      end
    

    b). 大小限制

    //app/models/micropost.rb
    class Micropost < ActiveRecord::Base
      belongs_to :user
      default_scope -> { order(created_at: :desc) }
      mount_uploader :picture, PictureUploader
      validates :user_id, presence: true
      validates :content, presence: true, length: { maximum: 140 }
      validate  :picture_size
    
      private
    
        # Validates the size of an uploaded picture.
        def picture_size
          if picture.size > 5.megabytes
            errors.add(:picture, "should be less than 5MB")
          end
        end
    end
    

    如果超过大小限制,我们需要来个alert提示.

    //app/views/shared/_micropost_form.html.erb
    <%= form_for(@micropost, html: { multipart: true }) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>
      
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
    <%= f.submit "Post", class: "btn btn-primary" %> <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %> <% end %>

    11.4.3 Image resizing

    调整大小,不然丑的不得了...(图片默认都是width=1240)

    PictureUploader中加一下限制即可:
    首先要安装一下imagemagick:

    brew install imagemagick
    
    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第9张图片
    brew install imagemagick

    然后resize_to_limit进行限制:

    //app/uploaders/picture_uploader.rb
    class PictureUploader < CarrierWave::Uploader::Base
    
      # Include RMagick or MiniMagick support:
      # include CarrierWave::RMagick
      include CarrierWave::MiniMagick
      process resize_to_limit: [400, 400]
    ...
    

    可以看一下,限制前后的对比:


    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第10张图片
    屏幕快照 2016-05-31 下午8.08.54.png

    12. Following users

    真正的社交属性来了啊

    12.1 The Relationship model

    关注(或者说互粉)这种功能,按照之前的逻辑是建个表,userA,has_many(model的关联属性) user.following表。但是这种的缺点是二者的耦合性太强,一旦用户改个名字或者什么,那对应的表都要修改,这种影响的范围太大. 于是考虑到,中和下,建立个关联表,作为中间过渡,这样可以确保,用户之间的关联只通过这个relationship表来查找,而不影响user.following表. 多说无意,一图解真相:

    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第11张图片
    屏幕快照 2016-05-31 下午8.19.23.png

    具体做起来:
    a). 生成一个Relationship的model:

    rails generate model Relationship follower_id:integer followed_id:integer
    

    b). 修改表结构,增加几个字段:

    //db/migrate/[timestamp]_create_relationships.rb
    class CreateRelationships < ActiveRecord::Migration
      def change
        create_table :relationships do |t|
          t.integer :follower_id
          t.integer :followed_id
    
          t.timestamps null: false
        end
        add_index :relationships, :follower_id
        add_index :relationships, :followed_id
        add_index :relationships, [:follower_id, :followed_id], unique: true
      end
    end
    

    c). 数据库迁移

    rake db:migrate
    

    12.1.2 User/relationship associations

    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第12张图片
    屏幕快照 2016-05-31 下午8.32.00.png

    12.1.4 Followed users && 12.1.4 Followers

    followeds 看起来太丑,于是用following:
    followers:自己的粉丝
    a). 设置关联属性

    //app/models/user.rb
    class User < ActiveRecord::Base
      has_many :microposts, dependent: :destroy
      has_many :active_relationships, class_name:  "Relationship",
                                      foreign_key: "follower_id",
                                      dependent:   :destroy  
      has_many :passive_relationships, class_name:  "Relationship",
                                       foreign_key: "followed_id",
                                       dependent:   :destroy 
      has_many :following, through: :active_relationships, source: :followed
      has_many :followers, through: :passive_relationships, source: :follower
    
    ...
    

    b). 然后我们需要在user model中添加关注方法,就是,真正实现关联的action

    //app/models/user.rb
    class User < ActiveRecord::Base
    ...
      def feed
        Micropost.where("user_id = ?", id)
      end
    
      # Follows a user.
      def follow(other_user)
        active_relationships.create(followed_id: other_user.id)
      end
    
      # Unfollows a user.
      def unfollow(other_user)
        active_relationships.find_by(followed_id: other_user.id).destroy
      end
    
      # Returns true if the current user is following the other user.
      def following?(other_user)
        following.include?(other_user)
      end
      
      private
    ...
    

    polen:
    如果大家平时测试中出现类似undefined method "xxx"这种错误,常见的是2个原因:

    • 对应的model中没有设置关联属性,像如下:
      has_many :following, through: :active_relationships,  source: :followed
    
    • 对应的model中没有定义相关方法,像如下:
      def follow(other_user)
        active_relationships.create(followed_id: other_user.id)
      end
    

    12.2 A web interface for following users

    12.2.2 Stats and a follow form

    a). 路由加一下:

    //config/routes.rb
    Rails.application.routes.draw do
      root                'static_pages#home'
      get    'help'    => 'static_pages#help'
      get    'about'   => 'static_pages#about'
      get    'contact' => 'static_pages#contact'
      get    'signup'  => 'users#new'
      get    'login'   => 'sessions#new'
      post   'login'   => 'sessions#create'
      delete 'logout'  => 'sessions#destroy'
      resources :users do
        member do
          get :following, :followers
        end
      end
      
      
      resources :users
      resources :account_activations, only: [:edit]
      resources :password_resets,     only: [:new, :create, :edit, :update]
      resources :microposts,          only: [:create, :destroy]
      resources :relationships,       only: [:create, :destroy]
    ...
    

    b). 写基础UI

    //app/views/shared/_stats.html.erb
    <% @user ||= current_user %>
    
    

    c). home页增加follower相关

    //app/views/static_pages/home.html.erb
    <% if logged_in? %>
      

    Micropost Feed

    <%= render 'shared/feed' %>
    <% else %> ...

    d) .改布局

    ...
    //app/assets/stylesheets/custom.css.scss
    .gravatar {
      float: left;
      margin-right: 10px;
    }
    
    .gravatar_edit {
      margin-top: 15px;
    }
    
    .stats {
      overflow: auto;
      margin-top: 0;
      padding: 0;
      a {
        float: left;
        padding: 0 10px;
        border-left: 1px solid $gray-lighter;
        color: gray;
        &:first-child {
          padding-left: 0;
          border: 0;
        }
        &:hover {
          text-decoration: none;
          color: blue;
        }
      }
      strong {
        display: block;
      }
    }
    
    .user_avatars {
      overflow: auto;
      margin-top: 10px;
      .gravatar {
        margin: 1px 1px;
      }
      a {
        padding: 0;
      }
    }
    
    .users.follow {
      padding: 0;
    }
    
    /* forms */
    
    ...
    

    e). 继续写几个基础空间:

    //app/views/users/_follow_form.html.erb
    <% unless current_user?(@user) %>
      
    <% if current_user.following?(@user) %> <%= render 'unfollow' %> <% else %> <%= render 'follow' %> <% end %>
    <% end %>
    //app/views/users/_follow.html.erb
    <%= form_for(current_user.active_relationships.build) do |f| %>
      
    <%= hidden_field_tag :followed_id, @user.id %>
    <%= f.submit "Follow", class: "btn btn-primary" %> <% end %>
    //app/views/users/_unfollow.html.erb
    <%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
                 html: { method: :delete }) do |f| %>
      <%= f.submit "Unfollow", class: "btn" %>
    <% end %>
    
    

    f). 最后show里面完善下:

    //app/views/users/show.html.erb
    <% provide(:title, @user.name) %>
    
    <% if @user.microposts.any? %>

    Microposts (<%= @user.microposts.count %>)

      <%= render @microposts %>
    <%= will_paginate @microposts %> <% end %>

    12.2.3 Following and followers pages

    a). controller 添加following和followers方法

    //app/controllers/users_controller.rb
    class UsersController < ApplicationController
    
      before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
                                            :following, :followers]
    ...
      def following
        @title = "Following"
        @user  = User.find(params[:id])
        @users = @user.following.paginate(page: params[:page])
        render 'show_follow'
      end
    
      def followers
        @title = "Followers"
        @user  = User.find(params[:id])
        @users = @user.followers.paginate(page: params[:page])
        render 'show_follow'
      end
    
    ...
    

    b). show的UI可以做起来了

    //app/views/users/show_follow.html.erb
    <% provide(:title, @title) %>
    

    <%= @title %>

    <% if @users.any? %> <%= will_paginate %> <% end %>

    12.2.4 A working follow button the standard way

    a). Relationships

     rails generate controller Relationships
    

    b). 写create方法和destroy方法

    //app/controllers/relationships_controller.rb
    class RelationshipsController < ApplicationController
      before_action :logged_in_user
    
      def create
        user = User.find(params[:followed_id])
        current_user.follow(user)
        redirect_to user
      end
    
      def destroy
        user = Relationship.find(params[:id]).followed
        current_user.unfollow(user)
        redirect_to user
      end
    end
    

    12.2.5 A working follow button with Ajax

    ruby on rails 怎么玩Ajax呢:

    a). 引入Ajax
    ruby中使用form_for后面加个remote: true,就会自动引入Ajax

    polen:
    深入学习看这里:Ruby on Rails 實戰聖經:Ajax 應用程式

    所以我们的代码是这样的:


    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第13张图片
    屏幕快照 2016-06-01 上午11.44.08.png

    b). RelationshipsController中加入相应js的代码
    首先看这样一段代码:

    respond_to do |format| 
      format.html { redirect_to user } 
      format.js
    end
    

    polen:

    • 这里的respond_to意思是对不同的请求进行不同的处理(目的是对不同的浏览器做兼容---比如有些浏览器禁用了JavaScript).
      意思是如果浏览器请求的是html,那么我们就xxxx处理;如果请求的是js,就xxxx处理,当然也可以加xml等等...
      可以参考:ActionController::MimeResponds
    • ruby还有个respond_to?, 其实就多加了个问号,但是目的就完全不一样了,这个是看看对象是否有对应的方法函数.
      可以参考: Confused about 'respond_to' vs 'respond_to?'
      这个和iOS里的respondsToSelector是一样的:
    • (BOOL)respondsToSelector:(SEL)aSelector;
    * `do |format| `这种格式,在ruby里,"| ... |"里的内容表示参数
    * 说到do ,额外插一句ruby的for循环,一直忘记说了.
    其他大部分语言喜习惯用`for in xxx`的模式,这个在ruby也可以用,但是推荐用` xxx.each do |item|`,这样更"ruby"一些.
    另外,二者的区别是for循环之后,item的值还是在的;但each循环之后,item值当场就被释放了.
    可以参考:[“for” vs “each” in Ruby](http://stackoverflow.com/questions/3294509/for-vs-each-in-ruby)
    
    >```
    # way 1
    @collection.each do |item|
      # do whatever
    end
    
    ># way 2
    for item in @collection
      # do whatever
    end
    

    c). 添加代码:

    # app/controllers/relationships_controller.rb
    class RelationshipsController < ApplicationController
      before_action :logged_in_user
    
      def create
        # @user = User.find(params[:followed_id])
        @user = User.find(params[:relationship][:followed_id])
        current_user.follow(@user)
        respond_to do |format|
          format.html { redirect_to @user }
          format.js
        end
      end
    
      def destroy
        @user = Relationship.find(params[:id]).followed
        current_user.unfollow(@user)
        respond_to do |format|
          format.html { redirect_to @user }
          format.js
        end
      end
    end
    
    

    polen:
    纠错:
    关于获取当前这个@user
    这里原文章是@user = User.find(params[:followed_id],但这实际是有问题的.如果直接看服务端log,能看到我们的请求是:

    Parameters: {
    "utf8"=>"✓", 
    "relationship"=>{
          "followed_id"=>"4"
       }, 
    "commit"=>"Follow"}
    

    可以看到followed_id是在relationship下面的(相当于2个dictionary 包裹起来的),所以我们的解析应该是params[:relationship][:followed_id]
    所以代码应该是:

        @user = User.find(params[:relationship][:followed_id])
    

    服务端log如下:

    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第14张图片
    User.find(params[:relationship][:followed_id])

    同时看他们github的代码这一行也是如此:
    railstutorial/ sample_app_rails_4
    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第15张图片
    屏幕快照 2016-06-01 下午5.50.25.png

    d). 写最终的js文件(ajax的本质是最终能调用js文件实现局部刷新):


    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第16张图片
    The Ruby JavaScript (RJS)

    polen:
    这里还是要做个纠正:
    文章里js文件结尾是带分号的";"的,但是实际跑起来还是会报错的:

    ActionView::Template::Error 
    (Missing partial user/_unfollow with 
    {:locale=>[:en], :formats=>[:js, :html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]}. 
    Searched in:
    ...
    

    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第17张图片
    Missing partial user/_unfollow

    就是找不到_unfollow.html.erb
    这里的解决方案是 去掉分号,查看了他们的源码也是如此:
    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第18张图片
    屏幕快照 2016-06-01 下午5.59.08.png

    至于为什么?anyone knows?

    e). debug的tips:

    polen:
    这里说个debug的小tip:
    因为之前测试的时候,是不是会报错,出现udefined method等错误,所以有时候需要检查下当前的user或者current_user
    这个如果只是调试的话,代码里加一行<%= debug current_user%>即可,像这样:

    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第19张图片
    debug @user

    看到的结果是这样子( 可以详细的看到user的debug信息):
    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第20张图片
    屏幕快照 2016-06-01 下午3.58.54.png

    12.3 The status feed

    让我们用户的feed,不只是自己的推文,还有其他用户的(不就是个"朋友圈"么).
    产品经理说要做成这样子:


    [Ruby]《Ruby on Rails Tutorial》的搬运工之三_第21张图片
    屏幕快照 2016-06-01 下午3.43.12.png

    12.3.2 A first feed implementation

    第一步,所有following_ids里的我们都添加进来

    //app/models/user.rb
    ...
      def feed
        Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
      end
    ...
    

    12.3.3 Subselects

    做个小小的优化,提升sql效率:

    //app/models/user.rb
    ...
      def feed
        following_ids = "SELECT followed_id FROM relationships
                         WHERE  follower_id = :user_id"
        Micropost.where("user_id IN (#{following_ids})
                         OR user_id = :user_id", user_id: id)
      end
    ...
    


    Github:


    本文所有的代码已上传github:
    polegithub/rails_sample_app_polen

    相关:


    [Ruby]《Ruby on Rails Tutorial》的搬运工之一
    [Ruby]《Ruby on Rails Tutorial》的搬运工之二


    插一曲:

    万万没想到看完这本书的时候,才发现有中文版本,好吧
    默默的写在这里了(感谢安道童鞋的翻译):
    Ruby on Rails 教程: 通过 Rails 学习 Web 开发



    by poles

    你可能感兴趣的:([Ruby]《Ruby on Rails Tutorial》的搬运工之三)