From bc596021514aab6bd6325ed148e504c0aeffdc62 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Thu, 10 Sep 2009 12:23:13 -0700 Subject: [PATCH] Add sql_expr extension, which adds the sql_expr to all objects, giving 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. --- CHANGELOG | 2 + lib/sequel/extensions/sql_expr.rb | 100 ++++++++++++++++++++++++++++++ spec/extensions/spec_helper.rb | 2 +- spec/extensions/sql_expr_spec.rb | 46 ++++++++++++++ www/pages/plugins | 1 + 5 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 lib/sequel/extensions/sql_expr.rb create mode 100644 spec/extensions/sql_expr_spec.rb diff --git a/CHANGELOG b/CHANGELOG index a1a617310c..f7bbca105f 100644 --- a/CHANGELOG +++ b/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) diff --git a/lib/sequel/extensions/sql_expr.rb b/lib/sequel/extensions/sql_expr.rb new file mode 100644 index 0000000000..ef07bf0d04 --- /dev/null +++ b/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 + diff --git a/spec/extensions/spec_helper.rb b/spec/extensions/spec_helper.rb index 502a3b5573..4730a284f2 100644 --- a/spec/extensions/spec_helper.rb +++ b/spec/extensions/spec_helper.rb @@ -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 diff --git a/spec/extensions/sql_expr_spec.rb b/spec/extensions/sql_expr_spec.rb new file mode 100644 index 0000000000..efe8b89a68 --- /dev/null +++ b/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 diff --git a/www/pages/plugins b/www/pages/plugins index 6d8804973c..4f70afdba3 100644 --- a/www/pages/plugins +++ b/www/pages/plugins @@ -54,6 +54,7 @@
  • pretty_table: Adds Dataset#print for printing a dataset as a simple plain-text table.
  • query: Adds Dataset#query for a different interface to creating queries that doesn't use method chaining.
  • schema_dumper: 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.
  • +
  • sql_expr: Adds sql_expr method to all objects, allowing easy use of Sequel's DSL.
  • string_date_time: Adds instance methods to String for converting the string into a Date/Time/DateTime.