ActiveRecord Follow Assoc

Test supported versions

Let's say that, in your Rails app, you want to get all of the comments to the recent posts the current user made.

Think of how you would do it.

Here's how this gem allows you to do it:

current_user.posts.recent.follow_assoc(:comments)

The follow_assoc method, added by this gem, allows you to query the specified association of the records that the current query would return.

Here is a more complete introduction to this gem.

Benefits of follow_assoc:

  • Works the same way for all kinds of association belongs_to, has_many, has_one, has_and_belongs_to_many
  • You can use where, order and other such methods on the result
  • By nesting SQL queries, the only records that need to be loaded are the final ones, so the above example wouldn't have loaded any Post from the database. This usually leads to faster code.
  • You avoid many problems with the alternative options.

Why / when do you need this?

As applications grow, you can end up with quite complex data model and even more complex business rules. You may end up needing to fetch records that are deep in your associations.

As a simple example, let's say you have a helper which receives sections of a blog and must return the recent comments in those sections.

def recent_comments_within(sections)
  sections.follow_assoc(:posts, :comments).recent
end

Note that this won't work if sections is an Array. follow_assoc is available in the same places as where. See Usage for details.

Doing this without follow_assoc can be verbose, error-prone and less efficient depending on the approach taken.

Installation

Rails 4.1 to 6.1 are supported with Ruby 2.1 to 3.0. Tested against SQLite3, PostgreSQL and MySQL. The gem only depends on the activerecord gem.

Add this line to your application's Gemfile:

gem 'activerecord_follow_assoc'

And then execute:

$ bundle install

Or install it yourself with:

$ gem install activerecord_follow_assoc

Usage

Starting from a query or a model, you call follow_assoc with an association's name. It returns another query that:

  • searches in the association's model
  • has a where to only return the records that are associated with the records that the initial query would have returned.

So my_comments.follow_assoc(:posts) gives you a query on Post which only returns the posts that are associated to the records of my_comments.

# Getting the spam comments to posts by a specific author
spam_comments = author.posts.follow_assoc(:comments).spam

As a shortcut, you can also give multiple association to follow_assoc. Doing so is equivalent to consecutive calls to it.

# Getting the spam comments to posts in some sections
spam_comments_in_section = my_sections.follow_assoc(:posts, :comments).spam
# Equivalent to
spam_comments_in_section = my_sections.follow_assoc(:posts).follow_assoc(:comments).spam

The follow_assoc method is only available on models and queries (also often called relation or scope). You cannot use it on an Array of record. If you need to use follow_assoc in that situation, then you must make a query yourself:

sections_query = Section.where(id: my_sections)
# Then you can use `follow_assoc`
spam_comments_in_section = sections_query.follow_assoc(:posts, :comments).spam

Detailed doc is here.

Known issues

No support for recursive has_one

The SQL to handle recursive has_one while isolating the different layers of conditions is a mess and I worry about the resulting performance. So for now, this will raise an exception. You can use the ignore_limit: true option to treat the has_one as a has_many.

MySQL doesn't support sub-limit

On MySQL databases, it is not possible to use has_one associations.

I do not know of a way to do a SQL query that can deal with all the specifics of has_one for MySQL. If you have one, then please suggest it in an issue/pull request.

In order to work around this, you must use the ignore_limit: true option, which means that the has_one will be treated like a has_many.

If you feel a need for this gem's feature, you may also be interested in another gem I made: activerecord_where_assoc.

It allows you to make conditions based on your associations (without changing the kind of objects returned). For simple cases, it's possible that both can build the query your need, but each can handle different situations. Here is an example:

# Find every posts that have comments by an admin
Post.where_assoc_exists([:comments, :author], &:admins)

This could be done with follow_assoc: User.admins.follow_assoc(:comments, :post). But if you wanted conditions on a second association, then follow_assoc wouldn't work. On the other hand, if you received a scope on users and wanted their posts, then follow_assoc would be a nicer tool for the job. It all depends on the context where you need to do the query and what starting point you have.

Development

After checking out the repo, run bundle install to install dependencies. Then, run rspec 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.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/MaxLap/activerecord_follow_assoc.

License

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

Code of Conduct

Everyone interacting in the ActiveRecordFollowAssoc project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.