What has happened to Arel

Thanks for your explanation on this matter!

I also volunteer for helping. I have been working a lot with Arel and SQL and I think I can help. Can you also add me, @rafaelfranca.

1 Like

I second taking inspiration from Sequel, one of my all-time fave Ruby libraries. One thing to keep in mind though is that there are two distinct layers here:

  1. “sugar” a la Squeel or the Sequel operator-based layer; and
  2. A clean, composable, stable object API that undergirds the sugar.
3 Likes

I think mainly nobody has come up with a better name for arel_table :grimacing:

@flanger001 what is your main entry point to getting Arel objects? I think most people use arel_table, but I don’t know for sure. AFAIK most Active Record query builder methods will take an arel object, so I don’t think that’s our main hurdle. My take is that we need to avoid folks ever typing “arel” or “Arel” as much as possible but still expose the power / flexibility.

4 Likes

@rafaelfranca Sign me up to help with Arel! The app I work on has over 13k lines of Arel/query object code.

1 Like

I used arel_table at first but have switched to Arel::SelectManager.new(Arel::Table.engine) recently because I use things like CTEs and referenced windows a lot.

4 Likes

Interesting. Are you passing the resulting select manager to AR to actually perform the query? Do you have any short-ish examples?

We could easily add factory methods to make constructing the select manager easier, but I’m not sure that’s going to get us to the “don’t type the letters a r e l” goal.

The entry point I use is mainly arel_table[]. In order not to type the “a r e l” letters I add a [] delegator to ApplicationRecord:

def self.[](attribute)
  arel_table[attribute]
end

I also use a sprinkle of Arel.star and Arel.sql.

3 Likes

I call to_sql on the manager and pass it to ApplicationRecord.connection.execute (usually), find_by_sql when it makes sense. —I should note I’m doing a lot of analytic type stuff, so often the results I want are counts and sums and what not.

Short-ish example coming …

I’ve seen this before. I like it but I’m worried about adding the [] method because maybe we want to use the method for something else. But I’ve had this same worry for literally years and we’ve not implemented that method. Maybe it’s time we do it?

2 Likes

@tenderlove here’s a rough example of how I compose query objects together. It’s probably missing some stuff and some of these methods I have in mixins, etc.

class MyQuery
  attr_reader :company_id, :other_query_object
  delegate :to_sql, to: :query

  def initialize(company_id, other_query_object:)
    @company_id = company_id
    @other_query_object = other_query_object
  end

  def execute
    cast_values ApplicationRecord.connection.execute(to_sql)
  end

  def query
    @query ||= begin
      manager
        .project([
          widgets[:person_id], 
          widgets[:cost].sum, 
          Arel::Nodes::Count.new(Arel.star)])
        .from(widgets)
        .where(widgets[:company_id].eq(company_id))
        .group(1)
        .with(widgets_cte)
    end
  end

  def manager
    @manager ||= Arel::SelectManager.new(Arel::Table.engine)
  end

  def widgets_cte
    Arel::Nodes::As.new(Arel::Table.new(:widgets), other_query_object)
  end
end

(query and manager are memoized so I can call them multiple times without duplicates from rebuilding)

1 Like

I use Model[:column] extensively, provided by the arel-helpers gem (which I found out about at RailsConf years and years ago).

Most of the day-to-day Arel I write is making clauses out of columns, so I actually tend to not directly use arel_table other than when I’m writing something generic. I have a number of complex joins (for example) that are written in much more explicit Arel (which are probably hard for newcomers to the project to understand but that’s the tradeoff).

3 Likes

My main entry is .arel_table as you said, e.g. Task.arel_table. Sometimes I use the #arel method on a relation:

[1] pry(main)> Task.all.arel
=> #<Arel::SelectManager:0x00007ff1cc026cd0
 @ast=
  #<Arel::Nodes::SelectStatement:0x00007ff1cc026ca8
   @cores=
    [#<Arel::Nodes::SelectCore:0x00007ff1cc026c80

(I wouldn’t use it on .all but this is for simplicity)

I find it’s the most useful in scopes. Here’s a more thorough example from a personal project:

class Station < ApplicationRecord
  has_many :gas_entries
  has_many :user_stations, :dependent => :destroy
  has_many :users, :through => :user_stations

  scope :with_user_visits, ->(user) {
    visits_t = Arel::Table.new(:visits)
    visits_d = GasEntry.for_user(user).select(:station_id).arel # <-- #arel
    visits = Arel::Nodes::As.new(visits_t, visits_d)

    stations_d = arel_table.
      project(arel_table[Arel.star], visits_t[:station_id].count.as("visits")).
      join(visits_t, Arel::Nodes::OuterJoin).on(visits_t[:station_id].eq(arel_table[:id])).
      group(arel_table[:id], visits_t[:station_id]).
      with(visits)

    from(stations_d.as("stations"))
  }
end

Essentially the idea here was to use a CTE to build a visits count column, because then in my view I can just use a station.visits method, and I didn’t have to do any counting in Ruby.

1 Like

I will add that one thing I hope to work with @rafaelfranca on here is exposing some more of this within ActiveRecord. The reason we reach to Arel is because ActiveRecord can’t do some of the stuff we want, so we can reduce the need for Arel if we can expose things through ActiveRecord.

3 Likes

If not typing Arel is a goal, what should be done about the requirement to type Arel.sql to escape deprecation notices for SQL fragments in things like order()?

I’ve made a PR so we can discuss the [] feature. https://github.com/rails/rails/pull/39198

4 Likes

I don’t know yet. Maybe we can’t eliminate it 100%, but it’s a goal to strive for :smile:

Nice. We should steal stuff from this gem and put it in Rails. Thank you.

3 Likes

Great, thanks!

Arel::Nodes::Count.new(Arel.star) can be rewritten as Arel.star.count.

We probably need an as factory method on Arel::Table. We have this, but it doesn’t seem to be on the Table class, and I’m not sure if converting the parameter to a sql literal is correct. Would you mind investigating?

I’m not sure what to do about creating the select manager. Lets discuss it.

1 Like

Awesome, thank you! Let’s figure out how to get rid of the references to “Arel”.

Arel has some builder methods like outer_join so we can eliminate the reference to Arel::Nodes::OuterJoin. I think the PR here will help us eliminate some of the Arel::Table.new calls.

Anyway, I think we just need to start implementing method to start chipping away at the references to “Arel” as much as possible

1 Like