README.md in active_hash_relation-1.2.0 vs README.md in active_hash_relation-1.4.0

- old
+ new

@@ -1,38 +1,40 @@ # ActiveHashRelation [ ![Codeship Status for kollegorna/active_hash_relation](https://app.codeship.com/projects/02f08850-cc66-0134-028f-5ad72e690a75/status?branch=master)](https://app.codeship.com/projects/200194) +ActiveHashRelation is a complete substitute of ActiveRecord::Relation that allows you to run ActiveRecord queries using only regular Hash using a very powerful yet simple API. It was initially built to allow front-end teams to specify from the API exactly what they need withoug bugging the backend developers, eventually emerged into its own little gem. + ## Introduction Simple gem that allows you to manipulate ActiveRecord::Relation using JSON. For instance: ```ruby -apply_filters(resource, {name: 'RPK', id: [1,2,3,4,5,6,7,8,9], start_date: {leq: "2014-10-19"}, act_status: "ongoing"}) +apply_filters(User.all, {name: 'filippos', created_at: {leq: "2016-10-19"}, role: 'regular', email: {like: 'vasilakis'}}) ``` -filter a resource based on it's associations: +filter a resource based on it's associations using a NOT filter: ```ruby -apply_filters(resource, {updated_at: { geq: "2014-11-2 14:25:04"}, unit: {id: 9}) +apply_filters(Microposts.all, {updated_at: { geq: "2014-11-2 14:25:04"}, user: {not: {email: vasilakisfil@gmail.com}}) ``` -or even filter a resource based on it's associations' associations: +or even filter a resource based on it's associations' associations using an OR filter: ```ruby -apply_filters(resource, {updated_at: { geq: "2014-11-2 14:25:04"}, unit: {id: 9, areas: {or: [{id: 22}, {id: 21}]} }}) +apply_filters(Comments.all, {updated_at: { geq: "2014-11-2 14:25:04"}, user: {id: 9, units: {or: [{id: 22}, {id: 21}]} }}) ``` -and the list could go on.. Basically your whole db is exposed\* there. It's perfect for filtering a collection of resources on APIs. +and the list could go on.. Basically you can run anything ActiveRecord supports, except from groupping. It's perfect for filtering a collection of resources on APIs. It should be noted that `apply_filters` calls `ActiveHashRelation::FilterApplier` class underneath with the same params. -_\*Actually nothing is exposed, but a user could retrieve resources based +You can also do [__aggregation queries__](#aggregation-queries), like `sum`, `avg`, `min` and `max` on any column. + +_\*A user could retrieve resources based on unknown attributes (attributes not returned from the API) by brute forcing -which might or might not be a security issue. If you don't like that check -[whitelisting](#whitelisting)._ +which might or might not be a security issue. If you don't like that check you can specify from the `params` exactly what is allowed and what is not allowed. For more information check: [whitelisting](#whitelisting)._ -*New*! You can now do [__aggregation queries__](#aggregation-queries). ## Installation Add this line to your application's Gemfile: - gem 'active_hash_relation', '~> 1.2.0 + gem 'active_hash_relation', '~> 1.3.0 And then execute: $ bundle @@ -59,11 +61,12 @@ render json: resources, each_serializer: Api::V1::ResourceSerializer end end ``` -If you **need to enable filtering on scopes**, you need to specify that explicitly from the initializer. For instance: +If you **need to enable filtering on scopes**, you need to specify that explicitly from the initializer. Please run: +`bundle exec rails g active_hash_relation:initialize` which will create an initializer with the following content: ```ruby ActiveHashRelation.configure do |config| #override default scope when accessing associations config.use_unscoped = true @@ -77,10 +80,11 @@ end #requires monkeyparched scopes, optional if you don't enable them ActiveHashRelation.initialize! ``` +If you are not using Rails, just add the code above in your equivelant initialize block. ## The API ### Columns For each param, `apply_filters` method will search in the model's (derived from the first param, or explicitly defined as the last param) all the record's column names @@ -138,13 +142,25 @@ However I would strongly advice you to use a pagination gem like Kaminari, and use `page` and `per_page` params. ### Sorting +You can apply sorting using the property as the key of the hash and order as the value. For instance: +* `{sort: {created_at: desc}}` + +You can also order by multiple attributes: +* `{sort: {created_at: desc, microposts_count: :asc}}` + +If there is no column named after the property value, sorting is skipped. + +#### Deprecated API (will be removed in version 2.0) You can apply sorting using the `property` and `order` attributes. For instance: -* `{sort: {property: 'created_at', order: 'desc'}}` +* `{sort: {property: :created_at, order: :desc}}` +You can also order by multiple attributes: +* `{sort: [{property: :created_at, order: :desc}, {property: :created_at, order: :desc}]}` + If there is no column named after the property value, sorting is skipped. ### Associations If the association is a `belongs_to` or `has_one`, then the hash key name must be in singular. If the association is `has_many` the attribute must be in plural reflecting the association type. When you have, in your hash, filters for an association, the sub-hash is passed in the association's model. For instance, let's say a user has many microposts and the following filter is applied (could be through an HTTP GET request on controller's index method): @@ -172,11 +188,24 @@ You can apply an `OR` on associations as well or even nested ones, there isn't much limitation on that. I suggest you though to take a look on the [tests](spec/tests/or_filter_spec.rb), cause the syntax gets a bit complex after a while ;) +### NOT Filter +You can apply an SQL `NOT` (for ActiveRecord 4+) using the following syntax: +`{not: {name: 'Filippos', email: {ends_with: '@gmail.com'}}}` + +It will generate: `WHERE (NOT (users.name = 'Filippos')) AND (NOT (users.email LIKE '%@gmail.com'))` + +You can apply an `NOT` on associations as well or even nested ones, there isn't much limitation on that. +I suggest you to also take a look on the [tests](spec/tests/not_filter_spec.rb). + +Also I should note that you need to add specific (partial) queries if you don't want +to have performance issues on tables with millions of rows. + + ### Scopes -**Filtering on scopes is not enabled by default**. +**Filtering on scopes is not enabled by default. You need to add the initializer mentioned in the beginning of the [How to use](#how-to-use) section.**. Scopes are supported via a tiny monkeypatch in the ActiveRecord's scope class method which holds the name of each scope. The monkey patch is as gentle as it can be: it aliases the method, adds some sugar and executes it. Scopes with arguments are also supported but not tested much. Probably they will work fine unless your arguments expect