{"id":24304193,"url":"https://github.com/trackingboard/collate","last_synced_at":"2025-10-06T09:31:32.191Z","repository":{"id":62556015,"uuid":"80760578","full_name":"trackingboard/collate","owner":"trackingboard","description":"Add some DSL to your model, then run a single method in your controller, and your model now accepts filtering through the parameters.","archived":false,"fork":false,"pushed_at":"2017-10-29T06:19:41.000Z","size":182,"stargazers_count":6,"open_issues_count":2,"forks_count":1,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-01-16T16:26:49.317Z","etag":null,"topics":["gem","ruby","ruby-on-rails"],"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/trackingboard.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}},"created_at":"2017-02-02T19:27:34.000Z","updated_at":"2017-10-08T17:32:50.000Z","dependencies_parsed_at":"2022-11-03T05:45:37.874Z","dependency_job_id":null,"html_url":"https://github.com/trackingboard/collate","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trackingboard%2Fcollate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trackingboard%2Fcollate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trackingboard%2Fcollate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trackingboard%2Fcollate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/trackingboard","download_url":"https://codeload.github.com/trackingboard/collate/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235519081,"owners_count":19003137,"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":["gem","ruby","ruby-on-rails"],"created_at":"2025-01-17T01:16:58.612Z","updated_at":"2025-10-06T09:31:26.863Z","avatar_url":"https://github.com/trackingboard.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Collate\n\n[![CircleCI](https://img.shields.io/circleci/project/github/trackingboard/collate.svg)](https://circleci.com/gh/trackingboard/collate)\n[![Coveralls](https://img.shields.io/coveralls/trackingboard/collate.svg)](https://coveralls.io/github/trackingboard/collate?branch=master)\n[![Gem](https://img.shields.io/gem/v/collate.svg)](https://rubygems.org/gems/collate)\n[![Gem](https://img.shields.io/gem/dt/collate.svg)](https://rubygems.org/gems/collate)\n\n## Installation\n\n```gem install collate```\n\nor with bundler in your Gemfile:\n\n```gem 'collate'```\n\n## Filter Usage\n\nThis gem currently only supports PostgreSQL.\n\nTo use collate filtering in a model, include several collation definitions. The first argument is the name of the database column to use in the query. The simplest example looks like this:\n\n```\nclass Person \u003c ActiveRecord::Base\n\tcollate_on :name\nend\n```\n\nThis will add a filter to the model that will grab all records where the ```name``` column equals the parameter that is passed in.\n\nThen you only need to use the ```collate``` method in the controller, passing the params:\n\n```\n@people = Person.collate(params)\n```\n\nThe params key needs to match the key that the gem is expecting. You can currently find out what that key is by iterating over the ```collate_filters``` hash on the model's class, or you can create a filter that matches the definition in the model, and grab the param_key like this:\n\n```\nfilter = Collate::Filter.new(:name, base_model_table_name: \"people\")\n\nparams[filter.param_key] = 'John Doe'\n\n@people = Person.collate(params)\n```\n\n### Operators\n\nYou can currently collate using multiple types of operators. To specify an operator to collate on, you can pass in the keyword argument ```operator```, like this:\n\n```\ncollate_on :name, operator: :ilike\n```\n\nTranslates to:\n\n```\nWHERE name ILIKE ?\n```\n\nHere are the currently available operators:\n\n| Operator | Behavior |\n|:------------------|:--------------------|\n| ```:eq```         | ```field = ?``` |\n| ```:ilike```      | ```field ILIKE ?``` |\n| ```:in```         | ```field IN (?)``` |\n| ```:le```         | ```field \u003c= ?``` |\n| ```:ge```         | ```field \u003e= ?``` |\n| ```:null```       | ```field IS NULL``` |\n| ```:contains```   | ```field @\u003e ?``` |\n| ```:present?```   | ```field = ?``` |\n| ```:\u0026```          | ```field \u0026 ?``` |\n\n### Field Transformations\n\nField transformations are database functions applied to a field before the operator is used to compare it with the value. Field transformations are passed in as an array of tuples, where the first element in the tuple is the symbol for the transfomation, and the second element is the first argument to the database function.\n\nFor example:\n\n```\ncollate_on :name, field_transformations: [[:split, ' ']]\n```\n\nThis would translate to this PostgreSQL query:\n\n```\nWHERE string_to_array(name, ' ') = ?\n```\n\nHere are the available field transformations:\n\n| Transformation | Behavior |\n|:-------------------------|:--------------------|\n| ```:date_difference``` | ```date_difference(arg1, field)``` |\n| ```:date_part```       | ```date_part(arg1, field)``` |\n| ```:array_agg```       | ```array_agg(field)``` |\n| ```:downcase```        | ```lower(field)``` |\n| ```:split```           | ```string_to_array(field, arg1)``` |\n| ```:array_length```    | ```array_length(field, arg1)``` |\n\nThese transformations can also be chained together on the same filter. They are applied in the order they appear in the array that is passed in.\n\nFor example:\n\n```\ncollate_on :name, field_transformations: [[:split, ' '], [:array_length, 1]]\n```\n\nTranslates to this PostgreSQL query:\n\n```\nWHERE array_length(string_to_array(name, ' '), 1) = ?\n```\n\n### Value Transformations\n\nValue transformations are functions applied to the user-supplied value before it is passed to the database query. They are passed in the same way as the field transmorations, as an array of tuples.\n\nFor example:\n\n```\ncollate_on :name, value_transformations: [[:join, ', ']]\n```\n\nTranslates to the following code:\n\n```\nvalue = value.join(', ')\nar_rel = ar_rel.where(\"name = ?\", value)\n```\n\nHere are the available value transformations:\n\n| Transformation | Behavior |\n|:-------------------------|:--------------------|\n| ```:join```        | ```value = value.join(arg1)``` |\n| ```:as_array```    | ```value = \"{#{value}}\"``` |\n| ```:downcase```    | ```value = value.downcase``` |\n| ```:string_part``` | ```value = \"%#{value}%\"``` |\n\n### Additional Arguments\n\nThere are many other additional arguments you can initialize a filter with. Here is a list of all of them:\n\n#### Label\n--------------\n```\ncollate_on :name, label: 'Character Name'\n```\n\nThis argument will overwrite the default label for the filter, which is ```field.to_s.titleize```\n\n#### Or\n--------------\n```\ncollate_on :name, or: true\n```\n\nThis argument causes the query to use \"OR\" for each element of the filter value array passed in from the user. For example, if the user passed in ```[\"John Doe\", \"Jane Doe\"]``` as the parameter for the above collation, this is the PostgreSQL query that would result:\n\n```\nWHERE name = \"John Doe\" OR name = \"Jane Doe\"\n```\n\n#### Not\n--------------\n```\ncollate_on :name, not: true\n```\n\nThis argument causes the entire query to be surrounded by a NOT(). The above, for example, translates to this PostgreSQL query:\n\n```\nWHERE NOT(name = ?)\n```\n\n#### Having\n--------------\n```\ncollate_on :name, having: true\n```\n\nThis argument tells the gem to use ```having``` instead of ```where``` in the ActiveRecord query. The above example then becomes:\n\n```\nHAVING name = ?\n```\n\n#### Joins\n--------------\n```\ncollate_on 'genres.id', operator: :in, joins: [:genres, :movies =\u003e [:people]]\n```\n\nThis argument tells the gem to use the ActiveRecord ```joins``` method with the value passed in. You can pass in an array of values, and it will evaluate each one in succession. The above code would then run this before any query is evaluated:\n\n```\nar_rel = ar_rel.joins(:genres)\nar_rel = ar_rel.joins(:movies =\u003e [:people])\n```\n\n#### Joins_prefix\n--------------\n```\ncollate_on 'select_genres.id', operator: :in, joins: [:genres], joins_prefix: 'select_'\n```\n\nThis argument will tell the gem to join in the relations specified in the ```joins``` argument, but to prefix all table names with the prefix specified. The above code would then translate to the following PostgreSQL query:\n\n```\nINNER JOIN genres AS select_genres ON ...\n```\n\n#### Component\n--------------\n```\ncollate_on 'genres.id', operator: :in, component: {load_records: true}\n```\n\nThis argument is used for rendering in the views. Currently the gem does not have helper methods for the views, but when those are added, that is how you would set the various options.\n\n### The View\n\nTo know the ```params``` key to use for each filter, you need to look at the filter's ```param_key``` method value. The filters are organized in a hierarchal scheme in the class variable ```collate_filters``` for the model you included the DSL on.\n\nHere is a small example of a few filters:\n\n```\nclass Person \u003c ActiveRecord::Base\n\tcollate_on :name, operator: :ilike\n\tcollate_on :birthday, operator: :le, label: 'Birthday Before'\nend\n```\n\nAnd here is how ```Person.collate_filters``` would look like:\n\n```\n{\n\t:main=\u003e {\n\t\t:label=\u003e\"Main\",\n\t\t:filters=\u003e [\n\t\t\t#\u003cCollate::Filter\n\t\t\t@base_model_table_name=\"people\",\n\t\t\t@component={:type=\u003e\"string\"},\n\t\t\t@field=\"people.name\",\n\t\t\t@field_transformations={},\n\t\t\t@grouping=nil,\n\t\t\t@html_id=\"1people_name\",\n\t\t\t@joins=nil,\n\t\t\t@label=\"Name\",\n\t\t\t@operator=:ilike,\n\t\t\t@value_transformations={}\u003e\n\t\t\t,\n\t\t\t#\u003cCollate::Filter\n\t\t\t@base_model_table_name=\"people\",\n\t\t\t@component={:type=\u003e\"string\"},\n\t\t\t@field=\"people.birthday\",\n\t\t\t@field_transformations={},\n\t\t\t@grouping=nil,\n\t\t\t@html_id=\"1people_name\",\n\t\t\t@joins=nil,\n\t\t\t@label=\"Birthday Before\",\n\t\t\t@operator=:le,\n\t\t\t@value_transformations={}\u003e\n\t\t]\n\t}\n}\n```\n\nIn order to use this in a view, you could have some HAML like this:\n\n```\n= form_tag '', :method =\u003e :get do\n\t- Person.collate_filters.each do |group_key, group|\n\t\t- filters = group[:filters]\n\t\t- filters.each do |filter|\n\t\t\t- case filter.component[:type]\n\t\t\t- when \"string\"\n\t  \t\t\t= filter.label\n\t  \t\t\t%br\n\t  \t\t\t= text_field_tag filter.param_key, params[filter.param_key], id: \"#{filter.html_id}\", style:'width:100%'\n\n```\n\nThis will ensure that the keys that the inputs are submitted with match the parameter key that the gem is expecting for that specific filter.\n\n## Sorting Usage\n\nTo use collate sorting for a model, include several ```collate_sort``` defintions. For example:\n\n```\nclass Person \u003c ActiveRecord::Base\n\tcollate_sort :name\n\tcollate_sort :popularity\t\nend\n```\n\nThen, in the controller, use the ```collate``` method:\n\n```\n@people = Person.collate(params)\n```\n\nThis will cause the people to be sorted on either ```name``` or ```popularity``` if the ```params[:order]``` value is set appropriately.\n\n```params[:order]``` must have a value of the format ```\"#{table_name}.#{field_name} ASC``` or ```\"#{table_name}.#{field_name} DESC```\n\nFor example:\n\n```\nparams[:order] = \"people.name ASC\"\n@people = Person.collate(params)\n```\n\nWill result in the following PostgreSQL query:\n\n```\nSELECT * FROM people ORDER BY people.name ASC\n```\n\nYou can also pass parameter options to ```collate_sort``` for more complex sorting.\n\n#### Default\n--------------\n```\ncollate_sort :name, default: 'asc'\n```\n\nThis tells collate that this particular sorting should be performed if there is no other sorting specified by the user.\n\nIt is also the second sorting to be applied on top of another sort.\n\nFor instance:\n\n```\nclass Person \u003c ActiveRecord::Base\n\tcollate_sort :name, default: 'desc'\n\tcollate_sort :popularity\t\nend\n```\n\n```\nparams[:order] = 'people.popularity ASC'\n@people = Person.collate(params)\n```\n\nThis would result in the following PostgreSQL query:\n\n```\nORDER BY people.popularity ASC, people.name DESC\n```\n\n#### Joins\n--------------\n```\ncollate_sort 'posts.created_at', joins: [:posts]\n```\n\nThis will perform the ActiveRecord joins before the sorting is applied. Doing this will allow you to sort on fields that are on related models.\n\n#### Field_select\n--------------\n```\ncollate_sort 'post_count', joins: [:posts], field_select: 'COUNT(posts.*) as post_count'\n```\n\nThis will perform the ActiveRecord select before the sorting is applied. Doing this will allow you to sort on fields created using a subquery.\n\n#### Nulls_first\n--------------\n```\ncollate_sort :popularity, nulls_first: true\n```\n\nThis will append ```NULLS FIRST``` to the order portion of the database query\n\n#### Nulls_last\n--------------\n```\ncollate_sort :popularity, nulls_last: true\n```\n\nThis will append ```NULLS LAST``` to the order portion of the database query\n\n#### Label\n--------------\n```\ncollate_sort :popularity, label: 'Popularity Score'\n```\n\nThis will modify the default sorting label, which is ```field.to_s.titleize```.\n\n#### Asc_label\n--------------\n```\ncollate_sort :popularity, asc_label: 'Popularity Score Lowest to Highest'\n```\n\nThis will modify the default ascending sorting label, which is ```#{label} ⬇```.\n\n#### Desc_label\n--------------\n```\ncollate_sort :popularity, desc_label: 'Popularity Score Highest to Lowest'\n```\n\nThis will modify the default descending sorting label, which is ```#{label} ⬆```.\n\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/trackingboard/collate.\n\n1. Fork.\n2. Branch.\n3. Pull Request your feature branch or fix.\n4. 🍕\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrackingboard%2Fcollate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftrackingboard%2Fcollate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrackingboard%2Fcollate/lists"}