显示标签为“Rails”的博文。显示所有博文
显示标签为“Rails”的博文。显示所有博文

2011年9月21日星期三

ActiveSupport中的class_attribute

看rails guide的时候遇到了Class#class_attribute,怎么使用参见guide和下面两篇文章,这里不做介绍
http://blog.obiefernandez.com/content/2010/04/tr3w-highlights-activesupport-class-class-attribute.html
http://ihower.tw/blog/archives/4878

在看源码的时候,发现第79行怎么都不能理解,为什么是
singleton_class.#{name}
而不是
self.class.#{name}
呢?

由于我看的是3.0.7的版本,于是又找到了最新的3.1.0的版本
看完之后更加迷惑,为什么要定义两个reader instance method,一个用singleton_class,一个用self.class呢?

查阅了相关资料,翻看了源码提交记录,我找到了答案。
其实本来在3.0.7版本中用self.class代替singleton_class是可以的,但为了支持在singleton_class上调用writer方法
klass = Class.new { class_attribute :setting }
object = klass.new
object.singleton_class.setting = "foo"
所以用了singleton_class。

由于object的singleton_class的superclass是klass,singleton_class自己又没定义过setting=(),所以singleton_class.setting=()还是会查找到klass里的定义并调用它。

实际使用的时候,大部分情况还是object.setting=()而不是object.singleton_class.setting=(),为每个object创建singleton_class开销较大,所以做了优化:object调用时直接查找self.class,避开了singleton_class。而针对singleton_class另做处理,由于方法查找时singleton_class优先于klass,所以
def #{name}
defined?(@#{name}) ? @#{name} : singleton_class.#{name}
end
会覆盖
def #{name}
defined?(@#{name}) ? @#{name} : self.class.#{name}
end

最后的问题是,谁引入的对singleton_class的支持呢?翻提交记录的时候找到了答案

2008年7月8日星期二

浅析Ruby on Rails部署方案(转)

http://docs.google.com/View?docid=ddcvzh74_28f9xppqfh
有数据有分析有结论,图文表并茂,相当强大。

2008年6月16日星期一

config my logger

config/environment.rb:
$my_logger = Logger.new("#{RAILS_ROOT}/log/my.log")
其实感觉放在config/environments/development.rb中更好,毕竟一般的调试信息只在开发时需要。

controller:
$my_logger.info("hello")

打印到控制台:
Logger.new(STDOUT).info("display in the console")
打印到开发日志文件:
logger.info("display in the development.log")

2008年6月15日星期日

"Please select"

下拉框里,要出现"Please select"的话,加上":prompt => true"选项:
<%= form.collection_select(:country_id, @countries, :id, :description, {:prompt  => true} %>

2008年6月10日星期二

Free Rails 2.1 Book

http://weblog.rubyonrails.com/2008/6/10/free-rails-2-1-book
一本介绍Rails 2.1新特性的书,原作是葡萄牙语写的,这里可以下载英文版的。

== 2008-06-18
不得不叹服Rails社区的热情,中文版马上就要出来了,从开始组织翻译到完成,不到一周,领取章节都在抢了。
http://chinaonrails.com/topic/view/1713.html

== 2008-06-20
http://chinaonrails.com/topic/view/1754.html
中文版已经出来了,点击这里下载,需要先注册。

2008年6月9日星期一

truncate(截取字符串)

ActionView::Helpers::TextHelper#truncate(text, length = 30, truncate_string = "...")
截取过长字符串,省略部分用truncate_string来代替(默认是...)。

If text is longer than length, text will be truncated to the length of length (defaults to 30) and the last characters will be replaced with the truncate_string (defaults to "…").

Examples

truncate("Once upon a time in a world far far away", 14)
# => Once upon a...

truncate("Once upon a time in a world far far away")
# => Once upon a time in a world f...

truncate("And they found that many people were sleeping better.", 25, "(clipped)")
# => And they found that many (clipped)

truncate("And they found that many people were sleeping better.", 15, "... (continued)")
# => And they found... (continued)


length = 显示的字符串长度 + truncate_string的长度,也就是说,设置length为10,实际显示的字符数是7个,还有3个是用来显示...的。
当然,如果结束符用其他的(比如......),那实际显示的字符串长度就是3个了。

源代码:
def truncate(text, length = 30, truncate_string = "...")
if text.nil? then return end
l = length - truncate_string.chars.length
(text.chars.length > length ? text.chars[0...l] + truncate_string : text).to_s
end
length的长度必须要大于truncate_string的长度,如有必要,在调用truncate方法前我们应该自己先判断一下。
这个截取方法对于中英文字符都有效,因为chars方法返回的是UTF-8的结果。

不过这样也带来了另一个问题,就是截取同样多的字符时,中文比英文显得要长一些:

truncate("Once upon a time in a world far far away", 14)
# => Once upon a...
truncate("这是一串很长很长的中文字符", 14)
# => 这是一串很长很长的中文...

因为程序截取是按照字符个数来截取,但是中文显示时,一个中文会占据两个英文的宽度。

javaeye有人出了一道Quiz来讨论怎么解决这个问题。

2008年6月3日星期二

HasFinder

http://pivots.pivotallabs.com/users/nick/blog/articles/284-hasfinder-it-s-now-easier-than-ever-to-create-complex-re-usable-sql-queries
纯备忘。

ActiveResource 源码 -- format

我机器上 ActiveResource gem 包的路径:
/var/lib/gems/1.8/gems/activeresource-2.0.2-/

跟format相关的文件有三个
lib/active_resource/formats.rb
lib/active_resource/formats/json_format.rb
lib/active_resource/formats/xml_format.rb

formats.rb
module ActiveResource
module Formats
# Lookup the format class from a mime type reference symbol. Example:
#
# ActiveResource::Formats[:xml] # => ActiveResource::Formats::XmlFormat
# ActiveResource::Formats[:json] # => ActiveResource::Formats::JsonFormat
def self.[](mime_type_reference)
ActiveResource::Formats.const_get(mime_type_reference.to_s.camelize + "Format")
end
end
end

require 'active_resource/formats/xml_format'
require 'active_resource/formats/json_format'
可以看到,format.rb其实是对另外两个文件的统一包装。
其中的const_get方法比较有意思。const_get方法是Module类提供的方法,意思是取Mudule中某常量的值。
ActiveResource::Formats.const_get()返回的是一个Module类。
我想这说明了两点:常量的值可以是任何对象。Module中嵌套的Module名也是常量,Module名是对Module类的引用。

json_format.rb和xml_format.rb没有太多好说的,都是调用ActiveSupport里的to_xxx和from_xxx方法。
xml_format.rb里的decode方法值得一提:
def decode(xml)
from_xml_data(Hash.from_xml(xml))
end

private
# Manipulate from_xml Hash, because xml_simple is not exactly what we
# want for ActiveResource.
def from_xml_data(data)
if data.is_a?(Hash) && data.keys.size == 1
data.values.first
else
data
end
end
可以看到,在decode方法里又调用了一次from_xml_data方法,为什么要这样呢?
因为ActiveSupport里Hash的from_xml方法是调用xml_simple的方法来实现的,而这个xml_simple返回的Hash往往在最外面还包了一层:

{"records"=>[{"name"=>"Matz", "id"=>1}, {"name"=>"David", "id"=>2}]}
{"hash"=>{"name"=>"David", "id"=>2}}

而实际上我们要的是里面的value:

[{"name"=>"Matz", "id"=>1}, {"name"=>"David", "id"=>2}]
{"name"=>"David", "id"=>2}

所以要用from_xml_data把数据再剥开一次。

ActiveSupport里Hash的from_xml方法定义如下(在activesupport/lib/active_support/core_ext/hash/conversions.rb里):
def from_xml(xml)
# TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple
undasherize_keys(typecast_xml_value(XmlSimple.xml_in(xml,
'forcearray' => false,
'forcecontent' => true,
'keeproot' => true,
'contentkey' => '__content__')
))
end
哈哈,DHH自己都说了要Refactor这个方法,不过估计也就只写了个TODO在这里,一直没Refactor过。

另外有个问题,为什么这里可以直接调用XmlSimple的方法呢?
首先要在文件里require一下:
require 'xml_simple'
其次在vendor目录下已经引入了XmlSimple:
activesupport/lib/active_support/vendor/xml_simple.rb

没错,XmlSimple就只有这样一个文件,不信你去把gem安装的XmlSimple打开来看吧,在我机器上的路径是:
/var/lib/gems/1.8/gems/xml-simple-1.0.11/
一千多行的文件,XmlSimple的作者可真能折腾啊。

2008年5月11日星期日

RailsCasts 006 symbol to proc

返回所有project的name,传统写法:
projects = Project.find(:all)
projects.collect { |p| p.name }
使用rails提供的shortcut:
projects.collect(&:name)
刚开始可能会觉得有点weird,不过习惯了就感觉很方便,还可以使用链式操作:
projects.collect(&:name).collect(&:downcase)
而且不止collect方法可以这样用,所有需要跟block的方法都可以用:
projects.all?(&:valid?)
projects.any?(&:valid?)
projects.each(&:save!)
意思是对每个project执行其save!方法,&后面跟的就是要执行的方法名称的symbol。
看完这个episode,不得不叹一句,编程就像是在说话一样,太强大了。

另外,在回复中有读者问到:如果&后的方法有argument的话,还能这样用吗?
作者的回复:Nope, it only works on very simple method calls which don't take an argument. Anything more complicated and you will need to use the full block.

还有老兄问能不能这样写:
ActorNames = Actor.find(:all).collect(&:last_name + ' ' + &:first_name)
作者推荐在actor model中定义一个full_name方法,然后调用这个方法:
ActorNames = Actor.find(:all).collect(&:full_name)

RailsCasts 005 using with scope

接上回episode 004,如果我们要查top 20 tasks而非全部记录,该怎么做呢?
不妨直接告诉聪明的find_incomplete方法:
@tasks = Task.find_incomplete :limit => 20
然后在方法定义中使用with_scope:
def self.find_incomplete(options => {})
with_scope :find => options do
find_all_by_complete(false, :order => 'created_at DESC')
end
end
当然,如果不用with_scope,或者还想传order条件(上面order是写死了的),可以merge options:
def self.find_incomplete(options => {})
find_all_by_complete(false, (:order => 'created_at DESC').merge(options))
end
或者使用reverse_merge:
find_all_by_complete(false, options.reverse_merge(:order => 'created_at DESC'))

在关联查询里,也可以传options进去:
@tasks = @project.tasks.find_incomplete :limit => 20
这里的task查询在两个scope里,一个是@project限定的scope,第二个是:limit限定的scope

RailsCasts 004 move find into model

在controller里:
@tasks = Task.find_all_by_complete(false, :order => 'created_at DESC')
改成:
@tasks = Task.find_incomplete
然后在model里定义incomplete方法,这是一个class方法,前面要加self:
def self.find_incomplete
find_all_by_complete(false, :order => 'created_at DESC')
end
注意方法定义体里的find…方法前不用加self,因为当前的作用域就是model类本身。
到这里都看似很平常,下面神奇的就来了。在episode 003里介绍的关联查询也可以用这里定义的find_incomplete方法:
@project = Project.find(params[:id])
@tasks = @project.tasks.find_all_by_complete(false, :order => 'created_at DESC')
可以改成:
@tasks = @project.tasks.find_incomplete
也就是说,凡是Task的class方法,都可以在@project.tasks这样的关联查询中使用。

RailsCasts 003 find through association

关联关系:
Project has_many Tasks
Task belongs_to Project
传统做法:
@project = Project.find(params[:id])
@tasks = Task.find(:all, :conditions => ['project_id = ? AND complete = ?'], @project.id, false)
Rails做法:
@tasks = @project.tasks.find(:all, :conditions => ['complete = ?'], false)
episode 002介绍的动态方法进一步简化:
@tasks = @project.tasks.find_all_by_complete(false)

RailsCasts 002 dynamic find by methods

传统的查询方法:
@tasks = Task.find(:all, :conditions => ['complete = ?', false])
@tasks = Task.find(:first, :conditions => ['complete = ?', false], :order => 'created_at DESC')
动态查询方法:
@tasks = Taks.find_all_by_complete(false)
@tasks = Taks.find_by_complete(false, :order => 'created_at DESC')
注意第二个,直接把:order这个hash作为第二个参数传进去就可以了。

RailsCasts 001 caching with instance variable

这是RailsCasts系列的笔记。

用实例变量来缓存数据库查询结果,这样就不用每次都访问数据库了:
@current_user ||= User.find(session[:user_id])
但是,实例变量的作用域是一次请求,第二次请求还是会重新去查数据库,得到的结果来给@current_user赋值。

2008年1月23日星期三

数据迁移命令

rake db:migrate

建立development的数据库结构

rake db:test:prepare

将development的数据库结构导入到test数据库中

rake db:fixtures:load

将test/fixtures中的数据读到数据库中(development数据库?)

rake spec:db:fixtures:load

将spec/fixtures中的数据读到数据库中(development数据库?)

Rails Environments and Configuration

Whenever you start a process to handle requests with Rails (such as a Webrick server), one of the first things that happens is that config/environment.rb is loaded. For instance, take a look at the top of public/dispatch.rb:
require File.dirname(__FILE__) + “/../config/environment”

A word of caution: If you were to set the RAILS_ENV environment variable to production here, or the constant variable RAILS_ENV for that matter, it would cause everything you did in Rails to run in production mode. For instance, your test suite would not work correctly, since test_helper.rb sets RAILS_ENV to test right before loading the environment.

因为在test/test_helper.rb中,先是:
ENV["RAILS_ENV"] = "test"
再是:
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
而在加载environment.rb时,又将RAILS_ENV覆写为prodoction。

项目中使用的Rails的版本:
# Specifies gem version of Rails to use when vendor/rails is not present
RAILS_GEM_VERSION = '2.0.1' unless defined? RAILS_GEM_VERSION

定义项目根目录:
RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)

RAILS_ROOT is used all over the place in the Rails codebase for finding files


read the config/environment.rb file as text (minus the commented lines) and parse out the RAILS_GEM_VERSION setting with a regexp.


parse_gem_version(read_environment_rb)

def parse_gem_version(text)
$1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*'([!~<>=]*\s*[\d.]+)'/
end

def read_environment_rb
File.read("#{RAILS_ROOT}/config/environment.rb")
end

Default Load Paths:

def default_frameworks
[ :active_record, :action_controller, :action_view, :action_mailer, :active_resource ]
end

def default_load_paths
paths = ["#{root_path}/test/mocks/#{environment}"]

# Add the app's controller directory
paths.concat(Dir["#{root_path}/app/controllers/"])

# Then components subdirectories.
paths.concat(Dir["#{root_path}/components/[_a-z]*"])

# Followed by the standard includes.
paths.concat %w(
app
app/models
app/controllers
app/helpers
app/services
components
config
lib
vendor
).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) }

paths.concat builtin_directories
end

def builtin_directories
# Include builtins only in the development environment.
(environment == 'development') ? Dir["#{RAILTIES_PATH}/builtin/*/"] : []
end

def default_plugin_paths
["#{root_path}/vendor/plugins"]
end

上面这个builtin_directories指的是:

It is the place for Rails to include application behavior (meaning models, helpers, and controllers). You can think about it as kind of like a framework-provided plugin mechanism.


http://localhost:3000/rails/info/properties
和在命令行下用script/about是一样的

在Rails项目里,绝大多数情况下,都不需要手动去load一个class或module,Rails有默认的规则去自动load需要的文件,其规则如下:
• If the class or module is not nested, insert an underscore between the constant’s names and require a file of this name. For example:
EstimationCalculator becomes require ‘estimation_calculator’
• If the class or module is nested, Rails inserts an underscore between each of the containing modules and requires a file in the corresponding set of subdirectories.For example:
MacGyver::SwissArmyKnife becomes require ‘mac_gyver/swiss_army_knife’

you should rarely need to explicitly load Ruby code in your Rails applications (using require) if you follow the naming conventions.

哈哈,顺我者昌

Rails::Initializer.run do |config|
...
end
里面的代码是关于Configuration的

# Settings in config/environments/* take precedence over those specified here.

The comment reminds you that the settings in the mode-specific environment files will take precedence over settings in environment.rb, which is essentially because they are loaded afterward and will overwrite your settings.


# Skip frameworks you're not going to use (only works if using vendor/rails).
# To use Rails without a database, you must remove the Active Record framework
# config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
如果项目中不用数据库,就去掉Active Record,如果不用Web service或Email,也去掉相应的模块
为什么要去掉呢:

Ruby is, of course, interpreted, and if you can get away with a smallersized codebase for the interpreter to parse, you should do so, simply for performance reasons.


# Only load the plugins named here, in the order given. By default, all plugins
# in vendor/plugins are loaded in alphabetical order.
# :all can be used as a placeholder for all plugins not explicitly named
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
plugins默认是按照字母顺序load的,也可以指定先load哪些plugin,再load剩下的(:all)

Rails takes advantage of the fact that Ruby provides a callback mechanism for missing constants. When Rails encounters an undefined constant in the code, it uses a classloader routine based on file-naming conventions to find and require the needed Ruby script.

利用Ruby提供的"missing constants"回调机制,Rails根据命名惯例去找对应的文件来load。

Want to see the contents of your project’s load path? Just fire up the console and type $:

想看项目里的load path的话,就script/console,然后敲$:(注意加上后面的冒号),然后load path就打出来了,一长串。$:是一个数组。

除了Rails提供的三种environment外,还可以自定义environment,比如:

Use the normal environment settings for development mode, but point its database connection to a production database server. It’s a potentially life-saving combination when you need to quickly diagnose issues in production.

可以用来快速诊断生产环境的问题。

使用log的方法:在irb里敲入:

require ‘logger’

logger = Logger.new STDOUT

logger.warn “do not want!!!”


log的级别(按严重程度由低到高):
debug -> info -> warn -> error -> fatal
debug是开发时方便调试程序的,生产环境中用不到。
info是一些不常发生的事件,但仍然属于正常行为范围内。
warn表示正常范畴之外的事件发生了,需要注意,值得去研究研究问题,例如:
def create
begin
@group.add_member(current_user)
flash[:notice] = “Successfully joined #{@scene.display_name}”
rescue ActiveRecord::RecordInvalid
flash[:error] = “You are already a member of #{@group.name}”
logger.warn “A user tried to join a group twice. UI should not have allowed it.”
end

redirect_to :back
end
error表示还不需要重启服务器的错误。
fatal是指能想到的最坏的情况已经发生了,你的应用现在已经挂了,需要重启服务器了。

清空log/目录下的所有.log文件

rake log:clear


development.log里包含的信息:

• The controller and action that were invoked
• The remote IP address of the computer making the request
• A timestamp indicating when the request happened
• The session ID associated with the request
• The hash of parameters associated with the request
• Database request information including the time and the SQL statement executed
• Query cache hit info including time and the SQL statement triggering results from the cache instead of a roundtrip to the database
• Rendering information for each template involved in rendering the view output and time consumed by each
• Total time used in completing the request with corresponding request-per-second figures
• Analysis of the time spent in database operations versus rendering
• The HTTP status code and URL of the response sent back to the client


资源/链接:
Rails Plugins: Extending Rails Beyond the Core
http://weblog.jamisbuck.org/2007/1/31/more-on-watchingactiverecord
http://seattlerb.rubyforge.org/SyslogLogger/

Tabnav

项目里用到了个plugin,叫Tabnav,具体见:http://www.seesaw.it/en/toolbox/widgets/

tab的name和link需要自己写个类来定义,以前的开发者将它放在models目录下(不知道可不可以放在别的目录下),命名为xyy_tabnav.rb
class XyyTabnav < Tabnav::Base
add_tab do
named 'tab_1'
links_to(lambda{ { :controller => 'xxx', :action => 'show', :id => 1 }})
end

add_tab do
named 'tab_2'
links_to(lambda{ { :controller => 'yyy', :action => 'index' }})
end
end
在Tabnav这个plugin的目录下,有个generators文件夹,里面相当于是个demo,模仿着用就行了。

Haml中的注释

以前一直不知道怎么在Haml中注释掉一段代码。
结果官方文档上写得清清楚楚:
/

The forward slash character, when placed at the beginning of a line, wraps all text after it in an HTML comment. For example:

%peanutbutterjelly
/ This is the peanutbutterjelly element
I like sandwiches!

is compiled to:

<peanutbutterjelly>
<!-- This is the peanutbutterjelly element -->
I like sandwiches!
</peanutbutterjelly>

The forward slash can also wrap indented sections of code. For example:

/
%p This doesn't render...
%div
%h1 Because it's commented out!

is compiled to:

<!--
<p>This doesn't render...</p>
<div>
<h1>Because it's commented out!</h1>
</div>
-->
还是跟缩进相关。

至于反斜杠\(backslash),当然是转义字符啦。
%title
= @title
\- MySite

is compiled to:

<title>
MyPage
- MySite
</title>

具体请参考官方文档:
The full Haml reference
The full Sass reference
The RDoc documentation

2008年1月16日星期三

不用render模板时,不使用布局的方法

# Renders the template for the action "short_goal" within the current controller,
# but without the current active layout
render :action => "short_goal", :layout => false

# Renders the template for the action "long_goal" within the current controller,
# but with a custom layout
render :action => "long_goal", :layout => "spectacular"


Rails API文档上的。

How to compare ActiveRecord objects

在ruby里,有三种方法可以比较对象
  • == 比较是否是同一个对象,可以被覆写
  • equal? 比较是否是同一个对象,不能被覆写
  • eql? 比较对象是否有相同的值

在写测试的时候发现,如果两个ActiveRecord对象有相同的类型,又有相同的id,则用==来做比较的话,得出的结果是true,即使它们的值不相同。可见ActiveRecord覆写了==方法。
例:
a = Account.create(:name => "aaa")
b = Account.create(:name => "bbb")
而因为不知名的原因,在测试的时候a与b有相同的id,即
a.id #=> 0
b.id #=> 0
于是
a == b       #=> true    #因为a与b类型相同,id相等,ActiveRecord就认为它们是同一个对象
a.equal?(b) #=> false #因为不是同一个对象
a.eql?(b) #=> false #因为值不相等