by agate - Published: 2010-03-29 [4:36 下午] - Category: 程序编码

预备知识:

1. Gmail 使用 TLS 协议
2. Gmail 有 Captcha 验证问题, 同样影响到 SMTP 客户端
3. 低于或等于 1.8.6 版本的 Ruby 不支持 TLS
4. 低于 2.2.1 版本的 Rails 不支持 TLS

所以呢, 你如果是符合 Ruby >= 1.8.7 并且 Rails >= 2.2.1 那么请 Follow 如下步骤:

1. 设置 ActionMailer::Base.smtp_settings

# 添加一个 rb 文件到 config/initializers/ 目录中, 内容如下:
ActionMailer::Base.smtp_settings = {
  :address              => "smtp.gmail.com",
  :port                 => 587,
  :authentication       => :plain,
  :enable_starttls_auto => true,
  :user_name            => "USERNAME@DOMAIN",
  :password             => "PASSWORD"
}
# 这里的关键在 enable_starttls_auto

2. 取消 Gmail 帐号的 Captcha
打开连接 www.google.com/a/DOMAIN/UnlockCaptcha
输入用户名密码即可解锁

如果你是老版本呢~ 可以参考

1. rails 官方文档 http://guides.rubyonrails.org/action_mailer_basics.html
2. 解决方案插件 http://github.com/openrain/action_mailer_tls

Tags: [ , , ] - Comments: View Comments
by agate - Published: 2009-07-09 [2:21 下午] - Category: 程序编码

今天为修一个 bug 到 ActiveRecord 模型层逛了一圈, 学了 2 个知识点.
1. Model 中的 belongs_to 和 has_many 如果指定了 :class_name 注意一定要写完整噢. 比如一个 Reply 类的完整类名是 Post::Reply 的话, 记得写这个完整的名字. 不要只写 Reply

has_many :replies,
         :class_name => 'Post::Reply'

2. 在 find 的时候 :order 参数中如果是根据别的对象的某个属性来排序的, 记得用 "表名.属性名", 注意不是 "类名(对象名).属性名"

举个例子:
环境: 有一个 Post 类, 一个 User 类, 一个 User 有多个 Post, 一个 Post 对应一个 User.
需求: 找出所有的 Post 并且根据其对应的 User 的 name 排序.
方法:

Post.find :all,
          :include => :user, #这里 :user 其实就是在 Post 类中 belongs_to 写的那个 symbol
          :order   => 'users.name' #这里 users 是表名

还有就是谢谢 wujiang 在我面前一步一步调试. 让我学了挺多的!

================================================================

补充一下, 昨天发现 belongs_to 和 has_many 这种写法还不一定好使. 原因在于这样写还不够 "绝对". 聪明的可能恍然大悟 --> ::Post::Reply 才是正解.

Tags: [ , ] - Comments: View Comments
by agate - Published: 2009-04-05 [5:13 下午] - Category: 开发环境

我一般不喜欢改动系统的总体配置, 所以一般我使用 gem 安装的时候都是不加 sudo 的. 于是我的大多数 gems 都是安装在 ~/.gem 下的(包括 rails). 今天装了 rails 2.3.2 似乎有点不爽(感觉). 于是想卸载. 但是在我执行:
$ gem uninstall rails
ERROR: While executing gem ... (Gem::InstallError)
  Unknown gem rails >= 0

弄了半天, 也查了是不是路径拼写错误. 还是没弄出来...
后来 google 了一下, 得知由于非默认安装位置(非 sudo 嘛). 所以需要指定 --install-dir 参数. 于是乎我就这么写了:
$ gem uninstall --install-dir ~/.gem/ruby/1.8
ERROR: While executing gem ... (Gem::InstallError)
  Unknown gem rails >= 0

还是一样噢... 无奈, 继续 google! 发现一个命令:
$ gem list -d rails
*** LOCAL GEMS ***
rails (2.3.2, 2.2.2)
  Author: David Heinemeier Hansson
  Rubyforge: http://rubyforge.org/projects/rails
  Homepage: http://www.rubyonrails.org
  Installed at (2.3.2): /home/agate/.gem/ruby/1.8
    (2.2.2): /home/agate/.gem/ruby/1.8

这样就十分方便地晓得了具体 gem 安装的路径了, 直接拷贝下来作为 --install-dir 参数即可.
$ gem uninstall --install-dir /home/agate/.gem/ruby/1.8 rails
Select gem to uninstall:
1. rails-2.2.2
2. rails-2.3.2
3. All versions
> 2
Successfully uninstalled rails-2.3.2

至此, 小问题解决.

PS. 你会说: 呀 "/home/agate/.gem/ruby/1.8" 和 "~/.gem/ruby/1.8" 不是一样么? 怎么第一次用 --install-dir ~/.gem/ruby/1.8 不行呢?
因为就是不行! 我也不知道, 他就是要绝对路径罢了...

Tags: [ , , ] - Comments: View Comments
by agate - Published: 2009-04-05 [9:36 上午] - Category: 开发环境

今天在新的 Ubuntu 上面安装 rails 的时候碰到了这样的问题:

`require_frameworks': no such file to load -- net/https (RuntimeError)

原来是缺少 libopenssl-ruby library:
解决方案:
$sudo apt-get install libopenssl-ruby
本文作为 Ubuntu下部署rbuy on rails开发环境 补充

Tags: [ , , ] - Comments: View Comments
by agate - Published: 2009-01-10 [4:32 下午] - Category: 程序编码

最近项目中使用了 sphinx 作为数据库的全文搜索引擎. 加之使用的是 ror 开发组合, 固然使用到了比较流行的 ultra sphinx plugin. 觉得十分易用就在这里记录一下使用的大致方式(就一 helloworld).

1. 当然是下载安装 sphinx.
可以到 http://www.sphinxsearch.com/downloads.html 网站下载最新的源码编译安装.
(ps 如果你要加入中文分词的支持就要安装 libmmseg. 编译前需要打个补丁. 或者直接使用作者打好补丁的进行编译安装. 具体的方式可以 google 一下)

2. 安装 rails 的 sphinx 插件 "ultra sphinx"
安装依赖 gem
$sudo gem install chronic
进入你的 rails 应用目录中
$./script/plugin install git://github.com/fauna/ultrasphinx.git

3. 开始使用.
其实具体的还是查阅官方 doc 比较好. 比较全面一些!
这里还是为了回顾方便, helloworld 来也!

第一步 [准备准备]
$rails sphinx -d mysql
$cd sphinx
$./script/plugin install git://github.com/fauna/ultrasphinx.git
$./script/generate scaffold post title:string body:text
$rake db:create:all
$rake db:migrate

(这些只是大步骤, 具体的什么数据库用户名密码设置这类的自己补上就是了. 还有, 目前 ultrasphinx 仅仅支持 mysql 和 PostgreSQL)

第二步 [声明 model 为 searchable]
编辑 [rails_root]/app/models/post.rb, 内容如下:

class Post < ActiveRecord::Base
  is_indexed :fields => ['created_at', 'title', 'body']
end

第三步 [配置/索引/启动]
首先复制配置文件的模板到 config/ultrasphinx 下.
$mkdir config/ultrasphinx
$cp vendor/plugins/ultrasphinx/examples/default.base config/ultrasphinx/

修改 default.base 中的 path 为这个 rails 工程的绝对地址

吧 <% path = "/opt/local/var/db/sphinx/" %>
改成 <% path = "/home/[你]/....." %>

$rake ultrasphinx:configure
$rake ultrasphinx:index
$rake ultrasphinx:daemon:start

(第一次执行时候,顺序不能颠倒!)

第四步 [添加点数据试试看]
添加数据我就不说了, 自己启动服务器到对应页面 new 几个出来. 然后重新索引 sphinx. 然后开启 rails 的 console:
$./script/console
>>@query = '自己写吧'
>>@search = Ultrasphinx::Search.new(:query => @query)
>>@search.run
>>@search.results

by agate - Published: 2008-12-25 [5:44 下午] - Category: 程序编码

默认情况下我们使用 rails 默认的 validates 来进行模型数据验证工作的时候常常会加上 message 参数来自定义我们的错误提示.

validates_presence_of :name, :message => 'is blank...'

出错的时候会提示

Name is blank...

但是要是我想显示

The name field is blank!

单靠改 message 的内容看来是不行的. 在 google 一番之后发现 ActiveRecord::Errors 这个类中有一个叫做 full_messages 的 helper function. 就是完成把出错的 field 的名字添加到 message 内容的前面的工作. 所以可以 override 一番. 就可以实现我们的功能了. 但是也许不小心会破坏过去的代码. 所以这里推荐一个插件( 由 rails 核心成员写的. )
Custom Error Message
其实十分简单, 它也是 override 了那个 helper function. 如果你不喜欢加插件的话自己在 lib 下面建立一个 ruby 文件就好了, 这里也顺便贴一下代码.

def full_messages
  full_messages = []

  @errors.each_key do |attr|
    @errors[attr].each do |msg|
    next if msg.nil?

      if attr == "base"
        full_messages << msg
      elsif msg =~ /^\^/
        full_messages << msg[1..-1]
      elsif msg.is_a? Proc
        full_messages << msg.call(@base)
      else
        full_messages << @base.class.human_attribute_name(attr) + " " + msg
      end
    end
  end

  return full_messages
end

使用方式:
在你需要自定义消息的前面加上 '^' 这个符号.(说白了就是很想正则告诉代码这是开始)

validates_presence_of :name, :message => '^The name field is blank!'

这样出错后就会显示:

The name field is blank!

Tags: [ , ] - Comments: View Comments
by agate - Published: 2008-10-29 [3:39 下午] - Category: 程序编码

很早就在 "Web 开发大全 -- Ruby on Rails 版" 书中看到过 restful_authentication 这个鉴定插件了~ 但是一直都没使用过. 今天看到一个中文的 ROR 视频网站 -- rubycnrails.cn 上面用视频介绍了 restful_authentication 这个插件的使用(但是这位仁兄老是出错... 看着我好着急阿!) 呵呵! 于是我也试着使用了一番, 发现真的满好用的. 这里就记录一下, 顺便说一下需要注意的地方.

github 地址 http://github.com/technoweenie/restful-authentication/tree/master
(rubycnrails.cn 上面那个视频似乎是使用旧的版本... 大概不知道这个插件已经迁移到 github 上面了吧.)

安装:
$cd [your-rails-app]
$./script/plugin install git://github.com/technoweenie/restful-authentication.git

使用:
$./script/generate authenticated Model-Name [Controller-Name]
别忘了 rake 一下, 来建立你的数据库!

结果:
生成对应的 controller / model / view. 添加了对应的 routes: signup / login / logout. 当然, 还添加了

lib/authenticated_system.rb
lib/authenticated_test_helper.rb

这两个才是重要的插件功能! 其中 AuthenticatedSystem 中包含了诸如: logged_in?, current_kid 等重要方法! 到时候我们只需要在需要调用的 controller 中 include AuthenticatedSystem 就好了.

注意:
不要傻乎乎地学 README 中键入: ./script/generate authenticated user sessions 这个 s 最好不要!(不要说我违反了 RESTful 的理念, 我说的是最好不要!) 对于初学者来说先别加这个 s, 因为这个会带来很多配置上的模糊: 比如 routes.rb 中 resource 是定义为 'session', 但是设置具体命名路由的时候使用 controller 参数时又要设置为 'sessions'. 所以, 如果你对这里头的细节不是很清楚的话, 建议你改用单数作为这里控制器的名称:
$./script/generate authenticated user session 我建议这么写
当然! 你清除的话, 或者你不关心这个的话, 那还是写上那个 s 吧! 这样才够 RESTful!
其他很细致的内容还是看看源代码或者看看 Plugin 的 README 吧! 写得很不错!

by agate - Published: 2008-10-28 [9:04 上午] - Category: 程序编码

看看我的 rails 版本吧!
$ gem list rails

*** LOCAL GEMS ***
rails (2.1.2, 2.1.1, 2.1.0, 2.0.2, 1.2.6)

是不是很多呀? 用 rails 命令创建一个新应用的时候, 默认使用最新的版本进行构建. 很多情况下要创建特定版本的应用, 可以使用一个额外的参数来达到这个目的, 具体如下:
$rails _[version number]_ appname
例如, 我要建立一个基于 rails 1.2.6 版本的经典 depot 应用就可以:
$rails _1.2.6_ depot

Tags: [ ] - Comments: View Comments
by agate - Published: 2008-10-21 [7:34 上午] - Category: 开发环境

怎么说呢~ 我还是比较喜欢视窗系统下的使用开发方式(具体原因是 Linux 和 Mac 对中文字体的渲染实在是不敢恭维). 于是目前我使用开发 web 程序的方式是使用 VMware, 在视窗系统下使用 PuTTY 和视窗共享来操作 Linux 系统, 并进行开发. 效果挺不错的. 本文书写的原因是 autotest 在远程调用时候比较不容易实现可视化的自动测试(出错有声音啊, 有红字啊~). 所以经过变通思考我决定采用 Firefox + Greasemonkey 这样的搭配来实现定时访问 autotest 生成的 html 结果, 从而在视窗系统下对远端 Linux 开发机进行自动化测试和提示.

先看看效果图:

你会问, autotest 生成的 html 并不能自动刷新啊, 这里我就想到了 Greasemonkey 这个强大的 Firefox 自定义扩展插件. 可以通过自己手动书写 JS 来扩展页面功能/样式.

于是我便写了一个简单的 JS 脚本, 提供 n 秒自动刷新, 有 failures 就发出报告声. 脚本如下:

// ==UserScript==
// @name           rspec results auto refresh
// @namespace      agatezone
// ==/UserScript==

// modify vars
var time_left = 10;
var path = "file:///E:/my_help_files/GreasemonkeyFiles/rspec_results_auto_refre";

// script begin ---------
// init
init();

// play error sound while has errors.
if (hasError()) {
	var sound = document.createElement('div');
	document.getElementById('reload').appendChild(sound);
	sound.innerHTML = "<embed name=\"pmsound2player\" src=\"" + path + "player.swf\" flashvars=\"sFile=" + path + "failure.mp3\" menu=\"false\" allowscriptaccess=\"sameDomain\" swliveconnect=\"true\" type=\"application/x-shockwave-flash\" width=\"0\" height=\"0\">";
}

// start timer
reloadTimer();

/////////////////////////////

// functions start ++++++++++++++++
function init() {
	var summary = document.getElementById('summary');
	var p = document.createElement('p');
	var t = document.createTextNode('');
	p.setAttribute('id', 'reload');
	p.appendChild(t);
	summary.appendChild(p);
}

function setTitle(x) {
	document.title = x;
	document.getElementById('reload').firstChild.nodeValue = x + "...";
}

function reloadTimer() {
	if (time_left <= 0) {
		setTitle('Reloading...');
		window.location.reload();
	} else {
		setTitle ('Reload in ' + time_left + ' second' + (time_left == 1 ? '' : 's'));
		setTimeout (reloadTimer, 1000);
		time_left --;
	}
}

function hasError() {
	var result_text = document.getElementById('totals').firstChild.nodeValue;
	var re = /(\d+)\s+examples?,\s*(\d+)\s+failures?(,\s*(\d+)\s+pending)?/;
	var results = re.exec(result_text);
	if (results[2] != 0) {
		return true;
	} else {
		return false;
	}
}
// functions end ++++++++++++++++
// script end ---------

这里有两个文件 DOWNLOAD 主要作为错误提示音使用, 请解压开后放置到一个位置, 并且把这段代码里头的 path 修改为这两个文件存放的位置. 就比如我把这两个提示音文件放在:
c:\autotest_rspec\
下面, 那么 path 就改成:
file:///C:/autotest_rspec/
这样的网页地址形式.

这样便可以简单地在微软视窗操作系统下实现自动化测试远端开发机并给出结果提示.

灵感: Greasemonkey 太强大了! JavaScript 同样! Dom 模型更是了不起!

Tags: [ , , ] - Comments: View Comments
by agate - Published: 2008-10-17 [1:18 下午] - Category: 开发环境, 程序编码

羡不羡慕那些 rails 视频在 MAC OS X 下面帅气的自动跳出绿色和红色的自动测试提示呢? 当然如果你是 MAC 用户就不用羡慕了哈!(比如我就不羡慕嘿嘿) 但是大部分朋友都是 PC 呀! 没有 MAC 下的 Growl 作为 UI 提示工具怎么办呢? 没事啦! 这些我们都好解决, Ubuntu 下面我们可以用 rnotify 这个 ruby gnome 的消息提示工具. Windows 下我们可以用 ruby-snarl 这个消息提示工具啦! 当然还有 AutoIt 这类辅助工具! 这里我们先说 Ubuntu 这样的 GNOME 环境中的解决方案.

首先是安装 autotest, rspec, rspec_rails 三个插件
$sudo gem install autotest
$rails hello
$cd hello
$./script/plugin install http://rspec.rubyforge.org/svn/tags/CURRENT/rspec
$./script/plugin install http://rspec.rubyforge.org/svn/tags/CURRENT/rspec_on_rails
$./script/generate rspec

这样便安装好最基础的 BDD 环境了. 手动在你的 rails 目录下键入
$./script/autospec
这是这个自动测试不是在后台运行的, 因为他要显示信息嘛!
所以最小化这个终端窗口吧, 或者新开一个 Tab 来继续操纵你的 rails 应用.

就可以启动针对此 rails 应用的持续测试功能了. 让我们来试试看! 如何工作, 在终端中你的 rails 目录下键入:
$./script/generate rspec_scaffold post title:string
$rake db:migrate
$rake db:test:clone

看看你的那个自动测试窗口吧! (如果你的小机器不太快, 这个测试窗口还和原来一样, 那么稍等一会会, 他马上就运行咯!) 你大概会看到类似的结尾结果:

哈! 是不是很有成就感? (当时我满有的...) 呵呵, 但是这个只是在终端中看到的, 难不成要我们每次保存了新的东西都要光顾我们的终端信息么? Mac 下有 Growl 来做桌面 UI 气泡提示. 在 Ubuntu 这样的 GNOME 下我们可以使用 ruby-notify 来实现. 首先必须安装 ruby-gnome2 / libnotify-dev 和 ruby-libnotify 这些组件依赖. (其中 ruby-libnotify 需要手动安装)
$sudo apt-get install ruby-gnome2 libnotify-dev
以上安装好了 ruby-gnome2 和 libnotify-dev , 至于 ruby-libnotify 可以到其官方网站下载手动其源代码编译安装, 当前最新版本为 0.3.3, 在其下载页面我下好了 ruby-libnotify-0.3.3.tar.bz2 文件, 将其解压开好. 使用终端进入其目录中执行:
$ruby extconf.rb
$sudo make
$sudo make install

这样就安装好 ruby-libnotify 了! 其实就是用它来代替 Mac 下的 Growl 啦! 接着建立 ~/.autotest 文件并填入如下代码.

require 'rnotify'
require 'gtk2'

module Autotest::RNotify
  class Notification
    attr_accessor :verbose, :image_root, :tray_icon, :notification,
      :image_pass, :image_pending, :image_fail,
      :image_file_pass, :image_file_pending, :image_file_fail,
      :status_image_pass, :status_image_pending, :status_image_fail

    def initialize(timeout = 5000,
        image_root = "#{ENV['HOME']}/.autotest_images" ,
        verbose = false)
      self.verbose = verbose
      self.image_root = image_root
      self.image_file_pass = "#{image_root}/pass.png"
      self.image_file_pending = "#{image_root}/pending.png"
      self.image_file_fail = "#{image_root}/fail.png"

      raise("#{image_file_pass} not found") unless File.exists?(image_file_pass)
      raise("#{image_file_pending} not found") unless File.exists?(image_file_pending)
      raise("#{image_file_fail} not found") unless File.exists?(image_file_fail)

      puts 'Autotest Hook: loading Notify' if verbose
      Notify.init('Autotest') || raise('Failed to initialize Notify')

      puts 'Autotest Hook: initializing tray icon' if verbose
      self.tray_icon = Gtk::StatusIcon.new
      tray_icon.pixbuf = Gdk::Pixbuf.new(image_file_pending,22,22)
      tray_icon.tooltip = 'RSpec Autotest'

      puts 'Autotest Hook: Creating Notifier' if verbose
      self.notification = Notify::Notification.new('X', nil, nil, tray_icon)

      notification.timeout = timeout

      Thread.new { Gtk.main }
      sleep 1
      tray_icon.embedded? || raise('Failed to set up tray icon')
    end

    def notify(icon, tray, title, message)
      notification.update(title, message, nil)
      notification.pixbuf_icon = icon
      tray_icon.tooltip = "Last Result: #{message}"
      tray_icon.pixbuf = tray
      notification.show
    end

    def passed(title, message)
      self.image_pass ||= Gdk::Pixbuf.new(image_file_pass, 48, 48)
      self.status_image_pass ||= Gdk::Pixbuf.new(image_file_pass, 22, 22)
      notify(image_pass, status_image_pass, title, message)
    end

    def pending(title, message)
      self.image_pending ||= Gdk::Pixbuf.new(image_file_pending, 48, 48)
      self.status_image_pending ||= Gdk::Pixbuf.new(image_file_pending, 22, 22)
      notify(image_pending, status_image_pending, title, message)
    end

    def failed(title, message)
      self.image_fail ||= Gdk::Pixbuf.new(image_file_fail, 48, 48)
      self.status_image_fail ||= Gdk::Pixbuf.new(image_file_fail, 22, 22)
      notify(image_fail, status_image_fail, title, message)
    end

    def quit
      puts 'Autotest Hook: Shutting Down...' if verbose
      #Notify.uninit
      Gtk.main_quit
    end
  end

  Autotest.add_hook :initialize do |at|
    @notify = Notification.new
  end

  Autotest.add_hook :ran_command do |at|
    results = at.results.last

    unless results.nil?
      output = results[/(\d+)\s+examples?,\s*(\d+)\s+failures?(,\s*(\d+)\s+pending)?/]
      if output
        failures = $~[2].to_i
        pending = $~[4].to_i
      end

      if failures > 0
        @notify.failed("Tests Failed", output)
      elsif pending > 0
        @notify.pending("Tests Pending", output)
      else
        unless at.tainted
          @notify.passed("All Tests Passed", output)
        else
          @notify.passed("Tests Passed", output)
        end
      end
    end
  end

  Autotest.add_hook :quit do |at|
    @notify.quit
  end
end

然后呢, 这里有几个图片, 请你下载下来保存到 ~/.autotest_images 目录下(自己建立这个文件夹).

(注意哦! 文件名不要变哦! 分别为 pending.png pass.png fail.png)

打工告成! 你可以在 rails 工程目录里头执行:
$./script/autospec
不出意外的话你应该会看到如下结果

Tips:
1. ~/.autotest 这个文件可以定制 autotest 的运行.
2. ruby-libnotify 可以用于 GNOME 中作为消息提示.(注意哦! 用源代码编译安装哦!)

Tags: [ , , , , ] - Comments: View Comments