Active Record Query Interface(1)
The 9th Round of ROR Lab.
March 17th, 2012
Hyoseong ChoiROR Lab.
ROR Lab.
ActiveRecord
• No more SQL statements: select * from tables
MySQLPostgreSQLSQLite...
ActiveRecord
FinderMethods
• SQL query• Fire• Ruby object• after_find callback
ORM
ROR Lab.
Finder Methods of ActiveRecord
1.where2.select3.group4.order5.reorder6.reverse_order7.limit8.offset9.joins10.includes11.lock12.readonly13.from14.having
ActiveRecord::Relation
ROR Lab.
Retrieving A Single Object
• find
• first
• last
• first!
• last!
ROR Lab.
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.
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.
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.
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.
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.
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.
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.
Retrieving Multiple Objects
- in Batches -
• 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
to iterate over a large set of records
# 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.
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.
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.
Retrieving Multiple Objects
- in Batches : find_in_batches -
# Give add_invoices an array of 1000 invoices at a timeInvoice.find_in_batches(:include => :invoice_lines) do |invoices| export.add_invoices(invoices)end
• :batch_size & :start• options of find method (except :order and :limit)
options :
ROR Lab.
Retrieving Multiple Objects
- in Batches : find_in_batches -
Article.find_in_batches { |articles| articles.each { |a| ... } } # => articles is array of size 1000Article.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.
Conditions
• String conditions
• Array conditions
• Hash conditions
- where -
ROR Lab.
String Conditions
Client.where("orders_count = ‘2’")
ROR Lab.
Array Conditions
Client.where("orders_count = ?", params[:orders])
Client.where("orders_count = ? AND locked = ?",
params[:orders], false)
Client.where("orders_count = #{params[:orders]}")Xhacking!!! by SQL injection
http://guides.rubyonrails.org/security.html#sql-injection
ROR Lab.
Array Conditions- Placeholder conditions -
Client.where("created_at >= :start_date AND created_at <= :end_date", {:start_date => params[:start_date], :end_date => params[:end_date]})
ROR Lab.
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.
Hash Conditions- Equality conditions -
Client.where(:locked => true)
Client.where('locked' => true)
ROR Lab.
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')
ROR Lab.
Hash Conditions- Subset conditions -
Client.where(:orders_count => [1,3,5])
SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
ROR Lab.
Ordering
Client.order("created_at")
Client.order("created_at DESC")# ORClient.order("created_at ASC")
Client.order("orders_count ASC, created_at DESC")
ROR Lab.
Selecting
Client.select("viewable_by, locked")
SELECT viewable_by, locked FROM clientsActiveModel::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
If the select method is used, all the returning objects will be read only.
ROR Lab.
Limit & Offset
Client.limit(5)
SELECT * FROM clients LIMIT 5
Client.limit(5).offset(30)
SELECT * FROM clients LIMIT 5 OFFSET 30
ROR Lab.
GroupOrder.select(
"date(created_at) as ordered_date, sum(price) as
total_price")
.group("date(created_at)")
SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at)
SQL
ROR Lab.
HavingOrder.select(
"date(created_at) as ordered_date, sum(price) as
total_price")
.group("date(created_at)")
.having("sum(price) > ?", 100)
SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at) HAVING sum(price) > 100
SQL
ROR Lab.
Overriding Conditions
• except
• only
• reorder
• reverse_order
ROR Lab.
Overriding Conditions
- except -
Post.where('id > 10').limit(20).order('id asc').except(:order)
SELECT * FROM posts WHERE id > 10 LIMIT 20
ROR Lab.
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.
Overriding Conditions
- reorder -
SELECT * FROM posts WHERE id = 10 ORDER BY name
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 posted_at DESC
ROR Lab.
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.
Readonly Objects
client = Client.readonly.first
client.visits += 1
client.save
ActiveRecord::ReadOnlyRecord exception
ROR Lab.
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.
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.
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_columnend
ROR Lab.
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.
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.
Pessimistic Locking
Item.transaction do i = Item.lock("LOCK IN SHARE MODE").find(1)
i.increment!(:views)end
item = Item.firstitem.with_lock do # This block is called within a transaction, # item is already locked. item.increment!(:views)end
ROR Lab.
Joining Tables
Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id = clients.id')
- Using a String SQL Fragment -
SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id
ROR Lab.
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.
Joining Tables
class Category < ActiveRecord::Base has_many :postsend class Post < ActiveRecord::Base belongs_to :category has_many :comments has_many :tagsend class Comment < ActiveRecord::Base belongs_to :post has_one :guestend class Guest < ActiveRecord::Base belongs_to :commentend class Tag < ActiveRecord::Base belongs_to :postend
- Using Array/Hash of Named Associations -
only with INNER JOIN
• a Single Association
Category.joins(:posts)
SELECT categories.* FROM categories INNER JOIN posts ON posts.category_id = categories.id
“return a Category object for all categories with posts”
ROR Lab.
Joining Tables
class Category < ActiveRecord::Base has_many :postsend class Post < ActiveRecord::Base belongs_to :category has_many :comments has_many :tagsend class Comment < ActiveRecord::Base belongs_to :post has_one :guestend class Guest < ActiveRecord::Base belongs_to :commentend class Tag < ActiveRecord::Base belongs_to :postend
- Using Array/Hash of Named Associations -
only with INNER JOIN
• Multiple Associations
Post.joins(:category, :comments)
SELECT posts.* FROM posts INNER JOIN categories ON posts.category_id = categories.id INNER JOIN comments ON comments.post_id = posts.id
“return all posts that have a category and at least one comment”
ROR Lab.
Joining Tables
class Category < ActiveRecord::Base has_many :postsend class Post < ActiveRecord::Base belongs_to :category has_many :comments has_many :tagsend class Comment < ActiveRecord::Base belongs_to :post has_one :guestend class Guest < ActiveRecord::Base belongs_to :commentend class Tag < ActiveRecord::Base belongs_to :postend
- Using Array/Hash of Named Associations -
only with INNER JOIN
• Nested Associations(Single Level)
Post.joins(:comments => :guest)
SELECT posts.* FROM posts INNER JOIN comments ON comments.post_id = posts.id INNER JOIN guests ON guests.comment_id = comments.id
“return all posts that have a comment made by a guest”
ROR Lab.
Joining Tables
class Category < ActiveRecord::Base has_many :postsend class Post < ActiveRecord::Base belongs_to :category has_many :comments has_many :tagsend class Comment < ActiveRecord::Base belongs_to :post has_one :guestend class Guest < ActiveRecord::Base belongs_to :commentend class Tag < ActiveRecord::Base belongs_to :postend
- Using Array/Hash of Named Associations -
only with INNER JOIN
• Nested Associations(Multiple Level)
Category.joins(:posts => [{:comments => :guest}, :tags]
SELECT categories.* FROM categories
INNER JOIN posts ON posts.category_id = categories.id
INNER JOIN comments ON comments.post_id = posts.id
INNER JOIN guests ON guests.id = comments.quest_id
INNER JOIN tags ON tags.post_id = posts.id
ROR Lab.
Joining Tables- Specifying Conditions on the Joined Tables -
: using Array and String Conditions
time_range = (Time.now.midnight - 1.day)..Time.now.midnightClient.joins(:orders)
.where('orders.created_at' => time_range)
: using nested Hash Conditions
time_range = (Time.now.midnight - 1.day)..Time.now.midnightClient.joins(:orders)
.where(:orders => {:created_at => time_range})
ROR Lab.
Joining Tables - Inner Join -
SELECT <select_list>FROM TableA AINNER JOIN TableB BON A.Key = B.Key
ROR Lab.
Joining Tables - Left Join -
SELECT <select_list>FROM TableA ALEFT JOIN TableB BON A.Key = B.Key
SELECT <select_list>FROM TableA ALEFT JOIN TableB BON A.Key = B.KeyWHERE B.Key IS NULL
ROR Lab.
Joining Tables - Right Join -
SELECT <select_list>FROM TableA ARIGHT JOIN TableB BON A.Key = B.Key
SELECT <select_list>FROM TableA ARIGHT JOIN TableB BON A.Key = B.KeyWHERE A.Key IS NULL
ROR Lab.
Joining Tables - Full Outer Join -
SELECT <select_list>FROM TableA AFULL OUTER JOIN TableB BON A.Key = B.Key
SELECT <select_list>FROM TableA AFULL OUTER JOIN TableB BON A.Key = B.KeyWHERE A.Key IS NULLOR B.Key IS NULL
ROR Lab.
ROR Lab.
감사합니다.����������� ������������������
Top Related