TolichP/thorderbolt

View on GitHub
README.md

Summary

Maintainability
Test Coverage
[![Build Status](https://travis-ci.org/privorotskii/thorderbolt.svg?branch=master)](https://travis-ci.org/privorotskii/thorderbolt) [![Test Coverage](https://api.codeclimate.com/v1/badges/2a3289492309d6f7740c/test_coverage)](https://codeclimate.com/github/privorotskii/thorderbolt/test_coverage) [![Gem Version](https://badge.fury.io/rb/thorderbolt.svg)](https://badge.fury.io/rb/thorderbolt)

# Thorderbolt

`Thorderbolt` adds the ability to order `ActiveRecord` relation in an arbitrary order without having to store anything extra in the database.

It's as easy as:

```ruby
class User < ActiveRecord::Base
  extend Thorderbolt
end

User.order_as(name: ['John', 'Tom'])
=> #<ActiveRecord::Relation [
     #<User id: 3, name: 'John'>,
     #<User id: 1, name: 'Tom'>,
     #<User id: 2, name: 'Alex'>,
     #<User id: 4, name: 'Mike'>
   #]>
```

Ordering of each specified field as equal is also supported.
In that case usual order will be applied for all the satisfying condition records:

```ruby
User.order_as_any(name: ['John', 'Tom'])
=> #<ActiveRecord::Relation [
     #<User id: 1, name: 'Tom'>,
     #<User id: 3, name: 'John'>,
     #<User id: 2, name: 'Alex'>,
     #<User id: 4, name: 'Mike'>
   #]>
```

Using `thorderbolt` doesn't require any additional tables in DB.

This gem is heavily inspired by [order_as_specified](https://github.com/panorama-ed/order_as_specified), but strongly refactored with addition of some extra features.

## Installation

Add this line to your application Gemfile:

```ruby
gem 'thorderbolt'
```

And then execute:

    $ bundle

Or install it yourself as:

    $ gem install thorderbolt

## Usage

actually, each example below is true for `order_as` and `order_as_any` methods. The difference is that `order_as` fixes ordering between specified records, when `order_as_any` just puts specified records at the top and don't change ordering between them at all.

Basic usage is simple:

```ruby
class User < ActiveRecord::Base
  extend Thorderbolt
end

User.order_as(name: ['John', 'Tom'])
=> #<ActiveRecord::Relation [
     #<User id: 3, name: 'John'>,
     #<User id: 1, name: 'Tom'>,
     #<User id: 2, name: 'Alex'>,
     #<User id: 4, name: 'Mike'>
   #]>
```

This returns all `Users` ordered by the given names. Note that this
ordering is not possible with a simple `ORDER BY`. Magic!

Like any other `ActiveRecord` relation, it can be chained:

```ruby
User
  .where(name: ['John', 'Tom', 'Mike']).
  .order_as(name: ['John', 'Tom'])
  .limit(3)
=> #<ActiveRecord::Relation [
     #<User id: 3, name: 'John'>,
     #<User id: 1, name: 'Tom'>,
     #<User id: 4, name: 'Mike'>
   ]>
```

We can use chaining in this way to order by multiple attributes as well:

```ruby
User.
  order_as(name: ['John', 'Mike']).
  order_as(id: [4, 3, 5]).
  order(:updated_at)
=> #<ActiveRecord::Relation [

  # First is name 'John'...
     #<User id: 1, name: 'John', updated_at: '2020-08-01 02:22:00'>,

  # Within the name, we order by :updated_at...
     #<User id: 2, name: 'John', updated_at: '2020-08-01 07:29:07'>,

  # Then name 'Mike'...
     #<User id: 9, name: 'Mike', updated_at: '2020-08-03 04:11:26'>,

    # Within the name, we order by :updated_at...
     #<User id: 8, name: 'Mike', updated_at: '2020-08-04 18:52:14'>,

  # Then id 4...
     #<User id: 4, name: 'Alex', updated_at: '2020-08-01 12:59:33'>,

  # Then id 3...
     #<User id: 3, name: 'Tom', updated_at: '2020-08-02 19:41:44'>,

  # Then id 5...
     #<User id: 5, name: 'Tom', updated_at: '2020-08-02 22:12:52'>,

  # Then we order by :updated_at...
     #<User id: 7, name: 'Alex', updated_at: '2020-08-02 14:27:16'>,
     #<User id: 6, name: 'Tom', updated_at: '2020-08-03 14:26:06'>,
   ]>
```

We can also use this when we want to sort by an attribute in another model:

```ruby
User
  .joins(:city)
  .order_as(cities: { id: [first_city.id, second_city.id, third_city.id] })
```

In all the cases, results with attribute values which aren't in tghe given list will be
sorted as if the attribute is `NULL` in a typical `ORDER BY`:

```ruby
User.order_as(name: ['Tom', 'John'])
=> #<ActiveRecord::Relation [
     #<User id: 2, name: 'Tom'>,
     #<User id: 3, name: 'John'>,
     #<User id: 1, name: 'Mike'>,
     #<User id: 4, name: 'Mike'>
   ]>
```

Note that an error is raised if a `nil` value was passed in the ordering params, because
databases do not have good or consistent support for ordering with `NULL` values
in an arbitrary order, so this behavior isn't permitted.

## Limitations

Databases may have limitations on the underlying number of fields you can have
in an `ORDER BY` clause. For example, in PostgreSQL if you pass in more than
1664 list elements you'll receive such error:

```ruby
PG::ProgramLimitExceeded: ERROR: target lists can have at most 1664 entries
```
That's a database limitation that this gem cannot avoid, unfortunately.

## Contributing

1. Fork it (https://github.com/privorotskii/thorderbolt/fork)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request

Please, make sure your changes have appropriate tests (`bundle exec rspec`) and conform to the Rubocop style specified.

## License

`Thorderbolt` is released under the [MIT License](https://github.com/privorotskii/thorderbolt/blob/master/LICENSE.txt).