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.

Empty? vs blank? vs any? – why you should not use any? to test if there’s anything in the array

I often work with junior programmers and somehow they tend to use syntax like this:

return unless array_data.any?
# or
do something if array_data.any?

And there’s almost nothing wrong with it, as long as you don’t work with huge arrays.

Here’s a difference in speed between empty?, blank? and any? with different array sizes (worse scenario):

worse-scenario

Why any? is so slow with bigger arrays? Well, basically because it iterates through the whole the collection and checks if there’s at least one non-false and non-nil element (when no block given). Of course it will stop with the first non-false value, but we were pessimistic and we assumed that there are no elements like that. Many developers assume that any? answers the: “is there anything in the array” question, when it really answers: “is there at least one non-false and non-nil element in the array”. And this can make huge difference with bigger arrays.

Of course if we decide to have a true-like value in a random place of an array, things look a bit different:

randomMore or less it looks randomly, but you can see a general pattern – the more elements there are, the longer it can get.

Conclusion: Using any? to determine, if there’s anything in the array can have a huge impact on your code performance if you work with bigger arrays. And even if you don’t, having bad habits is not a good idea.

02
Jun 2015
POSTED BY
POSTED IN Ruby Software
DISCUSSION 2 Comments