GIỚI THIỆU VÀ SỬ DỤNG GEM META WHERE TRONG RAILS

**I. Giới thiệu Gem Meta Where **:

  • Khi xây dựng ứng dụng Rails với MySQL chắc hẳn bạn sẽ gặp một vài những khó khăn với những câu lệnh truy vấn phức tạp.

  • MetaWhere sẽ giúp các nhà phát triển thao tác với ActiveRecord khá thuận tiện và dễ dàng bằng việc sử dụng sức mạnh của Arel Predications(phương pháp so sánh).

  • MetaWhere giúp bạn xác định được những điều kiện logic boolean phức tạp, thậm chí cả trong các bảng phụ.

II. Sử dụng Meta Where trong ứng dụng Rails :

1 . ** Setup** :

  • Thêm MetaWhere vào Gemfile của bạn :
			gem “meta_where”
- Cài đặt Gem : ``` “bundle install” ```

2 . **Sử dụng với Rails** :

- **WHERE**

    Sử dụng MetaWhere như một chuỗi phương thức thông thường :

```Ruby

Article.where(:title.matches => 'Hello%', :created_at.gt => 3.days.ago)
=> SELECT "articles".* FROM "articles" WHERE ("articles"."title" LIKE 'Hello%')
   AND ("articles"."created_at" > '2010-03-26 18:39:32.592087')

- **Tìm với điều kiện HASH**

    Cú pháp tương tự ActiveRecord :: Base # find:

```Ruby
Article.find(:all,
  			:conditions => {
    			:title.matches => 'Hello%',
    			:created_at.gt => 3.days.ago
  			}
		)

- **SCOPE**

```Ruby

    class Article
  			scope :recent, lambda {|v| where(:created_at.gt => v.days.ago)}
		end


	Article.recent(14).to_sql


	=> SELECT "articles".* FROM "articles"
   		WHERE ("articles"."created_at" > '2015-03-26 18:54:37.030951')

  • Các Toán tử

    MetaWhere hỗ trợ các toán tử mặc định như các method Arel , để sử dụng bạn cần gọi : MetaWhere.operator_overload! khi khởi tạo ứng dụng của mình.


Article.where(:created_at > 100.days.ago, :title =~ 'Hi%').to_sql
		=> SELECT "articles".* FROM "articles"
  		 WHERE ("articles"."created_at" > '2015-03-26 20:11:44.997446')
   		AND ("articles"."title" LIKE 'Hi%')


    >> (equal)
    ^ (not equal)
    + (in array/range)
    !~ (not matching, only available under Ruby 1.9)
    > (greater than)
    >= (greater than or equal to)
    < (less than)
    <= (less than or equal to)

  • Cấu trúc & and |

    • Với các toán tử :

    Article.where((:title =~ 'Hello%') | (:title =~ 'Goodbye%')).to_sql
			=> SELECT "articles".* FROM "articles" WHERE
			 (("articles"."title" LIKE 'Hello%'
   			OR "articles"."title" LIKE 'Goodbye%'))```

- **Thay thế:**

   	Article.where(:title.matches % 'Hello%' | :title.matches % 'Goodbye%').to_sql
=> SELECT "articles".* FROM "articles" WHERE (("articles"."title" LIKE 'Hello%' OR "articles"."title" LIKE 'Goodbye%'))

    - **Với Hashes :**

```Ruby
Article.where({:created_at.lt => Time.now} &
{:created_at.gt => 1.year.ago}).to_sql
=> SELECT "articles".* FROM "articles" WHERE
((("articles"."created_at" < '2015-03-27 00:26:30.629467')AND
("articles"."created_at" > '2015-03-27 00:26:30.629526')))

    - **Với HashesThay thế**
```Ruby

    Article.where( :title.matches % 'Hello%' &{:created_at.lt =>
    Time.now, :created_at.gt => 1.year.ago}).to_sql
		=> SELECT "articles".* FROM  "articles" WHERE
		(("articles"."title" LIKE 'Hello%' AND
		("articles"."created_at" < '2015-03-27 01:04:38.023615' AND
    		"articles"."created_at" > '2015-03-27 01:04:38.023720')))

    - **MetaWhere có thể xử lý một cấu trúc phức tạp hơn : **

```Ruby
     Article.joins(:comments).where( {:title=> 'Greetings'} |
((:created_at.gt % 21.days.ago & :created_at.lt % 7.days.ago) &
:body.matches % '%from the past%' )
{:comments => [:body =~ '%first post!%']}).to_sql
=> SELECT "articles".*FROM "articles"INNER JOIN "comments"
            ON "comments"."article_id" = "articles"."id"
   		    WHERE
   		    (("articles"."title" = 'Greetings' OR(((
           "articles"."created_at" > '2010-03-26 05:57:57.924258'
           AND "articles"."created_at" < '2010-04-09 05:57:57.924984')
         AND "articles"."body" LIKE '%from the past%')
       AND "comments"."body" LIKE '%first post!%')))

- **Join() một thuộc tính :**
```Ruby

    Article.joins(:comments => :moderations.outer).to_sql
		=> SELECT "articles".* FROM "articles"
  		INNER JOIN "comments"
  		    ON "comments"."article_id" = "articles"."id"
   		LEFT OUTER JOIN "moderations"
   		    ON "moderations"."comment_id" = "comments"."id"

-  **Các hàm SQL :**

```Ruby
    Manager.joins(:employees.outer).group('managers.id').
        having(:employees => (:count.func(:id) < 3))
	=> SELECT "managers".* FROM "managers"
   	LEFT OUTER JOIN "employees"
   	        ON "employees"."manager_id" = "managers"."id"
   	GROUP BY managers.id HAVING count("employees"."id") < 3
Nếu bạn cho phép dùng các toán tử hỗ trợ thì có thể dùng :count[:id] thay cho việc gọi function như trên.

 Ngoài các hàm thông thường  trong SQL  : SELECT, WHERE, HAVING, bạn cũng có thể đặt thêm “bí danh” với “:as” :
    Manager.select('managers.*').
        select(:find_in_set[:id, '3,2,1'].as('position'))
	=> SELECT managers.*, find_in_set("managers"."id",'3,2,1') AS
	    position FROM "managers"

- **Xử lý thông minh với điều kiện Hash : **

    PredicateBuilder (một phần của  ActiveRecord) sẽ truyền một Hash điều kiện vào một truy vấn SQL hợp lệ :

```Ruby

    Article.joins(:comments).where(:comments => {:body => 'hey'}).to_sql
	=> SELECT "articles".* FROM "articles" INNER JOIN "comments"
   	ON "comments"."article_id" = "articles"."id"
   	WHERE ("comments"."body" = 'hey')

- Hash lồng nhau :

    Article.where(
  	:comments => {
    	:body => 'yo',
   	 :moderations => [:value < 0]
  	},
 	 :other_comments => {:body => 'hey'}
	).joins(
  	{:comments => :moderations},
  	:other_comments
	).to_sql

        => SELECT "articles".* FROM "articles"
   	INNER JOIN "comments" ON
   	        "comments"."article_id" = "articles"."id"
  	 INNER JOIN "moderations" ON
  	        "moderations"."comment_id" = "comments"."id"
  	 INNER JOIN "comments" "other_comments_articles" ON
  	        "other_comments_articles"."article_id" = "articles"."id"
  	WHERE (("comments"."body" = 'yo' AND "moderations"."value" < 0
    	AND "other_comments_articles"."body" = 'hey'))

  • Cải tiến hợp nhất mối quan hệ :

    Một trong những thay đổi mà MetaWhere tạo ra đối với ActiveRecord là sẽ trì hoãn việc “compiling” các “where_values” vào thực tế Arel Predicates cho đến khi hoàn toàn cần thiết.

    Trong quá trình này, MetaWher cung cấp cho bạn một phương pháp tuyệt vời để hợp nhất, thay đổi parameter của mình :


    Article.where(:id < 2).merge(Comment.where(:id < 7), :lame_comments)
    .to_sql
	=> "SELECT "articles".* FROM "articles"
  	 INNER JOIN "comments" ON "comments"."article_id" = "articles"."id"
      AND "comments"."body" = 'first post!'
   	WHERE ("articles"."id" < 2) AND ("comments"."id" < 7)"

hay :


  (Comment.where(:id < 7) & Article.where(:title =~ '%blah%')).to_sql
	=> SELECT "comments".* FROM "comments" INNER JOIN "articles"
  	 ON "articles"."id" = "comments"."article_id"
   	WHERE ("comments"."id" < 7) AND ("articles"."title" LIKE '%blah%')"

- Tính đa hình với "belongs_to" :

    MetaWhere cho phép bạn truy vấn thông “belongs_to” theo cách sau :

```Ruby
    Note.joins(:notable.type(Developer)).
     	where(:notable.type(Developer) => {:name.matches => 'Ernie%'})
	=> SELECT "notes".* FROM "notes"
   	INNER JOIN "developers" ON "developers"."id" = "notes"."notable_id"
     	AND "notes"."notable_type" = 'Developer'
   	WHERE "developers"."name" LIKE 'Ernie%'

- **Sử dụng giá trị điều kiện với object ActiveRecord :**
```Ruby

# Developer belongs_to Company

	company = Company.find(123)
	Developer.where(:company => company)

# Developer HABTM Projects

	projects = [Project.first, Project.last]
	Developer.joins(:projects).where(:projects => projects)

# Note belongs_to :notable, :polymorphic => true

	dev1 = Developer.first
	dev2 = Developer.last
	project = Project.first
	company = Company.first
	Note.where(:notable => [dev1, dev2, project, company]).to_sql
	=> SELECT "notes".* FROM "notes" WHERE (((("notes"."notable_id" IN (1, 8)
   	AND "notes"."notable_type" = 'Developer') OR ("notes"."notable_id" = 1
   	AND "notes"."notable_type" = 'Project')) OR ("notes"."notable_id" = 1
   	AND "notes"."notable_type" = 'Company')))

Nguồn tham khảo : - http://erniemiller.org/ - http://jpospisil.com/2014/06/16/the-definitive-guide-to-arel-the-sql-manager-for-ruby.html - https://github.com/activerecord-hackery/meta_where - https://github.com/brynary/arel