Rails 判断mysql数据库是否存在

Posted by wxianfeng Thu, 16 Feb 2012 09:56:00 GMT

环境: Rails 3.0.3
Rails中判断表是否存在,表的某个字段是否存在都提供了API,但是如何判断数据库存在,没有提供api

可以使用下面的方法判断出来:

> ActiveRecord::Base.connection.execute("USE INFORMATION_SCHEMA") # 连接mysql INFORMATION_SCHEMA 数据库
>ActiveRecord::Base.connection.execute("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'db'").to_a
=>[["db"]] # 存在db
>ActiveRecord::Base.connection.execute("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'db1'").to_a
=>[] # 不存在db1

北京 798 Ruby/Rails 活动

Posted by wxianfeng Sat, 30 Jul 2011 06:36:00 GMT

时间: 2011-07-24
收获:
发现北京ROR的公司不是一般的多,签到单上看到N多公司,技术上没有太大收获,都是介绍性的,没有实战性的,内容主要涉及: mirah , Mongodb,Erlang,Grape

进程:http://www.surveymonkey.com/s/MSY2L7T

PS : 798 很好玩,很有艺术特色

现场:

093

798 入口

059

Ruby活动地方

054

Rails rumble 创始人

049

现场job board

067

现场

061

清一色老外,清一色Mac

056

介绍Mirah

045

798

083

798

043

798

MORE


Rails 深入学习 Migration limit

Posted by wxianfeng Sun, 17 Apr 2011 09:09:00 GMT

环境:ruby1.9.2 + rails 3.0.3

一直以为

add_column :users , :age  ,  :integer  , :limit=> 4  

在数据库里对应的类型是 int(4)

其实是错误的!!!

看下 limit的说明文档:

:limit - Requests a maximum column length. This is number of characters for :string and :text columns and number of bytes for :binary and :integer columns.

对于 string 和 text 比较简单,例如

add_column :users,:name , :string , :limit=> 60 

那么数据库中的 类型就是 varchar(60)

对于 binary 和 integer 的就不一样了 , 表示的是字节数 , 但是 :limit =>11 不是表示 11个字节的整数 , 是4 个字节整数

对应关系:

:limit          Numeric Type          Column Size
1                      tinyint                  1 byte
2                      smallint                  2 bytes
3                      mediumint             3 bytes
nil, 4, 11      int(11)                  4 bytes
5 to 8              bigint                  8 bytes

而mysql的integer类型(也是int型) 表示大小如下:

详细 here

rails 里的实现代码 here

核心代码:

 # Maps logical Rails types to MySQL-specific data types.
      def type_to_sql(type, limit = nil, precision = nil, scale = nil)
        return super unless type.to_s == 'integer'

        case limit
        when 1; 'tinyint'
        when 2; 'smallint'
        when 3; 'mediumint'
        when nil, 4, 11; 'int(11)'  # compatibility with MySQL default
        when 5..8; 'bigint'
        else raise(ActiveRecordError, "No integer type has byte size #{limit}")
        end
      end

从上面代码可以看出, 当limit 为 nil,4,11 的时候 , mysql的类型就是 int(11), 也就是常在 migration看到的 integer , :limit=>11

另外可以从已经有的表中得到字段的字节数

ruby-1.9.2-p0 > ActiveRecord::Migration.add_column :forms , :int9, :integer , :limit=>11
-- add_column(:forms, :int9, :integer, {:limit=>11})
  SQL (300.9ms)  ALTER TABLE `forms` ADD `int9` int(11)
   -> 0.3012s
 => nil 
ruby-1.9.2-p0 > Form.reset_column_information
 => nil 
ruby-1.9.2-p0 > Form.columns_hash["int9"].limit 
 => 4 
ruby-1.9.2-p0 > Form.columns_hash["int9"]
 => #<ActiveRecord::ConnectionAdapters::Mysql2Column:0xb8407c8 @null=true, @sql_type="int(11)", @name="int9", @scale=nil, @precision=nil, @limit=4, @type=:integer, @default=nil, @primary=false>

看到没,在迁移的时候 指定 :limit => 11 , 但是 通过 columns_hash 得到的 limit 确是4 , 也就是说 mysql 的 Column Size 是4 bytes , 如果指定 :limit => nil 或者 :limit=>4 得到的 都是 4 bytes

因为 rails 里面 主键id 默认是有符号的 int(11) ,所以 mysql的主键最大id 是 2147483647 , 如果改成无符号的 最大可以到 4294967295

ruby 如何得到 整数的字节数?

size 方法

ruby-1.9.2-p0 > 10_000_000_000.size
 => 8 
ruby-1.9.2-p0 > 1.size
 => 4 

如何让我的integer类型的字段变成无符号的?

ruby-1.9.2-p0 > ActiveRecord::Migration.add_column :forms , :int8, "integer unsigned"
-- add_column(:forms, :int8, "integer unsigned")
  SQL (297.4ms)  ALTER TABLE `forms` ADD `int8` integer unsigned
   -> 0.2976s
 => nil 

如何正确 添加mysql中int(4)类型的字段?

ruby-1.9.2-p0 > ActiveRecord::Migration.add_column :users , :number , "int(4)"
-- add_column(:users, :number, "int(4)")
  SQL (280.4ms)  ALTER TABLE `users` ADD `number` int(4)
   -> 0.2808s

最后看下 migration 类型 对应 数据库类型的关系:

SEE:

http://www.snowgiraffe.com/tech/366/rails-migrations-mysql-unsigned-integers-primary-keys-and-a-lot-of-fun-times/
http://thewebfellas.com/blog/2008/6/2/unsigned-integers-for-mysql-on-rails
http://www.kuqin.com/rubycndocument/man/built-in-class/class_object_numeric_integer.html


Rails 注意索引 name

Posted by wxianfeng Fri, 15 Apr 2011 05:56:00 GMT

测试环境:rails 2.X + rails 3.0.3

今天发现 rails api上索引name的命名规则是错误的,here

测试:

ruby-1.9.2-p0 > ActiveRecord::Migration.add_index :users , :email
-- add_index(:users, :email)
  SQL (0.4ms)  SHOW KEYS FROM `users`
  SQL (367.1ms)  CREATE INDEX `index_users_on_email` ON `users` (`email`)
   -> 0.3680s

按照 文档上写的应该是 users_email ,但是实际上是 index_users_on_email

ruby-1.9.2-p0 > ActiveRecord::Migration.add_index :users , :name , :unique=>true
-- add_index(:users, :name, {:unique=>true})
  SQL (0.3ms)  SHOW KEYS FROM `users`
  SQL (340.4ms)  CREATE UNIQUE INDEX `index_users_on_name` ON `users` (`name`)
   -> 0.3413s

按照文档写的应该是 users_name ,但是实际上是 index_users_on_name

ruby-1.9.2-p0 > ActiveRecord::Migration.add_index :users , [:login,:name] 
-- add_index(:users, [:login, :name])
  SQL (0.3ms)  SHOW KEYS FROM `users`
  SQL (314.3ms)  CREATE INDEX `index_users_on_login_and_name` ON `users` (`login`, `name`)
   -> 0.3152s

按照文档下写的应该是 users_login_name 而实际是 index_users_on_login_and_name

为什么 name 如此重要,因为 remove_index 也是根据name 来的,所以 规则必须记住!

例如文档上一个demo:

# Remove the suppliers_name_index in the suppliers table.
#   remove_index :suppliers, :name

其实 是删除 name 为 index_suppliers_on_name 的索引 ,而不是文档上说的 suppliers_name_index

文档上是错误的,从 源码中 也可以看出来

idnex_name method 源码:

 def index_name(table_name, options) #:nodoc:
        if Hash === options # legacy support
          if options[:column]
            "index_#{table_name}_on_#{Array.wrap(options[:column]) * '_and_'}" # HERE
          elsif options[:name]
            options[:name]
          else
            raise ArgumentError, "You must specify the index name"
          end
        else
          index_name(table_name, :column => options)
        end
      end

rails查看 表的索引:

ruby-1.9.2-p0 > ActiveRecord::Migration.indexes("users") # users 是表名

Rails console 测试路由

Posted by wxianfeng Wed, 02 Mar 2011 06:00:00 GMT

环境:ruby 1.9.2 + rails 3.0.3

rails console 用起来还是很爽的,路由也可以在console下使用 , 甚至可以 get , post , 下面介绍惯用手法:

1,rake 查看routes

>rake routes

2,console 下查看 routes

Rails.application.routes.routes # rails 2.x 使用 ActionController::Routing::Routes.routes 

3, 查看 root(routes)

ruby-1.9.2-p0 > app.root_path
 => "/" 
ruby-1.9.2-p0 > app.root_url
 => "http://www.example.com/" 
ruby-1.9.2-p0 > app.host = "www.wxianfeng.com"
 => "www.wxianfeng.com" 
ruby-1.9.2-p0 > app.root_url
 => "http://www.wxianfeng.com/" 

4,查看资源 路由

ruby-1.9.2-p0 >   user = User.first
  User Load (0.3ms)  SELECT `users`.* FROM `users` LIMIT 1
 => #<User id: 1, login: "entos", name: "", email: "entos@entos.com", crypted_password: "3dea29b4e40bc9a70bb63678678c5ff37fe49753", salt: "2ec7e5db7f3ce5de61f1add8275b674dbd2770dc", remember_token: nil, remember_token_expires_at: nil, activation_code: nil, activated_at: nil, status: 2, suspend_at: nil, avatar_id: nil, orgunit_id: nil, mobile_phone: nil, last_login_at: nil, language: nil, options: nil, created_at: "2011-03-01 07:42:37", updated_at: "2011-03-01 07:42:37"> 
ruby-1.9.2-p0 > app.user_path(user)
 => "/users/1" 
ruby-1.9.2-p0 > app.users_path
 => "/users" 
ruby-1.9.2-p0 > app.new_user_path
 => "/users/new" 
ruby-1.9.2-p0 > app.edit_user_path(:id=>user.id)
 => "/users/1/edit" 
ruby-1.9.2-p0 > app.users_url
 => "http://www.wxianfeng.com/users" 

5,不使用app调用

ruby-1.9.2-p0 > include ActionController::UrlWriter
 => Object 
ruby-1.9.2-p0 > default_url_options[:host] = "wxianfeng.com"
 => "wxianfeng.com" 
ruby-1.9.2-p0 > users_url
 => "http://wxianfeng.com/users"

6,path 和 route Hash 互转

ruby-1.9.2-p0 > r = Rails.application.routes
ruby-1.9.2-p0 > r.generate :controller => "users" , :action=>"new"
 => "/signup" 
ruby-1.9.2-p0 > r.generate :controller => "users" , :action=>"edit" , :id=>1
 => "/users/1/edit" 
ruby-1.9.2-p0 > r.recognize_path "/users/index"
 => {:action=>"show", :controller=>"users", :id=>"index"} 
ruby-1.9.2-p0 > r.recognize_path "/users",:method=>"post"
 => {:action=>"create", :controller=>"users"} 

7,get ,post

模拟get访问首页,没登录 然后跳转到了/login , 然后 post 提交登录 成功

ruby-1.9.2-p0 > app.class
 => ActionDispatch::Integration::Session 
ruby-1.9.2-p0 > app.get "/"
 => 302 
ruby-1.9.2-p0 > app.controller.params
 => {"controller"=>"welcome", "action"=>"index"} 
ruby-1.9.2-p0 > app.response.redirect_url
 => "http://www.example.com/login" 
ruby-1.9.2-p0 > app.post "/session" , {:login=>"entos",:password=>"netposa"}
  SQL (0.3ms)  SHOW TABLES
  User Load (0.2ms)  SELECT `users`.* FROM `users` WHERE (status = 2) AND (`users`.`login` = 'entos') LIMIT 1
  SQL (0.1ms)  BEGIN
  User Load (0.3ms)  SELECT `users`.* FROM `users` WHERE (`users`.`id` = 1) LIMIT 1
  SQL (0.0ms)  COMMIT
 => 302 
ruby-1.9.2-p0 > app.controller.params
 => {"login"=>"entos", "password"=>"netposa", "action"=>"create", "controller"=>"sessions"} 
ruby-1.9.2-p0 > app.session[:user_id]
 => 1 
ruby-1.9.2-p0 > app.cookies
 => #<Rack::Test::CookieJar:0xb010120 @default_host="www.example.com", @cookies=[#<Rack::Test::Cookie:0x9b726f0 @default_host="www.example.com", @name_value_raw="_ent_os_session=BAh7CEkiD3Nlc3Npb25faWQGOgZFRiIlMzM4ZTdhYzU4OTY3NDhmMmZmMGFhNDkyYTExZWVmOThJIgx1c2VyX2lkBjsARmkGSSIKZmxhc2gGOwBGSUM6JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoewY6C25vdGljZUkiG0xvZ2dlZCBpbiBzdWNjZXNzZnVsbHkGOwBUBjoKQHVzZWRvOghTZXQGOgpAaGFzaHsA--d8652cbfebcae436e64a824d7ac2f64a81aa6619", @name="_ent_os_session", @value="BAh7CEkiD3Nlc3Npb25faWQGOgZFRiIlMzM4ZTdhYzU4OTY3NDhmMmZmMGFhNDkyYTExZWVmOThJIgx1c2VyX2lkBjsARmkGSSIKZmxhc2gGOwBGSUM6JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoewY6C25vdGljZUkiG0xvZ2dlZCBpbiBzdWNjZXNzZnVsbHkGOwBUBjoKQHVzZWRvOghTZXQGOgpAaGFzaHsA--d8652cbfebcae436e64a824d7ac2f64a81aa6619", @options={"path"=>"/", "HttpOnly"=>nil, "domain"=>"www.example.com"}>, #<Rack::Test::Cookie:0x9b826f4 @default_host="www.example.com", @name_value_raw="auth_token=", @name="auth_token", @value="", @options={"path"=>"/", "domain"=>"www.example.com"}>]> 
ruby-1.9.2-p0 > app.response.redirect_url
 => "http://www.example.com/" 
ruby-1.9.2-p0 > app.flash
 => {:notice=>"Logged in successfully"} 
ruby-1.9.2-p0 >

甚至 你还可以 ajax 异步提交

>> app.xml_http_request "/store/add_to_cart", :id => 1
=> 200

8,分配一个 实例变量

>>app.assigns[:foo] = “bar”

SEE

http://clarkware.com/blog/2006/04/04/running-your-rails-app-headless
http://blog.zobie.com/2008/11/testing-routes-in-rails/
http://railstech.com/2010/06/routes-testing-in-rails/
http://stuartsierra.com/2008/01/08/testing-named-routes-in-the-rails-console


rails params[:id] 实现原理

Posted by wxianfeng Fri, 11 Feb 2011 05:36:00 GMT

环境:ruby1.9.2 + rails 3.0.3

我们知道 params 返回的是一个 hash , 例如 {"id"=>1} ,那为什么 params[:id] = 1 ,而不是 nil 呢 ?
irb下测试一下:

ruby-1.9.2-p0 > h={"id"=>1}
 => {"id"=>1} 
ruby-1.9.2-p0 > h[:id]
 => nil 

带着这个疑问,设置断点 ,debug 进rails 源码 , 发现了 原因,

1,跟到了 params 方法源码:

def params
@_params ||= request.parameters
end

给 @_params 设置Watch , 发现如下 :

发现 @_params 的class 是 ActiveSupport::HashWithIndifferentAccess
也就是说

params.class # => ActiveSupport::HashWithIndifferentAccess

2,继续 F7 跟进去

跟到了这个文件 的 default方法

require 'active_support/core_ext/hash/keys'

# This class has dubious semantics and we only have it so that
# people can write params[:key] instead of params['key']
# and they get the same value for both keys.

module ActiveSupport
  class HashWithIndifferentAccess < Hash
    def extractable_options?
      true
    end

    def initialize(constructor = {})
      if constructor.is_a?(Hash)
        super()
        update(constructor)
      else
        super(constructor)
      end
    end

    def default(key = nil)
      if key.is_a?(Symbol) && include?(key = key.to_s)
        self[key]
      else
        super
      end
    end

    def self.new_from_hash_copying_default(hash)
      ActiveSupport::HashWithIndifferentAccess.new(hash).tap do |new_hash|
        new_hash.default = hash.default
      end
    end

    alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
    alias_method :regular_update, :update unless method_defined?(:regular_update)

    # Assigns a new value to the hash:
    #
    #   hash = HashWithIndifferentAccess.new
    #   hash[:key] = "value"
    #
    def []=(key, value)
      regular_writer(convert_key(key), convert_value(value))
    end

    alias_method :store, :[]=

    # Updates the instantized hash with values from the second:
    #
    #   hash_1 = HashWithIndifferentAccess.new
    #   hash_1[:key] = "value"
    #
    #   hash_2 = HashWithIndifferentAccess.new
    #   hash_2[:key] = "New Value!"
    #
    #   hash_1.update(hash_2) # => {"key"=>"New Value!"}
    #
    def update(other_hash)
      other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
      self
    end

    alias_method :merge!, :update

    # Checks the hash for a key matching the argument passed in:
    #
    #   hash = HashWithIndifferentAccess.new
    #   hash["key"] = "value"
    #   hash.key? :key  # => true
    #   hash.key? "key" # => true
    #
    def key?(key)
      super(convert_key(key))
    end

    alias_method :include?, :key?
    alias_method :has_key?, :key?
    alias_method :member?, :key?

    # Fetches the value for the specified key, same as doing hash[key]
    def fetch(key, *extras)
      super(convert_key(key), *extras)
    end

    # Returns an array of the values at the specified indices:
    #
    #   hash = HashWithIndifferentAccess.new
    #   hash[:a] = "x"
    #   hash[:b] = "y"
    #   hash.values_at("a", "b") # => ["x", "y"]
    #
    def values_at(*indices)
      indices.collect {|key| self[convert_key(key)]}
    end

    # Returns an exact copy of the hash.
    def dup
      HashWithIndifferentAccess.new(self)
    end

    # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
    # Does not overwrite the existing hash.
    def merge(hash)
      self.dup.update(hash)
    end

    # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
    # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess.
    def reverse_merge(other_hash)
      super self.class.new_from_hash_copying_default(other_hash)
    end

    def reverse_merge!(other_hash)
      replace(reverse_merge( other_hash ))
    end

    # Removes a specified key from the hash.
    def delete(key)
      super(convert_key(key))
    end

    def stringify_keys!; self end
    def stringify_keys; dup end
    undef :symbolize_keys!
    def symbolize_keys; to_hash.symbolize_keys end
    def to_options!; self end

    # Convert to a Hash with String keys.
    def to_hash
      Hash.new(default).merge!(self)
    end

    protected
      def convert_key(key)
        key.kind_of?(Symbol) ? key.to_s : key
      end

      def convert_value(value)
        case value
        when Hash
          self.class.new_from_hash_copying_default(value)
        when Array
          value.collect { |e| e.is_a?(Hash) ? self.class.new_from_hash_copying_default(e) : e }
        else
          value
        end
      end
  end
end

HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess

好了 所有 实现的原理都在 这个文件里了,HashWithIndifferentAccess是 Hash的子类,其中覆盖了default 方法,Hash当找不到 hash 的 key 时 会寻找default值,即执行 default 方法 , so ….

Hash#default 用法demo:

ruby-1.9.2-p0 > h={}
 => {} 
ruby-1.9.2-p0 > h.default=1
 => 1 
ruby-1.9.2-p0 > h[:a]
 => 1 

核心代码:

def default(key = nil)
if key.is_a?(Symbol) && include?(key = key.to_s)
self[key]
else
super
end
end

当是symbol 时 转化为 string , 然后 self[string_key]

举一反三:

DEMO:

class MyHash < Hash

  def initialize(constructor = {})
    if constructor.is_a?(Hash)
      super()
      update(constructor) # Hash#update == Hash#merge!
    else
      super(constructor)
    end
  end

  def default(key = nil)
    p key #=> :id
    if key.is_a?(Symbol) && include?(key = key.to_s) # Hash#include? = Hash#has_key? = Hash#member? = Hash#key?
      p key #=> "id"
      self[key]
    else
      super
    end
  end
end

h = MyHash.new({"id"=>1})
p h #=> {"id"=>1}
p h.class #=> MyHash
p h[:id] #=> 1
p h["id"] #=> 1

SEE:

http://lukaszwrobel.pl/blog/ruby-hash-default-value


Rails try method

Posted by wxianfeng Fri, 14 Jan 2011 08:20:00 GMT

环境:ruby 1.9.2 + rails 3.0.3

我们经常会有这样的操作:

user = User.find_by_login("wxianfeng")  # =>  nil 
user.name # => NoMethodError: undefined method `name' for nil:NilClass

假如 login 为 wxianfeng 不存在 ,会报错:

NoMethodError: undefined method `name' for nil:NilClass

那么建议使用 try 方法避免报错,try 返回的是 nil

user.try(:name) # =>nil 

也就相当于

nil.try(:name) # => nil

看下源码: here

其实就是调用了 __send__ 方法 , __send__ 方法 和 send 方法等价 , 只不过 __send__ 方法 为了防止 有已经存在的 send 方法 , nil 的话 调用 NilClass 的 try 方法

另外 发现 github上 try方法已经重新写了 ,如下: here

class Object
  # Invokes the method identified by the symbol +method+, passing it any arguments
  # and/or the block specified, just like the regular Ruby <tt>Object#send</tt> does.
  #
  # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised
  # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass.
  #
  # If try is called without a method to call, it will yield any given block with the object.
  #
  # ==== Examples
  #
  # Without try
  # @person && @person.name
  # or
  # @person ? @person.name : nil
  #
  # With try
  # @person.try(:name)
  #
  # +try+ also accepts arguments and/or a block, for the method it is trying
  # Person.try(:find, 1)
  # @people.try(:collect) {|p| p.name}
  #
  # Without a method argument try will yield to the block unless the reciever is nil.
  # @person.try { |p| "#{p.first_name} #{p.last_name}" }
  #--
  # +try+ behaves like +Object#send+, unless called on +NilClass+.
  def try(*a, &b)
    if a.empty? && block_given?
      yield self
    else
      __send__(*a, &b)
    end
  end
end

class NilClass #:nodoc:
  def try(*args)
    nil
  end
end

其实只是判断了 if a.empty? && block_given? 这种情况 则直接执行block 内容然后返回,效果一样…..

DEMO:

require "active_support/core_ext/object/try"

class Klass

  def send(*args)
    "helo " + args.join(' ')
  end

  def hello(*args)
    "Hello " + args.join(' ')
  end

  def self.foobar(s)
     "#{s} foobar"
  end
end

k = Klass.new

# __send__ 为了防止有方法名叫send , 建议用 __send__
p k.__send__ :hello, "gentle", "readers"   #=> "Hello gentle readers" 
p k.send "gentle", "readers"   #=> "Helo gentle readers"

# Ruby 里一切皆是对象,类也是对象
# Klass(类) 是 Class 的实例 , Class 是 Object 的实例 , 那么 Klass 也就是 Object 的实例 所以 Klass 可以调用try 方法
p Klass.try(:foobar,"hey") # => "hey foobar"
# k 是Klass 的实例,Klass 的父类是 Object , 所以 k 可以调用 try 方法
p k.try(:send,"bla","bla") # => "helo bla bla"

# class 得到的是 实例关系
# superclass 得到的是 继承关系
p Klass.superclass # Object
p Klass.class # Class
p k.class # Klass

另外 这是 对象nil 那如果 没有那个字段了 , 就会 报 找不到方法的错误

例如:

ruby-1.9.2-p0 > u=User.first
  User Load (175.8ms)  SELECT `users`.* FROM `users` LIMIT 1
 => #<User id: 1, login: "entos", name: "", email: "entos@entos.com", crypted_password: "557c88b0713f63397249f4198368e4a57d6d400f", salt: "4e04ef1cf506595ac3edf6a249791c55995b0f8f", remember_token: nil, remember_token_expires_at: nil, activation_code: nil, activated_at: nil, status: 2, suspend_at: nil, avatar_id: nil, orgunit_id: nil, mobile_phone: nil, last_login_at: nil, language: nil, options: nil, created_at: "2011-02-24 02:55:42", updated_at: "2011-02-24 02:55:42"> 
ruby-1.9.2-p0 > u.hi
NoMethodError: undefined method `hi' for #<User:0x9fcfe00>

建议加上 respond_to? 判断

ruby-1.9.2-p0 > u.respond_to? "hi"
 => false

rails 控制台输出sql

Posted by wxianfeng Fri, 14 Jan 2011 03:14:00 GMT

环境:ruby 1.9.2 + rails 3.0.3

我们经常需要在 rails console 中进行Model的操作,想看执行的sql ,必须到 rails log 中去查看 , 现在 有一个更好的办法,直接输出到 console 中…

在console 运行下面这句话即可:

ActiveRecord::Base.logger = Logger.new(STDOUT)

或者 直接写到 config/appliction.rb 中 ,下次启动console的时候 不需要在写上面语句:

    if Rails.env == 'development'
      ActiveRecord::Base.logger = Logger.new(STDOUT)
    end

DEMO:

wxianfeng@ubuntu:/usr/local/system/projects/entos/ent_os$ rails  c 
Loading development environment (Rails 3.0.3)
ruby-1.9.2-p0 > ActiveRecord::Base.logger = Logger.new(STDOUT)
 => #<Logger:0xadc0730 @progname=nil, @level=0, @default_formatter=#<Logger::Formatter:0xadc071c @datetime_format=nil>, @formatter=nil, @logdev=#<Logger::LogDevice:0xadc06a4 @shift_size=nil, @shift_age=nil, @filename=nil, @dev=#<IO:<STDOUT>>, @mutex=#<Logger::LogDevice::LogDeviceMutex:0xadc0690 @mon_owner=nil, @mon_count=0, @mon_mutex=#<Mutex:0xadc0668>>>> 
ruby-1.9.2-p0 > User.last 
  User Load (0.2ms)  SELECT `users`.* FROM `users` ORDER BY users.id DESC LIMIT 1
 => #<User id: 15, login: "xxxxxx", name: "", email: "xx@zz.com", crypted_password: "471f98733c6d2456df58a354feddcf7af22ea78e", salt: "f03c284f91365a3eeb30a2898b79524694efdac5", remember_token: nil, remember_token_expires_at: nil, activation_code: nil, activated_at: "2011-01-07 08:00:25", status: 2, suspend_at: nil, avatar_id: nil, orgunit_id: nil, mobile_phone: nil, last_login_at: nil, language: nil, options: nil, created_at: "2011-01-07 08:00:17", updated_at: "2011-01-07 08:00:25">

另外 还可以 使用 hirb gem 来让输出格式以表格排列,个人不是太喜欢,原有的方式可以看出数据的返回格式,是集合数组 , 还是单个对象 一清二楚 。。。而hirb 就没有了

SEE:

http://tuohuang.thoughtworkers.org/?p=114


Rails源码 attr_internal

Posted by wxianfeng Fri, 06 Jan 2012 07: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 Sun, 02 Aug 2009 05: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/