아렐 노트
RailsConf 2014의 메모
대화 링크:
아렐이란?
Arel은 "A Relational Algebra"의 약자입니다.
실제로 Arel은 AST(Abstract Syntax Tree) 파서입니다.
Ruby 코드를 가져와 SQL 구문으로 변환합니다. Arel은 테이블이나 데이터베이스에 대해 아무것도 모릅니다. 순전히 Ruby를 사용하여 ActiveRecord와 통신하는 Query Builder입니다.
아렐 도우미
https://github.com/camertron/arel-helpers
걔들 뭐해?
Arel 구문의 장황함을 줄입니다.
예시:
Post.select(:id) # using ActiveRecord
Post.arel_table(:id) # using bare Arel
Post[:id] # using Arel Helpers.
터미널 방법이란
Post.select(:id).count.to_sql
# => NoMethodError: undefined method `to_sql' for 107:Integer
#count
는 "터미널 방법"으로 SQL 체인을 "종료"하고 연속 체인을 허용하지 않습니다.
기능 추가
Arel functions IE: 데이터베이스마다 다를 수 있는 비표준 SQL 메소드의 일부가 아닌 기능을 추가해야 한다고 가정해 보겠습니다.
이것이 어떻게 일어날 것인지는 다음과 같습니다.
Post.select(
Arel::Nodes::NamedFunction.new(
"LENGTH", [Post.arel_table[:text]]
).as("length")
).to_sql
# => SELECT LENGTH('posts', 'text') AS length from 'posts'
## To reduce verbosity
include Arel::Nodes
Post.select(
NamedFunction.new(
"LENGTH", [Post[:text]]
).as("length")
).to_sql
# => SELECT LENGTH('posts', 'text') AS length from 'posts'
아렐 스타!
"*"
를 Arel.star
로 대체하십시오!
Post.select("*")
# => SELECT * from 'posts'
Post.select(Arel.star)
# => SELECT * from 'posts'
에서 선택
Post.select(:id).from(Post.select([:id, :text]).ast).to_sql
# => SELECT id FROM SELECT id, text FROM 'posts'
.ast
는 주어진 Arel 함수에 대해 구성된 AST를 제공합니다.
어디에
Post.where(title: "Arel is Cool").to_sql # using ActiveRecord
Post.where(Post[:title].eq("Arel is Cool")).to_sql # Using Arel
Post.where("title != 'Arel is Cool'").to_sql
# Using AR
# => SELECT 'posts'.* from 'posts'
# WHERE (title != 'Arel is Cool')
Post.where(Post[:title].not_eq("Arel is Cool")).to_sql
# Using Arel
# => SELECT 'posts'.* from 'posts'
# WHERE 'posts'.'title' != 'Arel is Cool'
Post.where(Post[:title].not_eq(nil)).to_sql
# => SELECT 'posts'.* FROM 'posts'
# WHERE 'posts.title' IS NOT NULL
# Greater than
Post.where(Post[:visitors].gt(250)).to_sql
# => SELECT 'posts'.* FROM 'posts'
# WHERE 'posts'.'visitors' > 250
# Less than
Post.where(Post[:visitors].lt(250)).to_sql
# => SELECT 'posts'.* FROM 'posts'
# WHERE 'posts'.'visitors' < 250
# Greater than or equal to
Post.where(Post[:visitors].gteq(250)).to_sql
# => SELECT 'posts'.* FROM 'posts'
# WHERE 'posts'.'visitors' >= 250
# Less than or equal to
Post.where(Post[:visitors].lteq(250)).to_sql
# => SELECT 'posts'.* FROM 'posts'
# WHERE 'posts'.'visitors' <= 250
# Chaining AND + OR
Post.where(
Post[:title].eq("Arel is Cool")
.and(
Post[:id].eq(22)
.or(
Post[:id].eq(23)
)
)
).to_sql
# => SELECT 'posts'.* FROM 'posts'
# WHERE (
# 'posts'.'title' = 'Arel is Cool'
# AND
# ('posts'.'id' = 22 OR 'posts'.'id' = 23)
# )
# Using IN
Post.where(
Post[:title].eq("Arel is Cool")
.and(
Post[:id].in(22, 23)
)
)
# => SELECT 'posts'.* FROM 'posts'
# WHERE (
# 'posts'.'title' = 'Arel is Cool'
# AND
# 'posts'.'id' IN (22, 23)
# )
# Using our NamedFunction
Post.where(
Post[:title].eq("Arel is Cool")
.and(
NamedFunction.new("LENGTH", [Post[:slug]]).gt(10)
)
).to_sql
# => SELECT 'posts'.'title' = 'Arel is Cool' AND
# LENGTH('posts'.'slug') > 10
조인 사용
설정
다음 설정을 가정합니다.
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post
has_one :author
end
class Author < ApplicationRecord
belongs_to :comment
end
그것을 사용
일반INNER JOIN
을 사용하려면 다음을 수행합니다.
Author.joins(
Author.arel_table.join(Comment.arel_table)
.on(Comment[:id].eq(Author[:comment_id]))
.join_sources
)
.where(Post[:id].eq(42))
.to_sql
OUTER JOIN
를 사용하려면 다음을 수행합니다.
Author.joins(
Author.arel_table.join(Comment.arel_table, Arel::OuterJoin)
.on(Comment[:id].eq(Author[:comment_id]))
.join_sources
)
.where(Post[:id].eq(42))
.to_sql
ArelHelpers로 정리하기
위의 코드를 정리하기 위해 ArelHelpers#join_association
메서드를 사용할 수 있습니다.
include ArelHelpers::JoinAssociation
# INNER JOIN
Author.joins(
join_association(Author, :comment)
)
.where(Post[:id].eq(42))
.to_sql
# OUTER JOIN
Author.joins(
join_association(Author, :comment, Arel::OuterJoin)
)
.where(Post[:id].eq(42))
.to_sql
join_association 블록
조인 연관은 또한 블록을 생성할 수 있으며 해당 블록을 사용하여 조인 조건을 추가로 지정할 수 있습니다.
Author.joins(
join_association(Author, :comment) do |assoc_name, join_conds|
join_conds.and(Comment[:created_at].lteq(1.day.ago))
end
)
.where(Post[:id].eq(42))
.to_sql
테이블 조인
설정
다음 설정이 제공됩니다.
class Course < ApplicationRecord
has_and_belongs_to_many :teachers
end
class Teacher < ApplicationRecord
has_and_belongs_to_many :courses
end
2가지 가능성:
교사는 많은 과정을 가르칠 수 있습니다
한 과정에 많은 교사가 있을 수 있습니다.
이것은 3개의 테이블이 있음을 의미합니다.
Post.select(:id) # using ActiveRecord
Post.arel_table(:id) # using bare Arel
Post[:id] # using Arel Helpers.
Post.select(:id).count.to_sql
# => NoMethodError: undefined method `to_sql' for 107:Integer
Post.select(
Arel::Nodes::NamedFunction.new(
"LENGTH", [Post.arel_table[:text]]
).as("length")
).to_sql
# => SELECT LENGTH('posts', 'text') AS length from 'posts'
## To reduce verbosity
include Arel::Nodes
Post.select(
NamedFunction.new(
"LENGTH", [Post[:text]]
).as("length")
).to_sql
# => SELECT LENGTH('posts', 'text') AS length from 'posts'
Post.select("*")
# => SELECT * from 'posts'
Post.select(Arel.star)
# => SELECT * from 'posts'
Post.select(:id).from(Post.select([:id, :text]).ast).to_sql
# => SELECT id FROM SELECT id, text FROM 'posts'
Post.where(title: "Arel is Cool").to_sql # using ActiveRecord
Post.where(Post[:title].eq("Arel is Cool")).to_sql # Using Arel
Post.where("title != 'Arel is Cool'").to_sql
# Using AR
# => SELECT 'posts'.* from 'posts'
# WHERE (title != 'Arel is Cool')
Post.where(Post[:title].not_eq("Arel is Cool")).to_sql
# Using Arel
# => SELECT 'posts'.* from 'posts'
# WHERE 'posts'.'title' != 'Arel is Cool'
Post.where(Post[:title].not_eq(nil)).to_sql
# => SELECT 'posts'.* FROM 'posts'
# WHERE 'posts.title' IS NOT NULL
# Greater than
Post.where(Post[:visitors].gt(250)).to_sql
# => SELECT 'posts'.* FROM 'posts'
# WHERE 'posts'.'visitors' > 250
# Less than
Post.where(Post[:visitors].lt(250)).to_sql
# => SELECT 'posts'.* FROM 'posts'
# WHERE 'posts'.'visitors' < 250
# Greater than or equal to
Post.where(Post[:visitors].gteq(250)).to_sql
# => SELECT 'posts'.* FROM 'posts'
# WHERE 'posts'.'visitors' >= 250
# Less than or equal to
Post.where(Post[:visitors].lteq(250)).to_sql
# => SELECT 'posts'.* FROM 'posts'
# WHERE 'posts'.'visitors' <= 250
# Chaining AND + OR
Post.where(
Post[:title].eq("Arel is Cool")
.and(
Post[:id].eq(22)
.or(
Post[:id].eq(23)
)
)
).to_sql
# => SELECT 'posts'.* FROM 'posts'
# WHERE (
# 'posts'.'title' = 'Arel is Cool'
# AND
# ('posts'.'id' = 22 OR 'posts'.'id' = 23)
# )
# Using IN
Post.where(
Post[:title].eq("Arel is Cool")
.and(
Post[:id].in(22, 23)
)
)
# => SELECT 'posts'.* FROM 'posts'
# WHERE (
# 'posts'.'title' = 'Arel is Cool'
# AND
# 'posts'.'id' IN (22, 23)
# )
# Using our NamedFunction
Post.where(
Post[:title].eq("Arel is Cool")
.and(
NamedFunction.new("LENGTH", [Post[:slug]]).gt(10)
)
).to_sql
# => SELECT 'posts'.'title' = 'Arel is Cool' AND
# LENGTH('posts'.'slug') > 10
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post
has_one :author
end
class Author < ApplicationRecord
belongs_to :comment
end
Author.joins(
Author.arel_table.join(Comment.arel_table)
.on(Comment[:id].eq(Author[:comment_id]))
.join_sources
)
.where(Post[:id].eq(42))
.to_sql
Author.joins(
Author.arel_table.join(Comment.arel_table, Arel::OuterJoin)
.on(Comment[:id].eq(Author[:comment_id]))
.join_sources
)
.where(Post[:id].eq(42))
.to_sql
include ArelHelpers::JoinAssociation
# INNER JOIN
Author.joins(
join_association(Author, :comment)
)
.where(Post[:id].eq(42))
.to_sql
# OUTER JOIN
Author.joins(
join_association(Author, :comment, Arel::OuterJoin)
)
.where(Post[:id].eq(42))
.to_sql
Author.joins(
join_association(Author, :comment) do |assoc_name, join_conds|
join_conds.and(Comment[:created_at].lteq(1.day.ago))
end
)
.where(Post[:id].eq(42))
.to_sql
class Course < ApplicationRecord
has_and_belongs_to_many :teachers
end
class Teacher < ApplicationRecord
has_and_belongs_to_many :courses
end
Course.arel_table # => courses
Teacher.arel_table # => teachers
# ??? No model for courses_teacher join table.
join_table을 만들려면 다음을 수행합니다.
courses_teachers = Arel::Table.new(:courses_teachers)
위의 변수를 사용하여 다음 쿼리를 구성할 수 있습니다.
Course.joins(
Course.arel_table.join(Teacher.arel_table)
.on(Course[:id].eq(courses_teachers[:course_id]))
.and(Teacher[:id].eq(courses_teachers[:teacher_id]))
.join_sources
)
주문하다
# Using ActiveRecord
Post.order(:views)
Post.order(:views).reverse_order
# Using Arel
Post.order(Post[:views].desc).to_sql
Post.order(Post[:views].asc).to_sql
에
Post.where(
Post.arel_table[:title].in(
Post.select(:title).where(id: 5).ast
)
)
일치하는 검색어와 유사
Post.where(Post[:title].matches("%arel%")).to_sql
# => SELECT 'phrases'.* from 'phrases'
# WHERE ('phrases'.'key' LIKE x'256172656c25')
쿼리 빌더 패턴
class QueryBuilder
# https://ruby-doc.org/stdlib-2.7.3/libdoc/forwardable/rdoc/Forwardable.html
extend Forwardable
attr_reader :query
def_delegators :@query, :to_a, :to_sql, :each
def initialize(query)
@query = query
end
protected
# instantiates a new class and allow chaining.
def reflect(query)
self.class.new(query)
end
end
그것을 사용
class PostQueryBuilder < QueryBuilder
def initialize(query = nil)
super(query || Post.unscoped)
end
def with_title_matching(title)
reflect(
query.where(post[:title].matches("%#{title}%"))
)
end
# PostQueryBuilder.new.with_title_matching("stimulus_reflex")
def with_comments_by(usernames)
reflect(
query
.joins(comments: :author)
.where(Author[:username].in(usernames))
)
end
# PostQueryBuilder.new.with_comments_by(["hopsoft", "leastbad"])
def since_yesterday
reflect(
query.where(
post[:created_at].gteq(1.day.ago)
)
)
end
end
PostQueryBuilder.new
.with_title_matching("stimulus_reflex")
.with_comments_by(["hopsoft", "leastbad"])
.since_yesterday
스커틀!
http://www.scuttle.io/
SQL을 Arel 코드로 바꿉니다.
저와 함께해주셔서 감사합니다. 이것은 미래를 위한 참고 자료입니다!
보너스!
다음과 같이 사용 가능한 모든 매처를 보려면:
#gt
#gteq
#lt
#lteq
레일 콘솔에서 다음을 실행할 수 있습니다.
bundle exec rails console
Arel::Predications.instance_methods
# => [
:eq,
:eq_any,
:between,
:not,
# ...
]
Reference
이 문제에 관하여(아렐 노트), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/paramagicdev/arel-notes-hf0텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)