SearchCop은 ActiveRecord 모델을 확장하여 간단한 쿼리 문자열 및 해시 기반 쿼리를 통한 쿼리와 같은 전체 텍스트 검색 엔진을 지원합니다. title
, author
, stock
, price
, available
등 다양한 속성을 갖는 Book
모델이 있다고 가정합니다. SearchCop을 사용하면 다음을 수행할 수 있습니다.
Book . search ( "Joanne Rowling Harry Potter" )
Book . search ( "author: Rowling title:'Harry Potter'" )
Book . search ( "price > 10 AND price < 20 -stock:0 (Potter OR Rowling)" )
# ...
따라서 검색 쿼리 문자열을 모델에 전달할 수 있으며, SearchCop은 RDBMS의 전체 텍스트 인덱스 기능을 사용할 수 있으므로 추가 타사 검색 서버를 통합할 필요 없이 귀하, 앱 관리자 및/또는 사용자는 강력한 쿼리 기능을 얻을 수 있습니다. 데이터베이스에 구애받지 않는 방식(현재 MySQL 및 PostgreSQL 전체 텍스트 인덱스가 지원됨)을 사용하고 쿼리를 최적화하여 이를 최적으로 사용합니다. 아래에서 자세한 내용을 읽어보세요.
복잡한 해시 기반 쿼리도 지원됩니다.
Book . search ( author : "Rowling" , title : "Harry Potter" )
Book . search ( or : [ { author : "Rowling" } , { author : "Tolkien" } ] )
Book . search ( and : [ { price : { gt : 10 } } , { not : { stock : 0 } } , or : [ { title : "Potter" } , { author : "Rowling" } ] ] )
Book . search ( or : [ { query : "Rowling -Potter" } , { query : "Tolkien -Rings" } ] )
Book . search ( title : { my_custom_sql_query : "Rowl" } } )
# ...
애플리케이션의 Gemfile에 다음 줄을 추가하세요.
gem 'search_cop'
그런 다음 다음을 실행합니다.
$ bundle
또는 다음과 같이 직접 설치하십시오.
$ gem install search_cop
모델에 대해 SearchCop을 활성화하려면 include SearchCop
하고 search_scope
내의 검색어에 노출하려는 속성을 지정합니다.
class Book < ActiveRecord :: Base
include SearchCop
search_scope :search do
attributes :title , :description , :stock , :price , :created_at , :available
attributes comment : [ "comments.title" , "comments.message" ]
attributes author : "author.name"
# ...
end
has_many :comments
belongs_to :author
end
물론 원하는 대로 여러 개의 search_scope
블록을 지정할 수도 있습니다.
search_scope :admin_search do
attributes :title , :description , :stock , :price , :created_at , :available
# ...
end
search_scope :user_search do
attributes :title , :description
# ...
end
SearchCop은 쿼리를 구문 분석하고 이를 데이터베이스에 구애받지 않는 방식으로 SQL 쿼리에 매핑합니다. 따라서 SearchCop은 특정 RDBMS에 바인딩되지 않습니다.
Book . search ( "stock > 0" )
# ... WHERE books.stock > 0
Book . search ( "price > 10 stock > 0" )
# ... WHERE books.price > 10 AND books.stock > 0
Book . search ( "Harry Potter" )
# ... WHERE (books.title LIKE '%Harry%' OR books.description LIKE '%Harry%' OR ...) AND (books.title LIKE '%Potter%' OR books.description LIKE '%Potter%' ...)
Book . search ( "available:yes OR created_at:2014" )
# ... WHERE books.available = 1 OR (books.created_at >= '2014-01-01 00:00:00.00000' and books.created_at <= '2014-12-31 23:59:59.99999')
SearchCop은 이 사례에 대한 SQL 쿼리를 작성하는 데 사용되는 값에 대해 ActiveSupport의 Beginning_of_year 및 end_of_year 방법을 사용하고 있습니다.
물론 이러한 LIKE '%...%'
쿼리는 최적의 성능을 달성하지 못하지만, 결과 쿼리를 최적화하는 방법을 이해하려면 SearchCop의 전체 텍스트 기능에 대한 아래 섹션을 확인하세요.
Book.search(...)
는 ActiveRecord::Relation
반환하므로 가능한 모든 방법으로 검색 결과를 자유롭게 사전 또는 사후 처리할 수 있습니다.
Book . where ( available : true ) . search ( "Harry Potter" ) . order ( "books.id desc" ) . paginate ( page : params [ :page ] )
쿼리 문자열을 SearchCop에 전달하면 구문 분석, 분석 및 매핑되어 최종적으로 SQL 쿼리가 작성됩니다. 더 정확하게 말하면 SearchCop은 쿼리를 구문 분석할 때 쿼리 표현식(And-, Or-, Not-, String-, Date- 등 노드)을 나타내는 개체(노드)를 생성합니다. SQL 쿼리를 작성하기 위해 SearchCop은 Arel에서 사용되는 것과 같은 방문자 개념을 사용합니다. 따라서 모든 노드에는 노드를 SQL로 변환하는 방문자가 있어야 합니다. 방문자가 없는 경우 쿼리 빌더가 노드를 "방문"하려고 하면 예외가 발생합니다. 방문자는 사용자가 제공한 입력을 위생 처리할 책임이 있습니다. 이는 주로 인용(문자열, 테이블 이름, 열 인용 등)을 통해 수행됩니다. SearchCop은 SQL 주입을 방지하기 위해 삭제/인용을 위해 ActiveRecord 연결 어댑터에서 제공하는 방법을 사용하고 있습니다. 보안 문제로부터 100% 안전할 수는 없지만 SearchCop은 보안 문제를 심각하게 받아들입니다. 보안 관련 문제를 발견한 경우 flakks dot com의 보안을 통해 책임감 있게 신고해 주세요.
SearchCop은 MySQL용 json 필드와 postgres용 json, jsonb 및 hstore 필드를 지원합니다. 현재 필드 값은 항상 문자열이어야 하며 배열은 지원되지 않습니다. 다음을 통해 json 속성을 지정할 수 있습니다.
search_scope :search do
attributes user_agent : "context->browser->user_agent"
# ...
end
여기서 context
다음을 포함하는 json/jsonb 열입니다.
{
"browser" : {
"user_agent" : " Firefox ... "
}
}
기본적으로, 즉, 전체 텍스트 색인에 대해 SearchCop에 알리지 않으면 SearchCop은 LIKE '%...%'
쿼리를 사용합니다. 불행하게도 트라이그램 인덱스(postgres만 해당)를 생성하지 않는 한 이러한 쿼리는 SQL 인덱스를 사용할 수 없습니다. 따라서 Book.search("Harry Potter")
또는 이와 유사한 항목을 검색할 때 RDBMS에서 모든 행을 스캔해야 합니다. LIKE
쿼리로 인한 불이익을 피하기 위해 SearchCop은 MySQL 및 PostgreSQL의 전체 텍스트 인덱스 기능을 활용할 수 있습니다. 이미 존재하는 전체 텍스트 색인을 사용하려면 다음을 통해 SearchCop에 이를 사용하도록 지시하면 됩니다.
class Book < ActiveRecord :: Base
# ...
search_scope :search do
attributes :title , :author
options :title , :type => :fulltext
options :author , :type => :fulltext
end
# ...
end
그런 다음 SearchCop은 전체 텍스트 인덱스가 있는 속성에 대한 SQL 쿼리를 다음과 같이 투명하게 변경합니다.
Book . search ( "Harry Potter" )
# MySQL: ... WHERE (MATCH(books.title) AGAINST('+Harry' IN BOOLEAN MODE) OR MATCH(books.author) AGAINST('+Harry' IN BOOLEAN MODE)) AND (MATCH(books.title) AGAINST ('+Potter' IN BOOLEAN MODE) OR MATCH(books.author) AGAINST('+Potter' IN BOOLEAN MODE))
# PostgreSQL: ... WHERE (to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Harry') OR to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Harry')) AND (to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Potter') OR to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Potter'))
분명히 이러한 쿼리는 하위 문자열 대신 단어를 검색하기 때문에 와일드카드 LIKE
쿼리와 항상 동일한 결과를 반환하지는 않습니다. 그러나 일반적으로 전체 텍스트 인덱스는 더 나은 성능을 제공합니다.
게다가 위의 쿼리는 아직 완벽하지 않습니다. 이를 더욱 개선하기 위해 SearchCop은 전체 텍스트 인덱스를 최적으로 사용하는 동시에 전체 텍스트가 아닌 속성과 혼합할 수 있도록 쿼리를 최적화하려고 합니다. 쿼리를 더욱 향상시키려면 속성을 그룹화하고 검색할 기본 필드를 지정하여 SearchCop이 더 이상 모든 필드 내에서 검색하지 않도록 할 수 있습니다.
search_scope :search do
attributes all : [ :author , :title ]
options :all , :type => :fulltext , default : true
# Use default: true to explicitly enable fields as default fields (whitelist approach)
# Use default: false to explicitly disable fields as default fields (blacklist approach)
end
이제 SearchCop은 아직 최적이 아닌 다음 쿼리를 최적화할 수 있습니다.
Book . search ( "Rowling OR Tolkien stock > 1" )
# MySQL: ... WHERE ((MATCH(books.author) AGAINST('+Rowling' IN BOOLEAN MODE) OR MATCH(books.title) AGAINST('+Rowling' IN BOOLEAN MODE)) OR (MATCH(books.author) AGAINST('+Tolkien' IN BOOLEAN MODE) OR MATCH(books.title) AGAINST('+Tolkien' IN BOOLEAN MODE))) AND books.stock > 1
# PostgreSQL: ... WHERE ((to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Rowling') OR to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Rowling')) OR (to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Tolkien') OR to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Tolkien'))) AND books.stock > 1
다음과 같은 보다 성능이 좋은 쿼리에:
Book . search ( "Rowling OR Tolkien stock > 1" )
# MySQL: ... WHERE MATCH(books.author, books.title) AGAINST('Rowling Tolkien' IN BOOLEAN MODE) AND books.stock > 1
# PostgreSQL: ... WHERE to_tsvector('simple', books.author || ' ' || books.title) @@ to_tsquery('simple', 'Rowling | Tokien') and books.stock > 1
여기서 무슨 일이 일어나고 있나요? 글쎄, 우리는 author
와 title
으로 구성된 속성 그룹의 이름으로 all
지정했습니다. 또한 all
전체 텍스트 속성으로 지정했으므로 SearchCop은 author
및 title
에 복합 전체 텍스트 인덱스가 있다고 가정하여 이에 따라 쿼리가 최적화됩니다. 마지막으로 all
검색할 기본 속성으로 지정했습니다. 따라서 SearchCop은 쿼리 내에서 직접 지정되지 않는 한( stock > 0
과 같은) stock
과 같은 다른 속성을 무시할 수 있습니다.
다른 쿼리도 유사한 방식으로 최적화됩니다. 예를 들어 SearchCop은 쿼리 내의 전체 텍스트 제약 조건, 즉 MySQL의 경우 MATCH() AGAINST()
, PostgreSQL의 경우 to_tsvector() @@ to_tsquery()
최소화하려고 시도합니다.
Book . search ( "(Rowling -Potter) OR Tolkien" )
# MySQL: ... WHERE MATCH(books.author, books.title) AGAINST('(+Rowling -Potter) Tolkien' IN BOOLEAN MODE)
# PostgreSQL: ... WHERE to_tsvector('simple', books.author || ' ' || books.title) @@ to_tsquery('simple', '(Rowling & !Potter) | Tolkien')
MySQL에서 books.title
에 대한 전체 텍스트 색인을 생성하려면 다음을 사용하면 됩니다.
add_index :books , :title , :type => :fulltext
예를 들어 위에서 이미 지정한 all
필드에 사용될 복합 인덱스와 관련하여 다음을 사용합니다.
add_index :books , [ :author , :title ] , :type => :fulltext
MySQL은 MyISAM에 대한 전체 텍스트 인덱스를 지원하고 MySQL 버전 5.6+부터 InnoDB에 대해서도 지원합니다. MySQL 전체 텍스트 인덱스에 대한 자세한 내용을 보려면 http://dev.mysql.com/doc/refman/5.6/en/fulltext-search.html을 방문하세요.
PostgreSQL과 관련하여 전체 텍스트 인덱스를 생성하는 더 많은 방법이 있습니다. 그러나 가장 쉬운 방법 중 하나는 다음과 같습니다.
ActiveRecord :: Base . connection . execute "CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', title))"
또한 PostgreSQL의 경우 config/application.rb
에서 스키마 형식을 변경해야 합니다.
config . active_record . schema_format = :sql
PostgreSQL의 복합 인덱스와 관련하여 다음을 사용하십시오.
ActiveRecord :: Base . connection . execute "CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', author || ' ' || title))"
PostgreSQL에서 NULL 값을 올바르게 처리하려면 인덱스 생성 시와 search_scope
를 지정할 때 COALESCE를 사용하세요.
ActiveRecord :: Base . connection . execute "CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', COALESCE(author, '') || ' ' || COALESCE(title, '')))"
을 더한:
search_scope :search do
attributes :title
options :title , :type => :fulltext , coalesce : true
end
simple
이외의 다른 PostgreSQL 사전을 사용하려면 그에 따라 인덱스를 생성해야 하며 이에 대해 SearchCop에 알려야 합니다. 예:
search_scope :search do
attributes :title
options :title , :type => :fulltext , dictionary : "english"
end
PostgreSQL 전체 텍스트 색인에 대한 자세한 내용을 보려면 http://www.postgresql.org/docs/9.3/static/textsearch.html을 방문하세요.
전체 텍스트가 아닌 속성을 검색 쿼리(가격, 주식 등)에 노출하는 경우 Book.search("stock > 0")
와 같은 해당 쿼리는 일반적인 전체 텍스트가 아닌 인덱스에서 이익을 얻습니다. 따라서 검색 쿼리에 노출되는 모든 열에 일반 인덱스와 모든 전체 텍스트 속성에 대한 전체 텍스트 인덱스를 추가해야 합니다.
전체 텍스트 인덱스를 사용할 수 없는 경우, 예를 들어 전체 텍스트 지원 없이 InnoDB 또는 다른 RDBMS를 사용하는 동안 여전히 MySQL 5.5를 사용하고 있기 때문에 필요하지 않으면 RDBMS가 문자열 열에 대해 일반적인 비 전체 텍스트 인덱스를 사용하도록 할 수 있습니다. LIKE
쿼리 내에 와일드카드를 남겼습니다. 간단히 다음 옵션을 제공하십시오.
class User < ActiveRecord :: Base
include SearchCop
search_scope :search do
attributes :username
options :username , left_wildcard : false
end
# ...
SearchCop은 가장 왼쪽의 와일드카드를 생략합니다.
User . search ( "admin" )
# ... WHERE users.username LIKE 'admin%'
마찬가지로 올바른 와일드카드도 비활성화할 수 있습니다.
search_scope :search do
attributes :username
options :username , right_wildcard : false
end
검색 범위에 여러 필드를 정의하면 SearcCop은 기본적으로 AND 연산자를 사용하여 조건을 연결합니다. 예:
class User < ActiveRecord :: Base
include SearchCop
search_scope :search do
attributes :username , :fullname
end
# ...
end
따라서 User.search("something")
과 같은 검색은 다음 조건을 사용하여 쿼리를 생성합니다.
... WHERE username LIKE ' %something% ' AND fullname LIKE ' %something% '
그러나 AND를 기본 연산자로 사용하는 것이 바람직하지 않은 경우가 있으므로 SearchCop을 사용하면 이를 재정의하고 대신 OR을 기본 연산자로 사용할 수 있습니다. User.search("something", default_operator: :or)
와 같은 쿼리는 OR를 사용하여 조건을 연결하는 쿼리를 생성합니다.
... WHERE username LIKE ' %something% ' OR fullname LIKE ' %something% '
마지막으로, 전체 텍스트 인덱스/쿼리에도 적용할 수 있습니다.
다음과 같이 다른 모델에서 검색 가능한 속성을 지정하는 경우
class Book < ActiveRecord :: Base
# ...
belongs_to :author
search_scope :search do
attributes author : "author.name"
end
# ...
end
SearchCop은 Book.search(...)
수행할 때 기본적으로 참조된 연관을 eager_load
. 자동 eager_load
원하지 않거나 특별한 작업을 수행해야 하는 경우 scope
지정하십시오.
class Book < ActiveRecord :: Base
# ...
search_scope :search do
# ...
scope { joins ( :author ) . eager_load ( :comments ) } # etc.
end
# ...
end
그런 다음 SearchCop은 연결 자동 로드를 건너뛰고 대신 범위를 사용합니다. aliases
과 함께 scope
사용하여 임의로 복잡한 조인을 수행하고 조인된 모델/테이블에서 검색할 수도 있습니다.
class Book < ActiveRecord :: Base
# ...
search_scope :search do
attributes similar : [ "similar_books.title" , "similar_books.description" ]
scope do
joins "left outer join books similar_books on ..."
end
aliases similar_books : Book # Tell SearchCop how to map SQL aliases to models
end
# ...
end
협회 협회도 참조하고 사용할 수 있습니다.
class Book < ActiveRecord :: Base
# ...
has_many :comments
has_many :users , :through => :comments
search_scope :search do
attributes user : "users.username"
end
# ...
end
SearchCop은 데이터 유형 정의 등을 자동 감지하기 위해 지정된 속성에서 모델의 클래스 이름과 SQL 별칭을 유추하려고 시도합니다. 이는 일반적으로 매우 잘 작동합니다. self.table_name = ...
통해 사용자 정의 테이블 이름을 사용하는 경우 또는 모델이 여러 번 연결되는 경우 SearchCop은 클래스 및 SQL 별칭 이름을 추론할 수 없습니다. 예:
class Book < ActiveRecord :: Base
# ...
has_many :users , :through => :comments
belongs_to :user
search_scope :search do
attributes user : [ "user.username" , "users_books.username" ]
end
# ...
end
여기서 쿼리가 작동하려면 사용자 모델이 여러 번 연결되어 ActiveRecord가 SQL 쿼리 내에서 사용자에게 다른 SQL 별칭을 할당하기 때문에 users_books.username
사용해야 합니다. 그러나 이제 SearchCop은 users_books
에서 User
모델을 추론할 수 없으므로 다음을 추가해야 합니다.
class Book < ActiveRecord :: Base
# ...
search_scope :search do
# ...
aliases :users_books => :users
end
# ...
end
사용자 정의 SQL 별칭 및 매핑에 대해 SearchCop에 알립니다. 또한 언제든지 scope {}
블록과 aliases
통해 직접 조인을 수행하고 사용자 지정 SQL 별칭을 사용하여 ActiveRecord에서 자동 할당된 이름과 독립될 수 있습니다.
쿼리 문자열 쿼리는 AND/and
, OR/or
, :
, =
, !=
, <
, <=
, >
, >=
, NOT/not/-
, ()
, "..."
및 '...'
을 지원합니다. 기본 연산자는 AND
및 matches
이며 OR
AND
보다 우선합니다. NOT
단일 속성에 대한 중위 연산자로만 사용할 수 있습니다.
해시 기반 쿼리 지원 and: [...]
및 or: [...]
, 이는 not: {...}
, matches: {...}
, eq: {...}
, not_eq: {...}
배열을 사용합니다. not_eq: {...}
, lt: {...}
, lteq: {...}
, gt: {...}
, gteq: {...}
및 query: "..."
인수. 또한 query: "..."
사용하면 하위 쿼리를 생성할 수 있습니다. 쿼리 문자열 쿼리에 대한 다른 규칙은 해시 기반 쿼리에도 적용됩니다.
SearchCop은 search_scope
에서 generator
정의하여 사용자 정의 연산자를 정의하는 기능도 제공합니다. 그런 다음 해시 기반 쿼리 검색과 함께 사용할 수 있습니다. 이는 SearchCop에서 지원하지 않는 데이터베이스 연산자를 사용하려는 경우에 유용합니다.
생성기를 사용할 때 값을 삭제/인용할 책임은 귀하에게 있습니다(아래 예 참조). 그렇지 않으면 생성기가 SQL 삽입을 허용합니다. 따라서 현재 수행 중인 작업을 알고 있는 경우에만 생성기를 사용하십시오.
예를 들어 책 제목이 문자열로 시작하는 LIKE
쿼리를 수행하려는 경우 검색 범위를 다음과 같이 정의할 수 있습니다.
search_scope :search do
attributes :title
generator :starts_with do | column_name , raw_value |
pattern = " #{ raw_value } %"
" #{ column_name } LIKE #{ quote pattern } "
end
end
검색을 수행하려면 다음과 같이 사용하십시오.
Book . search ( title : { starts_with : "The Great" } )
보안 정보: 생성기에서 반환된 쿼리는 데이터베이스로 이동하는 쿼리에 직접 삽입됩니다. 그러면 앱에 잠재적인 SQL 주입 지점이 열립니다. 이 기능을 사용하는 경우 반환하는 쿼리가 실행하기에 안전한지 확인하고 싶을 것입니다.
부울, 날짜/시간, 타임스탬프 등의 필드를 검색할 때 SearchCop은 일부 매핑을 수행합니다. 다음 쿼리는 동일합니다.
Book . search ( "available:true" )
Book . search ( "available:1" )
Book . search ( "available:yes" )
게다가
Book . search ( "available:false" )
Book . search ( "available:0" )
Book . search ( "available:no" )
날짜/시간 및 타임스탬프 필드의 경우 SearchCop은 특정 값을 범위로 확장합니다.
Book . search ( "created_at:2014" )
# ... WHERE created_at >= '2014-01-01 00:00:00' AND created_at <= '2014-12-31 23:59:59'
Book . search ( "created_at:2014-06" )
# ... WHERE created_at >= '2014-06-01 00:00:00' AND created_at <= '2014-06-30 23:59:59'
Book . search ( "created_at:2014-06-15" )
# ... WHERE created_at >= '2014-06-15 00:00:00' AND created_at <= '2014-06-15 23:59:59'
검색 연결이 가능합니다. 그러나 현재 연결로는 SearchCop이 전체 텍스트 인덱스에 대한 개별 쿼리를 최적화하는 것을 허용하지 않습니다.
Book . search ( "Harry" ) . search ( "Potter" )
생성할 것이다
# MySQL: ... WHERE MATCH(...) AGAINST('+Harry' IN BOOLEAN MODE) AND MATCH(...) AGAINST('+Potter' IN BOOLEAN MODE)
# PostgreSQL: ... WHERE to_tsvector(...) @@ to_tsquery('simple', 'Harry') AND to_tsvector(...) @@ to_tsquery('simple', 'Potter')
대신에
# MySQL: ... WHERE MATCH(...) AGAINST('+Harry +Potter' IN BOOLEAN MODE)
# PostgreSQL: ... WHERE to_tsvector(...) @@ to_tsquery('simple', 'Harry & Potter')
따라서 전체 텍스트 인덱스를 사용하는 경우 연결을 피하는 것이 좋습니다.
Model#search
사용할 때 SearchCop은 전달된 쿼리 문자열이 유효하지 않은 경우(구문 분석 오류, 호환되지 않는 데이터 유형 오류 등) 특정 예외가 발생하는 것을 편리하게 방지합니다. 대신 Model#search
빈 관계를 반환합니다. 그러나 특정 사례를 디버깅해야 하는 경우 Model#unsafe_search
사용하면 문제가 발생합니다.
Book . unsafe_search ( "stock: None" ) # => raise SearchCop::IncompatibleDatatype
SearchCop은 #attributes
, #default_attributes
, #options
및 #aliases
라는 반사 방법을 제공합니다. 이러한 방법을 사용하여 모델에 대한 개별 검색 도움말 위젯을 제공할 수 있습니다. 이 위젯에는 검색할 속성과 기본 속성 등이 나열되어 있습니다.
class Product < ActiveRecord :: Base
include SearchCop
search_scope :search do
attributes :title , :description
options :title , default : true
end
end
Product . search_reflection ( :search ) . attributes
# {"title" => ["products.title"], "description" => ["products.description"]}
Product . search_reflection ( :search ) . default_attributes
# {"title" => ["products.title"]}
# ...
버전 1.0.0부터 SearchCop은 Semantic Versioning: SemVer를 사용합니다.
git checkout -b my-new-feature
).git commit -am 'Add some feature'
)git push origin my-new-feature
)