赫謙小便籤

HTTP Basic Authentication in Rails 3

有的時候想要偷懶採用比較簡單的認證方式時其實用HTTP Basic Authentication就可以了,但是在Rails中要怎樣實現呢?

答案是這個:authenticate_or_request_with_http_basic

把這個加在application_controller.rb內用before_filter去弄就好,像是

然後,另外一個比較簡單的方式是:http_basic_authenticate_with,用了這個我們就不需要再自己寫before_filter弄了,像是:

其中,realm是會提示給使用者看的訊息。

Rails 的 DateTime#change

最近在寫一個東西,要取得時間並且扔到SQL內做BETWEEN運算,所以我這樣寫

    now = Time.now
    objs = Obj.where({created_at: (now..(now+1.minutes))})

這樣執行後我發現會有問題,因為我其實只要把精準度調到分鐘,但是傳入的卻是到秒,也就是說我要的是類似 2012-01-01 10:20:00 這樣的格式,但是傳入的會是 2012-01-01 10:20:30 這樣的格式

所以為了解決這個辦法,我只好寫了很髒的code

    now = Time.now; now -= now.sec.seconds

這真的夠髒了 …

後來為了某個功能(也許下篇會說)跑去APIDock查API,發現到Rails擴充了DateTime加入了change這個功能,才發現原來我API真的不熟啊 … Orz

所以要怎麼用呢?

假設我現在時間是2012-01-02 03:04:05,想要把秒數改為30的話,我可以這樣做:

    now = "2012-01-02 03:04:05"
    now.change({sec: 30})

除了改秒數,也能改年份,所有可以改的參數:

  • year
  • month
  • day
  • hour
  • min
  • sec
  • offset
  • start

但是要注意,如果修改時間部分的話(也就是hourminsec)會根據順序去重新設定值。這是什麼意思呢?

假設我只修改hour的話,會把minsec給改為00;如果我只修改min的話,那只有sec會被改變為00

所以如果我只要修改hour而其餘不變的話,就得把整個hour,min,sec都給傳進去才會正常。

Sinatra 與 Bundler共舞

Bundler是個好東西,但是Sinatra似乎不會直接讀取到用Bundler裝的Rubygems …

Okay,上網找了一下,知道了該怎做才對

另外,Bundler如果要用到Github上的套件,可以這樣寫

除了可指定branch外,也能指定ref或者是tag,十分彈性。

詳情可參考:http://gembundler.com/git.html

打完收工。

N+1 Queries

什麼是 N+1 Query

在Rails中有個ActiveRecord,它可以很容易產生資料庫關連、操作的東西。

但是關連這種東西一用不好就有可能造成災難,看看範例。

範例?

我有一個CrashLog model belongs_to Product,然後在CrashLog內用Delegate對應到Productname

    # crash_log.rb
    delegate :name, to: :product, prefix: true

如此一來可以用c = CrashLog.first; c.product_name來直接存取到product.name

可是當你用迴圈的時候就有可能遇到 N+1 Query。

譬如說我在首頁上面寫的是 @crash_logs = CrashLog.all

結果SQL跑出上百筆類似以下的東西:

但是如果當我改成 @crash_logs = CrashLog.includes :product 就會變成另外一種結果

怎麼樣?很恐怖吧!

用FactoryGirl建立假資料

安裝FactoryGirl

先在 Gemfile 內寫:

    gem 'rspec-rails'
    gem 'factory_girl_rails'

執行 bundle install

上面這種方式會順便把RSpec給裝起來,反正沒壞處

接著打開 config/application.rb

加入

    config.generators do |g|
      g.test_framework :rspec, :fixture => true, :views => false, :fixture_replacement => :factory_girl
      g.fixture_replacement :factory_girl, :dir => "spec/factories"
    end

這樣做的話會在建立Model的時候一併建立相關檔案。

第一步

假設我們建立 Post model

rails g model Post title:string content:string author:string state:string; rake db:migrate

現在可以開始建立測試資料了,打開 spec/factories/posts.rb

我們先新增一個正常的資料結構

    FactoryGirl.define do
      factory :post do
        title "MyString"
        content "MyString"
        author "HeChien"
        state "public"
      end
    end

這樣子,我們就可以在rails console內透過FactoryGirl.create :post來建立一筆Post資料了

怎麼了?你忘了?說好的,亂數呢?

誰跟你說好了=_= … 不過要亂數的話 …

假設我們要讓title變亂數,那就把title改為

    title "MyString" # 原本是這樣
    sequence(:title) { |n| "Title -- #{n}" } # 改成這樣

如此一來就會產生"Title -- 1""Title -- 2"之類的資料了

但是有的時候我們想要產生客製化的資料,譬如像是又亂數標題又是stateclosed的文章的話要怎辦?

那就看下一節啊

第二步 - 不同狀態不同內容

同樣都是定義在Post中,我們可能需要不一樣的狀態、內容,所以我們可以這樣做

    # 1. title為亂數,content為亂數,發佈為published,author為亂數,叫做 :random_life
    # 2. title為"Hello, world",content為"XD",發佈為closed,author為預設,叫做 :posted_by_hechien
    # 改成以下這樣

    FactoryGirl.define do
      factory :post do
        title "MyString"
        content "MyString"
        author "HeChien"
        state "public"

        factory :random_life do
          sequence(:title) { |n| "Title -- #{n}" }
          sequence(:content) { |n| "Randome #{n} Content" }
          state "published"
          sequence(:author) { |n| "Author: #{n}" }
        end

        factory :posted_by_hechien do
          title "Hello, world"
          content "XD"
          state "closed"
        end
      end
    end

如此一來,我們就可以用

FactoryGirl.create :random_life 來產生幾乎都亂數的資料,以及用 FactoryGirl.create :posted_by_hechien 來產生與我本人有關的資料XD

第三步 - Relationship :”>

臉紅個屁!

總是會需要建立關連的,譬如說PostComment

    FactoryGirl.define do
      factory :comment do
        post do
          FactoryGirl.create :post
        end
        author "MyString"
        content "MyString"
      end
    end

好了,你已經知道怎麼做了 (誤

db/seeds.rb 邂逅

    100.times { FactoryGirl.create :random_life }
    10.times  { FactoryGirl.create :posted_by_hechien }
    20.times  { FactoryGirl.create :post }

rake db:seed 打完收工

但事實上,假資料要用這個建立 => https://github.com/ryanb/populator

Rails 3與Ajax的邂逅

昨天晚上花了一點點時間寫了Murmur Coder,後來加上了Ajax的功能讓發出表單內容後可以不需要重新整理頁面就能夠根據response去顯示畫面。

其實Ajax這種話題應該是已經被講到爛了才對,不過我還是做一下註記好了,畢竟這種方式很Cool … Rails 把這些東西包在rails.js內了,所以我們可以很簡單的就去實作Ajax的功能。

最近這幾年比較流行的JavaScript寫法似乎是Obtrusive JavaScript,所以我在這邊會簡單的聊一下。

在以往常見的JavaScript寫法,是把一些Event會發生的事件直接寫在HTML Tag上,例如:

<p onclick="alert('Hello, world');">Click me</p>

直接把onclick寫在p標籤內,這種inline的寫法就有點像是直接把CSS寫在style屬性內一樣噁心。之後好像Prototype、jQuery之類的JavaScript framework出來了,因為能夠透過CSS Selector的方式去抓DOM,所以就能夠變成是:

    <p>Click me</p>
    <script>$('p').click(function(){alert("Hello, world");})</script>

這種把HTML與JavaScript分離的作法就叫做Obtrusive JavaScript,讓JavaScript隱藏在某個角落內,使HTML、JavaScript能夠各司其職。

回到我們的主題,就因為Obtrusive JavaScript的概念,所以Rails在整合Ajax的時候十分容易。

我們來看看如何修改form_for吧 … 假設原本是 form_for @murmur do |form| 的話,就變成是: form_for(@murmur, remote: true) do |form| … 剩下的Rails已經幫你搞定了

不過開始測試時會發現:「那我要怎樣處理Callback?」、「錯誤咧?怎麼辦啊?」

嘖嘖,這個時候就是交給rails.js來處理囉 … rails.js 中提供了四個Callback function:

  • ajax:beforeSend: 用於在送出之前的狀態
  • ajax:success: 用在執行成功的狀態,基本上Response status code為200, 201等都會進來這邊
  • ajax:complete:不論結果,只要Request執行結束就會跑來這邊,適合重設表單等工作
  • ajax:error:當遇到錯誤的時候會顯示這邊,像是Status code為4xx, 5xx時就會跑來這邊了

因為我是直接寫CoffeeScript的,所以我就直接貼CoffeeScript code

簡單的說,就是bind住那四個callbacks就好。只不過在Controller中要記住的就是,如果response content type不是HTML的話,可能會進不了success,這點要注意一下 ….

用Devise與Omniauth實作Facebook自定callback回傳

在測試OAuth登入的時候,新浪與Twitter都會原封不動的把原本傳過去的網址(包含Querystring)一起傳回到Callback網址上,可是Facebook不管怎樣就是辦不到,害我沒辦法讓Mobile Safari收到Callback後Redirect到指定的App去。

原本以為是Omniauth中我有參數沒設到,或者是Facebook設定不對,但是一直trace code卻什麼都沒發現,只好退而求其次,用別的方式去硬幹這部份。

我的作法是,先到 config/routes.rb 去硬刻一個route給Facebook這種不會把Querystring跟著弄回來的Providers用,所以如下

然後在App端這邊發送Request的時候就必須從 http://host/users/auth/facebook?url_identify=xxx 改成 http://host/users/auth/facebook/callback/xxx 了。

然後在 omniauth_callbacks_controller.rb 中手動加入 passthru 這個 action

這樣子在指定的provider action中就能正常吃到url_identify了。

實作Drag and Drop上傳方式,傳到Rails + Paperclip

終於搞出來了 … 我的媽呀,基礎知識全部忘掉就是了 … 丟臉

Okay … 記錄一下實作,不過怎樣用Rails建立Paperclip上傳機制這部份我就不多說了

主要是這段JavaScript code

還有這段Rails code

是的,就只有這樣,哈

正在想著把這個部分再用jQuery封裝得好看一點 …

其實很簡單,就是JavaScript吃到drop event的時候,把收集到的檔案資訊整理起來,用FormData封裝,然後送到後台去這樣。

因為用到XMLHttpRequest,所以乾脆用jQuery來跑好了

註:drop事件、FormData似乎都是HTML 5後才有support的功能,而且不是每個Browser都有Support,所以記得還是得實作傳統的上傳機制 …

UPDATED: 2012/06/16 02:25 –

已經更改成用jQuery的ajax來上傳了,這樣就省得再用XMLHttpRequest了

在Rails中如何重新設定counter_cache的值?

Rails中有一個counter_cache可以用,這東西的用途就是破壞正規化的(XD) …

一般來說,當我們要計算關連的資料有幾筆的話,我們會用 SELECT COUNT(id) FROMcommentsWHEREpost_id= 1 這種方式來計算,可是如果當request 量一大且資料量也大的時候這樣子其實超級麻煩的,所以可以透過counter_cache來直接cache住關連的資料筆數。

但是有時候可能因為某些原因導致沒有正確記錄到cache值,這時候我們就可以用reset_counters來自動重新整理這些值 …

也許你會覺得奇怪,為什麼不要直接把值寫入comments_count這種欄位內就好呢?那是因為 …. 這個值是readonly的囧!

為了保護欄位值的正確只好這樣做,所以我們只好用reset_counters來處理這個部分了。

使用的方式是 Model.reset_counters(id, association_name)

若以上面的例子來說的話,就是 Post.reset_counters(1, :comments)

如何?很簡單吧?

用Subdomain時自定Devise的Mailer Template

這問題其實也是忽然間被雷到 … 在這樣做之前要先去修改 config/initializers/devise.rb,設定自定的Mailer,然後我們還要自己手動產生一個Mailer。

繼續閱讀 →