Gem Version Build Status Maintainability Test Coverage

Index

Description and History

Active Record Extended is the continuation of maintaining and improving the work done by Dan McClain, the original author of postgres_ext.

Overtime the lack of updating to support the latest versions of ActiveRecord 5.x has caused quite a bit of users forking off the project to create their own patches jobs to maintain compatibility. The only problem is that this has created a wild west of environments of sorts. The problem has grown to the point no one is attempting to directly contribute to the original source. And forked repositories are finding themselves as equally as dead with little to no activity.

Active Record Extended is intended to be a supporting community that will maintain compatibility for the foreseeable future.

Usage

Query Methods

Any

Postgres 'ANY' expression

In Postgres the ANY expression is used for gather record's that have an Array column type that contain a single matchable value within its array.

alice = User.create!(tags: [1])
bob   = User.create!(tags: [1, 2])
randy = User.create!(tags: [3])

User.where.any(tags: 1) #=> [alice, bob] 

This only accepts a single value. So querying for example multiple tag numbers [1,2] will return nothing.

All

Postgres 'ALL' expression

In Postgres the ALL expression is used for gather record's that have an Array column type that contains only a single and matchable element.

alice = User.create!(tags: [1])
bob   = User.create!(tags: [1, 2])
randy = User.create!(tags: [3])

User.where.all(tags: 1) #=> [alice] 

This only accepts a single value to a given attribute. So querying for example multiple tag numbers [1,2] will return nothing.

Contains

Postgres '@>' (Array type) Contains expression

Postgres '@>' (JSONB/HSTORE type) Contains expression

The contains/1 method is used for finding any elements in an Array, JSONB, or HSTORE column type. That contains all of the provided values.

Array Type:

alice = User.create!(tags: [1, 4])
bob   = User.create!(tags: [3, 1])
randy = User.create!(tags: [4, 1])

User.where.contains(tags: [1, 4]) #=> [alice, randy]

HSTORE / JSONB Type:

alice = User.create!(data: { nickname: "ARExtend" })
bob   = User.create!(data: { nickname: "ARExtended" })
randy = User.create!(data: { nickname: "ARExtended" })

User.where.contains(data: { nickname: "ARExtended" }) #=> [bob, randy]

Overlap

Postgres && (overlap) Expression

The overlap/1 method will match an Array column type that contains any of the provided values within its column.

alice = User.create!(tags: [1, 4])
bob   = User.create!(tags: [3, 4])
randy = User.create!(tags: [4, 8])

User.where.overlap(tags: [4]) #=> [alice, bob, randy]
User.where.overlap(tags: [1, 8]) #=> [alice, randy]
User.where.overlap(tags: [1, 3, 8]) #=> [alice, bob, randy]

Inet / IP Address

Inet Contains

Postgres >> (contains) Network Expression

The inet_contains method works by taking a column(inet type) that has a submask prepended to it. And tries to find related records that fall within a given IP's range.

alice = User.create!(ip: "127.0.0.1/16")
bob   = User.create!(ip: "192.168.0.1/16")

User.where.inet_contains(ip: "127.0.0.254") #=> [alice]
User.where.inet_contains(ip: "192.168.20.44") #=> [bob]
User.where.inet_contains(ip: "192.255.1.1") #=> []
Inet Contains or Equals

Postgres >>= (contains or equals) Network Expression

The inet_contains_or_equals method works much like the Inet Contains method, but will also accept a submask range.

alice = User.create!(ip: "127.0.0.1/10")
bob   = User.create!(ip: "127.0.0.44/24")

User.where.inet_contains_or_equals(ip: "127.0.0.1/16") #=> [alice]
User.where.inet_contains_or_equals(ip: "127.0.0.1/10") #=> [alice]
User.where.inet_contains_or_equals(ip: "127.0.0.1/32") #=> [alice, bob]
Inet Contained Within

Postgres << (contained by) Network Expression

For the inet_contained_within method, we try to find IP's that fall within a submasking range we provide.

alice = User.create!(ip: "127.0.0.1")
bob   = User.create!(ip: "127.0.0.44") 
randy = User.create!(ip: "127.0.55.20")

User.where.inet_contained_within(ip: "127.0.0.1/24") #=> [alice, bob]
User.where.inet_contained_within(ip: "127.0.0.1/16") #=> [alice, bob, randy]
Inet Contained Within or Equals

Postgres <<= (contained by or equals) Network Expression

The inet_contained_within_or_equals method works much like the Inet Contained Within method, but will also accept a submask range.

alice = User.create!(ip: "127.0.0.1/10")
bob   = User.create!(ip: "127.0.0.44/32")
randy = User.create!(ip: "127.0.99.1")

User.where.inet_contained_within_or_equals(ip: "127.0.0.44/32") #=> [bob]
User.where.inet_contained_within_or_equals(ip: "127.0.0.1/16") #=> [bob, randy]
User.where.inet_contained_within_or_equals(ip: "127.0.0.44/8") #=> [alice, bob, randy]
Inet Contains or Contained Within

Postgres && (contains or is contained by) Network Expression

The inet_contains_or_contained_within method is a combination of Inet Contains and Inet Contained Within. It essentially (the database) tries to use both methods to find as many records as possible that match either condition on both sides.

alice = User.create!(ip: "127.0.0.1/24")
bob   = User.create!(ip: "127.0.22.44/8")
randy = User.create!(ip: "127.0.99.1")

User.where.inet_contains_or_is_contained_within(ip: "127.0.255.80") #=> [bob]
User.where.inet_contains_or_is_contained_within(ip: "127.0.0.80") #=> [alice, bob]
User.where.inet_contains_or_is_contained_within(ip: "127.0.0.80/8") #=> [alice, bob, randy]

Conditional Methods

Any_of / None_of

any_of/1 simplifies the process of finding records that require multiple or conditions.

none_of/1 is the inverse of any_of/1. It'll find records where none of the contains are matched.

Both accepts An array of: ActiveRecord Objects, Query Strings, and basic attribute names.

Querying With Attributes:

alice = User.create!(uid: 1)
bob   = User.create!(uid: 2)
randy = User.create!(uid: 3)

User.where.any_of({ uid: 1 }, { uid: 2 }) #=> [alice, bob]

Querying With ActiveRecord Objects:

alice = User.create!(uid: 1)
bob   = User.create!(uid: 2)
randy = User.create!(uid: 3)

uid_one = User.where(uid: 1)
uid_two = User.where(uid: 2)

User.where.any_of(uid_one, uid_two) #=> [alice, bob]

Querying with Joined Relationships:

alice     = User.create!(uid: 1)
bob       = User.create!(uid: 2)
randy     = User.create!(uid: 3)
tag_alice = Tag.create!(user_id: alice.id)
tag_bob   = Tag.create!(user_id: bob.id)
tag_randy = Tag.create!(user_id: randy.id)

bob_tag_query   = Tag.where(users: { id: bob.id }).includes(:user)
randy_tag_query = Tag.where(users: { id: randy.id }).joins(:user)

Tag.joins(:user).where.any_of(bob_tag_query, randy_tag_query) #=> [tag_bob, tag_randy] (with users table joined)

Either Join

The #either_join/2 method is a base ActiveRecord querying method that will joins records based on a set of conditionally joinable tables.

class User < ActiveRecord::Base
  has_one :profile_l, class: "ProfileL"
  has_one :profile_r, class: "ProfileR"

  scope :completed_profile, -> { either_joins(:profile_l, :profile_r) }
end

alice = User.create!
bob   = User.create!
randy = User.create! # Does not have a single completed profile type
ProfileL.create!(user_id: alice.id)
ProfileR.create!(user_id: bob.id)

User.completed_profile #=> [alice, bob]
# alternatively
User.either_joins(:profile_l, :profile_r) #=> [alice, bob]

Either Order

The #either_order/3 method is a base ActiveRecord querying method that will order a set of columns that may or may not exist for each record. This works similar to how Either Join works. This does not however exclude records that do not have relationships.

alice = User.create!
bob   = User.create!
ProfileL.create!(user_id: alice.id, left_turns: 100)
ProfileR.create!(user_id: bob.id, right_turns: 50)

User.either_order(:asc, profile_l: :left_turns, profile_r: :right_turns) #=> [bob, alice]
User.either_order(:desc, profile_l: :left_turns, profile_r: :right_turns) #=> [alice, bob]

randy = User.create!
User.either_order(:asc, profile_l: :left_turns, profile_r: :right_turns) #=> [bob, alice, randy]
User.either_order(:desc, profile_l: :left_turns, profile_r: :right_turns) #=> [randy, alice, bob]

Installation

Add this line to your application's Gemfile:

gem 'active_record_extended'

And then execute:

$ bundle

Or install it yourself as:

$ gem install active_record_extended

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

License

The gem is available as open source under the terms of the MIT License.