rails

    Migrate rails-bestpractices.com to rails4

    14 Jul 2013

    These 2 weeks I migrated rails-bestpractices.com to rails 4 from rails 3.2.13. Here are some experience I'd like to share with you.

    Make sure you have good test code

    rails-bestpractices.com has many rspec and cucumber test code, they can find out most of warnings and errors after migration.

    Update Gems

    First, update rails to 4.0.0 in Gemfile, but soon you will find you have to update many gems, devise, compass-rails, cucumber-rails, etc., some are rc version or raisl4 branch,

    You also need to remove some gems, like strong_parameters and turbo-sproc...

    Read More

    Tags 


    How to render, upload and download large files on heroku with s3

    18 Jun 2013

    I'm consulting on a rails project on heroku, it involves generating a large pdf for customer, so you must already guess it led to 30s timeout on heroku.

    At first, I handled it with common sense, moving pdf render to a background job, in the client side, it polls the status of bj, if job is complete, then render the pdf.

    Everything works fine on my laptop, but after pushing to heroku, it succeed to running then job, polling the status, but finally it can't find the generated pdf. Then I realized web dyno and worker dyno are runni...

    Read More

    Tags 


    my railsconf 2012 video

    14 Jun 2012

    This is my railsconf 2012 video on youtube.

    Read More

    Tags 


    bullet 4.0.0 released

    09 May 2012

    bullet is designed to help you reduce the number of db queries, such as adding eager loading to kill n+1 queries and removing unused eager loadings.

    bullet works well in activerecord from 2.1 to 3.2 before, today I released bullet 4.0.0, it starts to support mongoid (>= 2.4.1) now.

    Why does bullet need to support mongoid? Does mongo also have n+1 queries issue?

    The answer is yes, check out the performance metric of mongoid eager loading, about 40% pe...

    Read More

    Tags 


    my presentation on railsconf 2012

    03 May 2012

    I attended and spoke at railsconf 2012 last week, the following is my presentation

    If you have any questions or suggestions, feel free to email me, tweet me or open issues on github.

    Read More

    Tags 


    master slave replication in rails

    28 Mar 2012

    Introduction

    By default activerecord works well with single db, it's applicable for most of websites with small/medium traffic, but if you website grows fast and gets much more reads than writes, you should definitly set up master slave replication for your databse. All inserts/updates are sent to master db, and reads are sent to slave db, it will reduce read load on your master db.

    Master slave replication allows to set up as many slave dbs as you need, it's scalable, that means you can easily increase you db read throughput by adding more...

    Read More

    Tags 


    bullet 2.3.0 released

    25 Mar 2012

    bullet is a gem to help you increase your application's performance by reducing the number of sql requests it makes. Today I released bullet 2.3.0 to better support rails 3.1 and 3.2 and performance improved. It's a long time I didn't do any changes to bullet, let me tell you the story I work for bullet 2.3.0.

    At the beginning of this month, bullet got its 1000th watcher on github, I realized it's time to improve it e.g. speed up and compatible with edge rails.

    The first thing I did is to refactor tests. Before I created several rspec tests, but they are more like integration tests instead of unit tests, so I move them to spec/integration/ directory. Then I added a bunch of test units to cover all codes, which can promise the correctness of furth...

    Read More

    Tags 


    multiple_mailers - send emails by different smtp accounts

    21 Mar 2012

    I use gmail to send email notifications on my website, it's really easy to build based on actionmailer

    ActionMailer::Base.smtp_settings = {
      :address => 'smtp.gmail.com',
      :port => 587,
      :domain => 'railsbp.com',
      :authentication => :plain,
      :user_name => 'notification@railsbp.com',
      :password => 'password'
    }

    But I found it does not allow to setup 2 different smtp accounts, e.g. I wa...

    Read More

    Tags 


    passenger with http_gzip_static_module

    27 Feb 2012

    Rails 3.1 has been released for a long time, asset pipeline becomes more and more popular, I also upgraded my rails website.

    I used nginx + passenger for my rails projects, but nginx only supports dynamic gzip support (compress in runtime), there is a http_gzip_static_module for nginx, which can make full use of rails asset pipeline.

    I don't like the way to customize my Nginx installation during passenger installation, I found there is a pull request to add http_gzip_static_module, so I changed to source code of passenger...

    Read More

    Tags 


    after_commit

    06 Nov 2011

    We are using RabbitMQ as our message queue system, ruby client is workling. This week we encountered a strange issue, we create a notification, and define an after_create callback to ask workling to find that notification and then push the notification to twitter or facebook, it works fine except that sometimes it will raise an error said "can't find the notification with the specified ID"

    class Notification < ActiveRecord::Base
      after_create :asyns_send_notification
      ......
      def async_send_not...
    Read More

    Tags 


    reset_counters in rails

    31 Oct 2011

    I thought reset_counters method is to reset a counter_cache column to be 0, but it is not. After trying several times, I finally realize that reset_counters is to update the value of counter_cache column to the exact count of associations. The usecase of reset_counters is when you add the counter_cache in migration and update the counter_cache value, like

    def self.up
      add_column :posts, :comments_count
      Post.all.each do |post|
        Post.reset_counters(post.id, :comments)
      end
    end

    it will add comments_count column to posts table, and calculate the comments count for each post, and set it to posts' comments_count...

    Read More

    Tags 


    rubykaigi presentation

    17 Jul 2011

    My presentation in RubyKaigin 2011 today.

    and the video is here: http://www.ustream.tv/recorded/16051491

    Read More

    Tags 


    将table的数据导出为csv

    20 Jul 2010

    项目中经常会有这样的usecase,把一个table中的数据导出为csv,用fastercsv这个gem可以快速完成这个功能。

    首先放一个导出csv的链接

    = link_to 'Export to CSV', participants_path(:format => :csv)

    然后在controller中生成相应的csv,并发送给用户

    def index
      @participants = Participant.all
    
      respond_to do |format|
        format.csv {
          participants_csv = FasterCSV.generate do |csv|
            csv  ["First Name", "Last Name", "Age", "Gender", "Address", "Phone", "Email"]
            @participants.each do |p|
              csv  [p.first_name, p.last_name, p.age, p.gender, p.address...
    Read More

    Tags 


    nginx上传进度条

    05 Jun 2010

    项目中经常需要应用的功能之一就是文件上传,一般对于小文件来说不需要特别的处理,但是一旦碰到允许大尺寸文件上传的时候,用户常常会被长时间的没有变化的上传过程而迷惑,这种时候就需要一个上传进度条来提醒用户。比如我最近一个项目允许用户上传100M的视频文件,上传的过程往往需要持续10多分钟,这种情况如果没有进度条的话,用户可能会以为系统出问题了。项目部署的环境为nginx+lighttpd,上传的过程是这样的:

    1. 用户选择上传的视频,点击提交按钮

    2. nginx将视频文件的二进制数据保存为/tmp目录下面的某个文件

    3. lighttpd执行rails的代码对/tmp目录下面的上传文件进行处理

    由此可见,上传视频的过程都是由nginx进行处理的,lighttpd并不知情,它只能通过上传表单了解到上传到/tmp目录下的文件名,这样的好处是,费时的上传过程并不会消耗rails进程。所以我们在做上传进度条的时候就要在nginx身上下功夫了。

    google了一下,发现网上已经有了解决方案,http://github.com/drogus/jquery-upload-progress

    同时修改nginx配置文件 ,增加...

    Read More

    Tags 


    追踪图片在外部系统的查看次数

    20 May 2010

    随着sns网站和web api的兴起,与第三方网站的交互越来越多。比如我们可以向用户facebook好友上的wall推送数据等等来做网站的推广,这就引来一个问题,我们不能只傻乎乎地做推广,更重要的是统计推广的效果。你总共推送了多少数据,有多少人看到了这些数据,又有多少人点击来到了你的网站?这些数据都是可以用来帮助你改进和提高推广的效果。

    对于推送了多少数据和有多少人看到了这些数据,这两者比较容易做到,前者可以在推送数据的时候做记录,后者可以在用户点击进入网站的时候做记录。对于有多少人看到了这些数据就比较麻烦了。

    下面拿facebook为例介绍如何统计数据在外部系统的查看次数。如果是纯文字的话几乎没法做到,但是如果是推送图片或视频的话,可以通过图片的显示次数来统计数据。

    一般往facebook推送图片或视频等图片的时候,只需要在推送的参数中设置图片或视频的完整url即可。然后facebook在显示图片或视频的时候,会发送请求到服务器上,你只需...

    Read More

    Tags 


    为resque写扩展

    13 May 2010

    resque是基于redis的ruby类库,用于创建后台任务,把这些后台任务放在多个队列中去,之后在处理它们。github就是使用resque来处理它们的后台任务的。

    对于需要长时间处理的任务,比如发送email,发tweet,图片resize等等,都是resque的用武之地。默认resque就是将任务加到redis的队列中去,然后定时取出来去处理,实际项目中我们往往需要对其增加额外的扩展,比如你需要增加日志功能,增加处理次数的限制,这个时候就可以给resque写一个plugin,就像rails的plugin一样。

    resque定义了非常良好的HOOK,使得为其写扩展变得更加容易。

    resque采取的是每隔n秒从队列中获取一个任务,然后fork一个子进程来执行这个任务。resque定义了before_fork, after_fork, before_perform, after_perform, around_perform, on_failure几个hook,执行顺序如下

    1. before_fork

    2. fork

    3. after_fork

    4. before_perform

    5. around_p...

    Read More

    Tags 


    css sprite best practices

    03 Apr 2010

    css sprite最佳实践(中文版)

    (Updated on 2010-4-4, thank Scott Ballantyne )

    The advantage of using the css sprite is to reduce a large number of http requests, so it makes the web page loaded much faster. I often find it it painful for me to compose a lot of images into one css sprite image and measure the x and y positions for each image.

    Last year, I wrote a css_sprite gem, but to use it you need to define all the images you want to do the css sprite in the configuration file, and it is not easy to use. Because of this, recently I rewrote the css_sprite gem, it is not necessary to use configuration file any more by default, th...

    Read More

    Tags 


    css sprite最佳实践

    02 Apr 2010

    css sprite best practices (english version)

    应用css sprite的好处在于可以大量减少http请求数,从而达到更快加载页面的效果。

    但是对于像我这样的懒人,你让我每次都一个一个把图片copy到一个css_sprite图片里,还得量一下这个每个图片对应的x和y坐标,实在是一种折磨。

    去年我就写了一个css_sprite的插件,但是由于需要在配置文件中定义所有需要组合在一起的图片,用起来还是很麻烦,不够傻瓜化。最近我把css_sprite插件重写了一遍,默认不需要使用配置文件,遵循rails的Convention Over Configuration的思想,可以做到全自动的css sprite操作。

    首先,让我们看看目录结构的Convention是如何定义的

    上图中蓝色部分就是Convention的css sprite目录,也就是在public/images目录下面的css_sprite目录或者以css_sprite结尾的目录(比如another_css_sprite),需要执行css_sprite操作。

    绿色部分则是需要被css sprite的图片,你可以动态的在css sprite目录下面增加或删除图片,css sprite操作...

    Read More

    Tags 


    类似facebook connect的方式验证twitter oauth

    31 Mar 2010

    最近一个项目需要实现类似与uservoice一样的widget,也就是把一段javascript放到任何的网站上,然后动态生成一个iframe来显示我们网站的内容。但是碰到一个问题,在这个widget内需要允许用户使用twiiter oauth的方式登录,但是twitter oauth认证之后会使用window.top来redirect你的页面,这样会重置我们的widget,这显然是对用户很不友好的。同时,我发现facebook connect的方式可以很好的应用在我们的widget上面,因为它不会重新刷新页面。于是我想能不能用类似与facebook connet的方式,弹出一个页面来做twitter oauth的身份验证呢?显然,这是可行的。

    其实很简单的,就是弹出一个页面,在那个页面上做twitter oauth的身份认证,在返回的时候记录session,同时关闭弹出的页面,javascript的代码如下

    if (!TwitterConnect) {
      var TwitterConnect = {};
    }
    TwitterConnect.Twitter = new func...
    Read More

    Tags 


    使用princely生成pdf

    24 Mar 2010

    Prince是一个将html和xml转换为pdf的程序,最突出的特点是prince能够根据css来格式化转换之后的pdf,这实在是太适合web程序员了。princely是一个基于prince的rails插件,使用起来也非常方便。

    首先,下载prince并按照文档进行安装。

    其次,安装princely

    sudo gem install princely

    接着就是在rails项目中生成pdf并供用户下载。定义一个名字叫download的action

    def download
      # any logic
    
      respond_to do |format|
        format.html
        format.pdf do
          render :pdf => "pdf_file_name",
                 :stylesheets => "pdf_css"
        end
      end
    end

    然后就是定义download.pdf.erb文件,它就和平时定义html.erb是一样的,样式由pdf_css.css决定。

    这样,当用户点击一个链接进入这个download action,服务器就会在后台生成pdf,并发送response给用户,用...

    Read More

    Tags 


    通过paperclip上传视频并转换为flv格式

    17 Mar 2010

    paperclip作为一个非常流行的文件上传的rails插件,最常被应用的就是上传图片,裁剪,格式转换等等,网上的demo也是一大堆,但是介绍视频上传的却很少。其实paperclip的处理模块定义非常清晰,可以很方便的实现视频的格式转换。

    我们的案例是这样的:用户上传任何视频文件,我们都将其转换为flv格式,然后再显示在网页上。

    首先,安装ffmpeg,所有的转换工作都是使用ffmpeg命令来执行的。安装文档网上有很多,这里就不重复了。不过我在mac下面用port安装之后,转换视频的时候总是报Audio encoding failed错误,需要将ffmpeg依赖的lame降版本到3.97_0。另外你需要安装paperclip插件。

    接着,新建/lib/paperclip_processors目录,同时在该目录下新建flash.rb文件

    module Paperclip
      class Flash  Processor
    
        attr_accessor :geometry, :file, :whiny
    
        def initialize(file, options = {},...
    Read More

    Tags 


    rails and facebook connect

    05 Mar 2010

    最近项目需要做一些facebook应用,比如要允许用户登录facebook,要获取用户facebook的好友信息等等。登录facebook自然选用时下流行的facebook connect。代码写起来非常简单,用户的体验也非常好。

    首先,进入facebook的开发者页面http://developers.facebook.com/,点击Start building for your site,开始创建你的facebook应用,按照提示一步一步继续。需要注意的是,你可能需要创建两个应用,一个针对本地development环境,一个针对production环境。

    接着,安装facebooker的gem,并且加入到rails gem依赖。在config目录下创建facebooker.yml文件,内容为

    development:
      api_key:
      secret_key:
      canvas_page_name: localhost
      callback_url: http://localhost:3000
      pretty_errors: true
      set_asset_host_to_...
    Read More

    Tags 


    button_to的使用

    02 Mar 2010

    页面间的跳转或者请求,用得最多的就是link_to和form_for,一个发送get或delete请求,一个post或put请求。但是碰到投票之类的链接,虽然是一个post请求,但是form里面却不需要任何数据,碰到这样的情况,我们希望像link_to那样一行搞定。

    也许你会通过link_to 'xx', 'xx', :method => :delete联想到link_to 'xx', 'xx', :method => :post,但是很不幸,没有这样使用的。还好,rails提供了一个简单的helperbutton_to

    button_to 'Vote', post_votes_path(post), :class => 'vote_icon'

    生成的html代码如下

    <form class="button-to" action="/post/1/comments" method="post">
        <div>
            <input type="submit" value="Vote" class="vote_icon" />
            <input type...
    Read More

    Tags 


    paperclip和id_partition

    02 Feb 2010

    很多网站都允许用户上传文件,如何管理这些上传的文件呢?以paperclip为例,其默认文件布局结构为

    :url  => "/system/:attachment/:id/:style/:filename",
    :path => ":rails_root/public:url",

    每个id都会占据一个目录,问题是文件系统的子目录数量是有限制的,ext3是32k,ext4是64k,所以网站的数据量达到规模时,默认的文件布局并不合适。比较好的方式是采用id_partition,即把id表示成九位,并且分成3级目录,比如:

    1 => 000/000/001

    10000 => 000/010/000

    100000000 => 100/000/000

    这样就无须为文件系统的子目录数量限制担忧了。实现上同样以papaerclip为例,只需要修改其默认的配置参数

    Paperclip::Attachment.default_options.merge!(
      :path => ":rails_root/public/pictures/:class...
    Read More

    Tags 


    activerecord属性保护

    30 Jan 2010

    最近在看rails安全方面的书,第一部分就是关于生成activerecord对象的参数保护问题。平时一直使用,今天心血来潮想起要看看源代码是如何实现的。

    activerecord属性保护就是通过attr_accessible和attr_protected来声明哪些属性可以访问,哪些不可以访问。当然,这些保护只是针对new, create和update_attributes方法,对于直接使用attribute=就无能为力了。

    attr_accessible的源码为

    def attr_protected(*attributes)
      write_inheritable_attribute(:attr_protected, Set.new(attributes.map()) + (protected_attributes || []))
    end

    原来activerecord会生成一个attr_protected属性,来记录所有的需要被保护的字段

    同样attr_accessible会生成attr_accessible属性

    def attr_accessible(*attr...
    Read More

    Tags 


    rails的异常处理

    23 Dec 2009

    当在本地开发模式下发生异常的时候,rails会将错误发生的点、错误桟以及请求和应答的内容显示在浏览器上,并且在console下面打印出错误桟,这些使得调试web应用变得更容易。那rails内部是如何处理异常的呢?

    rails把与ActionController相关的异常处理都定义在了ActionController::Rescue里面。其中最关键的是

    alias_method_chain :perform_action, :rescue

    perform_action是ActionController处理http请求的方法,它负责根据不同的请求调用相应的action。而上面这句话则为perform_action添加了处理异常的功能,看看具体的实现

    def perform_action_with_rescue #:nodoc:
      perform_action_without_rescue
    rescue Exception => exception
      rescue_action(exception)
    end

    可以看到,当perform_action...

    Read More

    Tags 


    add gem rake for rails

    30 Sep 2009

    使用过rails插件的一定知道,只要在插件的tasks目录下面定义rake文件,rails就会自动加入其中定义的task。但是gem就不能这样用了,即使rake gems:unpack也没用。

    解决的方法是将定义在gem中的task require到rails目录下。比如我在css_sprite gem的lib/css_sprite.rb中定义

    unless Rake::Task.task_defined? "css_sprite:build"
      load File.join(File.dirname(__FILE__), '..', 'tasks', 'css_sprite_tasks.rake')
    end

    上面3行的意思是如果当前的tasks中没有css_sprite:build的话,就load gem中的tasks/css_sprite_tasks.rake。

    然后在rails app中增加lib/tasks/css_sprite.rake

    require 'css_sprite'

    这样你就可以使用rake css_sprite:build task了。

    更懒的方法是直接把require css_sprite'加到rails目录下的Rakefile。...

    Read More

    Tags 


    disable browser cache in rack

    15 Sep 2009

    bullet插件在浏览器cache下总是会问题,因为页面被cache了,总是返回304 Not Modified,bulletware下的代码没有执行就直接跳过了。

    之前就是在README下面写了一段,让用户把web browser cache disable掉,不过始终不是一个解决方法,今天就直接在rack下把browser cache disable掉了

    class Bulletware
      def initialize(app)
        @app = app
      end
    
      def call(env)
        return @app.call(env) unless Bullet.enable?
        ......
        no_browser_cache(headers) if Bullet.disable_browser_cache
        [status, headers, response_body]
      end
    
      def no_browser_cache(headers)
        headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-r...
    Read More

    Tags 


    Add logger to rake task

    14 Sep 2009

    在rake task中写了一个爬虫,用cron定期去爬取,但是没有任何输出,实在心里没底。于是要加入log,结果发现logger在task中没有定义,只能自己加上去了。

    task :crawl => :environment do
      RAILS_DEFAULT_LOGGER.info "crawl start"
      crawl_board('XXX')
      RAILS_DEFAULT_LOGGER.info "crawl end"
      RAILS_DEFAULT_LOGGER.flush
    end

    或者也可以在最前面定义

    RAILS_DEFAULT_LOGGER.auto_flushing = true

    如果不flush的话,是看不到日志输出的

    Read More

    Tags 


    Console Snacks[摘自Advanced Rails Recipes]

    12 Sep 2009

    上周把Advanced Rails Recipes扫了一遍,受益匪浅,尤其是关于script/console那一段,都是之前没试过的,呵呵,做个摘录。

    1. Write Console Methods

    在~/.irbrc定义ActvieRecord::Base.connection.select_all方法

    # ~/.railsrc
    def sql(query)
      ActiveRecord::Base.connection.select_all(query)
    end
    # ~/.irbrc
    if ENV['RAILS_ENV']
      load File.dirname(__FILE__) + '/.railsrc'
    end

    这样就可以在直接在script/console下面执行sql查询

    $ script/console
    >> sql 'show databases'

    2. Log to the console

    ActiveRecord Logger

    # ~/.railsrc
    def loud_logger
      set_logger_to Logger.new...
    Read More

    Tags 


    ActiveRecord destroy之后的事情

    30 Aug 2009

    一般的Rails应用都在对象destroy之后自动跳转到另一个页面,不再去关心被destroy的对象如何了。其实被destroy的对象虽然从数据库中被删除了,但仍然存在于内存当中。

    举个例子吧,比如我们做个博客系统,有文章,有评论,当我们删除一个日志的时候,需要在日志中做下记录

    class Post
      after_destroy :log
    
      def log
        Logger.create(:action => 'destroy', :object_type => self.class, :object_id => self.id, :object_value => self.title)
      end
    end

    可见,我们是在post被删除之后再做日志记录,此时我们仍然能够得到post对象,并成功记录到日志系统中去。

    如果再加些BT的需求呢,要求在日志系统中同时记录子对象(即comments对象)的type, id和value。看看很简单,但是你会不会想到,comments在post之前就被删除了,我们去哪里拿这些数据呢?答案就是内存中

    class Post
      has_many...
    Read More

    Tags 


    default_scope影响attribute的default值

    25 Aug 2009

    之前有个需求,发表的文章需要审核之后才能显示,于是在Post类中加了一个default_scope

    default_scope :order => 'updated_at desc', :conditions => {:verify => true}

    之后就发觉每次创建的post对象,其verify值总是为true,除非手动设置verify=false。当然我在migration的时候已经设置verify的default为false了。很奇怪,于是看了下rails的源代码,其中是这么定义default_scope的

    def default_scope(options = {})
      self.default_scoping << { :find => options, :create => options[:conditions].is_a?(Hash) ? options[:conditions] : {} }
    end

    这里可以看到如果default_scope的conditions是一个Hash的话,那么这个Hash会被保存起来,并在对象initialize的时候生效

    ... Read More

    Tags 


    update_attribute和update_attributes的区别

    31 Jul 2009

    update_attribute和update_attributes都是用来修改model的属性,它们区别除了一个修改单个属性,一个修改多个属性外,最重要的是update_attribute不执行validation,而update_attributes执行validation,查看源码:

    def update_attribute(name, value)
      send(name.to_s + '=', value)
      save(false)
    end
    def update_attributes(attributes)
      self.attributes = attributes
      save
    end

    可以看出来,update_attribute执行的是save(false),而update_attributes执行的是save

    Read More

    Tags 


    Rails2.3.3新功能──touch

    29 Jul 2009

    touch是Rails2.3.3引入的新功能,可以将指定的attributes改为当前时间,默认是更改updated_at或updated_on。

    典型的用法在many-to-one时,当many端发生改变时,更新one端的updated_at时间。比如在一个论坛系统中,一个帖子的更新时间会随着之后的回复发生改变:

    class Post  ActiveRecord::Base
      has_many :replies
    end
    
    class Reply  ActiveRecord::Base
      belongs_to :post, :touch => true
    end

    这里声明的:touch = true,其实就是定义了一个method来更新Post的updated_at时间,并且在after_save和after_destroy的时候调用该method

    def add_touch_callbacks(reflection, touch_attribute)
      method_name = "belongs_to_touch_after_save_or_destroy_for_#{...
    Read More

    Tags 


    Hostmonster升级到Rails2.3.3

    25 Jul 2009

    前两天Hostmonster把Rails升级到2.3.3,导致我的网站无法访问。查看日志,Dispatcher failed to catch: undefined method read' for classFCGI::Stream' (NameError),给Hostmonster提交了ticket,到现在都还没有结果,没办法,只能靠自己了。

    google了一下,可能是rack中的一段代码的问题。

    首先,安装好自己的gem repository,并且在environment.rb中指定:

    ENV['GEM_PATH'] = '/home7/huangzhi/ruby/gems:/usr/lib/ruby/gems/1.8'

    然后,指定app的rails为2.3.3:

    RAILS_GEM_VERSION = '2.3.3' unless defined? RAILS_GEM_VERSION

    最后,修改gem中rack-1.0.0/lib/rack/handler/fastcgi.rb文件,将第7行注释掉

    #  alias _...
    Read More

    Tags 


    使用active_scaffold做为项目的后台管理

    08 Jul 2009

    最近做了一个很小的网站,需要一个简单的后台管理来对数据进行审核,由于只是个人用用,不想太浪费时间,就选用active_scaffold插件来做后台管理。对于小项目来说,应用active_scaffold可以快速地构建起项目的后台管理,自己不需要写view,相信大多数程序员都不怎么喜欢吧,controller也只需简单的配置即可。

    下面介绍一下我使用active_scaffold作为后台管理的方式吧:

    1. 在routes.rb中定义admin namespace

    map.namespace :admin do |admin|
      admin.connect 'admin/:controller/:action/:id'
      admin.connect 'admin/:controller/:action/:id.:format'
    end

    2. 定义AdminController基类,指明其子类必须是admin用户才能操作,以及layout

    # app/controller/admin_controller.rb
    class AdminController < ApplicationContr...
    Read More

    Tags 


    在hostmonster上搭建git server

    14 Jun 2009

    hostmonster本身并不支持git,不过还好它提供了ssh,我们可以ssh上去编译git。

    首先,ssh到hostmonster上,编译安装git

    $ mkdir git
    $ cd git
    $ wget http://kernel.org/pub/software/scm/git/git-1.6.3.2.tar.gz
    $ tar -zxvf git-1.6.3.2.tar.gz
    $ cd git-1.6.3.2/
    $ ./configure --prefix=$HOME/git
    $ make  make install

    修改~/.bashrc,设置环境变量

    export GIT_HOME=$HOME/git
    export PATH=$GIT_HOME/bin/:$GIT_HOME/lib/libexec/git-core/:$PATH

    验证结果

    $ source ~/.bashrc
    $ git --version

    我们在本地新建一个rails app来使用git server

    $ rails home -d mysql
    $ cd home
    $ git ...
    Read More

    Tags 


    通过Capistrano部署Rails App到Hostmonster

    06 Jun 2009

    贴贴我通过Capistrano自动发布到Hostmonster的deploy.rb文件吧

    set :application, "huangzhimin.com"
    set :repository,  "GIT_REPOSITORY"
    set :user, "huangzhi"
    set :scm, :git
    set :deploy_to, "DEPLOY_DIRECTORY"
    
    role :app, "www.huangzhimin.com"
    role :web, "www.huangzhimin.com"
    role :db,  "www.huangzhimin.com", :primary => true
    
    set :use_sudo, false
    set :run_method, :run
    
    namespace(:deploy) do
      task :after_update_code, :roles => :app do
        run "ln -s #{shared_path}/config/database.yml #{current_release}/config/da...
    Read More

    Tags 


    Rspec测试render :nothing => true

    03 Jun 2009

    对于controller render/redirect的测试,一般对应以下的测试方法

    render :action => :index
    response.should render_template('index')
    
    render :partial => 'post'
    response.should render_template('_post')
    
    redirect_to login_path
    response.should redirect_to(login_path)

    但是对于render :nothing = true来说,并没有相应的方法来测试,也无法用render_template来解决,只能是判断返回的response的内容是不是为空了。

    response.should have_text(' ')

    注意是 ,不是,至于为什么有个空格?我也没有仔细研究

    Read More

    Tags 


    ActiveRecord Without Rails

    02 Jun 2009

    前几天写了个小程序,帮我选号买彩票。主要是去网上抓取历次的开奖号码,存到数据库,然后再做统计分析。

    因为程序很小,所以实在不想把Java这个大胖子叫出来,就简单地在vi下写了几十行的ruby代码。由于要用到数据库,自然想到了ActiveRecord,平时都是在Rails环境下用的,现在却是要让它脱离出来,闹独立。

    首先是在mysql中新建数据库:

    mysqladmin -uroot create caipiao

    接着当然应该是定义database.yml

    adapter: mysql
    encoding: utf8
    database: caipiao
    username: root
    password:
    socket: /var/run/mysqld/mysqld.sock

    定义migration,新建db/migrate目录,新建migration文件,注意前面加上数字前缀,001_xxx, 002_yyy,migration文件内容就和rails中的一模一样。

    class CreateRedBlueBalls < ActiveRecord::Migration
      def...
    Read More

    Tags 


    压缩js和css文件的rake

    02 May 2009

    对于Web应用来说,一个页面的HTTP请求数越多,往往导致页面加载的时间越长,服务器的负担也越重。对于每个HTTP请求,都要进行握手,客户端说hello,服务器端也说hello,然后再传输内容,所以要尽量减少请求的数量。

    页面的HTTP请求,除了html本身之外,还有javascript、css和images。其中images可以通过css sprite来解决,而javascript和css则可以通过压缩合并,来减少HTTP的请求。

    今天写了一个rake,来压缩js和css,是基于yui compressor的。

    首先下载yui compressor,将jar文件copy到lib目录下,当然因为yui compressor是基于java的,所以你要先配好java的环境。

    然后就是写rake文件

    namespace :minifier do
      def minify(input_files, output_file)
        if input_files.class == String
          `java -jar lib/yuicompressor-2.4.2.jar #{...
    Read More

    Tags 


    通过数组转置来组织ActiveRecord的conditions

    20 Apr 2009

    使用ActiveRecord的conditions最基本的方法就是数组:

    :conditions => ['first_name = ? and middle_name = ? and last_name = ?', 'George', 'W', 'Bush']

    更灵活的方法是使用Hash来组织:

    :conditions => {:first_name => 'George', :middle_name => 'W', :last_name => 'Bush'}

    这样在动态构建查询条件的情况非常有帮助,比如根据查询参数来构建conditions:

    conditions = {}
    conditions.merge!({:first_name => params[:first_name]}) if params[:first_name]
    conditions.merge!({:first_name => params[:middle_name]}) if params[:middle_name]
    conditions.merge!({:first_name => params[...
    Read More

    Tags 


    counter_cache的migration

    18 Apr 2009

    counter_cache在提升查询性能上带来了很大的帮助,在rails中用起来也是非常的方便,增加一个xxx_count在数据表中,在belongs_to的声明后增加:counter_cache => true。

    但是今天在增加counter_cache的migration上却遇到了麻烦,比如增加Person的blog_posts_count,migration如下:

    def self.up
      add_column :people, :blog_posts_count, ;integer, :default => 0
    
      Person.reset_column_information
      Person.all.each do |p|
        p.update_attribute(:blog_posts_count, p.blogs.posts.length)
      end
    end

    运行之后,发现blog_posts_count的值总是为null,即使在console下面运行person的update_attribute方法也没有任何作用,errors却是空的,再看sql语句,只更新了updated_at。试了很久都没有结果,google之后发觉原来是rails 2.0之后把counter_cache的attri...

    Read More

    Tags 


    Rails源码分析——delegate

    13 Apr 2009

    Delegate是一种应用composite来代替extend的机制,可以有效地降低代码的耦合性。

    Rails 2.2增加了delegate方法,可以十分方便地实现delegate机制。来看看源码吧:

    def delegate(*methods)
      options = methods.pop
      unless options.is_a?(Hash) && to = options[:to]
        raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
      end
    
      if options[:prefix] == true && options[:to].to_s =~ /^[^a-z_]/
        raise ArgumentError, "Can only automatically set the ...
    Read More

    Tags