Models

Active Record

  • active record: design pattern for relational databases
  • ActiveRecord: Rails implementation of active record pattern
  • Retrieve and manipulate data as objects, not as static rows

ActiveRecord objects are “intellegent”

  • Understand the structure of the table
  • Contain data from table rows
  • Know how to create, read, update, and delete rows
  • Can be manupulated as objects, then saved easily

ActiveRecord example:

user = User.new
user.first_name = "Huang"
user.save # SQL INSERT

user.last_name = "Qiang"
user.save # SQL UPDATE

user.delete # SQL DELETE

ActiveRelation

  • Added in Rails v3
  • Also known as “ARel”
  • Object-oriented interpretation of relational algebra
  • Simplifies the generation of complext database queries
    • Small queries are chainable (like most Ruby objects)
    • Complex joins and aggregations use efficient SQL
    • Queries do not execute until needed

Example:

users = User.where(:first_name => "Huang")
users = users.order("last_name ASC").limit(5)
users = users.include(:articles_authored)
# include articles authored by this user in those search results

类似的SQL:

SELECT users.*, articles.*
FROM users
LEFT JOIN articules ON (users.id = articles.author_id)
WHERE users.first_name = 'Keven'
ORDER BY last_name ASC LIMIT 5;

Models下的user.rb文件内容很简单

class User < ActiveRecord::Base
end

It’s a simple Ruby class called user which inherits from ActiveRecord Base. That’s what makes it an ActiveRecord model. It inherits ActiveRecord’s behaviors, including sensible defaults and a lot of knowledge about interacting with the database. Just by adding that inheritance, our model has become database turbo charged.

User class默认寻找叫user的table,因为我们在migration里对user的table进行了重命名,

rename_table('users', 'admin_users')

所以它会找不到user table,这里我们需要指明给它table_name。这在working with legacy databases时很有用。

class User < ActiveRecord::Base
    self.table_name = "admin_users"
end

还有一种办法是把文件user.rb,重命名为admin_user.rb,class也重命名为AdminUser

class AdminUser < ActiveRecord::Base
end

One of the wonderful things that we inherit from ActiveRecord base is that it can see what the table admin_users looks like. It knows what columns exist on that table already, and it uses those columns to automatically provide attributes and attribute methods.

If this was a purely Ruby class, then we would need to define any object attributes that we wanted to access, using code like this:

class AdminUser < ActiveRecord::Base
  attr_accessor :first_name
  
  def last_name
    @last_name
  end
  
  def last_name=(value)
    @last_name = value
  end
end

Remember all table columns have corresponding attribute methods to let you read and write values.

注意,ActiveRecord的instance不再有instance variable @的标记,例如我们定义了:

def welcome
  'hello ' + @first_name # wrong way
  'hello ' + first_name # correct way
  'hello ' + self.first_name # 同上
  'hello ' + first_name() # 同上
end

这里first_name是个method call,等同于first_name(),括号省略掉了。这是一个ruby特别要注意的地方。也有人为了区分变量与method,把所有的method call都加上括号。

Ruby在解释时会先查看这个单词在scope里有没有对应的variable,如果没有,那么看看有没有对应的method。

Rails Console

在项目根目录下输入:

$ rails console <production>
# 或者
$ rails c
Running via Spring preloader in process 3244
Loading development environment (Rails 4.2.5)
2.3.0 :001 > 

默认是load development env, 可以给参数load production等

新建record

2.3.0 :001 > subject = Subject.new
 => #<Subject id: nil, name: nil, position: nil, visible: false, created_at: nil, updated_at: nil> 
2.3.0 :002 > subject.new_record?
 => true 

new_record? :=> true代表还没存入数据库,false代表已存入

设定attributes有两种方法

方法一:instantiate之后再一项一项设定

2.3.0 :003 > subject.name = "First Subject"
 => "First Subject" 
2.3.0 :004 > subject.name
 => "First Subject" 
2.3.0 :005 > subject = Subject.new(:name => "First Subject", :position => 1, :visible => true)
 => #<Subject id: nil, name: "First Subject", position: 1, visible: true, created_at: nil, updated_at: nil> 

方法二:新建的同时用hash传递参数

2.3.0 :006 > subject.save
   (0.3ms)  BEGIN
  SQL (2.7ms)  INSERT INTO `subjects` (`name`, `position`, `visible`, `created_at`, `updated_at`) VALUES ('First Subject', 1, 1, '2016-03-02 14:40:33', '2016-03-02 14:40:33')
   (8.2ms)  COMMIT
 => true
 2.3.0 :007 > subject.new_record?
 => false

上述两种方法的save总是返回true or false,代表是否保存成功。

2.3.0 :008 > subject.id
 => 1 
2.3.0 :009 > subject
 => #<Subject id: 1, name: "First Subject", position: 1, visible: true, created_at: "2016-03-02 14:40:33", updated_at: "2016-03-02 14:40:33">

如果保存过之后attrbute没有变,第二次call save不会写入数据库,rails会根据有没有变化来决定是否写入数据库。

2.3.0 :010 > if subject.save
2.3.0 :011?>   puts "Saved!"
2.3.0 :012?>   else
2.3.0 :013 >     puts "Not saved!"
2.3.0 :014?>   end
   (0.3ms)  BEGIN
   (0.1ms)  COMMIT
Saved!
 => nil

也可以通过Subject.create()把新建和保存放在一个步骤里。

2.3.0 :015 > subject = Subject.create(:name => "Second Subject", :position => 2)
   (0.2ms)  BEGIN
  SQL (0.4ms)  INSERT INTO `subjects` (`name`, `position`, `created_at`, `updated_at`) VALUES ('Second Subject', 2, '2016-03-02 14:50:31', '2016-03-02 14:50:31')
   (7.8ms)  COMMIT
 => #<Subject id: 2, name: "Second Subject", position: 2, visible: false, created_at: "2016-03-02 14:50:31", updated_at: "2016-03-02 14:50:31"> 
2.3.0 :016 > subject
 => #<Subject id: 2, name: "Second Subject", position: 2, visible: false, created_at: "2016-03-02 14:50:31", updated_at: "2016-03-02 14:50:31"> 

注意,Subject.create()返回这个object,不返回true or false。程序员自己决定是否check 储存结果

Update records

跟新建record一样,更新record也有两种办法,可以先找到subject,一个一个更新attribute,再save。也可以通过call update_attributes通过hash参数更新。

办法一:

2.3.0 :017 > subjet = Subject.find(1)
  Subject Load (0.5ms)  SELECT  `subjects`.* FROM `subjects` WHERE `subjects`.`id` = 1 LIMIT 1
 => #<Subject id: 1, name: "First Subject", position: 1, visible: true, created_at: "2016-03-02 14:40:33", updated_at: "2016-03-02 14:40:33"> 
2.3.0 :018 > subject.name = "Initial Subject"
 => "Initial Subject" 
2.3.0 :019 > subject.save
   (0.3ms)  BEGIN
  SQL (0.5ms)  UPDATE `subjects` SET `name` = 'Initial Subject', `updated_at` = '2016-03-02 14:58:45' WHERE `subjects`.`id` = 2
   (5.7ms)  COMMIT
 => true 
2.3.0 :020 > subject
 => #<Subject id: 2, name: "Initial Subject", position: 2, visible: false, created_at: "2016-03-02 14:50:31", updated_at: "2016-03-02 14:58:45"> 

subject.save执行UPDATE SQL 命令,update_at attribute会自动更新。

方法二:

2.3.0 :021 > subject = Subject.find(2)
  Subject Load (0.5ms)  SELECT  `subjects`.* FROM `subjects` WHERE `subjects`.`id` = 2 LIMIT 1
 => #<Subject id: 2, name: "Initial Subject", position: 2, visible: false, created_at: "2016-03-02 14:50:31", updated_at: "2016-03-02 14:58:45"> 
2.3.0 :022 > subject.update_attributes(:name => "Next Subject", :visible => true)
   (0.3ms)  BEGIN
  SQL (0.5ms)  UPDATE `subjects` SET `name` = 'Next Subject', `visible` = 1, `updated_at` = '2016-03-02 15:02:10' WHERE `subjects`.`id` = 2
   (7.5ms)  COMMIT
 => true

更新总是返回true or false,所以这里我们可以根据它的返回做其它事情

2.3.0 :038 > if subject.update_attributes(:name => 'Revised Subject')
2.3.0 :039?>   puts 'Updated!'
2.3.0 :040?>   else
2.3.0 :041 >     puts 'Not updated!'
2.3.0 :042?>   end
   (0.3ms)  BEGIN
  SQL (0.4ms)  UPDATE `subjects` SET `name` = 'Revised Subject', `updated_at` = '2016-03-02 15:08:22' WHERE `subjects`.`id` = 2
   (6.0ms)  COMMIT
Updated!
 => nil 

Delete records

删除用destroy而非delete,rails也有delete,但是它bypass了一些rails的功能,会产生不可控的结果,所以不要用。

2.3.0 :043 > Subject.create(:name => 'Bad subject')
   (0.4ms)  BEGIN
  SQL (0.4ms)  INSERT INTO `subjects` (`name`, `created_at`, `updated_at`) VALUES ('Bad subject', '2016-03-02 15:12:03', '2016-03-02 15:12:03')
   (5.9ms)  COMMIT
 => #<Subject id: 3, name: "Bad subject", position: nil, visible: false, created_at: "2016-03-02 15:12:03", updated_at: "2016-03-02 15:12:03"> 
2.3.0 :044 > subject = Subject.find(3)
  Subject Load (0.6ms)  SELECT  `subjects`.* FROM `subjects` WHERE `subjects`.`id` = 3 LIMIT 1
 => #<Subject id: 3, name: "Bad subject", position: nil, visible: false, created_at: "2016-03-02 15:12:03", updated_at: "2016-03-02 15:12:03"> 
2.3.0 :045 > subject.destroy
   (0.3ms)  BEGIN
  SQL (2.9ms)  DELETE FROM `subjects` WHERE `subjects`.`id` = 3
   (7.4ms)  COMMIT
 => #<Subject id: 3, name: "Bad subject", position: nil, visible: false, created_at: "2016-03-02 15:12:03", updated_at: "2016-03-02 15:12:03">

destroy会返回这个object,可以提示用户,但是该object不能做任何修改了。

Finding records

  • Primary key finder
    • Subject.find(2) returns an object or an error
  • Dynamic Attribute based finders
    • Subject.find_by_id(2) returns an object or nil
    • Subject.find_by_name(“First Subject”)
    • 如果多于一个records被找到,它返回第一个!要返回多个,需要
    • Subject.find_all_by_name(“First Subject”)
    • Subject.find_by_name!(“First Subject”)
    • 没找到会throw Exception
    • 可以用Subject.find_ColomnName_and_ColomnName(param1, param2)同时给出多个条件
    • Subject.find_by_name_and_position(“First Subject”, 1)
    • 上述find methods are handled by method_missing
  • Find all method
    • Subject.all returns an array of objects
  • First/last methods
    • Subject.first, Subject.last returns an object or nil

上述这些finder,都会直接call database,ActiveRelation Queries则是到需要的时候call。

Query Methods

Conditions

  • where(conditions)
    • Subject.where(:visible => true)
  • Returns an ActiveRelation object, which can be chained
    • Subject.where(:visible => true).order(‘positon ASC’)
  • Does not execute a database call immediately
  • empty?检查是否为空。
Condition expression types
  • String
    • “name = ‘First Subject’ AND visible = true”
    • Flexible, raw SQL
    • Use carefully and beware of SQL injection
    • 所以尽量不要用,如果要用,用hard coded string,不要从用户端接收数据,也不要从database里取数据。
  • Array
    • [“name = ? AND visible = true”, “First Subject”]
    • Flexible, esacaped SQL, safe from SQL injection
  • Hash
    • Simple, escaped SQL, safe from SQL injection
    • Each key-value pair is joined with AND
    • Only supports equality, range, and subset checking
    • No OR, LIKE, less than, or greater than
    • 用array type来实现上述query

Example

hash type

2.3.0 :057 > subjects = Subject.where(:visible => true)
  Subject Load (18.4ms)  SELECT `subjects`.* FROM `subjects` WHERE `subjects`.`visible` = 1
 => #<ActiveRecord::Relation [#<Subject id: 1, name: "First Subject", position: 1, visible: true, created_at: "2016-03-02 14:40:33", updated_at: "2016-03-02 14:40:33">, #<Subject id: 2, name: "Revised Subject", position: 2, visible: true, created_at: "2016-03-02 14:50:31", updated_at: "2016-03-02 15:08:22">]> 
2.3.0 :059 > subjects.class
 => Subject::ActiveRecord_Relation
2.3.0 :060 > subjects.to_sql
 => "SELECT `subjects`.* FROM `subjects` WHERE `subjects`.`visible` = 1" 

它不返回Array,返回的是ActiveRelation Object。

可以使用subjects.to_sql查询它的sql语句。

string type

2.3.0 :061 > subjects = Subject.where("visible = true")                                                                    
  Subject Load (0.5ms)  SELECT `subjects`.* FROM `subjects` WHERE (visible = true)
 => #<ActiveRecord::Relation [#<Subject id: 1, name: "First Subject", position: 1, visible: true, created_at: "2016-03-02 14:40:33", updated_at: "2016-03-02 14:40:33">, #<Subject id: 2, name: "Revised Subject", position: 2, visible: true, created_at: "2016-03-02 14:50:31", updated_at: "2016-03-02 15:08:22">]> 

array type

2.3.0 :063 > subjects = Subject.where(["visible = ?", true])                                                           
  Subject Load (0.6ms)  SELECT `subjects`.* FROM `subjects` WHERE (visible = 1)
 => #<ActiveRecord::Relation [#<Subject id: 1, name: "First Subject", position: 1, visible: true, created_at: "2016-03-02 14:40:33", updated_at: "2016-03-02 14:40:33">, #<Subject id: 2, name: "Revised Subject", position: 2, visible: true, created_at: "2016-03-02 14:50:31", updated_at: "2016-03-02 15:08:22">]> 

Daisy Chain

subjects = Subject.where(:visible => true).where(:position => 1)
# 完全相同
subjects = Subject.where(:visible => true, :position => 1)

where method永远返回一个集合,即使只有一个item。例如:

2.3.0 :083 >   subject = Subject.where(:id => 1).first                                                                     
  Subject Load (19.8ms)  SELECT  `subjects`.* FROM `subjects` WHERE `subjects`.`id` = 1  ORDER BY `subjects`.`id` ASC LIMIT 1
 => #<Subject id: 1, name: "First Subject", position: 1, visible: true, created_at: "2016-03-02 14:40:33", updated_at: "2016-03-02 14:40:33">

Other Methods

  • order(sql_fragment)
  • limit(integer)
  • offset(integer)

Example:

Subject.order("postion ASC").limit(20).offset(40)
  • Order SQL Format
    • table_name.column_name ASC/DESC
    • table_name
      • Not necessary for single table
      • Recommended with joined tables
      • Required when joined tables have same column names
        • order(“subjects.created_at ASC”)
        • sort by more than one column
        • order(“subjects.visible DESC, subjects.name ASC”)
        • 可以夸table,中间用逗号隔开。

Examples:

2.3.0 :084 > subjects = Subject.where(:visible => true).order("position ASC")
  Subject Load (14.2ms)  SELECT `subjects`.* FROM `subjects` WHERE `subjects`.`visible` = 1  ORDER BY position ASC
 => #<ActiveRecord::Relation [#<Subject id: 1, name: "First Subject", position: 1, visible: true, created_at: "2016-03-02 14:40:33", updated_at: "2016-03-02 14:40:33">, #<Subject id: 2, name: "Revised Subject", position: 2, visible: true, created_at: "2016-03-02 14:50:31", updated_at: "2016-03-02 15:08:22">]> 
2.3.0 :085 > subjects = Subject.where(:visible => true).order("position DESC").limit(1)
  Subject Load (0.6ms)  SELECT  `subjects`.* FROM `subjects` WHERE `subjects`.`visible` = 1  ORDER BY position DESC LIMIT 1
 => #<ActiveRecord::Relation [#<Subject id: 2, name: "Revised Subject", position: 2, visible: true, created_at: "2016-03-02 14:50:31", updated_at: "2016-03-02 15:08:22">]>
2.3.0 :086 > subjects = Subject.where(:visible => true).order("position DESC").limit(1).offset(1)
  Subject Load (0.8ms)  SELECT  `subjects`.* FROM `subjects` WHERE `subjects`.`visible` = 1  ORDER BY position DESC LIMIT 1 OFFSET 1
 => #<ActiveRecord::Relation [#<Subject id: 1, name: "First Subject", position: 1, visible: true, created_at: "2016-03-02 14:40:33", updated_at: "2016-03-02 14:40:33">]>
2.3.0 :087 > subjects = Subject.where(:visible => true).order("subjects.position DESC").limit(1).offset(1)                 
  Subject Load (0.7ms)  SELECT  `subjects`.* FROM `subjects` WHERE `subjects`.`visible` = 1  ORDER BY subjects.position DESC LIMIT 1 OFFSET 1
 => #<ActiveRecord::Relation [#<Subject id: 1, name: "First Subject", position: 1, visible: true, created_at: "2016-03-02 14:40:33", updated_at: "2016-03-02 14:40:33">]> 

Named scopes

  • Queries defined in a model
  • Defined using ActiveRelation query methods
  • Can be called like ActiveRelation methods
  • Can accept paramenters
  • Rails 4 requires lambda syntax

syntax

scope :active, lambda {where:(:active => true}
# 等同于
def self.active
	where(:active => true)
end
Customer.active #call scope
# scope with parameters
scope :with_content_type, lambda {|ctype|
  where(:content_type => ctype)
}
# 等同于
def self.with_content_type(ctype)
  where(:content_type => ctype)
end

Section.with_content_type('html')

Why lambda?

lambda evaluated when called, not when defined

scopre :recent, lambda {
  where(:created_at => 1.week.ago..Time.now)
}
# Chaining scopes
Article.recent.visible.newest_first

Example:

class Subject < ActiveRecord::Base
    
    scope :visible, lambda { where(:visible => true) }
    scope :invisible, lambda { where(:visible => false) }
    scope :sorted, lambda { order('subjects.position ASC') }
    scope :newest_first, lambda { order('subjects.created_at DESC') }
    scope :search, lambda {|query|
      where(["name LIKE ?", "%#{query}%"]) #search query somewhere in it
    }
end

Subject.visible
Subject.invisible
Subject.visible.sorted
Subject.visible.newest_first
Subject.search("vise")
Subject.search("Initial") # search sth not exist
Subject.search("Initial").nil? # false
Subject.search("Initial").empty? # true