2013年6月1日 星期六

Middleman奇技淫巧大揭密

上一篇我們介紹到了middleman的基礎知識以及slim的安裝,這一篇我們會介紹更多好用的工具,我們先接續上一篇講到的slim:

使用slim作為project預設的template engine


上一篇教到的slim安裝方式其實不能算是很方便,因為安裝過後還必須手動將所有的erb附檔名換成slim,檔案內容也要改成slim的格式,並不是太親民,我們希望一開始產生project時裡面的頁面就全部是以slim的格式呈現的。
幸好middleman-slim這一款gem可以幫我們做到這件事。來,簡單兩步驟:
  1. gem install middleman-slim 
  2. middleman init PROJECT_NAME --template slim

ok,這樣就搞定了。


懶得按F5沒關係 - LiveReload幫你自動刷新瀏覽器頁面


LiveReload真的可以稱得上是究極懶人功能,目前市面上最有名的是這個:http://livereload.com/,但他是要花錢買的。現在使用middleman讓你不花半毛錢,而且幾乎零設定,一樣是簡單的三步驟:
  1. 在Gemfile中加入這行:gem "middleman-livereload"
  2. 在config.rb中加入這行:activate :livereload
  3. 在終端機輸入:bundle install,重啟伺服器
現在只要在檔案中做了任意修改並存檔後,瀏覽器的頁面就會自動刷新,搭配外接螢幕真的可以說是神兵利器啊~XD


高效率撰寫css - sass&compass


sass是一種可以被編譯成css的語言,他的出現是為了補足css的諸多限制,例如:無法使用變數、無法進行數學運算、沒有函式。習慣了sass之後,你可以更有效率的撰寫css,只是在寫完以後會需要多一個編譯的動作罷了,但幸好在middleman中,這些事情都會自動幫我們做好。
在開始教學之前,還要在講解一個東西,那就是compass。compass之於sass就好像jquery之於javascript,他提供了許多常用的函式及模組讓我們能更有效率的撰寫sass。
好,那我們就開始安裝sass及compass吧~
  1. 在Gemfile中加入這兩行:gem "sass"、gem "compass"
  2. bundle install
記得將附檔名.css改成.css.sass,還要注意一下縮排、分號、大括號喔。


用Lorem產生假文字、假日期、假圖片


middleman之所以好用,不只是因為可以整合上面提到的plugin,最大的原因就是他能嵌入ruby程式碼,幫你搞定許多routine work,例如:迴圈產生假的list、產生假文字。

在這裡我們就介紹一下如何使用ruby code來產生迴圈及假資料

Loop:

<% 5.times do%>
    <!-- HTML code -->
<% end %>

假文字:

<%= lorem.words 5 %>

假句子:

<%= lorem.sentence %>

假圖片:

<%= lorem.image("300x400", :background_color=>"333", :color=>"111", :text=>"YEAH") %>

<%= lorem.image("300x400", :random_color=>true %>


Middleman快速入手

之前就時常聽到Middleman這個東東,但是一直沒有機會去試他,因為其實自己在刻html上還沒遇到什麼太大的產能問題。今天為了教設計師怎麼迅速產生靜態網站,自己便下海來學了一下。

why middleman?

當然是因為他好吃好看又好玩
  1. 可以內嵌partial頁面
  2. 可以導入haml、slim加快layout速度
  3. 可以導入sass加快寫css的效率及管理
  4. 可以livereload,瀏覽器即時更新目前頁面


How to start?

首先,你得安裝ruby環境及gem,這部份不贅述。
安裝好ruby環境後,一行指令輕鬆安裝:
gem install middleman

好了,這樣就裝好了,接著我們來介紹三個實用指令:
  • middleman init [project_name]
  • middleman server 
  • middleman build


產生middleman project - middleman init


很簡單,在你要產生project的路徑下打這段指令即可:
middleman init [project_name]

這時middleman會幫你產生一些檔案:
source(資料夾)、
config.rb、
Gemfile、
Gemfile.lock、
.gitignore

我們主要要修改的html,css,javascript等檔案,都在source這個資料夾裡面。


啟動伺服器 - middleman server 


如果還要先安裝apache那也太累了,一樣在專案資料夾底下一行指令:
middleman server

輕鬆啟動你的服務~預設是跑在localhost:4567上


輸出你的專案 - middleman build


一樣在專案資料夾底下一行指令:
middleman build

就會在專案資料夾中產生build資料夾,裡面就是輸出後的靜態頁面了。
看到這裡你可能會很好奇,為什麼我還要多跑這道手續呢?
因為在middleman裡我們預設是使用erb來寫html,雖然跟html幾乎一模一樣,但是我們還是會多少使用到一些嵌程式碼或局部頁面的功能,而這樣的東西還是要經過compile成一般的html檔案,瀏覽器才看得懂。如果你是用sass來寫css,在這個步驟也會同時將scss檔轉成css檔案。


安裝其他套件讓你的專案建立更快速


在middleman中管理套件的方式跟rails是一樣的,只需要把需要安裝的gem加到Gemfile中即可,安裝時只要打這個指令:
bundle install

拿slim這個套件做舉例好了,slim是個跟haml和jade有著異曲同工之妙的html簡化工具,能讓你用更加簡潔的方式撰寫html,例如原本要寫:
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    
    <title>Welcome to Middleman</title>
    
  </head>
  
  <body class="index">
    <div class="welcome">
      <h1>Middleman is Watching</h1>
      <p class="doc">
        <a href="http://middlemanapp.com/">Read Online Documentation</a>
      </p><!-- .doc -->
    </div><!-- .welcome -->
  </body>
</html>

但用slim的話只要這樣寫就夠了,有夠簡單:
| <!doctype html>
html
  head
    meta charset="utf-8"
      title Welcome to Middleman
  body.index
    .welcome
      h1 Middleman is Watching
      p.doc
        a href="http://middlemanapp.com/"
      /! .doc
    /! .welcome

那要怎樣在middleman中使用這個簡潔的撰寫方法勒?只要簡單三步驟:

  1. 在Gemfile中加入gem "slim"
  2. 在config.rb中加入require "slim"
  3. 在終端機輸入:bundle install


OK,接著你就可以開始體驗這種簡潔快速的撰寫方式了!

更多關於middleman的奇技淫巧請看下一篇:Middleman奇技淫巧大揭密

2013年5月16日 星期四

[JS] 『返樸歸真』讓jQuery慢慢淡出你的網站吧 (1) -- Basic part

其實兩三個月以前就想寫這篇文章了,直到最近看到越來越多的文章都在提jQuery的pros & cons,想想也是時候做個彙整,順便重新整理一下自己在前端設計的思維。下面這些連結都有很多不錯的建議及教學:
最後一個連結 - vanilla.js算是一個對前端framework重度使用者開的玩笑,「維護團隊」還煞有其事的做了一個客製化選單來幫你"build"需要的模組,但是下載下來的檔案中當然是一行程式碼都沒有。
官網中舉的一些case應該都能讓你反思「我們到底有沒有必要這麼依賴jquery?」

從事web開發以來,在前端開發時一直與jQuery脫不了關係,但隨著對javascript了解的增加,發現自己已經不知不覺陷入了jQuery的設計模式,很多普通的功能其實在原生的javascript中一點都不難做,效能也比較好,我們就一步步從簡單的部份開始,慢慢學習怎麼設計出jquery-free的程式。


$(document).ready()

通常需要載入一些比較肥大的js檔時(例如jquery)而且不考慮相依性時,很多人會將他塞進頁面的尾巴,最後載入。因為頁面的render是synchronise的,所以在最前面塞入一堆肥大的js檔會使得接下來的DOM物件的render被block住,而產生「很慢」的錯覺。
所以如果你是遵照這樣的best practice,在js程式碼中,只要將需要ready後執行的code放在最後即可,就用不到$(document).ready()了。


query dom物件

在使用jQuery的時候,我們已經很習慣使用$(".class #id tag")這樣的方法來query出我們要的dom物件,但這樣的query方式並不是jquery獨創的,而是從css那邊學來的。且在w3c也早有規劃去實作這個api(http://www.w3.org/TR/selectors-api/),那就是querySelectorAllquerySelector
querySelectorAll及querySelector的差別只在前者會query出所有符合條件的element,回傳nodeList;後者只會回傳第一個符合條件的element。兩著的使用時機如下:
  • document.querySelectorAll
  • document.querySelector
  • nodeList.querySelectorAll
  • nodeList.querySelector
  • element.querySelectorAll
  • element.querySelector
支援ie8以上及其他瀏覽器

dom物件的操作

dom物件本身就有相當豐富的method可供使用,了解他們之後你會發現很多情況下我們真的沒有必要用到jquery,這邊舉幾個例子:

Style:

$(elem).css("color","red"); 
vs
elem.style.color="red";

Class:

$(elem).addClass("active"); 
vs
elem.className+=" active"; 
or
elem.classList.add("active"); //ie10+


$(elem).removeClass("active"); 
vs
elem.className= elem.className.split("active").join(""); 
or
elem.classList.remove("active"); //ie10+

HTML、text

$(elem).text("content");
$(elem).html("<p>TEST</p>"); 
vs
elem.innerText="content";
elem.innerHTML="<p>TEST</p>";

Append:

$(elem).append(child);
vs
elem.appendChild(child);

Remove:

$(elem).remove();
vs
elem.remove();


Event-binding:

$(elem).click(function(){});
vs
elem.onclick = function(){};

It sucks, I know. 因為只能綁一個function,第二次綁定時就會把前一個給覆蓋掉了。

$(elem).bind("click", function(){});
vs
elem.addEventListener("click", function(){});


這種方式要bind幾次都ok,要注意的是在unbind的時候必須在第二個參數傳入相同的reference才行,例如:
var foo = function(){};
elem.addEventListener("click", foo);
elem.removeEventListener("click", foo);

2013年5月8日 星期三

[ROR] ActiveRecord效能調校

雖然在使用Rails的過程中感受到orm的方便,但是ActiveRecord用爽爽的同時,許多對sql不是很瞭解的開發者可能會犯下了許多問題導致無謂的浪費了sql資源。在系統剛做好,資料量不大的時候不太是問題,但在使用者及資料量多了以後,問題就會一一浮現了,我們先來看一下一些常見的問題:


Resource.find(id).child_resources


這樣的寫法很直覺,也很方便,但是會造成兩個query:
Resource Load (x ms)  SELECT "resource".* FROM "resource" where id = n
ChildResource Load (x ms)  SELECT "child_resource".* FROM "child_resource" WHERE "child_resource"."resource_id" = n

如果改成這樣的寫法只需要一次的query:
ChildResources.where("resource_id=#{id}")

雖然只少了一次的query,但最多能減少50%的sql操作時間



Resource.all (沒有加select)


當你可能只是很單純的要Resource的id及name,而不需要肥肥的"content"欄位,使用select只拉出需要的欄位,可以幫以省下不少傳輸時間和記憶體。
雖然可以使用slim_scrooge這種gem,不過別這麼懶,自己養成好習慣吧!


利用joins將需要的資料一次拿完

剛開始開發rails的時候,常常會拿出model後就直接往view丟,相關的relation到時候再拿就好,例如這樣:
<%= @category.products.counts %>
但是這樣常常會產生無謂的sql query,少量還好,當你在前台對一堆@catgories跑each的時候會發生很可怕的情形....也就是所謂的 "N+1 Query"
所以盡量養成一個習慣,就是要用到的資料一次拉進來就好,最嚴謹的檢查方式就是將產出的資料用attributes轉成hash,所有要拿的資料都在裡面了,沒有query進來的relation絕對拿不到。不過這樣的作法可能有點矯枉過正就是XD
要一次拿完所需資料最簡單就是用join+select操作,例如:
@products=Category.joins(:products=>:photo).select("categories.name cat_name, products.name pro_name, photos.url")
<% @products.each do |product| %>
  <%= product.cat_name %>
  <%= product.pro_name %>
  <%= image_tag product.url %>
<% end %>
而不是
@products=Product.all
<% @products.each do |product| %>
  <%= product.category.name %>
  <%= product.name %>
  <%= image_tag product.photo.url %>
<% end %>


適時使用counter_cache

相信這種情境一定很常見:
進入網站後,螢幕顯示出你有幾個朋友、幾個相簿、幾篇部落格
這時,在controller裡面....

Bad code:

  @friend_count = current_user.friends.count
  @album_count = current_user.albums.count
  @blog_count = current_user.blogs.count


這時我們該使用counter cache把他cache起來:
在users增加friends_count、albums_count、blogs_count等欄位
在friend.rb、album.rb、blog.rb中的
belongs_to :user
改成
belongs_to :user, :counter_cache => true
如果之前已經有資料了,記得把user.xxx_count給update到目前的count數,但為了保護counter的正確性,counter是read-only的,我們只能使用Base.reset_counter來設定他。
而counter的reset程式碼最好是寫在migration.rb裡,讓各種環境下migrate時順便reset counter:
class AddCounterCacheToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :comments_count, :integer, :default => 0
    User.find_each do |user|
      User.reset_counter user.id, :friends, :albums, :blogs
    end
  end

  def self.down
    remove_column :users, :comments_count
  end
end

Good code:

@counts = User.find(current_user.id).select(:friends_count, :albums_count, :blogs_count).attributes



善用Transaction


利用transaction


find.each

2013年5月2日 星期四

[RB] class, instance, class instance variable

Instance variable

在程式碼中我們常能看到單一個@開頭的變數,但是可別一味把它當成是instance variable喔,如果是在instance method中被創造出來的那肯定是instance變數,但要小心這種情況:
class Med

  @@c='This is class variable'
  @c='This is class instance variable'

  def self.class_instance_c
    @c
  end
  def class_c
    @@c
  end
  def instance_c
    @c
  end
end

m = Med.new
puts m.class_c #->'This is class variable'
puts m.instance_c #->''
puts Med.class_instance_c #->'This is class instance variable'
上面的@c其實是class instance variable,是不能被物件實例存取的。
而當物件實例想要存取的@c是instance variable,並沒有被定義出來,所以才會是nil,印不出東西。


Class instance & Class instance variable

相較Class instance variable,Class variable比較好辨認,"@@"開頭的就是了。
這兩種變數的差別在於,Class variable是共用於繼承鏈上的,當Class variable被改動時,所有繼承鏈上的其他類別的此變數也會被改動。
可以想像成是直接改動javascript中的prototype
但是Class instance variable是各個類別獨立的,「可以被繼承,但是繼承給子類別後就沒有任何關係了」



cattr_accessor

就像是attr_accessor一樣,只是他提供的是對Class variable的讀寫,而不是instance variable



參考資料

[ihower] 深入Rails3: ActiveSupport 的 class_attribute
[ihower] 一些 Ruby Dynamic Features 記事 (1) OOP

2013年4月25日 星期四

[RB] Ruby中的Proc及Block

Block:

在寫ROR時,大家或多或少會碰到這樣的語法:
posts.each do |post|
  puts post.tiltle
end

posts.each {|post| puts post.tiltle}
上面兩段程式碼中用do-end和大括號{}包起來的就是block,但用{}包裹程式碼的方法只適用於程式碼是一行的情況。
學ROR以來有好長一段時間我都不太清楚他的妙用,只覺得這麼普通的東西為什麼要寫的那麼拐彎抹角,但是在看了code school的教學影片後發現他跟javascript的callback機制有異曲同工之妙,這樣的設計讓我們在做一些enumeration操作的時候能將block中的code傳入,之前之所以對這個概念一直搞不清楚就是因為很難具體的想像在each中視怎麼call這段code,現在我們就用這個demo來剖開這個黑盒子:
class Child
  def initialize
    @toys=['gun','hammer','puzzle']
  end
  def each_toy
    for toy in @toys 
      yield toy
    end
  end
end

mark = Child.new
mark.each_toy do |toy|
  puts "I'm playing #{toy}~"
end

在這個範例中block像參數一樣被傳入each_toy這個method,並在裡面的loop內被多次呼叫。這樣的設計除了使用起來很靈活、有彈性以外,也能減少重複的程式碼。如果你對javascript比較了解的話可以看這一段程式碼來幫助了解:
function Child() {
    var toys = ['gun','hammer','puzzle'];
    this.eachToy = function (cb) {
        for (var _i in toys) {
            cb(toys[_i]);
        }
    }
}

mark = new Child();
mark.eachToy( function (toy) {
  console.log("I'm playing " + toy + "~");
});

但是上面的程式碼中,block看起來一點都不像是物件,也不能被重複使用。如果要像是javascript一樣能將函數像物件一樣被宣告、保存起來,並傳入其他函數作為callback function,我們還少學了一些東西,那就是接下來要介紹的Proc物件。

Proc:

proc物件可以將ruby的程式碼保存起來,在需要的時候再執行他,或當做block傳入其他函數。proc有兩種實作方法:

By Proc.new

routine = Proc.new {|variable| Do something... }

routine = Proc.new do |variable| 
  Do something...
end

By lambda

routine = lamda {|variable| Do something... }

routine = -> {|variable| Do something... }

直接執行

routine.call value


把proc轉成block

要把proc物件轉為block時,我們要在這個物件的前面加上"&",例如:
posts.each &routine

在method中判斷是否有block傳入

要知道有沒有傳入block,我們可以使用block_given?來判斷:
if block_given?
  yield
else
  puts "No block past in"
end


小結

現在我們就來改寫一下一開始的範例:
class Child
  def initialize
    @toys=['gun','hammer','puzzle']
  end
  def each_toy
    if block_given?
      for toy in @toys 
        yield toy
      end
    else
      puts "No block past in"
    end
  end
end

mark = Child.new
routine = lambda { |toy| puts "I'm playing #{toy}~" }
mark.each_toy &routine

2013年4月13日 星期六

[JS] javascript OO設計 - 繼承

接觸javascript有一定的人應該都知道在js中,你所看到的幾乎都是物件。
函數是物件的第一型,Function和Object都是物件,也是函數的實例,而所有的function都是Function的instance。

關於原形(prototype)

prototype是函數物件特有的屬性,使用函數建構出來的物件會藉由原型鏈(prototype chain)而存取得到建構函數的prototype的屬性,用文字說清楚很難,不如我們直接看範例:
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.nation = "Taiwan";
var kevin = new Person("Kevin", "18");
var laura = new Person("Laura", "17");
alert(kevin.nation); // "Taiwan"
alert(laura.nation); // "Taiwan"
雖然kevin和laura本身並沒有被定義"nation"這個屬性,但是當使用這些不存在的屬性時,會找他們的建構函數中的prototype,再沒有的話還會看這個prototype物件的建構函數中有沒有這個屬性,直到最高層,也就是Object建構函數的prototype,這就是所謂的prototype chain。為了證明這點,我們再次修改一下上面這個例子:
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.nation = "Taiwan";
Function.prototype.whatTypeAmI = "Funciton";
Object.prototype.whatTypeAmI = "Object";
var kevin = new Person("Kevin", "18");
alert(kevin.nation); // "Taiwan"
alert(kevin.whatTypeAmI); // "Object"
alert(Person.whatTypeAmI); // "Function"

從上面可以看出雖然kevin這個物件沒有whatTypeAmI這個屬性,但是他會往上找Person.prototype物件有沒有這個屬性,再找不到就會找Person.prototype的建構函式,也就是預設的Object函式,這時我們會在他的prototype中找到whatTypeAmI這個屬性是"Object"。
而我們在呼叫Person的whatTypeAmI時也會發生找不到的情形,這時就會往上找到他的建構函數也就是Funciton的prototype,如範例所示,這時回傳的是"Function"。


利用prototype來實踐javascript繼承模式

我們可以利用上面所提到的prototype chain來實踐多層祖孫繼承的模式,我們用範例來解釋一下這點:
// 哺乳綱
function Mammals() {
    this.blood = "warm";
}

// 靈長目
function Primate() {
    this.tail = true;
    this.skin = "hairy";
}
Primate.prototype = new Mammals();

// 人科
function Homo() {
    this.skin = "smooth";
}
Homo.prototype = new Primate();

var human = new Homo();
human.name = "Kevin";

alert(human.name); // "Kevin", from self
alert(human.skin); // "smooth", from Homo
alert(human.tail); // "true", from Primate
alert(human.blood); // "warm", from Mammals
上例應該很清楚的顯示出以原型實踐繼承模式的原理了。


原型設計模式的漏洞

雖然原型設計模式能夠很方便的實踐物件間的共用屬性及繼承模式,但是在操作上要對prototype chain有一定的了解,再加上細心的邏輯驗證,才不會出現如下的錯誤:
function Human() {}
Human.prototype.blood = "red";
Human.prototype.body = ["foot","hand"];

var john = new Human();
var kevin = new Human();

john.blood = "purple";
john.body.push("wing");

alert(kevin.blood); // "red"
alert(john.blood); // "purple"

alert(kevin.body.toString()); // "foot, hand, wing"
alert(kevin.body.toString()); // "foot, hand, wing"
從上面的例子可以看到,john因為不明原因而突變以後,不只血變成紫色的,也長出翅膀來了!但是在john突變之後,kevin的血雖然沒有變色,但是卻莫名其妙長出了翅膀。很明顯的,我們不小心改動到了Human的prototype。
原來在我們為john的blood指定顏色時,javascript會為john這個物件增加一個屬於自己的"blood"屬性,這種情況就跟為物件增加屬性的方式一樣。於是在後來的呼叫時,會先找到john自己的blood屬性。但要john的body屬性執行push函式時,會發生在john中找不到body的狀況,於是就往上找到了Human.prototype的body屬性,並由他來執行push函式,此時改動到的便是Human.prototype.body了,也就連帶的影響到了無辜的kevin。

其他繼承模式設計

javascript是個很活的語言,除了prototype的實踐方式以外,我們也可以使用別的方式來實現繼承:
// 哺乳綱
function Mammals() {
    this.blood = "warm";
}

// 靈長目
function Primate() {
    Mammals.call(this); // 記得放前面,不然會蓋掉重複的屬性
    this.tail = true;
    this.skin = "hairy";
}
Primate.prototype = new Mammals();

// 人科
function Homo() {
    Primate.call(this); // 記得放前面,不然會蓋掉重複的屬性
    this.skin = "smooth";
}

var human = new Homo();
human.name = "Kevin";

alert(human.name); // "Kevin", from self
alert(human.skin); // "smooth", from Homo
alert(human.tail); // "true", from Primate
alert(human.blood); // "warm", from Mammals
這種方式是將父類別的建構函式放在子類別的建構函式中以「this」的身分來執行,為自己建置父類別的屬性。這樣的作法有個好處,就是不會因為不當的操作,改動到別的物件的屬性,但是相對的也失去了共用屬性的便利性。

這種方式也能讓我們很方便的實作多重繼承,只要在子類別的建構函數中呼叫多個父類別的建構函式即可。