{"id":15029107,"url":"https://github.com/georgekaraszi/activerecordextended","last_synced_at":"2025-05-13T16:11:34.285Z","repository":{"id":32069669,"uuid":"131496023","full_name":"GeorgeKaraszi/ActiveRecordExtended","owner":"GeorgeKaraszi","description":"Adds additional postgres functionality to an ActiveRecord / Rails application","archived":false,"fork":false,"pushed_at":"2024-10-28T19:02:10.000Z","size":444,"stargazers_count":1302,"open_issues_count":4,"forks_count":43,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-10-29T15:33:11.963Z","etag":null,"topics":["activerecord","postgres","rails"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GeorgeKaraszi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-04-29T13:18:13.000Z","updated_at":"2024-10-28T19:02:15.000Z","dependencies_parsed_at":"2023-10-02T05:55:51.918Z","dependency_job_id":"f4ca8a5b-0d09-416a-8261-b6418ac20dd5","html_url":"https://github.com/GeorgeKaraszi/ActiveRecordExtended","commit_stats":{"total_commits":330,"total_committers":15,"mean_commits":22.0,"dds":0.06060606060606055,"last_synced_commit":"5fd4ea9270da07a35adb7cfbd33c25adc69bf057"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeorgeKaraszi%2FActiveRecordExtended","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeorgeKaraszi%2FActiveRecordExtended/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeorgeKaraszi%2FActiveRecordExtended/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeorgeKaraszi%2FActiveRecordExtended/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GeorgeKaraszi","download_url":"https://codeload.github.com/GeorgeKaraszi/ActiveRecordExtended/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250528868,"owners_count":21445517,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","postgres","rails"],"created_at":"2024-09-24T20:09:44.571Z","updated_at":"2025-04-23T23:11:30.622Z","avatar_url":"https://github.com/GeorgeKaraszi.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Gem Version](https://badge.fury.io/rb/active_record_extended.svg)](https://badge.fury.io/rb/active_record_extended)\n[![Build Status](https://github.com/GeorgeKaraszi/ActiveRecordExtended/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/GeorgeKaraszi/ActiveRecordExtended/actions/workflows/test.yml?query=branch%3Amaster+)\n[![Maintainability](https://api.codeclimate.com/v1/badges/98ecffc0239417098cbc/maintainability)](https://codeclimate.com/github/GeorgeKaraszi/active_record_extended/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/f22154211bb3a8feb89f/test_coverage)](https://codeclimate.com/github/GeorgeKaraszi/ActiveRecordExtended/test_coverage)\n## Index\n- [Description and history](#description-and-history)\n- [Compatibility](#compatibility)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Predicate Query Methods](#predicate-query-methods)\n    - [Any](#any)\n    - [All](#all)\n    - [Contains](#contains)\n    - [Overlap](#overlap)\n    - [Inet / IP Address](#inet--ip-address)\n      - [Inet Contains](#inet-contains)\n      - [Inet Contains or Equals](#inet-contains-or-equals)\n      - [Inet Contained Within](#inet-contained-within)\n      - [Inet Contained Within or Equals](#inet-contained-within-or-equals)\n      - [Inet Contains or Contained Within](#inet-contains-or-contained-within)\n  - [Conditional Methods](#conditional-methods)\n    - [Any_of / None_of](#any_of--none_of)\n    - [Either Join](#either-join)\n    - [Either Order](#either-order)\n  - [Common Table Expressions (CTE)](#common-table-expressions-cte)\n    - [Subquery CTE Gotchas](#subquery-cte-gotchas)\n  - [JSON Query Methods](#json-query-methods)\n    - [Row To JSON](#row-to-json)\n    - [JSON/B Build Object](#jsonb-build-object)\n    - [JSON/B Build Literal](#jsonb-build-literal)\n  - [Unionization](#unionization)\n    - [Union](#union)\n    - [Union ALL](#union-all)\n    - [Union Except](#union-except)\n    - [Union Intersect](#union-intersect)\n    - [Union As](#union-as)\n    - [Union Order](#union-order)\n    - [Union Reorder](#union-reorder)\n  - [Window Functions](#window-functions)\n    - [Define Window](#define-window)\n    - [Select Window](#select-window)\n\n## Description and History\n\nActive Record Extended is the continuation of maintaining and improving the work done by **Dan McClain**, the original author of [postgres_ext](https://github.com/DavyJonesLocker/postgres_ext).\n\nOvertime 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.\nThe 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.\n\nActive 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.\n\n## Compatibility\n\nThis package is designed align and work with any officially supported Ruby and Rails versions.\n - Minimum Ruby Version: 3.1.x **(EOL warning!)**\n - Minimum Rails Version: 6.1.x **(EOL warning!)**\n - Minimum Postgres Version: 12.x **(EOL warning!)**\n - Latest Ruby supported: 3.3.x\n - Latest Rails supported: 7.1.x\n - Postgres: 11-current(16) (probably works with most older versions to a certain point)\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'active_record_extended'\n```\n\nAnd then execute:\n\n    $ bundle\n\n## Usage\n\n### Predicate Query Methods\n\n#### Any\n[Postgres 'ANY' expression](https://www.postgresql.org/docs/10/static/functions-comparisons.html#id-1.5.8.28.16)\n\nIn Postgres the `ANY` expression is used for gather record's that have an Array column type that contain a single matchable value within its array.\n\n```ruby\nalice = User.create!(tags: [1])\nbob   = User.create!(tags: [1, 2])\nrandy = User.create!(tags: [3])\n\nUser.where.any(tags: 1) #=\u003e [alice, bob]\n\n```\n\nThis only accepts a single value. So querying for example multiple tag numbers `[1,2]` will return nothing.\n\n\n#### All\n[Postgres 'ALL' expression](https://www.postgresql.org/docs/10/static/functions-comparisons.html#id-1.5.8.28.17)\n\nIn Postgres the `ALL` expression is used for gather record's that have an Array column type that contains only a **single** and matchable element.\n\n```ruby\nalice = User.create!(tags: [1])\nbob   = User.create!(tags: [1, 2])\nrandy = User.create!(tags: [3])\n\nUser.where.all(tags: 1) #=\u003e [alice]\n\n```\n\nThis only accepts a single value to a given attribute. So querying for example multiple tag numbers `[1,2]` will return nothing.\n\n#### Contains\n[Postgres '@\u003e' (Array type) Contains expression](https://www.postgresql.org/docs/10/static/functions-array.html)\n\n[Postgres '@\u003e' (JSONB/HSTORE type) Contains expression](https://www.postgresql.org/docs/10/static/functions-json.html#FUNCTIONS-JSONB-OP-TABLE)\n\n\nThe `contains/1` method is used for finding any elements in an `Array`, `JSONB`, or `HSTORE` column type.\nThat contains all of the provided values.\n\nArray Type:\n```ruby\nalice = User.create!(tags: [1, 4])\nbob   = User.create!(tags: [3, 1])\nrandy = User.create!(tags: [4, 1])\n\nUser.where.contains(tags: [1, 4]) #=\u003e [alice, randy]\n```\n\nHSTORE / JSONB Type:\n```ruby\nalice = User.create!(data: { nickname: \"ARExtend\" })\nbob   = User.create!(data: { nickname: \"ARExtended\" })\nrandy = User.create!(data: { nickname: \"ARExtended\" })\n\nUser.where.contains(data: { nickname: \"ARExtended\" }) #=\u003e [bob, randy]\n```\n\n#### Overlap\n[Postgres \u0026\u0026 (overlap) Expression](https://www.postgresql.org/docs/10/static/functions-array.html)\n\nThe `overlap/1` method will match an Array column type that contains any of the provided values within its column.\n\n```ruby\nalice = User.create!(tags: [1, 4])\nbob   = User.create!(tags: [3, 4])\nrandy = User.create!(tags: [4, 8])\n\nUser.where.overlap(tags: [4]) #=\u003e [alice, bob, randy]\nUser.where.overlap(tags: [1, 8]) #=\u003e [alice, randy]\nUser.where.overlap(tags: [1, 3, 8]) #=\u003e [alice, bob, randy]\n\n```\n\n#### Inet / IP Address\n##### Inet Contains\n[Postgres \u003e\u003e (contains) Network Expression](https://www.postgresql.org/docs/current/static/functions-net.html)\n\nThe `inet_contains` method works by taking a column(inet type) that has a submask prepended to it.\nAnd tries to find related records that fall within a given IP's range.\n\n```ruby\nalice = User.create!(ip: \"127.0.0.1/16\")\nbob   = User.create!(ip: \"192.168.0.1/16\")\n\nUser.where.inet_contains(ip: \"127.0.0.254\") #=\u003e [alice]\nUser.where.inet_contains(ip: \"192.168.20.44\") #=\u003e [bob]\nUser.where.inet_contains(ip: \"192.255.1.1\") #=\u003e []\n```\n\n##### Inet Contains or Equals\n[Postgres \u003e\u003e= (contains or equals) Network Expression](https://www.postgresql.org/docs/current/static/functions-net.html)\n\nThe `inet_contains_or_equals` method works much like the [Inet Contains](#inet-contains) method, but will also accept a submask range.\n\n```ruby\nalice = User.create!(ip: \"127.0.0.1/10\")\nbob   = User.create!(ip: \"127.0.0.44/24\")\n\nUser.where.inet_contains_or_equals(ip: \"127.0.0.1/16\") #=\u003e [alice]\nUser.where.inet_contains_or_equals(ip: \"127.0.0.1/10\") #=\u003e [alice]\nUser.where.inet_contains_or_equals(ip: \"127.0.0.1/32\") #=\u003e [alice, bob]\n```\n\n##### Inet Contained Within\n[Postgres \u003c\u003c (contained by) Network Expression](https://www.postgresql.org/docs/current/static/functions-net.html)\n\nFor the `inet_contained_within` method, we try to find IP's that fall within a submasking range we provide.\n\n```ruby\nalice = User.create!(ip: \"127.0.0.1\")\nbob   = User.create!(ip: \"127.0.0.44\")\nrandy = User.create!(ip: \"127.0.55.20\")\n\nUser.where.inet_contained_within(ip: \"127.0.0.1/24\") #=\u003e [alice, bob]\nUser.where.inet_contained_within(ip: \"127.0.0.1/16\") #=\u003e [alice, bob, randy]\n```\n\n##### Inet Contained Within or Equals\n[Postgres \u003c\u003c= (contained by or equals) Network Expression](https://www.postgresql.org/docs/current/static/functions-net.html)\n\nThe `inet_contained_within_or_equals` method works much like the [Inet Contained Within](#inet-contained-within) method, but will also accept a submask range.\n\n```ruby\nalice = User.create!(ip: \"127.0.0.1/10\")\nbob   = User.create!(ip: \"127.0.0.44/32\")\nrandy = User.create!(ip: \"127.0.99.1\")\n\nUser.where.inet_contained_within_or_equals(ip: \"127.0.0.44/32\") #=\u003e [bob]\nUser.where.inet_contained_within_or_equals(ip: \"127.0.0.1/16\") #=\u003e [bob, randy]\nUser.where.inet_contained_within_or_equals(ip: \"127.0.0.44/8\") #=\u003e [alice, bob, randy]\n```\n\n##### Inet Contains or Contained Within\n[Postgres \u0026\u0026 (contains or is contained by) Network Expression](https://www.postgresql.org/docs/current/static/functions-net.html)\n\nThe `inet_contains_or_contained_within` method is a combination of [Inet Contains](#inet-contains) and [Inet Contained Within](#inet-contained-within).\nIt essentially (the database) tries to use both methods to find as many records as possible that match either condition on both sides.\n\n```ruby\nalice = User.create!(ip: \"127.0.0.1/24\")\nbob   = User.create!(ip: \"127.0.22.44/8\")\nrandy = User.create!(ip: \"127.0.99.1\")\n\nUser.where.inet_contains_or_is_contained_within(ip: \"127.0.255.80\") #=\u003e [bob]\nUser.where.inet_contains_or_is_contained_within(ip: \"127.0.0.80\") #=\u003e [alice, bob]\nUser.where.inet_contains_or_is_contained_within(ip: \"127.0.0.80/8\") #=\u003e [alice, bob, randy]\n```\n\n### Conditional Methods\n#### Any_of / None_of\n `any_of/1` simplifies the process of finding records that require multiple `or` conditions.\n\n `none_of/1` is the inverse of `any_of/1`. It'll find records where none of the contains are matched.\n\n Both accepts An array of: ActiveRecord Objects, Query Strings, and basic attribute names.\n\n Querying With Attributes:\n ```ruby\nalice = User.create!(uid: 1)\nbob   = User.create!(uid: 2)\nrandy = User.create!(uid: 3)\n\nUser.where.any_of({ uid: 1 }, { uid: 2 }) #=\u003e [alice, bob]\n```\n\nQuerying With ActiveRecord Objects:\n```ruby\nalice = User.create!(uid: 1)\nbob   = User.create!(uid: 2)\nrandy = User.create!(uid: 3)\n\nuid_one = User.where(uid: 1)\nuid_two = User.where(uid: 2)\n\nUser.where.any_of(uid_one, uid_two) #=\u003e [alice, bob]\n```\n\nQuerying with Joined Relationships:\n```ruby\nalice     = User.create!(uid: 1)\nbob       = User.create!(uid: 2)\nrandy     = User.create!(uid: 3)\ntag_alice = Tag.create!(user_id: alice.id)\ntag_bob   = Tag.create!(user_id: bob.id)\ntag_randy = Tag.create!(user_id: randy.id)\n\nbob_tag_query   = Tag.where(users: { id: bob.id }).includes(:user)\nrandy_tag_query = Tag.where(users: { id: randy.id }).joins(:user)\n\nTag.joins(:user).where.any_of(bob_tag_query, randy_tag_query) #=\u003e [tag_bob, tag_randy] (with users table joined)\n```\n\n#### Either Join\n\nThe `#either_join/2` method is a base ActiveRecord querying method that will joins records based on a set of conditionally joinable tables.\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  has_one :profile_l, class: \"ProfileL\"\n  has_one :profile_r, class: \"ProfileR\"\n\n  scope :completed_profile, -\u003e { either_joins(:profile_l, :profile_r) }\nend\n\nalice = User.create!\nbob   = User.create!\nrandy = User.create! # Does not have a single completed profile type\nProfileL.create!(user_id: alice.id)\nProfileR.create!(user_id: bob.id)\n\nUser.completed_profile #=\u003e [alice, bob]\n# alternatively\nUser.either_joins(:profile_l, :profile_r) #=\u003e [alice, bob]\n```\n\n#### Either Order\n\nThe `#either_order/3` method is a base ActiveRecord querying method that will order a set of columns that may or may not exist for each record.\nThis works similar to how [Either Join](#either-join) works. This does not however exclude records that do not have relationships.\n\n```ruby\nalice = User.create!\nbob   = User.create!\nProfileL.create!(user_id: alice.id, left_turns: 100)\nProfileR.create!(user_id: bob.id, right_turns: 50)\n\nUser.either_order(:asc, profile_l: :left_turns, profile_r: :right_turns) #=\u003e [bob, alice]\nUser.either_order(:desc, profile_l: :left_turns, profile_r: :right_turns) #=\u003e [alice, bob]\n\nrandy = User.create!\nUser.either_order(:asc, profile_l: :left_turns, profile_r: :right_turns) #=\u003e [bob, alice, randy]\nUser.either_order(:desc, profile_l: :left_turns, profile_r: :right_turns) #=\u003e [randy, alice, bob]\n```\n\n### Common Table Expressions (CTE)\n[Postgres WITH (CTE) Statement](https://www.postgresql.org/docs/current/static/queries-with.html)\n\nThe `.with/1` method is a base ActiveRecord querying method that will aid in creating complex queries.\n\n```ruby\nalice = User.create!\nbob   = User.create!\nrandy = User.create!\nProfileL.create!(user_id: alice.id, likes: 200)\nProfileL.create!(user_id: bob.id,   likes: 400)\nProfileL.create!(user_id: randy.id, likes: 600)\n\nUser.with(highly_liked: ProfileL.where(\"likes \u003e 300\"))\n    .joins(\"JOIN highly_liked ON highly_liked.user_id = users.id\") #=\u003e [bob, randy]\n```\n\nQuery output:\n\n```sql\nWITH \"highly_liked\" AS (SELECT \"profile_ls\".* FROM \"profile_ls\" WHERE (likes \u003e= 300))\nSELECT \"users\".*\nFROM \"users\"\nJOIN highly_liked ON highly_liked.user_id = users.id\n```\n\nYou can also chain or provide additional arguments to the `with/1` method for it to merge into a single, `WITH` statement.\n\n```ruby\nUser.with(highly_liked: ProfileL.where(\"likes \u003e 300\"), less_liked: ProfileL.where(\"likes \u003c= 200\"))\n    .joins(\"JOIN highly_liked ON highly_liked.user_id = users.id\")\n    .joins(\"JOIN less_liked ON less_liked.user_id = users.id\")\n\n# OR\n\nUser.with(highly_liked: ProfileL.where(\"likes \u003e 300\"))\n    .with(less_liked: ProfileL.where(\"likes \u003c= 200\"))\n    .joins(\"JOIN highly_liked ON highly_liked.user_id = users.id\")\n    .joins(\"JOIN less_liked ON less_liked.user_id = users.id\")\n```\n\nQuery output:\n\n```sql\nWITH \"highly_liked\" AS (SELECT \"profile_ls\".* FROM \"profile_ls\" WHERE (likes \u003e 300)),\n     \"less_liked\" AS (SELECT \"profile_ls\".* FROM \"profile_ls\" WHERE (likes \u003c= 200))\nSELECT \"users\".*\nFROM \"users\"\nJOIN highly_liked ON highly_liked.user_id = users.id\nJOIN less_liked ON less_liked.user_id = users.id\n```\n\nThere are three methods you can chain to the `with/1` to add modifiers to the query.\n#### `recursive`\n\n```ruby\nUser.with.recursive(highly_liked: ProfileL.where(\"likes \u003e 300\"))\n    .joins(\"JOIN highly_liked ON highly_liked.user_id = users.id\")\n```\n\nQuery output:\n\n```sql\nWITH RECURSIVE \"highly_liked\" AS (SELECT \"profile_ls\".* FROM \"profile_ls\" WHERE (likes \u003e= 300))\nSELECT \"users\".*\nFROM \"users\"\nJOIN highly_liked ON highly_liked.user_id = users.id\n```\n#### `materialized` (**Note**: MATERIALIZED modifier is only available in [PG versions 12+](https://www.postgresql.org/docs/release/12.0/).)\n\n\n```ruby\nUser.with.materialized(highly_liked: ProfileL.where(\"likes \u003e 300\"))\n    .joins(\"JOIN highly_liked ON highly_liked.user_id = users.id\")\n```\n\nQuery output:\n\n```sql\nWITH \"highly_liked\" AS MATERIALIZED (SELECT \"profile_ls\".* FROM \"profile_ls\" WHERE (likes \u003e= 300))\nSELECT \"users\".*\nFROM \"users\"\nJOIN highly_liked ON highly_liked.user_id = users.id\n```\n#### `not_materialized` (**Note**: NOT MATERIALIZED modifier is only available in [PG versions 12+](https://www.postgresql.org/docs/release/12.0/).)\n\n```ruby\nUser.with.not_materialized(highly_liked: ProfileL.where(\"likes \u003e 300\"))\n    .joins(\"JOIN highly_liked ON highly_liked.user_id = users.id\")\n```\n\nQuery output:\n\n```sql\nWITH \"highly_liked\" AS NOT MATERIALIZED (SELECT \"profile_ls\".* FROM \"profile_ls\" WHERE (likes \u003e= 300))\nSELECT \"users\".*\nFROM \"users\"\nJOIN highly_liked ON highly_liked.user_id = users.id\n```\n\n#### Subquery CTE Gotchas\n In order keep queries PG valid, subquery explicit methods (like Unions and JSON methods)\n will be subject to \"Piping\" the CTE clauses up to the parents query level.\n\n This also means there's potential for having duplicate CTE names.\n In order to combat duplicate CTE references with the same name, **piping will favor the parents CTE over the nested sub-queries**.\n\n This also means that this is a \"First come First Served\" implementation.\n So if you have a parent with no CTE's but two sub-queries with the same CTE name but with different querying statements.\n It will process and favor the one that comes first.\n\n Example:\n ```ruby\n    sub_query      = Person.with(dupped_cte: Person.where(id: 1)).select(\"dup_cte.id\").from(:dup_cte)\n    other_subquery = Person.with(unique_cte: Person.where(id: 5)).select(\"unique_cte.id\").from(:unique_cte)\n\n    # Will favor this CTE below, over the `sub_query`'s CTE\n    Person.with(dupped_cte: Person.where.not(id: 1..4)).union(sub_query, other_subquery)\n```\n\nQuery Output\n```sql\nWITH \"unique_cte\" AS (\n  SELECT \"people\".*\n  FROM \"people\"\n  WHERE \"people\".\"id\" = 5\n), \"dupped_cte\" AS (\n  SELECT \"people\".*\n  FROM \"people\"\n  WHERE NOT (\"people\".\"id\" BETWEEN 1 AND 4)\n)\n  SELECT \"people\".*\n  FROM (( (\n    SELECT dup_cte.id\n    FROM dup_cte\n  ) UNION (\n    SELECT unique_cte.id\n    FROM unique_cte\n  ) )) people\n```\n\n\n### JSON Query Methods\nIf any or all of your json sub-queries include a CTE, read the [Subquery CTE Gotchas](#subquery-cte-gotchas) warnings.\n\n#### Row To JSON\n[Postgres 'ROW_TO_JSON' function](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-CREATION-TABLE)\n\nThe implementation of the`.select_row_to_json/2` method is designed to be used with sub-queries. As a means for taking complex\nquery logic and transform them into a single or multiple json responses. These responses are required to be assigned\nto an aliased column on the parent(callee) level.\n\nWhile quite the mouthful of an explanation. The implementation of combining unrelated or semi-related queries is quite smooth(imo).\n\n**Arguments:**\n  - `from` [String, Arel, or ActiveRecord::Relation]: A subquery that can be nested into a `ROW_TO_JSON` clause\n\n**Options:**\n  - `as` [Symbol or String] (default=\"results\"): What the column will be aliased to\n  - `key` [Symbol or String] (default=[random letter]): Internal query alias name.\n    * This is useful if you would like to add additional mid-level predicate clauses\n  - `cast_with` [Symbol or Array\\\u003cSymbol\u003e]:\n    * `:to_jsonb`\n    * `:array`\n    * `:array_agg`\n    * `:distinct`  (auto applies `:array_agg` \u0026 `:to_jsonb`)\n  - `order_by` [Symbol or Hash]: Applies an ordering operation (similar to ActiveRecord #order)\n    * **Note**: this option will be ignored if you need to order a DISTINCT Aggregated Array.\n\n```ruby\n    physical_cat  = Category.create!(name: \"Physical\")\n    products      = 3.times.map { Product.create! }\n    products.each { |product|  100.times { Variant.create!(product: product, category: physical_cat) } }\n\n    # Since we plan to nest this query, you have access top level information. (I.E categories table)\n    item_query = Variant.select(:name, :id, :category_id, :product_id).where(\"categories.id = variants.category_id\")\n\n    # You can provide addition scopes that will be applied to the nested query (but will not effect the actual inner query)\n    # This is ideal if you are dealing with but not limited to, CTE's being applied multiple times and require additional constraints\n    product_query  =\n    Product.select(:id)\n            .joins(:items)\n            .select_row_to_json(item_query, key: :outer_items, as: :items, cast_with: :array) do |item_scope|\n              item_scope.where(\"outer_items.product_id = products.id\")\n                # Results to:\n                #  SELECT ..., ARRAY(SELECT ROW_TO_JSON(\"outer_items\")\n                #   FROM ([:item_query:]) outer_items\n                #   WHERE outer_items.product_id = products.id\n                # ) AS items\n            end\n\n    # Not defining a key will automatically generate a random key between a-z\n    category_query = Category.select(:name, :id).select_row_to_json(product_query, as: :products, cast_with: :array)\n    Category.json_build_object(:physical_category, category_query.where(id: physical_cat.id)).results\n    #=\u003e {\n    #        \"physical_category\" =\u003e {\n    #            \"name\" =\u003e \"Physical\",\n    #            \"id\" =\u003e 1,\n    #            \"products\" =\u003e [\n    #              {\n    #                \"id\" =\u003e 2,\n    #                \"items\" =\u003e [{\"name\" =\u003e \"Bojangels\", \"id\" =\u003e 3, \"category_id\" =\u003e 1, \"product_id\" =\u003e 2}, ...]\n    #              },\n    #              ...\n    #            ]\n    #        }\n    #  }\n    #\n```\n\nQuery Output\n```sql\nSELECT (JSON_BUILD_OBJECT('physical_category', \"physical_category\")) AS \"results\"\nFROM (\n     SELECT \"categories\".\"name\", \"categories\".\"id\", (ARRAY(\n         SELECT ROW_TO_JSON(\"j\")\n         FROM (\n              SELECT \"products\".\"id\", (ARRAY(\n                  SELECT ROW_TO_JSON(\"outer_item\")\n                  FROM (\n                       SELECT \"variants\".\"name\", \"variants\".\"id\", \"variants\".\"category_id\", \"variants\".\"product_id\"\n                       FROM \"variants\"\n                       WHERE (categories.id = variants.category_id)\n                       ) outer_items\n                  WHERE (outer_items.product_id = products.id)\n                )) AS \"items\"\n              FROM \"products\"\n              INNER JOIN \"items\" ON \"products\".\"id\" = \"items\".\"product_id\"\n              ) j\n       )) AS \"products\"\n     FROM \"categories\"\n     WHERE \"categories\".\"id\" = 1\n     ) AS \"physical_category\"\n```\n\n\n#### JSON/B Build Object\n[Postgres 'json(b)_build_object' function](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-CREATION-TABLE)\n\nThe implementation of the`.json_build_object/2` and `.jsonb_build_object/2` methods are designed to be used with sub-queries.\nAs a means for taking complex  query logic and transform them into a single or multiple json responses.\n\n**Arguments:**\n  - `key`: [Symbol or String]: What should this response return as\n  - `from`: [String, Arel, or ActiveRecord::Relation] : A subquery that can be nested into the top-level from clause\n\n**Options:**\n   - `as`: [Symbol or String] (defaults to `\"results\"`): What the column will be aliased to\n   - `value`: [Symbol or String] (defaults to `key` argument): How the response should handel the json value return\n\nSee the included example on [Row To JSON](#row-to-json) to see it in action.\n\n#### JSON/B Build Literal\n[Postgres 'json(b)_build_object' function](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-CREATION-TABLE)\n\nThe implementation of the`.json_build_literal/1` and `.jsonb_build_literal/1` is designed for creating static json objects\n that don't require subquery interfacing.\n\n**Arguments:**\n - Requires an Array or Hash set of values\n\n**Options:**\n - `as`: [Symbol or String] (defaults to `\"results\"`): What the column will be aliased to\n\n```ruby\n    User.json_build_literal(number: 1, last_name: \"json\", pi: 3.14).take.results\n     #=\u003e { \"number\" =\u003e 1, \"last_name\" =\u003e \"json\", \"pi\" =\u003e 3.14 }\n\n    # Or as array elements\n    User.json_build_literal(:number, 1, :last_name, \"json\", :pi, 3.14).take.results\n      #=\u003e { \"number\" =\u003e 1, \"last_name\" =\u003e \"json\", \"pi\" =\u003e 3.14 }\n\n```\n\nQuery Output\n```sql\nSELECT (JSON_BUILD_OBJECT('number', 1, 'last_name', 'json', 'pi', 3.14)) AS \"results\"\n  FROM \"users\"\n```\n\n\n### Unionization\nIf any or all of your union queries include a CTE, read the [Subquery CTE Gotchas](#subquery-cte-gotchas) warnings.\n\n#### SQL-Query Helpers\n - `.to_union_sql` : Will return a string of the constructed union query without being nested in the `from` clause.\n - `.to_nice_union_sql`(requires [NiceQL Gem](https://github.com/alekseyl/niceql) to be install): A formatted `.to_union_sql`\n\n\n#### Known issue\nThere's an issue with providing a single union clause and chaining it with a different union clause.\nThis is due to requirements of grouping SQL statements. The issue is being working on, but with no ETA.\n\nThis issue only applies to the first initial set of unions and is recommended that you union two relations right off the bat.\nAfterwords you can union/chain single relations.\n\nExample\n\n```ruby\n\nPerson.union(Person.where(id: 1..4)).union_except(Person.where(id: 3..4)).union(Person.where(id: 4))\n#=\u003e Will include all people with an ID between 1 \u0026 3 (throwing the except on ID 4)\n\n# This can be fixed by doing something like\n\nPerson.union_except(Person.where(id: 1..4), Person.where(id: 3..4)).union(Person.where(id: 4))\n#=\u003e Will include people with the ids of 1, 2, and 4 (properly excluding the user with the ID of 3)\n```\n\nProblem Query Output\n```sql\n( ( (\n  SELECT \"people\".*\n  FROM \"people\"\n  WHERE \"people\".\"id\" BETWEEN 1 AND 4\n) UNION (\n  SELECT \"people\".*\n  FROM \"people\"\n  WHERE \"people\".\"id\" BETWEEN 3 AND 4\n) ) EXCEPT (\n  SELECT \"people\".*\n  FROM \"people\"\n  WHERE \"people\".\"id\" = 4\n) )\n```\n\n\n#### Union\n[Postgres 'UNION' combination](https://www.postgresql.org/docs/current/queries-union.html)\n\n```ruby\nuser_1 = Person.where(id: 1)\nuser_2 = Person.where(id: 2)\nusers  = Person.where(id: 1..3)\n\nPerson.union(user_1, user_2, users) #=\u003e [#\u003cPerson id: 1, ..\u003e, #\u003cPerson id: 2,..\u003e, #\u003cPerson id: 3,..\u003e]\n\n# You can also chain union's\nPerson.union(user_1).union(user_2).union(users)\n```\n\nQuery Output\n```sql\nSELECT \"people\".*\n  FROM (( ( (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" = 1\n  ) UNION (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" = 2\n  ) ) UNION (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" BETWEEN 1 AND 3\n  ) )) people\n```\n\n\n#### Union ALL\n[Postgres 'UNION ALL' combination](https://www.postgresql.org/docs/current/queries-union.html)\n\n```ruby\nuser_1 = Person.where(id: 1)\nuser_2 = Person.where(id: 2)\nusers  = Person.where(id: 1..3)\n\nPerson.union_all(user_1, user_2, users)\n  #=\u003e [#\u003cPerson id: 1, ..\u003e, #\u003cPerson id: 2,..\u003e, #\u003cPerson id: 1, ..\u003e, #\u003cPerson id: 2,..\u003e, #\u003cPerson id: 3,..\u003e]\n\n# You can also chain union's\nPerson.union_all(user_1).union_all(user_2).union_all(users)\n# Or\nPerson.union.all(user1, user_2).union.all(users)\n```\n\nQuery Output\n```sql\nSELECT \"people\".*\n  FROM (( ( (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" = 1\n  ) UNION ALL (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" = 2\n  ) ) UNION ALL (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" BETWEEN 1 AND 3\n  ) )) people\n```\n\n#### Union Except\n[Postgres 'EXCEPT' combination](https://www.postgresql.org/docs/current/queries-union.html)\n\n```ruby\nusers               = Person.where(id: 1..5)\nexcept_these_users  = Person.where(id: 2..4)\n\nPerson.union_except(users, except_these_users) #=\u003e [#\u003cPerson id: 1, ..\u003e, #\u003cPerson id: 5,..\u003e]\n\n# You can also chain union's\nPerson.union.except(users, except_these_users).union(Person.where(id: 20))\n```\n\nQuery Output\n```sql\nSELECT \"people\".*\n  FROM (( ( (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" BETWEEN 1 AND 5\n  ) EXCEPT (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" BETWEEN 2 AND 4\n  )) people\n```\n\n#### Union Intersect\n[Postgres 'INTERSECT' combination](https://www.postgresql.org/docs/current/queries-union.html)\n\n```ruby\nrandy = Person.create!\nalice = Person.create!\nProfileL.create!(person: randy, likes: 100)\nProfileL.create!(person: alice, likes: 120)\n\nlikes_100           = Person.select(:id, \"profile_ls.likes\").joins(:profile_l).where(profile_ls: { likes: 100 })\nlikes_less_than_150 = Person.select(:id, \"profile_ls.likes\").joins(:profile_l).where(\"profile_ls.likes \u003c 150\")\nPerson.union_intersect(likes_100, likes_less_than_150) #=\u003e [randy]\n\n\n\n# You can also chain union's\nPerson.union_intersect(likes_100).union_intersect(likes_less_than_150) #=\u003e [randy]\n# Or\nPerson.union.intersect(likes_100, likes_less_than_150) #=\u003e [randy]\n\n```\n\nQuery Output\n```sql\nSELECT \"people\".*\n  FROM (( (\n    SELECT \"people\".\"id\", profile_ls.likes\n    FROM \"people\"\n    INNER JOIN \"profile_ls\" ON \"profile_ls\".\"person_id\" = \"people\".\"id\"\n    WHERE \"profile_ls\".\"likes\" = 100\n  ) INTERSECT (\n    SELECT \"people\".\"id\", profile_ls.likes\n    FROM \"people\"\n    INNER JOIN \"profile_ls\" ON \"profile_ls\".\"person_id\" = \"people\".\"id\"\n    WHERE (profile_ls.likes \u003c 150)\n  ) )) people\n```\n\n#### Union As\n\nBy default unions are nested in the from clause and are aliased to the parents table name.\nWe can change this behavior by chaining the method `.union_as/1`\n\n```ruby\nPerson.select(\"good_people.id\").union(Person.where(id: 1), Person.where(id: 2)).union_as(:good_people)\n```\n\nQuery Output\n```sql\nSELECT good_people.id\n  FROM (( (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" = 1\n  ) UNION (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" = 2\n  ) )) good_people\n```\n\n\n#### Union Order\n\nUnions allow for a final outside `ORDER BY` clause. This will ensure that all the results that come back are ordered in an expected return.\n\n```ruby\nquery_1 = Person.where(id: 1..3)\nquery_2 = Person.where(id: 3)\nquery_3 = Person.where(id: 3..10)\nPerson.union_except(query_1, query_2).union(query_3).order_union(:id, tags: :desc)\n```\n\nQuery Output\n```sql\nSELECT \"people\".*\n  FROM (( ( (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" BETWEEN 1 AND 3\n  ) EXCEPT (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" = 3\n  ) ) UNION (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" BETWEEN 3 AND 10\n  ) ) ORDER BY id ASC, tags DESC) people\n```\n\n#### Union Reorder\n\nmuch like Rails `.reorder`; `.reorder_union/1`  will clear the previous order in a new instance and/or apply a new ordering scheme\n```ruby\nquery_1     = Person.where(id: 1..3)\nquery_2     = Person.where(id: 3)\nquery_3     = Person.where(id: 3..10)\nunion_query = Person.union_except(query_1, query_2).union(query_3).order_union(:id, tags: :desc)\nunion_query.reorder_union(personal_id: :desc, id: :desc)\n```\n\nQuery Output\n```sql\nSELECT \"people\".*\n  FROM (( ( (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" BETWEEN 1 AND 3\n  ) EXCEPT (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" = 3\n  ) ) UNION (\n    SELECT \"people\".*\n    FROM \"people\"\n    WHERE \"people\".\"id\" BETWEEN 3 AND 10\n  ) ) ORDER BY personal_id DESC, id DESC) people\n```\n\n#### Window Functions\n[Postgres Window Functions](https://www.postgresql.org/docs/current/tutorial-window.html)\n\nLet's address the elephant in the room. Arel has had, for a long time now, window function capabilities;\nHowever they've never seen the lime light in ActiveRecord's query logic.\nThe following brings the dormant Arel methods up to the ActiveRecord Querying level.\n\n#### Define Window\n\nTo set up a window function, we first must establish the window and we do this by using the `.define_window/1` method.\nThis method also requires you to call chain `.partition_by/2`\n\n`.define_window/1` - Establishes the name of the window you'll reference later on in [.select_window](#select-window)\n   - Aliased name of window\n\n`.partition_by/2`  - Establishes the windows operations a [pre-defined window function](https://www.postgresql.org/docs/current/functions-window.html) will leverage.\n   - column name being partitioned against\n   - (**optional**) `order_by`: Processes how the window should be ordered\n\n```ruby\nUser\n.define_window(:number_window).partition_by(:number, order_by: { id: :desc })\n.define_window(:name_window).partition_by(:name, order_by: :id)\n.define_window(:no_order_name).partition_by(:name)\n```\n\nQuery Output\n```sql\nSELECT *\nFROM users\nWINDOW number_window AS (PARTITION BY number ORDER BY id DESC),\n       name_window   AS (PARTITION BY name ORDER BY id),\n       no_order_name AS (PARTITION BY name)\n```\n\n#### Select Window\n\nOnce you've define a window, the next step to to utilize it on one of the many provided postgres window functions.\n\n`.select_window/3`\n  - [window function name](https://www.postgresql.org/docs/current/functions-window.html)\n  - (**optional**) Window function arguments (treated as a splatted array)\n  - (**optional**) `as:` : Alias name of the final result\n  - `over:` : name of [defined window](#define-window)\n\n```ruby\nUser.create!(name: \"Alice\", number: 100) #=\u003e id: 1\nUser.create!(name: \"Randy\", number: 100) #=\u003e id: 2\nUser.create!(name: \"Bob\", number: 300)   #=\u003e id: 3\n\nUser\n.define_window(:number_window).partition_by(:number, order_by: { id: :desc })\n.select(:id, :name)\n.select_window(:row_number, over: :number_window, as: :row_id)\n.select_window(:first_value, :name, over: :number_window, as: :first_value_name)\n#=\u003e [\n #  { id: 1, name: \"Alice\", row_id: 2, first_value_name: \"Randy\" }\n #  { id: 2, name: \"Randy\", row_id: 1, first_value_name: \"Randy\" }\n #  { id: 3, name: \"Bob\",   row_id: 1, first_value_name: \"Bob\" }\n # ]\n #\n\n```\n\nQuery Output\n```sql\nSELECT \"users\".\"id\",\n        \"users\".\"name\",\n        (ROW_NUMBER() OVER number_window)      AS \"row_id\",\n        (FIRST_VALUE(name) OVER number_window) AS \"first_value_name\"\nFROM \"users\"\nWINDOW number_window AS (PARTITION BY number ORDER BY id DESC)\n```\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeorgekaraszi%2Factiverecordextended","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeorgekaraszi%2Factiverecordextended","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeorgekaraszi%2Factiverecordextended/lists"}