Active Record Query Interface(1) The 9th Round of ROR Lab. March 17th, 2012 Hyoseong Choi ROR Lab.
May 10, 2015
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.
감사합니다.����������� ������������������