Rails Notes 2
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
Read more: