乐者为王

Do one thing, and do it well.

用Rails 2.3打造简单记账应用(5)

虽然在前面已经调整了记账应用的页面流程,但在登录系统进入entries页面后却没有登出的链接,这次就来把这个完成。同时也给entries页面加上一个三行两列的布局,使之看上去更像一个应用。

在public/stylesheets下加入qianbao.css,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#container {
  width: 960px;
  margin: 5px auto;
}

#header {
  height: 2.5em;
  border-bottom: 1px solid #ccc;
}

#content {
}

#footer {
  border-top: 1px solid #ccc;
  text-align: center;
}

将app/views/layouts/目录下的entries.html.erb改名为application.html.erb,并修改文件内容为:

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
<!DOCTYPE html PUBLIC
  "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <title>Qianbao: <%= controller.action_name %></title>
  <%= stylesheet_link_tag 'scaffold' %>
  <%= stylesheet_link_tag 'qianbao' %>
</head>

<body>
  <div id="container">
    <div id="header">
      <%= link_to 'Home', root_path %>
      <% if logged_in? %>
        <%= link_to 'All entries', entries_path %>
        <strong>You are logged in as <%=h current_user.login %></strong>
        <%= link_to 'Logout', logout_path %>
      <% else %>
        <%= link_to 'Login', login_path %>
        <%= link_to 'Sign up', signup_path %>
      <% end %>
    </div>

    <div id="content">
      <p style="color: green"><%= flash[:notice] %></p>

      <%= yield %>
    </div>

    <div id="footer">© 2009</div>
  </div>
</body>
</html>

清理app/views/home/index.html.erb的内容,回归原始:

1
<h1>Welcom to Qianbao App!</h1>

代码下载:https://github.com/dohkoos/qianbao

用Rails 2.3打造简单记账应用(4)

今天我们来继续完善我们的记账应用。

首先,修改app/views/home/index.html.erb为如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
<h1>Welcom to Qianbao App!</h1>

<% if logged_in? %>
  <p><strong>You are logged in as <%=h current_user.login %></strong></p>
  <p><%= link_to 'Logout', logout_path %></p>
<% else %>
  <p><strong>You are currently not logged in.</strong></p>
  <p>
    <%= link_to 'Login', login_path %> or
    <%= link_to 'Sign up', signup_path %>
  </p>
<% end %>

要在视图中使用logged_in?和current_user这两个帮助器方法,还需要在app/controllers/application_controller.rb中增加引入AuthenticatedSystem语句:

1
2
3
4
5
6
7
8
9
class ApplicationController < ActionController::Base
  include AuthenticatedSystem

  helper :all # include all helpers, all the time
  protect_from_forgery # See ActionController::RequestForgeryProtection for details

  # Scrub sensitive parameters from your log
  # filter_parameter_logging :password
end

否则会报告下面的错误:

1
undefined method 'logged_in?'

既然在ApplicationController中已经包含了该模块,那么其它控制器中的也就可以删除了。

然后创建app/views/users/create.html.erb文件:

1
2
3
4
5
6
7
8
9
10
11
<h1>Please confirm your registration!</h1>

<p>
  A mail has been sent to <%= @user.email %> with instructions to activate your account.
  <li>
    If your email is not valid, you must <%= link_to "Sign up", signup_path %> again and provide a valid email address.
  </li>
  <li>
    If you don't recieve an email, check your bulk or trash folder, as your spam filter may have inadvertantly caught the registration email.
  </li>
</p>

同时注释掉app/controllers/users_controller.rb中create方法中的这行代码:

1
redirect_back_or_default('/')

这样当用户注册成功后就不会重定向到首页,而是显示提醒用户收取激活帐号邮件的页面。

另外,还需要把entries保护起来,不能让未登录的用户访问。可以在app/controllers/entries_controller.rb中添加一个before_filter做到:

1
2
3
4
5
6
class EntriesController < ApplicationController
  before_filter :login_required

  # GET /entries
  # GET /entries.xml
  def index

还要将登录后的页面改成entries列表,这需要将app/controllers/sessions_controller.rb中create方法下的:

1
redirect_back_or_default('/')

改为

1
redirect_back_or_default('/entries')

最后,还要调整下entries各个页面之间的流转顺序,把show这个页面去掉,使得在添加或更新记录后就直接跳到entries列表,而不再是show页面。

  1. 先将app/views/entries/show.html.erb删除;
  2. 删除app/views/entries/index.html.erb中的show链接;
  3. 删除app/views/entries/edit.html.erb中的show链接;
  4. 然后删除app/controllers/entries_controller.erb中的show方法;
  5. 再修改app/controllers/entries_controller.erb中的update和create方法为以下代码:
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
def create
  @entry = Entry.new(params[:entry])
  respond_to do |format|
    if @entry.save
      flash[:notice] = 'Entry was successfully created.'
      format.html { redirect_to(entries_url) }
      format.xml  { head :ok }
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @entry.errors, :status => :unprocessable_entity }
    end
  end
end

def update
  @entry = Entry.find(params[:id])
  respond_to do |format|
    if @entry.update_attributes(params[:entry])
      flash[:notice] = 'Entry was successfully updated.'
      format.html { redirect_to(entries_url) }
      format.xml  { head :ok }
    else
      format.html { render :action => "edit" }
      format.xml  { render :xml => @entry.errors, :status => :unprocessable_entity }
    end
  end
end

代码下载:https://github.com/dohkoos/qianbao

用Rails 2.3打造简单记账应用(3)

这次要给应用加上一个认证系统(注册、激活、登录、登出)。当用户输入注册信息时,必须输入有效的邮箱地址,注册成功后,用户并不能立即登录系统,而是要登录注册时输入的邮箱,通过该邮箱内的激活邮件来激活账户。通过这种方式可以防止用户的恶意注册。

restful_authentication是一个支持Rails 2.0的认证系统插件,它为你生成REST风格的认证系统模板,除了提供最基本的用户注册登录登出功能外,还有一个可选的邮件激活功能。只要一个命令,它就为你生成了User模型、管理注册和登录的控制器、相应的视图页面、mailer等等。

安装插件和生成框架代码

1
2
3
script/plugin install git://github.com/technoweenie/restful-authentication.git restful_authentication
script/generate authenticated user sessions --include-activation
rake db:migrate

--include-activation选项决定是否生成向新注册用户发送激活码邮件的代码。

如果你想你的URL看起来更符合惯例一些,那么可以在config/routes.rb中添加:

1
2
3
map.signup '/signup', :controller => 'users', :action => 'new'
map.login '/login', :controller => 'sessions', :action => 'new'
map.logout '/logout', :controller => 'sessions', :action => 'destroy'

因为使用了--include-activation选项,所以还要在config/routes.rb中增加以下映射:

1
2
map.activate '/activate/:activation_code',
             :controller => 'users', :action => 'activate', :activation_code => nil

最后,还需要添加一个observer到config/environment.rb的Rails::Initializer块中:

1
config.active_record.observers = :user_observer

设置ActionMailer,在config/environments/development.rb中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = true
config.action_mailer.perform_deliveries = true
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  :address => "smtp.example.com",
  :port => 25,
  :domain => "example.com",
  :authentication => :login,
  :user_name => "yourname@example.com",
  :password => "yourpassword"
}
config.action_mailer.default_charset = "utf-8"

HOST = "localhost:3000"
ADMINEMAIL = "yourname@example.com"

打开app/models/user_mailer.rb,修改

1
2
3
4
@body[:url]  = "http://YOURSITE/activate/#{user.activation_code}"
@body[:url]  = "http://YOURSITE/"
@subject     = "[YOURSITE] "
@from        = "ADMINEMAIL"

为以下代码:

1
2
3
4
@body[:url]  = "http://#{HOST}/activate/#{user.activation_code}"
@body[:url]  = "http://#{HOST}/"
@subject     = "[#{HOST}] "
@from        = "#{ADMINEMAIL}"

如果出现以下错误信息,那么可能是smtp_settings中的domain没有填写:

1
2
3
4
Net::SMTPSyntaxError (500 Error: bad syntax):
  RAILS_HOME/lib/ruby/1.8/net/smtp.rb:679:in 'check_response'
  RAILS_HOME/lib/ruby/1.8/net/smtp.rb:652:in 'getok'
  RAILS_HOME/lib/ruby/1.8/net/smtp.rb:622:in 'helo'

user_mailer.rb中的ADMINEMAIL必须是一个有效的邮件账号,否则会出现:

1
2
3
4
Net::SMTPFatalError (550 Invalid User):
  RAILS_HOME/lib/ruby/1.8/net/smtp.rb:679:in 'check_response'
  RAILS_HOME/lib/ruby/1.8/net/smtp.rb:652:in 'getok'
  RAILS_HOME/lib/ruby/1.8/net/smtp.rb:630:in 'mailfrom'

代码下载:https://github.com/dohkoos/qianbao

用Rails 2.3打造简单记账应用(2)

记账应用的基本功能都已经实现了,现在来给它加上个首页。

1
script/generate controller home index

上面的命令生成了控制器home,它有个动作index,并且在views目录下还生成了home/index.html.erb文件。打开它加入下面的内容:

1
2
<h1>Welcom to Qianbao App!</h1>
<%= link_to "All Entries", entries_path %>

在浏览器里面输入http://localhost:3000/,怎么出现了这个页面?

rails-welcome

这并不是我们想要的结果。Rails要求URL需以http://example.com/controller/action的形式出现,因此要指向应用自己的首页需要修改config/routes.rb文件。在routes.rb中加入:

1
map.connect '/', :controller => "controller_name", :action => "action_name"

或者:

1
map.root :controller => 'controller_name'

这里采用第二种方法,在routes.rb中去掉map.root行的注释,修改控制器名称welcome为home。

为了防止默认首页public/index.html与上面设定的页面产生冲突,可以选择将它删除。

代码下载:https://github.com/dohkoos/qianbao

用Rails 2.3打造简单记账应用(1)

我们先来定义一下什么是最简单的记账应用:

  1. 可以输入花钱的记录;
  2. 可以修改输入了的记录;
  3. 可以删除输入了的记录;
  4. 可以显示所有的记录。

下面我们就开始动手吧!

1
2
3
rails -d mysql qianbao
cd qianbao
rake db:create  # 在这之前要把数据库用户名和密码填写到config/database.yml中

从Rails 2.0开始,scaffold就从核心中移除了,不过可以直接作为script/generate对象来使用,所以现在scaffold的语法是这样子的:

1
script/generate scaffold model_name [field:type field:type]

具体细节可以使用script/generate scaffold命令查看。

1
2
3
script/generate scaffold entry amount:decimal tags:string comment:text
rake db:migrate
script/server

打开浏览器输入并访问http://localhost:3000/entries

怎么样,一个简单的记账应用是不是已经做好了。

代码下载:https://github.com/dohkoos/qianbao

在Windows Server 2008下安装Android SDK 2.0

SDK Setup.exe在Windows Server 2008上是无法执行的,看Readme文档知道可以用命令行的方式更新。不过不带参数执行时抛出了下面的错误:

1
2
3
Starting Android SDK Updater
SWT folder 'lib\x86_64' does not exist.
Please set ANDROID_SWT to point to the folder containing swt.jar for your platform.

查看tools/android.bat代码后发现有个bug,在拷贝代码时没有考虑x86_64平台的问题。找到如下代码:

1
xcopy lib\x86 %tmpdir%\lib\x86 /I /E /C /G /R /Y /Q > nul

把它改成

1
xcopy %swt_path% %tmpdir%\%swt_path% /I /E /C /G /R /Y /Q > nul

或者也可以在环境变量中添加ANDROID_SWT,使它指向lib\x86_64\swt.jar文件的位置。

10块钱破解指纹考勤

前段时间报名参加了驾驶员培训,结果发现了一个破解指纹识别的好办法。现在什么都讲高科技,驾驶员培训当然也不例外,在训练中就采用指纹考勤机来记录学员上车的时间,达不到规定时间的学员就不能参加考试。参加培训的都知道,经常会有学员因故缺席训练。那怎么能让学员不在也可以计算时间呢?这不,教练们只花了不到10元钱就搞定了这个问题。

材料:

  • 玻璃胶(硅酮密封胶)一瓶
  • 橡皮泥一盒

将橡皮泥搓到有点发硬的时候压平,然后在光滑的一面按上手指印,将玻璃胶挤入到手指印中,抹平,然后放置在阴凉处一段时间就行了。就这么简单!

如何在过了60天后再激活Windows Server 2008

登录系统后,会出现一个激活窗口,告诉你激活已过期,要接着使用的话就必须先激活。并提供几种激活的方式。选择第二项即Buy a new product key online,会弹出如下窗口:

go-microsoft-fwlink

点击确定,这时就会打开浏览器。在浏览器中选择打开文件菜单,找到cmd.exe文件,右键点击Run as administrator,然后执行slmgr.vbs -rearm指令,接着重启计算机就可以了。

DataWindow的数据缓冲区

在PowerBuilder中,DataWindow是用来存储和操纵数据的对象。在每个DataWindow对象中都有4个二维表作为数据缓冲区来存储数据。用户在DataWindow中对数据的操作实际上都是将数据在这几个缓冲区中进行修改和移动,最后在用户提交数据库时,系统根据这四个缓冲区中的信息形成SQL的INSERT、UPDATE、DELETE等语句。这四个缓冲区是:

Primary Buffer

这个缓冲区是存放填充窗口中DataWindow控件的数据的,调用DataWindow控件的Retrieve()函数和InsertRow()函数可以将数据填入这个缓冲区中。当使用有关DataWindow删除和过滤函数时,相应记录将从这一缓冲区中删除。而在执行DataWindow的Update()函数时,PowerBuilder将查看这一缓冲区中的记录以形成INSERT和UPDATE语句。

Delete Buffer

这个缓冲区保存的是用DeleteRow()函数从Primary Buffer中删除的记录,执行Update()函数时,系统根据这一缓冲区的记录形成DELETE语句。

Filter Buffer

这个缓冲区存储的是从Original Buffer使用Filter()函数过滤到Primary Buffer中后剩余的记录。

Original Buffer

这一缓冲区存储的是DataWindow最初执行Retrieve()函数时得到的全部记录。当提交数据库时,根据Primary Buffer生成的UPDATE语句和根据Delete Buffer生成的DELETE语句都要依据这一缓冲区来构造这些SQL语句中的WHERE子句。

Original Buffer由PowerBuilder内部维护,PowerBuilder所提供的任何函数都无法改变它的值,不过通过PowerBuilder所提供的GetItem系列函数可以读出DataWindow最初从数据库中查到的原始值。通过这些函数我们可以编程实现所谓的Undo功能,并且得到在使用乐观锁时形成提交数据库的WHERE子句。如果你当前使用的DataWindow没有设置修改的权力,你将不能对Delete Buffer和Original Buffer进行操作,而且当调用Update()时也将引起系统错误。

Primary Buffer和Delete Buffer都有行级和列级的状态值,这个状态值是一个枚举类型。在提交时由该行的状态值来决定是否要产生SQL语句,其中Primary Buffer产生的是INSERT和UPDATE语句,而Delete Buffer产生的是DELETE语句。我们用GetItemStatus()函数和SetItemStatus()函数可以对这一状态值进行操纵。这一枚举状态有以下四种:

1
2
3
4
NotModified! —— 指定单元的数据和原始数据相同,没有发生改变。
DataModified! —— 指定单元的数据和原始数据不同,发生了改变。
New! —— 该数据行是新增加的,但数据没有发生改变(数据为空或缺省值)。
NewModified! —— 该数据行是新增加的,且数据已发生改变(用户键盘输入或调用SetItem()函数)。

在上面的这四个值中,NotModifed!和DataModified!可以表示行和列的状态,而New!和NewModified!只可以表示行的状态。

让我们来看一个实例。有这样一张表,表中有3个字段,其中item是主键。

1
2
3
item CHAR(5)
name CHAR(20)
quantity INT

在代码中我们查询这张表的记录,得到以下的这些信息,它们被存储在Primary Buffer和Origianal Buffer中。

dwbuffer-1

在窗口中,我们过滤掉数量为0的行,并且加上一个空行:

1
2
3
dw_1.SetFilter("quantity=0")
dw_1.Filter()
dw_1.InsertRow()

这时Primary Buffer的状态为:

dwbuffer-2

在Filter Buffer中的记录为:

dwbuffer-3

用户在新插入行中输入数据,删除了第3行数据,并修改了第2行数据。当他离开这个DataWindow时,Primary Buffer和Delete Buffer的状态如下:

dwbuffer-4

dwbuffer-5

这时执行dw_1.Update()函数,系统将基于这两个缓冲区生成SQL语句。

dwbuffer-6

在Primary Buffer中,状态为NotModified!和New!的行将被忽略而不产生SQL语句。状态为DataModified!的行将产生UPDATE语句,状态为NewModified!的行将产生INSERT语句,在Delete Buffer中的行将产生DELETE语句。使用DataWindow的Reset()函数和Retrieve()函数以及改变DataObject属性时,系统将重置这几个缓冲区。

利用WHOIS协议查询域名信息

域名信息的查询原理非常简单,主要是基于RFC 954(RFC 812已被废弃)提供的WHOIS协议。WHOIS服务器是一个基于“查询/响应”的TCP事务服务器,用户程序通过访问WHOIS服务器,从WHOIS数据库中查询得到我们所需要的内容。其主要过程有以下三步:

  1. 在TCP服务端口43连接WHOIS服务主机;
  2. 发送一个命令,以回车换行符结尾;
  3. 接受相应命令的返回信息,一旦输出结束,服务器将关闭连接。

命令的格式非常简单。可以直接输入域名(例如example.com)查询相关域名信息;也可以使用help得到详细的帮助信息。以下是查询的代码:

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
wxString Whois::Lookup(wxString& host, wxString& szAddress)
{
    char szQuery[512];
    char szBuffer[512];
    wxString szResult;

    strcpy(szQuery, szAddress);
    strcat(szQuery, "\r\n");

    wxIPV4address addr;
    addr.Hostname(host);
    addr.Service(43);

    wxSocketClient socket;
    socket.Connect(addr, false);
    socket.WaitOnConnect(30);

    if (socket.IsConnected())
    {
        socket.Write(szQuery, strlen(szQuery));
        szResult = "";
        while (true)
        {
            // Clear buffer before each iteration
            memset(szBuffer, 0, 512);

            // Try to receive some data
            socket.Read(szBuffer, 500);
            if (socket.LastCount() <= 0)
            {
                break;
            }
            szResult += szBuffer;
        }
    }
    else
    {
        szResult = wxT("Failed! Unable to connect\n");
    }
    socket.Close();

    return wxString::FromUTF8(szResult);
}

常用的WHOIS服务器:

1
2
3
4
5
.cn=whois.cnnic.net.cn
.com=whois.internic.com
.net=whois.internic.com
.name=www.whois.name
.cc=whois.nic.cc