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.
June 30, 2015 — 12:48
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.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.
July 2, 2015 — 03:23
I normally just place them as scopes / class methods, then use them directly
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.
July 2, 2015 — 21:24
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.
July 3, 2015 — 03:03
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!