active_record_extended 0.7.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3332acf1ce7aa530fcbaddc871d5bdfc4560f71794fc4efbdd33653c6ea1eb5
4
- data.tar.gz: 73df494665607c73c5ddb2999aa0e021e166fc436a1cd75784cd1ffa30e1fe9e
3
+ metadata.gz: 84402ebc9c8f57ee3db39a3260233b2b737020aa14f26550a76b59bb1a4dd05c
4
+ data.tar.gz: e6613db1ec9bef7644cd6272a3b9f8c494119d45b756991c0ea61bf49b91312c
5
5
  SHA512:
6
- metadata.gz: 94c04d0fb830fd9e4b7f0b21bea2fb7a3b753903f4fb41ffb71fc72ed147ac3247507438411a37bf74235aaea943c2f98b5b5141467cf09b663dd9930269d2f0
7
- data.tar.gz: 3c5915ec558d623704dc4b824f052639dc25df2a0c0385f143eb597f4984f4beab1ef1d4083c7fbbf696ac0314b3ddd860640f8ecdc601681a4b042fe0224256
6
+ metadata.gz: edbe5dcb670d2db62f3c6171b377f42feb984babec1ae54780998170e12f3267d34218b03950f41a3b855fab7a4afac63a7536fba79d45cf37fdcdb990f40ce9
7
+ data.tar.gz: aa5131ef5f854a046762adb914ca199948265c11e01e70519cfc03d8895828d1f20416f8c88188bf7c10775fa3f89338f785f14927720abf204850207cabd52e
data/README.md CHANGED
@@ -4,9 +4,10 @@
4
4
  [![Test Coverage](https://api.codeclimate.com/v1/badges/f22154211bb3a8feb89f/test_coverage)](https://codeclimate.com/github/GeorgeKaraszi/ActiveRecordExtended/test_coverage)
5
5
  ## Index
6
6
  - [Description and history](#description-and-history)
7
+ - [Compatibility](#compatibility)
7
8
  - [Installation](#installation)
8
- - [Useage](#usage)
9
- - [Query Methods](#query-methods)
9
+ - [Usage](#usage)
10
+ - [Predicate Query Methods](#predicate-query-methods)
10
11
  - [Any](#any)
11
12
  - [All](#all)
12
13
  - [Contains](#contains)
@@ -21,6 +22,20 @@
21
22
  - [Any_of / None_of](#any_of--none_of)
22
23
  - [Either Join](#either-join)
23
24
  - [Either Order](#either-order)
25
+ - [Common Table Expressions (CTE)](#common-table-expressions-cte)
26
+ - [Subquery CTE Gotchas](#subquery-cte-gotchas)
27
+ - [JSON Query Methods](#json-query-methods)
28
+ - [Row To JSON](#row-to-json)
29
+ - [JSON/B Build Object](#jsonb-build-object)
30
+ - [JSON/B Build Literal](#jsonb-build-literal)
31
+ - [Unionization](#unionization)
32
+ - [Union](#union)
33
+ - [Union ALL](#union-all)
34
+ - [Union Except](#union-except)
35
+ - [Union Intersect](#union-intersect)
36
+ - [Union As](#union-as)
37
+ - [Union Order](#union-order)
38
+ - [Union Reorder](#union-reorder)
24
39
 
25
40
  ## Description and History
26
41
 
@@ -29,12 +44,21 @@ Active Record Extended is the continuation of maintaining and improving the work
29
44
  Overtime the lack of updating to support the latest versions of ActiveRecord 5.x has caused quite a bit of users forking off the project to create their own patches jobs to maintain compatibility.
30
45
  The only problem is that this has created a wild west of environments of sorts. The problem has grown to the point no one is attempting to directly contribute to the original source. And forked repositories are finding themselves as equally as dead with little to no activity.
31
46
 
32
- Active Record Extended is intended to be a supporting community that will maintain compatibility for the foreseeable future.
47
+ Active Record Extended is essentially providing users with the other half of Postgreses querying abilities. Due to Rails/ActiveRecord/Arel being designed to be DB agnostic, there are a lot of left out features; Either by choice or the simple lack of supporting API's for other databases. However some features are not exactly PG explicit. Some are just helper methods to express an idea much more easily.
33
48
 
34
49
 
50
+ ## Compatibility
51
+
52
+ This package is designed align and work with any officially supported Ruby and Rails versions.
53
+ - Minimum Ruby Version: 2.3.x **(EOL warning!)**
54
+ - Minimum Rails Version: 5.0.x **(EOL warning!)**
55
+ - Latest Ruby supported: 2.6.x
56
+ - Latest Rails supported: 5.2.x
57
+ - Postgres: 9.6-current(11) (probably works with most older versions to a certain point)
58
+
35
59
  ## Usage
36
60
 
37
- ### Query Methods
61
+ ### Predicate Query Methods
38
62
 
39
63
  #### Any
40
64
  [Postgres 'ANY' expression](https://www.postgresql.org/docs/10/static/functions-comparisons.html#id-1.5.8.28.16)
@@ -273,6 +297,486 @@ User.either_order(:asc, profile_l: :left_turns, profile_r: :right_turns) #=> [bo
273
297
  User.either_order(:desc, profile_l: :left_turns, profile_r: :right_turns) #=> [randy, alice, bob]
274
298
  ```
275
299
 
300
+ ### Common Table Expressions (CTE)
301
+ [Postgres WITH (CTE) Statement](https://www.postgresql.org/docs/current/static/queries-with.html)
302
+
303
+ The `.with/1` method is a base ActiveRecord querying method that will aid in creating complex queries.
304
+
305
+ ```ruby
306
+ alice = User.create!
307
+ bob = User.create!
308
+ randy = User.create!
309
+ ProfileL.create!(user_id: alice.id, likes: 200)
310
+ ProfileL.create!(user_id: bob.id, likes: 400)
311
+ ProfileL.create!(user_id: randy.id, likes: 600)
312
+
313
+ User.with(highly_liked: ProfileL.where("likes > 300"))
314
+ .joins("JOIN highly_liked ON highly_liked.user_id = users.id") #=> [bob, randy]
315
+ ```
316
+
317
+ Query output:
318
+
319
+ ```sql
320
+ WITH "highly_liked" AS (SELECT "profile_ls".* FROM "profile_ls" WHERE (likes >= 300))
321
+ SELECT "users".*
322
+ FROM "users"
323
+ JOIN highly_liked ON highly_liked.user_id = users.id
324
+ ```
325
+
326
+ You can also chain or provide additional arguments to the `with/1` method for it to merge into a single, `WITH` statement.
327
+
328
+ ```ruby
329
+ User.with(highly_liked: ProfileL.where("likes > 300"), less_liked: ProfileL.where("likes <= 200"))
330
+ .joins("JOIN highly_liked ON highly_liked.user_id = users.id")
331
+ .joins("JOIN less_liked ON less_liked.user_id = users.id")
332
+
333
+ # OR
334
+
335
+ User.with(highly_liked: ProfileL.where("likes > 300"))
336
+ .with(less_liked: ProfileL.where("likes <= 200"))
337
+ .joins("JOIN highly_liked ON highly_liked.user_id = users.id")
338
+ .joins("JOIN less_liked ON less_liked.user_id = users.id")
339
+ ```
340
+
341
+ Query output:
342
+
343
+ ```sql
344
+ WITH "highly_liked" AS (SELECT "profile_ls".* FROM "profile_ls" WHERE (likes > 300)),
345
+ "less_liked" AS (SELECT "profile_ls".* FROM "profile_ls" WHERE (likes <= 200))
346
+ SELECT "users".*
347
+ FROM "users"
348
+ JOIN highly_liked ON highly_liked.user_id = users.id
349
+ JOIN less_liked ON less_liked.user_id = users.id
350
+ ```
351
+
352
+ #### Subquery CTE Gotchas
353
+ In order keep queries PG valid, subquery explicit methods (like Unions and JSON methods)
354
+ will be subject to "Piping" the CTE clauses up to the parents query level.
355
+
356
+ This also means there's potential for having duplicate CTE names.
357
+ In order to combat duplicate CTE references with the same name, **piping will favor the parents CTE over the nested sub-queries**.
358
+
359
+ This also means that this is a "First come First Served" implementation.
360
+ So if you have a parent with no CTE's but two sub-queries with the same CTE name but with different querying statements.
361
+ It will process and favor the one that comes first.
362
+
363
+ Example:
364
+ ```ruby
365
+ sub_query = Person.with(dupped_cte: Person.where(id: 1)).select("dup_cte.id").from(:dup_cte)
366
+ other_subquery = Person.with(unique_cte: Person.where(id: 5)).select("unique_cte.id").from(:unique_cte)
367
+
368
+ # Will favor this CTE below, over the `sub_query`'s CTE
369
+ Person.with(dupped_cte: Person.where.not(id: 1..4)).union(sub_query, other_subquery)
370
+ ```
371
+
372
+ Query Output
373
+ ```sql
374
+ WITH "unique_cte" AS (
375
+ SELECT "people".*
376
+ FROM "people"
377
+ WHERE "people"."id" = 5
378
+ ), "dupped_cte" AS (
379
+ SELECT "people".*
380
+ FROM "people"
381
+ WHERE NOT ("people"."id" BETWEEN 1 AND 4)
382
+ )
383
+ SELECT "people".*
384
+ FROM (( (
385
+ SELECT dup_cte.id
386
+ FROM dup_cte
387
+ ) UNION (
388
+ SELECT unique_cte.id
389
+ FROM unique_cte
390
+ ) )) people
391
+ ```
392
+
393
+
394
+ ### JSON Query Methods
395
+ If any or all of your json sub-queries include a CTE, read the [Subquery CTE Gotchas](#subquery-cte-gotchas) warnings.
396
+
397
+ #### Row To JSON
398
+ [Postgres 'ROW_TO_JSON' function](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-CREATION-TABLE)
399
+
400
+ The implementation of the`.row_to_json/2` method is designed to be used with sub-queries. As a means for taking complex
401
+ query logic and transform them into a single or multiple json responses. These responses are required to be assigned
402
+ to an aliased column on the parent(callee) level.
403
+
404
+ While quite the mouthful of an explanation. The implementation of combining unrelated or semi-related queries is quite smooth(imo).
405
+
406
+ ```ruby
407
+ physical_cat = Category.create!(name: "Physical")
408
+ products = 3.times.map { Product.create! }
409
+ products.each { |product| 100.times { Variant.create!(product: product, category: physical_cat) } }
410
+
411
+ # Since we plan to nest this query, you have access top level information. (I.E categories table)
412
+ item_query = Variant.select(:name, :id, :category_id, :product_id).where("categories.id = variants.category_id")
413
+
414
+ # You can provide addition scopes that will be applied to the nested query (but will not effect the actual inner query)
415
+ # This is ideal if you are dealing with but not limited to, CTE's being applied multiple times and require additional constraints
416
+ product_query =
417
+ Product.select(:id)
418
+ .joins(:items)
419
+ .select_row_to_json(item_query, key: :outer_items, as: :items, cast_as_array: true) do |item_scope|
420
+ item_scope.where("outer_items.product_id = products.id")
421
+ # Results to:
422
+ # SELECT ..., ARRAY(SELECT ROW_TO_JSON("outer_items")
423
+ # FROM ([:item_query:]) outer_items
424
+ # WHERE outer_items.product_id = products.id
425
+ # ) AS items
426
+ end
427
+
428
+ # Not defining a key will automatically generate a random key between a-z
429
+ category_query = Category.select(:name, :id).select_row_to_json(product_query, as: :products, cast_as_array: true)
430
+ Category.json_build_object(:physical_category, category_query.where(id: physical_cat.id)).results
431
+ #=> {
432
+ # "physical_category" => {
433
+ # "name" => "Physical",
434
+ # "id" => 1,
435
+ # "products" => [
436
+ # {
437
+ # "id" => 2,
438
+ # "items" => [{"name" => "Bojangels", "id" => 3, "category_id" => 1, "product_id" => 2}, ...]
439
+ # },
440
+ # ...
441
+ # ]
442
+ # }
443
+ # }
444
+ #
445
+ ```
446
+
447
+ Query Output
448
+ ```sql
449
+ SELECT (JSON_BUILD_OBJECT('physical_category', "physical_category")) AS "results"
450
+ FROM (
451
+ SELECT "categories"."name", "categories"."id", (ARRAY(
452
+ SELECT ROW_TO_JSON("j")
453
+ FROM (
454
+ SELECT "products"."id", (ARRAY(
455
+ SELECT ROW_TO_JSON("outer_item")
456
+ FROM (
457
+ SELECT "variants"."name", "variants"."id", "variants"."category_id", "variants"."product_id"
458
+ FROM "variants"
459
+ WHERE (categories.id = variants.category_id)
460
+ ) outer_items
461
+ WHERE (outer_items.product_id = products.id)
462
+ )) AS "items"
463
+ FROM "products"
464
+ INNER JOIN "items" ON "products"."id" = "items"."product_id"
465
+ ) j
466
+ )) AS "products"
467
+ FROM "categories"
468
+ WHERE "categories"."id" = 1
469
+ ) AS "physical_category"
470
+ ```
471
+
472
+
473
+ #### JSON/B Build Object
474
+ [Postgres 'json(b)_build_object' function](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-CREATION-TABLE)
475
+
476
+ The implementation of the`.json_build_object/2` and `.jsonb_build_object/2` methods are designed to be used with sub-queries.
477
+ As a means for taking complex query logic and transform them into a single or multiple json responses.
478
+
479
+ **Arguments:**
480
+ - `key`: [Symbol or String]: What should this response return as
481
+ - `from`: [String, Arel, or ActiveRecord::Relation] : A subquery that can be nested into the top-level from clause
482
+
483
+ **Options:**
484
+ - `as`: [Symbol or String] (defaults to `"results"`): What the column will be aliased to
485
+ - `value`: [Symbol or String] (defaults to `key` argument): How the response should handel the json value return
486
+
487
+ See the included example on [Row To JSON](#row-to-json) to see it in action.
488
+
489
+ #### JSON/B Build Literal
490
+ [Postgres 'json(b)_build_object' function](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-CREATION-TABLE)
491
+
492
+ The implementation of the`.json_build_literal/1` and `.jsonb_build_literal/1` is designed for creating static json objects
493
+ that don't require subquery interfacing.
494
+
495
+ **Arguments:**
496
+ - Requires an Array or Hash set of values
497
+
498
+ **Options:**
499
+ - `as`: [Symbol or String] (defaults to `"results"`): What the column will be aliased to
500
+
501
+ ```ruby
502
+ User.json_build_literal(number: 1, last_name: "json", pi: 3.14).take.results
503
+ #=> { "number" => 1, "last_name" => "json", "pi" => 3.14 }
504
+
505
+ # Or as array elements
506
+ User.json_build_literal(:number, 1, :last_name, "json", :pi, 3.14).take.results
507
+ #=> { "number" => 1, "last_name" => "json", "pi" => 3.14 }
508
+
509
+ ```
510
+
511
+ Query Output
512
+ ```sql
513
+ SELECT (JSON_BUILD_OBJECT('number', 1, 'last_name', 'json', 'pi', 3.14)) AS "results"
514
+ FROM "users"
515
+ ```
516
+
517
+
518
+ ### Unionization
519
+ If any or all of your union queries include a CTE, read the [Subquery CTE Gotchas](#subquery-cte-gotchas) warnings.
520
+
521
+ #### SQL-Query Helpers
522
+ - `.to_union_sql` : Will return a string of the constructed union query without being nested in the `from` clause.
523
+ - `.to_nice_union_sql`(requires [NiceQL Gem](https://github.com/alekseyl/niceql) to be install): A formatted `.to_union_sql`
524
+
525
+
526
+ #### Known issue
527
+ There's an issue with providing a single union clause and chaining it with a different union clause.
528
+ This is due to requirements of grouping SQL statements. The issue is being working on, but with no ETA.
529
+
530
+ This issue only applies to the first initial set of unions and is recommended that you union two relations right off the bat.
531
+ Afterwords you can union/chain single relations.
532
+
533
+ Example
534
+
535
+ ```ruby
536
+
537
+ Person.union(Person.where(id: 1..4)).union_except(Person.where(id: 3..4)).union(Person.where(id: 4))
538
+ #=> Will include all people with an ID between 1 & 3 (throwing the except on ID 4)
539
+
540
+ # This can be fixed by doing something like
541
+
542
+ Person.union_except(Person.where(id: 1..4), Person.where(id: 3..4)).union(Person.where(id: 4))
543
+ #=> Will include people with the ids of 1, 2, and 4 (properly excluding the user with the ID of 3)
544
+ ```
545
+
546
+ Problem Query Output
547
+ ```sql
548
+ ( ( (
549
+ SELECT "people".*
550
+ FROM "people"
551
+ WHERE "people"."id" BETWEEN 1 AND 4
552
+ ) UNION (
553
+ SELECT "people".*
554
+ FROM "people"
555
+ WHERE "people"."id" BETWEEN 3 AND 4
556
+ ) ) EXCEPT (
557
+ SELECT "people".*
558
+ FROM "people"
559
+ WHERE "people"."id" = 4
560
+ ) )
561
+ ```
562
+
563
+
564
+ #### Union
565
+ [Postgres 'UNION' combination](https://www.postgresql.org/docs/current/queries-union.html)
566
+
567
+ ```ruby
568
+ user_1 = Person.where(id: 1)
569
+ user_2 = Person.where(id: 2)
570
+ users = Person.where(id: 1..3)
571
+
572
+ Person.union(user_1, user_2, users) #=> [#<Person id: 1, ..>, #<Person id: 2,..>, #<Person id: 3,..>]
573
+
574
+ # You can also chain union's
575
+ Person.union(user_1).union(user_2).union(users)
576
+ ```
577
+
578
+ Query Output
579
+ ```sql
580
+ SELECT "people".*
581
+ FROM (( ( (
582
+ SELECT "people".*
583
+ FROM "people"
584
+ WHERE "people"."id" = 1
585
+ ) UNION (
586
+ SELECT "people".*
587
+ FROM "people"
588
+ WHERE "people"."id" = 2
589
+ ) ) UNION (
590
+ SELECT "people".*
591
+ FROM "people"
592
+ WHERE "people"."id" BETWEEN 1 AND 3
593
+ ) )) people
594
+ ```
595
+
596
+
597
+ #### Union ALL
598
+ [Postgres 'UNION ALL' combination](https://www.postgresql.org/docs/current/queries-union.html)
599
+
600
+ ```ruby
601
+ user_1 = Person.where(id: 1)
602
+ user_2 = Person.where(id: 2)
603
+ users = Person.where(id: 1..3)
604
+
605
+ Person.union_all(user_1, user_2, users)
606
+ #=> [#<Person id: 1, ..>, #<Person id: 2,..>, #<Person id: 1, ..>, #<Person id: 2,..>, #<Person id: 3,..>]
607
+
608
+ # You can also chain union's
609
+ Person.union_all(user_1).union_all(user_2).union_all(users)
610
+ # Or
611
+ Person.union.all(user1, user_2).union.all(users)
612
+ ```
613
+
614
+ Query Output
615
+ ```sql
616
+ SELECT "people".*
617
+ FROM (( ( (
618
+ SELECT "people".*
619
+ FROM "people"
620
+ WHERE "people"."id" = 1
621
+ ) UNION ALL (
622
+ SELECT "people".*
623
+ FROM "people"
624
+ WHERE "people"."id" = 2
625
+ ) ) UNION ALL (
626
+ SELECT "people".*
627
+ FROM "people"
628
+ WHERE "people"."id" BETWEEN 1 AND 3
629
+ ) )) people
630
+ ```
631
+
632
+ #### Union Except
633
+ [Postgres 'EXCEPT' combination](https://www.postgresql.org/docs/current/queries-union.html)
634
+
635
+ ```ruby
636
+ users = Person.where(id: 1..5)
637
+ expect_these_users = Person.where(id: 2..4)
638
+
639
+ Person.union_except(users, expect_these_users) #=> [#<Person id: 1, ..>, #<Person id: 5,..>]
640
+
641
+ # You can also chain union's
642
+ Person.union.except(users, expect_these_users).union(Person.where(id: 20))
643
+ ```
644
+
645
+ Query Output
646
+ ```sql
647
+ SELECT "people".*
648
+ FROM (( ( (
649
+ SELECT "people".*
650
+ FROM "people"
651
+ WHERE "people"."id" BETWEEN 1 AND 5
652
+ ) EXCEPT (
653
+ SELECT "people".*
654
+ FROM "people"
655
+ WHERE "people"."id" BETWEEN 2 AND 4
656
+ )) people
657
+ ```
658
+
659
+ #### Union Intersect
660
+ [Postgres 'INTERSECT' combination](https://www.postgresql.org/docs/current/queries-union.html)
661
+
662
+ ```ruby
663
+ randy = Person.create!
664
+ alice = Person.create!
665
+ ProfileL.create!(person: randy, likes: 100)
666
+ ProfileL.create!(person: alice, likes: 120)
667
+
668
+ likes_100 = Person.select(:id, "profile_ls.likes").joins(:profile_l).where(profile_ls: { likes: 100 })
669
+ likes_less_than_150 = Person.select(:id, "profile_ls.likes").joins(:profile_l).where("profile_ls.likes < 150")
670
+ Person.union_intersect(likes_100, likes_less_than_150) #=> [randy]
671
+
672
+
673
+
674
+ # You can also chain union's
675
+ Person.union_intersect(likes_100).union_intersect(likes_less_than_150) #=> [randy]
676
+ # Or
677
+ Person.union.intersect(likes_100, likes_less_than_150) #=> [randy]
678
+
679
+ ```
680
+
681
+ Query Output
682
+ ```sql
683
+ SELECT "people".*
684
+ FROM (( (
685
+ SELECT "people"."id", profile_ls.likes
686
+ FROM "people"
687
+ INNER JOIN "profile_ls" ON "profile_ls"."person_id" = "people"."id"
688
+ WHERE "profile_ls"."likes" = 100
689
+ ) INTERSECT (
690
+ SELECT "people"."id", profile_ls.likes
691
+ FROM "people"
692
+ INNER JOIN "profile_ls" ON "profile_ls"."person_id" = "people"."id"
693
+ WHERE (profile_ls.likes < 150)
694
+ ) )) people
695
+ ```
696
+
697
+ #### Union As
698
+
699
+ By default unions are nested in the from clause and are aliased to the parents table name.
700
+ We can change this behavior by chaining the method `.union_as/1`
701
+
702
+ ```ruby
703
+ Person.select("good_people.id").union(Person.where(id: 1), Person.where(id: 2)).union_as(:good_people)
704
+ ```
705
+
706
+ Query Output
707
+ ```sql
708
+ SELECT good_people.id
709
+ FROM (( (
710
+ SELECT "people".*
711
+ FROM "people"
712
+ WHERE "people"."id" = 1
713
+ ) UNION (
714
+ SELECT "people".*
715
+ FROM "people"
716
+ WHERE "people"."id" = 2
717
+ ) )) good_people
718
+ ```
719
+
720
+
721
+ #### Union Order
722
+
723
+ Unions allow for a final outside `ORDER BY` clause. This will ensure that all the results that come back are ordered in an expected return.
724
+
725
+ ```ruby
726
+ query_1 = Person.where(id: 1..3)
727
+ query_2 = Person.where(id: 3)
728
+ query_3 = Person.where(id: 3..10)
729
+ Person.union_except(query_1, query_2).union(query_3).order_union(:id, tags: :desc)
730
+ ```
731
+
732
+ Query Output
733
+ ```sql
734
+ SELECT "people".*
735
+ FROM (( ( (
736
+ SELECT "people".*
737
+ FROM "people"
738
+ WHERE "people"."id" BETWEEN 1 AND 3
739
+ ) EXCEPT (
740
+ SELECT "people".*
741
+ FROM "people"
742
+ WHERE "people"."id" = 3
743
+ ) ) UNION (
744
+ SELECT "people".*
745
+ FROM "people"
746
+ WHERE "people"."id" BETWEEN 3 AND 10
747
+ ) ) ORDER BY id ASC, tags DESC) people
748
+ ```
749
+
750
+ #### Union Reorder
751
+
752
+ much like Rails `.reorder`; `.reorder_union/1` will clear the previous order in a new instance and/or apply a new ordering scheme
753
+ ```ruby
754
+ query_1 = Person.where(id: 1..3)
755
+ query_2 = Person.where(id: 3)
756
+ query_3 = Person.where(id: 3..10)
757
+ union_query = Person.union_except(query_1, query_2).union(query_3).order_union(:id, tags: :desc)
758
+ union_query.reorder_union(personal_id: :desc, id: :desc)
759
+ ```
760
+
761
+ Query Output
762
+ ```sql
763
+ SELECT "people".*
764
+ FROM (( ( (
765
+ SELECT "people".*
766
+ FROM "people"
767
+ WHERE "people"."id" BETWEEN 1 AND 3
768
+ ) EXCEPT (
769
+ SELECT "people".*
770
+ FROM "people"
771
+ WHERE "people"."id" = 3
772
+ ) ) UNION (
773
+ SELECT "people".*
774
+ FROM "people"
775
+ WHERE "people"."id" BETWEEN 3 AND 10
776
+ ) ) ORDER BY personal_id DESC, id DESC) people
777
+ ```
778
+
779
+
276
780
  ## Installation
277
781
 
278
782
  Add this line to your application's Gemfile:
@@ -289,14 +793,6 @@ Or install it yourself as:
289
793
 
290
794
  $ gem install active_record_extended
291
795
 
292
-
293
- ## Development
294
-
295
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
296
-
297
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
298
-
299
-
300
796
  ## License
301
797
 
302
798
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).