Skip to content

Commit

Permalink
Add sql_expr extension, which adds the sql_expr to all objects, givin…
Browse files Browse the repository at this point in the history
…g them easy access to Sequel's DSL

The sql_expr extension adds the sql_expr method to every object, which
returns an object that works nicely with Sequel's DSL.  This is
best shown by example:

  1.sql_expr < :a     # 1 < a
  false.sql_expr & :a # FALSE AND a
  true.sql_expr | :a  # TRUE OR a
  ~nil.sql_expr       # NOT NULL
  "a".sql_expr + "b"  # 'a' || 'b'

This isn't possible to do in Sequel by default.  I generally refrain
from modifying the core classes unless necessary, which is why this
is an extension instead of being included in Sequel itself.

This extension also allows you to do:

  o = Object.new
  o.sql_expr < :a

Of course, for this to work, you'll need to add your own extension
which literalizes the object properly.  You'll need to modify
Dataset#literal_other to recognize the object and literalize it
correctly.

I'm generally against parametized specs, but I did use them in a
couple instances here, since the specs are small and the behavior
is identical.
  • Loading branch information
jeremyevans committed Sep 10, 2009
1 parent 1d7580e commit bc59602
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG
@@ -1,5 +1,7 @@
=== HEAD

* Add sql_expr extension, which adds the sql_expr to all objects, giving them easy access to Sequel's DSL (jeremyevans)

* Add active_model plugin, which gives Sequel::Model an ActiveModel compliant API, passes the ActiveModel::Lint tests (jeremyevans)

* Fix MySQL commands out of sync error when using queries with multiple result sets without retrieving all result sets (jeremyevans)
Expand Down
100 changes: 100 additions & 0 deletions lib/sequel/extensions/sql_expr.rb
@@ -0,0 +1,100 @@
# The sql_expr extension adds the sql_expr method to every object, which
# returns an object that works nicely with Sequel's DSL. This is
# best shown by example:
#
# 1.sql_expr < :a # 1 < a
# false.sql_expr & :a # FALSE AND a
# true.sql_expr | :a # TRUE OR a
# ~nil.sql_expr # NOT NULL
# "a".sql_expr + "b" # 'a' || 'b'

module Sequel
module SQL
# The GenericComplexExpression acts like a
# GenericExpression in terms of methods,
# but has an internal structure of a
# ComplexExpression. It is used by Object#sql_expr.
# Since we don't know what specific type of object
# we are dealing with it, we treat it similarly to
# how we treat symbols or literal strings, allowing
# many different types of methods.
class GenericComplexExpression < ComplexExpression
include AliasMethods
include BooleanMethods
include CastMethods
include ComplexExpressionMethods
include InequalityMethods
include NumericMethods
include OrderMethods
include StringMethods
include SubscriptMethods
end
end
end

class Object
# Return a copy of the object wrapped in a
# Sequel::SQL::GenericComplexExpression. Allows easy use
# of the Object with Sequel's DSL. You'll probably have
# to make sure that Sequel knows how to literalize the
# object properly, though.
def sql_expr
Sequel::SQL::GenericComplexExpression.new(:NOOP, self)
end
end

class FalseClass
# Returns a copy of the object wrapped in a
# Sequel::SQL::BooleanExpression, allowing easy use
# of Sequel's DSL:
#
# false.sql_expr & :a # FALSE AND a
def sql_expr
Sequel::SQL::BooleanExpression.new(:NOOP, self)
end
end

class NilClass
# Returns a copy of the object wrapped in a
# Sequel::SQL::BooleanExpression, allowing easy use
# of Sequel's DSL:
#
# ~nil.sql_expr # NOT NULL
def sql_expr
Sequel::SQL::BooleanExpression.new(:NOOP, self)
end
end

class Numeric
# Returns a copy of the object wrapped in a
# Sequel::SQL::NumericExpression, allowing easy use
# of Sequel's DSL:
#
# 1.sql_expr < :a # 1 < a
def sql_expr
Sequel::SQL::NumericExpression.new(:NOOP, self)
end
end

class String
# Returns a copy of the object wrapped in a
# Sequel::SQL::StringExpression, allowing easy use
# of Sequel's DSL:
#
# "a".sql_expr + :a # 'a' || a
def sql_expr
Sequel::SQL::StringExpression.new(:NOOP, self)
end
end

class TrueClass
# Returns a copy of the object wrapped in a
# Sequel::SQL::BooleanExpression, allowing easy use
# of Sequel's DSL:
#
# true.sql_expr | :a # TRUE OR a
def sql_expr
Sequel::SQL::BooleanExpression.new(:NOOP, self)
end
end

2 changes: 1 addition & 1 deletion spec/extensions/spec_helper.rb
Expand Up @@ -8,7 +8,7 @@
require 'sequel/model'
end

Sequel.extension(*%w'string_date_time inflector pagination query pretty_table blank migration schema_dumper looser_typecasting')
Sequel.extension(*%w'string_date_time inflector pagination query pretty_table blank migration schema_dumper looser_typecasting sql_expr')
{:hook_class_methods=>[], :schema=>[], :validation_class_methods=>[]}.each{|p, opts| Sequel::Model.plugin(p, *opts)}

class MockDataset < Sequel::Dataset
Expand Down
46 changes: 46 additions & 0 deletions spec/extensions/sql_expr_spec.rb
@@ -0,0 +1,46 @@
require File.join(File.dirname(__FILE__), 'spec_helper')

context "Sequel sql_expr extension" do
specify "Object#sql_expr should wrap the object in a GenericComplexExpression" do
o = Object.new
s = o.sql_expr
s.should be_a_kind_of(Sequel::SQL::GenericComplexExpression)
s.op.should == :NOOP
s.args.should == [o]
(s+1).should be_a_kind_of(Sequel::SQL::NumericExpression)
(s & true).should be_a_kind_of(Sequel::SQL::BooleanExpression)
(s < 1).should be_a_kind_of(Sequel::SQL::BooleanExpression)
s.sql_subscript(1).should be_a_kind_of(Sequel::SQL::Subscript)
s.like('a').should be_a_kind_of(Sequel::SQL::BooleanExpression)
s.as(:a).should be_a_kind_of(Sequel::SQL::AliasedExpression)
s.cast(Integer).should be_a_kind_of(Sequel::SQL::Cast)
s.desc.should be_a_kind_of(Sequel::SQL::OrderedExpression)
s.sql_string.should be_a_kind_of(Sequel::SQL::StringExpression)
end

specify "Numeric#sql_expr should wrap the object in a NumericExpression" do
[1, 2.0, 2^40, BigDecimal.new('1.0')].each do |o|
s = o.sql_expr
s.should be_a_kind_of(Sequel::SQL::NumericExpression)
s.op.should == :NOOP
s.args.should == [o]
end
end

specify "String#sql_expr should wrap the object in a StringExpression" do
o = ""
s = o.sql_expr
s.should be_a_kind_of(Sequel::SQL::StringExpression)
s.op.should == :NOOP
s.args.should == [o]
end

specify "NilClass, TrueClass, and FalseClass#sql_expr should wrap the object in a BooleanExpression" do
[nil, true, false].each do |o|
s = o.sql_expr
s.should be_a_kind_of(Sequel::SQL::BooleanExpression)
s.op.should == :NOOP
s.args.should == [o]
end
end
end
1 change: 1 addition & 0 deletions www/pages/plugins
Expand Up @@ -54,6 +54,7 @@
<li><a href="rdoc-plugins/files/lib/sequel/extensions/pretty_table_rb.html">pretty_table</a>: Adds Dataset#print for printing a dataset as a simple plain-text table.</li>
<li><a href="rdoc-plugins/files/lib/sequel/extensions/query_rb.html">query</a>: Adds Dataset#query for a different interface to creating queries that doesn't use method chaining.</li>
<li><a href="rdoc-plugins/files/lib/sequel/extensions/schema_dumper_rb.html">schema_dumper</a>: Adds Database#dump_schema_migration and related methods for dumping the schema of the database as a migration that can be restored on other databases.</li>
<li><a href="rdoc-plugins/files/lib/sequel/extensions/sql_expr_rb.html">sql_expr</a>: Adds sql_expr method to all objects, allowing easy use of Sequel's DSL.</li>
<li><a href="rdoc-plugins/files/lib/sequel/extensions/string_date_time_rb.html">string_date_time</a>: Adds instance methods to String for converting the string into a Date/Time/DateTime.</li>
</ul>

Expand Down

0 comments on commit bc59602

Please sign in to comment.