丹哥的技術培養皿

A blogging framework for hackers.

Ruby (Rails) 內運算式 (Operator)的優先順序 " && " , " == " , " || " 符號

| Comments

本篇的概念來自於這篇stackover flow 的文章,筆記一下,否則每次看code都會卡住看不懂,不習慣背後的道理就都會很不直覺阿。 When do we use the “||=” operator in Rails ? What is its significance?

例如: 我們可能會看到這種程式碼,一開始的時候都是無法理解這代表什麼含意,是做了什麼判斷? 傳回什麼值? 誰跟誰比對? 優先順序又是什麼?

1
2
@_current_user ||= session[:current_user_id] &&
      User.find(session[:current_user_id])

我們要先講一個觀念是當看到 a ||= b 這種code的時候,要知道它其實是 a || a = b , 而他所代表的含意是如果 a 本身自己是 nil or false 的時候,則他就會等於 b 的值。 反之,如果 a 不是 nil , 不是 false的時候,那就保持原狀。 詳細的解說可以看這一篇文章 What Ruby’s ||= (Double Pipe / Or Equals) Really Does

讓我們來拆解這段程式碼,一步一步的解讀,

第一步

1
@_current_user ||= ( session[:current_user_id] && User.find(session[:current_user_id]) )

等同於

1
@_current_user || @_current_user = ( session[:current_user_id] && User.find(session[:current_user_id]) )

代表的是如果 @_current_user 是 nil or false 的時候, 就指定為||= 符號右邊的數值,但是右邊又是另一個判斷式,我們進一步解讀。

第二步

第二個觀念: 這一串 ( session[:current_user_id] && User.find(session[:current_user_id]) ) 是什麼意思?
這邊有一個小技巧,如果 session[:current_user_id] 不是 nil or false 那麼 ruby 就會去判斷 && 符號的右邊的內容 : 如果 User.find(session[:current_user_id]) 也不是 nil ,這樣的話就會傳回 User.find(session[:current_user_id]) 的值。

可以參考這一篇 : Ruby’s && operator for params 術語上似乎是叫做 short circuit evaluation.

第三個觀念: 優先順序的問題 我們可以從 ruby document 看到運算子的優先順序,我將這個表複製如下,規則是 越上面的越先運算、執行、判斷 ( PS: ruby version 2.1.2 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
!, ~, unary +
**
unary -
*, /, %
+, -
<<, >>
&
|, ^
>, >=, <, <=
<=>, ==, ===, !=, =~, !~
&&
||
.., ...
?, :
modifier-rescue
=, +=, -=, etc.
defined?
not
or, and
modifier-if, modifier-unless, modifier-while, modifier-until
{ } blocks

第三步

所以啦,回到原本的程式碼 當這一串 ( session[:current_user_id] && User.find(session[:current_user_id]) ) 先判斷完之後,就知道是 nil 或者是 && 右邊的值,接著再看 || 的左邊 @_current_user 如果不是 nil or false 就決定是保持原狀,否則就把 User.find(session[:current_user_id]) 指定給 @_current_user 。

持續更新 Sync (Follow) Fork下來的repo

| Comments

以下是更新的指令,前提是你的版本中沒有尚未commit的東西,不然會更複雜一些。 git remote add upstream “來源的git位置” git fetch upstream git checkout master git merge upstream/master 或 git rebase upstream/master git push origin (這一步一般是用在更新 github,當然如果有另外的 git server 也有用)

建議還可以參考鴉七寫得很詳細的 git rebase 解說 Git-rebase 小筆記

快速產生專案的template GEM

| Comments

From 這篇: 我的 Rails Template Feb 1st, 2014 | 1 Comment

Github 上有非常多的 Rails template,例如 Rails Composer, RailsBricks, Bootstrappers,這些都是很好的 Template,有他們自己很棒的 Practice,但是不管這些 Template 有多好,生成出來的東西,有些還是不符合自己的需求,還是需要砍掉部分不需要的,加入一些自己習慣用的。

有鑑於每次產生出來以後還要做很多事情,所以,最近用了 Rails Apps Composer 製作了自己的 Rails template,可以一行指令就產生我自己常用的 Gems, Settings, Views…等等:

1
rails new project_name -m https://raw.github.com/tonilin/rails_recipe/master/template.rb

( project_name 可以換成你想要的專案名稱。) 需求

Rails 4.0
Ruby 2.1.0
RVM
Mysql
使用 capistrano deploy
Server side 使用 Unicorn 當 Server

因為是自己的 Best Practice 所以就裡面只會問你資料庫帳號密碼,其他一律是用我習慣使用的,不會用一堆命令行問你資料庫想用哪種,要不要裝 Devise 之類的。如果不是你習慣用的可以 fork tonilin / rails-recipe 回去改,或是用比較有彈性的 Rails Composer 或 RailsBricks。

有興趣自己寫 template 的,改天會開一篇文章教大家如何用 Rails Apps Composer 產生自己習慣的 Rails template。 內容

使用 Rails Apps Composer 可以讓 recipe 看起來很乾淨,看 code 就可以知道包含了什麼,所以參考 https://github.com/tonilin/rails-recipe/blob/master/roachking.rb%EF%BC%8C%E5%A4%A7%E6%A6%82%E5%8F%AF%E4%BB%A5%E7%9F%A5%E9%81%93%E5%8C%85%E4%BA%86%E4%BB%80%E9%BA%BC%E6%9D%B1%E8%A5%BF%E9%80%B2%E5%8E%BB%E3%80%82

搜尋列內增加自動完成功能

| Comments

此篇文章的練習來自於這篇原作文章 Fast autocomplete search term - Rails APP

先大致翻譯一下這篇文章的安裝步驟如下

前言: 要實現在搜尋列內 (Search Form)內快速地跳出跳出猜測的字 (Fast Autocomplete),並且可以用不同分類的方式呈現,要使用到 NO SQL 資料庫: Redis、RAILS 裡面存取 Redis 的GEM: soulmate ,以及 CSS 和 jQuery 語法來呈現。 完成的範例可以看這個網站: seatgeek

1.要先安裝 Redis Server ,Redis 是什麼呢? 是一套開放原始碼的 NO SQL 資料庫 (一種 key-value 的資料庫,或稱為一種資料結構型態的資料庫) ,可以想成進階版的memcached,它提供了更多種資料結構的存放以及Operator 的控制; 主要的特色是 快!! 因為資料會 load 進記憶體後來與呼叫他的程式做IO的反應,所以放在這種要 search auto complete 的地方是相當有效果的。 MAC 我們可以使用homebrew ,用這個指令安裝 : brew install redis

裝好之後呢,就可以打下面的指令來啟動這套資料庫

$ redis-server

2.好了,我們可以開一個新的 rails 專案來練習一下這套工具怎麼使用。

$ rails new fast-autocomplete

3.在 Gemfile 裡面增加這些項目,然後 bundle install

1
2
3
gem 'faker', github: 'stympy/faker'
gem 'rack-contrib'
gem 'soulmate', :require => 'soulmate/server'

faker 這套 gem 是待會要用來產生假資料的,使用在 seed.rb 檔案內

4.接著使用 scaffold 快速建造出兩套骨架,用來demo用的

$ rails g scaffold noun name:string $ rails g scaffold verb name:string

5.產生後先執行 rake db:migrate

6.接著編輯生成假資料的檔案 seeds.rb

seeds.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# create 300 nouns
puts "Creating nouns"
300.times do
  Noun.create(name: Faker::Hacker.noun)
  Noun.create(name: Faker::Commerce.product_name)
end


# create 300 verbs
puts "Creating verbs"
300.times do
  Verb.create(name: Faker::Hacker.verb)
  Verb.create(name: Faker::Hacker.ingverb)
end

檔案編輯好後 就執行 rake db:seed 來生成假資料到DB內,這時候還不是把資料複製到到 redis 記憶體喔~ 是先在資料庫生成假資料。

7.接著我們將會編輯 Noun 和 Verb 這兩個類型的 model 檔案, 我們會使用 after_save 的 call back 方式來把存到資料庫的資料抄一份去 redis 記憶體上。 什麼時候存過去呢? 就是這段 load_into_soulmate method 以及在資料庫內的資料要被刪除之前, 會使用 before_destroy 的 callback 去把在 redis 記憶體上的資料先移除掉。 是另一段 remove_from_soulmate 的 method 的作用

程式碼如下

noun.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Noun < ActiveRecord::Base
  after_save :load_into_soulmate
  before_destroy :remove_from_soulmate

        validates_uniqueness_of :name

  private

  def load_into_soulmate
      loader = Soulmate::Loader.new("nouns")
      loader.add("term" => name, "id" => self.id, "data" => {
          "link" => Rails.application.routes.url_helpers.noun_path(self)
      })
  end

  def remove_from_soulmate
      loader = Soulmate::Loader.new("nouns")
      loader.remove("id" => self.id)
  end
end
verb.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Verb < ActiveRecord::Base
  after_save :load_into_soulmate
  before_destroy :remove_from_soulmate

        validates_uniqueness_of :name

  private

  def load_into_soulmate
      loader = Soulmate::Loader.new("verbs")
      loader.add("term" => name, "id" => self.id, "data" => {
          "link" => Rails.application.routes.url_helpers.verb_path(self)
      })
  end

  def remove_from_soulmate
      loader = Soulmate::Loader.new("verbs")
      loader.remove("id" => self.id)
  end
end

8.好了,現在我們要來實現把資料庫的資料 load 到記憶體去了,我們的假資料剛剛都已經生成了,那怎麼辦呢? 沒關係,讓我們到 rails console 去,然後分別執行這兩個指令,表示請rails 找出每一筆資料,然後再存檔一次,因此就會觸發 after_save 的 callback method 就自然而然會把資料抄一份到 redis 記憶體了。

$ rails console 2.1.0 :001 > Noun.find_each(&:save) 2.1.0 :001 > Verb.find_each(&:save)

我們可以再開另一個terminal 視窗,然後輸入

$ redis-cli

會叫出 redis 的 console端 我們可以試著打這個指令: hget soulmate-data:nouns 1 or hget soulmate-data:verbs 1 看看有沒有出現類似下面的資料,就表示資料有沒有成功的 load 到記憶體內了

127.0.0.1:6379> hget soulmate-data:nouns 1 “{\"term\”:\“alarm\”,\“id\”:1,\“data\”:{\“link\”:\“/nouns/1\”}} 127.0.0.1:6379> hget soulmate-data:verbs 1 “{\"term\”:\“calculate\”,\“id\”:1,\“data\”:{\“link\”:\“/verbs/1\”}}"


接著我們來處理讓 rails app 可以讀到 redis 資料的作法 首先,我們到 route.rb 上,告訴我們的 rails ,有一套 redis 的 client 端在待命著,

routes.rb
1
2
3
4
5
6
7
8
Rails.application.routes.draw do
  mount Soulmate::Server, :at => "/autocomplete"

  resources :verbs
  resources :nouns

  root 'home#index'
end

改好後重啟server, 網址打 localhost:3000/autocomplete 看看是否有出現類似這樣的畫面,就表示 soulmate 是否已成功地運行待命著

{ “soulmate”: “1.0.0”, “status”: “ok” }

接著,我們再來測試 redis 的資料是不是可以被 soulmate 這個client 套件讀出來,然後呈現到 rails app 網站上 我們可以打 http://localhost:3000/autocomplete/search?types[]=nouns&limit=6&term=pro 這串字,來測試當我們再搜尋列打上 pro 的時候, soulmate 會不會去撈出資料, 類似下面這樣 86e50be45a12784301ca852dae49ddb4.png

我們是藉由著網址裡面的 “type"去告訴 soulmate 我們要去找哪個 table (或是說存到 redis 上哪個類型的資料) 因此要切記,網址上面的 type 一定要是我們有的 table (或是有存到 redis 記憶體內的類型 )

或者我們也可以給定兩種類型,像是這樣 http://localhost:3000/autocomplete/search?types[]=verbs&types[]=nouns&limit=6&term=ha 這表示我們要找 noun 類型及 verb 類型內是否有 ha 開頭的字串 ,找到的話會類似下面這樣

2dcd49da89ef88b4876f05b57dc4fd8b.png


好了,終於要來處理前端的畫面了,這邊不用說,就是要套用 css 和 jQuery (Javascript) 的東西 soulmate 很棒的地方就是在於有人寫好了傳送到前端畫面的 js 檔案, 請到這邊抓取壓縮檔, soulmate.js @ github 是一個壓縮檔案, 請把目錄soulmate.js-master/src/compiled裡面的 jquery.soulmate.js檔案放到 assets/javascripts/ 下吧。 然後在 assets/javascripts/application.js 裡面掛上

routes.rb
1
2
3
4
5
//= require jquery
//= require jquery_ujs
//= require jquery.soulmate
//= require turbolinks
//= require_tree .

壓縮檔裡面的 soulmate.js-master/demo 目錄內有 demo.css的檔案 ,請放到 assets/stylesheets/ 裡面,然後改檔案名稱為 soulmate.css 然後在 assets/stylesheets/application.css 裡面掛上

routes.rb
1
2
3
4
*= require_tree .
*= require soulmate
*= require_self
*/

soulmate.css 檔案內容如下

soulmate.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#soulmate {
    background-color: #fafafa;
    border: 1px solid #bbb;
    display: none;
    font: 12px/14px "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-weight: normal;
    list-style: none;
    margin: 0 0 0 10px;
    padding: 0;
    position: absolute;
    text-shadow: 0 0 0 white;
    /* play around with the top and left properties for correct positioning */
    top: 201px;
    left: 460px;
    width: 579px;
    z-index: 1000;
    -webkit-border-radius: 4px;
    -moz-border-radius: 4px;
    -ms-border-radius: 4px;
    -o-border-radius: 4px;
    -khtml-border-radius: 4px;
    border-radius: 4px;
    -webkit-box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
    -moz-box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
    -khtml-box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
    -ms-box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
    -o-box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
 #soulmate .soulmate-type-container {
    border-top: 1px solid #ddd;
}
 #soulmate .soulmate-type-container:first-child {
    border-top: none;
}
 #soulmate .soulmate-type-suggestions {
    border-left: 1px solid #ddd;
    float: right;
    list-style: none;
    padding: 5px 0;
    width: 490px;
    letter-spacing: 0.5px;
}
 #soulmate .soulmate-suggestion {
    color: #111;
    cursor: pointer;
    font-weight: 500;
    font-size: 13px;
    padding: 5px 0 6px 12px;
    text-decoration: none;
}
 #soulmate .soulmate-suggestion.focus {
    color: white;
    margin-left: -1px;
    margin-right: -1px;
    padding-left: 13px;
    position: relative;
    text-shadow: 0 1px 1px #32629b;
    z-index: 1;
    -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
    -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
    -khtml-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
    -ms-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
    -o-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
    background: #545454;
    background: -moz-linear-gradient(top, #545454 0, #444444 100%);
    background: -webkit-gradient(linear, 0 0, 0 100%, from(#545454), to(#444444));
    -ms-filter: "progid: DXImageTransform.Microsoft.gradient(startColorstr=#545454,endColorstr=#444444)";
    filter: progid: DXImageTransform.Microsoft.gradient(startColorstr=#545454,endColorstr=#444444);
}
 #soulmate .soulmate-type {
    background-color: white;
    color: #333;
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: 11px;
    letter-spacing: 0.5px;
    margin-right: 490px;
    padding: 10px 10px 0 10px;
    text-align: right;
    text-transform: capitalize;
    vertical-align: top;
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
    -ms-border-radius: 5px;
    -o-border-radius: 5px;
    -khtml-border-radius: 5px;
    border-radius: 5px;
    zoom: 1;
}
 #soulmate .soulmate-type:before, #soulmate .soulmate-type:after {
    content: "";
    display: table;
}
 #soulmate .soulmate-type:after {
    clear: both;
}

最後,我們來裝飾查詢門面吧。 增加一個 controller

$ rails g controller home index

讓網站的 root 指向這個 home controller & index action

routes.rb
1
root `home#index`

接著設計一下 view 的畫面

index.html.erb
1
2
3
4
5
6
<div class="container">
  <h2>Search Nouns and Verbs</h2>
 <%= form_tag do %>
   <%= text_field_tag "search", nil, placeholder: "Search" %>
 <% end %>
</div>

再裝飾一下版面

home.css.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Place all the styles related to the home controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
.container {
  width: 500px;
  margin: 0 auto;
  padding: 100px;
  text-align: center;
}

#search{
  width: 400px;
  padding: 10px;
}

因此搜尋的首頁版面會長這樣: 3e832a9afea232cd44513215aea070e6.png

最後一個步驟,我們要讓首頁有一個 javascript (jQuery) 可以偵測我們在搜尋框打的字,然後接著就會去呼叫 soulmate.js的 javascript 進而開始對 soulmate & redis 做呼叫的動作及接到 server 吐回來的結果,進一步顯示到瀏覽器畫面上 請在 /assets/javascripts/ 裡面增加一個 home.js (或是把 home.js.coffee 更名為 home.js ) 因為我們要使用的 javascript code 是 pure js code 不是使用 coffee script 程式碼如下

home.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var ready = function(){
  var render, select;

  render = function(term, data, type) {
    return term;
  }

  select = function(term, data, type){
    // populate our search form with the autocomplete result
    $('#search').val(term);

    // hide our autocomplete results
    $('ul#soulmate').hide();

    // then redirect to the result's link 
    // remember we have the link in the 'data' metadata
    return window.location.href = data.link
  }

  $('#search').soulmate({
    url: '/autocomplete/search',
    types: ['nouns','verbs'],
    renderCallback : render,
    selectCallback : select,
    minQueryLength : 2,
    maxResults     : 5
  })


}
// when our document is ready, call our ready function
$(document).ready(ready);

// if using turbolinks, listen to the page:load event and fire our ready function
$(document).on('page:load', ready);

噹噹!! 大功告成了!!! 如果一切順利的話,當我們在搜尋列打上兩個英文字母,且有match 到資料庫的資料 ( 嚴格說應該是redis 吐回來給 soulmate 再吐給前端 ) ,那麼就會看到類似下面的畫面了。

3b21e629c8bf9af09cf67f28f79f5b42.png

以上,收工。

改天再來寫一篇,如何把這樣的技巧 match 到我們自己的網站。 我想,秘訣應該是在 model 的 code ,就是要怎麼寫 ruby code 把我們想要作為快速 key-value 比對的資料結構,從資料庫存檔的時候抄寫一份過去呢? 以及要動到 javascript 的東西吧 (頭痛) 好好研究一下。

快速把表格增加依照欄位排序、分頁功能

| Comments

之前無意間看到有人推薦了 jquery-datatables-rails 這個GEM 是用來快速把一個列表的資料依照欄位排序,或是增加分頁的功能,使用了Jquery的作法 少量的資料可以在 client端排,大量的資料的話應該會造成使用者執行的效能問題 進階作法會是在server side 排序之後才吐回去給前端顯示出來。

說明文件在這裡 jquery-datatables-rails 同時還參考了 Rails Cast 的解說 RailsCast #340 DataTables Apr 11, 2012

做法真的相當的簡單 1 Gemfile 裡面增加 'jquery-datatables-rails', '~> 2.1.10.0.3' 2 安裝這個Gem,執行 bundle install 3 在 assets/javascript/application.js 裡面增加: //= require dataTables/jquery.dataTables 要注意的是,如果原本的 application.js 裡面有 //= require_tree . 這個包進全部js的內容, 請把上面那行放在 require_tree 前面. 像是這樣 /app/assets/javascripts/application.js

1
2
3
4
//= require jquery
//= require jquery_ujs
//= require dataTables/jquery.dataTables
//= require_tree .

4 在 assets/stylesheets/application.css 裡面增加 *= require dataTables/jquery.dataTables 如果原本的 application.css 裡面有或 *= require_tree . 這個包進全部 css 的內容, 請把上面那行放在 require_tree 前面. 像是這樣: /app/assets/stylesheets/application.css

1
2
3
4
5
6
7
8
/*
 * This is a manifest file that'll automatically include all the stylesheets available in this directory
 * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
 * the top of the compiled file, but it's generally better to create a new file per style scope.
 *= require_self
 *= require dataTables/jquery.dataTables
 *= require_tree .
*/

接著在希望可以產生變化的table 所屬的 js 檔案增加: 例如,我希望產生變化的view檔案是在 /app/views/patents/index.html.erb
所以我就應該去 assets/javascripsts/ 裡面找 patents.js 這個檔案,然後在裡面加上

1
2
3
jQuery ->
        $('#patents').dataTable
          sPaginationType: "full_numbers"

第二行是指套上 Jquery 的功能,第三行是指再多增加分頁的功能

接著關鍵的地方就是要到view folder 的 index.html.erb 去設定 table 的一些屬性,讓 Jquery 可以找到這個table 原本的table那段的code 是這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<h2>查詢記錄 </h2>
<table class="table table-bpatented" >

    <tr>
      <th>No. </th>
      <th>申請號</th>
      <th>生成時間</th>
    </tr>

    <% @patents.each_with_index do |patent, i| %>
    <tr>
       <td><%= i+1  %></td>
       <td><%= link_to(patent.apply_no, patent) %> </td>
       <td><%= patent.created_at %> </td>
    </tr>
    <% end %>
</table>

有幾個項目要增加上去: 有 table id , thead , tbody 因此加上去後會變成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<h2>查詢記錄 </h2>

<table class="table table-bpatented" id="patents">
  <thead>
    <tr>
      <th>No. </th>
      <th>申請號</th>
      <th>生成時間</th>
    </tr>
  </thead>
  <tbody>
    <% @patents.each_with_index do |patent, i| %>
    <tr>
       <td><%= i+1  %></td>
       <td><%= link_to(patent.apply_no, patent) %> </td>
       <td><%= patent.created_at %> </td>
    </tr>
    <% end %>

  </tbody>
</table>

要注意到 table 的 id 和 .js 檔案裡面的字串是要一樣的,這樣 jQuery 才會對應到是哪個table 要有變化. 如此一來就快速的套進了按照欄位排序,以及有基本的分頁功能了。

網址要使用人類可以辨識帶有意義的字句

| Comments

這個需求應該會是一個普遍上一定會遇到的問題,就是網址不再使用rails因為 RESTful 的設計,不要都是直接抓出各種table的主id鍵值作為KEY值。

查了一些資料之後,發現用最多的應該就是這個gem了 “friendly_ID “ 將很多 CASE 都有考量進來。

當然另外也有土法煉鋼的方式,就是自己create slug 欄位,然後再存檔的時候,先處理字串,再寫到table的欄位去

先memo第一種做法:friendly_ID 的簡便做法 從官方網站上面節錄下來: FriendlyId is the “Swiss Army bulldozer” of slugging and permalink plugins for ActiveRecord

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Gemfile
gem 'friendly_id', '~> 5.0.0' # Note: You MUST use 5.0.0 or greater for Rails 4.0+

>rails generate friendly_id
>rake db:migrate

# edit app/models/user.rb
class User < ActiveRecord::Base
  extend FriendlyId
  friendly_id :name, use: :slugged
end

# Change User.find to User.friendly.find in your controller
User.friendly.find(params[:id])

# If you're adding FriendlyId to an existing app and need
# to generate slugs for existing users, do this from the
# console, runner, or add a Rake task:
User.find_each(&:save)

這邊有幾個重點: 1. model 裡面把你要置換的網址的欄位寫上去,此範例為使用 User table 的 name 欄位,要用名稱作為網址,而不要使用 xxx/xxx/user/:id 這種網址了。 2. controller 裡面一定要換成 User.friendly.find(params[:id] 這樣 rails 才會認得 xxx/user/name/edit or xxx/user/name/show 這種網址
3. 當我們要使用這樣的做法去處理每一筆新 create 的資料, FriendlyID都會自動幫我們增加到 slug 的欄位內 但是舊的怎麼辦呢? 所以就是到 console 裡面使用 User.find_each(&:save) 這樣的指令去對資料庫的資料重新做一次save, 因為 FriendlyID 會在 before_save 做 validation ,因此發現slug 沒有做,就會重新做一次。 (印象中是這種概念,有錯誤請指正我,謝謝)

第二個方法可以參考 TIM 的筆記,但我也還在摸索當中,暫時還看不太懂。 Sluggify Post Better Slug Post 進階:把很多地方要使用Sluggable Title 封裝為 Sluggable Module

另外 stackoverflow 上面也有一篇很有意義的文章 best way to generate slugs-human readable title in rails

補充另一個網友寫的筆記 Generate Slug

表格內要帶出序號的做法

| Comments

需求是這樣的,我希望我的表格的第一個欄位是序號(序列號)從 1 開始一直到整個 object 的迴圈讀完.

簡單查了一下,範例的 source code 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<h2>查詢記錄</h2>

<table class="table table-bpatented">
  <thead>
    <tr>
      <th>No.</th>
      <th>申請號</th>
      <th>生成時間</th>
    </tr>
  </thead>
  <tbody>
    <% @patents.each_with_index do |patent, i| %>
    <tr>
       <td><%= i+1  %></td>
       <td><%= link_to(patent.apply_no, patent) %> </td>
       <td>11.11.2014 </td>
    </tr>
    <% end %>

  </tbody>
</table>

最最關鍵的地方就是在這個 .each_with_index 這個 method , 搭配上 iterator 的 i 這樣表格第一欄我們再使用 i+1 這樣,就會從 “1 "開始了. 如果沒有加1的話,會從 0 開始顯示.

index 的iterate 變數名稱 一定要放在 | , | 之間的第二個參數位置,例如本例是 i

Rails 命名慣例

| Comments

此文章是從這個網站來的 :Rails中的约定与命名规范 直接貼過來作為常用的參考 Rails中的约定与命名规范

博客分类: Ruby On Rails Rails

约定优于配置是Rails三大哲学之一。Rails中充满了很多约定,本页面对遇到的约定做一个总结。

单复数的约定 Model用单数因为它表示一个对象如User, 数据库表用复数因为它存放的是对象的集合, Controller用复数因为它是对对象集合的操作

Routes.rb中定义session一般用resource :session,而不是普通的resources :sessions。因为一般只会操作当前用户的session,不会操作所有session,所以不能定义为复数。 即如果一个请求一个资源时不需要指定ID,就在routes中用单数,如/profile显示当前登录用户的信息,这样你可以使用单数的/profile而不是/profile/:id。 也可以用match “profile” => “users#show”

其它 Controller中可以用变量request,然后可以得到session, request_info, head, method等请求信息 .与#使用惯例:在阅读书时经常会遇到User.all, users#show这样的表示,其中的点.与井号#使用也是有约定的,点.用于调用类方法,井号#用于调用实例方法。

最重要的是要一致, 不要一会儿单数, 一会儿复数; 一会儿首字母小写, 一会儿首字母大写. 如果不能做到完全一致, 至少努力做大大部分情况下一致.

变量名的单复数和大小写问题

考虑下面三条命令: rails generate model user name:string rails generate controller user –no-test-framework rails generate integration_test user 其中比较容我困惑的是user这个单词, 出现在不同的场合可能有下面四种情况: user users User Users 上面的情况在railstutorial这本教程里面出现了至少3种, 在阅读的时候给我造成了很大的困惑. 我想, 到底什么才是最为推荐的? 如果不按照教程上面的例子做会不会有什么问题?

实验的结论: 首字母大小写是无关紧要的, 最终产生的结果是一致的. model一般都用单数, 复数的model会有歧义, 比如当你创建model的一个实例的时候, 会觉得你在创建多个实例 其它很多地方用复数形式, 比如数据表的名字, 比如routes中的url形式以及url的名称等等. 既然很多地方都用复数, 那么不如统一用复数形式. 另外经过检验, 统一用单数的方式有点行不通, 到restful routes那块跟Rails现有的设计有冲突. 那么索性就统一用复数形式好了.

最终的解决方案: 大小写问题已经没有争议了, 都用小写即可 单复数问题: model名字用单数, 其它场合都用复数

文件名的单复数问题 经过观察, rails所有的常见的文件名都是小写的. 所以大小写的问题没有争议, 都用小写即可. 但是单复数问题不太统一, 比如users_contoller.rb, model/user_spec.rb. 这边总结的规律是: 跟model相关的东西, 单数居多. 比如model文件, model_spec文件等等 跟controller相关的东西, 复数居多. 比如 users_contoller文件, request/users_spec文件等等. 跟view相关的东西也是复数居多. 因为view的文件的创建是在创建contoller的时候自动创建的, 所以它跟contoller基本上是一致的.比如 javascripts/users.js.coffee 其它场合应该是单复数都可以. 来自:http://www.tylerlong.me/1334585824/ http://rubyer.me/blog/293/

Week4 Homework Note

| Comments

這是 xdite 的 Ruby on Rails intermidate class 的第四堂課 主要是在講解一些進階的技巧,例如 rake 這個工具, 前端後端加速優化的技巧, 利用 capistrano deploy 自己的網站, 還有講解求職的一些 Tips.

剛剛先寫了一個簡單的 rake file 的作業

利用 Rake 建立 10 個User 假帳號

簡單的嘗試了一下,程式碼如下, 請從第三個 desc “build account” 開始看。

desc 用來描述這個 rake 工作是做什麼的 task 用一個 symbol 來命名這個 rake task , 搭配範例中第一行的 namepace 為 :dev , 因此在 shell 端就是直接打 rake dev:build_acc 這樣的指令 就會去執行我要他做的動作。 經過這樣測試之後,看起來是可以直接把原本在 rails console 要做的指令,搬過來,然後加上迴圈或判斷式加工,就可以做到很多我們想做的動作了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace :dev do

  desc "Rebuild system"
  task :build => [ "tmp:clear", "log:clear", "db:drop", "db:create", "db:migrate", "db:seed" ]

  desc "demo"
  task :demo => :environment do
    for i in 1..10 do
      puts i
    end
  end

  desc "build_account"
  task :build_acc => :environment do
    for n in 1..10 do
      User.create!(:email => "abc#{n}@xxx.com", :password => "12345678", :password_confirmation => "12345678")
    end
  end

end

Simple_form Nested Attribute 巢狀表格 Rails 要注意的點

| Comments

會有這一篇解bug的紀錄是因為發現自己在 edit product 的page 裡面,使用了 simple_form_for 的巢狀表格時,增加引入了 simple_fields_for 這個東西, 結果出現多個上傳照片的按鈕,這很明顯一定是一個bug.

後來查到這一篇在 stackoverflow 上的解法,才知道,應該是第二層的 instant variable 沒有被載入,所以就會create出新的。 正確的code應該這樣寫:

剛開始的時候使用 simple_form_for [:admin, @product ] 這樣的句法去載入在 namespce admin 下的 @product 變數。 在第二層的時候使用 simple_fields_for @product, :photos do |c| 這樣的句法去載入 @product 這個變數, 本來沒寫的時候, 你會發現每次網頁送出一次後,下次再進來就會出現兩列要上傳圖片的列, 看起來就不正確,不論重新上傳多少張,都應該要只有一列而已。 原因就是出現在第二層的表格,他不知道已經有產生過了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<h2> 新增產品 </h2>

<%= simple_form_for [:admin, @product ] , :html => { :class => "form form-horizontal"  } do |f| %>

  <div class="form-group">
      <div class="col-sm-10">
      <%= f.input :title %>
    </div>
  </div>

  <div class="form-group">

    <div class="col-sm-10">
      <%= f.input :description, :as => :text %>
    </div>
  </div>



  <div class="form-group">

    <div class="col-sm-1">
      <%= f.input :quantity %>
    </div>
  </div>


  <div class="form-group">
      <div class="col-sm-1">
      <%= f.input :price %>
    </div>
  </div>

    <%= f.simple_fields_for :photos do |c| %>
      <%= c.input :image , :as => :file %>
    <% end %>



  <div class="form-group">
    <%= f.submit "Submit", :class => "btn btn-default" , :disable_with => 'Submiting...' %>
  </div>




<% end %>