蟑螂窩

Bundle Install Parallel

| Comments

Bundler 在 v1.4 以後新增了 parallel install 的功能,可以增快 bundle install 的速度。

根據 https://github.com/bundler/bundler/pull/2481 這個 pull requst 來看,在 gem 的數量很多的時候,速度是相差非常大的:

1
2
bundle --path sequential  183.03s user 45.13s system 38% cpu 9:55.48 total
bundle --path parallel -j4  234.85s user 50.14s system 77% cpu 6:05.52 total

事先準備

首先要確定自己的 bundler 版本是不是大於 1.4:

1
bundle --version

如果不是的話請先升級 bundler:

1
gem install bundler -v ">=1.5.1"

確認自己 cpu 有幾個核心(以 osx 為範例):

1
sysctl -n hw.ncpu

我是用 macbook air,是四核,下面都以 4 核為範例。

使用 parallel install

升級 bundler 後就可以使用 parallel 了,只要在後面加上 -j4,4 代表 4 個 jobs:

1
bundle install -j4

也可以 global 更改預設的 jobs 數:

1
bundle config --global jobs 4

這樣就不用每次都打 -j4

使用在 capistrano 上

如果在 capistrano 的 deploy.rb 是使用 require "bundler/capistrano",只要在 deploy.rb 裡面加上:

1
set :bundle_flags,    "--deployment --quiet -j4"

這樣在跑 cap deploy 的時候, bundle install 就會加上 -j4 了。

Devise Create User Hook

| Comments

今天做案子時,需要在 Devise user create 前後做一些事情,但是又不想污染 User model,以往的方法就是寫一個 Users::RegistrationsController 去繼承 Devise::RegistrationsController,然後把整個 method copy 過來一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Users::RegistrationsController < Devise::RegistrationsController
  def create
    # 在這裡加入 before hook

    build_resource(sign_up_params)

    if resource.save
      # 在這裡加入 after hook

      if resource.active_for_authentication?
        set_flash_message :notice, :signed_up if is_navigational_format?
        sign_up(resource_name, resource)
        respond_with resource, :location => after_sign_up_path_for(resource)
      else
        set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_navigational_format?
        expire_session_data_after_sign_in!
        respond_with resource, :location => after_inactive_sign_up_path_for(resource)
      end
    else
      clean_up_passwords resource
      respond_with resource
    end
  end
end

但是這樣的寫法非常髒,而且如果 devise 有什麼修改,這邊有可能會壞掉。

今天在翻 Devise registrations_controller 的時候發現,在 3.2.1 版以後,create action 裡面多了一行:

1
yield resource if block_given?

於是,如果想要在 create user 的前後做一些事情,只需要:

1
2
3
4
5
6
7
8
9
class Users::RegistrationsController < Devise::RegistrationsController
  def create
    # 在這裡加入 before hook

    super do |resource|
      # 在這裡加入 after hook
    end
  end
end

仔細看會發現 update、destroy 也有這一行,所以也可以如法炮製用在 update、destroy user 上。

神祕的 Rails CRUD 陷阱

| Comments

最近做專案的時候,要把一個由 Form 送出去的 DELETE,變成用 Ajax 送出去,發生了很危險而且意料之外的事情。

情境

先來重現一下一開始的情境:

一開始先有一個 comment 的 Controller,裡面有一個 Destroy 的 Action,在使用者送 DELETE 到 /posts/1/comments/1時,會刪除 ID 為 1 的 Comment,接著 redirect 到 /posts/1

comments_controller.rb
1
2
3
4
5
6
7
class CommentsController < ApplicationController
  def destroy
    @comment = @post.comments.find(params[:id])
    @comment.destroy
    redirect_to post_url(@post)
  end
end

接著使用 jQuery ujs,送出 DELETE Form 到 post_comment_path(1, 1):

destroy_comment.html.erb
1
<%= link_to("Delete comment", post_comment_path(1, 1), :method => :delete) %>

狀況

這個時候覺得每次刪除 comment,都會重新刷新畫面,太擾人了,想改用 Ajax 來完成,於是我們把 HTML 多了一個, :remote => true

destroy_comment.html.erb
1
<%= link_to("Delete comment", post_comment_path(1, 1), :method => :delete, :remote => true) %>

Controller 則是維持不變。

這樣會發生什麼事情呢?如果你也覺得只會刪除 comment 那就掉入陷阱了。

實際情況

按下 Delete comment 會發生什麼事情呢?首先會刪除 comment,這應該沒有什麼問題。那接下來遇到的 redirect 會發生什麼事情呢?

如果送過來的不是 ajax,他會直接 redirect 到 /post/1 也就是 post_controller 的 show action,很理所當然的 show 出 post 內容。

但如果是 Ajax 呢?他一樣會 redirect 到 /post/1,但是卻會用 DELETE,所以是丟到 post_controller 的 destroy action,於是你的 post 就會被刪除了XDDD

為什麼會這樣呢?我也不知道XDDD,改天有空研究再另外 PO 一篇。

解決方法

其實只要依照不同的要求,回應不同的處理方法就好:

comments_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CommentsController < ApplicationController
  def destroy
    @comment = @post.comments.find(params[:id])
    @comment.destroy

    respond_to do |format|
      format.html do
        redirect_to post_url(@post)
      end
      format.js do
        head :no_content
      end
    end
  end
end

但是如果非常有自信得只改前端,肯定會掉入陷阱。第一次掉進去的時候,還真的嚇到我了,還好是在開發環境,沒刪到什麼重要資料,學了 Rails 半年多,到現在還是能給我驚奇 >////<

jQuery-ujs Link_to Issue

| Comments

在 Rails 中,常常會使用 remote => true 的方式,使連結變成 Ajax 的方式送出去:

1
link_to "Delete Comment", comment_path(@comment), :remote => true, :method => :delete

這樣的好處是不用另外寫 Ajax ,就可以輕鬆得送出 Ajax。

如果不指定 remote => true 只有指定 method,在點擊連結以後 jquery-ujs 會幫你產生一個表單,這個表單會放在 <body> 裡面,而且馬上送出去,如此,可以讓連結不只有 GET ,更可以做到 RESTful的效果。

1
link_to "Delete Comment", comment_path(@comment), :method => :delete

但是這樣的方法卻會產生一些 issue。

Filepicker.io Security in Ruby

| Comments

螢幕快照 2013-09-04 下午7.46.01.png

Filepicker 是一個非常好用的 File upload service,它可以讓你用簡單的 Javascript,就能做出超好用的圖片上傳,Backend 完全不用寫 Code,就可以讓使用者把圖片傳到 S3 上,詳細的介紹可以參考官網,這篇主要介紹 Filepicker 安全性問題。