Rails源码 attr_internal

Posted by wxianfeng Thu, 05 Jan 2012 18:34:00 GMT

环境:ruby 1.9.2 + rails 3.0.3 + ubuntu 10.10

params在rails中很常用,特别在表单提交的时候,params 产生的是一个Hash ,里面构造通过 form域的name构造 ,产生不同的 params 内容,今天 在看rails params 实现的时候 发现通过 attr_internal 的方法实现,params方法 的源码:

    def params
      @_params ||= request.parameters
    end

发现其实是从 request 这个方法得到的,那么request方法又是怎么定义的:

attr_internal :headers, :response, :request

就是 用了 attr_internal 方法

看下 整个 metal.rb文件: here

发现了 response,headers,session(借助delegate委派) ,status,params == 都是通过 attr_internal 实现的,来看看 attr_internal 到底是何须人也 :

源码: here

class Module
  # Declares an attribute reader backed by an internally-named instance variable.
  def attr_internal_reader(*attrs)
    attrs.each do |attr|
      module_eval "def #{attr}() #{attr_internal_ivar_name(attr)} end", __FILE__, __LINE__
    end
  end

  # Declares an attribute writer backed by an internally-named instance variable.
  def attr_internal_writer(*attrs)
    attrs.each do |attr|
      module_eval "def #{attr}=(v) #{attr_internal_ivar_name(attr)} = v end", __FILE__, __LINE__
    end
  end

  # Declares an attribute reader and writer backed by an internally-named instance
  # variable.
  def attr_internal_accessor(*attrs)
    attr_internal_reader(*attrs)
    attr_internal_writer(*attrs)
  end

  alias_method :attr_internal, :attr_internal_accessor

  class << self; attr_accessor :attr_internal_naming_format end
  self.attr_internal_naming_format = '@_%s'

  private
    def attr_internal_ivar_name(attr)
      Module.attr_internal_naming_format % attr
    end
end

发现其实就是通过 module_eval 给 对象 添加了 settet , getter 方法而已,但是命名格式是这样的:

  self.attr_internal_naming_format = '@_%s' 

DEMO:

require "active_support/core_ext/module/attr_internal"

class Foo
  
  attr_accessor :sex,:birthday # attr_accessor ruby里封装的method
  attr_internal :name,:city # attr_internal rails 封装的

  def bar
    name # call getter method # => @_name
  end

end

f = Foo.new
f.name = 'wxianfeng'
p f.instance_variables # => [:@_name]
p f.name # => "wxianfeng"
p f # => #<Foo:0x8630e18 @_name="wxianfeng">
p f.bar # => "wxianfeng"

所以 attr_internal 和 attr_accessor 其实是 等价的,只不过 从字面意思上看是内部变量(闭包变量的写法) ,attr_internal 希望你 通过方法名来调用,不用 @_%s 这个写法 来调用


所以 其实 一般我们在 controller 用的 request 方法 其实 可以直接这样写 @_request ,

request #=> @_request
params # => @_request.parameters
params # => @_params
headers #=> @_headers
status #=> @_status
.
.
.

但是一般 不建议这样写

还发现 这些和 http相关的东西都定义在 metal 模块, metal 是 rails 链接 rack 的中间件,源码中的解释:

ActionController::Metal provides a way to get a valid Rack application from a controller.

Rack 是一个 ruby实现的web server,封装了 http的请求和响应等,例如 rails,sinatra == 都是在 rack 基础上实现的……

有机会很有必要 深入学习下…

SEE:

http://rubyonrailswin.wordpress.com/2007/03/07/actioncontroller-and-what-the-heck-is-attr_internal/
http://www.oschina.net/p/rack


Rails 动态生成表和Model

Posted by wxianfeng Sat, 01 Aug 2009 17:12:00 GMT

环境:ruby 1.9.2 + rails 3.0.3 + ubuntu 10.10

项目需要运行中动态生成表 和 Model , 怎么办 ?

借助 ActiveRecord::Migration 来实现 动态建表 和 字段

可以 借助 Object.const_set 来实现 动态Model

DEMO:

# RUN : rails runner lib/dynamic_table.rb

ActiveRecord::Migration.create_table :posts
ActiveRecord::Migration.add_column :posts, :title, :string

Object.const_set(:Post,Class.new(ActiveRecord::Base)) # => Object.class_eval { const_set(:Post,Class.new(ActiveRecord::Base)) }
# p Post.columns
p Post.column_names # ["id", "title"]

ActiveRecord::Migration.add_column :posts, :body, :text

p Post.column_names # ["id", "title"]

Object.class_eval { remove_const :Post }
Object.const_set(:Post,Class.new(ActiveRecord::Base))

p Post.column_names # ["id", "title", "body"]

动态Model 实质就相当于 Post = Class.new(ActiveRecord::Base) 或者 Post < ActiveRecord::Base

Class.new(ActiveRecord::Base) 参数指定 super_class , 默认是 Object

可以从ruby源码中看出:

  #     Class.new(super_class=Object)   =>    a_class
  #
  #
  # Creates a new anonymous (unnamed) class with the given superclass
  # (or <code>Object</code> if no parameter is given). You can give a
  # class a name by assigning the class object to a constant.
  #
  #
  #
  def self.new(super_class=Object)
    # This is just a stub for a builtin Ruby method.
    # See the top of this file for more info.
  end

当给表添加了新的字段后,Model 需要重新 const_set 一次 ,注意 const_set 之前 需要 remove_const 一次 , 不然会出现 已经初始化的警告

see:

http://hildolfur.wordpress.com/2006/10/29/class-reloading-in-ruby/


rails check_box_tag boolean

Posted by wxianfeng Tue, 30 Jun 2009 16:16:00 GMT

环境:ruby 1.9.2 + rails 3.0.3 + ubuntu 10.10

scaffold 生成的 form_for 对应的 boolean 类型 是check_box , 数据库中 值是 1 check_box就选中, 是 0 就不选中, 但是不是 form_for 的话 ,check_box_tag 怎么实现呢

仿照 form_for check_box 生成的代码:

<input name="user[abc]" type="hidden" value="0" />
<input checked="checked" id="user_abc" name="user[abc]" type="checkbox" value="1" />

可以看出 多生成了 一段 hidden , 为什么 ?

因为 check_box 的工作原理是 :

选中了 就传check_box 的参数 , 不选中 不传 check_box 的 参数

所以上面 选中的话,传的值是 1

没选中的话, 传的是 0

所以check_box_tag 的话,就应该这样写了.

     <%= hidden_field_tag "user[abc]" , 0 %>
      <%= check_box_tag "user[abc]" , 1 , (true if ele.abc == 1)  %>

注意 需要 添加 hidden 域 , 并且 需要在 checkbox 的上面


rails attr_accessible attr_protected

Posted by wxianfeng Mon, 13 Dec 2010 14:07:00 GMT

环境:ruby 1.9.2 + rails 3.0.3 + ubuntu 10.10

我们经常在 用户注册 验证 的时候 会用到 attr_accessible , 那么有什么作用呢?

问题重现:

用户注册的时候 需要 填写 login , name , password , confirm_password

User:

attr_accessible :login, :email, :password, :password_confirmation

后台传参:

Parameters: {"utf8"=>"", "authenticity_token"=>"r+rfq6Wcu/64ZvkMOi6kPE1k6NHELDi99GmaehGFA4g=", "user"=>{"login"=>"wwwwww", "email"=>"ww@ww.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Sign up"}

顺利通过

User:

attr_accessible  :email, :password, :password_confirmation
 Parameters: {"utf8"=>"", "authenticity_token"=>"r+rfq6Wcu/64ZvkMOi6kPE1k6NHELDi99GmaehGFA4g=", "user"=>{"login"=>"qqqqqq", "email"=>"qq@qq.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Sign up"}

报错:

    * Login can't be blank
    * Login is too short (minimum is 3 characters)
    * Login use only letters, numbers, and .-_@ please.

为什么?

attr_accessible 是为了安全性考虑的,表单提交是 通过 attributes= 的块赋值 , 放在 attr_accessible 中的属性 才可以 通过块赋值,块就是指 “user”=>{"login"=>"qqqqqq", “email”=>"qq@qq.com", “password”=>"[FILTERED]", “password_confirmation”=>"[FILTERED]"} 后台 直接

user = User.new(params[:user]) 
user.save!

如果不加 attr_accessible 的话,用户可以 构造 参数 ,模拟表单提交 ,例如你的表里 status = 1 表示已经激活,admin = 1 表示 管理员 , 那么用户提交 :

"user"=>{"login"=>"wwwwww", "email"=>"ww@ww.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]",admin=>'1',status=>"1"}

自动就激活 和 变成管理员,

所以 重要字段 , 这是必须要考虑的..

那么 attr_protected 又是干什么的,它和 attr_accessible 相反 , attr_protected 是黑名单,attr_protected 保护的属性,都不可以通过块赋值,attr_accessible 是白名单,保护的属性可以 通过块赋值

rails 中自带 demo:

attr_accessible:

require 'active_model'

class Customer
  include ActiveModel::MassAssignmentSecurity

  attr_accessor :name, :credit_rating
  attr_accessible :name

  def attributes=(values)
    sanitize_for_mass_assignment(values).each do |k, v|
       send("#{k}=", v)
    end
  end
end

customer = Customer.new
customer.attributes = { :name => "David", :credit_rating => "Excellent" }
p customer.name          # => "David"
p customer.credit_rating # => nil

customer.credit_rating = "Average"
p customer.credit_rating # => "Average"

attr_protected:

require 'active_model'
class Customer
  include ActiveModel::MassAssignmentSecurity

  attr_accessor :name, :credit_rating
  attr_protected :credit_rating

  def attributes=(values)
    sanitize_for_mass_assignment(values).each do |k, v|
      send("#{k}=", v)
    end
  end
end

customer = Customer.new
customer.attributes = { "name" => "David", "credit_rating" => "Excellent" }
customer.name          # => "David"
customer.credit_rating # => nil

customer.credit_rating = "Average"
customer.credit_rating # => "Average"

模拟 attributes = 赋值,可以看出 attr_accessible 和 attr_protected 正好 相反

see:

http://www.javaeye.com/topic/161256


Rails 测试注意 use_transactional_fixtures

Posted by wxianfeng Tue, 31 Mar 2009 17:57:00 GMT

环境:ruby 1.9.2 + rails 3.0.3 + ubuntu 10.10 + mysql 5.1

这个问题搞了我接近两个工作日,现在还在恼火中,之前大家伙都没遇到过这个问题, 但是又必须要解决,不解决我的测试就跑不起来了,问题是这样的,用rails scaffold 生成的测试代码,

生成代码;

rails new scaffold -d mysql
rake db:create:all
rails g scaffold user name:string password:string birthday_at:datetime
rake db:migrate
rake test

执行rake test 后,出现错误:

ActiveRecord::RecordNotFound: Couldn't find User with ID=980190962

报错的是controller action的测试, 生成fixtures 的代码

users.yml

# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html

one:
  name: MyString
  password: MyString
  birthday_at: 2010-11-29 11:03:20

two:
  name: MyString
  password: MyString
  birthday_at: 2010-11-29 11:03:20

测试代码 userscontrollertest.rb:

# 执行顺序是按照name的排序(ASC)来执行的
require  File.expand_path("./test/test_helper")
# require 'test_helper'

#p Fixtures.identify(:one) # autogenerate id 的算法
#p Fixtures.identify(:two)

class UsersControllerTest < ActionController::TestCase


  setup do # 初始化对象 , 没有load 数据
    @user = users(:one)
  end

  test "should get index" do # 只要 执行了 test 方法,fixtures 的数据就已经load到test数据库中了
    get :index
    assert_response :success
    assert_not_nil assigns(:users)
  end

  test "should get new" do
    get :new
    assert_response :success
  end

  test "should create user" do 
    assert_difference('User.count') do
      post :create, :user => @user.attributes
    end
  
    assert_redirected_to user_path(assigns(:user))
  end

  test "should show user" do
    get :show, :id => @user.to_param
    assert_response :success
  end
  
  test "should get edit" do
    get :edit, :id => @user.to_param
    assert_response :success
  end
  
  test "should update user" do
    put :update, :id => @user.to_param, :user => @user.attributes
    assert_redirected_to user_path(assigns(:user))
  end
  
  test "should destroy user" do
    assert_difference('User.count', -1) do
      delete :destroy, :id => @user.to_param
    end

    assert_redirected_to users_path
  end
end

我想不应该啊,rails 经典的scaffold怎么会报错,于是开始排查: 1,在同事,宿舍,VPS上分别都做了测试 都通过了,都没有问题

2,下一步通过debug跟踪来调:

最后还是没有解决,但是 发现了一点蛛丝马迹,发现了 test 的执行顺序,是按照name的升序来执行的,上面图可以看出,不用netbeans怎么看出来?在Rails.root 根下执行:

yang@yang-OptiPlex-380:/usr/local/system/scaffold$ ruby test/functional/users_controller_test.rb -v
Loaded suite test/functional/users_controller_test
Started
UsersControllerTest#test_should_create_user: 0.19 s: .
UsersControllerTest#test_should_destroy_user: 0.06 s: .
UsersControllerTest#test_should_get_edit: 0.01 s: .
UsersControllerTest#test_should_get_index: 0.01 s: .
UsersControllerTest#test_should_get_new: 0.03 s: .
UsersControllerTest#test_should_show_user: 0.01 s: .
UsersControllerTest#test_should_update_user: 0.01 s: .

Finished in 0.310882 seconds.

7 tests, 10 assertions, 0 failures, 0 errors, 0 skips

Test run options: --seed 52586 --verbose

就是跑单个测试文件,加上 -v 参数 ,更多的可以 -h 查看下,注意把单个文件的 test_helper 路径加入正确,上面的我那个已经修改过来.

还可以看出 是执行到 destroy 方法的时候报错的,把这个destroy方法注释掉,问题就没了,但是根本问题没解决.

3,改变ruby rails 版本测试 切换到ruby 1.8.7 + rails 2.3.5 scaffold生成测试代码,问题依然存在

4,跟踪测试log看看

SQL (0.0ms)  BEGIN
  SQL (0.2ms)  SHOW TABLES
  User Load (0.1ms)  SELECT `users`.* FROM `users` WHERE (`users`.`id` = 980190962) LIMIT 1
  SQL (0.1ms)  SELECT COUNT(*) FROM `users`
  Processing by UsersController#create as HTML
  Parameters: {"user"=>{"birthday_at"=>2010-11-29 11:03:20 UTC, "created_at"=>2010-12-01 07:20:20 UTC, "id"=>980190962, "name"=>"MyString", "password"=>"[FILTERED]", "updated_at"=>2010-12-01 07:20:20 UTC}}
WARNING: Can't mass-assign protected attributes: id
  SQL (0.1ms)  SAVEPOINT active_record_1
  SQL (0.5ms)  describe `users`
  AREL (0.2ms)  INSERT INTO `users` (`name`, `password`, `birthday_at`, `created_at`, `updated_at`) VALUES ('MyString', 'MyString', '2010-11-29 11:03:20', '2010-12-01 07:20:20', '2010-12-01 07:20:20')
  SQL (0.0ms)  RELEASE SAVEPOINT active_record_1
Redirected to http://test.host/users/980190964
Completed 302 Found in 11ms
  SQL (0.1ms)  SELECT COUNT(*) FROM `users`
  SQL (0.0ms)  ROLLBACK
  SQL (0.0ms)  BEGIN
  User Load (0.1ms)  SELECT `users`.* FROM `users` WHERE (`users`.`id` = 980190962) LIMIT 1
  SQL (0.1ms)  SELECT COUNT(*) FROM `users`
  Processing by UsersController#destroy as HTML
  Parameters: {"id"=>"980190962"}
  User Load (0.2ms)  SELECT `users`.* FROM `users` WHERE (`users`.`id` = 980190962) LIMIT 1
  SQL (0.0ms)  SAVEPOINT active_record_1
  AREL (0.1ms)  DELETE FROM `users` WHERE (`users`.`id` = 980190962)
  SQL (0.0ms)  RELEASE SAVEPOINT active_record_1
Redirected to http://test.host/users
Completed 302 Found in 2ms
  SQL (0.1ms)  SELECT COUNT(*) FROM `users`
  SQL (0.0ms)  ROLLBACK
  SQL (0.0ms)  BEGIN
  User Load (0.1ms)  SELECT `users`.* FROM `users` WHERE (`users`.`id` = 980190962) LIMIT 1
  SQL (0.0ms)  ROLLBACK
  SQL (0.0ms)  BEGIN
  User Load (0.1ms)  SELECT `users`.* FROM `users` WHERE (`users`.`id` = 980190962) LIMIT 1
  SQL (0.0ms)  ROLLBACK
  SQL (0.0ms)  BEGIN
  User Load (0.1ms)  SELECT `users`.* FROM `users` WHERE (`users`.`id` = 980190962) LIMIT 1
  SQL (0.0ms)  ROLLBACK
  SQL (0.0ms)  BEGIN
  User Load (0.1ms)  SELECT `users`.* FROM `users` WHERE (`users`.`id` = 980190962) LIMIT 1
  SQL (0.0ms)  ROLLBACK
  SQL (0.0ms)  BEGIN
  User Load (0.1ms)  SELECT `users`.* FROM `users` WHERE (`users`.`id` = 980190962) LIMIT 1
  SQL (0.0ms)  ROLLBACK

这是test输出的log,发现了好东西,ROLLBACk ,这个问题就来了,经过进一步的研究,我把 destroy 的测试方法给注释了,跑了我的测试 ,最后数据库中产生 3条数据,但是正确的一台机子上 最后数据库中users表中是 2 条数据,问题就在这里 ,最后搞懂了 rails 自带testcase的测试原理,才恍然大悟!

测试的表必须 innodb 引擎,因为测试的原理默认是 事务回滚 机制,来看看测试代码就知道了:

test "should create user" do 
    assert_difference('User.count') do
      post :create, :user => @user.attributes
    end
  
    assert_redirected_to user_path(assigns(:user))
  end

这段代码 的意思是 测试前的 User.count + 1 = yield block后的 User.count , 测试就是success了,1是默认值,可以查看assert_difference源码得知, 但是数据是回滚的,例如测试前数据库中是两条数据,那么测试create数据后,数据库中应该还是原来的fixtures中的两条数据

同理 destroy 的原理一样;

 test "should destroy user" do

assert_difference(<span class="s"><span class="dl">'</span><span class="k">User.count</span><span class="dl">'</span></span>, <span class="i">-1</span>) <span class="r">do</span>
  delete <span class="sy">:destroy</span>, <span class="sy">:id</span> =&gt; <span class="iv">@user</span>.to_param
<span class="r">end</span>

assert_redirected_to users_path

end

看下 assert_difference 源码(重点看注释):

     # Test numeric difference between the return value of an expression as a result of what is evaluated
      # in the yielded block.
      #
      #   assert_difference 'Article.count' do
      #     post :create, :article => {...}
      #   end
      #
      # An arbitrary expression is passed in and evaluated.
      #
      #   assert_difference 'assigns(:article).comments(:reload).size' do
      #     post :create, :comment => {...}
      #   end
      #
      # An arbitrary positive or negative difference can be specified. The default is +1.
      #
      #   assert_difference 'Article.count', -1 do
      #     post :delete, :id => ...
      #   end
      #
      # An array of expressions can also be passed in and evaluated.
      #
      #   assert_difference [ 'Article.count', 'Post.count' ], +2 do
      #     post :create, :article => {...}
      #   end
      #
      # A error message can be specified.
      #
      #   assert_difference 'Article.count', -1, "An Article should be destroyed" do
      #     post :delete, :id => ...
      #   end
      def assert_difference(expression, difference = 1, message = nil, &block)
        b = block.send(:binding)
        exps = Array.wrap(expression)
        before = exps.map { |e| eval(e, b) }
        p before

        yield

        exps.each_with_index do |e, i|
          error = "#{e.inspect} didn't change by #{difference}"
          error = "#{message}.\n#{error}" if message
          assert_equal(before[i] + difference, eval(e, b), error)
        end
      end

我的mysql是 myisam 引擎,test.log 的insert那一部分输出ROLLBACK,其实没有rollback,表中insert 数据进去了,所以这里的log输出是错误的,谴责下DHH,开玩笑了^_^.

期间遇到的问题;

1,fixtures现在不用指定id,会自己给你生成,那生成的算法是什么呢?

Fixtures.identify(:one)

demo:

wxianfeng@ubuntu:/usr/local/system/projects/rails3__scaffold$ rails c
Loading development environment (Rails 3.0.3)
ruby-1.9.2-p0 > require "rails/test_help"
 => ["FixtureClassNotFound", "FixturesFileNotFound", "Fixtures", "Fixture"] 
ruby-1.9.2-p0 > Fixtures.identify(:one)
 => 980190962 

看下 identify 方法的源码:

MAX_ID = 2 ** 30 - 1

  def self.identify(label)
    Zlib.crc32(label.to_s) % MAX_ID
  end

2,怎么在netbeans中debug测试文件呢,即上面的图

需要安装 ruby-debug-ide , ruby-debug-base , test-unit , 还需要 把依赖的 gem包都打进入项目中,

bundle install .  # . 是path.当前位置

即可,你的Rails.root下多了ruby文件夹,里面都是用到的 gems ,

3,怎么跑单个测试文件?

ruby test/functional/users_controller_test.rb # 注意修改 require test_helper ,默认会找不到

4,怎么测试一个测试文件中的某个测试方法?

ruby test/functional/users_controller_test.rb -n test_should_show_user

5,既然测试是事务安全型的 ,那我可以把事务关闭测试吗 可以,添加

self.use_transactional_fixtures = false 即可

6,测试的时候 fixtures.yml 中数据 什么时候 被load到表中的呢

结果测试 只需要有test 定义就可以,哪怕

test ""  do
end

block中没有任何测试的东西,这个时候 数据已经 load 的数据库中

7, 用 rspec 有没有这个问题 同理, 一样存在这个问题

建议:

mysql 数据库表建议都是innodb引擎,innodb 确实比myisam有优势,后期的mysql版本默认用的都是innodb了,之前都是myisam默认的,上次编译安装mysql忘记把innodb编译进去了,需要重新编译,所以也才造就了这次的一系列折腾!

疑问:

rails 自带的测试 必须 innodb引擎吗?google 了下,毛也搜不到, here 但是实验下来确实是这样的.很是疑问

再经过探究,test 可以设置 事务型的和 非事务型的,innodb 两者都可以,myisam 就必须是 非事务型的了

class UsersControllerTest < ActionController::TestCase
  self.use_transactional_fixtures = false
end

ref: http://stackoverflow.com/questions/2616173/rails-unit-testing-with-myisam-tables http://guides.rubyonrails.org/testing.html http://ar.rubyonrails.org/classes/Fixtures.html http://stackoverflow.com/questions/763881/automatic-associations-in-ruby-on-rails-fixtures http://railstips.org/blog/archives/2006/06/30/assert_difference-for-easier-tests/ http://www.softiesonrails.com/2007/3/7/how-to-run-a-single-test-from-the-command-line http://www.javaeye.com/wiki/Rails-EveryDay/1047-Rails宝典八十一式:Rails2.0之Fixtures尝鲜


rails 脚本里调用helper方法

Posted by wxianfeng Sun, 01 Feb 2009 14:21:00 GMT

环境:ubuntu 10.10 + ruby 1.8.7 + rails 2.3.5

有这样一个需求,以前写script都是一般调用Model里的方法,这次不同,调用的是helper方法,需要批量的更新数据库中的一个字段,于是写了个script,然后用script/runner执行,需要调用一个helper方法,出现下面了这个错误:

NoMethodError: undefined method `url_for' for nil:NilClass

最后发现原因是 helper 方法里有 link_to 导致的,那怎么办?也不好调试,runner输出的玩意也跟踪不到错误的地方,只是抛出异常,于是通过我不断的debug到rails源码里,查出到底谁是nil了,最终找到了禍手,并且现在可以很方便的在script里用helper任何方法了,下面我把过程重现一遍.

建好demo project:

rails scaffold -d mysql
cd scaffold
rake db:create
ruby script/generate scaffold user name:string password:string birthday_at:datetime
rake db:migrate
rake rails:freeze:gems  # 把rails打包进去 ,为了方便查看rails源码和debug
ruby script/server
create 一条user数据

users_helper.rb 添加如下代码:

  def link_to_user
   # link_to "user_first","/users/show/#{User.first.id}"  这个不会报错,why? 后面会提到
    link_to "user_first",:controller => "users",:action=>"show",:id=>User.first
  end

script 代码: RAILSROOT/script/tools/callhelper.rb:

# at RAILS_ROOT run : ruby script/runner script/tools/call_helper.rb
p ApplicationController.helpers.link_to_user

调用 runner执行报错:

NoMethodError: undefined method `url_for' for nil:NilClass

debug 跟进去发现是link_to 的问题:

linkto 源码:(/usr/local/system/projects/scaffold/vendor/rails/actionpack/lib/actionview/helpers/url_helper.rb)

def link_to(*args, &block)

    <span class="r">if</span> block_given?
      options      = args.first || {}
      html_options = args.second
      concat(link_to(capture(&amp;block), options, html_options).html_safe!)
    <span class="r">else</span>
      name         = args.first
      options      = args.second || {}
      html_options = args.third

      url = url_for(options)

      <span class="r">if</span> html_options
        html_options = html_options.stringify_keys
        href = html_options[<span class="s"><span class="dl">'</span><span class="k">href</span><span class="dl">'</span></span>]
        convert_options_to_javascript!(html_options, url)
        tag_options = tag_options(html_options)
      <span class="r">else</span>
        tag_options = <span class="pc">nil</span>
      <span class="r">end</span>

      href_attr = <span class="s"><span class="dl">&quot;</span><span class="k">href=</span><span class="ch">\&quot;</span><span class="il"><span class="idl">#{</span>url<span class="idl">}</span></span><span class="ch">\&quot;</span><span class="dl">&quot;</span></span> <span class="r">unless</span> href
      <span class="s"><span class="dl">&quot;</span><span class="k">&lt;a </span><span class="il"><span class="idl">#{</span>href_attr<span class="idl">}</span></span><span class="il"><span class="idl">#{</span>tag_options<span class="idl">}</span></span><span class="k">&gt;</span><span class="il"><span class="idl">#{</span>name || url<span class="idl">}</span></span><span class="k">&lt;/a&gt;</span><span class="dl">&quot;</span></span>.html_safe!
    <span class="r">end</span>

end

里面又调用到了 urlfor 方法,urlfor 源码:

def url_for(options = {})

    options ||= {}
    url = <span class="r">case</span> options
    <span class="r">when</span> <span class="co">String</span>
      escape = <span class="pc">true</span>
      options
    <span class="r">when</span> <span class="co">Hash</span>
      options = { <span class="sy">:only_path</span> =&gt; options[<span class="sy">:host</span>].nil? }.update(options.symbolize_keys)
      escape  = options.key?(<span class="sy">:escape</span>) ? options.delete(<span class="sy">:escape</span>) : <span class="pc">true</span>
      <span class="iv">@controller</span>.send(<span class="sy">:url_for</span>, options)
    <span class="r">when</span> <span class="sy">:back</span>
      escape = <span class="pc">false</span>
      <span class="iv">@controller</span>.request.env[<span class="s"><span class="dl">&quot;</span><span class="k">HTTP_REFERER</span><span class="dl">&quot;</span></span>] || <span class="s"><span class="dl">'</span><span class="k">javascript:history.back()</span><span class="dl">'</span></span>
    <span class="r">else</span>
      escape = <span class="pc">false</span>
      polymorphic_path(options)
    <span class="r">end</span>

    escape ? escape_once(url) : url
  <span class="r">end</span></pre></div>

执行的是 Hash 那一块,好,就在这设断点,最后发现了真正的问题 原来是 @controller.send(:urlfor, options) 这里报错的,@controller 为nil,之前为什么 linkto 后面给string不报错,url_for源码已经告诉你了

那么如何解决,修改call_helper.rb:

# at RAILS_ROOT run : ruby script/runner script/tools/call_helper.rb
include ActionController::UrlWriter
p ApplicationController.helpers.link_to_user

ok,这下就可以了.

ActionController::UrlWriter 中url_for 源码;

def url_for(options)

  options = <span class="pc">self</span>.class.default_url_options.merge(options)

  url = <span class="s"><span class="dl">'</span><span class="dl">'</span></span>

  <span class="r">unless</span> options.delete(<span class="sy">:only_path</span>)
    url &lt;&lt; (options.delete(<span class="sy">:protocol</span>) || <span class="s"><span class="dl">'</span><span class="k">http</span><span class="dl">'</span></span>)
    url &lt;&lt; <span class="s"><span class="dl">'</span><span class="k">://</span><span class="dl">'</span></span> <span class="r">unless</span> url.match(<span class="s"><span class="dl">&quot;</span><span class="k">://</span><span class="dl">&quot;</span></span>)

    raise <span class="s"><span class="dl">&quot;</span><span class="k">Missing host to link to! Please provide :host parameter or set default_url_options[:host]</span><span class="dl">&quot;</span></span> <span class="r">unless</span> options[<span class="sy">:host</span>]

    url &lt;&lt; options.delete(<span class="sy">:host</span>)
    url &lt;&lt; <span class="s"><span class="dl">&quot;</span><span class="k">:</span><span class="il"><span class="idl">#{</span>options.delete(<span class="sy">:port</span>)<span class="idl">}</span></span><span class="dl">&quot;</span></span> <span class="r">if</span> options.key?(<span class="sy">:port</span>)
  <span class="r">else</span>
    <span class="c"># Delete the unused options to prevent their appearance in the query string.</span>
    [<span class="sy">:protocol</span>, <span class="sy">:host</span>, <span class="sy">:port</span>, <span class="sy">:skip_relative_url_root</span>].each { |k| options.delete(k) }
  <span class="r">end</span>
  trailing_slash = options.delete(<span class="sy">:trailing_slash</span>) <span class="r">if</span> options.key?(<span class="sy">:trailing_slash</span>)
  url &lt;&lt; <span class="co">ActionController</span>::<span class="co">Base</span>.relative_url_root.to_s <span class="r">unless</span> options[<span class="sy">:skip_relative_url_root</span>]
  anchor = <span class="s"><span class="dl">&quot;</span><span class="k">#</span><span class="il"><span class="idl">#{</span><span class="co">CGI</span>.escape options.delete(<span class="sy">:anchor</span>).to_param.to_s<span class="idl">}</span></span><span class="dl">&quot;</span></span> <span class="r">if</span> options[<span class="sy">:anchor</span>]
  generated = <span class="co">Routing</span>::<span class="co">Routes</span>.generate(options, {})
  url &lt;&lt; (trailing_slash ? generated.sub(<span class="rx"><span class="dl">/</span><span class="ch">\?</span><span class="k">|</span><span class="ch">\z</span><span class="dl">/</span></span>) { <span class="s"><span class="dl">&quot;</span><span class="k">/</span><span class="dl">&quot;</span></span> + <span class="gv">$&amp;</span> } : generated)
  url &lt;&lt; anchor <span class="r">if</span> anchor

  url
<span class="r">end</span>

end

发现这个 urlfor 已经和 actonview 下的不一样了,完全重写了,所以 当你include ActionController::UrlWriter 后,调用的是这个 urlfor 方法,而不是之前的那个 urlfor 了........这两个方法 源码 还有待进一步研究,之前nil问题解决了.

期间遇到的问题:

1,在rails console 里可以直接调用 helper 方法,为什么在用runner执行的script不可以找到helper变量?

console 中helper:

wxianfeng@ubuntu:/usr/local/system/projects/scaffold$ ruby script/console 
Loading development environment (Rails 2.3.5)
ruby-1.8.7-p302 > helper
 => #<ActionView::Base:0xb71dc038 @assigns_added=nil, @_first_render=nil, @assigns={}, @view_paths=[], @helpers=#<ActionView::Base::ProxyModule:0xb71dbf98>, @controller=nil, @_current_render=nil> 

那我们就来看看 runner 和 console 到底有什么区别:

runner源码:

#!/usr/bin/env ruby
require File.expand_path('../../config/boot',  __FILE__)
require 'commands/runner

console源码:

#!/usr/bin/env ruby
require File.expand_path('../../config/boot',  __FILE__)
require 'commands/console'

commands/runner 源码:(/usr/local/system/projects/scaffold/vendor/rails/railties/lib/commands/runner.rb)

require 'optparse'

options = { :environment => (ENV['RAILS_ENV'] || "development").dup }
code_or_file = nil

ARGV.clone.options do |opts|
  script_name = File.basename($0)
  opts.banner = "Usage: #{$0} [options] ('Some.ruby(code)' or a filename)"

  opts.separator ""

  opts.on("-e", "--environment=name", String,
          "Specifies the environment for the runner to operate under (test/development/production).",
          "Default: development") { |v| options[:environment] = v }

  opts.separator ""

  opts.on("-h", "--help",
          "Show this help message.") { $stderr.puts opts; exit }

  if RUBY_PLATFORM !~ /mswin/
    opts.separator ""
    opts.separator "You can also use runner as a shebang line for your scripts like this:"
    opts.separator "-------------------------------------------------------------"
    opts.separator "#!/usr/bin/env #{File.expand_path($0)}"
    opts.separator ""
    opts.separator "Product.find(:all).each { |p| p.price *= 2 ; p.save! }"
    opts.separator "-------------------------------------------------------------"
  end

  opts.order! { |o| code_or_file ||= o } rescue retry
end

ARGV.delete(code_or_file)

ENV["RAILS_ENV"] = options[:environment]
RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV)

require RAILS_ROOT + '/config/environment'

begin
  if code_or_file.nil?
    $stderr.puts "Run '#{$0} -h' for help."
    exit 1
  elsif File.exist?(code_or_file)
    eval(File.read(code_or_file), nil, code_or_file)
  else
    eval(code_or_file)
  end
ensure
  if defined? Rails
    Rails.logger.flush if Rails.logger.respond_to?(:flush)
  end
end

commands/console.rb 源码 (/usr/local/system/projects/scaffold/vendor/rails/railties/lib/commands/console.rb)

irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'

require 'optparse'

options = { :sandbox => false, :irb => irb }
OptionParser.new do |opt|
  opt.banner = "Usage: console [environment] [options]"
  opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v }
  opt.on("--irb=[#{irb}]", 'Invoke a different irb.') { |v| options[:irb] = v }
  opt.on("--debugger", 'Enable ruby-debugging for the console.') { |v| options[:debugger] = v }
  opt.parse!(ARGV)
end

libs =  " -r irb/completion"
libs << %( -r "#{RAILS_ROOT}/config/environment")
libs << " -r console_app"
libs << " -r console_sandbox" if options[:sandbox]
libs << " -r console_with_helpers"

if options[:debugger]
  begin
    require 'ruby-debug'
    libs << " -r ruby-debug"
    puts "=> Debugger enabled"
  rescue Exception
    puts "You need to install ruby-debug to run the console in debugging mode. With gems, use 'gem install ruby-debug'"
    exit
  end
end

ENV['RAILS_ENV'] = case ARGV.first
  when "p"; "production"
  when "d"; "development"
  when "t"; "test"
  else
    ARGV.first || ENV['RAILS_ENV'] || 'development'
end

if options[:sandbox]
  puts "Loading #{ENV['RAILS_ENV']} environment in sandbox (Rails #{Rails.version})"
  puts "Any modifications you make will be rolled back on exit"
else
  puts "Loading #{ENV['RAILS_ENV']} environment (Rails #{Rails.version})"
end
exec "#{options[:irb]} #{libs} --simple-prompt"

可以发现 ,原来 console 里 加载了 consolewithhelpers ,另外 发现 其实 rails console就是 irb 只不过 加载了 一些lib

看看 consolewithhelpers 源码:(/usr/local/system/projects/scaffold/vendor/rails/railties/lib/consolewithhelpers.rb)

def helper
  @helper ||= ApplicationController.helpers
end

@controller = ApplicationController.new

so,看明白了把,改变原来的 script:

# at RAILS_ROOT run : ruby script/runner script/tools/call_helper.rb
include ActionController::UrlWriter
require 'console_with_helpers'
p helper.link_to_user

2,那我直接用ruby调用一个script可以吗,不用runner来执行,可以.

#!/usr/bin/env ruby
#
# author : wang.fl1429@gmail.com
# run at RAILS_ROOT :  ruby script/tools/call_helper_script.rb
require File.expand_path('../../../config/boot',  __FILE__)
# ENV['RAILS_ENV'] ||= 'production'
require RAILS_ROOT + '/config/environment'
include ActionController::UrlWriter
p ApplicationController.helpers.link_to_user

3,为什么一般长久大批量执行用 runner ,而不是直接用 ruby 执行

看上面rails runner.rb的源码 也没发现为什么 ,只是用了eval 来执行ruby代码, 改天 benchmark 一把看看 , 瞧瞧eval 的好处!,不用 rails runner 我们同样可以达到runner的效果:

ruby eval(File.read("/....../call_helper.rb"))

demo project download: here

SEE: http://kpumuk.info/ruby-on-rails/memo-6-using-named-routes-and-url_for-outside-the-controller-in-ruby-on-rails/


最终环境 : ubuntu 10.10 + ruby 1.8.7 + rails 2.3.5 + mysql 5.1
换了新工作,重新搭建开发环境,鉴于之前mysql遇到问题,总是不好解决,束手无策,甚至找不到 mysql 安装哪去了,所以这次主要想编译安装mysql,这样有利于了解mysql

1,下载ruby1.8.7 (http://www.ruby-lang.org/en/news/2010/08/16/ruby-1-8-7-p302-is-released/)

wget ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p302.tar.bz2

2,安装依赖的库

sudo apt-get install build-essential 
sudo apt-get install autoconf 
sudo apt-get install zlib1g-dev 
sudo apt-get install openssl libssl-dev

3,编译安装ruby

tar -jxvf ruby-1.8.7-p302.tar.bz2 
cd ruby-1.8.7-p302/
autoconf
./configure --prefix=/usr/local/system/ruby
make
sudo make install

4,ruby 加到PATH 中

vim ~/.bashrc
export PATH="$PATH:/usr/local/system/ruby/bin"

5,ruby -v

安装rubygems(http://rubyforge.org/frs/?group_id=126&release_id=43601)

1,下载

wget http://rubyforge.org/frs/download.php/70696/rubygems-1.3.7.tgz

2,解压

tar -zvxf rubygems-1.3.7.tgz

3,安装

cd rubygems-1.3.7/
wxianfeng@ubuntu:~/Desktop/rubygems-1.3.7$ ruby setup.rb 
ERROR:  While executing gem ... (Errno::EACCES)
    Permission denied - /usr/local/system/ruby/lib/ruby/site_ruby/1.8/rubygems
wxianfeng@ubuntu:~/Desktop/rubygems-1.3.7$ sudo ruby setup.rb 
[sudo] password for wxianfeng: 
sudo: ruby: command not found
改变权限
cd /usr/local
sudo chmod -R 777 system/ 
再次安装就ok了,不然你以后还会遇到这个问题

4,gem -v

安装rails 2.3.5

>gem install rails -v 2.3.5 --no-ri --no-rdoc

编译安装 mysql

1,下载并且编译安装
download (http://downloads.mysql.com/archives.php?p=mysql-5.1) 从archives 找到 ,注意下载的是source包

sudo apt-get install g++ libncurses5-dev # 安装依赖的库
sudo  groupadd mysql  # 建立mysql用户组
sudo  useradd -g mysql mysql # 添加用户mysql
tar -zvxf mysql-5.1.51.tar.gz 
cd mysql-5.1.51/
./configure --prefix=/usr/local/system/mysql --with-charset=utf8 --with-collation=utf8_general_ci --with-extra-charsets=latin1(all) --with-plugins=innobase(all)  # config 很重要,括号里是或者
make
sudo make install
sudo cp support-files/my-medium.cnf /etc/my.cnf # 配置文件

注意mysql sock文件安默认装在tmp路径下 /tmp/mysql.sock,编译的时候 —with-plugins=all 建议加上 不然你会发现编译后不支持innodb引擎,那就麻烦了!

2, 初始化数据库并修改目录权限

cd /usr/local/system/mysql
sudo bin/mysql_install_db --user=mysql
sudo chown -R root .
sudo chown -R mysql /usr/local/system/mysql/var
sudo chgrp -R mysql .

3,启动mysql

bin/mysqld_safe --user=mysql &
netstat -antup | grep 3306
sudo cp /usr/local/system/mysql/share/mysql/mysql.server /etc/init.d/mysql
sudo killall mysqld # kill 刚刚启动的mysql
sudo /etc/init.d/mysql start|stop|restart|status # 重启mysql

4,添加PATH

>vim ~/.bashrc
export PATH="$PATH:/usr/local/system/mysql/bin"
wxianfeng@ubuntu:/usr/local/system/mysql/bin$ mysql --version
mysql  Ver 14.14 Distrib 5.1.51, for pc-linux-gnu (i686) using  EditLine wrapper

5, 修改mysql root 密码

>sudo /etc/init.d/mysql start --skip-grant-tables
>mysql -uroot -p mysql
>update user set password=passsword('root') where user='root'; 
重启mysql
就ok了 root 账户的密码为root

6,怎么判断我的mysql是编译安装的

wxianfeng@ubuntu:~$ mysql --version
mysql  Ver 14.14 Distrib 5.1.51, for pc-linux-gnu (i686) using  EditLine wrapper

如果你是apt-get 安装的话就显示 for ubuntu-linux-gnu
yum 安装的话就显示 for redhat-linux-gnu 了

遇到的问题:

1,
>mysql -uroot -p
Can’t connect to local MySQL server through socket ‘/var/run/mysqld/mysqld.sock’ (2)

>mysql -uroot -p --socket=/tmp/mysql.sock 可以启动,所以是sock问题
后来发现 my.cnf 有两个配置文件,/etc/mysql/my.cnf 多了一个这个,里面sock路径指定不一样所致,rename 掉
>mv /etc/mysql/my.cnf /etc/mysql/my_cnf.bak

2,

Starting MySQL. * Manager of pid-file quit without updating file. 错误

wxianfeng@ubuntu:/usr/local/system/shell$ /etc/init.d/mysql start 
Starting MySQL. * Manager of pid-file quit without updating file.

需要加sudo

3,查看mysql data目录

root@ubuntu:/usr/local/system/mysql/var# ps auxf | grep mysql
root     25756  0.0  0.0   4012   764 pts/3    S+   15:32   0:00  |           \_ grep --color=auto mysql
root     25000  0.0  0.0   4904  1400 ?        S    14:22   0:00 /bin/sh /usr/local/system/mysql/bin/mysqld_safe --datadir=/usr/local/system/mysql/var --pid-file=/usr/local/system/mysql/var/ubuntu.pid
mysql    25106  0.0  0.8 119652 16568 ?        Sl   14:22   0:00  \_ /usr/local/system/mysql/libexec/mysqld --basedir=/usr/local/system/mysql --datadir=/usr/local/system/mysql/var --user=mysql --log-error=/usr/local/system/mysql/var/ubuntu.err --pid-file=/usr/local/system/mysql/var/ubuntu.pid --socket=/tmp/mysql.sock --port=3306

可以看出在var下 ,注意 var目录的用户是 mysql ,必须root用户才能cd进入

sudo su # 切换到root用户

4,启动rails project报错:因为mysql gem的版本过高的原因

uninitialized constant MysqlCompat::MysqlRes (NameError)
>sudo apt-get install libmysqlclient-dev
>gem install mysql -v=2.7 --no-ri --no-rdoc

SEE:
http://blog.sitearth.com/ubuntu%E4%B8%8A%E6%90%AD%E5%BB%BAlamp%E7%8E%AF%E5%A2%83%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94mysql%E6%BA%90%E7%A0%81%E5%AE%89%E8%A3%85%E5%8F%8A%E9%85%8D%E7%BD%AE/
http://51jsp.cn/html/ror/2010/0327/14859.html
see:http://qichunren.javaeye.com/blog/577556


rails + swfupload 文件上传 session 问题

Posted by wxianfeng Wed, 10 Nov 2010 13:43:00 GMT

环境:ruby 1.8.7 + rails 2.3.5 + swfupload

swfupload 上传文件很方便,支持多文件上传,但是在往往需要做知道这张图片是谁传的,这样就需要取当前登录的session,这样就会出现问题,

因为图片是通过flash传上来的,swfupload里的session和rails里的session不是同一个,理论上属于跨域问题了,所以rails取不到session值,所以必须解决session问题,网络上大家一般都是通过修改session来解决的,即吧session的参数从swfupload里给从传到后台,方可验证通过,eg: http://huacnlee.com/blog/rails-multi-files-upload-with-swfupload

我是通过关闭session,通过参数来验证的,例如传login名字,得到当前是哪个用户:

controller:

class CkeditorController < ApplicationController
skip_before_filter :verify_authenticity_token, :only => [:create]
session :off , :only => [:create]

def create
   @record.user ||= User.find_by_login(params[:login])
end

end

view 页面上:注意中文注释部分

<script>
window.onload = function() {
                upload1 = new SWFUpload({
                        // Backend Settings
                        upload_url: "/ckeditor/create",
                        post_params: {"login" : <%= current_user.login %>,"dir":dir}, // 这里很重要

                        // File Upload Settings
                        file_size_limit : "102400",        // 100MB
                        file_types : "*.*",
                        file_types_description : "All Files",
                        file_upload_limit : 100,
                        file_queue_limit : 0,

                        // Event Handler Settings (all my handlers are in the Handler.js file)
                        swfupload_preload_handler : preLoad,
                        swfupload_load_failed_handler : loadFailed,
                        file_dialog_start_handler : fileDialogStart,
                        file_queued_handler : fileQueued,
                        file_queue_error_handler : fileQueueError,
                        file_dialog_complete_handler : fileDialogComplete,
                        upload_start_handler : uploadStart,
                        upload_progress_handler : uploadProgress,
                        upload_error_handler : uploadError,
                        upload_success_handler : uploadSuccess,
                        upload_complete_handler : uploadComplete,

                        // Button Settings
                        button_image_url : "/images/XPButtonUploadText_61x22.png",
                        button_placeholder_id : "spanButtonPlaceholder1",
                        button_width: 61,
                        button_height: 22,

                        // Flash Settings
                        flash_url : "/javascripts/swfupload/swfupload.swf",
                        flash9_url : "/javascripts/swfupload/swfupload_fp9.swf",


                        custom_settings : {
                                progressTarget : "fsUploadProgress1",
                                cancelButtonId : "btnCancel1"
                        },

                        // Debug Settings
                        debug: false
                });

        }
</script>

大胡子Katz离开Rails社区

Posted by wxianfeng Thu, 16 Sep 2010 03:46:00 GMT

Rails社区核心人物之一Katz今天离开了engineyard,离开了rails社区,有点遗憾,这个人我想rails社区的fans都应该知道,katz在kungfurails的时候来过中国,并且作了rails3的演讲,视频见此:
http://v.youku.com/v_show/id_XMTI4NDQ2OTIw.html,katz做过很多我们耳熟能详的项目,Rails,Merb,jQuery,datamapper 等等,并且最近刚发布的rails3主要贡献者就是katz,把merb的想法带入了rails,Yehuda Katz 在blog里说明离开的原因和去向,并且说明了未来三年的计划,他的去向是去开发 sproutcore了,sproutcore是apple公司开源出来的,然而sproutcore的创造者在今年7月8日离开了apple公司,创办了自己的公司,依据sproutcore开发更好的桌面体验般的webapp,sproutcore创始人准备将sproutcore打造为html5的框架,为移动互联网,IPAD等提供良好的应用体验,看来HTML5也是大势所趋啊….

无论怎样,Good luck Katz,必定你为rails社区贡献了很多,katz承诺虽然离开了engineyard,但是rails3.1的开发工作还将由他主导,说不定也因为他rails+sproutcore会很好的结合起来呢,over。。。

叽歪一下祝贺Katz进入HTML5 team

See:
http://www.javaeye.com/news/17731-yehuda-kats-leave-engine-yard

http://www.infoq.com/cn/news/2009/09/sproutcore-1-0


rails + mongodb 入门demo

Posted by wxianfeng Thu, 15 Jul 2010 05:49:00 GMT

NoSQL 渐渐火了起来,最近同事也在讨论这个问题,一不做,二不休,就学习了下,mongodb是采用C++写的,开源,免费,目前已经有公司(10gen)在背后提供商业支持,文件之小,windows下载二进

制包解压后是45M左右,linux下的就更小了,读写操作性能之高,即使是在大数据量,大并发的情况下,也能保持良好的性能,可以索性,例如可以结合memcached,sphinx使用,哇。。。cool。。。
这两天利用mongodb + sinatra 做了个短地址服务网址,当然只是自娱自乐一把而已,domain太ugly了,地址是 http://url.wxianfeng.com,这编来介绍

rails中使用mongodb的入门教程,是railscasts上的那篇教程,但是有许多注意点,在此重点指出。
在开始这个demo前,如果你对mongodb还不熟的话,建议先学习下mongodb,就想先学习下mysql 一样,掌握一些基本的操作过程
我的环境:windows xp + rails 2.3.5 + mongodb 1.4.4 + mongo_mapper 0.8.2

1,安装需要的gem包

gem install mongo_mapper

如果顺利的话,可以安装成功,并且包依赖的其他gem也都同时安装上了 , 例如我在美国的linode服务器上,直接执行上面命令,就安装成功了,但是在大陆无法安装成功,至少我是经过下面的安装步

骤成功的:

gem install jnunemaker-validatable --source http://gemcutter.org
gem install bson --source http://rubygems.org
gem install mongo --source http://gemcutter.org
gem install plucky --source http://gemcutter.org
gem install mongo_mapper --source http://gemcutter.org

2,新建rails工程,修改你的enviroment.rb

config.gem "mongo_mapper",:source=>"http://gemcutter.org"
config.frameworks -= [:active_record] # - 代表不加载 active_record

在这里需要注意,当你需要使用scaffold生成代码时,必须把 – 给去掉,来加载active_record,因为scaffold默认需要使用activerecord,当你开启server时,再把 – 号给加上

4,修改 database.yml

development:
  adapter: mysql
  encoding: utf8
  reconnect: false
  database: trade_supplier_development
#  pool: 5
  username: root
  password: root
#  socket: /var/run/mysqld/mysqld.sock
  host: localhost

需要保证mysql的username,和 password正确,还是为了能使用 scaffold

5,添加 配置,在 config/initializers 下新建 mongo_config.rb

MongoMapper.database = "todo-#{Rails.env}"  

配置连接时的db name,start server时不会建立 db,当插入数据时会 同时建立 db 和 collection

6,修改Model

class Project 
  include MongoMapper::Document
  key :name, String, :required => true
  key :priority, Integer
  many :tasks 
end
class Task 
  include MongoMapper::Document
  key :project_id, ObjectId
  key :name, String
  key :completed, Boolean
  belongs_to :project
end

核心部分就是Model部分,db中的字段是根据 key来动态改变的,没有shama,没有migrate,当你添加新的key时,原有的数据不会改变,上面的两个model是1对多的关系,例如mongo——mapper 可以来

实现 1对1,1对多,多对多的关系,并且和 activerecord操作非常之相像

7,启动 mongodb
进入mongodb bin 目录下

mongod.exe --dbpath E:/mongodb/data

8,启动server

thin start

这一步会提示安装 bson_ext gem包, 我在linux中安装成功了,在windows无法顺利安装,貌似只能在UNIX中安装,需要make编译,但是只要你安装bson包,程序都可以顺利跑起来,BSON是json的binary

形式,这样会节省存储空间,最重要的是减少网络消耗

9,ok

http://localhost:3000/projects 瞧瞧

DEMO源码下载:
http://www.uushare.com/user/fl1429/file/3274129

See:
http://asciicasts.com/episodes/194-mongodb-and-mongomapper