ruby 数据sql操作

ActiveRecord

ActiveRecord 是Rails 的ORM 元件,负责与资料库沟通,让我们可以用物件导向的语法操作资料库。在”打造CRUD 应用程式”一章中提到的对应概念如下:

  • 将资料库表格(table) 对应到一个类别(classe)
  • 类别方法就是操作表格(table)
  • 将资料库一列(row) 对应到一个物件(object)
  • 物件方法就是操作个别的资料(row)
  • 将资料库栏位(column) 对应到物件的属性(object attribute)

因此,资料库里面的资料表,我们用一个Model 类别来表示,而其中的一笔资料,就是一个Model 物件。

ActiveRecord这个函式库实作了Martin Fowler的Active Record设计模式(Design Pattern) http://martinfowler.com/eaaCatalog/activeRecord.html

ORM 与抽象渗漏法则

ORM (Object-relational mapping ) 是一种对映设关联式资料与物件资料的程式技术。物件导向和从数学理论发展出来的关联式资料库,有着显著的区别,而ORM 正是解决这个不匹配问题所产生的工具。它可以让你使用物件导向语法来操作关联式资料库,非常容易使用、撰码十分有效率,不需要撰写繁琐的SQL语法,同时也增加了程式码维护性。

不过,有些熟悉SQL 语法的程式设计师反对使用这样的机制,因为直接撰写SQL 可以确保操作资料库的执行效率,毕竟有些时候ORM 产生出来的SQL 效率不是最佳解,而你却不一定有经验能够意识到什么时候需要担心或处理这个问题。

知名软体人Joel Spolsky (他有两本中文翻译书值得推荐:约耳趣谈软体和约耳续谈软体,悦知出版)有个理论:抽象渗漏法则:所有重大的抽象机制在某种程式上都是有漏洞的。有非常多程式设计其实都是在建立抽象机制,C语言简化了组合组言的繁杂、动态语言如Ruby简化了C语言、TCP协定简化了IP通讯协定,甚至车子的挡风玻璃跟雨刷也简化了下雨的事实。

但是这些抽象机制或多或少都会力有未及的地方,用C 语言撰写的Linux 核心也包括少量组合语言、部分Ruby 套件用C 语言撰写扩充来增加效能、保证讯息会抵达TCP 讯息,碰到IP 封包在路由器上随机遗失的时候,你也只会觉得速度很慢、即使有挡风玻璃跟雨刷,开车还是必须小心路滑。

当某人发明一套神奇可以大幅提升效率的新程式工具时,就会听到很多人说:「应该先学会如何手动进行,然后才用这个神奇的工具来节省时间。」任何抽象机制都有漏洞,而唯一能完美处理漏洞的方法,就是只去弄懂该抽象原理以及所隐藏的东西。这是否表示我们应该永远只应该使用比较低阶的工具呢?不是这样的。而是应该依照不同的情境,选择效益最大的抽象化工具。以商务逻辑为多的Web 应用程式,选择动态语言开发就相对合适,用C 语言开发固然执行效率极高,但是完成相同的功能却需要极高的人月开发时数。如果是作业系统,使用无法随意控制记忆体分配的动态语言也显然不是个好主意。

能够意识到什么时候抽象化工具会产生渗漏,正是”有纯熟经验”的程式设计师和”新手”设计师之间的差别。ORM 虽然替我们节省了工作的时间,不过对资深的程式设计师来说,学习SQL 的时间还是省不掉的。这一切都似乎表示,即使我们拥有愈来愈高阶的程式设计工具,抽象化也做得愈来愈好,要成为一个由高阶到低阶都纯熟的程式设计专家是愈来愈困难了(也越来越稀有及宝贵)。

建立Model

首先,让我们示范如何建立一个Model:

rails g model Category

这个指令会产生几个档案

category.rb
category_test.rb
categories.yml
xxxxxxxx_create_categories.rb

打开xxxxxxxx_create_categories.rb 你可以看到资料表的定义,让我们加上几个栏位吧:

class CreateCategories < ActiveRecord::Migration
    def self.up
        create_table :categories do |t|
          t.string :name
          t.integer :position
          t.timestamps
        end
      end

  def self.down
    drop_table :categories
  end
end

接着执行以下指令便会产生出资料库资料表

rake db:migrate

db:migrate 指令会将上述的Ruby 程式变成以下SQL 执行。

CREATE TABLE categories (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"name" varchar(255) DEFAULT NULL,
"position" int(4) DEFAULT NULL,
"created_at" datetime DEFAULT NULL,
"updated_at" datetime DEFAULT NULL);    

接着我们打开category.rb 你可以看到

class Category < ActiveRecord::Base
end

这是一个继承ActiveRecord::Base 的Category 类别。

我们在学习Ruby 的时候提过irb 这个互动工具,而Rails 也提供了特殊的irb 介面叫做console,让我们可以直接与Rails 程式互动:

rails console (可以簡寫成 rails c)

透过console,我们可以轻易的练习操作ActiveRecord。

观看Log

不像rails server 可以直接看到log,在Rails 主控台下必须透过观察log 档案。我们可以透过log 观察到Rails 产生出来的SQL 长的如何。

tail -f log/development.log

Windows上没有这个指令,可以安装  Tail for Win32  这个工具来即时观察log档案。或是安装  GNU utilities for Win32  来获得tail指令。

基础操作

如何新增

ActiveRecord提供了四种API,分别是save、save!、create和create!:

a = Category.new( :name => 'Ruby', :position => 1 )
a.save

b = Category.new( :name => 'Perl', :position => 2 )
b.save!
        
Category.create( :name => 'Python', :position => 3 )
c = Category.create!( :name => 'PHP', :position => 4 )

其中createcreate!就等于new完就savesave!,有无惊叹号的差别在于validate资料验证不正确的动作,无惊叹号版本会回传布林值(true或false),有惊叹号版本则是验证错误会丢出例外。

何时使用惊叹号版本呢?save和create通常用在会处理回传布林值(true/false)的情况下(例如在controller 里面根据成功失败决定render 或redirect),否则在预期应该会储存成功的情况下,请用save!或create! 来处理,这样一旦碰到储存失败的情形,才好追踪bug。

透过:valiate => false 可以略过验证

c.save( :validate => false )

在Rails3 之前的版本是user.save(false)

如何查询

ActiveRecord 使用了Arel 技术来实作查询功能,你可以自由组合where、limit、select、order 等条件。

Arel 是relational algebra” library。但根据2.0 实作者tenderlove 的说法,也可以说是一种SQL compiler。 http://engineering.attinteractive.com/2010/12/architecture-of-arel-2-0/

first, last 和all

这三个方法可以分别拿出资料库中的第一笔、最后一笔及全部的资料:

c1 = Category.first
c2 = Category.last
categories = Category.all # 這會是一個陣列

如果资料量较多,请不要在正式上线环境中执行.all 把所有资料拿出来,这样会耗费非常多的记忆体。请用分页或缩小查询范围。

find

已知资料的主键ID 的值的话,可以使用find 方法:

c3 = Category.find(1)
c4 = Category.find(2)

find 也可以接受阵列参数,这样就会一次找寻多个并回传阵列:

arr = Category.find([1,2])
# 或是
arr = Category.find(1,2)

如果找不到资料的话,会丢ActiveRecord::RecordNotFound 例外。如果是find_by_id 就不会丢出例外,而是回传nil。

find_by_* 和find_all_by_*

find_by_* 和find_all_by_* 是Rails 的动态方法,可以自由用and 组合,例如:

c5 = Category.find_by_name('Ruby')
c6 = Category.find_by_name_and_position('Ruby', 1)
c7 = Category.find_all_by_position(2)

find_by_sql

如果需要手动撰写SQL,可以使用find_by_sql,例如:

c8 = Category.find_by_sql("select * from categories")

不过在绝大多数的情况,是不需要手动写SQL 的。

where 查询条件

where 可以非常弹性的组合出SQL 查询,例如:

c9 = Category.where( :name => 'Ruby', :position => 1 )
c10 = Category.where( [ "name = ? or position = ?", 'Ruby', 2] )

其中参数有两种写法,一种是Hash,另一种是Array。前者的写法虽然比较简洁,但是就没办法写出or 的查询。注意到不要使用字串写法,例如

Category.where("name = #{params[:name]}") # 請不要這樣寫

这是因为字串写法会有SQL injection 的安全性问题,请改用阵列写法。

另外,where 是lazy loading,也就是直到真的需要取值的时候,才会跟资料库拿资料。如果需要立即触发,可以接着使用.all, .first, .last,例如

c11 = Category.where( :name => 'Ruby', :position => 1 ).all

limit

limit 可以限制笔数

c = Category.limit(5).all
c.size # 5

order

order 可以设定排序条件

Category.order("position")
Category.order("position DESC")
Category.order("position DESC, name ASC")

如果要消去order条件,可以用reorder

Category.order("position").reorder("name") # 改用 name 排序
Category.order("position").reorder(nil) # 取消所有排序

offset

offset 可以设定忽略前几笔不取出,通常用于资料分页:

c = Category.limit(2)
c.first.id # 1
Category.limit(2).offset(3)
c.first.id # 4

select

预设的SQL 查询会取出资料的所有栏位,有时候你可能不需要所有资料,为了效能我们可以只取出其中特定栏位:

Category.select("id, name")

例如栏位中有Binary 资料时,你不会希望每次都读取出庞大的Binary 资料占用记忆体,而只希望在使用者要下载的时候才读取出来。

joins

针对Model中的belongs_tohas_many关连,可以使用joins,也就是INNER JOIN

Category.joins(:events)
# SELECT categories.* FROM categories INNER JOIN events ON events.category_id = categories.id

可以一次关连多个:

Post.joins(:category, :comments)

不过这样抓出来的category物件是没有event物件的,如果需要一次载入出来,会使用includesjoins主要的用途是条件:

Category.joins(:events).where("events.name is NOT NULL")

也可以直接给SQL字串:

Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id = clients.id')
# SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id

includes

includes可以预先将关连的资料读取出来,避免N+1问题(见效能一章)

Event.includes(:category)
# SELECT * FROM events
# SELECT * FROM categories WHERE categories.id IN (1,2,3...)

同理,也可以一次载入多个关连:

Post.includes(:category, :comments)

includes方法也可以加上条件:

Event.includes(:category).where( :category => { :position => 1 } )
# 或 Event.includes(:category).where( "categories.position = 1" )

group

(TODO)

lock

(TODO)

readonly

(TODO)

from

(TODO)

having

串接写法

以上的where, order , limit, offset, joins, select 等等,都可以自由串接起来组合出最终的SQL 条件:

c12 = Category.where( :name => 'Ruby' ).order("id desc").limit(3)

find_each 批次处理

如果资料量很大,但是又需要全部拿出来处理,可以使用find_each 批次处理

Category.where("position > 1").find_each do |category|
    category.do_some_thing
end

预设会批次捞1000 笔,如果需要设定可以加上:batch_size 参数。

重新载入

如果已经读取的AR 资料,需要重新载入,可以用reload 方法:

p = Category.first
p.reload

如何删除

一种是先抓到该物件,然后删除:

c12 = Category.first
c12.destroy

另一种是直接对类别呼叫删除,传入ID 或条件:

Category.delete(2)
Category.delete_all(conditions = nil)
Category.destroy_all(conditions = nil) 

delete 不会有callback 回呼,destroy 有callback 回呼。什么是回呼详见下一章。

统计方法

Category.count
Category.average(:position)
Category.maximum(:position)
Category.sum(:position)

其中我们可以利用上述的where 条件缩小范围,例如:

Category.where( :name => "Ruby").count

如何更新

c13 = Category.first
c13.update_attributes(attributes)
c13.update_attributes!(attributes)
c13.update_attribute(attribute_name, value)

注意update_attribute 会略过validation 资料验证注意mass assign 安全性问题,可以透过attr_protected 或attr_accessor 设定,详见安全性一章。

Scopes 作用域

Model Scopes是一项非常酷的功能,它可以将常用的查询条件宣告起来,让程式变得干净易读,更厉害的是可以串接使用。例如,我们编辑app/models/event.rb,加上两个Scopes

class Event < ActiveRecord::Base
    scope :public, where( :is_public => true )
    scope :recent_three_days, where(["created_at > ? ", Time.now - 3.days ])
end

Event.create( :name => "public event", :is_public => true )
Event.create( :name => "private event", :is_public => false )
Event.create( :name => "private event", :is_public => true )

Event.public
Event.public.recent_three_days

串接的顺序没有影响

接着,我们可以设定一个预设的Scope,通常会拿来设定排序:

class Event < ActiveRecord::Base    
    default_scope order('id DESC')        
end

unscoped方法可以暂时取消预设的default_scope

Event.unscoped do
    Event.all
    # SELECT * FROM events
end

最后,Scope也可以接受参数,例如:

class Event < ActiveRecord::Base
    scope :recent, lambda{ |date| where(["created_at > ? ", date ]) } 
    # 或 scope :recent, Proc.new{ |t| where(["created_at > ? ", t ]) }
end

Event.recent( Time.now - 7.days )

不过,笔者会推荐上述这种带有参数的Scope,改成如下的类别方法,可以比较明确看清楚参数是什么,特别是你想给预设值的时候:

class Event < ActiveRecord::Base
    def recent(t=Time.now)
        where(["created_at > ? ", t ])
    end
end

Event.recent( Time.now - 7.days )

这样的效果是一样的,也是一样可以和其他Scope做串接。

scoped方法可以将Model转成可以串接的形式,方便依照参数组合出不同查询,例如

fruits = Fruit.scoped
fruits = fruits.where(:colour => 'red') if options[:red_only]
fruits = fruits.limit(10) if limited?

自定attribute 与资料库互动

(TODO)

使用attr_accessor

可以使用read_attribute 和write_attribute 这两个比较底层的API

 

原文地址:https://www.cnblogs.com/wangyuyu/p/3242611.html

猜你喜欢

转载自blog.csdn.net/qq_42672332/article/details/86645344
今日推荐