Extending ActiveRecord association to cleanup your associations code

For some reason not many RoR developers know that they can extend ActiveRecord associations. This feature can be a great way to cleanup any relational code that is dependent on a parent resource (or to just add a simple functionalities to scopes). Here's a really simple example how you can use it:

class User < ActiveRecord::Base
  module GroupActions
    # Here you work on a scope
    def reactivate!
      # I know we could use update_all - this is not the case ;)
      map(&:reactivate!)
    end
  end

  belongs_to :group

  def reactivate!
    update!(active: true)
  end
end

class Group < ActiveRecord::Base
  has_many :users, extend: User::GroupActions
end

That way you can perform actions like this one:

# Looks much better than any method like current_group.reactivate_users!
current_group.users.reactivate!

Dynamic pagination (changeable per page on a parent resource)

You can also use a small trick to access parent model and it's data. It can be useful for example when implementing a dynamic pagination model (based on a parent key value) or any other functionality that somehow depends on a relation owning model. Instead of doing something like this in your controllers:

def index
  @pictures = current_gallery.pictures.per(current_gallery.per_page).page(current_page)
end

you can leave the implementation details out of it (which in general is a good idea):

def index
  @pictures = current_gallery.pictures.paginated(current_page)
end

And this is how you can achieve such a behavior:

class Gallery < ActiveRecord::Base
  has_many :pictures,
    extend: Picture::RelationExtensions

  validates :per_page,
    numericality: { only_integer: true },
    inclusion: { in: (1..100).to_a }
end

class Picture < ActiveRecord::Base
  module RelationExtensions
    def paginated(current_page)
      # We create a picture instance that has a gallery reference already
      # because in the controller - gallery is a current scope starting point
      page(current_page).per(self.new.gallery.per_page)
    end
  end

  belongs_to :gallery, inverse_of:  :pictures
end

It's a really great idea to use approach like this also because you can use it with any standard scope:

current_gallery.active.paginated(current_page)
# or if you return an active record association you can chain methods
current_gallery.active.paginated(current_page).reactivate!

Conclusion

If you want to hide implementation details of any association related action (especially when it uses owning model fields data), using ActiveRecord association can be a really good idea.

Categories: Rails, Ruby, Software

6 Comments

  1. Nice post!

    Another great use case of this would be one where you’re bulk updating a model, which encapsulates another model to hide its internal complexity from outsiders.

    A method like reactivate! can then also take care of updating the records of that encapsulated model, while hiding the complexity of updating several different types of records.

  2. Maciej Mensfeld

    June 30, 2015 — 15:56

    Yup. There are many more use-cases. I just wanted to show two simple that illustrate what can be achieved with such an approach.

  3. I normally just place them as scopes / class methods, then use them directly

  4. Maciej Mensfeld

    July 2, 2015 — 21:19

    as a class methods you cannot chain them. As a scopes they might be confusing – I don’t feel that scopes should have any logic like this.

  5. Scopes are effectively class methods, the only difference compared to a scope is that you are responsible for returning a relation. Though I would only chain them inside the model itself to prevent excessive coupling with the outside world.

  6. I admit I am wrong for saying scope (the AR one). But the class method one is chainable.
    I use class methods all the time!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Copyright © 2024 Closer to Code

Theme by Anders NorenUp ↑