{"id":13411794,"url":"https://github.com/mrkamel/search_cop","last_synced_at":"2025-04-10T03:49:00.364Z","repository":{"id":16667463,"uuid":"19423156","full_name":"mrkamel/search_cop","owner":"mrkamel","description":"Search engine like fulltext query support for ActiveRecord","archived":false,"fork":false,"pushed_at":"2024-05-30T07:09:53.000Z","size":313,"stargazers_count":825,"open_issues_count":15,"forks_count":39,"subscribers_count":15,"default_branch":"master","last_synced_at":"2024-10-29T15:12:59.977Z","etag":null,"topics":["activerecord","fulltext-indices","fulltext-support","mysql","postgres","ruby","search","search-engine"],"latest_commit_sha":null,"homepage":"","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/mrkamel.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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-05-04T09:27:00.000Z","updated_at":"2024-10-08T09:07:06.000Z","dependencies_parsed_at":"2024-01-05T21:50:11.248Z","dependency_job_id":"a862bcec-2131-48e0-967e-1a18c03db3f6","html_url":"https://github.com/mrkamel/search_cop","commit_stats":{"total_commits":218,"total_committers":15,"mean_commits":"14.533333333333333","dds":"0.22935779816513757","last_synced_commit":"b49d05dbca9565414108b847c6a34827fc65f3ac"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrkamel%2Fsearch_cop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrkamel%2Fsearch_cop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrkamel%2Fsearch_cop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrkamel%2Fsearch_cop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrkamel","download_url":"https://codeload.github.com/mrkamel/search_cop/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248154995,"owners_count":21056542,"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","fulltext-indices","fulltext-support","mysql","postgres","ruby","search","search-engine"],"created_at":"2024-07-30T20:01:16.991Z","updated_at":"2025-04-10T03:49:00.334Z","avatar_url":"https://github.com/mrkamel.png","language":"Ruby","readme":"# SearchCop\n\n[![Build Status](https://github.com/mrkamel/search_cop/workflows/test/badge.svg?branch=master)](https://github.com/mrkamel/search_cop/actions?query=workflow%3Atest)\n[![Code Climate](https://codeclimate.com/github/mrkamel/search_cop.png)](https://codeclimate.com/github/mrkamel/search_cop)\n[![Gem Version](https://badge.fury.io/rb/search_cop.svg)](http://badge.fury.io/rb/search_cop)\n\n![search_cop](https://raw.githubusercontent.com/mrkamel/search_cop_logo/master/search_cop.png)\n\nSearchCop extends your ActiveRecord models to support fulltext search\nengine like queries via simple query strings and hash-based queries. Assume you\nhave a `Book` model having various attributes like `title`, `author`, `stock`,\n`price`, `available`. Using SearchCop you can perform:\n\n```ruby\nBook.search(\"Joanne Rowling Harry Potter\")\nBook.search(\"author: Rowling title:'Harry Potter'\")\nBook.search(\"price \u003e 10 AND price \u003c 20 -stock:0 (Potter OR Rowling)\")\n# ...\n```\n\nThus, you can hand out a search query string to your models and you, your app's\nadmins and/or users will get powerful query features without the need for\nintegrating additional third party search servers, since SearchCop can use\nfulltext index capabilities of your RDBMS in a database agnostic way (currently\nMySQL and PostgreSQL fulltext indices are supported) and optimizes the queries\nto make optimal use of them. Read more below.\n\nComplex hash-based queries are supported as well:\n\n```ruby\nBook.search(author: \"Rowling\", title: \"Harry Potter\")\nBook.search(or: [{author: \"Rowling\"}, {author: \"Tolkien\"}])\nBook.search(and: [{price: {gt: 10}}, {not: {stock: 0}}, or: [{title: \"Potter\"}, {author: \"Rowling\"}]])\nBook.search(or: [{query: \"Rowling -Potter\"}, {query: \"Tolkien -Rings\"}])\nBook.search(title: {my_custom_sql_query: \"Rowl\"}})\n# ...\n```\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'search_cop'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install search_cop\n\n## Usage\n\nTo enable SearchCop for a model, `include SearchCop` and specify the\nattributes you want to expose to search queries within a `search_scope`:\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include SearchCop\n\n  search_scope :search do\n    attributes :title, :description, :stock, :price, :created_at, :available\n    attributes comment: [\"comments.title\", \"comments.message\"]\n    attributes author: \"author.name\"\n    # ...\n  end\n\n  has_many :comments\n  belongs_to :author\nend\n```\n\nYou can of course as well specify multiple `search_scope` blocks as you like:\n\n```ruby\nsearch_scope :admin_search do\n  attributes :title, :description, :stock, :price, :created_at, :available\n\n  # ...\nend\n\nsearch_scope :user_search do\n  attributes :title, :description\n\n  # ...\nend\n```\n\n## How does it work\n\nSearchCop parses the query and maps it to an SQL Query in a database agnostic way.\nThus, SearchCop is not bound to a specific RDBMS.\n\n```ruby\nBook.search(\"stock \u003e 0\")\n# ... WHERE books.stock \u003e 0\n\nBook.search(\"price \u003e 10 stock \u003e 0\")\n# ... WHERE books.price \u003e 10 AND books.stock \u003e 0\n\nBook.search(\"Harry Potter\")\n# ... WHERE (books.title LIKE '%Harry%' OR books.description LIKE '%Harry%' OR ...) AND (books.title LIKE '%Potter%' OR books.description LIKE '%Potter%' ...)\n\nBook.search(\"available:yes OR created_at:2014\")\n# ... WHERE books.available = 1 OR (books.created_at \u003e= '2014-01-01 00:00:00.00000' and books.created_at \u003c= '2014-12-31 23:59:59.99999')\n```\nSearchCop is using ActiveSupport's beginning_of_year and end_of_year methods for the values used in building the SQL query for this case.\n\nOf course, these `LIKE '%...%'` queries won't achieve optimal performance, but\ncheck out the section below on SearchCop's fulltext capabilities to\nunderstand how the resulting queries can be optimized.\n\nAs `Book.search(...)` returns an `ActiveRecord::Relation`, you are free to pre-\nor post-process the search results in every possible way:\n\n```ruby\nBook.where(available: true).search(\"Harry Potter\").order(\"books.id desc\").paginate(page: params[:page])\n```\n\n## Security\n\nWhen you pass a query string to SearchCop, it gets parsed, analyzed and mapped\nto finally build up an SQL query. To be more precise, when SearchCop parses the\nquery, it creates objects (nodes), which represent the query expressions (And-,\nOr-, Not-, String-, Date-, etc Nodes). To build the SQL query, SearchCop uses\nthe concept of visitors like e.g. used in\n[Arel](https://github.com/rails/arel), such that, for every node there must be\na [visitor](https://github.com/mrkamel/search_cop/blob/master/lib/search_cop/visitors/visitor.rb),\nwhich transforms the node to SQL. When there is no visitor, an exception is\nraised when the query builder tries to \"visit\" the node. The visitors are\nresponsible for sanitizing the user supplied input. This is primilarly done via\nquoting (string-, table-name-, column-quoting, etc). SearchCop is using the\nmethods provided by the ActiveRecord connection adapter for sanitizing/quoting\nto prevent SQL injection. While we can never be 100% safe from security issues,\nSearchCop takes security issues seriously. Please report responsibly via\nsecurity at flakks dot com in case you find any security related issues.\n\n## json/jsonb/hstore\n\nSearchCop supports json fields for MySQL, as well as json, jsonb and hstore\nfields for postgres. Currently, field values are always expected to be strings\nand no arrays are supported. You can specify json attributes via:\n\n```ruby\nsearch_scope :search do\n  attributes user_agent: \"context-\u003ebrowser-\u003euser_agent\"\n\n  # ...\nend\n```\n\nwhere `context` is a json/jsonb column which e.g. contains:\n\n```json\n{\n  \"browser\": {\n    \"user_agent\": \"Firefox ...\"\n  }\n}\n```\n\n## Fulltext index capabilities\n\nBy default, i.e. if you don't tell SearchCop about your fulltext indices,\nSearchCop will use `LIKE '%...%'` queries. Unfortunately, unless you\ncreate a [trigram index](http://www.postgresql.org/docs/9.1/static/pgtrgm.html)\n(postgres only), these queries can not use SQL indices, such that every row\nneeds to be scanned by your RDBMS when you search for `Book.search(\"Harry\nPotter\")` or similar. To avoid the penalty of `LIKE` queries, SearchCop\ncan exploit the fulltext index capabilities of MySQL and PostgreSQL. To use\nalready existing fulltext indices, simply tell SearchCop to use them via:\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  # ...\n\n  search_scope :search do\n    attributes :title, :author\n\n    options :title, :type =\u003e :fulltext\n    options :author, :type =\u003e :fulltext\n  end\n\n  # ...\nend\n```\n\nSearchCop will then transparently change its SQL queries for the\nattributes having fulltext indices to:\n\n```ruby\nBook.search(\"Harry Potter\")\n# MySQL: ... WHERE (MATCH(books.title) AGAINST('+Harry' IN BOOLEAN MODE) OR MATCH(books.author) AGAINST('+Harry' IN BOOLEAN MODE)) AND (MATCH(books.title) AGAINST ('+Potter' IN BOOLEAN MODE) OR MATCH(books.author) AGAINST('+Potter' IN BOOLEAN MODE))\n# PostgreSQL: ... WHERE (to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Harry') OR to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Harry')) AND (to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Potter') OR to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Potter'))\n```\n\nObviously, these queries won't always return the same results as wildcard\n`LIKE` queries, because we search for words instead of sub-strings. However,\nfulltext indices will usually of course provide better performance.\n\nMoreover, the query above is not yet perfect. To improve it even more,\nSearchCop tries to optimize the queries to make optimal use of fulltext indices\nwhile still allowing to mix them with non-fulltext attributes. To improve\nqueries even more, you can group attributes and specify a default field to\nsearch in, such that SearchCop must no longer search within all fields:\n\n```ruby\nsearch_scope :search do\n  attributes all: [:author, :title]\n\n  options :all, :type =\u003e :fulltext, default: true\n\n  # Use default: true to explicitly enable fields as default fields (whitelist approach)\n  # Use default: false to explicitly disable fields as default fields (blacklist approach)\nend\n```\n\nNow SearchCop can optimize the following, not yet optimal query:\n\n```ruby\nBook.search(\"Rowling OR Tolkien stock \u003e 1\")\n# MySQL: ... WHERE ((MATCH(books.author) AGAINST('+Rowling' IN BOOLEAN MODE) OR MATCH(books.title) AGAINST('+Rowling' IN BOOLEAN MODE)) OR (MATCH(books.author) AGAINST('+Tolkien' IN BOOLEAN MODE) OR MATCH(books.title) AGAINST('+Tolkien' IN BOOLEAN MODE))) AND books.stock \u003e 1\n# PostgreSQL: ... WHERE ((to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Rowling') OR to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Rowling')) OR (to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Tolkien') OR to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Tolkien'))) AND books.stock \u003e 1\n```\n\nto the following, more performant query:\n\n\n```ruby\nBook.search(\"Rowling OR Tolkien stock \u003e 1\")\n# MySQL: ... WHERE MATCH(books.author, books.title) AGAINST('Rowling Tolkien' IN BOOLEAN MODE) AND books.stock \u003e 1\n# PostgreSQL: ... WHERE to_tsvector('simple', books.author || ' ' || books.title) @@ to_tsquery('simple', 'Rowling | Tokien') and books.stock \u003e 1\n```\n\nWhat is happening here? Well, we specified `all` as the name of an attribute\ngroup that consists of `author` and `title`. As we, in addition, specified\n`all` to be a fulltext attribute, SearchCop assumes there is a compound\nfulltext index present on `author` and `title`, such that the query is\noptimized accordingly. Finally, we specified `all` to be the default attribute\nto search in, such that SearchCop can ignore other attributes, like e.g.\n`stock`, as long as they are not specified within queries directly (like for\n`stock \u003e 0`).\n\nOther queries will be optimized in a similar way, such that SearchCop\ntries to minimize the fultext constraints within a query, namely `MATCH()\nAGAINST()` for MySQL and `to_tsvector() @@ to_tsquery()` for PostgreSQL.\n\n```ruby\nBook.search(\"(Rowling -Potter) OR Tolkien\")\n# MySQL: ... WHERE MATCH(books.author, books.title) AGAINST('(+Rowling -Potter) Tolkien' IN BOOLEAN MODE)\n# PostgreSQL: ... WHERE to_tsvector('simple', books.author || ' ' || books.title) @@ to_tsquery('simple', '(Rowling \u0026 !Potter) | Tolkien')\n```\n\nTo create a fulltext index on `books.title` in MySQL, simply use:\n\n```ruby\nadd_index :books, :title, :type =\u003e :fulltext\n```\n\nRegarding compound indices, which will e.g. be used for the default field `all`\nwe already specified above, use:\n\n```ruby\nadd_index :books, [:author, :title], :type =\u003e :fulltext\n```\n\nPlease note that MySQL supports fulltext indices for MyISAM and, as of MySQL\nversion 5.6+, for InnoDB as well. For more details about MySQL fulltext indices\nvisit\n[http://dev.mysql.com/doc/refman/5.6/en/fulltext-search.html](http://dev.mysql.com/doc/refman/5.6/en/fulltext-search.html)\n\nRegarding PostgreSQL there are more ways to create a fulltext index. However,\none of the easiest ways is:\n\n```ruby\nActiveRecord::Base.connection.execute \"CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', title))\"\n```\n\nMoreover, for PostgreSQL you should change the schema format in\n`config/application.rb`:\n\n```ruby\nconfig.active_record.schema_format = :sql\n```\n\nRegarding compound indices for PostgreSQL, use:\n\n```ruby\nActiveRecord::Base.connection.execute \"CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', author || ' ' || title))\"\n```\n\nTo handle NULL values with PostgreSQL correctly, use COALESCE both at index\ncreation time and when specifying the `search_scope`:\n\n```ruby\nActiveRecord::Base.connection.execute \"CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', COALESCE(author, '') || ' ' || COALESCE(title, '')))\"\n```\n\nplus:\n\n```ruby\nsearch_scope :search do\n  attributes :title\n\n  options :title, :type =\u003e :fulltext, coalesce: true\nend\n```\n\nTo use another PostgreSQL dictionary than `simple`, you have to create the\nindex accordingly and you need tell SearchCop about it, e.g.:\n\n```ruby\nsearch_scope :search do\n  attributes :title\n\n  options :title, :type =\u003e :fulltext, dictionary: \"english\"\nend\n```\n\nFor more details about PostgreSQL fulltext indices visit\n[http://www.postgresql.org/docs/9.3/static/textsearch.html](http://www.postgresql.org/docs/9.3/static/textsearch.html)\n\n## Other indices\n\nIn case you expose non-fulltext attributes to search queries (price, stock,\netc.), the respective queries, like `Book.search(\"stock \u003e 0\")`, will profit\nfrom the usual non-fulltext indices. Thus, you should add a usual index on\nevery column you expose to search queries plus a fulltext index for every\nfulltext attribute.\n\nIn case you can't use fulltext indices, because you're e.g. still on MySQL 5.5\nwhile using InnoDB or another RDBMS without fulltext support, you can make your\nRDBMS use usual non-fulltext indices for string columns if you don't need the\nleft wildcard within `LIKE` queries. Simply supply the following option:\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  include SearchCop\n\n  search_scope :search do\n    attributes :username\n\n    options :username, left_wildcard: false\n  end\n\n  # ...\n```\n\nsuch that SearchCop will omit the left most wildcard.\n\n```ruby\nUser.search(\"admin\")\n# ... WHERE users.username LIKE 'admin%'\n```\n\nSimilarly, you can disable the right wildcard as well:\n\n```ruby\nsearch_scope :search do\n  attributes :username\n\n  options :username, right_wildcard: false\nend\n```\n\n## Default operator\n\nWhen you define multiple fields on a search scope, SearcCop will use\nby default the AND operator to concatenate the conditions, e.g:\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  include SearchCop\n\n  search_scope :search do\n    attributes :username, :fullname\n  end\n\n  # ...\nend\n```\n\nSo a search like `User.search(\"something\")` will generate a query\nwith the following conditions:\n\n```sql\n... WHERE username LIKE '%something%' AND fullname LIKE '%something%'\n```\n\nHowever, there are cases where using AND as the default operator is not desired,\nso SearchCop allows you to override it and use OR as the default operator instead.\nA query like `User.search(\"something\", default_operator: :or)` will\ngenerate the query using OR to concatenate the conditions\n\n```sql\n... WHERE username LIKE '%something%' OR fullname LIKE '%something%'\n```\n\nFinally, please note that you can apply it to fulltext indices/queries as well.\n\n## Associations\n\nIf you specify searchable attributes from another model, like\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  # ...\n\n  belongs_to :author\n\n  search_scope :search do\n    attributes author: \"author.name\"\n  end\n\n  # ...\nend\n```\n\nSearchCop will by default `eager_load` the referenced associations, when\nyou perform `Book.search(...)`.  If you don't want the automatic `eager_load`\nor need to perform special operations, specify a `scope`:\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  # ...\n\n  search_scope :search do\n    # ...\n\n    scope { joins(:author).eager_load(:comments) } # etc.\n  end\n\n  # ...\nend\n```\n\nSearchCop will then skip any association auto loading and will use the scope\ninstead. You can as well use `scope` together with `aliases` to perform\narbitrarily complex joins and search in the joined models/tables:\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  # ...\n\n  search_scope :search do\n    attributes similar: [\"similar_books.title\", \"similar_books.description\"]\n\n    scope do\n      joins \"left outer join books similar_books on ...\"\n    end\n\n    aliases similar_books: Book # Tell SearchCop how to map SQL aliases to models\n  end\n\n  # ...\nend\n```\n\nAssocations of associations can as well be referenced and used:\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  # ...\n\n  has_many :comments\n  has_many :users, :through =\u003e :comments\n\n  search_scope :search do\n    attributes user: \"users.username\"\n  end\n\n  # ...\nend\n```\n\n## Custom table names and associations\n\nSearchCop tries to infer a model's class name and SQL alias from the\nspecified attributes to autodetect datatype definitions, etc. This usually\nworks quite fine. In case you're using custom table names via `self.table_name\n= ...` or if a model is associated multiple times, SearchCop however can't\ninfer the class and SQL alias names, e.g.\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  # ...\n\n  has_many :users, :through =\u003e :comments\n  belongs_to :user\n\n  search_scope :search do\n    attributes user: [\"user.username\", \"users_books.username\"]\n  end\n\n  # ...\nend\n```\n\nHere, for queries to work you have to use `users_books.username`, because\nActiveRecord assigns a different SQL alias for users within its SQL queries,\nbecause the user model is associated multiple times. However, as SearchCop\nnow can't infer the `User` model from `users_books`, you have to add:\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  # ...\n\n  search_scope :search do\n    # ...\n\n    aliases :users_books =\u003e :users\n  end\n\n  # ...\nend\n```\n\nto tell SearchCop about the custom SQL alias and mapping. In addition, you can\nalways do the joins yourself via a `scope {}` block plus `aliases` and use your\nown custom sql aliases to become independent of names auto-assigned by\nActiveRecord.\n\n## Supported operators\n\nQuery string queries support `AND/and`, `OR/or`, `:`, `=`, `!=`, `\u003c`, `\u003c=`,\n`\u003e`, `\u003e=`, `NOT/not/-`, `()`, `\"...\"` and `'...'`. Default operators are `AND`\nand `matches`, `OR` has precedence over `AND`. `NOT` can only be used as infix\noperator regarding a single attribute.\n\nHash based queries support `and: [...]` and `or: [...]`, which take an array\nof `not: {...}`, `matches: {...}`, `eq: {...}`, `not_eq: {...}`,\n`lt: {...}`, `lteq: {...}`, `gt: {...}`, `gteq: {...}` and `query: \"...\"`\narguments. Moreover, `query: \"...\"` makes it possible to create sub-queries.\nThe other rules for query string queries apply to hash based queries as well.\n\n### Custom operators (Hash based queries)\n\nSearchCop also provides the ability to define custom operators by defining a\n`generator` in `search_scope`. They can then be used with the hash based query\nsearch. This is useful when you want to use database operators that are not\nsupported by SearchCop.\n\nPlease note, when using generators, you are responsible for sanitizing/quoting\nthe values (see example below). Otherwise your generator will allow SQL\ninjection. Thus, please only use generators if you know what you're doing.\n\nFor example, if you wanted to perform a `LIKE` query where a book title starts\nwith a string, you can define the search scope like so:\n\n```ruby\nsearch_scope :search do\n  attributes :title\n\n  generator :starts_with do |column_name, raw_value|\n    pattern = \"#{raw_value}%\"\n    \"#{column_name} LIKE #{quote pattern}\"\n  end\nend\n```\n\nWhen you want to perform the search you use it like this:\n\n```ruby\nBook.search(title: { starts_with: \"The Great\" })\n```\n\nSecurity Note: The query returned from the generator will be interpolated\ndirectly into the query that goes to your database. This opens up a potential\nSQL Injection point in your app. If you use this feature you'll want to make\nsure the query you're returning is safe to execute.\n\n## Mapping\n\nWhen searching in boolean, datetime, timestamp, etc. fields, SearchCop\nperforms some mapping. The following queries are equivalent:\n\n```ruby\nBook.search(\"available:true\")\nBook.search(\"available:1\")\nBook.search(\"available:yes\")\n```\n\nas well as\n\n```ruby\nBook.search(\"available:false\")\nBook.search(\"available:0\")\nBook.search(\"available:no\")\n```\n\nFor datetime and timestamp fields, SearchCop expands certain values to\nranges:\n\n```ruby\nBook.search(\"created_at:2014\")\n# ... WHERE created_at \u003e= '2014-01-01 00:00:00' AND created_at \u003c= '2014-12-31 23:59:59'\n\nBook.search(\"created_at:2014-06\")\n# ... WHERE created_at \u003e= '2014-06-01 00:00:00' AND created_at \u003c= '2014-06-30 23:59:59'\n\nBook.search(\"created_at:2014-06-15\")\n# ... WHERE created_at \u003e= '2014-06-15 00:00:00' AND created_at \u003c= '2014-06-15 23:59:59'\n```\n\n## Chaining\n\nChaining of searches is possible. However, chaining does currently not allow\nSearchCop to optimize the individual queries for fulltext indices.\n\n```ruby\nBook.search(\"Harry\").search(\"Potter\")\n```\n\nwill generate\n\n```ruby\n# MySQL: ... WHERE MATCH(...) AGAINST('+Harry' IN BOOLEAN MODE) AND MATCH(...) AGAINST('+Potter' IN BOOLEAN MODE)\n# PostgreSQL: ... WHERE to_tsvector(...) @@ to_tsquery('simple', 'Harry') AND to_tsvector(...) @@ to_tsquery('simple', 'Potter')\n```\n\ninstead of\n\n```ruby\n# MySQL: ... WHERE MATCH(...) AGAINST('+Harry +Potter' IN BOOLEAN MODE)\n# PostgreSQL: ... WHERE to_tsvector(...) @@ to_tsquery('simple', 'Harry \u0026 Potter')\n```\n\nThus, if you use fulltext indices, you better avoid chaining.\n\n## Debugging\n\nWhen using `Model#search`, SearchCop conveniently prevents certain\nexceptions from being raised in case the query string passed to it is invalid\n(parse errors, incompatible datatype errors, etc). Instead, `Model#search`\nreturns an empty relation. However, if you need to debug certain cases, use\n`Model#unsafe_search`, which will raise them.\n\n```ruby\nBook.unsafe_search(\"stock: None\") # =\u003e raise SearchCop::IncompatibleDatatype\n```\n\n## Reflection\n\nSearchCop provides reflective methods, namely `#attributes`,\n`#default_attributes`, `#options` and `#aliases`. You can use these methods to\ne.g. provide an individual search help widget for your models, that lists the\nattributes to search in as well as the default ones, etc.\n\n```ruby\nclass Product \u003c ActiveRecord::Base\n  include SearchCop\n\n  search_scope :search do\n    attributes :title, :description\n\n    options :title, default: true\n  end\nend\n\nProduct.search_reflection(:search).attributes\n# {\"title\" =\u003e [\"products.title\"], \"description\" =\u003e [\"products.description\"]}\n\nProduct.search_reflection(:search).default_attributes\n# {\"title\" =\u003e [\"products.title\"]}\n\n# ...\n```\n\n## Semantic Versioning\n\nStarting with version 1.0.0, SearchCop uses Semantic Versioning:\n[SemVer](http://semver.org/)\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\n","funding_links":[],"categories":["Ruby","Search"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrkamel%2Fsearch_cop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrkamel%2Fsearch_cop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrkamel%2Fsearch_cop/lists"}