Archive

Archive for the ‘rails’ Category

如何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:

把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: ,

给类方法加alias_method_chain

August 23rd, 2010 yakjuly No comments

alias_method_chain 是给实例方法 加一个链,非常好用,使用方法如下:
alias_method_chain(target, feature)

Encapsulates the common pattern of:

alias_method :foo_without_feature, :foo
alias_method :foo, :foo_with_feature

With this, you simply do:

alias_method_chain :foo, :feature

And both aliases are set up for you.

Query and bang methods (foo?, foo!) keep the same punctuation:

alias_method_chain :foo?, :feature

is equivalent to

alias_method :foo_without_feature?, :foo?
alias_method :foo?, :foo_with_feature?

so you can safely chain foo, foo?, and foo! with the same feature.

这次想做一个插件,改写某个特殊model的find方法。于是想到了alias_method_chain。
刚开始 我是这样写的。

module CacheModel
  def self.included(base)
    base.extend ClassMethods
  end
  module ClassMethods
    alias_method_chain :find, :cache
    def find_with_cache(*args)
    end
  end
end

运行时报错 CacheModule::ClassMethods没有定义 find方法。
原因:alias_method_chain可以在module中使用。在这里ruby并不认为alias_method_chain是对included 的 “类” 的 “类方法” 进行重命名。
网上查资料后发现 需要按照下面的方法写就可以。太酷了!

module CacheModel
  def self.included(base)
    base.extend ClassMethods
    base.class_eval do
      class << self
        alias_method_chain :find, :cache
      end
    end
  end

  module ClassMethods
    #doing some thing
    def find_with_cache(*args)
      #doing some thing
    end
  end

end

如果你遇到这样的问题,希望这篇文章能帮到你的忙。

Categories: rails, ruby Tags:

rails中的select和include查询

August 2nd, 2010 yakjuly No comments

Rails中ActiveRecord finders不允许我们在include预加载关联对象时使用select。

例如这样的情况:

class Student < ActiveRecord::Base
   has_many :calls
   has_one :call
end

class Call < ActiveRecord::Base
   belongs_to :student
end

希望在显示student时,显示它的最近更新的call的日期。

改善前:

@students = Student.all(:include => [:call])

改善后:

#include eql left outer join
@students = Student.all(:select => "students.id, students.login, students.called_times,
calls.updated_at as last_called_at", :joins => "left outer join calls on students.id = calls.student_id")
Categories: rails, ruby Tags: ,

Haml/Sass 给你的代码洗洗澡吧

July 16th, 2010 yakjuly No comments

Haml :是一种标记语言,它使html代码变的更加简练。

haml:

#profile
  .left.column
    #date= print_date
    #address= current_user.address
  .right.column
    #email= current_user.email
    #bio= current_user.bio

html:

<div id="profile">
  <div class="left column">
    <div id="date"><%= print_date %></div>
    <div id="address"><%= current_user.address %></div>
  </div>
  <div class="right column">
    <div id="email"><%= current_user.email %></div>
    <div id="bio"><%= current_user.bio %></div>
  </div>
</div>

Sass是CSS3的扩展,它让css变得更加有趣

Sass 与Haml是一对活宝。Haml优化了html,Sass则是优化了css。

Sass在css的基础上添加了四种不同的元素,让css写起来更加方便,看起来更加整洁。

Variable

想在不同的地方使用相同的颜色?需要对文本长度计算高度和宽度?Sass支持使用变量和一些基本的计算以及许多有用的功能

scss:

$blue: #3bbfce;
$margin: 16px;

.content-navigation {
  border-color: $blue;
  color:
    darken($blue, 9%);
}

.border {
  padding: $margin / 2;
  margin: $margin / 2;
  border-color: $blue;
}

sass:

$blue: #3bbfce
$margin: 16px

.content-navigation
  border-color: $blue
  color: darken($blue, 9%)

.border
  padding: $margin / 2
  margin: $margin / 2
  border-color: $blue

产生的css:

/* CSS */

.content-navigation {
  border-color: #3bbfce;
  color: #2b9eab;
}

.border {
  padding: 8px;
  margin: 8px;
  border-color: #3bbfce;
}

Nesting

Sass避免重复的写选择器。相同的效果可以用以下的写法。

scss:

table.hl {
  margin: 2em 0;
  td.ln {
    text-align: right;
  }
}

li {
  font: {
    family: serif;
    weight: bold;
    size: 1.2em;
  }
}

sass:

table.hl
  margin: 2em 0
  td.ln
    text-align: right

li
  font:
    family: serif
    weight: bold
    size: 1.2em

产生的css:

/* CSS */

table.hl {
  margin: 2em 0;
}
table.hl td.ln {
  text-align: right;
}

li {
  font-family: serif;
  font-weight: bold;
  font-size: 1.2em;
}

Mixins

比变量更加有用,mixin允许你重复使用大片的CSS,属性以及选择器。你甚至可以给它们传递参数。(类似函数方法)

Scss:

@mixin table-base {
  th {
    text-align: center;
    font-weight: bold;
  }
  td, th {padding: 2px}
}

@mixin left($dist) {
  float: left;
  margin-left: $dist;
}

#data {
  @include left(10px);
  @include table-base;
}

Sass:

@mixin table-base
  th
    text-align: center
    font-weight: bold
  td, th
    padding: 2px

@mixin left($dist)
  float: left
  margin-left: $dist

#data
  @include left(10px)
  @include table-base

产生的CSS:

/* CSS */

#data {
  float: left;
  margin-left: 10px;
}
#data th {
  text-align: center;
  font-weight: bold;
}
#data td, #data th {
  padding: 2px;
}

Selector Inheritance

Sass可以让一个选择器 继承 另一个选择器 除了重复属性外的其他所有的CSS属性(有点绕,就是相当于优先级比较高)。

Scss:

.error {
  border: 1px #f00;
  background: #fdd;
}
.error.intrusion {
  font-size: 1.3em;
  font-weight: bold;
}

.badError {
  @extend .error;
  border-width: 3px;
}

Sass:

.error
  border: 1px #f00
  background: #fdd

.error.intrusion
  font-size: 1.3em
  font-weight: bold

.badError
  @extend .error
  border-width: 3px

产生css:

/* CSS */

.error, .badError {
  border: 1px #f00;
  background: #fdd;
}

.error.intrusion,
.badError.intrusion {
  font-size: 1.3em;
  font-weight: bold;
}

.badError {
  border-width: 3px;
}
更多文章:

Why use HAML (and SASS)? I already know HTML.

Haml And Sass in 15 muntinues (ppt)

Categories: rails Tags: