乐者为王

Do one thing, and do it well.

使用Fleximage上传图片时的问题

因为只是处理图片,所以选择了Fleximage,一个单纯的上传和处理图片的Rails插件。本来是想用Fleximage和MiniMagick的,据说RMagick内存泄露的问题比较厉害,作为替代品MiniMagick不存在内存泄露的问题,因Fleximage不能指定processor而作罢。

Fleximage项目的Wiki上有些资料已经过时了。它说渲染图片的时候要创建flexi文件,还要修改相应的action方法,添加format.image_type到respond_to代码块中,并且在view中使用formatted_photo_path来显示图片,但事实上formatted_photo_path已经被废弃了(起先也没发现,直到为了解决一个问题查看log后才知道),可以直接使用image_tag或photo_url。或者干脆不建什么flexi文件,也不添加format.image_type到action中,直接就在view中使用embedded_image_tag方法,也可以渲染图片的。

sqlite3-ruby和uninitialized constant Encoding

Ruby 1.8.6 + Rails 2.3.5

创建项目后在运行rake db:migrate时出现了以下错误:

1
2
3
4
5
6
** Invoke db:migrate (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute db:migrate
rake aborted!
uninitialized constant Encoding

具体错误信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/active_support/dependencies.rb:443:in 'load_missing_constant'
/active_support/dependencies.rb:80:in 'const_missing'
/active_support/dependencies.rb:92:in 'const_missing'
/sqlite3-0.0.8/lib/sqlite3/encoding.rb:9:in 'find'
/sqlite3-0.0.8/lib/sqlite3/database.rb:66:in 'initialize'
/active_record/connection_adapters/sqlite3_adapter.rb:13:in 'new'
/active_record/connection_adapters/sqlite3_adapter.rb:13:in 'sqlite3_connection'
/active_record/connection_adapters/abstract/connection_pool.rb:223:in 'send'
/active_record/connection_adapters/abstract/connection_pool.rb:223:in 'new_connection'
/active_record/connection_adapters/abstract/connection_pool.rb:245:in 'checkout_new_connection'
/active_record/connection_adapters/abstract/connection_pool.rb:188:in 'checkout'
/active_record/connection_adapters/abstract/connection_pool.rb:184:in 'loop'
/active_record/connection_adapters/abstract/connection_pool.rb:184:in 'checkout'

http://stackoverflow.com/questions/1797199/uninitialized-constant-encoding-with-sqlite3-ruby-on-windows 查到原因说是因为安装了非sqlite3-ruby包所导致。记起安装SQLite时使用的命令是gem install sqlite3,而实际上sqlite3包仅是针对ruby 1.9版本,在1.8.6上需要使用sqlite3-ruby包。用gem list查看发现果然安装的是sqlite3包。gem uninstall sqlite3把它卸载掉,然后装上sqlite3-ruby包,再次运行rake db:migrate,可以了,不再出现错误信息了。

使用CSS创建可伸缩的面包屑

面包屑是一个用来描述层次性链接的术语,告诉用户当前处于网站的什么位置。简单的来说,就是显示从主页到当前页面路径的导航模式。其最简单的形式通常是这样的:

有许多种不同的方法来实现面包屑。在这里,我们仅使用无序列表和基本的CSS样式。

以下是HTML代码:

1
2
3
4
5
6
7
<ul id="breadcrumbs">
  <li><a href="#">Home</a> > </li>
  <li><a href="#">Main</a> > </li>
  <li><a href="#">Sub</a> > </li>
  <li><a href="#">Sub sub</a> > </li>
  <li>Current page</li>
</ul>

在上述的例子中,除了用户当前所在的页面——即最后一个条目外,其它所有的条目都是链接,指向路径中的某个页面。

如果你试着在浏览器中查看上面的这个例子,你会发现它只是一个简单的无序列表。必须给它添加一些CSS样式才能符合我们的预期。

下面是CSS代码:

1
2
3
4
5
ul, li {
  list-style-type: none;
  padding: 0;
  margin: 0;
}

首先我们要重置基本列表样式的行为:把默认的圆形条目符号隐藏掉,并移除缩进。

1
2
3
4
5
6
7
8
9
10
#breadcrumbs {
  height: 2.3em;
}

#breadcrumbs li {
  float: left;
  line-height: 2.3em;
  padding-left: .25em;
  color: #777;
}

然后,给面包屑设置一个具体的高度。因为所有的碎屑需要保持在同一水平线,所以我们把每个li元素设置成float: left。为保证条目的垂直居中对齐,需要把条目的行高设置成和列表相等。碎屑之间需要留些空间,不然的话就会太拥挤,因此我们给每个碎屑左边加上0.25em的填充。

1
2
3
4
5
6
7
8
#breadcrumbs li a:link, #breadcrumbs li a:visited {
  text-decoration: none;
  color: #777;
}

#breadcrumbs li a:hover, #breadcrumbs li a:focus {
  color: #dd2c0d;
}

最后,给文本链接添加样式,使用text-decoration: none隐藏掉下划线。

Android下显示本地Contact Number信息(续)

前文的代码只能显示联系人的第一个号码,很不方便。如果要列出联系人的所有号码,需要将People.CONTENT_URI改为Phones.CONTENT_URI。具体代码如下:

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
import android.app.Activity;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Contacts.People;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.contact_list);

        ListView contactList = (ListView)findViewById(R.id.contactList);

        Cursor contactsCursor = managedQuery(Phones.CONTENT_URI,
                null, null, null, null);
        startManagingCursor(contactsCursor);

        String[] columnsToMap = new String[] {Phones.NAME, Phones.NUMBER};
        int[] mapTo = new int[] {R.id.contactName, R.id.contactNumber};

        SimpleCursorAdapter mAdapter = new SimpleCursorAdapter(this,
                R.layout.contact_entry, contactsCursor,
                columnsToMap, mapTo);
        contactList.setAdapter(mAdapter);
    }
}

如何获取Android手机的型号

怎么样才能获的Android手机的型号呢?例如HTC Hero或HTC Magic等名字。android.os.Build类有很多静态属性可以获得机器的信息,其中的MODEL属性就指代手机型号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView tv = new TextView(this);
        tv.setText("Product Model: " + Build.MODEL);
        setContentView(tv);
    }
}

在我的机器上显示的是:

1
Product Model: HTC Hero

Android下显示本地Contact Number信息

首先要在AndroidManifest.xml中设置允许读取联系人的权限:

1
<uses-permission android:name="android.permission.READ_CONTACTS" />

联系人列表布局文件contact_list.xml:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="vertical"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent">
    <ListView android:id="@+id/contactList"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

联系人列表项布局文件contact_entry.xml:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent">
    <TextView android:id="@+id/contactName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView android:id="@+id/contactNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

MainActivity.java文件内容:

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
import android.app.Activity;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Contacts.People;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.contact_list);

        ListView contactList = (ListView)findViewById(R.id.contactList);

        Cursor contactsCursor = managedQuery(People.CONTENT_URI,
                null, null, null, null);
        startManagingCursor(contactsCursor);

        String[] columnsToMap = new String[] {People.NAME, People.NUMBER};
        int[] mapTo = new int[] {R.id.contactName, R.id.contactNumber};

        SimpleCursorAdapter mAdapter = new SimpleCursorAdapter(this,
                R.layout.contact_entry, contactsCursor,
                columnsToMap, mapTo);
        contactList.setAdapter(mAdapter);
    }
}

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

在这以前我们把tags作为Entry模型的一个字段来实现,这样做有很大的局限性,而且还有好多插件可以实现这个功能,现在我们就来把它替换用插件acts_as_taggable_on_steroids实现。

安装插件

1
2
3
script/plugin install git://github.com/jviney/acts_as_taggable_on_steroids.git
script/generate acts_as_taggable_migration
rake db:migrate

在Entry模型中添加一句代码:

1
2
class Entry < ActiveRecord::Base
  acts_as_taggable

还要将所有视图中原来的tags字段替换成tag_list字段。

1
2
# index.html.erb
<%=h entry.tag_list %>
1
2
# edit.html.erb
<%= f.text_field :tag_list %>
1
2
# new.html.erb
<%= f.text_field :tag_list %>

还要将Entry模型中的验证字段tags改成tag_list。做完这些插件也就安装完成了。

既然用了插件,那么原来的tags字段就不需要了,写个迁移任务把它处理掉吧!

1
script/generate migration remove_tags_from_entries

迁移代码如下:

1
2
3
4
5
6
7
8
9
class RemoveTagsFromEntries < ActiveRecord::Migration
  def self.up
    remove_column :entries, :tags
  end

  def self.down
    add_column :entries, :tags, :string
  end
end

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

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

当网站出现问题时,让它发送一份错误报告到你的邮箱是件挺不错的事,这样你就不必再担心不能及时发现问题了。

exception_notification插件的功能就是当你的Rails应用出错时,它会向指定的邮箱地址发送错误日志。

使用很简单,安装插件后配置一下邮件接受者就可以了。

1
script/plugin install git://github.com/rails/exception_notification.git

在config/environment.rb中的最后添加邮件接收者的地址:

1
ExceptionNotifier.exception_recipients = %w(yourname@example.com)

然后告诉exception_notification哪些控制器出错才发送日志。当然是全部啦:

1
2
class ApplicationController < ActionController::Base
  include ExceptionNotifiable

exception_notification采用ActionMailer发送邮件,所以使用的前提是确保ActionMailer可以正常发送邮件。还有就是exception_notification默认只在production环境下才会生效,如果想要在development下也生效,需要将config/environments/development.rb文件中的:

1
config.action_mailer.raise_delivery_errors = false

修改为

1
2
3
config.action_mailer.raise_delivery_errors = true
# Let exception_notification generate email notifications
config.action_controller.consider_all_requests_local = false

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

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

从2.2版本起Rails开始内置支持i18n,因此以后实现国际化/本地化就可以不再需要各种各样的插件了。

Rails默认的Locale文件夹是config/locales,假设你要支持中文和英文,那么你需要在这个文件夹下放置zh.yml和en.yml两个文件。

相应的入门教程网上有不少,我也就不多讲了。这里主要说一下如何在记账应用中实现可以让用户指定语言的i18n实现,即当用户选择English,那么界面就切换成英文界面,并且以后打开的页面也是以英文出现,反之亦如此。

首先在header区添加以下代码:

1
2
<%= link_to 'Chinese', :locale => 'zh' %>
<%= link_to 'English', :locale => 'en' %>

点击其中某个链接后,浏览器就会传递对应的参数zh或en到后台。

然后在app/controllers/application_controller.rb中添加以下代码:

1
2
3
4
5
6
before_filter :set_locale

protected
  def set_locale
    I18n.locale = params[:locale]
  end

不过这里有个问题就是url中必须得带着参数,不然的话用户的选择就会失效。要想使选择达到持续的效果,可以考虑把这些信息保存在session中,改进后的代码如下:

1
2
3
4
def set_locale
  I18n.locale = params[:locale] || session[:locale]
  session[:locale] = I18n.locale
end

现在这样就基本上差不多了。剩下的就是处理第一次访问时的语言,这个可以从request的ACCEPT_LANGUAGE参数中获取。最终实现的代码:

1
2
3
4
5
def set_locale
  accept_lang = request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
  I18n.locale = params[:locale] || session[:locale] || accept_lang || 'zh'
  session[:locale] = I18n.locale
end

不过当用户选择中文后,在新建记录时会报以下错误:

1
2
3
4
5
6
7
8
can't convert Symbol into String

Extracted source (around line #8):

    6:   <p>
    7:     <%= t(:effective_date) %><br />
    8:     <%= f.date_select :effective_date %>
    9:   </p>

查找资料后发现可以通过在date_select中加入order来解决,不过日期下拉列表中的月份还有问题,出现的是一些随机数,不是正常月份。这个也可以通过添加month_names参数解决。

1
2
<%= t(:effective_date) %><br />
<%= f.date_select :effective_date, :order => [:year, :month, :day] %>

更好的办法是把它们放到对应的Locale文件里:

1
2
3
4
5
# en.yml
en:
  date:
    month_names: [~, January, February, ..., November, December]
    order: [:year, :month, :day]
1
2
3
4
5
# zh.yml
zh:
  date:
    month_names: [~, 一月, 二月, ..., 十一月, 十二月]
    order: [:year, :month, :day]

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

如何导入/导出Heroku中应用的数据

导入/导出Heroku上应用的数据需要安装Taps包:

1
gem install taps

导入数据到Heroku里:

1
heroku db:push

导出数据到本地:

1
heroku db:pull

其中导入/导出数据由config/database.yml配置设定。

Heroku还支持主机到主机的数据库传输,具体细节可以查看 http://docs.heroku.com/taps 文档。