아렐 노트

31379 단어 railssqlrubyarel

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개의 테이블이 있음을 의미합니다.
  • 코스 테이블
  • 교사 테이블
  • 코스 교사 테이블

  • 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,
      # ...
    ]
    

    좋은 웹페이지 즐겨찾기