Skip to content

Migrating from Squeel

Ray Zane edited this page Sep 10, 2016 · 9 revisions

Migrating to Baby Squeel is not intended to be difficult, but there were a few decisions made that result in Baby Squeel being slightly different from the original Squeel.

Query Methods

The first difference you'll notice is that Baby Squeel offers different query methods from Squeel. Squeel overrides Active Record's query methods (select, joins, where, etc). There are a few drawbacks to this approach:

  1. select {} conflicts with Array#select. See here and here.
  2. When Active Record's where is given no arguments, it returns a WhereChain. This makes monkey patching Active Record's query methods just a little bit weirder.
  3. Overriding Active Record internals has proven to be a maintenance burden time and time again.

Baby Squeel hates confrontation, so rather than overriding Active Record's query methods, it adds new ones. If you're migrating to Baby Squeel, you'll have to make the following replacements:

select {}   ->    selecting {}
order {}    ->    ordering {}
where {}    ->    where.has {}
group {}    ->    grouping {}
having {}   ->    when_having {}
joins {}    ->    joining {}

The one exception is includes, so just change:

includes { dog.owner }     ->    includes(dog: :owner)

If you don't feel like making this migration right now, see Compatibility mode.

Polymorphic associations

In Squeel, you might query a polymorphic association like so:

Note.joins { notable(Person).outer }

In order to keep the usage of method_missing simple, BabySqueel uses it's chaining API to support polymorphic associations:

Note.joins { notable.of(Person).outer }

SQL Literals

Squeel uses backticks (`) for SQL literals. While there is very sound reasoning behind this decision, I feel that using backticks makes SQL literals blend in a little too much. I wanted it to be obvious that SQL literals were being used.

With Baby Squeel, you'll have to change:

Dog.where { `name` == 'Fido' }

To:

Dog.where.has { sql('name') == 'Fido' }

If you don't feel like making this migration right now, see Compatibility mode.

Non-existent columns

Baby Squeel will throw and error if you try to query a column or association that doesn't exist.

Dog.where.has { i_am_not_a_real_column == 'uber hax' }
#=> BabySqueel::NotFoundError: There is no column or association named 'i_am_not_a_real_column' for Dog.

Okay, cool. Let's try the original Squeel:

Dog.where { i_am_not_a_real_column == 'uber hax' }
#=> Dog Load (0.2ms)  SELECT "dogs".* FROM "dogs" WHERE "dogs"."i_am_not_a_real_column" = 'uber hax'

As you can see, the original Squeel just assumes it's a column. Baby Squeel tries to protect you from typos by checking:

Dog.column_names.include?('i_am_not_a_real_column')
Dog.reflect_on_association(:i_am_not_a_real_column)

instance_exec and #my

Both Squeel and Baby Squeel use instance_exec to dynamically change what self is within DSL blocks. This is why they seem so magical 🌈 . However, the use instance_exec can sometimes confuse the hell out of you.

Take this example:

class DogQuery
  def dog_name
    'Fido'
  end

  def execute
    Dog.where.has { name == dog_name }
  end
end

DogQuery.new.execute
#=> BabySqueel::NotFoundError: There is no column or association named 'dog_name' for Dog.

Baby Squeel seems to think dog_name is a column. Because self no longer refers to the instance of DogQuery.

The original Squeel offers two strategies for working around this:

Dog.where { |t| t.name == dog_name } # giving arity to the block
Dog.where { name == my{ dog_name } } # using #my

Baby Squeel does not support #my, because it is... weird. Just give arity to the block and call it a day. If you don't feel like making this migration right now, see Compatibility mode.

Clone this wiki locally