find()

Client.find(10)
# SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1

Client.find([1, 10])
# SELECT * FROM clients WHERE (clients.id IN (1,10))
find_by()
레일즈 3 까지 find_by_name('Tom') 형식으로 사용되던 동적 find()는 find_by(name: 'Tom') 형태로 변경되었다.

# Dynamic finders that return collections are deprecated
Post.find_all_by_title('Rails 4') # (x)
Post.where(titie: 'Rails 4') # (o)
Post.find_last_by_author('Rails 4') # (x)
Post.where(titie: 'Rails 4').last # (o)

# Dynamic find_by finders with conditions are deprecated
Post.find_by_title('Rails 4', conditions: {author: 'admin'}) # (x)
Post.find_by(title: 'Rails 4', author: 'admin') # (o)

# Dynamic finders that create new object are deprecated
Post.find_or_initialize_by_title('Rails 4') # (x)
Post.find_or_create_by_title('Rails 4') # (x)
Post.find_or_initialize_by(title: 'Rails 4') # (o)
Post.find_or_create_by(title: 'Rails 4') # (o)
한편, find_by()에 전달되는 인자도 전통적인 해시(Hash) 뿐 아니라 where()에서 사용되는 모든 인자 포맷을 지원한다.

# Allow dynamic input more easily
post_params = {title: 'Rails 4', author: 'admin'}
Post.find_by(post_params)

# Acceps same arguments as where
Post.find_by("published_on > ?", 2.weeks.ago)
where()

# Array Conditions
Client.where("orders_count = ? AND locked = ?", params[:orders], false)

# Placeholder Conditions
Client.where("created_at >= :start_date AND created_at <= :end_date", {start_date: params[:start_date], end_date: params[:end_date]})

# Hash Conditions
Client.where(locked: true)

# Range Conditions
Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)

# Subsets Conditions
Client.where(orders_count: [1,3,5])
not()

author = 'Bob'
Blog.where(author: author)
# SELECT blogs.* FROM blogs WHERE blogs.author = 'Bob'
Blog.where.not(author: author)
# SELECT blogs.* FROM blogs WHERE blogs.author != 'Bob'

author = nil
Blog.where(author: author)
# SELECT blogs.* FROM blogs WHERE blogs.author IS NULL
Blog.where.not(author: author)
# SELECT blogs.* FROM blogs WHERE blogs.author IS NOT NULL
not()을 지원하지 않는 레일즈3에서 위와 같은 쿼리를 하기 위해서는 아래와 같이 다소 번거로운 코드를 작성해야 했다.

if author
  Blog.where("author != ?", author)
else
  Blog.where("author is NOT NULL")
order()와 reorder()

# 기본 형태
Client.order(created_at: :desc)
# OR
Client.order("created_at DESC")

# Multiple fields
User.order(:name, created_at: :desc)
# SELECT * FROM users ORDER BY name asc, created_at desc

# Preferences between Rails 3 & 4
default_scope { order(:name) }
User.order("created_at DESC")
# 레일즈 3은 SELECT * FROM users ORDER BY name asc, created_at desc
# 레일즈 4는 SELECT * FROM users ORDER BY created_at desc, name asc

class Article < ActiveRecord::Base
  has_many :comments, -> { order('posted_at DESC') }
end

Article.find(10).comments.reorder('name')
select()

Client.select("viewable_by, locked")
# SELECT viewable_by, locked FROM clients

Client.select(:name).distinct
# SELECT DISTINCT name FROM clients
limit()와 offset()

Client.limit(5)
# SELECT * FROM clients LIMIT 5

Client.limit(5).offset(30)
# SELECT * FROM clients LIMIT 5 OFFSET 30

Client.last(3)
# SELECT * FROM clients ORDER BY clients.id DESC LIMIT 3
all()
레일즈 3에서 모델 클래스 메쏘드 all은 Array를 리턴하고 scoped는 ActiveRecord::Relation를 리턴했으나, 레일즈 4부터는 all은 ActiveRecord::Relation를 리턴하고 scoped는 지원하지 않는다. 만약 Array 리턴을 받고 싶다면 Blog.all.to_a와 같이 to_a를 붙여서 사용한다.
all vs all.load
Model.all이 실제로 DB상에 쿼리가 이루어지는 시점은 실제로 그 데이터가 소모되는 때까지 유예(=Lazy Load)된다. 반면 Model.all.load는 즉각적으로 DB 쿼리가 발생한다. 두 경우 모두 리턴되는 형태는 ActiveRecord::Relation이다.
none()
none은 비어있는(=[]) ActiveRecord::Relation를 리턴한다.

# User.rb
def visible_posts
  case role
  when 'Country Manager'
    Post.where(country: country)
  when 'Reviewer'
    Post.published
  when 'Bad User'
    Post.none
  end
end

# users_controller.rb
@posts = current_user.visible_posts
@posts.recent
scope()
레일즈 3까지는 scope()에 Eager load되는 where()의 사용을 허락했으나, 레일즈 4부터는 오직 lambda 블럭만을 허용하고 있다.

# Rails 3
scope :sold, where(state: 'sold')
default_scope where(state: 'available')

# Rails 4
scope :sold, lambda { where(state: 'sold') }
scope :sold, -> { where(state: 'sold') }
default_scope -> { where(state: 'available') }
# Passing in arguments
scope :created_before, ->(time) { where("created_at < ?", time)
또한 scope()에 joins().merge()를 같이 사용하면 어쏘시에이션 관계인 두 모델간의 유용한 쿼리를 만들어 낼 수 있다.

# product.rb
scope :cheap, lambda { where("products.price < 5") }

# category.rb
scope :with_cheap_products, lambda { joins(:products).merge(Product.cheap) }

Category.with_cheap_products

# Building Records Through Named Scopes
scope :discontinues, -> { where(discontinued: true) }

Product.discontinued.build(price: 5)
update()와 update_xxx()

Blog.update(3, {title: 'The 3rd world', summary: 'Lorem...'})
Blog.update_all(released: true)

blog = Blog.first
blog.update(title: 'The 3rd world', summary: 'Blah blah')
blog.update_attribute(:title, 'The 3rd world')
to_sql()과 explain

Blog.recent.to_sql
Blgo.recent.explain