{"id":13665701,"url":"https://github.com/brianhempel/active_record_union","last_synced_at":"2025-05-14T03:10:44.270Z","repository":{"id":18810778,"uuid":"22025328","full_name":"brianhempel/active_record_union","owner":"brianhempel","description":"UNIONs in ActiveRecord! Adds proper union and union_all methods to ActiveRecord::Relation.","archived":false,"fork":false,"pushed_at":"2024-11-10T16:42:14.000Z","size":56,"stargazers_count":472,"open_issues_count":13,"forks_count":46,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-05-13T00:12:56.500Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brianhempel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-07-20T03:46:12.000Z","updated_at":"2025-05-04T18:01:00.000Z","dependencies_parsed_at":"2024-12-15T23:11:03.085Z","dependency_job_id":null,"html_url":"https://github.com/brianhempel/active_record_union","commit_stats":{"total_commits":71,"total_committers":8,"mean_commits":8.875,"dds":"0.16901408450704225","last_synced_commit":"8ebe558709aabe039abd24e3e7dd4d4354a6de88"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianhempel%2Factive_record_union","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianhempel%2Factive_record_union/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianhempel%2Factive_record_union/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianhempel%2Factive_record_union/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brianhempel","download_url":"https://codeload.github.com/brianhempel/active_record_union/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254059512,"owners_count":22007769,"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":[],"created_at":"2024-08-02T06:00:48.021Z","updated_at":"2025-05-14T03:10:39.244Z","avatar_url":"https://github.com/brianhempel.png","language":"Ruby","readme":"# ActiveRecordUnion\n\n[![Gem Version](https://badge.fury.io/rb/active_record_union.svg)](http://badge.fury.io/rb/active_record_union)\n[![Build Status](https://travis-ci.org/brianhempel/active_record_union.svg)](https://travis-ci.org/brianhempel/active_record_union)\n\nUse unions on ActiveRecord scopes without ugliness.\n\nIf you find yourself writing `pluck(:id)` and then feeding that into another query, you may be able to reduce the number of database requests by using a nested query or a UNION without writing crazy JOIN statements.\n\nQuick usage examples:\n\n```ruby\ncurrent_user.posts.union(Post.published)\ncurrent_user.posts.union(Post.published).where(id: [6, 7])\ncurrent_user.posts.union(\"published_at \u003c ?\", Time.now)\nuser_1.posts.union(user_2.posts).union(Post.published)\nuser_1.posts.union_all(user_2.posts)\n```\n\nActiveRecordUnion is tested against Rails 6.0, 6.1, 7.0, 7.1, 7.2 and 8.0.\n\nIf you are using Postgres, you might alternatively check out [ActiveRecordExtended](https://github.com/georgekaraszi/ActiveRecordExtended) which includes support for unions as well as other goodies.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'active_record_union'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install active_record_union\n\n## Usage\n\nActiveRecordUnion adds `union` and `union_all` methods to `ActiveRecord::Relation` so we can easily gather together queries on mutiple scopes.\n\nConsider some users with posts:\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  has_many :posts\nend\n\nclass Post \u003c ActiveRecord::Base\n  belongs_to :user\n\n  scope :published, -\u003e { where(\"published_at \u003c ?\", Time.now) }\nend\n```\n\nWith ActiveRecordUnion, we can do:\n\n```ruby\n# the current user's (draft) posts and all published posts from anyone\ncurrent_user.posts.union(Post.published)\n```\n\nWhich is equivalent to the following SQL:\n\n```sql\nSELECT \"posts\".* FROM (\n  SELECT \"posts\".* FROM \"posts\"  WHERE \"posts\".\"user_id\" = 1\n  UNION\n  SELECT \"posts\".* FROM \"posts\"  WHERE (published_at \u003c '2014-07-19 16:04:21.918366')\n) \"posts\"\n```\n\nBecause the `union` method returns another `ActiveRecord::Relation`, we can run further queries on the union.\n\n```ruby\ncurrent_user.posts.union(Post.published).where(id: [6, 7])\n```\n```sql\nSELECT \"posts\".* FROM (\n  SELECT \"posts\".* FROM \"posts\"  WHERE \"posts\".\"user_id\" = 1\n  UNION\n  SELECT \"posts\".* FROM \"posts\"  WHERE (published_at \u003c '2014-07-19 16:06:04.460771')\n) \"posts\"  WHERE \"posts\".\"id\" IN (6, 7)\n```\n\nThe `union` method can also accept anything that `where` does.\n\n```ruby\ncurrent_user.posts.union(\"published_at \u003c ?\", Time.now)\n# equivalent to...\ncurrent_user.posts.union(Post.where(\"published_at \u003c ?\", Time.now))\n```\n\nWe can also chain `union` calls to UNION more than two scopes, though the UNIONs will be nested which may not be the prettiest SQL.\n\n```ruby\nuser_1.posts.union(user_2.posts).union(Post.published)\n# equivalent to...\n[user_1.posts, user_2.posts, Post.published].inject(:union)\n```\n```sql\nSELECT \"posts\".* FROM (\n  SELECT \"posts\".* FROM (\n    SELECT \"posts\".* FROM \"posts\"  WHERE \"posts\".\"user_id\" = 1\n    UNION\n    SELECT \"posts\".* FROM \"posts\"  WHERE \"posts\".\"user_id\" = 2\n  ) \"posts\"\n  UNION\n  SELECT \"posts\".* FROM \"posts\"  WHERE (published_at \u003c '2014-07-19 16:12:45.882648')\n) \"posts\"\n```\n\n### UNION ALL\n\nBy default, UNION will remove any duplicates from the result set. If you don't care about duplicates or you know that the two queries you are combining will not have duplicates, you call use UNION ALL to tell the database to skip its deduplication step. In some cases this can give significant performance improvements.\n\n```ruby\nuser_1.posts.union_all(user_2.posts)\n```\n```sql\nSELECT \"posts\".* FROM (\n  SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1\n  UNION ALL\n  SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 2\n) \"posts\"\n```\n\n## Caveats\n\nThere's a couple things to be aware of when using ActiveRecordUnion:\n\n1. ActiveRecordUnion will raise an error if you try to UNION any relations that do any preloading/eager-loading. There's no sensible way to do the preloading in the subselects. If enough people complain, maybe, we can change ActiveRecordUnion to let the queries run anyway but without preloading any records.\n2. There's no easy way to get SQLite to allow ORDER BY in the UNION subselects. If you get a syntax error, you can either write `my_relation.reorder(nil).union(other.reorder(nil))` or switch to Postgres.\n\n## Another nifty way to reduce extra queries\n\nActiveRecord already supports turning scopes into nested queries in WHERE clauses. The nested relation defaults to selecting `id` by default.\n\nFor example, if a user `has_and_belongs_to_many :favorited_posts`, we can quickly find which of the current user's posts are liked by a certain other user.\n\n```ruby\ncurrent_user.posts.where(id: other_user.favorited_posts)\n```\n```sql\nSELECT \"posts\".* FROM \"posts\"\n  WHERE \"posts\".\"user_id\" = 1\n  AND \"posts\".\"id\" IN (\n    SELECT \"posts\".\"id\"\n      FROM \"posts\" INNER JOIN \"user_favorited_posts\" ON \"posts\".\"id\" = \"user_favorited_posts\".\"post_id\"\n      WHERE \"user_favorited_posts\".\"user_id\" = 2\n  )\n```\n\nIf we want to select something other than `id`, we use `select` to specify. The following is equivalent to the above, but the query is done against the join table.\n\n```ruby\ncurrent_user.posts.where(id: UserFavoritedPost.where(user_id: other_user.id).select(:post_id))\n```\n```sql\nSELECT \"posts\".* FROM \"posts\"\n  WHERE \"posts\".\"user_id\" = 1\n  AND \"posts\".\"id\" IN (\n    SELECT \"user_favorited_posts\".\"post_id\"\n      FROM \"user_favorited_posts\"\n      WHERE \"user_favorited_posts\".\"user_id\" = 2\n  )\n```\n\n(The above example is illustrative only. It might be better with a JOIN.)\n\n## State of the Union in ActiveRecord\n\nWhy does this gem exist?\n\nRight now in ActiveRecord, if we call `scope.union` we get an `Arel::Nodes::Union` object instead of an `ActiveRecord::Relation`.\n\nWe could call `to_sql` on the Arel object and then use `find_by_sql`, but that's not super clean. Also, on Rails 4.0 and 4.1 if the original scopes included an association then the `to_sql` may produce a query with values that need to be bound (represented by `?`s in the SQL) and we have to provide those ourselves. (E.g. `user.posts.to_sql` produces `SELECT \"posts\".* FROM \"posts\"  WHERE \"posts\".\"user_id\" = ?`.) Rails 4.2's `to_sql` replaces the bind values before showing the SQL string and thus can more readily be used with `find_by_sql`. (E.g. Rails 4.2 `to_sql` would say `WHERE \"posts\".\"user_id\" = 1` instead of `WHERE \"posts\".\"user_id\" = ?`.)\n\nWhile ActiveRecord may eventually have the ability to cleanly perform UNIONs, it's currently stalled. If you're interested, the relevant URLs as of July 2014 are:\n\nhttps://github.com/rails/rails/issues/939 and\nhttps://github.com/rails/arel/pull/239 and\nhttps://github.com/yakaz/rails/commit/29b8ebd187e0888d5e71b2e1e4a12334860bc76c\n\nThis is a gem not a Rails pull request because the standard of code quality for a PR is a bit higher, and we'd have to wait for the PR to be merged and relased to use UNIONs. That said, the code here is fairly clean and it may end up in a PR sometime.\n\n## Changelog\n\n**1.3.0** - January 14, 2018\n  - Ready for Rails 5.2! Updates provided by [@glebm](https://github.com/glebm).\n\n**1.2.0** - June 26, 2016\n  - Ready for Rails 5.0! Updates provided by [@glebm](https://github.com/glebm).\n\n**1.1.1** - Mar 19, 2016\n  - Fix broken polymorphic associations and joins due to improper handling of bind values. Fix by [@efradelos](https://github.com/efradelos), reported by [@Machiaweliczny](https://github.com/Machiaweliczny) and [@seandougall](https://github.com/seandougall).\n  - Quote table name aliases properly. Reported by [@odedniv](https://github.com/odedniv).\n\n**1.1.0** - Mar 29, 2015 - Add UNION ALL support, courtesy of [@pic](https://github.com/pic).\n\n**1.0.1** - Sept 2, 2014 - Allow ORDER BY in UNION subselects for databases that support it (not SQLite).\n\n**1.0.0** - July 24, 2014 - Initial release.\n\n## License\n\nActiveRecordUnion is dedicated to the public domain by its author, Brian Hempel. No rights are reserved. No restrictions are placed on the use of ActiveRecordUnion. That freedom also means, of course, that no warrenty of fitness is claimed; use ActiveRecordUnion at your own risk.\n\nThis public domain dedication follows the the CC0 1.0 at https://creativecommons.org/publicdomain/zero/1.0/\n\n## Contributing\n\n1. Fork it ( https://github.com/brianhempel/active_record_union/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Run the tests:\n  1. Install MySQL and PostgreSQL.\n  2. You need to be able to connect to a local MySQL and Postgres database as the default user, so the specs can create a `test_active_record_union` database. To set up the users this test expects, execute `bin/create-db-users` (or set the environment variables referenced in `spec/support/databases.rb`).\n  3. Run `rake` to test with all supported Rails versions. All needed dependencies will be installed via Bundler (`gem install bundler` if you happen not to have Bundler yet).\n  4. Run `rake test_rails_8_0` or `rake test_rails_7_2` etc. to test a specific Rails version.\n4. There is also a `bin/console` command to load up a REPL for playing around\n5. Commit your changes (`git commit -am 'Add some feature'`)\n6. Push to the branch (`git push origin my-new-feature`)\n7. Create a new Pull Request\n","funding_links":[],"categories":["Ruby","1. language"],"sub_categories":["1.1 ruby"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianhempel%2Factive_record_union","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrianhempel%2Factive_record_union","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianhempel%2Factive_record_union/lists"}