-
Notifications
You must be signed in to change notification settings - Fork 51
Migrating from Squeel
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.
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:
-
select {}
conflicts withArray#select
. See here and here. - 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. - 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.
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 }
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.
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)
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.