Permalink
Browse files

Add sql_expr extension, which adds the sql_expr to all objects, givin…

…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...
1 parent 1d7580e commit bc596021514aab6bd6325ed148e504c0aeffdc62 @jeremyevans jeremyevans committed Sep 10, 2009
Showing with 150 additions and 1 deletion.
  1. +2 −0 CHANGELOG
  2. +100 −0 lib/sequel/extensions/sql_expr.rb
  3. +1 −1 spec/extensions/spec_helper.rb
  4. +46 −0 spec/extensions/sql_expr_spec.rb
  5. +1 −0 www/pages/plugins
View
@@ -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)
@@ -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
+
@@ -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
@@ -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
View
@@ -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>

0 comments on commit bc59602

Please sign in to comment.