Activity 2-unit 2-update 2024. English translation
ActiveRecord Query Interface (1), Season 1
1. The 9th Round of ROR Lab.
Active Record
Query Interface(1)
March 17th, 2012
Hyoseong Choi
ROR Lab.
2. ActiveRecord
• No more SQL statements MySQL
PostgreSQL
: select * from tables SQLite
...
ORM
Active
• SQL query Record
• Fire Finder
• Ruby object Methods
• after_find callback
ROR Lab.
4. Retrieving
A Single Object
• find
• first
• last
• first!
• last!
ROR Lab.
5. Retrieving
A Single Object
- find -
# Find the client with primary key (id) 10.
client = Client.find(10)
# => #<Client id: 10, first_name: "Ryan">
SELECT * FROM clients WHERE (clients.id = 10)
ActiveRecord::RecordNotFound exception
ROR Lab.
6. Retrieving
A Single Object
- first -
client = Client.first
# => #<Client id: 1, first_name: "Lifo">
SELECT * FROM clients LIMIT 1
nil if no matching record is found
ROR Lab.
7. Retrieving
A Single Object
- last -
client = Client.last
# => #<Client id: 221, first_name: "Russel">
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
nil if no matching record is found
ROR Lab.
8. Retrieving
A Single Object
- first! -
client = Client.first!
# => #<Client id: 1, first_name: "Lifo">
SELECT * FROM clients LIMIT 1
RecordNotFound if no matching record
ROR Lab.
9. Retrieving
A Single Object
- last! -
client = Client.last!
# => #<Client id: 221, first_name: "Russel">
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
RecordNotFound if no matching record
ROR Lab.
10. Retrieving
Multiple Objects
• Using multiple primary keys
• In batches
• find_each :batch_size, :start,
• find_in_batches :batch_size, :start
+ find options (except for :order, :limit)
ROR Lab.
11. Retrieving
Multiple Objects
- Using multiple primary keys -
# Find the clients with primary keys 1 and 10.
client = Client.find([1, 10])
# Or even Client.find(1, 10)
# => [#<Client id: 1, first_name: "Lifo">,
#<Client id: 10, first_name: "Ryan">]
SELECT * FROM clients WHERE (clients.id IN (1,10))
ActiveRecord::RecordNotFound exception
ROR Lab.
12. Retrieving
Multiple Objects
- in Batches -
to iterate over a large set of records
• find_each : each record to the block individually as a model
• find_in_batches : the entire batch to the block as an array of models
# This is very inefficient when the users table
has thousands of rows.
User.all.each do |user|
NewsLetter.weekly_deliver(user)
end
OK to 1,000
ROR Lab.
13. Retrieving
Multiple Objects
- in Batches : find_each -
User.find_each do |user|
NewsLetter.weekly_deliver(user)
end
User.find_each(:batch_size => 5000) do |user|
NewsLetter.weekly_deliver(user)
end
User.find_each(:start => 2000, :batch_size => 5000)
do |user|
NewsLetter.weekly_deliver(user)
end
ROR Lab.
14. Retrieving
Multiple Objects
- in Batches : find_each -
Article.find_each { |a| ... } # => iterate over all
articles, in chunks of 1000 (the default)
Article.find_each(:conditions => { :published =>
true }, :batch_size => 100 ) { |a| ... }
# iterate over published articles in chunks of
100
http://archives.ryandaigle.com/articles/
2009/2/23/what-s-new-in-edge-rails-batched-find
ROR Lab.
15. Retrieving
Multiple Objects
- in Batches : find_in_batches -
# Give add_invoices an array of 1000 invoices at a
time
Invoice.find_in_batches(:include => :invoice_lines)
do |invoices|
export.add_invoices(invoices)
end
options :
• :batch_size & :start
• options of find method (except :order and :limit)
ROR Lab.
16. Retrieving
Multiple Objects
- in Batches : find_in_batches -
Article.find_in_batches { |articles| articles.each { |
a| ... } } # => articles is array of size 1000
Article.find_in_batches(:batch_size => 100 ) { |articles|
articles.each { |a| ... } }
# iterate over all articles in chunks of 100
class Article < ActiveRecord::Base
scope :published, :conditions => { :published => true }
end
Article.published.find_in_batches(:batch_size => 100 )
{ |articles| ... }
# iterate over published articles in chunks of 100
ROR Lab.
21. Array Conditions
- Range conditions -
Client.where(:created_at =>
(params[:start_date].to_date)..
(params[:end_date].to_date))
SELECT "clients".* FROM "clients" WHERE
("clients"."created_at" BETWEEN '2010-09-29' AND
'2010-11-30')
ROR Lab.
26. Selecting
If the select method is used, all the returning objects will be read only.
Client.select("viewable_by, locked")
SELECT viewable_by, locked FROM clients
ActiveModel::MissingAttributeError: missing attribute: <attribute>
Client.select(:name).uniq
SELECT DISTINCT name FROM clients
query = Client.select(:name).uniq
# => Returns unique names
query.uniq(false)
# => Returns all names, even if there are duplicates
ROR Lab.
28. Group
Order.select(
"date(created_at) as ordered_date, sum(price) as
total_price")
.group("date(created_at)")
SQL
SELECT date(created_at) as ordered_date, sum(price)
as total_price FROM orders GROUP BY
date(created_at)
ROR Lab.
29. Having
Order.select(
"date(created_at) as ordered_date, sum(price) as
total_price")
.group("date(created_at)")
.having("sum(price) > ?", 100)
SQL
SELECT date(created_at) as ordered_date, sum(price)
as total_price FROM orders GROUP BY
date(created_at) HAVING sum(price) > 100
ROR Lab.
31. Overriding
Conditions
- except -
Post.where('id > 10').limit(20).order('id asc').except(:order)
SELECT * FROM posts WHERE id > 10 LIMIT 20
ROR Lab.
32. Overriding
Conditions
- only -
Post.where('id > 10').limit(20).order('id desc').only(:order, :where)
SELECT * FROM posts WHERE id > 10 ORDER BY id DESC
ROR Lab.
33. Overriding
Conditions
- reorder -
class Post < ActiveRecord::Base
..
..
has_many :comments, :order => 'posted_at DESC'
end
Post.find(10).comments.reorder('name')
SELECT * FROM posts WHERE id = 10 ORDER BY name
SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC
ROR Lab.
34. Overriding
Conditions
- reverse_order -
Client.where("orders_count > 10").order(:name).reverse_order
SELECT * FROM clients WHERE orders_count > 10 ORDER BY name DESC
Client.where("orders_count > 10").reverse_order
SELECT * FROM clients WHERE orders_count > 10 ORDER BY
clients.id DESC
ROR Lab.
36. Locking Records
for Update
• To prevent “race conditions”
• To ensure “atomic updates”
• Two locking mechanisms
‣ Optimistic Locking : version control
‣ Pessimistic Locking : DB lock
ROR Lab.
37. Optimistic Locking
• “lock_version” in DB table (default to 0)
• set_locking_column to change column name
c1 = Client.find(1)
c2 = Client.find(1)
c1.first_name = "Michael"
c1.save # increments the lock_version column
c2.name = "should fail"
c2.save # Raises an ActiveRecord::StaleObjectError
ROR Lab.
38. Optimistic Locking
• To turn off,
ActiveRecord::Base.lock_optimistically = false
• To override the name of the lock_version
column
class Client < ActiveRecord::Base
set_locking_column :lock_client_column
end
ROR Lab.
39. Pessimistic Locking
• A locking mechanism by DB
• An exclusive lock on the selected rows
• Usually wrapped inside a transaction
• Two types of Lock
‣ FOR UPDATE (default, an exclusive lock)
‣ LOCK IN SHARE MODE
ROR Lab.
40. Pessimistic Locking
Item.transaction do
i = Item.lock.first
i.name = 'Jones'
i.save
end
SQL (0.2ms) BEGIN
Item Load (0.3ms) SELECT * FROM `items` LIMIT 1 FOR UPDATE
Item Update (0.4ms) UPDATE `items` SET `updated_at` =
'2009-02-07 18:05:56', `name` = 'Jones' WHERE `id` = 1
SQL (0.8ms) COMMIT
ROR Lab.
41. Pessimistic Locking
Item.transaction do
i = Item.lock("LOCK IN SHARE MODE").find(1)
i.increment!(:views)
end
item = Item.first
item.with_lock do
# This block is called within a transaction,
# item is already locked.
item.increment!(:views)
end
ROR Lab.
42. Joining Tables
- Using a String SQL Fragment -
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
ROR Lab.
43. Joining Tables
- Using Array/Hash of Named Associations -
only with INNER JOIN
• a Single Association
• Multiple Associations
• Nested Associations(Single Level)
• Nested Associations(Multiple Level)
ROR Lab.
44. Joining Tables
- Using Array/Hash of Named Associations -
only with INNER JOIN
class Category < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base • a Single Association
belongs_to :category
has_many :comments
has_many :tags
end Category.joins(:posts)
class Comment < ActiveRecord::Base
belongs_to :post
has_one :guest
end SELECT categories.*
FROM categories
class Guest < ActiveRecord::Base
INNER JOIN posts
belongs_to :comment
end ON posts.category_id = categories.id
class Tag < ActiveRecord::Base
belongs_to :post “return a Category object for all categories with posts”
end
ROR Lab.
45. Joining Tables
- Using Array/Hash of Named Associations -
only with INNER JOIN
class Category < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base • Multiple Associations
belongs_to :category
has_many :comments
has_many :tags
end Post.joins(:category, :comments)
class Comment < ActiveRecord::Base
belongs_to :post
has_one :guest
SELECT posts.* FROM posts
end
INNER JOIN categories
class Guest < ActiveRecord::Base ON posts.category_id = categories.id
belongs_to :comment INNER JOIN comments
end ON comments.post_id = posts.id
class Tag < ActiveRecord::Base
“return all posts that have a category and at least one comment”
belongs_to :post
end
ROR Lab.
46. Joining Tables
- Using Array/Hash of Named Associations -
only with INNER JOIN
class Category < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base • Nested Associations(Single Level)
belongs_to :category
has_many :comments
has_many :tags
end Post.joins(:comments => :guest)
class Comment < ActiveRecord::Base
belongs_to :post
has_one :guest
SELECT posts.* FROM posts
end
INNER JOIN comments
class Guest < ActiveRecord::Base ON comments.post_id = posts.id
belongs_to :comment INNER JOIN guests
end ON guests.comment_id = comments.id
class Tag < ActiveRecord::Base
belongs_to :post “return all posts that have a comment made by a guest”
end
ROR Lab.
47. Joining Tables
- Using Array/Hash of Named Associations -
only with INNER JOIN
class Category < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base • Nested Associations(Multiple Level)
belongs_to :category
has_many :comments
has_many :tags
end Category.joins(:posts =>
class Comment < ActiveRecord::Base
[{:comments => :guest}, :tags]
belongs_to :post
has_one :guest
end
SELECT categories.* FROM categories
class Guest < ActiveRecord::Base INNER JOIN posts ON posts.category_id = categories.id
belongs_to :comment
INNER JOIN comments ON comments.post_id = posts.id
end
INNER JOIN guests ON guests.id = comments.quest_id
class Tag < ActiveRecord::Base INNER JOIN tags ON tags.post_id = posts.id
belongs_to :post
end
ROR Lab.
48. Joining Tables
- Specifying Conditions on the Joined Tables -
: using Array and String Conditions
time_range = (Time.now.midnight - 1.day)..Time.now.midnight
Client.joins(:orders)
.where('orders.created_at' => time_range)
: using nested Hash Conditions
time_range = (Time.now.midnight - 1.day)..Time.now.midnight
Client.joins(:orders)
.where(:orders => {:created_at => time_range})
ROR Lab.
49. Joining Tables
- Inner Join -
SELECT <select_list>
FROM TableA A
INNER JOIN TableB B
ON A.Key = B.Key
ROR Lab.
50. Joining Tables
- Left Join -
SELECT <select_list> SELECT <select_list>
FROM TableA A FROM TableA A
LEFT JOIN TableB B LEFT JOIN TableB B
ON A.Key = B.Key ON A.Key = B.Key
WHERE B.Key IS NULL
ROR Lab.
51. Joining Tables
- Right Join -
SELECT <select_list> SELECT <select_list>
FROM TableA A FROM TableA A
RIGHT JOIN TableB B RIGHT JOIN TableB B
ON A.Key = B.Key ON A.Key = B.Key
WHERE A.Key IS NULL
ROR Lab.
52. Joining Tables
- Full Outer Join -
SELECT <select_list> SELECT <select_list>
FROM TableA A FROM TableA A
FULL OUTER JOIN TableB B FULL OUTER JOIN TableB B
ON A.Key = B.Key ON A.Key = B.Key
WHERE A.Key IS NULL
OR B.Key IS NULL
ROR Lab.