Bruce 的玩具間

my works and notes on ruby, rails, git, ubuntu linux, mac os x, etc...

Posts match “ rails ” tag:

Deploy an existing rails 3.2 app to heroku

| Comments

Assume that your project directory is ~/my_project. Using MySQL And manage you code with git. You want to deploying it to heroku and keeping working locally with MySQL. This is my solution:

Pre-requirements

Download and install heroku toolbelt.

After installed, you should got heroku command in CLI now.

cd to your project directory:

cd ~/my_project

Set up heroku app

Login to heroku with email and password:

heroku login

You might be asked to generate SSH publich key at first time login

Create and rename an heroku app

heroku create
heroku rename MY_PROJECT

Code modifications

Heroku provide 3 ways to use asset pipeline. I currently use "compiling assets locally". It's simple but less automatically.

Make sure your had set production environmnet correctly in local. Or you may encounter problems on running rake … RAILS_ENV=production.

rake db:create RAILS_ENV=production

RAILS_ENV=production bundle exec rake assets:precompile

git add .
git commit -m "precompile assets"

To keep local working with MySQL. I opened a branch for heroku depolyment called heroku.

git branch heroku
git checkout heroku

Edit ~/my_project/Gemfile. change:

gem 'mysql2'

To this:

gem 'pg'

Now run bundle install

bundle install

Last, deploy to heroku

git push heroku heroku:master

If you are pushing local master branch to heroku than you should use git push heroku master

Notes

I like to keep heroku-related modifications to heroku branch. So merge frequently:

# modifications don on master...
git checkout heroku
git merge master --no-ff
git push heroku heroku:master

Links

Upgrade natescherer:apn_on_rails to new version of PRX:apn_on_rails gem

| Comments

中文版: natescherer:apn_on_rails 換到新版 PRX:apn_on_rails 筆記 - 小B雜Blog

Major changes

  • send_notifications method in PRX's did not accept an array of notifications as paramter. It sends all unsent notifications.
  • Add APN::App. Put cert in database and allow you to send to multiple iOS apps with 1 rails app
  • Add APN::Group. Allow you to send group notifications. (But it still send one by one to Apple APN server)

Steps

Edit Gemfile

gem 'apn_on_rails'

And

bundle install

Migrateions

rails generate apn_on_rails:install
  • If it complains about some migrations have a same name, rename the old migrations and run again, delete the duplicated and rename back.
  • README says that maigration 002 create_apn_notifications was modified by hand and may cause a problem. See Upgrade Notes if needed.

And

rake db:migrate

Create first APN::App and move cert into database

Copy the content of /config/apple_push_notification_development.pem and apple_push_notification_production and save into APN::app#apn_dev_cert and APN::app#apn_prod_cert. See this post. (but you need to reslove the massive-assignment problem before running it on rails 3.2)

Modifiy APN::Notification.send_notifications and let it accept an array of notificaitons

Because that the natescherer:apn_on_rails folk accepts an array of notificaitons to send. But the PRX one are not. So I added it by hand.

Create config/initializers/apn_notification_reopen.rb

class APN::Notification

  # ...

  def self.send_notifications(notifications = APN::Notification.where("sent_at is null"))
    app_ids = notifications.map {|n| n.device.app_id }.uniq
    app_ids.each do |app_id|
      begin
        if (app = APN::App.where(:id => app_id).first)
          the_cert = Rails.env.production? ? app.apn_prod_cert : app.apn_dev_cert
          APN::Connection.open_for_delivery({:cert => the_cert}) do |conn, sock|
            unsent_notifications = notifications.select {|n| n.device.app_id == app_id }
            unsent_notifications.each do |noty|
              conn.write(noty.message_for_sending)
              noty.sent_at = Time.now
              noty.save
            end
          end
        end
      rescue Exception => e
        STDERR.puts e.message
        raise e
      end
    end

  end
end

Troubleshooting

NameError: uninitialized constant APN::App::RAILS_ENV

Add following line in config/environment.rb:

APN::App::RAILS_ENV = Rails.env

Errno::ENOENT: No such file or directory - /config/apple_push_notification_development.pem

run:

script/rails generate configatron:install

It will generate configatron files. Edit config/configatron/defaults.rb add:

configatron.apn.cert = ''

Rails 打基礎 - Rails Guides 3.2.13 閱讀筆記 Part 1

| Comments

Rails Guides 是 Rails 官方提供的線上教學文件,還蠻基礎的。

1到9章包含了 Migration, Model, View, Controller, Routes 等寫一個 Rails app 最基本的部份,摸索過 Rails 一段時間的人應該多少都能做出一個簡單的 Blog 或論壇,畢竟還有 scaffold 可以參考。那麼為什麼還要讀 Rails Guides 呢?

就像是節奏型鼓手,反正學會這3個節奏就可以打很多POP歌了。但很快就會遇到瓶頸,倒頭來還是得用打點練習重新打基礎,反而還得克服之前養成的壞習慣。

hh |x-x-x-x-x-x-x-x-|x-x-x-x-x-x-x-x-|x-x-x-x-x-x-x-x-|
S  |----o-------o---|----o-------o---|----o-------o---|
B  |o-------o-------|o-------o-o-----|o-----o-o-------|

小弟拖了很久才終於狠下心開始讀 Rails Guides,這邊整理我讀到的、值得注意的部分,以及我之前不知道的手法。這系列適合的對象是 Rails 學一小段時間、已經能做出簡單的 Blog、論壇的人,或者雖然使用 Rails 很久了、但一直都隨便亂寫的人 (例如我XD)。

另外,想開始讀 Rails Guides 的人,如果有 Kindle、或有在用 Kindle 手機版的話,官方有提供 .mobi 檔可抓。每天通勤、等人的時候就可以讀。

Part 1 包含 2~4 章的筆記:

Getting Started with Rails

(無筆記)

Rails Database Migrations

無法回溯的 Migration

來自 3.5 Using the up/down Methods

有時候有些 Migration 就是無法回溯,加上還有其他考量、打死都不准許回溯的話,此時可以在 down method 裡寫:

raise ActiveRecord::IrreversibleMigration

要回溯還是要命?

在 migration 內使用 model 時,跳過 validation 的方法

來自 5 Using Models in Your Migrations

在 migration 內有使用 model 存取 attributes 的時候,在別台機器上可能會遇到因為 validator 已經宣告了、但 table 欄位實際上還不存在,所以炸掉了。此時可以在 migration 內宣告一個空的 model 來 override 掉真正的。

需注意的是,在開始之前最好還是呼叫 Model.reset_column_information 清掉 cache,否則可能會有問題。詳細使用方式請參考來源。

Active Record Validations and Callbacks

valid? and invalid?

來自 2.4 valid? and invalid?

這兩個 methods 的回傳值剛好相反(如名稱所示)。

app/models/person.rb
class Person < ActiveRecord::Base
    validates :name, :presence => true
end
p = Person.new
p.errors.messages # => {}

即使 model 裡有寫 validations,此時因為還沒有執行檢查,所以不會有錯誤(p.errors.messages 裡也不會有東西)。 .valid?.invalid? 可以觸發 validations。

p.valid? # => false

p.errors.messages # => {:name=>["can't be blank"]}

自定 validates 的子檢查

來自 6.1 Custom Validators

如果想實作這樣的自定 validation (在此例是 :email => true

app/models/person.rb
class Person < ActiveRecord::Base
  validates :email, :presence => true, :email => true
end

可繼承 ActiveModel::EachValidator 來實作,詳情請見來源。

取得 errors 的完整句子的訊息

來自 7.3 errors.add

直接印出 errors.messages 會得到類似這樣的結果

1.9.3-p392 :001 > p.errors.messages
 => {:name=>["can't be blank"]}

用 errors.full_messages 或 errors.to_a 可以取得完整句子的訊息

1.9.3-p392 :002 > p.errors.to_a
 => ["Name can't be blank"]
1.9.3-p392 :003 > p.errors.full_messages
 => ["Name can't be blank"]

transaction 內停止執行並 rollback

來自 13 Halting Execution

下列狀況會停止 transaction 並 rollback

  • before callback 回傳 false 或丟 Exception
  • after callback 丟 Exception

需注意的是,Rails Guides 提到直接丟一般的 Exception 會中斷執行,可能導致其他預料外的問題,Guides 是推薦丟 ActiveRecord::Rollback exception,不會 re-raise,所以不用再包一層 begin ... rescue。

(這邊我不是很確定會發生什麼樣的問題,還請大大指點)

Active Record Associations

inverse_of

來自 3.5 Bi-directional Associations

使用 inverse_of 在某些情境下可以避免 inconsistencies,以及增進效率(因為是同一個 object)

class Customer < ActiveRecord::Base
  has_many :orders, :inverse_of => :customer
end
class Order < ActiveRecord::Base
  belongs_to :customer, :inverse_of => :orders
end
c = Customer.first
o = c.orders.first
oc = o.customer
c.__id__  # => 70099731440980
oc.__id__ # => 70099731440980 # 相同

如果沒有 inverse_of

class Customer < ActiveRecord::Base
  has_many :orders
end
class Order < ActiveRecord::Base
  belongs_to :customer
end
c = Customer.first
o = c.orders.first
oc = o.customer     
c.__id__  # => 70297963786100
oc.__id__ # => 70297971379700 # 不同

但是有些限制:不能搭配 :through:polymorphic:as 使用 and For belongs_to associations, has_many inverse associations are ignored.

build_association 與 create_association

來自 4.1.1 Methods Added by belongs_to

association 請取代成關連的名稱,使用範例如下:

@customer = @order.build_customer(:customer_number => 123, :customer_name => "John Doe")
@customer = @order.create_customer(:customer_number => 123, :customer_name => "John Doe")

總之就是關連物件的 newcreate 用法,還會自動幫填 foreign key。

另外 has_many 那方也有類似的功能,也是會自動幫填 foreign key。

@order = @customer.orders.build(...)
@order = @customer.orders.create(...)

counter_cache

來自 4.1.2 Options for belongs_to

功能如其名,就是關連項目的 count cache,設定好後會用在這裡:

@customer.orders.size

count 仍然會下 select count(*) ...

@customer.orders.count

設定方式請參考來源,不過 Rails Guides 沒有提到一件事:如果是先有資料才補 counter_cache 欄位的話,在 migration 內你應該要給予現有資料正確的 count,但用一般的寫法會炸 Exception,要用 reset_counters 才對。

dependent

來自 4.1.2.5 :dependent

簡單來說就是類似 on delete cascade 的效果。

class Customer < ActiveRecord::Base
  has_many :orders, :dependent => :destroy
end

當 customer instance 被 destroy 的時候,關連的 orders 也都會被 destroy。

除了 :delete,也可以設 :nullify (foreign key 填 null)。

另外請勿用在 (belongs_to && 對面是 has_many) 的狀況,會導致孤兒 records。

加上 :include 增進效率、以及不必加的狀況

來自 4.1.2.7 :include

  1. 官方的範例寫得蠻好的,所以就直接過去看吧。
  2. 兩層才需要手動加 :include,只有一層關連的話會自動做。

關連物件何時儲存

整理自 4 Detailed Association Reference 下四個 When are Objects Saved? 子項目

Relation When are Objects Saved?
belongs_to Assigning an object to a belongs_to association does not automatically save the object. It does not save the associated object either.
has_one When you assign an object to a has_one association, that object is automatically saved (in order to update its foreign key).
has_many When you assign an object to a has_many association, that object is automatically saved (in order to update its foreign key). If you assign multiple objects in one statement, then they are all saved.
has_and_belongs_to_many When you assign an object to a has_and_belongs_to_many association, that object is automatically saved (in order to update the join table). If you assign multiple objects in one statement, then they are all saved.


xxxxx_ids 與 xxxxx_ids=

來自 4.3.1 Methods Added by has_many

除了熟悉的:

@customer.orders
@customer.orders = @orders

有時候你只有 ids,要撈出 objects 反而多一道手續,可以用:

@customer.order_ids  # => [1, 2, 3, ...]

@customer.order_ids = [1, 2, 3]

exists?

來自 4.3.1 Methods Added by has_many

有時候你只是想知道是否有存在符合條件的 object,我之前是這樣寫

@customer.where(:name => "John").present?

但其實可以這樣寫

@customer.exists?(:name => "John")

當資料量稍大以後,後者效能也明顯較好:

用 156,046 筆資料實驗,結果用 where(...).present? 要 121.2ms,用 exists? 只要 0.3ms。

關連物件加 :conditions

來自 4.3.2 Options for has_many > 4.3.2.4 :conditions

# 如果 model 長這樣..

class Customer < ActiveRecord::Base
  has_many :confirmed_orders, :class_name => "Order", :conditions => { :confirmed => true }
end

# 那麼下面這行只會撈出符合 :confirmed => true 的關聯物件

@customer.confirmed_orders

# 下面這行則會自動塞 :confirmed => true

@customer.confirmed_orders.create

另外也接受字串或 Proc 的條件限制,但不支援自動塞值。

改寫預設會下的 SQL

來自 4.3.2 Options for has_many > 4.3.2.5 :counter_sql4.3.2.8 :finder_sql

可以改寫預設會下的 SQL 嗎?可以,請參考來源。除了這兩個外其實還有很多其他的自定選項。

來自 4.3.2 Options for has_many > 來源 4.3.2.15 :order

關連物件預設的排序方式

class Customer < ActiveRecord::Base
  has_many :orders, :order => "date_confirmed DESC"
end

這樣子從關連取得的結果就會預設以 date_confirmed DESC 排序。

before_add 與 after_add 等 callback

來自 4.5 Association Callbacks

有這些 callback 可以用(功能如名字所示):

  • before_add
  • after_add
  • before_remove
  • after_remove

使用範例

class Customer < ActiveRecord::Base
  has_many :orders, :before_add => :check_credit_limit
 
  def check_credit_limit(order)
    ...
  end
end

寫自定的 find_by_xxx

來自 4.6 Association Extensions

來源的範例寫得很好,也很難再簡潔了,所以這邊就不重貼一次,請直接參考來源。

目前我閱讀到第9章,之後的筆記請稍待我整理完後貼出。

Rails 打基礎 - Rails Guides 3.2.13 閱讀筆記 Part 2

| Comments

接續 Rails 打基礎 - Rails Guides 3.2.13 閱讀筆記 Part 1

Active Record Query Interface

find_each 與 find_in_batches

來自 1.3 Retrieving Multiple Objects in Batches

find_each 用起來跟 each 差不多,但預設一次只會從 DB 撈 1,000 筆,尤其當資料多起來以後,直接下 User.all 會很吃記憶體。

User.find_each do |user|
  NewsLetter.weekly_deliver(user)
end

find_in_batches 也是預設一次 1,000 筆,但讀出來的變數是 array。(追 source code 可以發現,find_each 實際上是去呼叫 find_in_batches

Invoice.find_in_batches(:include => :invoice_lines) do |invoices|
  export.add_invoices(invoices)
end

兩者都有 :batch_size:start 參數可以設定。

但他有些限制,例如不能指定排序。根據 API 文件

It's not possible to set the order. That is automatically set to ascending on the primary key (“id ASC”) to make the batch ordering work. This also mean that this method only works with integer-based primary keys. ...

Range Conditions 的精美語法

來自 2.2.2 Range Conditions

下範圍條件,我原本的寫法

Client.where("created_at BETWEEN ? AND ?", params[:start_date].to_date, params[:end_date].to_date)

可以改成精美的語法

Client.where(:created_at => (params[:start_date].to_date)..(params[:end_date].to_date))

reorder

來自 8.3 reorder

如其名,可以把預設的 order override 掉。

class Post < ActiveRecord::Base
  has_many :comments, :order => 'posted_at DESC'
end
 
Post.first.comments.reorder('posted_at ASC')

joins + where 的精美語法

來自 11.3 Specifying Conditions on the Joined Tables

Client.joins(:orders).where('orders.created_at' => time_range)

改成精美的語法

Client.joins(:orders).where(:orders => {:created_at => time_range})

Scopes

來自 13 Scopes

簡單來說就是

class Post < ActiveRecord::Base
  scope :published, where(:published => true)
end

就可以用類似這樣的語法。

Post.published
@board.posts.published # 假設 board has_many posts

scope 會回傳 ActiveRecord::Relation object,所以你還可以串接,例如

Post.published.highlight.where(:pinned => true)

由於我讀的是 Rails 3.2 的版本,所以還是舊語法,Rails 4 已經強制改成

class Post < ActiveRecord::Base
  scope :published, -> { where(:published => true) }
end

可以傳入參數的 scope

來自 13 Scopes > 13.2 Passing in arguments

可以到來源查看 scope + lambda 允許傳入參數的手法,但即使是 Rails 4 的 Guides 仍不推薦該手法,而是推薦:

class Post < ActiveRecord::Base
  def self.before(time)
    where("created_at < ?", time)
  end
end

Post.before(Time.zone.now)

這種寫法一樣可以支援串接

Category.first.posts.before(Time.zone.now)

default_scope

來自 13 Scopes > 13.4 Applying a default scope

有時候想預設就套用 scope,可以用 default_scope

取消 default_scope

來自 13 Scopes > 13.5 Removing all scoping

用 default_scope 之後就會有個問題:某個管理功能需要取得 全部 的資料,甚至有時候是用別人的外掛、別人的code 繼承下來的等狀況,只想直接跳過 default scope 的話。可以用:

Client.unscoped.all

find_by_xxx 加驚嘆號後的行為

來自 > 14 Dynamic Finders

id = 123
find(id)        # 找不到 123 會炸 Exception

find_by_id(id)  # 找不到 123 會回傳 nil

find_by_id!(id) # 找不到 123 會炸 Exception

找不到符合條件的就 new/create

來自 > 15.1 first_or_create

Client.where(:first_name => 'Andy').first_or_create(:locked => false)

除了 create 外,還有其他的選擇

  • first_or_initialize 相當於 new
  • first_or_create! invalid 的時候會炸 exception

直接下 SQL, 回傳 hashes

來自 > 17 select_all

find_by_sql 可能蠻多人知道的,但是他會轉成 model instance。如果想要各欄位的 hash,可以用 select_all

Client.connection.select_all("SELECT * FROM clients WHERE kind = '1'")

他會回傳類似

[{"id"=>57, "kind"=>"1", "created_at"=>2013-11-16 14:09:44 +0800, "updated_at"=>2013-11-16 14:09:44 +0800}, {"id"=>57, ... }, ...]

pluck 取出某欄位的值成為一個陣列

來自 > 18 pluck

若只想取出某一欄位的值成為陣列,直覺的想法可能是:

User.select(:id).where(:actived => true).map { |c| c.id }

但如果你之後沒有要使用物件的話,這麼做其實白耗了 instantiate 的時間。

User.where(:actived => true).pluck(:id)

因為 pluck 不會 initialize,速度會差非常多。根據這篇 Getting to Know Pluck and Select ,一萬筆資料可以差到 10 幾倍 (1.11秒 vs 0.08秒)。

.any? 跟 .many?

來自 > 19 Existence of Objects

["a"].any?            # => true

["a"].many?           # => false

["a", "b", "c"].any?  # => true

["a", "b", "c"].many? # => true

關連物件跟 scope 也可以這樣用

Post.recent.any?
Post.recent.many?

但如果只是要檢查是否存在,沒有更多的動作的話,.exists? 會比 .any? 理想,請參考 閱讀筆記 part 1

Ruby on Rails x Mac OSX 10.9 Mavericks 環境安裝步驟

| Comments

這篇文章現在搬到新站: Ruby on Rails x Mac OSX 10.9 Mavericks 環境安裝步驟

之前「很久沒維護」的那台,後來終於生氣重灌成 Mavericks 了。這邊記錄一下 Rails 環境設定過程。

這篇的原則是:有 GUI 就用 GUI,有官方安裝指令就用官方的。

Rails 環境以外的設定還可以參考我的 個人環境設定 Memo,持續更新中。

要裝的東西與順序

有些的順序非常重要,所以沒事就照順序裝吧!

  1. 工具、套件管理 (GCC, Homebrew)
  2. ImageMagic
  3. MySQL
  4. RVM and Rubies
  5. Rails
  6. Pow

...

(完整內容請到新站觀看 Ruby on Rails x Mac OSX 10.9 Mavericks 環境安裝步驟)

應徵 Rails 工作的心得

| Comments

這篇文章現在搬到新站: 應徵 Rails 工作的心得

這篇是我之前應徵工作的心得,以及我未來還會採用的應徵技巧,還有一些工作上的心得。

我的經驗並不適用所有狀況,看的時請考量自己與所在產業、企業的狀況,不要照單全收。

我在得到第一份正職工作後不久寫了 新鮮人找工作的注意事項 & 自己的面試經驗,這篇算是續集,希望對 Rails 工程師新鮮人有所幫助。

關於履歷、面試

這部份基本上是通用的,不限定 Rails 或軟體工程師。

...

(完整內容請到新站觀看 應徵 Rails 工作的心得)

安裝 Sublime Text 3 互動式 Ruby Debug 外掛

| Comments

最近發現 sublime_debugger (在 Package Control 裡叫 "Ruby Debugger") 這個外掛,是我一直非常期待的功能之一:

類似 Visual Studio 的強大互動式 Debug 介面

背景

不知道有多少人玩過類似這樣的東西:

Rails Guides - Debugging Rails Applications 裡就有收錄類似工具的教學,但可能是因為文字介面,功能又很多,感覺好像很複雜,導致很多人都不願去用它。

現在有整合到 Sublime Text 3 的外掛,當然要開始孜孜不倦的推坑了(?)

這篇是入門教學,如果你用起來覺得不錯,可以看 sublime_debugger 的 READMERails Guides 的 Debugging Rails Applications 篇 來了解更多。

(寫這篇的時候,sublime_debugger 才三個月大,但目前已經有堪用的版本,而且作者也持續開發中。我自己測過 Ruby 1.9.3 + Rails 3.2 與 Ruby 2.0.0 + Rails 4.0 都沒問題)

安裝

安裝指定的 debugger gem

  • Ruby 1.9.3 => gem install debugger
  • Ruby 2.0.0 => gem install byebug --version '>=2.5.0'

也可以加到 Gemfile 並跑 bundle install

透過 Package Control 安裝 Ruby Debugger 外掛

  • 目前 Ruby Debugger 外掛只支援 Sublime Text 3(在 Mac 上可跟 Sublime Text 2 並存)
  • 如果你的 Sublime Text 3 還沒有安裝 Package Control,請先安裝
  • 透過 Package Control 安裝 Ruby Debugger 外掛

使用

開始 debugging rails(Start debugging 則是對純 ruby 檔案 debug)

等幾秒後,會看到 Output 視窗出現開啟 web server 的熟悉訊息。需注意如果你的 project 很大的話,這邊的啟動會非常久,第一次試用可以先找小 project 來玩。

另外,按 F9 可以設定 break point (中斷點),不想記快速鍵的話,右鍵選單裡也有。

一些常見問題:

  • 有些版本會在這步停在 rails 啟動的 code,此時就 F10 一路執行過去,直到讓他順利開啟 web server。
  • 如果你已經在 port 3000 自己開了 server 就會衝到,請把自己開的 web server 關掉。
  • sublime_debugger 目前仍有不穩的狀況,有時候 web server 會卡在那邊關不掉。遇到這種狀況可以把佔用 port 3000 的 process 找出來並砍掉。或直接 killall ruby 並一一重開。
  • 如果有遇到其他問題,除了試試看重開,也可以搜尋一下有沒有相關 issue 討論。

接下來,切到瀏覽器,瀏覽會觸發 break point 的頁面:

(在這個例子裡,/home 是指到 videos#index)

接下來你就可以用 F10 (Step Over)、F11 (Step Into) 來逐行執行。
由於 F11 是 Show Desktop 的快速鍵,如果你的 Mac OS X 版本有預設開啟的話,記得去把它關掉。

除了逐行執行,還有一些常用的功能,例如持續監看某指令的執行結果:

現在 category 的執行結果是錯誤(因為執行到這行的時候,的確還沒有定義 category)。那麼現在我們跳進迴圈:

此時就抓到 category

tips: 底下的視窗預設不會自動換行,要開啟可以用 cmd+shift+p 並執行 Word Wrap: Toggle

Immediate (即時執行) 也是很常用的功能

其他還有 Locals (區域變數列表)、Stack 等功能,這邊就不介紹了。

再來是「善後」:

還有一點值得一提,debugger 不僅可以拿來 debug,還有一個很棒的價值:

追別人的 code

有時候維護別人的 code、用別人的 gem,甚至是剛學程式的人,無法理解為什麼會跑出某種不符預期的結果時,就可以用 debugger 跑一遍,很直覺、好理解、也可以很詳細的查看變數狀態。

實際上我高職初學程式時,就很常玩 VB 6.0 IDE 的這個功能,幫助我瞭解控制流程的基本觀念很多。在前一篇 應徵 Rails 工作的心得 也提到,即使高職讀資處科,也不見得每個人都會認真學程式,光是課堂上、模擬考出現的,都遠遠不及親自動手玩來得有幫助。

Rails Guides 也有提到類似的點

The debugger can also help you if you want to learn about the Rails source code but don't know where to start.

現在就把 sublime_debugger 裝起來玩玩吧 \(☉▽☉)/

Capistrano 自動選擇當前 branch deploy 到 staging

| Comments

一般 config/deploy/staging.rb 的 branch 內容可能會設定成如下:

config/deploy/staging.rb
# ...

set :branch, "staging"
set :rails_env, "staging"
# ...

我們團隊使用 GitHub Flow,常常會 deploy 非 master 的 branch 到 staging 上。照上面的 config 設定,要麻把 staging branch reset 到目前的 commit (很麻煩),不然就是把 set :branch 改成當前的 branch 名稱、但不 commit。

但即使如此,還是很麻煩,而且有時候會忘記改。

搜尋 & 嘗試了一下,最後我改成:

config/deploy/staging.rb
# ...

set :branch, ENV["BRANCH"] || `git rev-parse --abbrev-ref HEAD`.chop
set :rails_env, "staging"
# ...

其中

`git rev-parse --abbrev-ref HEAD`.chop

可以丟到 rails console 執行看看,就是取得當前的 git branch name

這樣設定以後,只要在 terminal 下:

cap staging deploy

就會自動選擇當前的 branch 來 deploy 了(production 則當然保持 "master" 不改)。

另外,若有一些情況想要「在這個 branch 但 deploy 那個 branch」的話,可以:

BRANCH=other_branch cap staging deploy

這個技巧在 capistrano 2 跟 3 都適用。

2014-09-29 更新:Capistrano 不知道從何開始範例檔有以下的 code,與上面取得 branch 名稱的解法意思差不多,但使用 chomp 的確是比較好的習慣。

proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call

如何簡化「常常在 rails console 裡反覆輸入某些指令」的狀況

| Comments

開發的時候常需要在 rails console 下尋找一個 user 來做某些實驗,通常是用自己的帳號,因此就會每天都在敲 user = User.find_by_email(自己的 email),很浪費時間,用這個技巧就可以改善此類問題。

解決方法

在家目錄底下建立 .irbrc (如果用 pry-rails,則是 ~/.pryrc)

~/.irbrc
def find_me
  User.find_by_email("your_mail@example.com")
end

之後就可以在 rails console 下

[1] pry(main)> user = find_me
  User Load (0.3ms)  SELECT `users`.* FROM `users` WHERE `users`.`email` = 'your_mail@example.com' LIMIT 1
  ...

常在 rails console 下打的指令都可以考慮如何利用這個手法,但必須注意 method 命名不能跟現有專案的衝突。

ps. 以前我會寫一支開發專用的 app/models/dev.rb,裡面有各種方便的 methods,但由於現在手上專案很多,每個都加一次很麻煩,其他人可能也不想共用這種手法,所以現在改採不會動到專案 code 的解決方案。

避免 pow 動不動就很長等待時間、以及 pow 如何跟 spring/zeus 合作

| Comments

避免 pow 動不動就很長等待時間

TL; DR;

在 Terminal

echo "export POW_TIMEOUT=36000" >> ~/.powconfig
# 或你想自己 vim ~/.powconfig 開來編輯也可
touch ~/.pow/restart.txt

參考 Pow 文件 - 3. Configuring Pow

為什麼要做這件事

因為我手上有幾個肥大的 projects,啟動時間可以到達 60 秒以上,而 pow 預設每 15 分鐘就會 kill 掉 idle 的 app,於是動不動就要等 60 秒啟動,很浪費時間。

如果你只是苦於每一段時間就要等啟動很久,這個方案是最簡單的方法:延長 timeout。

最開始的設定範例中

export POW_TIMEOUT=36000

的時間單位是秒。我的想法是上班時間是 8+1 小時,所以再長一點基本上就沒問題了, 每天早上來只要把所有頁面打開(我集中在一個 bookmark folder),一整天都可以快樂的使用不必等 這樣會很佔記憶體,所以還是只開有用到的吧。

必要的時候還是可以用原本的方法重新啟動 app (touch tmp/restart.txt, powder restart 或用直接用 Anvil)。

pow 如何跟 spring/zeus 合作

之所以有這個段落是因為我一開始以為解決等待時間必須用 rails preloader,後來才發現可以延長 timeout。

很不幸的,目前 pow 跟 spring/zeus 合作並不方便。不過某些情境仍是有好處的,以下說明:

讓 pow 的網址改連到 rails s 打開的 server

如此一來就可以披著 xxx.dev 的網址,實際上連到 rails s -p 3000 (或其他 port) 開好的 server。

參考 pow manual - 2.1.4 Port Proxying

  1. cd ~/.pow/
  2. rm project_name 把你原本的 link 移掉
  3. echo 13001 > project_name 把想用的 port 寫到原名的檔案裡
  4. cd 到你的 project 目錄
  5. rails s -p 13001

需注意如果你有多個 projects,必須自己管理網址跟 port 的對應。

port 我選用 13001~13050,原因是依照 Wikipedia 的 List of TCP and UDP port numbers 來看,這段比較有名的應用是 Second Life,然而現在很少人在玩 (我玩過一陣子)。

優點

  • 可以直接 binding.pry

缺點

  • 必須自己管理 port 對應
  • 必須自己啟動 rails s
  • 如果 zeus 或 terminal app crash,你需要手動刪除 .zeus.socktmp/pids/server.pid 才能順利再啟動
  • 如果你手上有 5 個站、而且還會互打 API,上面三點就會非常麻煩了(每次重開或 crash 就要處理 5~10 個 tabs)

spring 的問題

spring 的問題是它根本沒有 preload spring rails s (issue 72),所以跟 rails s 意思是一樣的。

附帶一提,關於到底該用 spring rails 還是 bin/railsspring/README 裡其實有提到:

If you don't want spring-related code checked into your source repository, it's possible to use spring without adding to your Gemfile. However, using spring binstubs without adding spring to the Gemfile is not supported.

To use spring like this, do a gem install spring and then prefix commands with spring. For example, rather than running bin/rake -T, you'd run spring rake -T.

簡單來說就是你如果不寫進 Gemfile,就必須用 spring rails

zeus 的問題

zeus 需要兩個 terminal app tabs,一個開 zeus start 另一個跑 zeus s -p 13001,所以你如果有 5 個主要 projects,就要為了這件事開 10 個 tabs...

另外雖然動到 config 時 zeus 本身會自動重開,卻不會重開 zeus s,所以你還是要手動 ctrl+c 關掉重開。據說以前會自動處理,不曉得這是 bug 還是 feature...

唯一的好處可能是動到 config 的時候,重開 zeus s 還是比 rails s 或 pow 重開快一點。

如果你手上的專案不多,想採取這個方案的話,我嘗試過這樣的技巧:

echo "13001" > .pow_port    # 把要用的 port 寫到專案根目錄
zeus s -p $(more .pow_port) # 以後只要下同樣的指令就可以打開

但還不夠完美,有需要的話可以再想想怎麼改善。也歡迎留言回報你的解法囉!

結論

  • 如果你手上有很多肥大 projects,可以用延長 timeout 來節省時間
  • 如果你很常用 pry 來 debug,但覺得 binding.remote_pry 不好用的話,用 Port Proxying 解法可以維持 xxx.dev 的網址且可用 binding.pry

UPDATE:

超棒的 rails console 設定

| Comments

這篇文章現在搬到新站: 超棒的 rails console 設定

這篇是我閱讀 Using pry in production 後,結合我自己的經驗與公司前輩留下來的設定等,最後得出的一套組合。由於主題的關係這篇只介紹 rails console 相關的部分,實際上那套組合還有別的東西,以後若有機會再另外介紹。

ps. 依照 Using pry in production 的設定將會受到一個 readline 的 bug 影響,在 pry 解決該問題前,本篇有 workaround 教學。

想達到的效果

1. 預設漂亮的格式、還可以輸出 table

注意到 prompt 顯示出 project 的名稱,而不是預設的 "irb" 或 "pry",同時維護多個專案的話很實用。

2. 在 staging/production 的 rails console 會有明顯提示,防止手誤

...

(完整內容請到新站觀看 超棒的 rails console 設定)

Use codeclimate-test-reporter without a CI server

| Comments

For some reason, we didn't setup a CI server. But we still want Code Climate to know test coverage after each deployment.

After some searching and experiment. We came up with this solution.

1. Add Capistrano run_tests task

Because we don't have a CI server. We use Ben Dixon's run_tests technique covered in his book Reliably Deploying Rails Applications, which is:

lib/capistrano/tasks/run_tests.cap
namespace :deploy do
  desc "Runs test before deploying, can't deploy unless they pass"
  task :run_tests do
    test_log = "log/capistrano.test.log"
    tests = fetch(:tests)
    tests.each do |test|
      puts "--> Running tests: '#{test}', please wait ..."
      unless system "bundle exec rspec #{test} > #{test_log} 2>&1"
        puts "--> Tests: '#{test}' failed. Results in: #{test_log} and below:"
        system "cat #{test_log}"
        exit;
      end
      puts "--> '#{test}' passed"
    end
    puts "--> All tests passed"
    system "rm #{test_log}"
  end
end
config/deploy.rb
set :tests, ["spec"]
before :deploy, "deploy:run_tests"

It will run test before every deploy. And stop deploy process if there are any failed test.

2. Install and setup codeclimate-test-reporter

Install codeclimate-test-reporter by adding it into Gemfile

Gemfile
gem "codeclimate-test-reporter", group: :test

and run bundle install

Start the test reporter. Make sure that you put these lines at top of your spec_helper.rb.

spec_helper.rb
require "codeclimate-test-reporter"
CodeClimate::TestReporter.start

Manually tirgger fist test report (your project token can be found at Settings > Test Coverage)

(in terminal)
$ cd your_project_root
$ CODECLIMATE_REPO_TOKEN=your_token_here bundle exec rspec spec

It should show following message:

Coverage = xx.xx%. Sending report to https://codeclimate.com for branch master... done.

Now you should be able to see the report showing in Code Climate. Sometimes it can take a few minutes.

3. Modify run_tests and make it report after each deployment

When you run rspec without CODECLIMATE_REPO_TOKEN, even you have test reporter started, it won't send test report to Code Climate. In other words, you can control when to send report by giving token or not.

Modify the rspec command in lib/capistrano/tasks/run_tests.cap

I assume you always run complete test suit with this technique.

lib/capistrano/tasks/run_tests.cap
namespace :deploy do
  desc "Runs test before deploying, can't deploy unless they pass"
  task :run_tests do
    test_log = "log/capistrano.test.log"
    tests = fetch(:tests)
    tests.each do |test|
      puts "--> Running tests: '#{test}', please wait ..."
+      rspec_command = "bundle exec rspec #{test} > #{test_log} 2>&1"
+      rspec_command = "CODECLIMATE_REPO_TOKEN=#{fetch(:codeclimate_token)} #{rspec_command}" if fetch(:codeclimate_token)
+      puts "--> Running tests: '#{rspec_command}', please wait ..."
+      unless system(rspec_command)
-      unless system "bundle exec rspec #{test} > #{test_log} 2>&1"
        puts "--> Tests: '#{test}' failed. Results in: #{test_log} and below:"
        system "cat #{test_log}"
        exit;
      end
      puts "--> '#{test}' passed"
    end
    puts "--> All tests passed"
    system "rm #{test_log}"
  end
end
config/deploy.rb
set :codeclimate_token, your_token_here

Now, test will be run before every deployment and test report will be sent after that.

Note: you can put your token in run_tests.cap for simplicity. But I rarely commit any key or token into repostory. Instead, I put it in a git-ignored, separated config file. Than read and assign the value in config/deploy.rb.

workshp 的斷線備案 - 離線 bundle install

| Comments

之前準備在 Rails Pacific 上帶的 Refactoring workshop 時,想到以前參加 conference 時 wifi 通常都很不穩,因此有先想一下斷網備案。

但 Rails Pacific 2014 的現場網路很穩(感謝中午吃飯時間還在架設網路的小蟹),這招不但沒用到,而且還有幾位遇到 nokogiri 裝不起來的問題。

如何離線 bundle install

簡單來說就是用

bundle package

把 gems 放到 vendor/cache 裡。之後透過 usb 碟等離線方式 copy 給別人後,請他跑

bundle install --local

詳細的設定與原理可參考官方文件 bundle package

但不同作業系統可能會不相容,有些 gem 像 nokogiri 可能也無法只靠這樣安裝,應該避免使用。這次 workshop 是因為最後一題的測試使用到了 capybara,又因為生病沒有注意到而沒排除掉。

以上,提供給要帶 ruby 相關 workshop 但擔心網路問題的人參考,這只是很陽春的記錄,也許您有更完整的備案準備方式,歡迎留言指教 m(_ _)m

Refactoring Workshop 補充 (Rails Pacific 2014)

| Comments

2014-09-26 我在 Rails Pacific 主持一場 Refactoring 的 Workshop,這篇是補充一些可參考的資源。

原始投影片

個人其實對這次的品質有一些遺憾,有些細節跟題目設定都沒有很好,也沒有足夠的排演,實在對參加者很抱歉,但我真的盡力了,且讓我解釋一下發生了什麼事。

Rails Pacific 開始的前幾天,因為下腹持續疼痛跑醫院,醫生建議我立刻掛急診做斷層掃描,依照報告結果選擇要「住院+打抗生素」或「住院+開刀」,結果我回醫師說:

可是我禮拜五有活動耶

醫師就一臉無奈:「我們醫生是這樣建議啦,但還是要尊重你的意見」,於是 workshop 教材還是做出來了(驚險萬分),我只能說這真的是用半條命換來的,其實 workshop 當天早上我人也在醫院,現在寫這篇的同時,身體也都還有一點不舒服。

進入正題

pry 的進階

關於 Concerns

為什麼這個 workshop 完全沒有提到放進 concerns,即使有些題目其實是可以用的?

“Any application with an app/concerns directory is concerning.”

請看這篇文章 http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

需了解這其實沒有標準答案,需依照條件,例如說如果是拋棄式活動網站(只維護短期),「花時間把架構搞得很好」是錯誤的。團隊現有慣例與未來接手維護的安排也都是要考量的點。

只有一個地方用到,還要抽成 method 嗎?

這基本上有兩派說法,容我引用 Ga Dii 的心得

其中印象比較深刻的是被問到對於 over refactoring 的看法:Akira 說他 over refactoring 的經驗是過度抽象化,有時候甚至是 commit 之後才覺得後悔;Ryan 認為有時候一些 method 只有在一個地方被呼叫到的話,就不應該抽出來,而是讓整個 function 一氣呵成;Nick 則不贊同 Ryan 的想法,他認為這些 method 即使是只有被呼叫一次,但如果抽出來能夠讓 code 可讀性變高,那還是應該要拆開比較好。

不過我沒有當場聽到 Panel discussion,因為那個時候我人在醫院。

Service Object 的補充

RailsCasts 上有一篇講得不錯(付費) http://railscasts.com/episodes/398-service-objects

Form Object 的補充

也是 RailsCasts,也是付費 http://railscasts.com/episodes/416-form-objects

需注意 workshop 主要目的是帶觀念,production 網站要使用的話,應該用類似 Reform 的工具來做比較理想。(Reform 的作者也就是這次的講者之一 Nick Sutterer)

在T客邦 新進工程師如何學習 Rails

| Comments

T客邦有許多優秀前輩經手並建立起來的 codebase 與 infrastructure、數個以維護為主的產品、不固定的新網站開發需求、已實行一段時間的軟體工程與專案管理方法等,因此在許多人眼中是不錯的 Rails 開發者練功環境。

也因此這是許多對T客邦工作有興趣的人會想知道的事,這次趁機會寫出來給大家參考,T客邦是個好溝通、氣氛佳、愛學習的團隊,歡迎來應徵。

但是,若抱著「我什麼都不會,但是我願意學習」的心態來應徵,恐怕機會是比較小的。如果有心,應該會展現在「我在某某線上課程完成多少課」、「我做過這個那個 side projects」或「我有個技術筆記分享部落格」等結果上,如果跟你競爭的應徵者多少有這類的成果可參考,那結果...你知道的。

這部分還可以參考我的另一篇文章 應徵 Rails 工作的心得

Rails 101 訓練

T客邦長期都使用 XDite 的教材 Rails 101 進行新人訓練,但一直有隨著時間進化,目前除了熟悉 Rails 功能、coding style,也會順便練習實際的工作流程。

  • 使用 Rails 101 的題目,但會加上一些限制,例如不准使用 scaffold generator
  • 負責帶新人的同事會用 Redmine 派票,也會要求養成回報到票上的習慣
  • Git 分支策略採用 GitHub flow,branch 名稱也有命名原則
  • 每個 PR 都會抓效能問題、不良的開發習慣、可改寫得更漂亮的地方與 coding style 等問題

這套練習還包含架設一台 Ubuntu server、用 Unicorn 跟 Capistrano 達到 zero-downtime deploy 等。

線上課程與文章

公司會要求的只有上述 Rails 101 的練習,其他的就只能看個人願意花多少時間學習/練習了。

所以觀看線上教學、閱讀 tutorial 或官方文件等都都是要自己去安排的。不過公司會補助一些線上課程的費用,例如 Code School, RailsCast, TeaLeaf 等。就算不在目前補助範圍內的,有需要的話也可以談。

另外就是工程師之間都會互相丟好文章連結、推薦不錯的書籍或 Leanpub 電子書等。最近復活的 Techbang 技術部 粉絲頁 前 20 幾則文章基本上都是從頻道紀錄裡撈出來貼的。

追程式碼與查資料

由於 Techbang 的許多專案都累積幾年了,有些架構不直覺可能是有原因的,因此接到某些票發現解法不單純的話,有可能需要追某個功能的所有相關 code、翻那段 code 的 git 修改歷史紀錄、找出對應的票並搞清楚當初設計的原因、繞過或處理歷史包袱等等。

有些功能則是沒有前例可查,但可以用關鍵字找網路上的範例或構想、查官方文件想辦法兜出解決方案、研究別人的 open source project 是如何實做的等等。

讀別人的 code、上網找資源兜解法,常常可以學到很多技巧、探索一些之前不知道的 API、library 或工具。

當然也不是派了票就放你自生自滅 生命會自己找到出路 ,有遇到問題都可以提出來討論,工程師前輩或熟悉業務邏輯的 PM 會很樂意提供意見。