Roman
Roman

Reputation: 1192

Make single ActiveRecord query through several associated models

I have the following models and associations:

class Organization
  has_many :buildings
end
class Building
  has_many :counters
end
class Counter
  has_many :counter_records
end
class CounterRecord
  belongs_to :counter
end

I would like to get something like this

organization.counter_records(dimension_day: start_date...end_date)

([dimension_day: start_date...end_date] - it's condition).

How do I get counters records of organization through all these models?

Upvotes: 2

Views: 55

Answers (1)

Andrey Deineko
Andrey Deineko

Reputation: 52357

Look into Activerecord Querying guide.

Specifically you're interested in joins:

Organization.joins(buildings: { counters: :counter_records })
            .where(counter_records: { dimension_day: start_date...end_date })
            .group('organizations.id')

You can create a method:

class Organization
  def filter_counter_records(start_date, end_date)
    self.class
        .where(id: id)
        .joins(buildings: { counters: :counter_records })
        .where(counter_records: { dimension_day: start_date...end_date })
        .group('organizations.id')
  end
end

Now the following is possible:

organization = Organization.first
organization.filter_counter_records(start_date, end_date)

But more idiomatic/conventional option would be using associations:

class Organization
  has_many :buildings
  has_many :counters,        through: :buildings
  has_many :counter_records, through: :counters
end

Now you can just go with

organization = Organization.first
organization.counter_records.where(dimension_day: start_date..end_date)

The last step here would be setting up the scope in CounterRecord:

class CounterRecord
  scope :by_date_range, ->(start_date, end_date) { where(dimension_day: start_date..end_date) }
end

And now

organization = Organization.first
organization.counter_records.by_date_range(start_date, end_date)

Upvotes: 5

Related Questions