环境: 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
时间: 2011-07-24
收获:
发现北京ROR的公司不是一般的多,签到单上看到N多公司,技术上没有太大收获,都是介绍性的,没有实战性的,内容主要涉及: mirah , Mongodb,Erlang,Grape
进程:http://www.surveymonkey.com/s/MSY2L7T
PS : 798 很好玩,很有艺术特色
现场:
798 入口
Ruby活动地方
Rails rumble 创始人
现场job board
现场
清一色老外,清一色Mac
介绍Mirah
798
798
798
环境: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 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 是表名
环境: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
环境: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::HashWithIndifferentAccess2,继续 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:
环境: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
环境: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:
环境: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
环境: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/










