When Classes Collide (With Themselves)

Berel Levy
4 min readJun 17, 2020

How to avoid method naming conflicts in ActiveRecord when one model is associated to another model in more than one way.

Hello there, suppose you wanted to create a new image sharing platform for very wise people, called say, Instagramps. Smart coder that you are you’d of course be using Ruby, and, of course, ActiveRecord.

One of the many wonderful features ActiveRecord provides is the ability to endow a class with many association methods by just typing a few words. But don’t take my word for it, let’s start building this cool app and I’ll show you along the way.

Let’s start by building out the classes for our app.

class Image < ActiveRecord::Base
end
class User < ActiveRecord::Base
end
class Review < ActiveRecord::Base
end

By simply inheriting from ActiveRecord::Base our classes now have many great methods that integrate with their respective tables in our database. But, our classes don’t simply want to keep to themselves, they want to interact with each other, they are, after all, enlightened classes.

We turn, with hopeful eyes back to ActiveRecord and, sure enough, we get another great incantation; by simply adding a few words in each of our classes can have a whole new vocabulary they can use to dine at one another’s tables.

class Image < ActiveRecord::Base
has_many :reviews
end
class User < ActiveRecord::Base
has_many :reviews
end
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :image
end

Jokes aside, let’s break down the code we’ve just written.

First the structure, an Image can have many reviews and a User can leave many reviews. A review is left on one image by one person.

Now the methods, by including the words has_many :reviews in our image class now has many new methods to explore it’s association with the Review class. One example is the #reviews method.

image = Image.new
# give this image a bunch of reviews
image.reviews # returns an array with all the reviews associated with this image
=> [review1, review2, etc.]

Of course, since our Review class is related to both the User class and the Image class, you can pass it’s has_many abilities on, and in so doing you open up the fantastic scary world of many to many relationships.

class Image < ActiveRecord::Base
has_many :reviews
has_many :users, through: :reviews
end
class User < ActiveRecord::Base
has_many :reviews
has_many :images, through: :reviews
end

The has_many, through: macro will now give our Image class a bunch of methods to explore it’s relationship with the User class. So, for example, you can see a list of all users who have reviewed an image.

image = Image.new
# add a bunch of reviews
image.users # returns an array of users that left reviews all over this image.
=> [user1, user2, etc.]

Now the fun begins. You show this app to your grampa and he loves it so much he has a suggestion: why not give users the ability to react to images as well? 🥺😝🤯❤️😝😂

Well, that’s not so hard, is it? No really, I’ll show you! All you need to add is a Reaction class, right? Just like the Review class.

class Reaction < ActiveRecord::Base
belongs_to :user
belongs_to :image
end

And then we can add some methods to our User and Image class.

class Image < ActiveRecord::Base
has_many :reviews
has_many :reactions
has_many :users, through: :reviews
end
class User < ActiveRecord::Base
has_many :reviews
has_many :reactions
has_many :images, through: :reviews
end

Finally, we can even add methods to our Image class to see Users that have reacted to it and vice versa! Oh baby!

class Image < ActiveRecord::Base
has_many :reviews
has_many :reactions
has_many :users, through: :reviews
has_many :users, through: :reactions
end
class User < ActiveRecord::Base
has_many :reviews
has_many :reactions
has_many :images, through: :reviews
has_many :images, through: :reactions
end

Wow, what a rush. Programming is so powerful!

So, to see a list of users that reacted to our image we would run:

image = Image.new
# add a bunch of reviews
image.users # returns an array of users that reacted to this image.
=> [user1, user2, etc.]

But something is not right. I’ve seen that code recently, but with a different comment. Didn’t we just use Image.users to return an array of users that reviewed our image? What happened to that?

Oy Gevalt! When we added has_many :users, through: :reactions we overwrote the previous line; has_many :users, through: :reviews and now we have no method to access image’s reviewers.

Of course, it’s not so bad, after all the creators of ActiveRecord probably encountered this problem before us. We’re not inventing the wheel now, are we?

No, we aren’t; and it’s actually as simple as changing our code a wee tiny bit. First let’s change our code in the Review and Reaction classes:

class Review < ActiveRecord::Base
belongs_to :reviewer, class_name: "User", foreign_key: :user_id
belongs_to :reviewed_image, class_name: "Image", foreign_key: :image_id
end
class Reaction < ActiveRecord::Base
belongs_to :reactor, class_name: "User", foreign_key: :user_id
belongs_to :reacted_to_image, class_name: "Image", foreign_key: :image_id
end

Basically, by renaming the methods in Review and Reaction we can also rename the methods in our Image and User classes as below. Since the has_many, through: macro essentially passes on the singular method with a pluralized name.

class Image < ActiveRecord::Base
has_many :reviews
has_many :reactions
has_many :reviewers, through: :reviews
has_many :reactors, through: :reactions
end
class User < ActiveRecord::Base
has_many :reviews
has_many :reactions
has_many :reviewed_images, through: :reviews
has_many :reacted_to_images, through: :reactions
end

Now we can call image.reviewers to get an array of users that reviewed this image, and image.reactors to get an array of users that reacted to this image.

Acknowledgments:

Thanks to Eric Kim, my instructor at Flatiron FiDi, for introducing me to this concept.

Thanks to Andrew Santos, my coach at Flatiron FiDi, for helping me figure out the correct syntax for the answer.

--

--

Berel Levy

Experienced software engineer and data engineer who enjoys the finer things in coding.