环境: 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
环境: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
最近部署一个项目,采用的是centos + nginx+ passenger
发现rails project不打log, 是文件权限问题,passenger 规定文件权限不能是root ,如果你部署在ubuntu就不会有这个问题
passenger典型部署结构
lecai-
|-- current
|-- releases
|-- shared把根目录lecai的权限改了即可
>useradd deploy >chown -R deploy:deploy lecai
环境:
rails3.0.6 + ruby 1.9.2
前面说我 服务器上文件 导出有问题 , 想在本地测试 , 本地开启production , 发现所有的 静态文件都不显示 , 原来是 rails production.rb 配置了 rails 不处理 静态文件 , 在生产环境中都交给了 nginx 等web server 处理了。。
所以你本地没有 nginx 环境 , 直接
>rails s -e production 起的话
需要 开启 rails 处理public 下处理静态文件的功能:
# production.rb config.serve_static_assets = false
把 false 改为 true
环境:
ruby 1.9.2 + rails 3.0.6 + ubuntu 11.04 + centos 5.5
最近一个项目 在本地导出 excel 都没有问题 ,可是上了服务器 导出全部是 空 0 bytes , 原来是因为我部署在nginx 上 ,rails project 需要 配置使用 nginx的send-file 功能 ,
修改production.rb:
# For nginx:
config.action_dispatch.x_sendfile_header = ‘X-Accel-Redirect’
prodcution.rb 中默认的 send_file 配置使用的是apache ,lighttpd 的 配置 , 即 X-Sendfile , nginx的话, 需要使用上面配置
这样导出就没有问题了!!
SEE:
http://stackoverflow.com/questions/3724853/rails-sends-0-byte-files-using-send-file
环境: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 是表名
目前 rails的稳定版本是 rails3.0.5,但是rails3.1的新特性已经出来了 , 来看看 。。。
1,Scopes
rails 3.0 我们使用scope,常这样使用:
class Product < ActiveRecord::Base scope :nokia, lambda { where(:name => 'Nokia') } scope :category, lambda { |value| where(:category => value) } scope :combined, lambda { |value| nokia.category(value) } end
$ @nokia = Product.nokia.all # to get all the products with name Nokia $ Product.category("Mobile").all # to get all the products with category Mobile $ Product.nokia.category("Mobile").all #Combined : to get all the products with name = Nokia and category = Mobile
发现scope 只能使用在一个Class中,那么有多个Class 怎么办,rails 3.1 你可以创建一个类 作为 通用的 filter
class Filter < Struct.new(:klass) def call(*args); end end module NameFilter def call(*args) klass.where(:name => args.shift) super(*args) end end module CategoryFilter def call(category, *args) klass.where(:category => args.shift) super(*args) end end class Product < ActiveRecord::Base scope :combined, Filter.new(self).extend(CategoryFilter).extend(NameFilter) end class User < ActiveRecord::Base scope :combined, Filter.new(self).extend(CategoryFilter).extend(NameFilter) end Product.combined('Nokia','Mobile').all User.combined('John','Manager').all
2,Automatic Flushing
Automatic Flushing 是改善性能的一项技术,例如 之前rails渲染页面的机理是这样的,第一步 rails 生成 静态页面 ,例如css,图片,js文件 ,页面html ,然后在一个一个的下载
添加了 Automatic Flushing 这个技术后 ,将会 大大提高性能 , 服务器端一边 生成静态文件 ,浏览器一边就下载了 ,改善了用户体验 和性能
3,css sprites(图片拼合)
rails 3.1 默认支持 icons sprite , 多个icon放在一张图片上 ,显示icon通过css来控制,这样有利于减少http请求数
4,js,css文件可以放到views 下面
#Preprocess: app/views/js/item.js.erb app/views/css/style.css.erb #This code will be compiled to the files like this: #Compiles: public/application.js public/style.css
5,一些旧的用法将不被支持,例如 :conditions
旧的写法:
class User scope :name, :conditions => { :name => 'David' } scope :age, lambda {|age| {:conditions => ["age > ?", age] }} end
新的写法:
class User scope :name, where(:name => 'David') scope :age, lambda {|age| where("age > ?", age) } end
SEE:
http://hemju.com/2011/02/23/rails-3-1-release-rumors-and-news-about-features/
环境: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
环境:ruby 1.9.2 + rails 3.0.3
rails3里 autoload_paths 到底有什么用,什么情况加到 autoload_paths 中 ,什么时候使用require? , rails 2.X 里是 load_paths , 例如 rails3里添加 文件夹到 autoload_paths
config.autoload_paths += %W(#{Rails.root}/lib) # 不包括子文件夹
然后 代码里
include AuthenticatedSystem 就会从 autoload_paths 中寻找 authenticated_system.rb , autoload_paths 和java中的classpath类似 。。
所以没有必要把所有额外的 .rb 文件 都require 一下 , 这样会 影响启动时间 , 但是有些情况是必须 require 的 ,例如扩展 String ,Array ,Hash 类时 就必须require 了
autoload_paths 默认加到了 $LOAD_PATH 全局数组变量中 ,或者写成 $: , 用来保存 paths
autoload_paths 源码 : here
see: