This document discusses ActiveRecord and various querying methods in Rails such as finding single and multiple records, adding conditions to queries, joining tables, and more. It provides code examples for methods like find, where, select, joins, and others. It also covers topics like readonly records, locking, overriding conditions, and joining with associations.
1. Ror lab. season 2
- the 10th round -
Active Record
Query Interface (1)
November 17th, 2012
Hyoseong Choi
2. ActiveRecord
• No more SQL statements MySQL
PostgreSQL
: select * from tables SQLite
...
ORM
Action Sequences
Active
1. SQL query Record
2. Result set
Finder
3. Ruby object Methods
4. (after_find callback)
3. Finder Methods of
ActiveRecord
1.where
2.select
3.group
4.order
5.reorder
6.reverse_order
7.limit
8.offset
9.joins
10.includes
11.lock
12.readonly
13.from
14.having
ActiveRecord::Relation
4. Retrieving
A Single Object
• find :ActiveRecord::RecordNotFound
• first :nil
• last :nil
• first! : ActiveRecord::RecordNotFound
• last! : ActiveRecord::RecordNotFound
5. Retrieving
A Single Object
- find -
# Find the client with primary key (id) 10.
client = Client.find(10)
SELECT * FROM clients WHERE (clients.id = 10)
ActiveRecord::RecordNotFound exception
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
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
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
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
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)
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,
SELECT * FROM clients WHERE (clients.id IN (1,10))
ActiveRecord::RecordNotFound exception
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)
OK to 1,000
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)
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
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)
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
22. 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')
24. Hash Conditions
- Range conditions -
Client.where(:created_at => (Time.now.midnight -
1.day)..Time.now.midnight)
SELECT * FROM clients WHERE (clients.created_at BETWEEN
'2008-12-21 00:00:00' AND '2008-12-22 00:00:00')
25. Hash Conditions
- Subset conditions -
Client.where(:orders_count => [1,3,5])
SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
27. 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
28. Limit & Offset
Client.limit(5)
SELECT * FROM clients LIMIT 5
Client.limit(5).offset(30)
SELECT * FROM clients LIMIT 5 OFFSET 30
29. 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)
30. 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
32. Overriding
Conditions
- except -
Post.where('id > 10').limit(20).order('id asc').except(:order)
SELECT * FROM posts WHERE id > 10 LIMIT 20
33. 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
34. Overriding
Conditions
- reorder -
class Post < ActiveRecord::Base
..
..
has_many :comments, :order => 'posted_at DESC'
end
SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC
SELECT * FROM posts WHERE id = 10 ORDER BY name
35. 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
37. Locking Records
for Update
• To prevent “race conditions”
• To ensure “atomic updates”
• Two locking mechanisms
‣ Optimistic Locking : version control
‣ Pessimistic Locking : DB lock
38. 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"
39. 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
40. 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
41. Pessimistic Locking
Item.transaction do
i = Item.lock.first
i.name = 'Jones'
i.save
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
42. 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)
43. Joining Tables
- Using a String SQL Fragment -
Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id
SELECT clients.*
FROM clients
LEFT OUTER JOIN addresses
ON addresses.client_id = clients.id
44. 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)
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 • 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
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 • Multiple Associations
belongs_to :category
has_many :comments
has_many :tags
end Post.joins(:category, :comments)
class Comment < ActiveRecord::Base
belongs_to :post and
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
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(Single Level)
belongs_to :category
has_many :comments
has_many :tags
end Post.joins(:comments => :guest)
class Comment < ActiveRecord::Base
belongs_to :post
nested
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
48. 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 => [{:comments
class Comment < ActiveRecord::Base
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
49. 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)
: using nested Hash Conditions
time_range = (Time.now.midnight - 1.day)..Time.now.midnight
Client.joins(:orders)