activerecord query interface (1), season 1

54
Active Record Query Interface(1) The 9th Round of ROR Lab. March 17th, 2012 Hyoseong Choi ROR Lab.

Upload: ror-lab

Post on 10-May-2015

2.107 views

Category:

Education


1 download

DESCRIPTION

제9차 ROR Lab 강의록입니다.

TRANSCRIPT

Page 1: ActiveRecord Query Interface (1), Season 1

Active Record Query Interface(1)

The 9th Round of ROR Lab.

March 17th, 2012

Hyoseong ChoiROR Lab.

Page 2: ActiveRecord Query Interface (1), Season 1

ROR Lab.

ActiveRecord

• No more SQL statements: select * from tables

MySQLPostgreSQLSQLite...

ActiveRecord

FinderMethods

• SQL query• Fire• Ruby object• after_find callback

ORM

Page 3: ActiveRecord Query Interface (1), Season 1

ROR Lab.

Finder Methods of ActiveRecord

1.where2.select3.group4.order5.reorder6.reverse_order7.limit8.offset9.joins10.includes11.lock12.readonly13.from14.having

ActiveRecord::Relation

Page 4: ActiveRecord Query Interface (1), Season 1

ROR Lab.

Retrieving A Single Object

• find

• first

• last

• first!

• last!

Page 5: ActiveRecord Query Interface (1), Season 1

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

Page 6: ActiveRecord Query Interface (1), Season 1

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

Page 7: ActiveRecord Query Interface (1), Season 1

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

Page 8: ActiveRecord Query Interface (1), Season 1

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

Page 9: ActiveRecord Query Interface (1), Season 1

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

Page 10: ActiveRecord Query Interface (1), Season 1

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)

Page 11: ActiveRecord Query Interface (1), Season 1

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

Page 12: ActiveRecord Query Interface (1), Season 1

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

Page 13: ActiveRecord Query Interface (1), Season 1

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

Page 14: ActiveRecord Query Interface (1), Season 1

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

Page 15: ActiveRecord Query Interface (1), Season 1

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 :

Page 16: ActiveRecord Query Interface (1), Season 1

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

Page 17: ActiveRecord Query Interface (1), Season 1

ROR Lab.

Conditions

• String conditions

• Array conditions

• Hash conditions

- where -

Page 18: ActiveRecord Query Interface (1), Season 1

ROR Lab.

String Conditions

Client.where("orders_count = ‘2’")

Page 19: ActiveRecord Query Interface (1), Season 1

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

Page 20: ActiveRecord Query Interface (1), Season 1

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]})

Page 21: ActiveRecord Query Interface (1), Season 1

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')

Page 22: ActiveRecord Query Interface (1), Season 1

ROR Lab.

Hash Conditions- Equality conditions -

Client.where(:locked => true)

Client.where('locked' => true)

Page 23: ActiveRecord Query Interface (1), Season 1

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')

Page 24: ActiveRecord Query Interface (1), Season 1

ROR Lab.

Hash Conditions- Subset conditions -

Client.where(:orders_count => [1,3,5])

SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))

Page 25: ActiveRecord Query Interface (1), Season 1

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")

Page 26: ActiveRecord Query Interface (1), Season 1

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.

Page 27: ActiveRecord Query Interface (1), Season 1

ROR Lab.

Limit & Offset

Client.limit(5)

SELECT * FROM clients LIMIT 5

Client.limit(5).offset(30)

SELECT * FROM clients LIMIT 5 OFFSET 30

Page 28: ActiveRecord Query Interface (1), Season 1

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

Page 29: ActiveRecord Query Interface (1), Season 1

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

Page 30: ActiveRecord Query Interface (1), Season 1

ROR Lab.

Overriding Conditions

• except

• only

• reorder

• reverse_order

Page 31: ActiveRecord Query Interface (1), Season 1

ROR Lab.

Overriding Conditions

- except -

Post.where('id > 10').limit(20).order('id asc').except(:order)

SELECT * FROM posts WHERE id > 10 LIMIT 20

Page 32: ActiveRecord Query Interface (1), Season 1

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

Page 33: ActiveRecord Query Interface (1), Season 1

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

Page 34: ActiveRecord Query Interface (1), Season 1

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

Page 35: ActiveRecord Query Interface (1), Season 1

ROR Lab.

Readonly Objects

client = Client.readonly.first

client.visits += 1

client.save

ActiveRecord::ReadOnlyRecord exception

Page 36: ActiveRecord Query Interface (1), Season 1

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

Page 37: ActiveRecord Query Interface (1), Season 1

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

Page 38: ActiveRecord Query Interface (1), Season 1

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

Page 39: ActiveRecord Query Interface (1), Season 1

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

Page 40: ActiveRecord Query Interface (1), Season 1

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

Page 41: ActiveRecord Query Interface (1), Season 1

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

Page 42: ActiveRecord Query Interface (1), Season 1

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

Page 43: ActiveRecord Query Interface (1), Season 1

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)

Page 44: ActiveRecord Query Interface (1), Season 1

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”

Page 45: ActiveRecord Query Interface (1), Season 1

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”

Page 46: ActiveRecord Query Interface (1), Season 1

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”

Page 47: ActiveRecord Query Interface (1), Season 1

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

Page 48: ActiveRecord Query Interface (1), Season 1

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})

Page 49: ActiveRecord Query Interface (1), Season 1

ROR Lab.

Joining Tables - Inner Join -

SELECT <select_list>FROM TableA AINNER JOIN TableB BON A.Key = B.Key

Page 50: ActiveRecord Query Interface (1), Season 1

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

Page 51: ActiveRecord Query Interface (1), Season 1

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

Page 52: ActiveRecord Query Interface (1), Season 1

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

Page 53: ActiveRecord Query Interface (1), Season 1

ROR Lab.

Page 54: ActiveRecord Query Interface (1), Season 1

ROR Lab.

감사합니다.����������� ������������������