乐者为王

Do one thing, and do it well.

随机化Paperclip的上传文件名

有时我们希望可以设定上传文件名的格式。一是可以统一文件名,而不是一些乱七八糟的名字;二是可以让文件名不是那么容易被猜测出来。下面的这段代码就是在网上找到的:

1
2
3
4
5
6
7
8
9
10
11
class Photo < ActiveRecord::Base
  has_attached_file :image, :url => "/uploads/:basename.:extension"

  before_create :randomize_file_name

  private
  def randomize_file_name
    extension = File.extname(image_file_name).downcase
    self.image.instance_write(:file_name, "#{Time.now.strftime("%Y%m%d%H%M%S")}#{rand(1000)}#{extension}")
  end
end

代码是从 http://trevorturk.com/2009/03/22/randomize-filename-in-paperclip/ 找到的,这里我把随机参数给改了,这样文件名的格式就类似20110310095632768这样。还有要注意的就是在:url中必须使用:basename参数,因为修改的:file_name就是它。

Rails 2.3和Paperclip实现无刷新异步上传文件

1
2
3
4
5
6
7
rails upload
cd upload
script/plugin install git://github.com/thoughtbot/paperclip.git
script/plugin install git://github.com/markcatley/responds_to_parent.git
script/generate scaffold user name:string
script/generate paperclip user avatar
rake db:migrate

在user.rb中增加:

1
2
3
4
5
  has_attached_file :avatar, :url => "/uploads/:basename.:extension"
  validates_attachment_presence :avatar
  validates_attachment_content_type :avatar,
    :content_type => ['image/jpeg', 'image/jpg', 'image/png']
  validates_attachment_size :avatar, :less_than => 1.megabytes

修改views/users/new.html.erb文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<% form_for(@user, :url => users_path(:format => 'js'),
      :html => { :id => 'upload_form', :multipart => true, :target => 'uframe' }) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.label :avatar %><br />
    <%= f.file_field :avatar %>
    <div id="avatar">
    <% if @user.avatar.exists? %>
      <%= image_tag @user.avatar.url(:medium) %>
    <% end %>
    </div>
  </p>
  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>
<iframe id="uframe" name="uframe" style="display: none;"></iframe>

当点击submit按钮后,表单被提交到隐藏的iframe,相应的action被执行,执行结果会被返回给隐藏的iframe,我们需要上传后结果能返回到母窗口,这就是responds_to_parent插件提供的功能。

在views/layouts/user.html.erb中增加:

1
<%= javascript_include_tag :defaults %>

修改users_controller.rb中的create方法,增加:

1
2
3
4
5
6
7
8
9
format.js do
  flash[:notice] = 'avatar created'
  responds_to_parent do
    render :update do |page|
      page.replace_html :avatar, image_tag(@user.avatar.url(:medium))
      page['upload_form'].reset
    end
  end
end

restful_authentication实现用email登录

如果要使用用户名和email并行的登录方式,那么只要在models/user.rb中添加一行代码就可以了:

1
2
3
4
5
6
def self.authenticate(login, password)
  return nil if login.blank? || password.blank?
  u = find_by_login(login.downcase)  # need to get the salt
  u ||= find_by_email(login.downcase)  # 有了这行代码就可以使用email登录了
  u && u.authenticated?(password) ? u : nil
end

假如要使只能用email登录,就要先移除models/user.rb中的下列代码:

1
2
3
validates_presence_of :login
validates_length_of :login
validates_uniqueness_of :login

然后修改self.authenticate方法为:

1
2
3
4
5
def self.authenticate(email, password)
  return nil if email.blank? || password.blank?
  u = find_by_email(email.downcase)  # need to get the salt
  u && u.authenticated?(password) ? u : nil
end

接着将注册页面views/users/new.html.erb中的login块移除,还有就是要将相关邮件模板中的@user.login改为@user.email。

经过这些修改后就实现了只能使用email登录的功能。

uninitialized constant Typus::Authentication::ByPassword

使用的Rails版本是2.3.5,所以在安装typus的时候需要加上-r 2-3-stable。

1
script/plugin install git://github.com/fesplugas/typus.git -r 2-3-stable

在安装完后执行script/generate typus出现了下面的错误:

1
uninitialized constant Typus::Authentication::ByPassword

在网上找到的答案是因为和restful_authentication中Authentication模块有冲突,可以将app/models/user.rb中的:

1
2
3
include Authentication
include Authentication::ByPassword
include Authentication::ByCookieToken

改成

1
2
3
include ::Authentication
include ::Authentication::ByPassword
include ::Authentication::ByCookieToken

再次执行generator时提示:

1
undefined method 'login_regex' for Typus::Authentication:Module

经过再三查找,原来是user.rb文件中还有别的调用Authentication的地方,全部加上::符号,问题解决了。

使用tabnav插件实现Tab导航

tabnav是rails-widgets中的一个组件。不过我们不需要使用其它的组件,所以我们只要使用omenking创建的tabnav就可以,他已经帮我们把tabnav从rails-widgets中分离出来了。

下载插件:

1
script/plugin install git://github.com/omenking/tabnav.git

生成views/shared/_tabnav.html.erb文件:

1
script/generate tabnav tabnav

添加一个标签页到_tabnav.html.erb中,并且把里面显示每个控制器的代码注释掉:

1
2
3
4
add_tab do |t|
  t.named 'Home'
  t.links_to :controller => 'home', :action => 'index'
end

再添加以下代码到tabs.css文件做些许修饰:

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
44
.tabnav {
  background-color: #fff;
  padding: 5px;
  height: 2em;
  font-size: 10pt;
}

.tabnav ul {
  height: 2em;
  margin: 0;
  padding: 0;
}

.tabnav li {
  display: inline;
  line-height: 1.167em;
  padding: 0;
}

.tabnav li a {
  background: #5ec998;
  color: #ebebeb;
  text-decoration: none;
  border: 0.083em solid #fff;
  border-bottom: 0.083em solid #5ec998;
  padding: 0.5em 0.667em 0.417em 0.5em;
  float: left;
}

.tabnav li a.active {
  color: #ebebeb;
  font-weight: bold;
  background: #004000;
  border: 0.083em solid #fff;
  border-bottom: 0.083em solid #004000;
}

.tabnav li a:hover {
  color: #004000;
}

.tabnav li a.active:hover {
  color: #ebebeb;
}

Invalid char '\xxx' in expression

Ruby文件中包含中文字符串,编译时提示错误:

1
2
3
Invalid char '\243' in expression
Invalid char '\254' in expression
Invalid char '\273' in expression

在网上搜索原因是因为Ruby不支持带BOM的UTF-8文件(用16进制编辑器打开会发现最前面有EF BB BF三个隐藏字符)。我用的编辑器是Notepad++,它有个功能是Encoding in UTF-8 without BOM,先删除Ruby文件中的中文字符串,然后选中该选项,再输入中文字符串后编译就可以了。

为Rails应用添加feedback功能

首先,将Rails中的Prototype替换为jQuery。接着安装feedback插件:

1
script/plugin install git://github.com/jsboulanger/feedback.git

生成代码,由于我们用jQuery替换了Prototype,所以要在后面加上--jquery选项:

1
script/generate feedback_form --jquery

在app/views/layouts/application.html.erb中添加:

1
<%= javascript_include_tag :defaults %>

在要添加feedback的页面中加上以下代码:

1
<%= feedback_tab(:position => 'top') %>

然后修改app/models/feedback_mailer.rb中的设置:

1
2
3
4
def feedback(feedback)
  @recipients = 'webmaster@example.com'
  @from = 'noreply@example.com'
  @subject = "[Feedback for example.com] #{feedback.subject}"

这样feedback功能就加上了。

为Rails应用增加RSS输出功能

在EntriesController中增加feed方法,内容如下:

1
2
3
4
5
6
def feed
  @entries = Entry.all
  respond_to do |format|
    format.rss { render :layout => false }  # feed.rss.rxml
  end
end

在app/views/entries中增加feed.rss.rxml视图模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
xml.instruct! "xml", :version => "1.0"
xml.rss "version" => "2.0" do
  xml.channel do
    xml.title "example.com Entries"
    xml.description "Listing Entries for example.com"
    xml.link entries_url

    @entries.each do |entry|
      xml.item do
        xml.title entry.title
        xml.link entry_url(entry)
        xml.description entry.content
        xml.guid entry_url(entry)
      end
    end
  end
end

然后在config/routes.rb中添加:

1
map.feed '/feed', :controller => 'entries', :action => 'feed'

这样,当访问http://example.com/feed时就可看到所有内容的RSS输出了。

用simple_captcha插件实现验证码

simple_captcha是一个可以帮我们在Rails中轻松实现验证码功能的插件,它使用简单,并且支持图片和数字验证。

安装插件

1
2
3
script/plugin install git://github.com/eshopworks/simple_captcha.git
rake simple_captcha:setup
rake db:migrate

在config/routes.rb中添加:

1
map.simple_captcha '/simple_captcha/:action', :controller => 'simple_captcha'

在app/controllers/application_controller.rb中添加:

1
2
ApplicationController < ActionController::Base
  include SimpleCaptcha::ControllerHelpers

验证方式有两种:基于控制器和基于模型。这里使用后者作为示例。

在app/views/entries/new.html.erb的form中加上:

1
<%= show_simple_captcha(:ojbect => 'entry', :label => 'Please type the text from the image.') %>

在app/models/entry.rb中添加:

1
2
class Entry < ActiveRecord::Base
  apply_simple_captcha

将app/controllers/entries_controller.rb中的create方法里的

1
if @entry.save

改为

1
if @entry.save_with_captcha

2011/2/17更新

当看不清楚,需要换个验证码时稍麻烦,需要借助Ajax实现,simple_captcha没有提供这个功能,还好小日本写好了一段代码可以参考:http://d.hatena.ne.jp/kusakari/20080130/1201666383 。不过可惜使用的是Prototype库,花了点时间将它改成了jQuery代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="captcha_image">
  <%= show_simple_captcha(:object => 'entry', :label => 'Please type the text from the image.') %>
</div>
<a href="javascript:void(0);" id="recognize_captcha">I cannot recognize.</a>
<script type="text/javascript">
  $("#recognize_captcha").click(function() {
    $.ajax({
      url: "<%= url_for(:action => 'update_captcha') %>",
      success: function(response) {
        $("#captcha_image").html(response);
      }
    });
  });
</script>

在app/controllers/entries_controller.rb中加上:

1
2
3
def update_captcha
  render :layout => false
end

创建app/views/entries/update_captcha.html.erb,代码如下:

1
<%= show_simple_captcha(:object => "entry", :label => 'Please type the text from the image.') %>

还有就是要在routes.rb中添加个映射,不然会找不到action:

1
map.update_captcha '/update_captcha', :controller => 'entries', :action => 'update_captcha'

is not recognized by the 'identify' command

使用Paperclip时遇到一个问题,在上传图片是一直提示图片临时文件is not recognized by the 'identify' command,检查发现临时文件没有问题,有资料说使用Paperclip.options[:command_path]变量可以解决,试过后无效。

查看日志文件看到有如下错误信息:

1
An error was received while processing: #<Paperclip::NotIdentifiedByImageMagickError

根据这条信息找到了解决方法,就是在has_attached_file后将whiny属性设置为false:

1
:whiny => false