丹哥的技術培養皿

A blogging framework for hackers.

快速產生專案的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 %>




Week2 Homework Note

| Comments

上課的時候有講到一個技巧,如果 controller 的 method 要給 view 用的時候 可以使用 helper_method 這個技巧

在 controllers/application_controller.rb 可以放入這段 CODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 helper_method :current_cart

  def current_cart
    @current_cart ||= find_cart
  end

  def find_cart

    cart = Cart.find_by(id: session[:cart_id])

    unless  cart.present?
      cart = Cart.create
    end

    session[:cart_id] = cart.id
    cart
  end

其中一開始看不懂的是這個符號 “ ||= ” 這什麼鬼阿! 可以看這篇的解釋, 解釋 ||= 這個符號的意義

因為現在是 many to many 的關聯性 , 剛剛採了一個雷 就是在找購物車的商品的時候,我傳遞了選擇的 product id 去 carts 的 controller, 把 product.id 帶過去 然後因為我要在 cart_items 這個 table 去找出商品 所以一開始這樣寫

def remove_item

@item = Cart_Item.find_by(product_id: params[:id]) if @item.destroy flash[:notice] = “你已成功移除這項商品” else flash[:warning] = “Something Wrong when remove item!” end redirect_to carts_path end

(sorry 為了凸顯那個我錯的地方 ,所以用引用的方式show 程式碼 , 結果無法縮排 ) 雖然 table name 是 cart_items , 但是在上面的時候正確應該是要打 @item = CartItem.find_by(product_id: params[:id]) 這應該是命名慣例阿~ 踩過一次就記得了吧

問題好多 1. 信用卡付款之後跳轉訂單畫面,但是應該要清空購物車 ,想法是可以先去走 cart/destory 的controller , 但是裡面會跳出 你已成功清除購物車 , 理論上結帳過來的動作不應該跳出這段字才對 2. 不同USER的訂單編號應該都要從 1 開始…. 而不是直接顯示 order table 裡面的編號 , 那是全部系統訂單的編號 3. 4. 如果已經變更了 aasm 裡面的狀態,結果要變更第二次的時候就噴error了,所以應該要有 error handling 的機制

Week1 Homework Note

| Comments

今天在把原本在 github 上的 branch 要 clone 下來到 nitrous.io 網站的時候發生了一些問題 先講到底要怎麼去拉自己在遠端的repo branch . 假設這個在 github 上的repo 叫做 artstore , 有 Master 的主 branch 以及後來新增加的 add_uploader 這個第二個brnach 那麼,我在 nitrous.io網站的時候,因為從來沒有同步過這個 repo 會先下這個指令 git clone https://github.com/dangjlin/artstore.git 這樣就會先把 github 上的這個 master branch 同步回 nitrous.io

第二步 因為想要去拉 add_uploader 這個 branch 所以會下這個指令 git checkout --track -b add_uploader origin/add_uploader 其中第一個 add_uploader 是指 nitrous.io 這一端的 branch 的名稱 , origin 後面的add_uploader 指的是遠端 github上 這個repo 裡面的 branch 的名稱

之後要push 的時候就一樣可以下 git push origin add_uploader

接著當然是要先執行一次 bundle install 重新安裝所需要的 gem file, 然後再執行 rake db:migrate create 出應該有的 table,

[補充說明] 因為後來有從 nitrous.io 的網站 push 新的變更到 github 上面 結果我回家使用自己的 macbook air 的時候,勢必是一定要再把變動拉回自己的筆電上 所以要下這個指令 git pull origin add_uploader

第一個 origin 是要跟 git 說請到遠端去 ( 前提是 remote name 如果沒有變更過的話就會是 origin ) 第二個 add_uploader 是本地端的 branch name , 不用擔心, 因為前一次筆電端 push 的時候就已經知道 origin 端也有這個 branch name 了, 所以 git 會去遠端把 add_uploader 這個 branch 的資料 拉回來本機端 然後 merge 本機端的 branch

[補充說明二] 如何追蹤 fork 的 repo 又有新增的 branch , 要新增到自己的 local site, 可以參考這篇文章寫得很淺顯易懂 http://fireqqtw.logdown.com/posts/200871-github-sync-fork-project === ===

[補充說明三] 這是個不錯的簡表 How to Git: a Brief Guide to Making Open-Source Contributions ft. Bridge Troll

接著就到了一個卡關的地方, 因為這次的專案在使用者認證系統使用了 devise 這個 gem , 所以所有的使用者註冊、權限、驗證、密碼相關問題都由這個 gem去處理。 因此之前進入 rails console 去 create USER帳號的時候,無論怎麼做都會錯誤, 一開始我用這個指令 :

1
2
3
u = User.new 
u.update_attribute(:email => "xxx@xxx.com" , :encrypted_password => "abcde" ) 
u.save 

不用說,這就報 error 了, 因為 1) update_attribute 只能更新一個欄位的資料 , 2) 更新一個欄位的格式要這樣寫 : u.update_attribute(:email, "xxx@xxx.com) 所以後來我查了一下我又換了一個方式,

1
2
u = User.new(:email => "xxx@xxx.com", :encrypted_password => "abcde" ) 
u.save 

用這個指令也還是報 error,百思不得其解,後來google了才知道因為使用者在進入頁面的時候, rails 會用 devise去處理帳號密碼, password 欄位會用 bcrypt 的加密去 hash 過,然後再去跟資料庫的 User table 的 encrypted_password 欄位對比看是否一致。 所以我在 rails console 裡面直接把 encrypted_password 塞 “abcde ” 這樣就被吐error回來了,因為與 bcrypt的 hash 結果格式不相符 [ 這邊是我猜為什麼無法這樣新增的原因 ]

結果回去看了教材才知道要用這個指令才是正確的

1
2
u = User.new(:email => "xxx@xxx.com", password => "abcde" , password_confirmation => "abcde" ) 
u.save 

這樣一來就會成功的創造一筆資料了, 而後來用 u.encrypted_password 去看,就會看到 abcde 已經被 hash 過了…. 至於為什麼會這樣呢? 我還在找資料,我猜想一定是 device 個 rails gem 的作用,意思是 create user 的時候應該經過 password & password_confirmation 這兩個變數的資料,然後 device 會自動拿 bcrypt 的函示庫去做密碼加密的動作,然後再塞到User Table 的密碼欄位當中。

基本上一開始的外觀應該就可以直接使用twitter bootstrap 這個超棒的前端 framework 來美化我們的版面

新手一開始的動作很簡單,有三個步驟

  1. 在Gemfile.rb 中添加 bootstrap Gem gem 'bootstrap-sass'

  2. 在 app/assets/stylesheets 的 application.css 要加上這一行

1
*= require bootstrap 
  1. 但是因為我發現為什麼下拉式選單竟然跳不下來,原因是因為那是javascript 的效果 所以還要記得在 app/assets/javascrtip 的 application.js 要加上這一行
1
//= require bootstrap

這樣之後才可以開始放肆的改各式樣的版面調整了~