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:
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:
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, but strongly refactored with addition of some extra features.
Installation
Add this line to your application Gemfile:
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:
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:
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:
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:
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
:
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:
PG::ProgramLimitExceeded: ERROR: target lists can have at most 1664 entries
That's a database limitation that this gem cannot avoid, unfortunately.
Contributing
- Fork it (https://github.com/privorotskii/thorderbolt/fork)
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - 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.