{"id":13850644,"url":"https://github.com/activerecord-hackery/meta_search","last_synced_at":"2025-07-12T22:31:22.659Z","repository":{"id":56883424,"uuid":"546593","full_name":"activerecord-hackery/meta_search","owner":"activerecord-hackery","description":"Object-based searching (and more) for simply creating search forms. Not currently maintained.","archived":true,"fork":false,"pushed_at":"2022-03-29T17:15:13.000Z","size":361,"stargazers_count":906,"open_issues_count":49,"forks_count":140,"subscribers_count":28,"default_branch":"master","last_synced_at":"2024-05-21T01:33:08.836Z","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-03-04T13:49:07.000Z","updated_at":"2024-02-02T05:45:30.000Z","dependencies_parsed_at":"2022-08-20T13:10:53.452Z","dependency_job_id":null,"html_url":"https://github.com/activerecord-hackery/meta_search","commit_stats":null,"previous_names":["ernie/meta_search"],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activerecord-hackery%2Fmeta_search","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activerecord-hackery%2Fmeta_search/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activerecord-hackery%2Fmeta_search/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/activerecord-hackery%2Fmeta_search/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/activerecord-hackery","download_url":"https://codeload.github.com/activerecord-hackery/meta_search/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225839621,"owners_count":17532305,"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:21.433Z","updated_at":"2024-11-22T03:31:43.661Z","avatar_url":"https://github.com/activerecord-hackery.png","language":"Ruby","readme":"This project is archived\n\n\n= MetaSearch\n\nMetaSearch is extensible searching for your form_for enjoyment. It “wraps” one of your ActiveRecord models, providing methods that allow you to build up search conditions against that model, and has a few extra form helpers to simplify sorting and supplying multiple parameters to your condition methods as well.\n\n== NOTE\n\nThe successor to MetaSearch is {Ransack}[http://github.com/ernie/ransack]. It's got features\nthat MetaSearch doesn't, along with some API changes. I haven't had the time to dedicate to\nmaking it bulletproof yet, so I'm releasing a 1.1.x branch of MetaSearch to help with migrations\nto Rails 3.1.\n\nThis is intended to be a stopgap measure.\n\nt's important to note that the long-term migration path for your apps should be toward\nRansack, which is written in a more sane manner that will make supporting new versions\nof Rails much easier going forward.\n\n== Getting Started\n\nIn your Gemfile:\n\n  gem \"meta_search\"  # Last officially released gem\n  # gem \"meta_search\", :git =\u003e \"git://github.com/ernie/meta_search.git\" # Track git repo\n\nor, to install as a plugin:\n\n  rails plugin install git://github.com/ernie/meta_search.git\n\nIn your controller:\n\n  def index\n    @search = Article.search(params[:search])\n    @articles = @search.all   # load all matching records\n    # @articles = @search.relation # Retrieve the relation, to lazy-load in view\n    # @articles = @search.paginate(:page =\u003e params[:page]) # Who doesn't love will_paginate?\n  end\n\nIn your view:\n\n  \u003c%= form_for @search, :url =\u003e articles_path, :html =\u003e {:method =\u003e :get} do |f| %\u003e\n    \u003c%= f.label :title_contains %\u003e\n    \u003c%= f.text_field :title_contains %\u003e\u003cbr /\u003e\n    \u003c%= f.label :comments_created_at_greater_than, 'With comments after' %\u003e\n    \u003c%= f.datetime_select :comments_created_at_greater_than, :include_blank =\u003e true %\u003e\u003cbr /\u003e\n    \u003c!-- etc... --\u003e\n    \u003c%= f.submit %\u003e\n  \u003c% end %\u003e\n\nOptions for the search method are documented at MetaSearch::Searches::ActiveRecord.\n\n== \"Wheres\", and what they're good for\n\nWheres are how MetaSearch does its magic. Wheres have a name (and possible aliases) which are\nappended to your model and association attributes. When you instantiate a MetaSearch::Builder\nagainst a model (manually or by calling your model's +search+ method) the builder responds to\nmethods named for your model's attributes and associations, suffixed by the name of the Where.\n\nThese are the default Wheres, broken down by the types of ActiveRecord columns they can search\nagainst:\n\n=== All data types\n\n* _equals_ (alias: _eq_) - Just as it sounds.\n* _does_not_equal_ (aliases: _ne_, _noteq_) - The opposite of equals, oddly enough.\n* _in_ - Takes an array, matches on equality with any of the items in the array.\n* _not_in_ (aliases: _ni_, _notin_) - Like above, but negated.\n* _is_null_ - The column has an SQL NULL value.\n* _is_not_null_ - The column contains anything but NULL.\n\n=== Strings\n\n* _contains_ (aliases: _like_, _matches_) - Substring match.\n* _does_not_contain_ (aliases: _nlike_, _nmatches_) - Negative substring match.\n* _starts_with_ (alias: _sw_) - Match strings beginning with the entered term.\n* _does_not_start_with_ (alias: _dnsw_) - The opposite of above.\n* _ends_with_ (alias: _ew_) - Match strings ending with the entered term.\n* _does_not_end_with_ (alias: _dnew_) - Negative of above.\n\n=== Numbers, dates, and times\n\n* _greater_than_ (alias: _gt_) - Greater than.\n* _greater_than_or_equal_to_ (aliases: _gte_, _gteq_) - Greater than or equal to.\n* _less_than_ (alias: _lt_) - Less than.\n* _less_than_or_equal_to_ (aliases: _lte_, _lteq_) - Less than or equal to.\n\n=== Booleans\n\n* _is_true_ - Is true. Useful for a checkbox like \"only show admin users\".\n* _is_false_ - The complement of _is_true_.\n\n=== Non-boolean data types\n\n* _is_present_ - As with _is_true_, useful with a checkbox. Not NULL or the empty string.\n* _is_blank_ - Returns records with a value of NULL or the empty string in the column.\n\nSo, given a model like this...\n\n  class Article \u003c ActiveRecord::Base\n    belongs_to :author\n    has_many :comments\n    has_many :moderations, :through =\u003e :comments\n  end\n\n...you might end up with attributes like \u003ctt\u003etitle_contains\u003c/tt\u003e,\n\u003ctt\u003ecomments_title_starts_with\u003c/tt\u003e, \u003ctt\u003emoderations_value_less_than\u003c/tt\u003e,\n\u003ctt\u003eauthor_name_equals\u003c/tt\u003e, and so on.\n\nAdditionally, all of the above predicate types also have an _any and _all version, which\nexpects an array of the corresponding parameter type, and requires any or all of the\nparameters to be a match, respectively. So:\n\n  Article.search :author_name_starts_with_any =\u003e ['Jim', 'Bob', 'Fred']\n\nwill match articles authored by Jimmy, Bobby, or Freddy, but not Winifred.\n\n== Advanced usage\n\n=== Narrowing the scope of a search\n\nWhile the most common use case is to simply call Model.search(params[:search]), there\nmay be times where you want to scope your search more tightly. For instance, only allowing\nusers to search their own projects (assuming a current_user method returning the current user):\n\n  @search = current_user.projects.search(params[:search])\n\nOr, you can build up any relation you like and call the search method on that object:\n\n  @projects_with_awesome_users_search =\n    Project.joins(:user).where(:users =\u003e {:awesome =\u003e true}).search(params[:search])\n\n=== ORed conditions\n\nIf you'd like to match on one of several possible columns, you can do this:\n\n  \u003c%= f.text_field :title_or_description_contains %\u003e\n  \u003c%= f.text_field :title_or_author_name_starts_with %\u003e\n\nCaveats:\n\n* Only one match type is supported. You \u003cb\u003ecan't\u003c/b\u003e do\n  \u003ctt\u003etitle_matches_or_description_starts_with\u003c/tt\u003e for instance.\n* If you're matching across associations, remember that the associated table will be\n  INNER JOINed, therefore limiting results to those that at least have a corresponding\n  record in the associated table.\n\n=== Compound conditions (any/all)\n\nAll Where types automatically get an \"any\" and \"all\" variant. This has the same name and\naliases as the original, but is suffixed with _any and _all, for an \"OR\" or \"AND\" search,\nrespectively. So, if you want to provide the user with 5 different search boxes to enter\npossible article titles:\n\n  \u003c%= f.multiparameter_field :title_contains_any,\n        *5.times.inject([]) {|a, b| a \u003c\u003c {:field_type =\u003e :text_field}} +\n        [:size =\u003e 10] %\u003e\n\n=== Multi-level associations\n\nMetaSearch will allow you to traverse your associations in one form, generating the\nnecessary joins along the way. If you have the following models...\n\n  class Company \u003c ActiveRecord::Base\n    has_many :developers\n  end\n\n  class Developer \u003c ActiveRecord::Base\n    belongs_to :company\n    has_many :notes\n  end\n\n...you can do this in your form to search your companies by developers with certain notes:\n\n  \u003c%= f.text_field :developers_notes_note_contains %\u003e\n\nYou can travel forward and back through the associations, so this would also work (though\nbe entirely pointless in this case):\n\n  \u003c%= f.text_field :developers_notes_developer_company_name_contains %\u003e\n\nHowever, to prevent abuse, this is limited to associations of a total \"depth\" of 5 levels.\nThis means that while starting from a Company model, as above, you could do\nCompany -\u003e :developers -\u003e :notes -\u003e :developer -\u003e :company, which has gotten you right\nback where you started, but \"travels\" through 5 models total.\n\nIn the case of polymorphic belongs_to associations, things work a bit differently. Let's say\nyou have the following models:\n\n  class Article \u003c ActiveRecord::Base\n    has_many :comments, :as =\u003e :commentable\n  end\n\n  class Post \u003c ActiveRecord::Base\n    has_many :comments, :as =\u003e :commentable\n  end\n\n  class Comment \u003c ActiveRecord::Base\n    belongs_to :commentable, :polymorphic =\u003e true\n    validates_presence_of :body\n  end\n\nYour first instinct might be to set up a text field for :commentable_body_contains, but\nyou can't do this. MetaSearch would have no way to know which class lies on the other side\nof the polymorphic association, so it wouldn't be able to join the correct tables.\n\nInstead, you'll follow a convention Searchlogic users are already familiar with, using the\nname of the polymorphic association, then the underscored class name (AwesomeClass becomes\nawesome_class), then the delimiter \"type\", to tell MetaSearch anything that follows is an\nattribute name. For example:\n\n  \u003c%= f.text_field :commentable_article_type_body_contains %\u003e\n\nIf you'd like to match on multiple types of polymorphic associations, you can join them\nwith \\_or_, just like any other conditions:\n\n  \u003c%= f.text_field :commentable_article_type_body_or_commentable_post_type_body_contains %\u003e\n\nIt's not pretty, but it works. Alternately, consider creating a custom search method as\ndescribed below to save yourself some typing if you're creating a lot of these types of\nsearch fields.\n\n=== Adding a new Where\n\nIf none of the built-in search criteria work for you, you can add new Wheres. To do so,\ncreate an initializer (\u003ctt\u003e/config/initializers/meta_search.rb\u003c/tt\u003e, for instance) and add lines\nlike:\n\n  MetaSearch::Where.add :between, :btw,\n    :predicate =\u003e :in,\n    :types =\u003e [:integer, :float, :decimal, :date, :datetime, :timestamp, :time],\n    :formatter =\u003e Proc.new {|param| Range.new(param.first, param.last)},\n    :validator =\u003e Proc.new {|param|\n      param.is_a?(Array) \u0026\u0026 !(param[0].blank? || param[1].blank?)\n    }\n\nSee MetaSearch::Where for info on the supported options.\n\n=== Accessing custom search methods (and named scopes!)\n\nMetaSearch can be given access to any class method on your model to extend its search capabilities.\nThe only rule is that the method must return an ActiveRecord::Relation so that MetaSearch can\ncontinue to extend the search with other attributes. Conveniently, scopes (formerly \"named scopes\")\ndo this already.\n\nConsider the following model:\n\n  class Company \u003c ActiveRecord::Base\n    has_many :slackers, :class_name =\u003e \"Developer\", :conditions =\u003e {:slacker =\u003e true}\n    scope :backwards_name, lambda {|name| where(:name =\u003e name.reverse)}\n    scope :with_slackers_by_name_and_salary_range,\n      lambda {|name, low, high|\n        joins(:slackers).where(:developers =\u003e {:name =\u003e name, :salary =\u003e low..high})\n      }\n  end\n\nTo allow MetaSearch access to a model method, including a named scope, just use\n\u003ctt\u003esearch_methods\u003c/tt\u003e in the model:\n\n  search_methods :backwards_name\n\nThis will allow you to add a text field named :backwards_name to your search form, and\nit will behave as you might expect.\n\nIn the case of the second scope, we have multiple parameters to pass in, of different\ntypes. We can pass the following to \u003ctt\u003esearch_methods\u003c/tt\u003e:\n\n  search_methods :with_slackers_by_name_and_salary_range,\n    :splat_param =\u003e true, :type =\u003e [:string, :integer, :integer]\n\nMetaSearch needs us to tell it that we don't want to keep the array supplied to it as-is, but\n\"splat\" it when passing it to the model method. Regarding \u003ctt\u003e:types\u003c/tt\u003e: In this case,\nActiveRecord would have been smart enough to handle the typecasting for us, but I wanted to\ndemonstrate how we can tell MetaSearch that a given parameter is of a specific database \"column type.\" This is just a hint MetaSearch uses in the same way it does when casting \"Where\" params based\non the DB column being searched. It's also important so that things like dates get handled\nproperly by FormBuilder.\n\n=== multiparameter_field\n\nThe example Where above adds support for a \"between\" search, which requires an array with\ntwo parameters. These can be passed using Rails multiparameter attributes. To make life easier,\nMetaSearch adds a helper for this:\n\n  \u003c%= f.multiparameter_field :moderations_value_between,\n      {:field_type =\u003e :text_field}, {:field_type =\u003e :text_field}, :size =\u003e 5 %\u003e\n\n\u003ctt\u003emultiparameter_field\u003c/tt\u003e works pretty much like the other FormBuilder helpers, but it\nlets you sandwich a list of fields, each in hash format, between the attribute and the usual\noptions hash. See MetaSearch::Helpers::FormBuilder for more info.\n\n=== checks and collection_checks\n\nIf you need to get an array into your where, and you don't care about parameter order,\nyou might choose to use a select or collection_select with multiple selection enabled,\nbut everyone hates multiple selection boxes. MetaSearch adds a couple of additional\nhelpers, +checks+ and +collection_checks+ to handle multiple selections in a\nmore visually appealing manner. They can be called with or without a block. Without a\nblock, you get an array of MetaSearch::Check objects to do with as you please.\n\nWith a block, each check is yielded to your template, like so:\n\n  \u003ch4\u003eHow many heads?\u003c/h4\u003e\n  \u003cul\u003e\n    \u003c% f.checks :number_of_heads_in,\n      [['One', 1], ['Two', 2], ['Three', 3]], :class =\u003e 'checkboxy' do |check| %\u003e\n      \u003cli\u003e\n        \u003c%= check.box %\u003e\n        \u003c%= check.label %\u003e\n      \u003c/li\u003e\n    \u003c% end %\u003e\n  \u003c/ul\u003e\n\nAgain, full documentation is in MetaSearch::Helpers::FormBuilder.\n\n=== Sorting columns\n\nIf you'd like to sort by a specific column in your results (the attributes of the base model)\nor an association column then supply the \u003ctt\u003emeta_sort\u003c/tt\u003e parameter in your form.\nThe parameter takes the form \u003ctt\u003ecolumn.direction\u003c/tt\u003e where +column+ is the column name or\nunderscore-separated association_column combination, and +direction+ is one of \"asc\" or \"desc\"\nfor ascending or descending, respectively.\n\nNormally, you won't supply this parameter yourself, but instead will use the helper method\n\u003ctt\u003esort_link\u003c/tt\u003e in your views, like so:\n\n  \u003c%= sort_link @search, :title %\u003e\n\nOr, if in the context of a form_for against a MetaSearch::Builder:\n\n  \u003c%= f.sort_link :title %\u003e\n\nThe \u003ctt\u003e@search\u003c/tt\u003e object is the instance of MetaSearch::Builder you got back earlier from\nyour controller. The other required parameter is the attribute name itself. Optionally,\nyou can provide a string as a 3rd parameter to override the default link name, and then\nadditional hashed for the +options+ and +html_options+ hashes for link_to.\n\nBy default, the link that is created will sort by the given column in ascending order when first clicked. If you'd like to reverse this (so the first click sorts the results in descending order), you can pass +:default_order =\u003e :desc+ in the options hash, like so:\n\n  \u003c%= sort_link @search, :ratings, \"Highest Rated\", :default_order =\u003e :desc %\u003e\n\nYou can sort by more than one column as well, by creating a link like:\n\n  \u003c%= sort_link :name_and_salary %\u003e\n\nIf you'd like to do a custom sort, you can do so by setting up two scopes in your model:\n\n  scope :sort_by_custom_name_asc, order('custom_name ASC')\n  scope :sort_by_custom_name_desc, order('custom_name DESC')\n\nYou can then do \u003ctt\u003esort_link @search, :custom_name\u003c/tt\u003e and it will work as you expect.\n\nAll \u003ctt\u003esort_link\u003c/tt\u003e-generated links will have the CSS class sort_link, as well as a\ndirectional class (ascending or descending) if the link is for a currently sorted column,\nfor your styling enjoyment.\n\nThis feature should hopefully help out those of you migrating from Searchlogic, and a thanks\ngoes out to Ben Johnson for the HTML entities used for the up and down arrows, which provide\na nice default look.\n\n=== Including/excluding attributes and associations\n\nIf you'd like to allow only certain associations or attributes to be searched, you can do\nso inside your models\n\n  class Article \u003c ActiveRecord::Base\n    attr_searchable :some_public_data, :some_more_searchable_stuff\n    assoc_searchable :search_this_association_why_dontcha\n  end\n\nIf you'd rather blacklist attributes and associations rather than whitelist, use the\n\u003ctt\u003eattr_unsearchable\u003c/tt\u003e and \u003ctt\u003eassoc_unsearchable\u003c/tt\u003e method instead. If a\nwhitelist is supplied, it takes precedence.\n\nExcluded attributes on a model will be honored across associations, so if an Article\n\u003ctt\u003ehas_many :comments\u003c/tt\u003e and the Comment model looks something like this:\n\n  class Comment \u003c ActiveRecord::Base\n    validates_presence_of :user_id, :body\n    attr_unsearchable :user_id\n  end\n\nThen your call to \u003ctt\u003eArticle.search\u003c/tt\u003e will allow \u003ctt\u003e:comments_body_contains\u003c/tt\u003e\nbut not \u003ctt\u003e:comments_user_id_equals\u003c/tt\u003e to be passed.\n\n=== Conditional access to searches\n\n\u003ctt\u003esearch_methods\u003c/tt\u003e, \u003ctt\u003eattr_searchable\u003c/tt\u003e, \u003ctt\u003eattr_unsearchable\u003c/tt\u003e,\n\u003ctt\u003eassoc_searchable\u003c/tt\u003e, and \u003ctt\u003eassoc_unsearchable\u003c/tt\u003e all accept an \u003ctt\u003e:if\u003c/tt\u003e\noption. If present, it should specify a Proc (or other object responding to \u003ctt\u003ecall\u003c/tt\u003e)\nthat accepts a single parameter. This parameter will be the instance of the MetaSearch::Builder\nthat gets created by a call to Model.search. Any unused search options (the second hash param)\nthat get passed to Model.search will be available via the Builder object's \u003ctt\u003eoptions\u003c/tt\u003e\nreader, and can be used for access control via this proc/object.\n\nExample:\n\n  assoc_unsearchable :notes,\n                     :if =\u003e proc {|s| s.options[:access] == 'blocked' || !s.options[:access]}\n\n=== Localization\n\nMetaSearch supports i18n localization in a few different ways. Consider this abbreviated\nexample \"flanders\" locale:\n\n  flanders:\n    activerecord:\n      attributes:\n        company:\n          name: \"Company name-diddly\"\n        developer:\n          name: \"Developer name-diddly\"\n          salary: \"Developer salary-doodly\"\n    meta_search:\n      or: 'or-diddly'\n      predicates:\n        contains: \"%{attribute} contains-diddly\"\n        equals: \"%{attribute} equals-diddly\"\n      attributes:\n        company:\n          reverse_name: \"Company reverse name-diddly\"\n        developer:\n          name_contains: \"Developer name-diddly contains-aroonie\"\n\nFirst, MetaSearch will use a key found under meta_search.attributes.model_name.attribute_name,\nif it exists. As a fallback, it will use a localization based on the predicate type, along with\nthe usual ActiveRecord attribute localization (the activerecord.attributes.model_name keys above).\nAdditionally, a localized \"or\" can be specified for multi-column searches.\n\n== Contributions\n\nThere are several ways you can help MetaSearch continue to improve.\n\n* Use MetaSearch in your real-world projects and {submit bug reports or feature suggestions}[http://metautonomous.lighthouseapp.com/projects/53012-metasearch/].\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_search] and send me a pull request (please include tests!)\n* If you like MetaSearch, spread the word. More users == more eyes on code == more bugs getting found == more bugs getting fixed (hopefully!)\n* Lastly, if MetaSearch 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/9647] to the project. I have spent hours of my personal time coding and supporting MetaSearch, 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_search","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Factiverecord-hackery%2Fmeta_search","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factiverecord-hackery%2Fmeta_search/lists"}