馬克的程式架構筆記

為了創造有價值的產品,想法、實作架構、行銷有同等的重要性

Rails之ajax心得紀錄

備註:這篇文章是根據實驗結果之後的心得紀錄,僅供參考。如有任何問題歡迎討論。

事前工作:

  • 1.加respond_to到你要call的action

  • 2.在html.erb設定觸發連結或在js內寫request

  • 3.寫好你要render的 partial erb(ex: _event.html.erb)

  • 4.在js.erb加入類似 $(‘#content’).html(“<%= escape_javascript(render :partial => ‘event’) %>”) 的code.

其實有一點複雜,建議自已嘗試一下。

試了一下rails的ajax刷新畫面做法,大致分為以下三種:

1.傳json資料回js,callback回success,再組畫面。

index.js
1
2
3
4
5
6
7
    $.ajax({
      url: '/posts.json',
      dataType:'json',
    }).success(function(data) {
      var json = JSON.stringify(data);
      $('#content').html(json);
    })

是最基本(也是最麻煩的一招),必須要在js裡面自已寫HTML tag。

2.在link內設定remote true,這時rails會在你點擊按鈕的同時去將js.erb的script load進來,你可以在這裡載入你想要的partial。

index.html.erb
1
<%= link_to('remote page','/posts',:class => "btn btn-small btn-info",:remote => true, "data-type" => "script") %>
index.js.erb
1
2
$('#ajax_content').html("<%= j(render :partial => 'index') %>");
//要render哪個partial是可以改變的哦

單純的情況下這樣很省事

3.自已寫ajax request,你可以在任何時機去處理你要做的動作,同時還是可以load你想要的partial。

index.js
1
2
3
4
    $.ajax({
      url: '/posts',
      dataType:'script'
    })
index.js.erb
1
2
$('#ajax_content').html("<%= j(render :partial => 'index') %>");
//要render哪個partial是可以改變的哦

自定性高。

額外註記一下,有些地方的教學會要你加以下這段code到action裡

1
2
3
4
    respond_to do |format|
      format.html
      format.js
    end

經過測試,其實在我目前的環境(rails4的原因嗎?)下是可以不用加的,但假設你另外有返回json格式的情況,那就一定要寫成

1
2
3
4
5
    respond_to do |format|
      format.html
      format.js
      format.json { render :json => @posts }
    end

少寫的部分就會不能作用,要注意。

Rails Service層的使用時機

在Rails 102的作業中,有一段code自已覺得很醜,請教了一下xdite大:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  def create_comment
    @post = Post.find(params[:id])
    comment = @post.comments.create(comment_params)
    comment.author = current_user

    if comment.save
      #取得所有人的email 去掉重覆的和自已的
      emails = @post.comments.map{ |x| x.author.email}
      emails_add_posts = emails + [@post.author.email]
      uniq_emails = emails_add_posts.uniq{|x| x}
      uniq_emails_delete_self = uniq_emails - [comment.author.email]

      PostMailer.sendmessage(uniq_emails_delete_self,@post, comment.comment).deliver
      redirect_to post_path(@post)
    else
      render :show
    end
  end

這個action主要的用途是用於在留言,而留言完成後需要寄信通知其他不重覆的回應者(不包括自已)

1
2
3
4
5
      #取得所有人的email 去掉重覆的和自已的
      emails = @post.comments.map{ |x| x.author.email}
      emails_add_posts = emails + [@post.author.email]
      uniq_emails = emails_add_posts.uniq{|x| x}
      uniq_emails_delete_self = uniq_emails - [comment.author.email]

這段code照了我想像的邏輯處理,但它不適合寫在controller裡面,Controller原本就只需處理由model取得資料—將資料展現在View這樣的動作,其餘額外的動作應該另外開一層Service。

於是就來把services目錄做出來

1
2
cd yourproject/app
mkdir services

rails已經幫你預先載入這個路徑下的檔案了,所以裡面新增的程式都可以在controller下呼叫

1
vi yourproject/app/services/post_mailer_service.rb

加入以下內容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class PostMailerService
  def initialize
  end

  #取得應收到訊息的email list
  #去掉重覆的和自已的
  def send_email_to_other_people(post,comment)
      emails = post.comments.map{ |x| x.author.email}
      emails_add_posts = emails + [post.author.email]
      uniq_emails = emails_add_posts.uniq{|x| x}
      uniq_emails_delete_self = uniq_emails - [comment.author.email]

      PostMailer.sendmessage(uniq_emails_delete_self,post, comment.comment).deliver
  end
end

原本的程式改為

1
2
3
4
5
6
7
8
9
10
...
    if comment.save

      PostMailerService.new().send_email_to_other_people(@post,comment)

      redirect_to post_path(@post)
    else
      render :show
    end
...

這樣雖然還不能說很漂亮,但已經達到我們要把不相關的邏輯由controller拆分出來的目的了。

參考文件 http://yedingding.com/2013/03/04/steps-to-refactor-controller-and-models-in-rails-projects.html

Gem Acts_as_commentable

針對rails102作業裡的acts_as_commentable給一些說明。

當然也可以自已建一個對應Posts的Comments Table。 但主要有的時候你的評論不一定只用在一篇文章,也有可能針對像圖片,連結等等的物件,那就可以考慮以這個Gem做處理。

主要安裝過程(for rails4)

在Gemfile加入

1
gem 'acts_as_commentable'

generator對應的程式碼

1
rails g comment

在你需要加評論的對象moel上加acts_as_commentable

1
2
3
class Post < ActiveRecord::Base
  acts_as_commentable
end

自已寫對應的method和route來處理新增評論的情況,有幾個點要注意,以下是範例

1.在你的user.rb下加入

1
has_many :comments

2.在你的posts_controller.rb會加入類似如下的code

1
2
3
4
5
6
7
8
9
10
11
def create_comment
  @post = Post.find(params[:id])
  comment = @post.comments.create(comment_params)
  comment.author = current_user
  comment.save
  redirect_to post_path(@post)
end

def comment_params
  params.require(:comment).permit(:comment)
end

3.在routes.rb加入

1
  post "/posts/:id/comments/create" => "posts#create_comment"

4.posts/show.html.erb加入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  <% @post.comments.each do |comment| %>
    <table>
      <tr><%= comment.author.email %> 表示:</tr>
      <td>
        <%= comment.comment %>
      </td>
    </table>
  <% end %>
  <div>
    <%= form_for(@comment,:url => post_path(@post)+'/comments/create') do |f|  %>
    <%= f.text_area :comment %>
    <%= f.submit "留言" %>
    <% end %>
  </div>

筆記供參考,但最好了解一下為什麼要加入這些code的理由和它做了些什麼事,幫助會更大。

參考文章主要是http://juixe.com/techknow/index.php/2006/06/18/acts-as-commentable-plugin/

關於Goliath的洞

最近持續的研究Goliath

原本想要做的事情: Java (Spring Framework)—ActiveMQ(JMS)—WebSocket(Goliath)

白話文: 將從java過來的訊息透過java message service以goliath server接收,最後以WebSocket協定送至畫面上。

選擇MQ時考慮過的其他做法

AMQP(Advanced Message Queuing Protocol)

要不是不想動MQServer(需要換成RabbitMQ),這個做法其實比較標準

STOMP

很簡單的架構,但不太夠用。可是jms就非得要用jruby做,就去找了jruby-jms這個套件來處理

結果!結果!Goliath硬是和jruby不合,跑起來就是不對勁

一下子頁面出不來

一下子Message接不到

一下子WebSocket莫名其妙斷掉

看來還是不要逆天行事,以後大家還是乖乖用AMQP做事好了,研究完WebSocket的部分,再來好好介紹AMQP。

Ruby的block和Closure

今天介紹一下Ruby的block和Closure 參考了各方意見,決定為它下一個定義(因為真的很容易搞混),而我也只針對Ruby的部分做解釋,也許在其他語言上的定義也不一定一樣。

block(區塊)

你只要看到

1
xxx {}

1
2
3
xxx do
 
end

這就是區塊,它就是一塊會被xxx呼叫的一段程式碼而已。你可以當作它是前一個方法所需的參數,但它就只是一段程式碼。

然而

closure(閉包)

是一個物件,這個物件是一個可被呼叫的函式,包含了兩個概念,這兩個概念都屬於closure

proc

procedure,一段被物件化的程式碼

它是程式碼, 但和block不同,它被物件化,而使得我們可以呼叫它。

lambda

函式,一段被物件化的method

它是方法,絕大多數的情況下它運作的方式和proc都一樣,不同之處在於它return 自已的結果,而proc會return呼叫它的人的結果。

看範例:

Compare Proc with Lambda
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def foo
  f = Proc.new { return "return from foo from inside proc" }
  f.call # control leaves foo here
  return "return from foo"
end

def bar
  f = lambda { return "return from lambda" }
  f.call # control does not leave bar here
  return "return from bar"
end

puts foo # prints "return from foo from inside proc" 
puts bar # prints "return from bar"

了解為什麼嗎? 你可以當作當你在你原本的method呼叫proc作用時,你的code就被直接塞了一段程式碼進去,於是當它寫到return時,後面的code就不會執行了,但lambda是一個函式,當你在你原本的method呼叫它時,它只是呼叫另一個函式。當lambda執行到return時,它傳回它自已執行的結果。

1
2
3
4
5
6
7
b = lambda do |x|
  puts x
end

1.upto(10) { |x| b.call(x) }
1.upto(10, &b)
1.upto(10) { |x| puts x }

以上三個執行結果相同,都會把1~10印出來。 而

1
2
3
4
5
6
7
8
9
10
11
12
a = Proc.new do |x|
  puts x
end
a.call('hello','world')  # hello
b = proc do |x|
  puts x
end
b.call('hello','world')  # hello
c = lambda do |x|
  puts x
end
c.call('hello','world')  # error

目前在Ruby 2.0版輸出的結果,a和b都可接受不定長度的參數,而c會error

這和 http://www.cnblogs.com/puresoul/archive/2011/11/02/2232809.html 上面的結果不同,需要特別注意

一般來說如果真的要使用的話,採用lamdba會比較沒有程式意外的狀況。

定義清楚這些名詞的差別,以後就不會看得霧煞煞啦。

Easygo

Goliath是以Ruby開發的non-blocking (asynchronous) Server,相對於最近熱門到不行的node.js來說,也是一個強調高效能且即時的Server。

目前個人做了一個小玩具,叫EasyGo(liath),就是幫goliath(巨人)接接原本它沒有的翅膀呀,手呀腳呀(Goliath給出來的東西都是小東西,發育不良),自已做起東西就方便得多。

至於為什麼不想用node.js來做呢?

  • 對javascipt有恐懼感 = = 我沒有jquery就不會做事了
  • 一堆callback看到我眼睛都抽筋了
  • 比較想用ruby寫東西(個人私心)

以上只代表一件事 I love ruby > javascript

回主題,重點是Goliath和其他元件如何搭配出我在Java做了三年的工具,所以原本其實只要搞定連接mqserver,使用Websocket和在不支援前者時改用long-polling的問題就可以處理掉通知功能,但還是做了不少其他的部分,有些東西,像測試,以前用java不會花時間寫,但在重構的過程中卻是很重要的部分,所以也算是一種進步吧。

目前用到的模組

Grape

用以處理路由的部分,以前我寫spring MVC的人,路由不喜歡另外搞一個檔案出來維護,route檔案肥了你也看不懂,還不如照著Rest-like的規則把Controller寫好不是很方便嗎?

Postgresql

資料庫嘛…總是要挑一種來存資料的,我們用postgresql習慣了,所以就暫時接postgresql

Rspec、Guard、simplecov、pry

Rspec和Guard 非常重要,沒有它保證程式運作正常,心裡都會毛毛的

simplecov 以後程式慢慢變肥的話,測試覆蓋率是一個指標,想重構之前先看看自已寫測試了沒。

pry 就是設中斷點給你用啦!不然除錯會除到哭出來哦!

接下來要做的部分

WebSocket

其實這才是重點= =,如何能夠讓頁面元素透過Server-Client即時的交換資料來出現神奇的功能就靠它啦!

與MQ的串接

原本我們在公司是用ActiveMQ接tomcat來做訊息的傳遞,看到了Stomp這個Apache推出的support,應該不會是難事了吧。

後面還有不少東西要作,一步一步來吧

Octopress & Markdown

一直有寫文件的需求,苦於對blog或wordpress這類的東西不太上手,到現在才看到Octopress,覺得相當適合,打算拿它來寫點技術性的文章。

也順便來推廣一下 markdown 好了,最近對markdown的語法愛不釋手,覺得用來寫技術文件蠻適合的,基本上寫東西的需求很少,能夠讓字該大的時候大,該小的時候小,沒事加個圖片、超連結或code block這樣就夠了。

Mou是Mac上的markdown editor 很不錯用(有markdown前和後的對照就很方便了)

1
2
3
4
5
6
7
8
9
[馬克的xxx筆記](http://motephyr.github.io)

![ruby icon](http://upload.wikimedia.org/wikipedia/commons/thumb/f/f1/Ruby_logo.png/105px-Ruby_logo.png)
# This is H1 
## This is H2
### This is H3
#### This is H4
##### This is H5
###### This is H6

出現的效果就像下面這樣

馬克的ooxx筆記

ruby icon

This is H1

This is H2

This is H3

This is H4

This is H5
This is H6

之後有空再來介紹一下我在github放的專案,Readme.md只能寫個大概,在這邊應該可以寫的比較詳細吧?