rails 笔记
启动项目:
ruby script/server -e development(默认)
ruby script/server -e test
ruby script/server -e production
在编写ruby代码时,如果要引用另一个文件中的类和模块,需要使用require关键字,但是当我们在rails中引用另一个文件中的类和模块时,rails会自动把类名称根据命名约定改为文件名,然后在同一目录下加载该文件。
按模块组织控制器:
ruby script/generate controller Admin::Book action1 action2
admin模块下的book_controller控制器
Rails给Enumerable类扩展的方法
1》 group_by: 将一个对象集合分组,针对每个对象调用块,然后根据代码块的返回值作为分组的键,值是所有拥有同一个键的对象组成的数组。
eg:
User.all.group_by{|user| user.sex}
User.all.group_by(&:name)
2》 sum: 对集合进行加总,把每个元素传递给一个代码块,并对代码块返回的值进行累加。
User.all.sum(&:id) 返回所有user对象的id的和
3》 in_groups_of(arg1, arg2) 将某一个数组分为子元素由arg1个元素组合的数组元素组成的数组,出现无法填充的值由arg2填补。
eg:
[1,2,3,4,5].in_groups_of(2,"blank") =>
[[1,2],[3,4],[5,"blank"]]
字符串扩展
str[0] 第一个字符的Ascill码。
str[2..3] 返回字符串的第三个字符开始的3个字符组成的字符串。
str.at(0) 第一个字符
str.to(4) 前5个字符组成的字符串
str.from(4) 最后5个字符组成的字符串
str.first
str.last
str.starts_with?(字符串)
str.ends_with?(字符串)
str.each_char{|char| char.upcase}
str.pluralize 复数形式
str.singularize 单数形式
str.humanize
str.titleize
对数值的扩展:
figure.ordinalize 返回数字的序数形式
figure.bytes、kilobytes、megabytes、gigabytes、terabytes、
perabytes、exabyte 数据大小
figure.seconds、minutes、hours、days、weeks、fortnights、months、years 时间
注意: ago、 until、 from_now、 since
eg:
1.day.ago
1.day.ago(Time.now)
时间和日期的扩展
now = Time.now
now.to_s(:short)、to_s(:db)、to_s(:long)、to_s(:rfc822)
now.ago(2.days)、since(2.days)
change(:hour => 12) 改变日期的hour为12
advance(:hour => 12) 推迟日期的12hour
to_date、to_time
at_beginning_of_week、at_beginning_of_month、at_beginning_of_quarter(季)、at_beginning_of_year
ruby符号的扩展:
posts.group_by{|post| post.author_id}
posts.group_by(&:author_id)
with_options的使用:
Rails里有些方法使用一个Hash作为最后一个可选的参数,如果对多个方法有同样的选项,我们可以使用with_options来减少冗余:
eg:
with_options :if => :should_validate_password? do |user|
user.validates_presence_of :password
user.validates_confirmation_for :password
user.validates_format_of :password, :with => /^[^\s]+$/
end
迁移任务:
create_table
:force => true 如果表存在,强制删除,然后再创建表
:temporary => true 创建一张临时表,程序与数据库断开链接,则表被删除。
:options => "xxxx" 指定针对于底层数据库的选项,这些选项会被加到create table语句的后面
:primary_key 主键
:id => false 没有主键的表
如果迁移任务 提供的方法 不够满足你 的需要,也 可以使用数 据库专有的 功能:使用execute() 方法就可以运行原生 SQL 语句。例如:给表加上外键
class CreateLineItems < ActiveRecord::Migration
def self.up
create_table :line_items do |t|
t.column :product_id, :integer
t.columnrder_id, :integer
end
execute "alter table line_items
add constraint fk_line_item_products
foreign key (product_id) references products(id)"
end
def self.down
drop_table :line_items
end
end
ActiveRecord::Base
column_names 属性组成的数组
columns_hash["属性名称"] 属性名称信息
属性_before_type_cast 读取属性时,ActiveRecod会尽量将得到的值转型成适当的ruby类型,如果我们希望的到属性的原始值,可以在属性名称后面加上before_type_cast。
eg:User.first.created_at => 2010-06-24 09:41:17
连接数据库: ActiveRecord::Base.establish_connection(:adapter => "mysql", :host => "localhost", :database => "rails", :username => "root", :password => "1234")
防止恶意注入:
1> 问号占位符: User.find(:all, :conditions => ["id > ?", 3]
2> 命名占位符: User.find(:all, :conditions => ["id > :id", {:id => 3}])
注意:
命名占位符的语句可以转换为
User.find(:all, :conditions => ["id > :id", params[:user]])
获取字段统计信息:
average:
maximum:
minimum:
sum :
count :
以上方法的参数:
:conditions、:joins、:limit、:order、:having、:select、 :distinct
eg:
Order.maximum :amount, :group => "state", :limit => 3,rder => "max(amount) desc"
拥有订单最大的三个州
更新操作:
update_attribute、update_attributes、update、update_all
创建操作:
create、create!、save、save!
删除数据:
delete、delete_all、destroy、destroy_all
delete绕过了ActiveRecord的回调和验证,destory没有绕过回调和验证
序列化数据:
serialize :属性
序列化的属性可以直接存入Array和hash类型的数据,也可以直接读出来使用
缺点:
ruby应用之外的应用访问序列化的数据时,除非它能够理解yaml格式,否则无法获取这个字段的信息。
弥补这种缺点的方式是用对象聚合的方式来实现类似的效果
聚合/组合:
聚合可以把一个或多个属性封装为一个类,然后这个类里可以添加操作属性的方法,这样我们就可以实现序列化所要实现的目的了,而且可以避免序列化的缺点。
composed_of :attr_name, :class_name => 类名称, :mapping => [字段与属性组成的数组]
eg:
class Xingxi < ActiveRecord::Base
composed_of :inf, :class_name => "Inf", :mapping => [[:name, :name], [:phone, :phone]]
end
class Inf
attr_reader :name, :phone
def initialize(name, phone)
@name = name
@phone = phone
end
def to_s
[@name, @phone].compact.join(" ")
end
end
inf = Inf.new("zcy", "12344454")
Xingxi.create(:inf => inf)
注意:
教程上指出 :class_name对应的“类名称”可以是类常量,也可以是包含类常量的字符串,但实际上只能是包含类常量的字符串,如果类名恰好是属性名的混合大写形式,那么class_name可以省略,但是实际上不可以,包括:mapping也不可以。
关联:
class Line < ActiveRecord::Base
belongs_to :product
end
belongs_to :product 会产生以下方法
1>:product(force_reload=false) 返回关联的product对象,默认情况下product对象会被缓存,当force_reload=true时,将重新查询数据库。
2>:product=: line对象和product对象关联起来,将line记录的外键值设为product的主键值,如果product没有保存,line保存的时候会保存product,包括外键。
3>:build_product(attributes={}) 新建一个product对象,用指定的属性对其初始化,相当于product=Product.new(attributes)。
4>:build_create(attributes={}) 创建一个product对象,与line关联,保存product对象。
order: has_one中也有order,主要用于例如最后一个***,此时便可以用order指定排序。
:dependent :destroy(true)、:nullify、 false
:destroy 删除记录的同时也删除子记录
:nullify 删除记录的同时删除子记录的外键
false 只删除记录
has_many :lines
class Product < ActiveRecord::Base
has_many :lines
end
has_many :lines 会产生以下方法
lines(force_reload=false) 同上
lines.build(attributes = {})
lines.create(attributes = {})
lines << line 将当前line对象添加到lines对象数组里
lines.push(line对象) line对象添加到lines
lines.delete(line,...) 删除一个或多个line对象,如果关联为:destroy => :destroy,子对象被删除,否则只是删除子对象的外键,打断与父对象的关联。
lines.delete_all 调用所有子对象的delete方法
lines.destroy_all 调用所有子对象的destroy方法
lines.clear 和delete一样,不同的是clear针对的是所有的子对象。
lines.find(options)
lines.count(options)
lines.size
lines.length 强制加载所有子对象,返回对象的集合。
lines.empty?
lines.replace(line对象数组) line对象数组替换原先的lines
lines.sum(options) 不便利内存中的子对象集合,直接在数据库端操作
lines.uniq 返回一个数组,包含所有具备独立id的子对象
1> finder_sql: 重新定义了统计子记录的sql语句
counter_sql: 重新定义了统计子记录的数目
注意: 如果指定了finder_sql而没有指定counter_sql,finder_sql中的子句会被替换为select count(*),然后记录子记录的数量。
eg:
has_manyingxis, :finder_sql => "select x.* from xingxis x, infos i where x.info_id = i.id and x.id > 4"
作用: 当:conditions无法满足时,:finder_sql显的非常重要。
:order : 以特定的顺序排列
:conditions : 返回符合条件的子记录
:dependent :destroy、:nullify、 false
:destroy 删除记录的同时也删除子记录
:nullify 删除记录的同时删除子记录的外键
false 只删除记录
多对多的关联有两种
1: 利用默认的关联表
2:自己创建关联表,利用关联中的:through
:through 告诉rails通过guanlians表来导航关联
:source 用来指定关联在那个属性上
:uniq => true 去掉重复的对象 等同于 :select => "distinct books.*"
eg:
class Kind < ActiveRecord::Base
has_many :guanlians
has_many :readers, :through => :guanlians, :source => :book
end
注意:
has_many :reader1s,:conditions => "guanlians.cishu > 0"
当使用conditions不能很好的表达时,无法提供参数,可以考虑以下格式,而且当同样的方法被多次调用时,可以将方法写在模块中,然后在关联中:extend => "模块名称" 来调用。
has_many :readers do
def reader(limit = 3)
find(:all, :limit => limit)
end
end
单表继承:
优点: 这样做的好处是提高读取表的速度,因为没有关联,只有一个表。
缺点: 当各个子类的相似的地方很少的时候,会导致表中有很多属性,这样处理这个问题的方法是用多态关联。
多态关联:
types表中的多态外键如果是duotai,那么表的属性是duotai_id, duotai_type,一个记录键值,一个记录类名。
class Article < ActiveRecord::Base
has_one :type, :as => :duotai
end
class Image < ActiveRecord::Base
has_one :type, :as => :duotai
end
class Type < ActiveRecord::Base
belongs_to :duotai, :polymorphic => true
end
自引用的连接:
class Employee < ActiveRecord::Base
belongs_to :boss, :class_name => "Employee", :foreign_key => :employee_id
end
预先读取子记录
以下实例如果没有:include => :people, infos为n, 那么他将执行2n+1次,如果指定了:include,将预加载与info有关的people记录,此时将提高效率,但是如果预加载了数据,但没有用这些数据,反而会降低效率。
Benchmark.bm() do |x|
x.report{
infos = Info.find(:all, :include => :people)
infos.each do |info|
info.people.name
end
}
end
如果一个对象刚刚创建出来,还没有与数据库记录建立映射,我们称它为新对象(new records)。调用new_records?
模型验证:
validate、 validate_on_create、 validate_on_update
ActiveRecord::Errors
[] info.errors[:email]
on info.errors.on(:email)
add(:属性, 信息)
size、length、count
each、each_error 迭代错误属性和错误属性对应的信息
each_full 迭代错误信息
full_messages 错误信息组成的数组
invalid?(属性) 如果属性无效,则返回true
to_xml
empty?
ActiveRecord::Errors.default_error_messages 这个返回所有的默认信息,如果修改默认信息的话,可以从这里修改。
回调:
创建方式
1:直接在回调中写代码。
2:声明回调处理器,处理器可以是一个代码块也可以是一个方法,如果用方法作为一个处理器,应该被声明为private或protected。
eg
def before_save
...
end
before_save :check
private
def check
end
注意:以下方法将跳过回调
* decrement
* decrement_counter
* delete
* delete_all
* find_by_sql
* increment
* increment_counter
* toggle
* update_all
* update_counters
ActiveRecord::Base
column_names: 所有列名组成的数组
columns: 所有包含列信息的对象组成的数组。
columns_hash: 列名与列对象组成的hash数组。
实例方法:
attributes: 所有属性和属性值组成的hash数组。
attributes=
attribute_names: 所有属性组成的数组。
attribute_present?(属性名称) 如果属性名称存在,则返回true,反之亦然。
事务: 要么全部执行,要么全部不执行。
模型类.transaction do
end
begin ... rescue ...end
eg:
class Info < ActiveRecord::Base
def validate
errors.add(:age, "age is not less than 0") if age < 0
end
def jian(i)
self.money = self.money - i
self.save!
end
end
info = Info.create!(:money => 100)
begin
Info.transaction do; info.jian(200); end
rescue
end
puts info.money =》 -100
该实例中要求money不能小于0,而我们也做到了,表中的数据不会被改,但是info.money 返回的值是-100,模型对象的值被该变了,这是因为ActiveRecord没有跟综对象在事务前后的状态,我们可以指定事务跟踪哪些模型对象。
Info.transaction(info) do; .... end,此时输出的值就是100了。
路由:
map.connect 重要参数
:requirements => {:name => /regexp/} 要求url中特定的组成部分与指定的正则表达式一一匹配。
:defaults => {:name => "value", ...} 设定各组成部分的默认值
:conditions => {:name => /regexp/orstring,...} 设定路由的条件
:name => value 设定:name参数的默认值
eg:
map.connect 'infos/:year', :controller => "infos", :action => "hello", :defaults => {:year => 2008},:requirements => {:year => /(19|20)\d\d/}, :conditions => {:method => :get}
注意:
map.connect "*aa", :controller => "", :action => ""
符合任何的url格式,但是一定要注意的是要放在最后,否则其他的路由都无法匹配了。
有名路由
map.hello "hello/:id", :controller => "infos", :action => "index"
1> hello_url(:id => 1)或hello_url :controller => "infos", :action => "index"
可以访问这个url。
注意: 以上url可以用path替代。
2> 地址栏中可以输入hello访问这个url。
资源路由:
map.resources :articles, :collection => {:recent => :get}, :member => {:release => :put}, :new => {:hello => :get}
增加以下规则:
recent_articles_path
release_article_path(:id => 1)
hello_new_article_path
map.resources :articles do |article|
article.resources :comments
end
articles/99/comments/4
控制器环境(可以在action中直接引用)
controller_name: controller名称
action_name: action名称
session: session
cookies: cookie
params: 存放着参数的hash对象
rsponse
request: 进入控制器的请求对象,包含属性。
domain: 请求地址域名部分的最后两端。
remote_ip: 客户端的ip地址。
env: 返回请求的环境。
get?、post?、put?、delete?、head?
method: 返回客户端访问所使用的请求方法。
xhr?或xml_http_request? 如果请求来自AJAX的辅助方法,则返回true,否则返回false。
控制器应答:
1:渲染一个模板,最常见的(.html、 .rxml、 .rjs)。
2:返回一个字符串给浏览器,主要用于发送错误提示。
3:发送html以外的数据,这种方式通常用于提供下载。
控制器每次只响应一个请求:
render、 redirect_to、 send_xxx
1> render :action
2> render :template
3> render :file
4> render :partial 调用局部模板
:object 指定传递给局部模板的对象。
5> renderml
6> render :nothing
7> render :inline
8> render :text
9> render(:update) do |page| ... end
重要参数:
:locals => {},指定模板要使用的局部变量。
:layout => false|nil|true|模板名称。
render_to_string 可以当作render使用,不过它不会跳转,只会把整个的内容作为字符串返回。
method_missing:调用指定的action名称无效时,method_missing会渲染一个内联的模板,将action的名称和请求参数显示出来。
eg:
def method_missing(name, *args)
render(:inline => %{<h2> Unknown action: #{name}</h2>
Here are the request parameters:<br/>
<%= debug(params) %>})
end
send_data(data, options) 发送一个数据流给客户端。
参数:
:filename、 disposition(inline|attachment)、status、type
send_file(path, options) 发送一个文件给客户端
参数:
:filename、 :disposition、 :status、 :type、 buffer_size、 stream
重定向:
redirect_to(:action => "")
redirect_to(path)
redirect_to(:back) 返回上一页,注意:如果redirect_to(:back)写在了index的action中,并且访问index的时候能够直接运行redirect_to(:back),则不能直接访问index的action,因为他会跳转上一页,但是没有上一页。
过滤器:
prepend_before_filter
before_filter、append_before_filter
prepend_after_filter
after_filter、append_before_filter
参数:
:only、 :except
定义过滤器的方式:
1:
before_filter do
...
end
2:
before_filter :hello,nly => [:show, :index]
skip_filter、 skip_before_filter、skip_after_filter
参数:
:only、 :except
缓存:
片段缓存、 页面缓存、 action缓存
caches_page、 caches_action、 cache do .... end
默认配置下,cache只有在产品环境下才生效,如果要在开发环境下生效,要配置以下信息中的一条。(config/environments/development.rb)
ActionController::Base.perform_caching = true|false
config.action_controller.perform_caching = true
解除缓存:
expire_action、 expire_page、 expire_fragment :controller => "...", :action => "..."
1》默认:缓存片段的名字取决于渲染该页面的控制器名称和action名称,所以使用expire_fragment指定控制器名称和action名称来是对应的片段缓存失效。
2》指定缓存名称(等同于url_for的参数)
缓存存储机制的全局设置(environment.rb设置):
缓存目录:
config.action_controller.page_cache_directory = RAILS_ROOT + "/public/caches"
缓存类型:
config.action_controller.page_cache_extension = ".html"
缺点:导致大量的数据从网络驱动器传输到某一台具体的web服务器,然后将这些数据发送给用户,所以,如果在高吐量的网站中使用这种存储机制,服务器之间的网络宽带应该非常宽。
缓存存储体系只对action缓存和片段有效,全页面缓存必须以文件的形式放在public目录下。
页面缓存是最快速的一种缓存应用。那么应该在什么时候使用他呢?
1、对于所有用户都相同的页面
2、公开的页面,没有用户认证的页面
缓存分页:
因为页面缓存的时候会忽略掉像/blog /list?page=2这样的参数,所以你需要使用/blog/list/2这样的地址形式,而原来我们使用的是id保存参数值,现在我们需要用 page来保存参数值。
下面我们修改 /config/routes.rb文件
map.connect 'blog/list/:page',
:controller => 'blog',
:action => 'list',
:requirements => { :page => /\d+/},
:page => nil
使用了新的routes定义,我们的连接也应该改成
<%= link_to "Next Page", :controller => 'blog', :action => 'list', :page => 2 %>
最终的连接结果是"/blog/list/2",当我们点这个连接的时候,后台会处理两件事情
1、应用将2放入page这个参数中,而不是原来id这个参数
2、缓存将生成 /public/blog/list/2.html 这个页面
所以,缓存分页,就要将页面参数变成页面的一部分,而不要使用地址参数的形式,他是会被忽略的。
解决危险链接的方式:
1:使用表单和按钮(button_to)
2:使用确认页面
link_to、link_to_if、link_to_unless、button_to
stylesheet_link_tag
javascript_include_tag: 假设文件在public/javascripts目录
对象调用属性:
eg: info.name 或 info["name"]
form_for、 form_tag
form_for(:info, @info, :url => {:action => "create"}, :html => {:method => "post"}
:info 告诉rails正在操作哪个模型类的对象
@info 通过哪个实例变量获得该对象
:url 访问的action地址
:html 定义访问的方法,可以定义:multipart => true,上传。
select标签:
1>collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {:prompt => true})
2>select、select_tag "people", "<option>David</option>"
3>options_from_collection_for_select(@people, 'id', 'name')
4>options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
子表单:
<% fields_for :name, @info.name do |field| %>
Name: <%= field.text_field :name %>
<% end %>
卸载layout页面中:
<% content_for :head do %>
<ul><li>a</li><li>b</li></ul>
<% end %>
<%= yield :head %>
发送邮件:
config.action_mailer.delivery_method = :smtp|:sendmail|:test
开发环境下默认的设置是:smtp。
:smtp、:sendmail 可以让ActionMailer发送邮件。
:test 可以用于单元测试和功能测试,电子邮件不会发送出去,而是被放入一个数组。
禁止开发环境下发送邮件:
config/environments/development.rb中设置config.action_mailer.delivery_method = :test
config.action_mailer.perform_deliveries = true|false
perform_deliveries为true,邮件被正常发送,否则不发送,用于测试。
config.action_mailer.raise_delivery_errors = true|false
设置邮件发送过程中的异常是否抛还给应用程序。false为忽略,true为抛还。
config.action_mailer.default_charset = "utf-8"
设置发送邮件的字符集。
ActionMailer::Base.smtp_settings = {
:address => "smtp.163.com",:port => 25,:domain => ".com", :authentication => :login,:user_name => "zhangcaiyan0123@163.com",:password => "zhangcaiyan",}
设置smtp服务器(其中domain是域名称)
ruby script/generate mailer OrderMailer confirm sent
模型中定义方法:
def confirm(sent_at = Time.now)
subject '标题'
recipients "zhangcaiyanbeyond@gmail.com"
from 'zhangcaiyan0123@163.com'
sent_on sent_at
body "邮件正文"
end
bcc 暗送
cc 抄送
charset 默认为smtp_settings中的default_charset属性
subject 标题
from 发件人的邮箱
recipients 收件人的邮箱
body 正文
send_on 发送时间
headers 指定邮件的头信息
attachment(上传插件) :filename => 文件名称, :body => 文件内容(file.read), :content_type => "内容类型"
OrderMailer.deliver_confirm 发送邮件
email = OrderMailer.create_confirm 创建邮件对象(TMail::Mail对象)
OrderMailer.deliver(email)
注意: OrderMailer是模型类,create_confirm中的create是表示要创建对象,confirm是表示模型类中的方法,deliver表示要发送邮件。
保护rails应用:
防御sql注入攻击:
eg:这个实例就是恶意注入的例子,其中1为true,他会查处表中的所有数据。
Core::User.find(:all, :conditions => "id > '' or 1")
防止恶意注入:
绝对不要用ruby的#{...}机制直接把字符串插入到sql语句中,而应该用Rails提供的变量绑定。
eg:
Core::User.find(:all, :conditions => ["id > ?", 3])
Core::User.find(:all, :conditions => ["id > :id", {:id => 3}])
用参数直接创建记录:
User.create(params[:user])
为了防止用户模拟一些属性提交,我们可以将某些属性保护起来。
attr_protected :属性1, :属性2 列出被保护的属性
attr_accessible :属性1, :属性2 列出允许自动赋值的属性,也就是说除此之外的都是被保护的属性
被保护的属性也就是不能通过create(params[:user])直接保存,而是需要通过赋值才可以。eg:
user = User.new
user.role = params[:role]
user.save
不要相信ID参数:
当我们通过id查找出数据,然后对数据进行操作时,我们要注意某些情况:例如当我们浏览(/show/id)某个用户的数据时,可以利用url浏览其他用户的数据,为了防止这样的事发生,可以这样:
eg:
1》 order = Order.find(params[:id], :conditions => ["user_id = ?", user_id])
2》 order = @user.orders.find(id)
3》 order = @user.orders.find(id).destroy
防御XSS攻击:
攻击者把自己编写的javascript脚本放进网页,来获得他们想要的cookie,我们可以通过rails提供的h(string)辅助方法(实际上是html_escape()方法的别名)
生成rails项目的HTML文档
rake doc:app
ActiveRecord::Errors.default_error_messages = hash
修改默认错误显示信息
named_scope的使用:
named_scope :quanbu, :conditions => ["name=zcy"]
named_scopeianzhi, :lambda{|limit|{:limit = limit}}
Shili.quanbu.xianzhi(3)
显示错误页面: 当指定的异常发生时,将显示特定的页面,这个最好放到application_controller中
rescue_from 异常, :with => :action
eg:
rescue_from Exception, :with => :error
def error
render :file => "novel/infos/error", :layout => "novel"
end
对于boolean类型的数据,应该省略前面的is_,ActiveRecord会自动加个?号,映射成actived? ,不是boolean型的数据也会也会扩展?
_form下的共享表单,区间的情况
1:map.namespace :doctor do |doctor|
doctor.resources :patients
end
["doctor", @patient ]
2:map.resources :doctor do |doctor|
doctor.resources :patients
end
[@doctor, @patient ]