capistrano + nginx + thin + subversion 自动部署

Posted by wxianfeng Fri, 02 Jul 2010 19:45:00 GMT

以前部署app还是用很老套的ssh客户端,windows我使用 SSH Secure Shell Client , linux 上使用 Filezilla , 传代码非常之慢,今天体验下rails capistrano 自动部署 , 确实挺方便的 , 实现了ssh远程登录操作的一系列操作 , capistrano 的实现原理是 , 本地客户端 执行命令,实质进行了一系列远程操作,部署同步的根本是check 版本控制系统的最新版,版本控制系统可以是subversion,git等 , 我用的是 subversion,步骤:

我的环境;nginx + thin + capistrano 2.5.19 (客户端) + centos 5.5 + subversion

1,安装 capistrano

gem install capistrano

2,rails app加入 capistrano 配置文件

capify .

3,配置 config 下的deploy.rb ,最核心的东西都在这个文件里

set :application, "wxianfeng_com" #  工程名, 随便写了 

set :repository,  "svn://173.230.155.150/wxianfeng_com"   # svn 地址
set :scm, :subversion # 使用 suversion版本控制
# Or: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none`
set :scm_username, 'wxianfeng'   # svn 用户名
set :scm_password, '123456'  #svn 密码
set :checkout, "export" # 使用export方式, svn版本信息不需要 track下来

role :web, "173.230.155.150"                          #  这三个填你服务器的ip地址
role :app, "173.230.155.150"                          # 
role :db,  "173.230.155.150", :primary => true # 
# role :db,  "your slave db-server here"

set :deploy_to, "/usr/local/system/www/wxianfeng_com"  # 部署到remote 服务器路径
set :deploy_via, :remote_cache # 通过 remote_cache 方式部署 ,还有一种本地copy的方式
set :user, "root"  # remote 服务器的用户名 
set :runner, "root" # 同上
set :password, "123456" # 服务器的 root 密码

set :rake, "/usr/bin/rake" # 服务器上rake 命令path
default_run_options[:shell] = false 
default_run_options[:pty] = true
set :use_sudo, true # 允许使用 sudo

namespace :deploy do
  
  task :start , :roles => :app do
    invoke_command "cd  .." # invoke_command 是连到 服务器上执行的命令
    invoke_command "thin start -C /etc/thin/thin.yml"
  end
  
  task :stop ,:roles => :app  do
    invoke_command "killall thin" # or "thin stop -C /etc/thin/thin.yml"
  end

  task :restart, :roles => :app, :except => { :no_release => true } do
    #run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
    invoke_command "cd .."
    invoke_command "thin restart -C /etc/thin/thin.yml -O" #  -O 是onebyone(关一个,启一个) 启动thin ,注意是 大写
  end

  desc "Symlink shared configs and folders on each release."
  task :symlink_shared do
    run "rm -rf #{release_path}/public/files" # 创建 软连接, 保证服务器上 图片的 同步
    run "ln -nfs #{shared_path}/public/files #{release_path}/public/files" # or  current_path    run 等同于 invoke_command
  end


end

after 'deploy:update_code', 'deploy:symlink_shared' # 在 deploy:update_code 执行后 callback deploy:symlink_shared

以上当你执行 cap deploy 后 大体执行过程是 先 update_code 然后 deploy:symlink_shared 最后 deploy:restart 服务器 , 上面有一个thin的配置 /etc/thin/thin.yml,如果没有的话, 可以到服务器上生成:

thin config -C /etc/thin/thin.yml -c /usr/local/system/www/wxianfeng_com/current  -s 3 -e production -p 3000

4,部署
第一次部署需要运行下面的命令:

cap deploy:setup

报错:

sh: sudo: command not found 

解决:

>vi  /etc/sudoers
# Disable "ssh hostname sudo <cmd>", because it will show the password in clear.
#         You have to run "ssh -t hostname sudo <cmd>".
#
#Defaults    requiretty #将这行注释掉

这个命令会在服务器上配置好相关的目录结构.然后再运行:


cap deploy:check



检查通过

第一次部署用的命令是:


cap deploy:cold

以后部署都可以直接用:

cap deploy

修改 nginx 配置:

 root   /usr/local/system/www/wxianfeng_com/current/public/;



修改 root 根目录为 cpistrano 的 current_path 的 public 目录
ok , 你以后更新代码 只需要 cap deploy 就ok了 , 前提是你先 commit 代码到 svn 服务器上 , 用capistrano好处还有一个可以 备份代码 , 因为每一次部署 其实就是相当于 track了svn版本里的最新代码

See:
http://gautamrege.wordpress.com/2009/11/10/capistrano-nginx-thin-deployment-on-linode/
http://weekface.info/2010/03/15/rails-git-capistrano
http://rustammamedov.wordpress.com/2009/12/27/capistrano-nginx-mongrel-deployment/


rails中使用RSpec BDD测试入门

Posted by wxianfeng Tue, 02 Feb 2010 21:51:00 GMT


环境 : ruby 1.8.7 + rails 2.3.2



这一demo介绍rspec在rails中的入门 , 需要安装的依赖库 , 和执行过程 , 目录结构等

1, 安装依赖包 rspec 和 rspec-rails

>sudo  gem install rspec
>sudo gem install rspec-rails


安装结束后,>gem list r 查看r开头的gem包:

rspec (1.3.0)
rspec-rails (1.3.2)


rspec 包 是rspec的核心库 , rspec-rails 是把rspec集成到rails中 , 例如 支架 rspec_scaffold , rspec_model 等等

2,新建rails工程

>rails test_rspec -d mysql


3,修改 database.yml

注意 development 和 test 都要修改

4,建立数据库

同时建立 development 和 test 数据库

例如分别为 : test_rspec_development 和 test_rspec_test

5,支架生成MVC和rspec测试文件

>./script/generate rspec_scaffold user name:string


使用rspec_scaffold 比原来的 scaffold 支架 仅仅是多了 rspec部分, 在rails project 中在 spec 文件夹下

6, migrate

>rake db:migrate


6,测试

>rake spec


更多的关于spec的rake可以这样看下:

>rake -T spec


或者

>spec spec/models/user_spec.rb


直接执行某个 rspec 文件 测试 , 使用 spec 执行, 还可以加入一些参数 例如 :

>spec spec/models/user_spec.rb -f specdoc 


输出 doc, 更多参数 >spec -h 查看

ref:

http://wenke.javaeye.com/blog/254496


rails comet juggernaut

Posted by wxianfeng Tue, 24 Nov 2009 03:34:00 GMT
环境 : ruby 1.8.7 + rails 2.1.0 + ubuntu 8.10 desktop

comet 是 server push 技术,说白了就是 服务器端直接 推送数据到客户端,据说 是 未来能取代ajax的一门技术,comet要求客户端 和 server 要建立一个长连接,无论是http 方式 还是 socket 方式,ajax是单用户的异步请求,comet是多用户异步请求,这个技术 已经应用的很广了,特别对于一些 交互性 和 实时性 要求比较高的 系统,例如 股票 实时刷新,web聊天(web qq,gtalk,meebo.com等等),实时提醒(xiaonei.com) 等功能,

那么 在rails中 有这样的comet现成东西吗? 答案是有,juggernaut 插件,juggernaut 原理是什么呢,官网是这样 说的:

1,客户端A 和 comet server 建立一个 socket 连接 (注意这里的server 是 juggernaut的 server)

2,客户端B 向 rails server 发送一个 异步 ajax 请求 (例如这里的rails server 开发环境下为 webrick)

3,rails server 发送数据 给comet server

4,comet server 广播数据到 客户端 (客户端用户可以指定)

plugin 依赖的 库 :

* Rails 2.0.2 or edge

* json gem (gem install json)

* EventMachine gem (gem install eventmachine)

* juggernaut gem (gem install juggernaut)

demo: 实时聊天室

基于 prototype || jQuery

1,新建 rails project

2,安装插件

script/plugin install http://juggernaut.rubyforge.org/svn/trunk/juggernaut


安装完后 会在 config 下看到 juggernaut 的 host 文件

3,配置 host 文件

:hosts:
  - :port: 5001 # 默认就是5001
    :host: 192.168.1.3 # 你电脑的ip
    :public_host: 192.168.1.3
    :public_port: 5001


4,cd 到 config 目录下,生成 juggernaut 的 配置文件

juggernaut -g juggernaut.yml


会在 config下 看到 juggernaut.yml 文件

4,配置 juggernaut.yml

     :allowed_ips: 
                  - 127.0.0.1
                  - 192.168.1.3 # 添加你电脑的ip


5,开启 juggernaut

juggernaut -c juggernaut.yml


显示成 Starting Juggernaut server 0.5.8 on port: 5001... 这样就已经开启了,不要以为还在 start 当中

6,controller

class ChatController < ApplicationController
  
  def index
  end

  def chat_prototype    
  end

  def chat_jquery    
  end

  # prototype
  def send_data_p
    render :juggernaut do |page|
      page.insert_html :top, 'chat_data', "<li>#{h params[:chat_input]}</li>"
    end
    render :nothing => true
  end

  # jQuery
  def send_data_j
    render :juggernaut do |page|
      page["#chat_data"].prepend "<li>#{h params[:chat_input]}</li>"
      # page["#titl"].Text = "wang"
      page["#updatetitle"].click()
    end
    render :nothing => true
  end  
end


7,View

chat_prototype.html.erb (基于 prototype )

<html>
<head>
  <title>测试</title>
  <%= javascript_include_tag :defaults, :juggernaut %>
  <%= juggernaut(:debug => false) %>
</head>
<body>
  <%= form_remote_tag(
    :url => { :action => :send_data_p },
    :complete => "$('chat_input').value = ''" ) %>
  <%= text_field_tag( 'chat_input', '', { :size => 20, :id => 'chat_input'} ) %>
  <%= submit_tag "Add" %>
  </form>
  <ul id="chat_data" style="list-style:none">
  </ul>
</body>
</html>


上面的 <%= juggernaut(:debug => false) %> 就是 和 comet server 建立连接的,还有很多参数可以添加,详细到 juggernaut helper 文件中查看,例如我上面加了 :debug => false 就是 不输出 调试

chat_jquery.html.erb (基于jQuery,这个我给它加了动态改变title 的做法,原先想在action中改的,可是没成功,后来就在action中click页面button,然后button 触发js 修改title 成功)

<html>
  <head>
    <title id="titl" runat="server">测试</title>
    <%= javascript_include_tag 'jquery', 'json','juggernaut/juggernaut', 'juggernaut/jquerynaut', 'juggernaut/swfobject'  %>
    <%= juggernaut(:debug => false) %>
  </head>
  <body>
    <form action="" method="get">
      <div style="margin:0;padding:0">
      <input id="chat_input" name="chat_input" size="20" type="text" value="" />
      <input name="commit" type="submit" value="Add" />
    </form>
    
    <script>
      $(document).ready(function(){

        $('form').submit(function(){
          $.get('/chat/send_data_j', { chat_input: $('#chat_input').val() } )
          return false;
        })
      })

      function settitle() {
        var b = "新提醒";
        var c = ":^:";
        var t = new Date();
        s = t.getSeconds();
        if(s%2 == 0){
          document.title = b;
        }else{
          document.title = c;
        }
        setTimeout("settitle()", 1000);
      }

    </script>

    <div style="display:none">
      <input type='button' id="updatetitle" onclick='settitle();' value='Change Title'/>
    </div>

    <ul id="chat_data" style="list-style:none"></ul>

  </body>
</html>


注意jquery 加载的资源文件,和 prototype 的 不一样,所有资源在下载的 插件media中 可以找到

8,开启webrick

9,打开网址开始聊天

http://192.168.1.3:3000/chat/chat_jquery

多个用户 打开上面的网址就可以实时聊天了.......

ref:

http://juggernaut.rubyforge.org/

http://ajaxian.com/archives/juggernaut-comet-for-rails

http://macrochen.javaeye.com/blog/28020


Rails 自引用实现最近来访

Posted by wxianfeng Mon, 12 Oct 2009 04:33:00 GMT

环境 : ruby 1.8.6 + rails 2.1.0 + ubuntu 8.10

效果:就像 xiaonei 这样

snapshot30

当然最简单的办法 你可以 直接sql操作就ok 了。。。我这里介绍 利用 rails的 activerecord自引用 解决,一个系统有一个users表,但是每一个user可能有friends 或者 followers , 而这个 friends 和 followers 也在 users这个表里,这样就需要 用到 rails的自引用了。

实现最近来访 只是实现了一层的 相当于 取出 twitter的 followers

表结构:

1,用户表 client_infos (username,password,........)

2,访问关系表 vistorships

snapshot31

model关系

class ClientInfo < ActiveRecord::Base
 has_many :vistorships  
 has_many :vistors, :through => :vistorships
end
class Vistorship < ActiveRecord::Base 
 belongs_to :client_info   
belongs_to :vistor, :class_name => "ClientInfo"
end

添加最近来访 数据:

    @client_info = ClientInfo.find(params[:id])
    @recentvistor = @client_info.vistorships.build( :vistor => current_client_info)
    @recentvistor.save

取出全部来访 数据

@client_info.vistorships

Model 取出最近来访的12个vistor

    def get_vistors(client_info) # client_info 是显示那个人的对象
      find(:all,
        :conditions => ["client_info_id = ? and vistor_id <> ?" , client_info.id , client_info.id],
        :select => ["distinct(vistor_id)"],
        :limit => 12,
        :order => 'created_at DESC'
      )
    end

controller中取出数据

@latestvistors = Vistorship.get_vistors(@client_info)

View中输出

<ul>
        <%- for lvistor in @latestvistors -%>
<li>
            <%= image_link lvistor.vistor, :image => :thumbnail , :class => 'image' %>
            <%= client_info_link lvistor.vistor, :class => 'name' %>
          </li>

        <%- end -%>
      </ul>

ref:

http://railscasts.com/episodes/163-self-referential-association


rails send mail set from name

Posted by wxianfeng Thu, 17 Sep 2009 17:14:00 GMT

效果

以前 用 google 的 smtp 服务 成功发送了 email.

http://www.blogjava.net/fl1429/archive/2009/05/04/268866.html

由于 google 的 smtp 每天只容许 发送 500 封email,所以必须自己搭建邮件服务器了。。。。邮件服务器用的是 kerio mail server , 搭好之后 enviroment.rb 中 配置

ActionMailer::Base.perform_deliveries = true
 ActionMailer::Base.raise_delivery_errors = true
 ActionMailer::Base.default_charset = "utf-8"
 ActionMailer::Base.default_content_type = "text/html"
 ActionMailer::Base.smtp_settings = {
     :address => "59.314.13.266",
     :port => 25,
     :domain => "www.abc.com",
     :user_name => "webmaster",
     :password => "riskfit654321",
     :authentication => :login
     }

Model里的send 方法

  def send_password(recipient, subject, name,password)
    @subject = subject 
    @recipients = recipient 
    @from = 'webmaster@abc.com'
    @sent_on = Time.now   
    @body["name"] = name 
    @body['password'] = password 
    @headers = {}
  end

可是 发送 email 后 , 邮箱 显示的发件人 始终是 webmaster , 那么 我 想让发件人的名字显示成 测试 , 该如何设置呢,那么把@from 设置成这样 :

@from = '"测试"<webmaster@abc.com>' # 主意是外面是单引号 , 里面 是 双引号 ,而且用户名webmaster 必须和 config里的user_name 一样

ref:

http://www.javaeye.com/topic/126875