背景:
- 最近比較閑,想學習ruby on rails
- 于是找到了https://www.railstutorial.org 上的首推教程《Ruby on Rails Tutorial》
屏幕快照 2016-05-29 上午11.04.20.png
這本書第一章和第二章講了2個基本demo,實在沒啥意思,姑且略過. 從第三章開始到第十二章是從0到1實現了一個類似Twitter的簡單社交網站(首頁,登錄注冊,發布推文,關注等功能). 怎么樣是不是很棒?
但是這個本書實在講得過于詳細,對于我這種本身沒有那么多時間(也沒那么多耐心??)去一點一點看下來的童鞋,看著實在太著急了,于是準備快速整理下(把里面的干貨和代碼提取出來),方便大家可以分分鐘coding出這個demo出來.
當然真正學習還是要看原教程,我這個只是"扒皮版本".
<br />
原文鏈接
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
<br />
ruby學習框架圖
第3-7章節見:
[Ruby]RUBY ON RAILS TUTORIAL 的搬運工之一
第8-10章節見:
[Ruby]《Ruby on Rails Tutorial》的搬運工之二
<br />
下面是第11章開始
11. User microposts
用戶的推文功能實現:
11.1 A Micropost model
a). 首先我們需要新建一個基本model
rails generate model Micropost content:text user:references
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
做數據關聯:
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層展示了:
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
<li id="micropost-<%= micropost.id %>">
<%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
<span class="user"><%= link_to micropost.user.name, micropost.user %></span>
<span class="content"><%= micropost.content %></span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
</span>
</li>
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) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
</aside>
<div class="col-md-8">
<% if @user.micropost.any? %>
<h3>Miscroposts (<%= @user.micropost.count %>)</h3>
<ol class="microposts">
<%= render @microposts %>
</ol>
<%= will_paginate @microposts %>
<% end %>
</div>
</div>
d). 布局調整一下,不然目前是這樣的
//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;
}
}
調整之后是這樣子:
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? %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= render 'shared/user_info' %>
</section>
<section class="micropost_form">
<%= render 'shared/micropost_form' %>
</section>
</aside>
</div>
<% else %>
<div class="center jumbotron">
<h1>Welcome to the Sample App</h1>
<h2>
This is the home page for the
<a >Ruby on Rails Tutorial</a>
sample application.
</h2>
<%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
</div>
<%= 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 %>
<h1><%= current_user.name %></h1>
<span><%= link_to "view my profile", current_user %></span>
<span><%= pluralize(current_user.microposts.count, "micropost") %></span>
//app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
</div>
<%= 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? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(object.errors.count, "error") %>.
</div>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% 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? %>
<ol class="microposts">
<%= render @feed_items %>
</ol>
<%= will_paginate @feed_items %>
<% end %>
d). home頁把feed加進去
//app/views/static_pages/home.html.erb
<% if logged_in? %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= render 'shared/user_info' %>
</section>
<section class="micropost_form">
<%= render 'shared/micropost_form' %>
</section>
</aside>
<div class="col-md-8">
<h3>Micropost Feed</h3>
<%= render 'shared/feed' %>
</div>
</div>
<% 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
<li id="micropost-<%= micropost.id %>">
<%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
<span class="user"><%= link_to micropost.user.name, micropost.user %></span>
<span class="content"><%= micropost.content %></span>
<span class="timestamp">
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 %>
</span>
</li>
b). controller 中增加def destroy
和def 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 %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
</div>
<%= f.submit "Post", class: "btn btn-primary" %>
<span class="picture">
<%= f.file_field :picture %>
</span>
<% 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
...
<span class="content">
<%= micropost.content %>
<%= image_tag micropost.picture.url if micropost.picture? %>
</span>
...
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 %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
</div>
<%= f.submit "Post", class: "btn btn-primary" %>
<span class="picture">
<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
</span>
<% end %>
<script type="text/javascript">
$('#micropost_picture').bind('change', function() {
var size_in_megabytes = this.files[0].size/1024/1024;
if (size_in_megabytes > 5) {
alert('Maximum file size is 5MB. Please choose a smaller file.');
}
});
</script>
11.4.3 Image resizing
調整大小,不然丑的不得了...(簡書圖片默認都是width=1240)
PictureUploader中加一下限制即可:
首先要安裝一下imagemagick:
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]
...
可以看一下,限制前后的對比:
12. Following users
真正的社交屬性來了啊
12.1 The Relationship model
關注(或者說互粉)這種功能,按照之前的邏輯是建個表,userA,has_many
(model的關聯屬性) user.following表。但是這種的缺點是二者的耦合性太強,一旦用戶改個名字或者什么,那對應的表都要修改,這種影響的范圍太大. 于是考慮到,中和下,建立個關聯表,作為中間過渡,這樣可以確保,用戶之間的關聯只通過這個relationship表來查找,而不影響user.following表. 多說無意,一圖解真相:
具體做起來:
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
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 %>
<div class="stats">
<a href="<%= following_user_path(@user) %>">
<strong id="following" class="stat">
<%= @user.following.count %>
</strong>
following
</a>
<a href="<%= followers_user_path(@user) %>">
<strong id="followers" class="stat">
<%= @user.followers.count %>
</strong>
followers
</a>
</div>
c). home頁增加follower相關
//app/views/static_pages/home.html.erb
<% if logged_in? %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= render 'shared/user_info' %>
</section>
<section class="stats">
<%= render 'shared/stats' %>
</section>
<section class="micropost_form">
<%= render 'shared/micropost_form' %>
</section>
</aside>
<div class="col-md-8">
<h3>Micropost Feed</h3>
<%= render 'shared/feed' %>
</div>
</div>
<% 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) %>
<div id="follow_form">
<% if current_user.following?(@user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
</div>
<% end %>
//app/views/users/_follow.html.erb
<%= form_for(current_user.active_relationships.build) do |f| %>
<div><%= hidden_field_tag :followed_id, @user.id %></div>
<%= 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) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
<section class="stats">
<%= render 'shared/stats' %>
</section>
</aside>
<div class="col-md-8">
<% if @user.microposts.any? %>
<h3>Microposts (<%= @user.microposts.count %>)</h3>
<ol class="microposts">
<%= render @microposts %>
</ol>
<%= will_paginate @microposts %>
<% end %>
</div>
</div>
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) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= gravatar_for @user %>
<h1><%= @user.name %></h1>
<span><%= link_to "view my profile", @user %></span>
<span><b>Microposts:</b> <%= @user.microposts.count %></span>
</section>
<section class="stats">
<%= render 'shared/stats' %>
<% if @users.any? %>
<div class="user_avatars">
<% @users.each do |user| %>
<%= link_to gravatar_for(user, size: 30), user %>
<% end %>
</div>
<% end %>
</section>
</aside>
<div class="col-md-8">
<h3><%= @title %></h3>
<% if @users.any? %>
<ul class="users follow">
<%= render @users %>
</ul>
<%= will_paginate %>
<% end %>
</div>
</div>
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 應用程式
所以我們的代碼是這樣的:
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如下:
同時看他們github的代碼這一行也是如此:
railstutorial/sample_app_rails_4
d). 寫最終的js文件(ajax的本質是最終能調用js文件實現局部刷新):
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:
...
就是找不到_unfollow.html.erb
這里的解決方案是 去掉分號,查看了他們的源碼也是如此:
至于為什么?anyone knows?
e). debug的tips:
polen:
這里說個debug的小tip:
因為之前測試的時候,是不是會報錯,出現udefined method等錯誤,所以有時候需要檢查下當前的user或者current_user
這個如果只是調試的話,代碼里加一行<%= debug current_user%>
即可,像這樣:
debug @user
看到的結果是這樣子(可以詳細的看到user的debug信息):
屏幕快照 2016-06-01 下午3.58.54.png
12.3 The status feed
讓我們用戶的feed,不只是自己的推文,還有其他用戶的(不就是個"朋友圈"么??).
產品經理說要做成這樣子:
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
...
<br />
Github:
本文所有的代碼已上傳github:
polegithub/rails_sample_app_polen
相關:
[Ruby]《Ruby on Rails Tutorial》的搬運工之一
[Ruby]《Ruby on Rails Tutorial》的搬運工之二
<br />
插一曲:
萬萬沒想到看完這本書的時候,才發現有中文版本,好吧
默默的寫在這里了(感謝安道童鞋的翻譯):
Ruby on Rails 教程: 通過 Rails 學習 Web 開發
<br />
by poles