rails3.1 新特性

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

目前 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/


Rails console 测试路由

Posted by wxianfeng Tue, 01 Mar 2011 17: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


rails3 autoload_paths

Posted by wxianfeng Wed, 23 Feb 2011 13:23:00 GMT

环境: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:

http://errtheblog.com/posts/3-organize-your-models


rails params[:id] 实现原理

Posted by wxianfeng Thu, 10 Feb 2011 16: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 Thu, 13 Jan 2011 19: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 Thu, 13 Jan 2011 14: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_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


最终环境 : 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>

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