首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 网络技术 > 网络基础 >

rails 札记

2012-12-19 
rails 笔记启动项目:ruby script/server -e development(默认)ruby script/server -e testruby script/ser

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 ]

热点排行