{"id":13850606,"url":"https://github.com/activerecord-hackery/meta_where","last_synced_at":"2025-07-12T22:31:11.932Z","repository":{"id":871625,"uuid":"611970","full_name":"activerecord-hackery/meta_where","owner":"activerecord-hackery","description":"ActiveRecord 3 query syntax on steroids. Not currently maintained.","archived":true,"fork":false,"pushed_at":"2022-03-29T17:17:12.000Z","size":345,"stargazers_count":591,"open_issues_count":10,"forks_count":34,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-07-04T08:42:09.125Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://erniemiller.org/2013/11/17/anyone-interested-in-activerecord-hackery/","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/activerecord-hackery.png","metadata":{"files":{"readme":"README.rdoc","changelog":"CHANGELOG","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2010-04-15T14:59:04.000Z","updated_at":"2025-02-23T12:22:33.000Z","dependencies_parsed_at":"2022-07-05T19:01:30.319Z","dependency_job_id":null,"html_url":"https://github.com/activerecord-hackery/meta_where","commit_stats":null,"previous_names":["ernie/meta_where"],"tags_count":26,"template":false,"template_full_name":null,"purl":"pkg:github/activerecord-hackery/meta_where","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activerecord-hackery%2Fmeta_where","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activerecord-hackery%2Fmeta_where/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activerecord-hackery%2Fmeta_where/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activerecord-hackery%2Fmeta_where/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/activerecord-hackery","download_url":"https://codeload.github.com/activerecord-hackery/meta_where/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activerecord-hackery%2Fmeta_where/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265066119,"owners_count":23706062,"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-04T20:01:20.184Z","updated_at":"2025-07-12T22:31:11.692Z","avatar_url":"https://github.com/activerecord-hackery.png","language":"Ruby","readme":"# Archived\n\nThis project is archived\n\n= MetaWhere\n\nMetaWhere puts the power of Arel predications (comparison methods) in your ActiveRecord\ncondition hashes.\n\n= Using Rails 3.1?\n\nYou want to take a look at {Squeel}[http://github.com/ernie/squeel] instead.\n\nThere were extensive changes to ActiveRecord between Rails 3.0 and 3.1, and *MetaWhere\nwill not be updated to support 3.1.*\n\n== Why?\n\n\u003cb\u003eI hate SQL fragments in Rails code.\u003c/b\u003e Resorting to \u003ctt\u003ewhere('name LIKE ?', '%something%')\u003c/tt\u003e is an admission of defeat. It says, \"I concede to allow your rigid, 1970's-era syntax into my elegant Ruby world of object oriented goodness.\" While sometimes such concessions are necessary, they should \u003cem\u003ealways\u003c/em\u003e be a last resort, because \u003cb\u003eonce you move away from an abstract representation of your intended query, your query becomes more brittle.\u003c/b\u003e You're now reduced to hacking about with regular expressions, string scans, and the occasional deferred variable interpolation trick (like '#{quoted_table_name}') in order to maintain some semblance of flexibility.\n\nIt isn't that I hate SQL (much). I'm perfectly capable of constructing complex queries from scratch, and did more than my fair share before coming to the Rails world. It's that I hate the juxtaposition of SQL against Ruby. It's like seeing your arthritic grandfather hand in hand with some hot, flexible, yoga instructor. Good for him, but sooner or later something's going to get broken.  It's like a sentence which, tanpa alasan, perubahan ke bahasa lain, then back again (\"for no reason, changes to another language\" -- with thanks to Google Translate, and apologies to native speakers of Indonesian). It just feels \u003cem\u003ewrong\u003c/em\u003e. It breaks the spell -- the \"magic\" that adds to programmer joy, and \u003cem\u003efor no good reason\u003c/em\u003e.\n\nMetaWhere is a gem that sets out to right that wrong, and give tranquility to you, the Rails coder.\n\n== Getting started\n\nIn your Gemfile:\n\n  gem \"meta_where\"  # Last officially released gem\n  # gem \"meta_where\", :git =\u003e \"git://github.com/ernie/meta_where.git\" # Track git repo\n\nor, to install as a plugin:\n\n  rails plugin install git://github.com/ernie/meta_where.git\n\n== Example usage\n\n=== Where\nYou can use MetaWhere in your usual method chain:\n\n  Article.where(:title.matches =\u003e 'Hello%', :created_at.gt =\u003e 3.days.ago)\n  =\u003e SELECT \"articles\".* FROM \"articles\" WHERE (\"articles\".\"title\" LIKE 'Hello%')\n     AND (\"articles\".\"created_at\" \u003e '2010-04-12 18:39:32.592087')\n\n=== Find condition hash\nYou can also use similar syntax in a conditions hash supplied to ActiveRecord::Base#find:\n\n  Article.find(:all,\n    :conditions =\u003e {\n      :title.matches =\u003e 'Hello%',\n      :created_at.gt =\u003e 3.days.ago\n    }\n  )\n\n=== Scopes\nThey also work in named scopes as you would expect.\n\n  class Article\n    scope :recent, lambda {|v| where(:created_at.gt =\u003e v.days.ago)}\n  end\n\n  Article.recent(14).to_sql\n  =\u003e SELECT \"articles\".* FROM \"articles\"\n     WHERE (\"articles\".\"created_at\" \u003e '2010-04-01 18:54:37.030951')\n\n=== Operators (Optionally)\nAdditionally, you can use certain operators as shorthand for certain Arel predication methods.\n\nThese are disabled by default, but can be enabled by calling MetaWhere.operator_overload! during\nyour app's initialization process.\n\nThese are experimental at this point and subject to change. Keep in mind that if you don't want\nto enclose other conditions in {}, you should place operator conditions before any hash conditions.\n\n  Article.where(:created_at \u003e 100.days.ago, :title =~ 'Hi%').to_sql\n  =\u003e SELECT \"articles\".* FROM \"articles\"\n     WHERE (\"articles\".\"created_at\" \u003e '2010-01-05 20:11:44.997446')\n     AND (\"articles\".\"title\" LIKE 'Hi%')\n\nOperators are:\n\n* \u003e\u003e (equal)\n* ^ (not equal)\n* + (in array/range)\n* - (not in array/range)\n* =~ (matching -- not a regexp but a string for SQL LIKE) \u003cb\u003eNOTE:\u003c/b\u003e This will override 1.9.x \"symbol as string\" =~ behavior.\n* !~ (not matching, only available under Ruby 1.9)\n* \u003e (greater than)\n* \u003e= (greater than or equal to)\n* \u003c (less than)\n* \u003c= (less than or equal to)\n* [] (SQL functions -- more on those below)\n\n=== Compounds\nYou can use the \u0026 and | operators to perform ands and ors within your queries.\n\n\u003cb\u003eWith operators:\u003c/b\u003e\n  Article.where((:title =~ 'Hello%') | (:title =~ 'Goodbye%')).to_sql\n  =\u003e SELECT \"articles\".* FROM \"articles\" WHERE ((\"articles\".\"title\" LIKE 'Hello%'\n     OR \"articles\".\"title\" LIKE 'Goodbye%'))\n\nThat's kind of annoying, since operator precedence is such that you have to put\nparentheses around everything. So MetaWhere also supports a substitution-inspired\n(String#%) syntax.\n\n\u003cb\u003eWith \"substitutions\":\u003c/b\u003e\n  Article.where(:title.matches % 'Hello%' | :title.matches % 'Goodbye%').to_sql\n  =\u003e SELECT \"articles\".* FROM \"articles\" WHERE ((\"articles\".\"title\" LIKE 'Hello%'\n     OR \"articles\".\"title\" LIKE 'Goodbye%'))\n\n\u003cb\u003eWith hashes:\u003c/b\u003e\n  Article.where(\n    {:created_at.lt =\u003e Time.now} \u0026 {:created_at.gt =\u003e 1.year.ago}\n  ).to_sql\n  =\u003e SELECT \"articles\".* FROM \"articles\" WHERE\n     (((\"articles\".\"created_at\" \u003c '2010-04-16 00:26:30.629467')\n     AND (\"articles\".\"created_at\" \u003e '2009-04-16 00:26:30.629526')))\n\n\u003cb\u003eWith both hashes and substitutions:\u003c/b\u003e\n  Article.where(\n    :title.matches % 'Hello%' \u0026\n    {:created_at.lt =\u003e Time.now, :created_at.gt =\u003e 1.year.ago}\n  ).to_sql\n  =\u003e SELECT \"articles\".* FROM  \"articles\" WHERE ((\"articles\".\"title\" LIKE 'Hello%' AND\n     (\"articles\".\"created_at\" \u003c '2010-04-16 01:04:38.023615' AND\n      \"articles\".\"created_at\" \u003e '2009-04-16 01:04:38.023720')))\n\n\u003cb\u003eWith insanity... errr, complex combinations(*):\u003c/b\u003e\n\n  Article.joins(:comments).where(\n    {:title =\u003e 'Greetings'} |\n    (\n      (\n        :created_at.gt % 21.days.ago \u0026\n        :created_at.lt % 7.days.ago\n      ) \u0026\n      :body.matches % '%from the past%'\n    ) \u0026\n    {:comments =\u003e [:body =~ '%first post!%']}\n  ).to_sql\n  =\u003e SELECT \"articles\".*\n     FROM \"articles\"\n       INNER JOIN \"comments\"\n       ON \"comments\".\"article_id\" = \"articles\".\"id\"\n     WHERE\n     ((\n       \"articles\".\"title\" = 'Greetings'\n       OR\n       (\n         (\n           (\n             \"articles\".\"created_at\" \u003e '2010-03-26 05:57:57.924258'\n             AND \"articles\".\"created_at\" \u003c '2010-04-09 05:57:57.924984'\n           )\n           AND \"articles\".\"body\" LIKE '%from the past%'\n         )\n         AND \"comments\".\"body\" LIKE '%first post!%'\n       )\n     ))\n\n(*) Formatting added for clarity. I said you could do this, not that you should. :)\n\n== Join type specification\nYou can choose whether to use an inner join (the default) or a left outer join by tacking\n\u003ctt\u003e.outer\u003c/tt\u003e or \u003ctt\u003e.inner\u003c/tt\u003e to the symbols specified in your joins() call:\n\n  Article.joins(:comments =\u003e :moderations.outer).to_sql\n  =\u003e SELECT \"articles\".* FROM \"articles\"\n     INNER JOIN \"comments\" ON \"comments\".\"article_id\" = \"articles\".\"id\"\n     LEFT OUTER JOIN \"moderations\" ON \"moderations\".\"comment_id\" = \"comments\".\"id\"\n\n== SQL Functions\nYou can use SQL functions in your queries:\n\n  Manager.joins(:employees.outer).group('managers.id').\n          having(:employees =\u003e (:count.func(:id) \u003c 3))\n  =\u003e SELECT \"managers\".* FROM \"managers\"\n     LEFT OUTER JOIN \"employees\" ON \"employees\".\"manager_id\" = \"managers\".\"id\"\n     GROUP BY managers.id HAVING count(\"employees\".\"id\") \u003c 3\n\nIf you enable Symbol operators, you can just use \u003ctt\u003e:count[:id]\u003c/tt\u003e, instead of calling\n\u003ctt\u003efunc\u003c/tt\u003e as shown above. SQL functions work in the SELECT, WHERE, and HAVING clauses,\nand can be aliased with \u003ctt\u003eas\u003c/tt\u003e:\n\n  Manager.select('managers.*').\n          select(:find_in_set[:id, '3,2,1'].as('position'))\n  =\u003e SELECT managers.*, find_in_set(\"managers\".\"id\",'3,2,1') AS position\n     FROM \"managers\"\n\n=== But wait, there's more!\n\n== Intelligent hash condition mapping\nThis is one of those things I hope you find so intuitive that you forget it wasn't\nbuilt in already.\n\nPredicateBuilder (the part of ActiveRecord responsible for turning your conditions\nhash into a valid SQL query) will allow you to nest conditions in order to specify a\ntable that the conditions apply to:\n\n  Article.joins(:comments).where(:comments =\u003e {:body =\u003e 'hey'}).to_sql\n  =\u003e SELECT \"articles\".* FROM \"articles\" INNER JOIN \"comments\"\n     ON \"comments\".\"article_id\" = \"articles\".\"id\"\n     WHERE (\"comments\".\"body\" = 'hey')\n\nThis feels pretty magical at first, but the magic quickly breaks down. Consider an\nassociation named \u003ctt\u003e:other_comments\u003c/tt\u003e that is just a condition against comments:\n\n  Article.joins(:other_comments).where(:other_comments =\u003e {:body =\u003e 'hey'}).to_sql\n  =\u003e ActiveRecord::StatementInvalid: No attribute named `body` exists for table `other_comments`\n\nIck. This is because the query is being created against tables, and not against associations.\nYou'd need to do...\n\n  Article.joins(:other_comments).where(:comments =\u003e {:body =\u003e 'hey'})\n\n...instead.\n\nWith MetaWhere:\n\n  Article.joins(:other_comments).where(:other_comments =\u003e {:body =\u003e 'hey'}).to_sql\n  =\u003e SELECT \"articles\".* FROM \"articles\" INNER JOIN \"comments\"\n     ON \"comments\".\"article_id\" = \"articles\".\"id\" WHERE ((\"comments\".\"body\" = 'hey'))\n\nThe general idea is that if an association with the name provided exists, MetaWhere\nwill build the conditions against that association's table as it's been aliased, before falling\nback to assuming you're specifying a table by name. It also handles nested associations:\n\n  Article.where(\n    :comments =\u003e {\n      :body =\u003e 'yo',\n      :moderations =\u003e [:value \u003c 0]\n    },\n    :other_comments =\u003e {:body =\u003e 'hey'}\n  ).joins(\n    {:comments =\u003e :moderations},\n    :other_comments\n  ).to_sql\n  =\u003e SELECT \"articles\".* FROM \"articles\"\n     INNER JOIN \"comments\" ON \"comments\".\"article_id\" = \"articles\".\"id\"\n     INNER JOIN \"moderations\" ON \"moderations\".\"comment_id\" = \"comments\".\"id\"\n     INNER JOIN \"comments\" \"other_comments_articles\"\n       ON \"other_comments_articles\".\"article_id\" = \"articles\".\"id\"\n    WHERE ((\"comments\".\"body\" = 'yo' AND \"moderations\".\"value\" \u003c 0\n      AND \"other_comments_articles\".\"body\" = 'hey'))\n\nContrived example, I'll admit -- but I'll bet you can think of some uses for this.\n\n== Enhanced relation merges\n\nOne of the changes MetaWhere makes to ActiveRecord is to delay \"compiling\" the\nwhere_values into actual Arel predicates until absolutely necessary. This allows\nfor greater flexibility and last-second inference of associations/joins from any\nhashes supplied. A drawback of this method is that when merging relations, ActiveRecord\njust assumes that the values being merged are already firmed up against a specific table\nname and can just be thrown together. This isn't the case with MetaWhere, and would\ncause unexpected failures when merging. However, MetaWhere improves on the default\nActiveRecord merge functionality in two ways. First, when called with 1 parameter,\n(as is always the case when using the \u0026 alias) MetaWhere will try to determine if\nan association exists between the two models involved in the merge. If it does, the\nassociation name will be used to construct criteria.\n\nAdditionally, to cover times when detection is impossible, or the first detected\nassociation isn't the one you wanted, you can call merge with a second parameter,\nspecifying the association to be used during the merge.\n\nThis merge functionality allows you to do this...\n\n  (Comment.where(:id \u003c 7) \u0026 Article.where(:title =~ '%blah%')).to_sql\n  =\u003e SELECT \"comments\".* FROM \"comments\" INNER JOIN \"articles\"\n     ON \"articles\".\"id\" = \"comments\".\"article_id\"\n     WHERE (\"comments\".\"id\" \u003c 7) AND (\"articles\".\"title\" LIKE '%blah%')\"\n\n...or this...\n\n  Article.where(:id \u003c 2).merge(Comment.where(:id \u003c 7), :lame_comments).to_sql\n  =\u003e \"SELECT \"articles\".* FROM \"articles\"\n     INNER JOIN \"comments\" ON \"comments\".\"article_id\" = \"articles\".\"id\"\n        AND \"comments\".\"body\" = 'first post!'\n     WHERE (\"articles\".\"id\" \u003c 2) AND (\"comments\".\"id\" \u003c 7)\"\n\n== Enhanced order clauses\n\nIf you are used to doing stuff like \u003ctt\u003eArticle.order('title asc')\u003c/tt\u003e, that will still\nwork as you expect. However, if you pass symbols or arrays in to the \u003ctt\u003eorder\u003c/tt\u003e method,\nyou can take advantage of intelligent association detection (as with \"Intelligent hash condition\nmapping,\" above) and also some convenience methods for ascending and descending sorts.\n\n  Article.order(\n    :title.desc,\n    :comments =\u003e [:created_at.asc, :updated_at]\n  ).joins(:comments).to_sql\n  =\u003e SELECT \"articles\".* FROM \"articles\"\n     INNER JOIN \"comments\" ON \"comments\".\"article_id\" = \"articles\".\"id\"\n     ORDER BY  \"articles\".\"title\" DESC,\n       \"comments\".\"created_at\" ASC, \"comments\".\"updated_at\"\n\n== Polymorphic belongs_to joins\n\nPolymorphic associations provide great flexibility, but they can sometimes be a bit of a hassle\nwhen it comes to querying through a belongs_to association. First, you have to know what type\nyou're looking for to do a proper join, and then, you're forced into using a string join in order\nto make it happen (which would prevent the use of MetaWhere intelligent condition mapping).\n\nMetaWhere allows you to join polymorphic belongs_to associations like this:\n\n  Note.joins(:notable.type(Developer)).\n       where(:notable.type(Developer) =\u003e {:name.matches =\u003e 'Ernie%'})\n  =\u003e SELECT \"notes\".* FROM \"notes\"\n     INNER JOIN \"developers\" ON \"developers\".\"id\" = \"notes\".\"notable_id\"\n       AND \"notes\".\"notable_type\" = 'Developer'\n     WHERE \"developers\".\"name\" LIKE 'Ernie%'\n\n== Using ActiveRecord objects as condition values\n\nWouldn't it be nice if you could do something like this?\n\n  # Developer belongs_to Company\n  company = Company.find(123)\n  Developer.where(:company =\u003e company)\n\n  # Developer HABTM Projects\n  projects = [Project.first, Project.last]\n  Developer.joins(:projects).where(:projects =\u003e projects)\n\n  # Note belongs_to :notable, :polymorphic =\u003e true\n  dev1 = Developer.first\n  dev2 = Developer.last\n  project = Project.first\n  company = Company.first\n  Note.where(:notable =\u003e [dev1, dev2, project, company]).to_sql\n  =\u003e SELECT \"notes\".* FROM \"notes\" WHERE ((((\"notes\".\"notable_id\" IN (1, 8)\n     AND \"notes\".\"notable_type\" = 'Developer') OR (\"notes\".\"notable_id\" = 1\n     AND \"notes\".\"notable_type\" = 'Project')) OR (\"notes\".\"notable_id\" = 1\n     AND \"notes\".\"notable_type\" = 'Company')))\n\nWith MetaWhere, you can.\n\n== Thanks\nA huge thank you goes to Pratik Naik (lifo) for a dicussion on #rails-contrib about a patch\nI'd submitted, and his take on a DSL for query conditions, which was the inspiration for this\ngem.\n\n== Contributions\n\nThere are several ways you can help MetaWhere continue to improve.\n\n* Use MetaWhere in your real-world projects and {submit bug reports or feature suggestions}[http://metautonomous.lighthouseapp.com/projects/53011-metawhere/].\n* Better yet, if you’re so inclined, fix the issue yourself and submit a patch! Or you can {fork the project on GitHub}[http://github.com/ernie/meta_where] and send me a pull request (please include tests!)\n* If you like MetaWhere, spread the word. More users == more eyes on code == more bugs getting found == more bugs getting fixed (hopefully!)\n* Lastly, if MetaWhere has saved you hours of development time on your latest Rails gig, and you’re feeling magnanimous, please consider {making a donation}[http://pledgie.com/campaigns/10096] to the project. I have spent hours of my personal time coding and supporting MetaWhere, and your donation would go a great way toward justifying that time spent to my loving wife. :)\n\n== Copyright\n\nCopyright (c) 2010 {Ernie Miller}[http://metautonomo.us]. See LICENSE for details.\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factiverecord-hackery%2Fmeta_where","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Factiverecord-hackery%2Fmeta_where","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factiverecord-hackery%2Fmeta_where/lists"}