Archive

Archive for the ‘ruby’ Category

狡诈的concat方法

December 23rd, 2010 yakjuly No comments

今天做一个小玩意儿,想用类似webistrano的构思,后台执行任务,前台显示进度。

于是在后台执行任务的时候 数据库产生一条 deployment的记录,deployment有log字段。

在执行任务的同时 把deployment的log字段中 添加 日志信息。

前台同时 定时读取deployment的信息,显示出来。

问题是 我在后台任务更新 log的时候,前台始终读不出来deployment的最新log。

调查半天,发现ActiveRecord::Base的一个bug。

我的代码是这样写的

def digui_add_file(struct, path, zip)
    dup_path = path.dup

    if struct["name"]
      if dup_path.blank?
        dup_path = struct['name']
      else
        dup_path << "/#{struct['name']}"
      end

      if struct["children"].present?
        struct["children"].each do |child|
          digui_add_file(child, dup_path, zip)
        end
      elsif struct["files"].present?
        files = UploadFile.all(:conditions => ["id in (?)", struct["files"]])
        files.each do |file|
          file_path = dup_path + "/" + file.filename
          #log info
          @deploy.log << "add: #{file_path}\n"
          @deploy.save
          zip.add(file_path, file.url)
        end
      else
        #log info
        @deploy.log << "mkdir: #{dup_path}\n"
        @deploy.save
        zip.mkdir(dup_path)
      end
    end
  end

产生的结果是 运行正常 在代码中 调查 p @deploy 也是正常, 但是@deploy 并没有更新。

console中运行以下命令:

>> d = Deployment.last
=> #<Deployment id: 27, uuid: "decf4194a1b9640d126bf927ba259588d3c6811f", log: "sdfdfbaga", result: "", profile_id: 113403, pid: nil, status: "success", created_at: "2010-12-23 03:51:40", updated_at: "2010-12-23 04:05:22">
>> d.log << "hahaha"
=> "sdfdfbagahahaha"
>> d.save
=> true
>> d.log
=> "sdfdfbagahahaha"
>> d.reload
=> #<Deployment id: 27, uuid: "decf4194a1b9640d126bf927ba259588d3c6811f", log: "sdfdfbaga", result: "public/f/tmp/level1.zip", profile_id: 113403, pid: nil, status: "success", created_at: "2010-12-23 03:51:40", updated_at: "2010-12-23 04:05:22">
>> d.log
=> "sdfdfbaga"

结果让人大吃一惊,原来 d.log << “xxxx” 在save前后并没有起到作用。

原因不明,按理说d.log << “xxx” 后 d.log #=> “xxx” 已经生效,d.save应该会把d的log更新了。

可惜事实不是如此。记录d只有在调用了log= 方法后 才会认为d被改变了。

以后在更新记录时切忌不要对单个字符串字段使用 << 或者concat 方法

Categories: rails, ruby Tags:

如何stub实例方法

November 19th, 2010 yakjuly 2 comments

rspec测试 controller。
在Factory构建数据的时候,需要调用一些方法,而这些方法无法在测试controller中通过实例来模拟。

例如,

class A < ActiveRecord::Base
  def before_save
    self.url = do_something
  end

  def do_something(arg=nil)
    42
  end

end

你希望在before_save的时候 将do_something的结果 修改为 24. 在测试 controller的时候怎么办呢?

new_method = A.method(:new)
A.stub!(:new).and_return do |*args|
  a = new_method.call(*args)
  a.should_receive(:do_something).with(any_args()).and_return(24)
  a
end

以上只适用于 new 出来的 实例。

A.any_instance.stubs(:do_something).returns(24)
Categories: rails, rspec, ruby Tags:

subclasses_of的用法

October 21st, 2010 yakjuly No comments
Object.subclasses_of(ActiveRecord::Base)
#or
ActiveRecord::Base.send(:subclasses)

返回
ActiveRecord::Base的子类。

Categories: ruby Tags:

不能使用”self.xx”调用私有方法

October 19th, 2010 yakjuly No comments

前几天将代码整理了一遍,把一些model内用的方法标上private。防止外部使用。同时,也可以清晰的明白各个方法的用途。

整理完后启动服务,调试了一遍,报错:NoMethodError (Attempt to call private method).

检查到报错的地方代码,发现 使用 self.xxx 不能调用 私有方法。

例子:

Don’t do


self.get_uri_from_path()

do


get_uri_from_path()

Because…


class AccessPrivate
    def a
    end
    private :a # a is private method

    def accessing_private
      a              # sure!
      self.a         # nope! private methods cannot be called with an explicit receiver at all, even if that receiver is "self"
      other_object.a # nope, a is private, you can't get it (but if it was protected, you could!)
    end
  end

Categories: ruby Tags:

把AJAX操作 从controller中移到helper

September 3rd, 2010 yakjuly 2 comments

rails中的ajax操作结合prototype,非常方便。方法也挺简单。

我平常操作ajax的代码喜欢在controller中使用 render :update {|page| … }

但是这样的代码在controller写多了 会显得非常肮脏 难读。并且,我要对页面进行某个操作,需要在controller中写页面中的dom元素的id等。

例如:

render :update do |page|
  page.replace_html("xxxx", :partial => "xxx")
end

render :update do |page|
   #use alert
   page.alert(@object.errors.full_messages.join(","))
   #or replace notice area
   page.replace_html("error_div", @object.errors.full_messages.join(","))
end

当一次ajax访问需要 对页面调用多个动作时,代码就显得乱了。利用rails的helper,我们可以将render:update的block中的内容放到helper中,这为我们复用和重构提供了一个有效的工具。 例如:

module ApplicationHelper

  def update_time
    page.alert 'hello'
  end

  def spike
    render(:update){|page| page.alert_hello}
  end
end

另外,把代码移到helper中也可以让这些ajax操作在controller中 显得更有意义。

controller:

class User::CommentsController < User::BaseController
  before_filter :prepare_comment

  def index

  end

  def new

    respond_to do |type|
      type.js {
        render :update do |page|
          page.create_comment_form
        end
      }
    end
  end

  def create
    success = @comment.save

    respond_to do |type|
      type.js {
        render :update do |page|
          if success
            page.clear_comment_form
            refresh_comment_list(page)
          else
            page.show_error_messages(@comment)
          end
        end
      }
    end
  end

  def edit
    respond_to do |type|
      type.js {
        render :update do |page|
          page.create_comment_form
        end
      }
    end
  end

  def update
    success = @comment.save
    respond_to do |type|
      type.js {
        render :update do |page|
          if success
            page.clear_comment_form
            refresh_comment_list(page)
          else
            page.show_error_messages(@comment)
          end
        end
      }
    end
  end

  def destroy
    @comment.destroy
    render :nothing => true
  end

  private

  def prepare_comment
    case action_name
    when "new", "create"
      @comment = @user.comments.build(params[:comment])
    when "edit", "update", "destroy"
      @comment = @user.comments.find(params[:id])
      @comment.attributes=(params[:comment])
    when "index"
      @comments = @user.comments
    end
  end

end

接下来 helper

module User::CommentHelper
  def refresh_comment_list(page)
    @comments = @user.comments
    page.replace_html("comment_list", :partial => "list")
  end

  def clear_comment_form
    page.replace_html("edit_comment", "")
  end

  def create_comment_form
    page.replace_html("edit_comment", :partial => "form")
  end

  def comment_form_head
    case params[:action]
    when "new", "create"
      [@comment, {:url => user_comments_path}]
    when "edit", "update"
      [@comment, {:url => user_comment_path(@user, @comment), :html => {:method => :put}}]
    end
  end
end

这里有个缺憾 就是 refresh_comment_list方法 不能直接写成page.refresh_comment_list

因为在javascript generator中不能读到 @user 这个实例变量。因此只有写成refresh_comment_list(page). 并且helper方法中包含了数据库查询语句。

虽然美中不足,但代码看起来还是舒服多了。

view中还可以重用helper中的部分方法

<% remote_form_for *comment_form_head do |f| %>
<div class="order" style="clear:left">
  <table>
    ........
    <tr><th></th><td><%= submit_tag(t(:submit)) %> <%= link_to_function(t(:cancel), update_page{|page| page.clear_comment_form}) %></td></tr>
  </table>
</div>
<% end %>

经过这样改写后,controller中不用关注 dom对象如何变化,helper与页面中的dom对象关系更加紧密。

Categories: rails, ruby Tags: ,