RSS Amplifier

k0va1's blog · Jan 22, 2025

Understanding of ActiveRecord Aggregations

0
Sign in to vote or save

Understanding of ActiveRecord Aggregations

It's quite common thing we all bump in while working with Models in Rails apps. Imagine that we have Event model that represents some meetup or conference with following fields:

  • city

  • address

  • longitude

  • latitude

  • ...

All these fields are stored in the database in events table. And it would be really nice to work with all these 4 fields as an object. Let's call it Location. Why would it be nice? Because having an abstraction gives us flexibility. For example we can compare 2 locations with each other or check that one location is close enough to other one. Furthermore this logic could be encapsulated in Location class and could be tested separately. Lets take a look at examples below

# app/models/event.rb
#
# == Schema Information
#
# Table name: events
#
#  id         :bigint           not null, primary key
#  address    :string
#  city       :string
#  latitude   :float
#  longitude  :float
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
class Event < ApplicationRecord
  composed_of :location, mapping: {address: :address, latitude: :latitude, longitude: :longitude }
end

With composed_of we can create an aggregation of this 4 fields to the value object Location.

# app/models/location.rb
require "math"
class Location
  attr_reader :city, :address, :latitude, :longitude
  EARTH_RADIUS_KM = 6371
  def initialize(city:, address:, latitude:, longitude:)
    @city = city
    @address = address
    @latitude = latitude
    @longitude = longitude
  end
  def ==(other)
    latitude == other.latitude && longitude == other.longitude
  end
  def close_to?(other, max_distance_km = 1)
    distance = haversine_distance(latitude, longitude, other.latitude, other.longitude)
    distance <= max_distance_km
  end
  private
  def haversine_distance(lat1, lon1, lat2, lon2)
    # Convert degrees to radians
    lat1_rad, lon1_rad = to_radians(lat1), to_radians(lon1)
    lat2_rad, lon2_rad = to_radians(lat2), to_radians(lon2)
    # Haversine formula
    delta_lat = lat2_rad - lat1_rad
    delta_lon = lon2_rad - lon1_rad
    a = Math.sin(delta_lat / 2)**2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(delta_lon / 2)**2
    c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
    # Distance in kilometers
    EARTH_RADIUS_KM * c
  end
  def to_radians(degrees)
    degrees * Math::PI / 180
  end
end

Due to composed_of macro we can compare two Events by theirs location

event1 = Event.new(city: "Warsaw", address: "Marszałkowska 1", latitude: 52.22977, longitude: 21.01178)
event2 = Event.new(city: "Warsaw", address: "Marszałkowska 5", latitude: 52.22978, longitude: 21.01179)
event1.location.close_to?(event2.location) # => true

Also Rails gives us the ability to query records using Location object in a where clause

location = Location.new(city: "Warsaw", address: "Marszałkowska 1", latitude: 52.22977, longitude: 21.01178)
Event.where(location: location) # => returns Events where fields match with Location fields

For more examples take a look at the documentation page of Active Record Aggregations.

Read the original on k0va1.dev

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.