7

I have two ActiveRecord models which have a HABTM association.

I want to write a scope to get the orphan records using Arel.

My problem is that I couldn't find a method to retrieve the arel_table of the association. Since the relation is HABTM, there is no model to call arel_table on.

I have the following now (which works), but I make a new arel table with the name of the join table (retrieved by using the reflect_on_association method).

scope :orphans, lambda {
    teachers = arel_table
    join_table = Arel::Table.new(reflect_on_association(:groups).options[:join_table])

    join_table_condition = join_table.project(join_table[:teacher_id])
    where(teachers[:id].not_in(join_table_condition))
}

This produces the following SQL:

SELECT `teachers`.* 
FROM `teachers`    
WHERE (`teachers`.`id` NOT IN (SELECT `groups_teachers`.`teacher_id`  
                               FROM `groups_teachers` ))

So is there any better way to retrieve the arel_table instead of making a new one?

Maher4Ever
  • 1,270
  • 11
  • 26

2 Answers2

4

Unfortunately, I believe your solution is pretty much the cleanest there is right now and is, in fact, what the association itself does internally when instantiated:

https://github.com/rails/rails/blob/46492949b8c09f99db78b9f7a02d039e7bc6a702/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb#L7

I believe the reflection.join_table they use vs reflection.options[:join_table] is only in master right now though.

Mike Auclair
  • 373
  • 2
  • 8
3

If you know the name of the association and therefore the join table, which in this case it looks like you do, you should be able to call:

Arel::Table.new(:groups_teachers)

In my testing that returns Arel table of a habtm association. Thanks to this answer for pointing this out to me.

Community
  • 1
  • 1
Andrew
  • 42,517
  • 51
  • 181
  • 281
  • 1
    At least by now (looking at ARel 3.0.2 and AR 3.2) you can do some ugly athletics like `Groups.reflect_on_association(:teachers).through_reflection.klass.arel_table`, which should give you a properly bound object. – Nicos Nov 18 '13 at 14:57
  • Quick example if anyone is confused! `User.joins(:sports).where(Arel::Table.new(:sports_users)[:sport_id].eq(SPORT_ID))` – Marrs Aug 25 '16 at 08:24