2014年12月12日 星期五

linux, bash, find 的應用(-exec, sed -i, 檔案內取代, 與xargs比較)

find -exec

find -exec 指的是將找到的檔案,送到後面的指令去處理。從-exec\;為止,代表是接受從find送來要處理的指令,而送來的檔案將用{}代表找到的檔案。

譬如你想把所有副檔名為.log的檔案刪除掉,可以這麼做

find . -type f -name "*.log" -exec rm {} \;

(其實我加上-type f有點多餘,因為我已經指定-name "*.log"了,就不可能輸出資料夾了。)

如果刪檔案的時候,一直要你按yes,可以這樣

yes | find . -type f -name "*.log" -exec rm {} \;

yes這個指令會一直串流輸出yes,這樣刪檔案就自動一直輸入yes

例子中find找到的檔案,就放到-exec\;之間的指令去處理,譬如找到了檔案develop.log,就會變成

rm develop.log

另外,{}並不是規定只能出現一次,譬如你要將資料夾內所有檔案加上副檔名log,可以這麼做

find . -type f -exec mv {} {}.log \;

這指令會包含子資料前內的檔案都加上.log副檔名。如果只想要目前資料夾的檔案,所以可以加上-maxdepth 1,若要地回到下一層資料夾可以將1改成2,3或4以此類推

find . -maxdepth 1 -type f -exec mv {} {}.log \;

因此,若要批次改變檔案的內容,就可以搭配find -execsed -i。sed 加上 -i 參數,代表直接對檔案內容做修改。

我常常在幫別人複製或移動網站,很多人的網址都寫成包含域名的絕對路徑,所以常常要用這個指令去找出所有含舊域名的檔案,並改成新域名。

譬如舊域名為http://www.old.com,要改成http://www.new.com,我會這麼做

find . -type f -exec sed -i 's/www.old.com/www.new.com/g' {} \;

(sed這個指令在OS X上面如果照上例那樣執行,會有錯誤訊息。詳細請參考stackoverflow上面的討論)

這樣子會找遍所有的檔案,需要的時間有點久。我會再加上參數-not -name "不需要的檔名",來排除一些不需要尋找的檔案

find . -type f -not -name "*.log" -not -name "*.jpg" -exec sed -i 's/www.old.com/www.new.com/g' {} \;

像這樣跳過所有的log檔與jpg圖檔,有需要可以加上其他如-not -name "*.png",才加快速度。或者乾脆指定-name "*html"-name "*.php",只尋找相關網頁檔。

xargs

xargs使用起來就沒有像find -exec可以將檔名以{}的形式傳給指令去安排。xargs只能收管線資料當成指令參數來執行。

雖然使用上沒有靈活,但管線指令就是他的優點,所以他不只可以搭配find指令,echo資料給他也可以,譬如

echo a b c d e | xargs touch

一次創造了a b c d e五個檔案。如果find後面不用-exec,可以改用管線指令傳給xargs,譬如本篇第一個刪除檔案的例子,改用xargs變成

find -name "*.log" | xargs rm

但是後面改檔名的例子,就沒辦法這麼簡單的處理(可以處理,但改用xargs還要搭配其他指令,本篇已經太長,不再節外生枝介紹)

另外像改檔案內容的指令,因為檔名參數接在最後面,所以也可以很簡單的改用xargs來處理

find . -type f | xargs sed -i 's/www.old.com/www.new.com/g'

因此,當我用find在處理檔案時,我會用find -exec比較靈活。其他情況下我就用管線串流到xargs來處理

2014年12月9日 星期二

HTML, CSS, 相對視窗或螢幕的高度與寬度

w3school.com網站,CSS Units有各種與寬度的表示法

以我使用的頻率來排序:

  • px: 使用螢幕幾個像素(但是還要考慮Retina螢幕像素是一般螢幕像素的兩倍)
  • %: 相對父層的大小比例
  • vh, vw: 相對於瀏覽器展示網頁區域的大小(不是整個瀏覽器的大小,沒包含瀏覽器的工具列,只有展示網頁的區域)
  • vmin: vh, vw取最小值(另外還有vmax則是取最大值,但是目前IE跟safari不支援)

px%很常用,vhvwvmin是CSS3的新產物,表示相對瀏覽器展示頁面的大小


相對視窗大小

這邊先說明,vhvwvmin只包含網頁顯示區域的長寬,不包含瀏覽器的工具列

先從dom的最根本講起好了,一份HTML文件,根是<html></html>(雖然沒有嚴格規定,不寫也能顯示),然後這個根的父元件就是瀏覽器的網頁頁面顯示區

因此,如果對<html></html>宣告大小是100%就跟宣告100vh一樣,因為都是指瀏覽器的網頁頁面顯示區大小

這個範例是將html設定為100vh

<html style="height: 100vh">
    <head>
        <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>

        <script type="text/javascript">
            console.log($('html').height());
        </script>
    </head>
</html>

這個範例是將html設定為100%

<html style="height: 100%">
    <head>
        <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>

        <script type="text/javascript">
            console.log($('html').height());
        </script>
    </head>
</html>

這兩者在瀏覽器的console應該輸出一樣的大小

如果從html, body到div , 這些tag都填入100%,則從頭到尾都填滿整個視窗(當然還注意body預設有padding, margin佔有一些寬度,要把他們都設為0,才會真的佔滿全畫面)

不過現在有vh, vw,就不用這麼麻煩了,可以只設定長度為100vh, 寬度為100vw,就可以填滿整個畫面(依然得注意body預設佔有padding跟margin)

像下面這樣就能把兩個div排在一起,並且以瀏覽器寬度3:7的比例,分割成兩個區塊

<html>
    <head></head>
    <body style="margin: 0;padding: 0;">
        <div style="width: 30vw;float:left;">Left...</div>
        <div style="width: 70vw;float:left;">Right...</div>
    </body>
</html>

還有一個問題,以往使用桌上型電腦的螢幕,我們可以預期大部分人的電腦,都是寬度大於長度,大部份的人也都是用全螢幕瀏覽網頁。所以我們很信任的將長寬都設定成vh就可以變成正方形,並且不超過瀏覽器大小。

但是現在手機螢幕,可以拿直的,也可以拿橫的,那要怎麼辦呢?就使用vmin

譬如我們要顯示一張正方形的圖片,希望以最大張的方式呈現

<html>
    <head></head>
    <body style="margin: 0;padding: 0;">
        <img src="圖片路徑" style="width: 100vmin;height: 100vmin;">
    </body>
</html>

這樣就不怕桌機或手機,螢幕擺直得或橫的,都以最大張,且不會超出瀏覽器範圍欄顯示圖片


相對螢幕大小

那可不可以相對於使用者整個螢幕的大小?

可以,但是要使用Javascript去設定,在Javascript裡面直接使用screen.heightscreen.width,就可以取得螢幕大小(關於screen是什麼?請參考w3school的相關頁面)

之後再用dom語法,去改你要改的地方,假設你要將所有的div tag寬度都隨使用者螢幕寬度改變,這邊使用jQuery:

$('div').width(screen.width);

這時候不管使用者怎麼拉動瀏覽器的寬度,所有的div tag寬度都等是螢幕寬度

如果想單純使用javascipt的話,語法比較冗長一點,可以參考w3chool的頁面,注意他傳入的參數後面還要接單位,所以在screen.width之後記得再加上"px",之後才當引數傳進去

回到原本的jQuery版本,如果想要75%的螢幕大小

$('div').width(screen.width * 0.75);

想要30%的螢幕大小

$('div').width(screen.width * 0.3);

以此類推,我一開始會害怕對screen.width做數學運算,因為不確定內容是字串還是數字。但是多慮了,javascript可以對數字做數學運算,也可以對內容是數字的字串做數學運算

'10' * '10' // 輸出100
'10' * 10 // 也輸出100

如果是在Ruby,則是輸出"10101010101010101010",所以在javascript裡面,不用怕,他內容是數字,就直接對他做數學運算,不用先檢查型別。(若字串內容不是數字,會輸出NaN,Not a Number的意思)

2014年12月4日 星期四

Linux, bash, 壓縮, tar, bz2, xz, 7z, 壓縮率

Linux, bash, 壓縮, tar, bz2, xz, 7z, 壓縮率

如果只是想查各種副檔名的解壓縮指令,只要Google副檔名就可以找到(例如:tar.bz2)

這邊主要是在對資料夾壓縮時,為了自訂壓縮率而深入研究壓縮指令而產生。

tar 可以對檔案或資料夾進行單存的打包(或解開打包),後面接c代表打包,x代表解開打包。這邊在Bash底下產生一個簡單的文字檔來練習:

echo "Hello World" > text
tar c text
# 輸出 text000644 000765 000000 00000000014 12437746653 012241 0ustar00wemeewheel000000 000000 Hello World

# 如果要儲存,加上參數f,並指定存檔名稱
tar cf text1.tar text

# 或者使用串流來寫入檔案也可以(實際應用不建議)
tar c text > text2.tar

原始檔案12B, 打包後2K, 使用串流寫入的10K, 可以確定打包完全不會有壓縮的效果

 12B 12  4 10:57 text
2.0K 12  4 11:01 text1.tar
 10K 12  4 11:01 text2.tar

其實打包主要目的在於,我常用的bz2壓縮,只能對檔案壓縮,不能對目錄壓縮,所以才需要先把目錄打包成一個檔案,再用bz2去壓縮它

接下來,弄個目錄跟文字檔來測試

mkdir dir
echo "Hello World" > dir/text

tar cf dir1.tar dir
tar c dir > dir2.tar

結果一樣,使用串流寫入的,佔的容量比較大

102B 12  4 11:28 dir
2.5K 12  4 11:44 dir1.tar
 10K 12  4 11:44 dir2.tar

接下來,使用bz2壓縮看看

bzip2 -z dir1.tar
bzip2 -z dir2.tar

來看看所佔用的容量

102B 12  4 11:28 dir
167B 12  4 11:44 dir1.tar.bz2
169B 12  4 11:44 dir2.tar.bz2

壓縮過後都變小了,分成先打包,再用bz2壓縮,其實是因為tar指令只是打包,頂多只能加上參數j,表示打包過程使用bz2,(或參數z,表示打包過程使用gz),無法指定壓縮比

# 將目錄打包之後,再用bz2壓縮並指定壓縮比
bzip2 -9 -z dir1.tar

如果要寫成一行的話,可以打包時,輸出到串流,在使用管線指令輸入到bzip2, 因為輸入是串流,bzip2無法指定要壓縮的檔案,所以把壓縮結果使用參數c,再一次輸出到串流,並儲存成檔案

tar c dir | bzip2 -c > dir.tar.bz2

# tar 無法指定壓縮比,在bzip2指定壓縮比
tar c dir | bzip2 -9 -c > dir.tar.bz2

同理,如果使用xz壓縮時,想一併指定壓縮比,也用同樣方式

tar c dir | xz -9 -c > dir.tar.xz

因為bz2壓縮,在Windows開啟中文檔名,常常變成亂碼,可以改用7zip來壓縮,如果使用7zip,他沒有辦法吃管線進來的資料,那怎麼辦?分兩行指令,先打包在壓縮嗎?

不用啦~ 7zip可以直接對目錄壓縮,直接壓縮,只是它指定壓縮比的參數是-mx數字

7za a -mx9 dir.7z dir

這樣子就直接對dir這個目錄以最大壓縮的壓縮比去執行了

2014年12月3日 星期三

Ruby, dup跟clone的不同, 淺層複製與深層複製

Ruby, dup跟clone的不同, 淺層複製與深層複製

Ruby裡面,對於物件的複製有兩個方法,分別是對物件呼叫dupclone,這兩種有什麼不同呢?這兩種方法並非只是alias的名稱而已。

先不討論淺層複製深層複製的問題,先來看看呼叫dup做了什麼事情。假設有個class:

class Foo
    def foo
        'foo'
    end
end

我們來試試看,產生Foo物件,然後對他呼叫dup

foo = Foo.new
dup_foo = foo.dup

dup_foo.foo # => 'foo'

一切依照預期,順利的進行複製了,接下來我們對foo動態增加一些Method上去,看看能不能夠複製(至於如何對物件動態增加Method請參考這篇)

class << foo
    def run
        'run'
    end
end

foo.foo # => 'foo' 原本class就有宣告 沒問題
foo.run # => 'run' 動態新增的Method 也沒問題

dup_foo_2 = foo.dup
dup_foo_2.foo # => 'foo' 原本class就有宣告 沒問題
dup_foo_2.run # => 發生錯誤,No Method Error

由此,可以知道,dup方法,僅對完整宣告的class進行複製,動態增加的不會複製,那麼我們再多測試一點,使用另一種方法增加Method。我們把整個程式碼完整再寫過

class Foo
    def foo
        'foo'
    end
end

foo = Foo.new
dup_foo = foo.dup

# 使用另一種方法增加Method到Foo上面去
class Foo
    def go
        'go'
    end
end

foo.go # => 'go'
dup_foo.go # => 'go', 正常執行

由此可見,只要是宣告成所有class Foo可以使用的Methods都可以被dup所複製,只有動態為單一Foo物件增加的Methods不能被dup複製進去

那麼clone方法,相對dup方法,就是完整地複製出一模一樣的物件

class Bar
    def bar
        'bar'
    end
end

bar = Bar.new

class << bar
    def run
        'run'
    end
end

clone_bar = bar.clone
clone_bar.bar # => 'bar'
clone_bar.run # => 'run', 動態增加的方法也一併複製了

接下來,來探討一下淺層複製深層複製的問題,都只是淺層複製

ary = ['hello']
dup_ary = ary.dup
clone_ary = ary.clone

dup_ary[0][0] = 'X'
p ary # => ["Xello"], 原本的ary被改掉了

clone_ary[0][0] = 'Y'
p ary # => ["Yello"], 原本的ary也被改掉了

如果要進行深層複製,就得遞迴對陣列中每個物件,都進行複製。

在Ruby on Rails裡面,直接提供deep_dup(但沒有提供deep_clone)

如果是在傳統的Ruby,我們可以自己實作deep_dup方法,來進行深層複製,在stackoverflow上面,我找到一個不錯的寫法

# 寫得不錯,如果物件是Array, 就繼續遞迴
class Array
    def deep_dup
        map {|x| x.deep_dup}
    end
end

# 直到不是Array,就進行複製,但這邊沒考慮到Ruby的容器還有Hash, Set ... 等,這些也需要做深層複製,所以這邊若遇到Hash, Set之類的容器就變成淺層複製了
class Object
    def deep_dup
        dup
    end
end

# 這邊小心Numeric類別的物件,沒有dup方法,所以就直接回傳本身
class Numeric
    def deep_dup
        self
    end
end

這個例子只有對Array類別進行深層複製,如果你有用到Hash, Set,甚至是你自己寫的容器,自己實做的iterator pattern ... 等,就得自行再加上deep_dup方法上去。

2014年12月2日 星期二

RUBY,大寫數字轉小寫數字,國字數字轉阿拉伯數字

RUBY,大寫數字轉小寫數字,國字數字轉阿拉伯數字

今天,我在處理中文地址的時候,發現有人的門牌號碼填國字,譬如中正路四三號,也有人會填中正路43號,當然沒人是填中正路肆參號,反正程式邏輯是一樣的。(本方法忽略四十三->43這種格式,若有必要,請自行寫程式判定京兆億萬千百十出現的位置,是否合理,然後再進行轉換)

一般想到的就是把字串裡面每個字遍尋一遍,然後寫個switch,對出現的字元進行轉換,譬如:

// psudo code
for each character in string
    switch(character)
    case: '一'
        convert_to(1)
    case: '二'
        convert_to(2)
        .
        .
        .
    case: '九'
        convert_to(9)
    end
end

但其實你使用的是Ruby語言的話,其實Ruby語言有提供一個很簡潔的方法string.tr(from_str, to_str)

使用的方式是這樣子的,提供兩個字串,兩個字串相對應的位置會互相轉換,譬如提供兩個字串:

#輸入字串1
'abc'
#輸入字串2
'123'

則會將a變為1, b變為2, c變為3,舉個實際例子:

"xx_abc_xx".tr('abc', '123')
# 輸出 "xx_123_xx"

"xx_ccc_xx".tr('abc', '123')
# 輸出 "xx_333_xx", '1'跟'2'都沒出現?因為呼叫tr的字串裡面也沒有'a'跟'b'呀~

"cba".tr('abc', '123')
# 輸出 "321", 為什麼反過來?因為呼叫tr的字串是'cba'呀~(a變為1, b變為2, c變為3)

上面的例子,是比較快速簡易的應用,兩個輸入的字串長度一樣,對於呼叫tr方法的字串,就會將內容替換掉。那如果要傳入兩個字串不一樣長,譬如第一二個比較長,那多出來的部分則不會用到,譬如:

"abc".tr('ab', '123')
# 輸出"12c", 第二個引數中,那個'3'則不會用到

如果是第一個字串比較長,譬如:

"abc".tr('abc', '12')
# 輸出"122", 第一個引數比第二個引數的長度還要長,則第二個引數比較長沒得對應的部分,直接用第二個引數中,最後一個字元替換

#同理
"abcdefghijk".tr('abcdefg', '12')
# 輸出"1222222hijk"

所以我們將大小寫國字數字轉阿拉伯數字,可以全部用列舉的方舉出來,讓他們一一對應

chinese = "零一二三四五六七八九"
arabic = "0123456789"

"一三五七九".tr(chinese, arabic)
# 輸出 "13579"

# 反過來,也可以用阿拉伯數字轉國字
"98765".tr(arabic, chinese)
#輸出 "九八七六五"

#多個大寫數字
big_chinese = "零壹贰叁肆伍陆柒捌玖"
"壹贰叁肆伍".tr(big_chinese, chinese)
# 輸出"一二三四五"

但這邊有個簡易的寫法,參考Ruby的正規表示式(可以在這個網頁做測試)

輸入引入的部分,如果正規表示式中,使用中括號的用法,例如:

[abc]  # 表示任何abc
[^abc] # 表示任何非abc,注意'^'符號放在中括號內,代表'非',而不是代表開頭字元
[1-9] # 表示任何1到9之間的字元

所以我們數字轉換的例子也可以寫成

chinese = "零一二三四五六七八九"
"一三五七九".tr(chinese, '0-9')
# 輸出 "13579"

就可以直接用'0-9'代表所有的阿拉伯數字,那麼是否也可以用'零-九'來代表所有的國字數字呢?

不行的,因為國字的排序並不像英文字元一樣的排法,你可以這樣測試看看

('零'..'九').each{|c| puts c}
# 沒有印出任何字元

('一'..'九').each{|c| puts c}
#印出 一 丁 丂 七 丄 丅 丆 ... 九

可見國字是用筆劃排序的,所以無法使用'零-九'來代表數字,得一一列舉出來。

所以關於門牌號碼,就可以這樣子處理了,全部轉為阿拉伯數字

address = '中正路四三號'
chinese = "零一二三四五六七八九"

address.tr(chinese, '0-9')
輸出"中正路43號"

同理,也可以玩玩英文大小寫轉換

# 小寫轉大寫,一般寫法
'hello world'.upcase # => "HELLO WORLD"

# 使用tr, 多此一舉一下
'hello world'.tr('a-z', 'A-Z') # => "HELLO WORLD"

2014年7月26日 星期六

Ruby: class << self

Ruby: class << self

我們先不管class << self是什麼意思,我們先來看看動態語言有哪些有趣的用法。

Ruby可以動態的在class裡面新增,修改一些方法。譬如:

class Foo
  def foo
    return 'Hello Foo'
  end
end

f = Foo.new
puts f.foo # Hello Foo

class Foo
  def foo
    return 'Edit Foo'
  end
end
puts f.foo # Edit Foo

有玩過RPG Maker的人應該很熟練了,RPG Maker的模組都是這樣掛上去的,直接把模組複製到整個Script的最後面,將系統原先的方法override掉(這邊我講override其實有點問題,因為override比較適合用在編譯時期,子類別繼承並修改父類別的方法,但這裡單純只是修改類別原先的方法)

好的,那如果我產生兩個Foo物件,延續上例的類別

class Foo
  def foo
    return 'Hello Foo'
  end
end

f1 = Foo.new
f2 = Foo.new
puts f1.foo # Hello Foo
puts f2.foo # Hello Foo

class Foo
  def foo
    return 'Edit Foo'
  end
end

puts f1.foo # Edit Foo
puts f2.foo # Edit Foo

因為是直接對類別做修改,所以上例中,f1與f2的行為都會改變。

那如果我們今天想要做出像Javascript那種功能,只對單一物件新增或修改方法,有辦法做到嗎?譬如Javascript可以這麼做:

function Foo(){
}

Foo.prototype.foo = function(){
  return "Hello foo";
}

var f1 = new Foo();
var f2 = new Foo();

console.log( f1.foo() ); // Hello foo
console.log( f2.foo() ); // Hello foo

// 只對物件f1修改方法

f1.foo = function(){
  return 'Edit foo';
}

console.log( f1.foo() ); // Edit foo
console.log( f2.foo() ); // Hello foo

// 只對物件f1新增方法

f1.bar = function(){
  return 'Hello bar';
}

console.log( f1.bar() ); // Hello bar
console.log( f2.bar() ); // ERROR, undefined

這麼動態呀~那Ruby呢?Ruby有沒有辦法做到,Ruby有個語法,看起來很像直接對單一物件,宣告出一個類別

class << some_object

直接來個可以執行的範例

class Foo
  def foo
    return 'foo'
  end
end

f1 = Foo.new
f2 = Foo.new

puts f1.foo # foo
puts f2.foo # foo

# 只對f1 修改方法
class << f1
  def foo
    return 'Edit foo'
  end
end

puts f1.foo # Edit foo
puts f2.foo # foo

# 只對f1 新增方法
class << f1
  def bar
    return 'bar'
  end
end

puts f1.bar # bar
puts f2.bar # undefined method `bar'

好的,到這裡,回到我們的標題class << self,這是什麼意思?可以用在什麼地方呢?

還記得Ruby每個東西都是物件,我們要對類別增加類別方法是怎麼做的嗎?

class Foo
  def self.foo
    return 'foo'
  end
end

puts Foo.foo #foo

直接在類別裡,並非在物件會被呼叫到的地方使用self,則會指向Foo這個Class物件,所以我們直接對Foo這個Class物件加上方法,就會變成Foo的類別方法。

有沒有發現跟上面我們對單一物件新增或修改方法有點像?是的,既然Foo是個單一的Class物件,我們也可以單一的為它加上方法,成為類別方法。

class Foo
  class << self
    def foo
      return 'foo'
    end
  end
end

class Bar
end

puts Foo.foo #foo
puts Bar.foo #undefined method `foo' for Bar:Class (NoMethodError)

foo加到Foo類別的成為它的類別方法,所以不影響Bar類別

主要是學習Ruby語言的一個動態特性,使用簡潔的程式碼達到這些目的。就跟JAVA 8也做出Lambda Function了一樣。

那麼實際上有什麼應用呢?譬如Design Pattern裡面的Visitor Pattern在Ruby裡面可以這麼應用:

class Good
  attr_accessor :price

  def initialize price
    @price = price
  end

  def accept visitor
    visitor.visit self
  end
end

class TaxVisitor

  def initialize rate
    @rate = rate
  end

  def visit obj
    if obj.respond_to? :price

      class << obj
        attr_accessor :tax
      end

      obj.tax = obj.price * @rate
    end
  end
end

good = Good.new 30
good.accept(TaxVisitor.new(0.25))
puts good.tax

我們對Good類別,accept稅金的訪問,然後就在訪問時,動態的為貨物加上了tax這個方法,可以對貨物存取稅金。這個範例寫得不太好,其實price與tax應該要對外關閉,寫成唯讀才對


另外,物件也可以動態的掛載module

module Foo
    def foo
        puts 'foo'
    end
end

obj1 = Object.new
obj2 = Object.new

obj1.extend Foo

obj1.foo # => foo
obj2.foo # => 錯誤,NoMethodError

2014年4月14日 星期一

Ruby: module, include, extend, self, module_function

module

一個最簡單的module

include

在class使用include, 可以讓module內的方法為class的物件方法,在ruby稱為mixin

include可用在模擬多重繼承,因Ruby不像C++可以直接多重繼承,所以宣告為module,在mixin到class裡面

extend

在class使用extend,可以讓module內的方法為class的類別方法

與include的不同點在於

  • include: module內的方法為class的物件方法
  • extend: module內的方法為class的類別方法
module Foo
  def foo
    puts 'foo'
  end
end

class Bar
  extend Foo
end

Bar.foo  # 印出hello foo (foo已成為Bar的類別法)

self in module

self是什麼?就是呼叫Ruby當前物件本身指標所指的,在主程序直接呼叫self返回main。

先看最簡單的module

module Foo
end

直接呼叫self會出現什麼呢?

module Foo
  p self #印出 Foo
end

別忘了ruby式腳本式的語言,這個程式沒寫錯,單純只是宣告module,他裡頭的命令句還是被執行,所以印出Foo

那麼這個Foo物件是什麼?簡單,印出來看看

module Foo
  p self.class #印出Module
end

非常清楚,Ruby號稱所有東西都是物件,所以module也是物件,所以self的使用很清楚了,直接動態的在Foo物件上面加上方法

module Foo
  def self.foo
    puts 'hello foo'
  end
end

Foo.foo # 印出hello foo, 直接呼叫Foo這個Module形態的物件,裡頭的foo方法

所以同理,class裡面的self也是一樣的應用,class也是可以實體化為物件,並動態的加上方法,通常稱為類別方法,這種類別方法,在其他程式語言裡面也很常用,只是實作方式不同,Ruby是把class也當作物件處理

extend self

綜合上面所說的,如果module裡面 extend self會發生什麼事呢?

module Foo
  extend self

  def foo
    puts 'hello foo'
  end
end

Foo.foo # 印出hello foo

如果在class內使用extend會使module內的方法,成為類別方法,那如果在module使用,同樣會成為module方法,這個講法並不好,因為在Ruby裡面都是物件,只是在其他語言稱為類別方法,所以我照著命名

extend self如果有點難懂,先看extend 別的模組

module Foo
  def foo
    puts 'hello foo'
  end
end

module Bar
  extend Foo
end

Bar.foo # 印出hello foo (foo成為bar的類別方法)

就在跟class裡面extend一樣。同理extend self,就可以讓自己本身的方法,可以用module方法的方式呼叫,也可以被class include時,被當成物件方法呼叫

module_function

從名稱得知,使用module_function,則只能當成module function來使用

將要變成module function的方法名稱,使用symbol的方式傳入

module Foo
  def foo
    puts 'hello foo'
  end

  module_function :foo
end

Foo.foo  印出hello foo(變成module方法)

跟上面extend self不同的是,它就只能當module方法,如果在class內include,也不會變成物件方法

module Foo
  def foo
    puts 'hello foo'
  end

  module_function :foo
end

class Brb
  include Foo
end

Foo.foo # 印出hello foo
brb = Brb.new
brb.foo # 跳出NoMethodError錯誤,因為foo只能當module方法,不是當物件方法來呼叫

included

還有一個動態的方法,讓class include時,再決定哪些是類別方法,哪些是物件方法

module內建一個方法叫做include,可以在該module被include時被呼叫,舉個例子

module Foo
  def self.included(receiver)
    puts "#{self}被#{receiver} include進去了"
  end
end

class Brb
  include Foo #印出Foo被Brb include進去了
end

所以我們可以在module內決定include它的class要哪些類別方法,幫它(class)呼叫extend就行了,例如

module Bar
  def bar
    puts 'hello bar'
  end
end

module Foo
  def self.included(receiver)
    receiver.extend Bar # 幫它(class)呼叫extend 並extend Bar進去
  end
end

class Brb
    include Foo
end

Brb.bar # 印出hello bar

同理,也可以幫它呼叫include

module Bar
  def bar
    puts 'hello bar'
  end
end

module Foo
  def self.included(receiver)
    receiver.send :include, Bar # 幫它(class)呼叫include 並include Bar進去
  end
end

class Brb
    include Foo
end

brb = Brb.new
brb.bar # 印出hello bar

也可以在module直接嵌套在module裡面 讓程式馬更簡潔,下面示範嵌套完,並直接呼叫extend與include

module Foo
  module Bar # 將用來當類別方法
      def bar
        puts 'hello bar'
    end
  end

  module Bla # 將用來當物件方法
    def bla
        puts 'hello bla'
    end
  end

  def self.included(receiver)
    receiver.extend Bar # 將Bar設定為類別方法
    receiver.send :include, Bla # 將bla設定為物件方法
  end
end

class Brb
    include Foo
end

Brb.bar
brb = Brb.new
brb.bla

2014年3月19日 星期三

twitter-bootstrap-rails使用font awesome

twitter-bootstrap-rails已經把font awesome編譯在裡面了,不過用法有點不一樣,我是上stack over flow才知道, ,這種更動沒有寫在README裡面誰知道啊

這邊是標準font awesome的用法 譬如

<i class="fa fa-anchor"></i>

不過用twitter-bootstrap-rails編譯在裡頭的font awesome要這樣用

<i class="icon-anchor"></i>

第一個fa不用,第二個fa改成icon

或者直接使用它的Helper

<%= glyph(:anchor) %>

用Helper是比較簡潔一點

2014年3月18日 星期二

ssh 直接複製到檔案結尾 (ssh append to file)

scp 沒辦法,scp會直接覆蓋。rsync沒辦法,會讓兩個檔案同步變成一模一樣...

這時候就只好用ssh 原始操作方式

ssh user@remote "要處理的指令"

那麼就簡單了,如果要抓取遠端的檔案,附加到本地,那就在遠端使用cat指令,然後用>>修改資料串流附加到本地的檔案不就行了嗎?沒錯

ssh user@remote "cat filename" >> /LocalPath/FileName

那我就舉一反三,資料串流改用>就是複製!!! ... 對不起,脫褲子放屁了,直接用scprsync不就好了... 這邊就是發現scprsync只能複製,我們才去找可以附加檔案的方式,別又把這個方法,重新拿回去當複製的的功能用,有句名言當你手上拿著鎚子,你就覺得任何東西都像釘子

好,剛剛是抓遠端檔案附加到本地,那反過來,抓本地檔案附加到遠端呢?我上查到了一個很神奇的用法

cat local-source-file-name | ssh user@some.domain “cat >> remote-target-file-name“

得改用管線的指令,如果用ssh user@some.domain “cat >> remote-target-file-name“ < cat local-source-file-name,一定失敗,因為那串指令會通通送到遠端去執行,cat local-source-file-name這個指令也是在遠端執行,根本不是cat你本地的資料。

總結:

  1. 遠端到本地,直接ssh 遠端 cat 檔案 >> 本地檔案
  2. 本地到遠端,要做管線,cat 本地檔案 | ssh 遠端 cat >> 檔案

2014年3月11日 星期二

安裝zsh + oh-my-zsh 出現 /usr/bin/env: zsh: 沒有此一檔案或目錄 (/usr/bin/env: zsh -: No such file or directory)

zsh 跟 oh-my-zsh 安裝完,可能會出現類似下面錯誤訊息,雖然他沒任何影響,不過我總覺得怪怪的

# 中文系統
/usr/bin/env: zsh: 沒有此一檔案或目錄

# 英文系統
/usr/bin/env: zsh -: No such file or directory

通常會出現這類的訊息,都是指令找不到。

這邊指zsh這個指令找不到,試試看輸入which zsh看看是不是沒有把zsh這個加到$PATH裡面,沒有的話加入就好了。

如果加入了一樣找不到,我trace了一下zsh的執行過程,通常都是跟oh-my-zsh的安裝順序錯了的時候才會發生。

這時編輯家目錄底下檔案vim ~/.zshrc,會看到

# 他先執行了oh-my-zsh.sh
source $ZSH/oh-my-zsh.sh
# 然而oh-my-zsh.sh已經在使用zsh這個指令了

# 這裡才把宣告路徑,所以當然zsh指令找不到
export PATH="/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/bin:/bin:/root/bin:/usr/local/bin"

這時就把它們兩個對調一下就好了

# 像這樣把 export PATH 放到 source $ZSH/oh-my-zsh.sh 上面
export PATH="/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/bin:/bin:/root/bin:/usr/local/bin"
source $ZSH/oh-my-zsh.sh
# 這樣oh-my-zsh.sh裡面就可以用zsh指令了

存檔後,登出再進來,他就可以正常使用zsh指令,不再出現這種錯誤訊息了

CentOS zsh 自行編譯

CentOS用yum安裝的zsh版本有點舊,這邊記錄一下,怎麼下載自己安裝

# 下載最新版的 zsh (筆下日期 2014/03/11) 並存檔為 zsh.tar.bz2
wget http://sourceforge.net/projects/zsh/files/latest/download\?source\=files -O zsh.tar.bz2

# 解壓縮
tar xvjf zsh.tar.bz2

# 你的版本不一定是5.0.5,我筆下日期 2014/03/11 到5.0.5版,你的可能更新
cd zsh-5.0.5

# 普通編譯流程,configure後 看缺什麼lib 裝一裝
./configure
make
sudo make install

# 把"/usr/local/bin/zsh" 這串字加到 sudo tee -a /etc/shells 裡
echo "/usr/local/bin/zsh" | sudo tee -a /etc/shells

# 上面那一步 你的 chsh -l 才查得到zsh
# 可以切換到/usr/local/bin/zsh 了
chsh -s /usr/local/bin/zsh

# 重新登入 shell就切換過去了
exit # and relogin

# 跟bash一樣,放個.zshrc檔案在使用者根目錄,讓系統進入zsh前讀取
touch ~/.zshrc # 你的變數 或alias就可以加在這裡面了
# 有點跟bash不一樣的地方是,沒有.zsh_profile,讓系統只讀第一次的檔案

# 如果你有裝oh-my-zsh,建議直接建立軟連結(上面那一步touch就不用做了,做了就先把~/.zshrc刪掉就可以了)
ln -s ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

2014年3月5日 星期三

Linux系統which與type指令,對比$PATH與Hashed路徑

我安裝Ruby後,為了搞定路徑花了老半天,後來弄懂了Linux指令在whichtype$PATHHashed就清楚多了。

如果你的系統有內建Ruby,打which ruby,會發現他的路徑在usr/bin/ruby底下,接著不管你用了什麼方法裝了新的Ruby,就把新的路徑在$PATH裡加到usr/bin的前面,系統通常就預設執行在$PATH裡面,放在前面先找到的那個Ruby。

但是有個情況,你加了$PATH以後沒有用,系統還是一直去執行舊的Ruby。可是打which ruby明明就是新的路徑呀?

我遇到的情況是這樣打type ruby會發現ruby is hased(/usr/bin/ruby),這是什麼意思呢?為什麼which rubytype ruby的路徑會不一樣。這種情況是ruby這個命令,在系統一開始就被寫在系統的hash table裡面。這邊簡單分類一下whichtype的不同:

  • which:
  • 單純從$PATH裡面尋找到的路徑。
  • type:
  • 指令真實被執行時使用的路徑。

which只是單純顯示在$PATH,type才是真實下命令按Enter後去執行的路徑。

所以安裝了新Ruby,可是系統若之前就把Ruby放在hash table裡面,他就會一直執行舊版本的Ruby,這時候只要重登目前這個Shell,讓hash table refresh一下就好了。如果還是不行,系統不曉得寫在什麼地方,他就是喜歡自動把舊Ruby放到hash table裡面。那就只好手動改了,在bash底下執行:

hash -r #清掉hash table裡所有的資料

上面這樣子是全清掉,全部的hash table重新建立。或者也可以像下面這樣:

hash -d ruby #單純在hash table清掉Ruby

這樣子hash table裡面沒有ruby了,系統就得乖乖執行$PATH裡面找到的ruby囉。

不只有ruby唷,不管安裝什麼php,python還是sqlite之類的,如果跟系統預設衝突,而且還是放在hash table裡面的,都可以用這個方法解決路徑的問題

2014年2月21日 星期五

陣列洗牌程式(shuffle array)

陣列如何把它的順序打亂,作出類似洗牌的效果,我一直都很頭痛,搞得非常的複雜。至從用了Ruby,Array物件包含shuffle方法之後,我就沒思考過陣列洗牌的問題了,反正Ruby幫我處理得好好的。

Ruby

# 52張牌的牌堆
poker = (1..52).to_a    # => [1, 2, 3, ... , 52] 
# 洗牌打亂
shuffled = poker.shuffle           # => [22, 32, 12, ...
# 也可以直接打亂原來的陣列
poker.shuffle!          # => [39, 47, 3, ...

Javascript

Javascript我就頭痛了,我得自己寫洗牌的方法。我在網路上找到了這個演算法,仔細看了之後,才知道原來洗牌可以這麼簡單:

// 原本for迴圈是一行程式,太難理解,這邊改寫成多行
function shuffle(o){
    for(var j, x, i = o.length; i;){
        j = Math.floor(Math.random() * i);

        // javascript的array是0-base
        // 所以迴圈第一次進入,--i後表示陣列最後一個位置。
        x = o[--i];
        o[i] = o[j]; 
        o[j] = x;
        // 以上三行代表以x為temp, o[i], o[j]做交換
    } 
    return o; //回傳陣列,我一開始也看錯看成回傳0
};

變數說明

  • 引數o: 將被洗牌的陣列
  • for迴圈內
    • i : 將會從陣列的最後一個位置,慢慢往前移到第一個位置(但移到第一個位置時for迴圈不執行,因為Javascript的數值0也代表false,會離開迴圈。0代表flase這點跟Ruby不一樣)
    • j : 將會被亂數選擇,選到要被交換的位置
    • x : 用來暫存o[i]的數值,幫助o[i]與o[j]做數值交換

就這樣從最後一個位置開始,依次往前隨機挑選一個位置與它交換(可能挑到自己,表示不交換),來達到洗牌的效果,陣列多大,就執行幾次,時間複雜度O(n),效率也不錯。 注意這邊傳入的陣列是call by reference,會修改原來呼叫方法時傳入的陣列,所以直接:

// 5張牌的牌堆
poker = [1, 2, 3, 4, 5];
shuffle(poker);
// poker 就直接被打亂了

如果不想打亂原來的牌堆,就稍微改寫一下,不要動到原來的陣列:

function shuffle(o){ //v1.0
    var ary = o.slice(0);
    for(var j, x, i = ary.length; i;){
        j = Math.floor(Math.random() * i);
        x = ary[--i], ary[i] = ary[j];
        ary[j] = x;
    }
    return ary;
};

這邊用到了一個小技巧slice(start, end),這是javascript用來複製陣列的一個區塊,指令開始與結束的位置,小技巧是如果傳入0,就直接複製整個陣列。如果你想說:

啊直接ary = o 不是更快

呵呵,我建議你從C的指標在開始複習一下。

C語言

使用C語言,改些小地方,C語言的rand()產生的是0 ~ RAND_MAX的一個數字,所以直接j = rand() % i;就行了。還有C語言的0也表示false的意思,大概只有Ruby跟大家不同,目前我只見過Ruby的0表示true。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int *shuffle(int *o, int length){
    int i,j,x;
    for(j, x, i=length; i;){
        j = rand() % i;
        x = o[--i];
        o[i] = o[j];
        o[j] = x;
    }
    return o;
}

int main(void) {
    srand(time(NULL));
    int i, poker[] = {1, 2, 3, 4, 5};
    shuffle(poker, 5);
    for(i=0; i&lt;5; i++)
        printf("%d ", poker[i]);
    return 0;
}

這邊我是用call by address的寫法,如果你不想改變原來的陣列。C語言有一堆記憶體的函式讓你用,你可以宣告一塊記憶體空間(malloc),然後記憶體複製(memcpy),之後傳入shuffle去洗牌

不建議在shuffle函式裡面宣告記憶體區塊,因為什麼時候要free掉記憶體很麻煩,交給呼叫他的函式去決定什麼時候free掉比較正確,shuffle函式就專心洗牌就好。

JQuery

在網頁上面,要把一些HTML的元件打亂,可以直接在JQuery裡面加個物件方法給他:

jQuery.fn.shuffleElements = function () {
    var o = $(this);
    // 這邊的for迴圈,使用原本的一行寫法
    for (var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
    return o;
};

裡面那個for迴圈洗牌的演算法沒變。

至於一些基本的jQuery用法,jQuery.fn,可以直接取得jQuery的prototype,關於javascript的prototype是什麼,請至函式 prototype 特性觀看。這邊使用jQuery.fn就可以直接在jQuery的protoype裡面加入shuffleElements方法,然後就直接傳入一堆HTML元件讓他去洗牌,譬如你的網頁有一堆圖片,簡單一點就這樣:

var img = $("img").shuffleElements();
$("body").append(img);

這麼寫的話,可以簡單地把你網頁裡面的圖片全部洗牌之後,再全部放回<body>最下面。這邊主要是講洗牌,你可以改一改把它變成<ul>的順序之類的,或其他的應用。

2014年2月19日 星期三

Rails 在CSS裡使用圖片

暴力法

反正最後都會被編譯在public下面,可以下rake assets:precompile指令,看看最後圖片被放到哪裡去了,通常都在public/asset底下,而網頁伺服器的根目錄是public/所以你的css就直接把圖片定位在/assets/底下,例如:

.your_class {
    background-image:url('/assets/your_img.png');
}

非常不建議用這個方法,程式碼非常不靈活

內嵌Ruby法

在Rails裡面,你的檔案想要內嵌Ruby,只要把檔名結尾加上.erb,就可以最常見的就是page.html.erb,但可沒這麼簡單唷,就連CSS檔以可以內嵌Ruby,一樣把你的CSS加上.erb,譬如style.css.erb,這樣CSS內就可以使用Ruby了。

既然可以使用Ruby了,那就大膽的把Rails內建的Helper拿出來用吧,asset_path("img.jpg"),就行了,例如:

.your_class {
    background-image:url('<%= asset_path("your_img.png") %>');
}

這樣至少靈活多了,至少你不用擔心Rails編譯之後,會把你的圖檔放到Public的哪裡去,被改了什麼檔名。

SCSS法

Rails預設使用SCSS,你打開你的Gemfile就可以看到了,預設已經有gem 'sass-rails',在Rails的CSS裡面取圖,我推薦這個方法,因為這個方法程式碼最簡潔:

.your_class {
    background-image: image-url("your_img.jpg");
}

發生什麼事?沒錯使用SCSS的話,image-url("your_img.jpg");就行了,image-url是Rails的Helper,可別跟CSS原生的background-image:url(your_img.jpg);給搞混囉

.your_class {
    /* 這個是Rails的Helper */
    background-image: image-url("your_img.jpg"); 

    /* 這個是CSS的原生寫法 */
    background-image: url(your_img.jpg);
}

這兩種寫法要到的圖片可不一樣唷,Rails的Helper編譯後,會自動幫你安排圖片的路徑。而CSS原生的寫法,你就得自己編譯過後,自己去public/asset裡面找,你的圖跑到哪去了。

2014年2月14日 星期五

Ruby,檔案定期備份

我最近把資料庫系統換成了SQLite,直接放到RamDisk去執行。今天想要寫支程式,可以定時檢查RamDisk,裡面的sqlite檔有沒有變動,有變動就備份到硬碟去。於是使用Ruby寫下了:

FILE_TO_CHECK = ARGV[0]
FILE_TO_SAVE = ARGV[1]

if !File.exists?(FILE_TO_SAVE) 
  `cp #{FILE_TO_CHECK} #{FILE_TO_SAVE}` 
  exit
end

check_file = File.new FILE_TO_CHECK 
save_file = File.new FILE_TO_SAVE

if check_file.mtime > save_file.mtime
  `cp #{FILE_TO_CHECK} #{FILE_TO_SAVE}` 
end

不錯,不錯,想說這樣這隻程式可以放到crontab去,將來有同樣要檢查的檔案就編輯在crontab裡:

0 * * * * ruby /check_file.rb sinkfile sourcefile

不過做到這邊,突然想到,安裝rsync不就行了,直接

0 * * * * rsync sinkfile sourcefile

rsync就是檢查檔案有無更動,有更動的才會複製,而且它是這麼老牌又穩定的lib了,呵呵,沒關係,就當做是在練習使用vim編輯器

2014年2月13日 星期四

Ruby,Time.new(自定時間格式)

Ruby 製作Time物件時,必須遵守一定的格式,譬如

Time.new(2014, 2, 13, 16, 30, 30, "+08:00") #=> 2014-02-13 16:30:30 +0800

Time.new裡頭接受的參數從年,月,日,時,分,秒,時區,沒填的就預設是0,全部都沒填,那就給系統當前時間。那問題如果我們得到的時間是一個字串呢?例如我們有一個時間字串:

2014-02-13 16:50:47 +0800

這串文字當參數傳到到Time.new裡面只會出現錯誤訊息,難道要開始做苦工先字串解析,在放到Time.new裡面嗎?

Time.strptime

用到字串解析也就太辛苦了,Ruby 的Time class提供一個strptime方法,可以直接定義time format,require 'time'之後就可以使用了直接舉個例子:

require 'time'

t = Time.strptime("2014-02-13 16:50:47 +0800", "%Y-%m-%d %H:%M:%S %z") 
# => 2014-02-13 16:50:47 +0800

t.class # => Time

就直接得到了Time物件了,省去了字串解析的麻煩。

如果你需要的DateTime物件,也可以依法炮製:

require 'time'

dt = DateTime.strptime("2014-02-13 16:50:47 +0800", "%Y-%m-%d %H:%M:%S %z") 
# => #<DateTime: 2014-02-13T16:50:47+08:00 ((2456702j,31847s,0n),+28800s,2299161j)>

dt.class # => DateTime 

一樣可以直接得到DateTime物件。

如此就可以從各式各樣的時間格式,直接產生TimeDateTime物件了。來試個怪怪的格式:

time = Time.strptime("22日2008年06月 16-50::23", "%d日%Y年%m月 %M-%S::%H")
# => 2008-06-22 23:16:50 +0800

呵呵 怪怪的格式也沒問題了

2014年2月12日 星期三

以Ruby語言執行系統命令

以Ruby語言去執行系統命令,有下列三種方式

  • exec args
  • system args
  • %x(args) 或 `args`

exec args

exec會中斷目前Ruby正在進行的process,然後執行系統命令。所以這個指令還蠻少用的,可以在irb裡面試試

root$ irb
2.1.0 :001 > exec 'pwd'
/Users/wemee/Documents/GitHub/test
root$

irb直接被中斷掉,然後進入bash執行pwd,所以大概只能用在linux的crontab裡面,定期執行一次就中斷

system args

system則會保留目前Ruby的process,執行完系統命令後,依照執行結果回傳: true: 執行成功 false: 執行失敗,譬如移動不存在的檔案 * nil: 執行的指令本身就打錯

system 'pwd'    # => true
system 'mv not_exist_file.txt foo.txt'  # => false
system 'wrong instruction'  # => nil 

%x(args) 或 `args`

這兩個也都不會重斷Ruby的process,並且執行完後,完整回傳系統命令回傳的字串。%x(args) 或 `args`兩種寫法執行結果都一樣,所以我都寫`args`,而且ruby-style-guide也多半認為少用%x比較好。

dir = `pwd`     # => dir = "/Users/root/projects\n"
files = `ls`    # => files = "main.rb\ntemp.txt\ntest.txt\n"

這邊可以接著用string.chmop把最後面字尾那個"\n"刪掉,或用用string.gsub("\n", " ")把所有的"\n"替換成空白欄位。


最後,執行這些指令之前,記得檢查一下你的系統唷,很多linux的指令,跟windows上面的指令不同,回傳值也不同。如果一定要用的話,最好去先找找官方的函式庫有沒有可以利用的,或到GitHub上面找找有沒有人寫出可以跨平台的功能了。

2014年2月7日 星期五

Rails, 上傳圖片或其他檔案

目前網路上關於如何在Ruby on Rails上傳檔案的方式,最熱門的就屬paperclip

它處理圖檔必須用到convert指令,如果是Linux系統可以用which convert這個指令查看一下,系統裡面有沒有convert指令。如果沒有的話paperclip是推薦用ImageMagick,如果你有要上傳圖檔的話,就安裝一下吧,Linux可以用rpm安裝,MacOS就直接brew install imagemagick

之後就使用gem安裝paperclip,gem "paperclip"

簡易使用方式

我是使用Rails 4,以下是Rails 4的場景。

產生資料庫欄位的部分它沒有實作在rails generate指令裡面,所以必須自己先產生Model, 然後再產生一個Migration,把欄位加上去之後,做rake db:migrate,例如:

# 產生 "image" Model, 以及一個欄位"title"
rails g model image title:string

打開所產生的model檔案(app/models/image.rb),為它增加一個file欄位關聯

class Image < ActiveRecord::Base
  has_attached_file :file, 
                    styles: {medium" "300x300>", thumb: "100x100>" }, 
                    default_url: "/images/:style/missing.png"

  validates_attachment_content_type :file, content_type: /\Aimage\/.*\Z/
end

但是只是先設定好Model關聯而已,我們實際上資料庫Table裡面並沒有file欄位,我們利用migration為他增加資料庫的file欄位:

# 產生一個migration 準備增加file欄位
rails g migration image_add_column_file

開始修改產生的migration檔案

class ImageAddColumnFile < ActiveRecord::Migration
  def self.up
    add_attachment :images, :file
  end

  def self.down
    remove_attachment :images, :file
  end
end

執行rake db:migrate

這邊要注意,因為我們習慣在migration裡面,使用change函式,讓migarate自動決定up與down的做法,譬如up做add_column,那麼down就做remove_column。

但rails裡頭並沒有內建add_attachment相對是remove_attachment,所以這邊我們自己保險一點,up與down做什麼都定義清楚。

Rails_Admin

如果你有裝Rails_Admin,這時候進入後台就可以看到,已經可以上傳圖檔了。


Controller and View

先用Rails_Admin隨便加幾個圖片之後,接下來我們弄個Controller來玩玩

rails g controller images

路徑設定,就直接用直接RESTful

resources :images

打開images_controller.rb,先弄個index來試試

class ImagesController < ApplicationController
  def index
    @image = Image.first
  end
end

直接取出第一筆資料 在images/index.html.erb裡面顯示看看

<%= image_tag @image.file.url %>
<%= image_tag @image.file.url(:medium) %>
<%= image_tag @image.file.url(:thumb) %>

效果:


上傳圖片,讀出圖片都沒問題,其他就是Rails基本功的問題囉。至於paperclip其他部分,有研究在繼續Update本篇文章下面

2014年2月6日 星期四

Ruby 併行賦值(Parallel Assignment)

前言

原本在找Ruby要怎麼交換兩個陣列的位置,找到一個很神奇的方法

class Array
    def swap!(a,b)
        self[a], self[b] = self[b], self[a]
        self
    end
end

完全看不懂在幹嘛,如果是C語言,在同一行裡使用逗號,則表示從右到左依次執行而已,例如:

int i,j;
for(i=0,j=5;i<10;i++,j--);

在C語言這個迴圈共執行10次,每次都執行i++與j--,我把i++與j--寫在一起用逗號分開,其實也可以這麼寫

int i,j;
for(i=0,j=5;i<10;){
    i++;
    j--;
}

有時候為了簡潔會塞在同一行。

可是Ruby這麼做,我全看不懂在幹嘛,在文章開頭的swap!函式裡頭,self[a]在我眼裡就是回傳self[a],然後將self[b]賦值self[b]等於沒變,最後再回傳self[a]而已,完全不清楚這是怎麼swap。

Parallel Assignment

查了一下ruby-doc.com之後,找到了Parallel Assignment這個關鍵字,終於搞清楚了,Ruby會將等號右邊所有的物件,按照相對位置賦值到等號左邊的參數,就叫做Parallel Assignment,譬如你要將x,y,z分別賦值1,2,3,在C語言你可能這麼寫

int x=1, y=2, z=3;

在Ruby裡面可以這麼寫

x,y,z = 1,2,3

就是如此而已,x,y,z就分別被賦值1,2,3。

當然,像我這種宅宅,一定還會好好地玩弄一下,我故意讓等號兩邊無法成對:

1: a,b = 1,2,3     # a=1, b=2
2: i,j,k = 1,2     # i=1, j=2, k=nil

行1,我原本以為b會被賦值為陣列[2,3],其實不用這麼古怪,Ruby設計直接把多出來的3丟掉,聰明的設計。

行2,我原本以為會出現錯誤訊息,但這也太嚴謹了,Ruby直接讓k=nil就行了,也是聰明的設計。

接著繼續惡搞一下,看他的語法有沒有靈活性:

a,b = c=1, 2            # a=1, b=2, c=1
a,b = c,d = 1, 2, 3     # Compiler Error
# 編譯錯誤 所以自己加上括號
a,b = (c,d = 1, 2), 3   # a=[1,2], b=3, c=1, d=2

有趣唷Parallel Assignment會回傳一個陣列,所以很靈活

SWAP

既然如此,我們就可以利用Parallel Assignment,來進行簡潔的swap,將x,y兩數交換:

# Parallel Assignment 定初值
x,y = 1,2   # x=1, y=2

# 一樣用Parallel Assignment技巧交換兩數
x,y = y,x # x=2, y=1

你想要3個數字一起交換也可以

x,y,z = 1,2,3   # x=1, y=2, z=3
x,y,z = y,z,x   # x=2, y=3, z=1

搞清楚之後,文章開頭提的那個陣列交換swap!函式就看得懂了

def swap!(a,b)
    self[a], self[b] = self[b], self[a]
    self
end

簡單將a,b兩個位置交換之後,最後把回傳陣列本身

2014年2月5日 星期三

費氏數列(Fibonacci)

Fibonacci on wiki

使用遞回

/*
 * @parama n 第n項(n為正整數)
 * @return 第n項的值
 */
function fibonacci(n)
    if n <= 1
        return n
    return fibonacci(n-1) + fibonacci(n-1)
end

使用迭代

/*
 * @parama n 第n項(n為正整數)
 * @return 第n項的值
 */
function fibonacci(n)
    if n <= 1
        return n
    int a=0, b=1,t;
    for i = 2 to n do
        t = a+b
        a = b
        b = t
    end
    return b
end

使用cache優化遞回

/*
 * @parama n 第n項(n為正整數)
 * @return 第n項的值
 */
function fibonacci(n)
    static Array cache ||= {0,1}
    if cache[n] not null
        return cache[n]
    cache[n] = fibonacci(n-1) + fibonacci(n-1)
    return cache[n]
end

我在Github上使用Ruby的實作

Fork me on github

Markdown to HTML(Maruku)

Maruku on Github

本文網址

純Ruby做成,可將Markdown語法轉換成HTML,latex,md...等格式,以下簡單做點介紹。

安裝

gem install maruku

先在irb下面做點測試

require 'Maruku'            # => true

# 設定Markdown字串
markdown_string = '#大標題'

# 使用Markdown字串 建立Maruku物件
doc = Maruku.new(markdown_string)

#從Maruku物件輸出HTML文檔
doc.to_html                 # => "\n<h1 id=\"\">大標題</h1>\n" 

#從Maruku物件輸出latext文檔
doc.to_latext_document      # => "\\documentclass{article}\n\n% ... 

至於還有哪些Methods 自己用Ruby內建的methods語法來顯示

Maruku.methods              # => 一堆class methods
Maruku.new.methods          # => 一堆instance methods

可以再加上正規表示式來尋找

Maruku.new.methods.grep(/html/)  # => 一堆HTML相關的instance methods
Maruku.new.methods.grep(/latex/) # => 一堆latex相關的instance methods

與Ruby on Rails結合

在Gemfile裡面加上

gem 'maruku'

譬如你自己做個Blog,讓人使用Markdown語法來寫文章。文件從資料庫取出來,直接傳給view顯示就行了,例如文章儲存在Post資料表的content欄位

Markdown_string = Post.first.content
@doc = Maruku.new(Markdown_string)

在view的部分找個地方直接

<span>@doc.to_html.html_safe</span>

記得要使用html_safe,不然只會出現一堆&lt ; &gt ; 的符號,當然你自己得確定從Markdown轉出來的HTML是安全的(至少我目前還沒發現,要怎麼在Markdown裡面塞javascript,所以都當它們都是安全的)

Code Block for Blogger

Code Block for Blogger

我使用Markdonw語法打部落格文章,先轉換成Html之後,在貼到Blogger去,但是闡述程式碼的Code Block的區塊,產生出來的HTML是

<pre><code> ... </pre></code> 

這些HTML貼到Blooger去,因為沒有CSS樣式裝飾,所以只有簡單的換行縮排,看起來有點像引言

這邊我們可以自己加上CSS Style上去,讓他看起來漂亮點。首先Markdown轉出來的code block有兩種形式,一種是嵌入式的,譬如-> int i=0; <- 直接嵌入在行內,另一種是整個程式碼區塊,譬如:

int i=0;    /* 程式碼自己佔一個區塊 */

嵌入式的程式碼區塊產生的HTML會是`int i=0;

整個程式碼區塊產生的HTML會是`

int i=0;

差在<pre>tag的有無,所以我們只要分別加上這兩個CSS Style給blooger的範本,就行了。可以自己寫,不過我直接在網路上找別人做好的:

<style>
code {
background-color: #F9F9F9;
border: 1px dashed #2F6FAB;
color: black;
/* line-height: 1.1em; 
padding: 1em;*/
}

pre code {
display: block; /* fixes a strange ie margin bug */
font-family: Courier New;
font-size: 10pt;
overflow:auto;
background: #f0f0f0 url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAASwCAYAAAAt7rCDAAAABHNCSVQICAgIfAhkiAAAAQJJREFUeJzt0kEKhDAMBdA4zFmbM+W0upqFOhXrDILwsimFR5pfMrXW5jhZr7PwRlxVX8//jNHrGhExjXzdu9c5IiIz+7iqVmB7Hwp4OMa2nhhwN/PRGEMBh3Zjt6KfpzPztxW9MSAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzB8HS+J9kUTvzEDMwAAAABJRU5ErkJggg==) left top repeat-y;
border: 1px solid #ccc;
padding: 10px 10px 10px 21px;
max-height:1000px;
line-height: 1.2em;
}
</style>

把上面一串貼到你的Blogger範本去,進入編輯HTML,放在<head>裡面就行了。


這個CSS將兩個HTML tag <code><pre><code> 加上了一些裝飾。

如果在網路上找相關的CSS style樣式表,大部份別人的CSS style樣式表是內容大約是這樣

.post .codeblock { ... }

事實上這樣比較正確,在指定的程式碼區塊Tag加上class=codeblock,才會套用此CSS樣式。此法可以將Markdown產生的程式碼區塊,與非Markdown產生的<code></code>鬆綁。

但我懶得Markdown轉換後還要一個一個加上class name,而且我貼到Blogger去的文章一定都是我用Markdown打的,所以就直接CSS指定<code><pre><code>區塊都套用CSS。