(2 minute read)
Squel 1.1 is now available. This release was a major rewrite of how Squel works underneath the hood and has been a long time coming. There are two main benefits introduced by this new release:
The documentation has been updated with info and demos of the above. I'll talk a bit about the architectural changes in this post.
Squel query builders now consist of one or more building blocks, where each block is responsible for building a different part of the query. Each block provides methods which the query builder then exposes through its interface. For instance, the OffsetBlock provides the offset() method:
class cls.OffsetBlock extends cls.Block
constructor: (options) ->
super options
@offsets = null
# Set the OFFSET transformation.
#
# Call this will override the previously set offset for this query. Also note that Passing 0 for 'max' will remove
# the offset.
offset: (start) ->
start = @_sanitizeLimitOffset(start)
@offsets = start
buildStr: (queryBuilder) ->
if @offsets then "OFFSET #{@offsets}" else ""
The query builder base class - QueryBuilder - constructor will create a offset() method on the query builder instance which acts as proxy to the OffsetBlock.offset() method:
class cls.QueryBuilder extends cls.BaseBuilder
# Constructor
#
# blocks - array of cls.BaseBuilderBlock instances to build the query with.
constructor: (options, blocks) ->
super options
@blocks = blocks or []
# Copy exposed methods into myself
for block in @blocks
for methodName, methodBody of block.exposedMethods()
if @[methodName]?
throw new Error "#{@_getObjectClassName(@)} already has a builder method called: #{methodName}"
( (block, name, body) =>
@[name] = =>
body.apply(block, arguments)
@
)(block, methodName, methodBody)
All blocks inherit from the Block base class which provides the exposedMethods() method used above. This method will expose all block methods which aren't prefixed with _. Blocks can and do get re-used between different query builders. The current list of blocks is as follows:
To build the query we simply call buildStr() on each block and then concatenate the resulting strings. QueryBuilder.toString() has the logic:
class cls.QueryBuilder extends cls.BaseBuilder
...
toString: ->
(block.buildStr(@) for block in @blocks).filter( (v) -> return (0 < v.length)).join(' ')
If we take a look at the SELECT query builder code we can see how easy it is to now customise it:
class cls.Select extends cls.QueryBuilder
constructor: (options, blocks = null) ->
blocks or= [
new cls.StringBlock(options, 'SELECT'),
new cls.DistinctBlock(options),
new cls.GetFieldBlock(options),
new cls.FromTableBlock(options),
new cls.JoinBlock(options),
new cls.WhereBlock(options),
new cls.GroupByBlock(options),
new cls.OrderByBlock(options),
new cls.LimitBlock(options),
new cls.OffsetBlock(options)
]
super options, blocks
By passing in our own blocks list to the constructor we can change the query content entirely. For instance, if we wanted to customise the UPDATE queries such that it supports the OFFSET keyword:
squel.update = (options) ->
new squel.cls.Update options, [
new squel.cls.StringBlock(options, 'UPDATE')
new squel.cls.UpdateTableBlock(options)
new squel.cls.SetFieldBlock(options)
new squel.cls.WhereBlock(options)
new squel.cls.OrderByBlock(options)
new squel.cls.OffsetBlock(options)
new squel.cls.LimitBlock(options)
]
After running the above, calling squel.update() will return our customised version of the query builder. But we can do more. We can go ahead and build an entirely new type of query if we so wish to, by subclassing QueryBuilder and then passing in Block instances (either built-in blocks or our own creations). See a live example of this in the docs.