about 6 years ago

HTML5新增加了WebSQL這個技術,讓開發者可以利用WebSQL做到Client資料庫的功能,當要開啟WebSQL資料庫時,通常大家都會考量到幾個問題:

  1. Client端有資料庫存在嗎?
  2. Client端有正確的資料庫結構(schema)嗎?

所以一般網路上的教學大致上都是這樣寫的:

var dbSize = 5 * 1024 * 1024; //5mb

var db = openDatabase("MyFirstDB", "1", "Description", dbSize);

db.transaction(function(tx) {
 tx.executeSql("CREATE TABLE IF NOT EXISTS myFirstTable(ID INTEGER PRIMARY KEY, name TEXT)", []);
});

上面程式碼的做法是在開啟資料庫後,馬上執行建立table的語法,並且使用 CREATE TABLE IF NOT EXISTS 來避免錯誤。

當然這樣做是不會有問題的,但如果程式已經上線了,開發者因為要加入新的功能而需要新增一個欄位時,就沒那麼簡單了,因為SQLite3(WebSQL的引擎)並沒有 ALTER TABLE tableName ADD COLUMN IF NOT EXISTS newCol TEXT 這種語法可以用。當然你可以用 PRAGMA table_info(myFirstTable); 去取出table的欄位資料,再遂筆檢查欄位是否存在,不存在才執行新增欄位的語法,不過這樣做是非常麻煩的事,程式碼應該會非常的長。

WebSQL的版本管理機制

WebSQL當然有考量到這種狀況,所以本身就提供了一個版本管理的功能,在開啟資料庫的 openDatabase 語法就可以指定版本的參數,如果版本號碼給錯可是會開不了資料庫的:

//openDatabase(database_name, versionNumber, description, db_size)

var db = openDatabase("MyFirstDB", "1", "Description", dbSize);

//versionNumber傳空白字串代表不限定版本

var db = openDatabase("MyFirstDB", "", "Description", dbSize); 

另外也提供了 changeVersion 來變更資料庫的版本

//changeVersion(oldVersionNumber, newVersionNumber, callback, errorCallback, successCallback)

db.changeVersion("", "1", function (tx) {
    tx.executeSql("CREATE TABLE myFirstTable (ID INTEGER PRIMARY KEY, name TEXT)");
}, function (err) {
    console.log(err);
}, function () {
    console.log('success');
});

我們可以利用 db.version 來判斷資料庫的版本,並執行更新schema的程式碼,範例如下:

var dbSize = 5 * 1024 * 1024; //5mb
var db = openDatabase("MyFirstDB", "", "Description", dbSize);  //versionNumber傳空白字串代表不限定版本
var dbVersion = parseInt(db.version) || 0;
if(dbVersion < 1) {
    db.changeVersion("", "1", function (tx) {
    tx.executeSql("CREATE TABLE IF NOT EXISTS myFirstTable(ID INTEGER PRIMARY KEY, name TEXT)", []);
  });
}
if(dbVersion < 2) {
  db.changeVersion("1", "2", function (tx) {
    tx.executeSql("ALTER TABLE myFirstTable ADD COLUMN tel TEXT", []);
  });
}

注意,如果想偷懶用 db.version 傳入 changeVersionoldVersionNumber 是不行的,因為WebSQL的 executeSql 是非同步(Asynchronous)執行的,所以如果使用者的資料庫版本,是非常舊的版本,一次需要執行2次以上 changeVersion,雖然程式碼實際上都有執行到,但第2個 changeVersion 執行時,因版本編號已被變更,所以就會發生下列的錯誤:

current version of the database and `oldVersion` argument do not match
重構一下

當然上面的程式碼可以跑,但是很醜,所以可以將版本控制的部份改用function來實作:

var migrate = function(version, upgradeFunction) {
  var dbVersion = parseInt(db.version) || 0;
  version = parseInt(version);

  if(dbVersion < version) {
    var oldVersion = (version - 1) || "";
    db.changeVersion(String(oldVersion), String(version), upgradeFunction);
  }
}

var dbSize = 5 * 1024 * 1024; //5mb
var db = openDatabase("MyFirstDB", "", "Description", dbSize);  //versionNumber傳空白字串代表不限定版本

migrate(1, function(tx){
    tx.executeSql("CREATE TABLE IF NOT EXISTS myFirstTable(ID INTEGER PRIMARY KEY, name TEXT)", []);
});

migrate(2, function(tx){
  tx.executeSql("ALTER TABLE myFirstTable ADD COLUMN tel TEXT", []);
});
以Promise實作的版本管理

如果想等資料庫都更新完成再執行某些動作,也可以用Promise的方式來包裝:

var migrations = [];
var set_migration = function(version, upgradeFunction) {
  migrations.push(
    new Promise(function(resolve, reject) {
      var dbVersion = parseInt(db.version) || 0;
      version = parseInt(version);

      if(dbVersion < version) {
        var oldVersion = (version - 1) || "";
        db.changeVersion(String(oldVersion), String(version), upgradeFunction, function (err) {
          reject(Error(err.message));
        }, function () {
          resolve();
        });
      } else {
        resolve();
      }
    })
  );
}

var dbSize = 5 * 1024 * 1024; //5mb
var db = openDatabase("MyFirstDB", "", "Description", dbSize);  //versionNumber傳空白字串代表不限定版本

set_migration(1, function(tx){
    tx.executeSql("CREATE TABLE IF NOT EXISTS myFirstTable(ID INTEGER PRIMARY KEY, name TEXT)", []);
});

set_migration(2, function(tx){
  tx.executeSql("ALTER TABLE myFirstTable ADD COLUMN tel TEXT", []);
});

Promise.all(migrations).then(function(results) {
    migrations = [];
    //do something...
}, function(err) {
    console.log(err);
});

參考資料

  1. http://www.w3.org/TR/webdatabase/
  2. http://blog.maxaller.name/2010/03/html5-web-sql-database-intro-to-versioning-and-migrations/
 
about 6 years ago

網路上很多用純CSS畫圖的效果,每次看都覺得很厲害,但沒想過他們是怎麼畫出來的,直到最近想用CSS做對話框的效果,才來研究了一下。對話框要做方框很容易,但想用CSS加個箭頭,就沒那麼簡單了,html+css可以畫三角形嗎? 其實是可以的,只是要用點小技巧。

先來看一下我們常用的border:

HTML
<span>abc</span>
CSS
span {
    border-style:solid;
    border-width:10px;
    border-color: #000;
}

看不出什麼東西對吧? 再來將各border以不同顏色顯示:

HTML
<span>abc</span>
CSS
span {
    border-style:solid;
    border-width:10px;
    border-color: #000 #00f #f00 #0f0;;
}

有沒有發現原來border是梯形的? 這個梯形就是我們畫三角形最重要的重點。

接下來,我們改成只顯示一邊的border,但別用 border-width 去隱藏其他的border,而是用 border-color 設定 transparent 將其他不顯示的border做透明效果。

HTML
<span>abc</span>
CSS
span {
    border-style:solid;
    border-width:10px;
    border-color: #000 transparent transparent transparent;
}

並將span中的內容拿掉:

HTML
<span></span>
CSS
span {
    border-style:solid;
    border-width:10px;
    border-color: #000 transparent transparent transparent;
}


結果就變成三角形了!

再來,如果要做出不同的三角形,就要花點腦筋,先來談談剛剛為什麼要用 border-color 設定 transparent 來隱藏其他border? 因為如果是用 border-width 設定 0px 來隱藏,會長這樣:

HTML
<span>abc</span>
CSS
span {
    border-style:solid;
    border-width:10px 0px 10px 10px;
    border-color: #000 #00f #f00 #0f0;
}


所以如果沒有右邊的border在,上方border的右邊,會以直角的方式呈現,而不是梯形。我們就可以利用這點來做出讓三角形轉向的應用:

HTML
<span class='triangle1'></span>
<span class='triangle2'></span>
<span class='triangle3'></span>
<span class='triangle4'></span>
CSS
span {
    margin-right: 10px;
}
.triangle1 {
    border-style:solid;
    border-width:10px 10px 10px 0px;
    border-color: #000 transparent transparent transparent;
}

.triangle2 {
    border-style:solid;
    border-width:10px 0px 10px 10px;
    border-color: #000 transparent transparent transparent;
}

.triangle3 {
    border-style:solid;
    border-width:0px 0px 10px 10px;
    border-color: transparent transparent #f00 transparent;
}

.triangle4 {
    border-style:solid;
    border-width:0px 10px 10px 0px;
    border-color: transparent transparent #f00 transparent;
}

再加上 border-radius 圓角效果就可做出相當多的應用,發揮想像力甚至可以做出 http://www.bchanx.com/logos-in-pure-css-demo 這種的效果。

參考資料

  1. http://css-tricks.com/snippets/css/ribbon/
  2. http://blog.mukispace.com/pseudo-elements-10-examples/
 
about 6 years ago

現在寫javascript常常會有很多非同步(asynchronous)的程式要處理,如果只有一個非同步的程式,在處理successfailure時應該還算是簡單的。最簡單的範例如下:

$.ajax({
    url:'/abc.php',
    success: function () {
        $('#success').show();
    }, 
    error: function (jqXHR, textStatus, errorThrown) {
        console.log(errorThrown);
    }
);

successfailure(error)在處理上相當的直覺簡單。但如果要同時處理多個非同步的successfailure,例如:「等待三個ajax處理完成,統一做一個alert。」這樣子的案例在處理上就會變得相當麻煩。

如果要等待多個非同步的程式執行完成才做一些動作,在jQuery 1.5後的版本可使用 $.when 這個方法。上面說的案例「等待三個ajax處理完成,統一做一個alert。」可以這樣寫:

$.when($.ajax('abc.php'), $.ajax('abc2.php'), $.ajax('abc3.php'))
.done(function(result1, result2, result3) {
    alert('success');
}).fail(function (jqXHR, textStatus, errorThrown) {
    console.log('error');
});

這樣統一的處理,看起來簡單,也省去很多麻煩事。$.when可以用在jQuery的$.ajax$.get$.post...等很多jquery內建的方法上,都不會有問題。因為 $.ajax 本身會回傳一個 $.Deferred 物件,$.Deferred 是一種可串接的物件,主要可以用來把callback丟到callback佇列中,進而統一管理callback,所以jquery後來的文件也建議 $.ajax 改用跟上面 $.when 相同的寫法:

$.ajax('abc.php').done(function(result) {
    alert('success');
}).fail(function (jqXHR, textStatus, errorThrown) {
    console.log('error');
});

但是其他的非jQuery的程式,例如:WebSQL的executeSql,在使用 $.when 時,可能就會有問題出現了,jQuery官方文件有寫到:

If a single argument is passed to jQuery.when and it is not a Deferred or a Promise, it will be treated as a resolved Deferred and any doneCallbacks attached will be executed immediately. 

意思是說如果傳進 $.when 的參數不是一個 DeferredPromise 物件,將會被視為已成功執行(resolved),而且所有的doneCallback都會馬上被執行。所以程式根本不會等到非同步程式執行完,才執行done的程式,而是「馬上」執行!既然這樣,那我們只好把非jquery的非同步程式包成一個 $.Deferred 物件來讓 $.when 可以正常的執行。

$.Deferred 的概念其實蠻簡單的,$.Deferred 裡有個狀態的flag,當物件剛宣告時,是 pending,成功執行時,可以呼叫 deferred.resolve() 將狀態改為 resolved。反之,可以呼叫 deferred.reject() 將狀態改為 rejected,讓程式去呼叫fail。簡而言之,$.when 就是利用這個狀態的flag來判斷該執行done還是fail,或是其他callback。所以只要將狀態管理好即可。

PromiseDeferred 相當類似,只是 Promise 少了設定狀態的方法,ex.resolve、reject...等。jQuery官方建議只return promise物件,以防止其他程式變更狀態。

接下來看看範例,可以宣告一個 $.Deferred 物件,再success時用程式去呼叫 resolve 即可,如下:

var insertToDB = function () {
    var deferred = $.Deferred();   //宣告Deferred物件

    var dbSize = 1024 * 1024;
    var db = openDatabase("db1", "1", "", dbSize);
    db.transaction(function(tx){
        tx.executeSql("INSERT INTO table1(col1, col2) VALUES (?,?)", ['value1', 'value2'],
            function () { 
                console.log('insert successed');
                deferred.resolve();   //update state

            },
            function (tx, error) {
                console.log('error:' + error);
                deferred.reject();    //update state

            });
   }); 
   
   return deferred.promise();  
}

//execute

$.when(insertToDB()).then(function () {
  alert('when complete');
});

另外,也可以利用 $.Deferred 的beforeStart去完成 $.Deferred 的實作,範例如下:

var dbAccessor = $.Deferred(function(deferred) {
    var dbSize = 1024 * 1024;
    var db = openDatabase("db1", "1", "", dbSize);
    db.transaction(function(tx){
        tx.executeSql("INSERT INTO table1(col1, col2) VALUES (?,?)", ['value1', 'value2'],
            function () { 
                console.log('insert successed');
                deferred.resolve();   //update state

            },
            function (tx, error) {
                console.log('error:' + error);
                deferred.reject();    //update state

            });
   }); 
});

//execute

$.when(dbAccessor).then(function () {
  alert('when complete');
});

參考資料

  1. http://api.jquery.com/category/deferred-object/
  2. http://joseoncode.com/2011/09/26/a-walkthrough-jquery-deferred-and-promise/
  3. http://danieldemmel.me/blog/2013/03/22/an-introduction-to-jquery-deferred-slash-promise/
 
about 6 years ago

最近需要用到FTP,所以裝了 vsftpd 來使用,這篇同樣也是使用的心得筆記。vsftpd 是一套簡單使用的FTP,而且號稱相對安全。這裡我設定不使用linux上的帳號登入,而是透過PAM使用 htpasswd 做為登入的驗證。(PAM是一個整合多種驗證方式的一種機制,詳細的說明可參考鳥哥的介紹)。

安裝與設定

  1. 安裝
    簡單的指令即可安裝完成。

    sudo apt-get update
    sudo apt-get install vsftpd libpam-pwdfile
    
  2. 編輯設定檔
    先使用vim或nano等編輯器打開vsftpd.conf

    sudo vim /etc/vsftpd.conf
    

    以下介紹一些比較常修改的設定,有些設定在預設的設定檔裡是沒有的,需要自己加入:

    anonymous_enable=NO #取消匿名用戶
    connect_from_port_20=NO #停用預設的20 port
    listen_port=123 #設定port
    idle_session_timeout=600  #設定閒置時間
    local_enable=YES #使用linux帳戶登入
    chroot_local_user=YES #設定只能在自己的home目錄下
    #以下為PAM用
    hide_ids=YES
    guest_enable=YES    #設定pam的user全使用同一個帳號
    guest_username=ftp  #設定pam的user所使用的linux帳號
    user_sub_token=$USER
    local_root=/srv/ftp/$USER #設定user的Home目錄
    
  3. 編輯PAM設定
    開啟設定檔

    sudo vim /etc/pam.d/vsftpd
    

    主要是加入下面這兩行:

    auth    required pam_pwdfile.so pwdfile /etc/vsftpd.passwd
    account required pam_permit.so
    

    @include 那幾行及 pam_shells.so 通通要註解掉,最後設定檔如下:

    #Standard behaviour for ftpd(8).
    auth   required        pam_listfile.so item=user sense=deny file=/etc/ftpusers onerr=succeed
    #Note: vsftpd handles anonymous logins on its own. Do not enable pam_ftp.so.
    #Standard pam includes 
    #@include common-account #註解掉
    #@include common-session #註解掉
    #@include common-auth #註解掉
    #auth   required        pam_shells.so #註解掉
    #Customized login using htpasswd file
    auth    required pam_pwdfile.so pwdfile /etc/vsftpd.passwd
    account required pam_permit.so
    
  4. 建立使用者
    首先透過 htpasswd 來建立使用者,沒有 /etc/vsftpd.passwd 時,要用 -cd 來建立檔案,之後只要使用 -d 即可。 -d 是指定 htpasswd 的編碼方式為crypt,預設是使用MD5,但 vsftpd 對MD5的支援好像有點問題。

    htpasswd -cd /etc/vsftpd.passwd user1 #建立第一個user時使用
    

    建立使用者的Home目錄

    mkdir /srv/ftp/user1
    chown ftp:ftp /srv/ftp/user1
    chmod 555 /srv/ftp/user1 #chroot的根目錄不可以有寫入的權限
    
  5. 重新啟動vsftpd

    sudo service vsftpd restart 
    

疑難雜症

  1. 530 Login incorrect.
    檢查 pam.d/vsftpd 的設定,該註解的要註解掉。另外,authaccount 同一行內不可以有註解符號(#)。

  2. 500 OOPS: vsftpd: refusing to run with writable root inside chroot()
    這是因為新版的 vsftpd 限制當啟用chroot時,user的home目錄不可以有寫入的權限,只可以在子目錄下做寫入的動作。google大神上的解法,除了安裝另一個套件(可參考這裡)我沒測試過外,其餘目前測試都無效。所以乖乖把寫入的權限拿掉吧。

    chmod a-w /srv/ftp/user1
    
  3. 226 transfer done (but failed to open directory)
    這是因為目錄沒有x權限的問題,把x權限加上去即可

    chmod a+x /srv/ftp/user1 
    
  4. 550 Failed to change directory
    同226的問題,把x權限加上去即可

    chmod a+x /srv/ftp/user1 
    

參考資料

  1. https://vpsboard.com/topic/2417-running-your-own-small-ftp-server/
  2. 鳥哥
  3. http://askubuntu.com/questions/239239/ubuntu-12-04-500-oops-vsftpd-refusing-to-run-with-writable-root-inside-chroot
 
about 6 years ago

今天在使用jQuery Validatioin時,犯了個低級錯誤,所以記下來警剔自己一下。

這個功能只是很簡單的表單輸入,程式碼如下:

Html
<form role="form" method='POST'>
  <label>姓名:</label>
  <input type="text" placeholder="姓名" id='txtName' required>                  
  <label>Email:</label>
  <input type="email" placeholder="Email" id='txtEmail' required>
</form>
JavaScript
$(document).ready(function () {
    $('form').validate();
});

看起來好像沒什麼問題是吧? 但執行起來,整個form卻只會驗證第一個欄位(ex.txtName),其餘欄位都會被忽略,只要第一個欄位有輸入,form就會submit出去。
google之後才知道,如果想偷懶用 <input type="text" required> 這種寫法的話,input 也一定要給 name 這個屬性jQuery Validatioin才會正常的跑。

所以幫各input加入name可以跑了:

Html
<form role="form" method='POST'>
  <label>姓名:</label>
  <input type="text" placeholder="姓名" id='txtName' name='name' required>                  
  <label>Email:</label>
  <input type="email" placeholder="Email" id='txtEmail' name='email' required>
</form>

參考資料:

  1. http://stackoverflow.com/questions/5971392/jquery-validate-only-validates-one-field
 
over 6 years ago

Whenever是一個使用crontab的方式來做排程工作的gem,crontab 是一個linux內建的排程工具,詳細介紹可參考這裡crontab並不是目前最理想的排程方式,詳情可參考這裡。如果覺得whenever不合用的話,在rails還是有很多其他的gem可以選用,像是rufus-schedulerClockwork ...等,可依照自己需求做選擇。

以下就介紹一下 whenever的使用方式:

安裝
  1. 先在server或開發環境上安裝 whenever,直接於command line輸入以下指令:

    gem 'whenever', :require => false
    
  2. 再來就要思考要使用哪種方式執行工作,whenever 預設支援下列幾種方式,差別可看官方文件job types部份

    1. rake
    2. runner
    3. command
    4. script
    5. 當然您喜歡的話,也可以自己定義一種方式
  3. 這裡我選擇以 rake 的方式執行。所以在 lib/tasks 裡新增一個rake檔案,這裡我使用的檔名為jobs.rake

    lib/tasks/jobs.rake
    # encoding: utf-8
    
    namespace :[namespace] do
        desc "這是一個rake" #此處可自行輸入task的描述
    
                task :[your task name] => :environment do
                        [your code here]
                end
        end
    end
    
  4. 接下來可以測試一下是否可正常執行,可以在command line輸入以下指令做測試:

    rake -T #會列出剛新增的rake
    bundle exec rake [namespace]:[task] #執行rake
    
  5. rake做完後,就要新增排程了,先產生必要的檔案,在專案目錄下以command line輸入以下指令:

    wheneverize .
    

    執行完後,會產生 config/schedule.rb 這個檔案,排程的設定可以設定在此。

  6. 編輯 config/schedule.rb 來設定排程:

    config/schedule.rb
    env :PATH, ENV['PATH'] #要用bundle時必須要加
    
    set :output, 'log/cron.log' #設定log的路徑
    
    every 1.day, :at => '1:00 am' do
        rake "[剛剛的namespace名稱]:[剛剛的task名稱]"
    end
    
  7. 在command line輸入以下指令來檢查排程設定是不是都正確:

    whenever
    

    此命令會以 crontab 的方式將你設定的排程指令顯示出來,如下:

    0 1 * * * /bin/bash -l -c 'cd [/path/to] && RAILS_ENV=production bundle exec rake [namespace]:[task] --silent >> log/cron.log 2>&1'
    

    其中 crontab 的說明可在command line下 man 5 crontab來看說明文件,格式大致上為: 分 時 日 月 星期 命令* 代表全部,以上面的 0 1 * * * 來說,就是每天的1:00都要執行。

  8. 如果有使用capistrano的話,可以在config/deploy.rb 加入下面程式:

    config/deploy.rb
    set :whenever_command, "bundle exec whenever" #要用bundle時必須要加,否則就不用
    
    require "whenever/capistrano"
    

    這樣設定的話,deploy時就會自動執行 whenever --update-crontab 將排程設定至 crontab 中。
    這個設定僅適用於 Capistrano 版本2以下,3以上請參考 官方文件 Capistrano V3 Integration部份

疑難排解
  1. 若排程沒有正常執行,可以去開啟 config/schedule.rb:output 設定的log檔位置,此案例中為 log/cron.log,檢查是否有錯誤訊息。
  2. 若錯誤訊息為 bundle command not found 請在 config/schedule.rb 中加入 env :PATH, ENV['PATH']
參考資料
  1. 官方文件
  2. Rails Guides
  3. http://stackoverflow.com/questions/9482298/rails-cron-whenever-bundle-command-not-found
  4. 鳥哥的 Linux 私房菜
 
over 6 years ago

今天把Rails的專案佈署到OpenSUSE時,執行 bundle install 後發生如下的錯誤:

Building native extensions.  This could take a while...
ERROR:  Error installing bcrypt-ruby:
    ERROR: Failed to build gem native extension.

        /usr/bin/ruby1.9 extconf.rb
mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h

Gem files will remain installed in /usr/lib/ruby/gems/1.9.1/gems/bcrypt-ruby-3.1.2 for inspection.
Results logged to /usr/lib/ruby/gems/1.9.1/gems/bcrypt-ruby-3.1.2/ext/mri/gem_make.out

找了好久,才google到是少了 ruby-devel 這個套件,直接下下面的指令就可以解決了。

zypper install ruby-devel

這個問題可能不只有OpenSUSE會出現,其他Linux版本(如ubuntu、centos...等)若有相同狀況的話,試著安裝 ruby-devel 試試,也許就可以解決了。

備忘一下,OpenSUSE一般還需要 sqlite-develgcc-c++ 這兩個套件,才可以成功執行完 bundle install,記得也要安裝一下

zypper install sqlite-devel gcc-c++

參考資料
http://onlyproblems.wordpress.com/2013/03/14/redmine-2-2-3-on-opensuse-12-3/

 
over 6 years ago

想要同時支援iOS 6及7的App,若有使用 UITableView,且cell的內容是自己定的,也就是客製化的cell(custom cell),在iOS 7使用時,可能會發現跑起來會怪怪的,甚至有bug出現。

我的狀況是:
我在 UITableView 的每個cell中,放入 UITextField ,讓使用者可以直接修改值。修改值後,就會變更sqlite中的資料。這在iOS 5、6 都可正常的修改sqlite中的值,但在iOS 7就不正常了。經過測試後,發現iOS 7會在 UITextFieldUITableViewCell 中間,加入一個叫 UITableViewCellScrollView 的東西。

  • iOS 6以下的結構:

    UITableViewCell > UITextField
    
  • iOS 7以上的結構:

    UITableViewCell > UITableViewCellScrollView > UITextField
    

所以我使用 UITextFieldsuperview 去取得 UITextField 所處的cell時,就會取不到,所以IndexPath.row都是0,造成資料儲存的錯誤。

知道問題就比較好解決了,可用class來判斷是否為 UITableViewCell,若不是的話,就再抓一次 superview

UITableViewCell *cell = (UITableViewCell *)((UITextField *)sender).superview;
    
//for iOS 7
if(![cell isKindOfClass:[UITableViewCell class]]) {
    cell = (UITableViewCell *)cell.superview;
}

或者也可以用版本來控制:

UITableViewCell *cell = (UITableViewCell *)((UITextField *)sender).superview;
    
//for iOS 7
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {       
    cell = (UITableViewCell *)cell.superview;
}
 
over 6 years ago

git 在做 merge 時,git會很聰明的判斷現在的狀況是否適合是使用fast-forward,如果適合就會自動使用fast-forward,使用與不使用有什麼差別,就讓我在這說明一下。

以下直接用案例說明比較清楚:

  1. 我要新增一個功能,所以在master上開一個branch叫feature1。

  2. 我在新增功能時,加了3個commit在feature1中。

  3. 當功能開發完成時,checkout回master,要將 feature1合併回master,這時可以有兩個選擇:

    (1)使用 fast-forward:
    megre時可加上 --ff 來強制使用fast-forward

git merge --ff feature1

master會將feature1上的3個commit全視為master的commit,並將HEAD移到跟feature1的HEAD相同的commit上。結果如下:
![screenshot_23.png](http://user-image.logdown.io/user/1213/blog/1196/post/166352/oNdSHJElTqaJQzVxKqPj_screenshot_23.png)


<span style='color:blue;'>(2)不使用 fast-forward(non-fast-forward):</span>

megre時可加上 --no-ff 來強制不使用fast-forward

git merge --no-ff feature1

master會新增一個commit,內容為feature1上的改變,並將HEAD移到新的commit上。結果如下:

至於該使用哪種方式,就要看個人需求了,如果是新增功能的branch,個人認為使用non-fast-forward的方式比較好。這樣可以很清楚的看出哪些是新增功能用的commit,哪些是原本master的commit。master才不會有太多不相干的commit交錯在一起。而且master要移除功能時,也只要處理一個commit就可以了。

參考資料:
http://git-scm.com/docs/git-merge
http://ihower.tw/blog/archives/2620
http://victorlin.me/posts/2013/09/30/keep-a-readable-git-history

 
over 6 years ago

simple-captcha 是一個簡單使用的驗證碼gem。
本來simple-captcha都用的好好的,但Mac升級至OSX 10.9 Mavericks突然就看不到驗證碼了,取而代之的是一個叉燒包圖片。

檢查了一下log,發現是 ImageMagick 有問題,出現的錯誤訊息如下:

StandardError (Error while running convert: convert: unable to read font `/usr/local/share/ghostscript/fonts/n019003l.pfb' @ error/annotate.c/RenderFreetype/1125.
  convert: Postscript delegate failed `/var/tmp/magick-53221tooRNTmQ6Ur8': No such file or directory
  @ error/ps.c/ReadPSImage/833.
  convert: no images defined `/var/folders/l1/rchc9ldj1xq35j85hg2b_ylm0000gn/T/simple_captcha20131126-53212-1tot2et.jpg' @ error/convert.c/ConvertImageCommand/3078.
  ):
  /usr/local/lib/ruby/gems/1.9.1/gems/galetahub-simple_captcha-0.1.5/lib/simple_captcha/utils.rb:17:in `run'
  galetahub-simple_captcha (0.1.5) lib/simple_captcha/image.rb:78:in `generate_simple_captcha_image'
galetahub-simple_captcha (0.1.5) lib/simple_captcha/middleware.rb:36:in `make_image'
galetahub-simple_captcha (0.1.5) lib/simple_captcha/middleware.rb:18:in `call'
warden (1.2.3) lib/warden/manager.rb:35:in `block in call'
warden (1.2.3) lib/warden/manager.rb:34:in `catch'
...

才知道是 ghostscript 的字型不見了,知道問題就好解決了,直接用brew安裝ghostscript就可以解決了。

 brew install ghostscript

PS.並不一定是每個人升級OSX 10.9 Mavericks都會出現這問題,只是我剛好遇到問題而已