Skip to content
Browse files

Add document describing the core extensions

Also, link to this document from some other documents, including
the README, so that new users can be exposed to the core extensions
early.
  • Loading branch information...
1 parent 76b71fa commit 34222ed66d43b17b32dd95358baf21c621ebf2e5 @jeremyevans committed Jul 13, 2012
Showing with 399 additions and 2 deletions.
  1. +10 −0 README.rdoc
  2. +1 −1 doc/cheat_sheet.rdoc
  3. +374 −0 doc/core_extensions.rdoc
  4. +4 −0 doc/dataset_filtering.rdoc
  5. +2 −0 doc/object_model.rdoc
  6. +3 −1 doc/querying.rdoc
  7. +4 −0 doc/sql.rdoc
  8. +1 −0 www/pages/documentation
View
10 README.rdoc
@@ -296,6 +296,16 @@ You can also specify descending order:
posts.order(Sequel.desc(:stamp))
# ORDER BY stamp DESC
+=== Core Extensions
+
+Note the use of <tt>Sequel.desc(:stamp)</tt> in the above example. Much of Sequel's DSL uses this style, calling methods on the Sequel module that return SQL expression objects. Sequel also ships with a {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]) that integrates Sequel's DSL better into the ruby language, allowing you to write:
+
+ :stamp.desc
+
+instead of:
+
+ Sequel.desc(:stamp)
+
=== Selecting Columns
Selecting specific columns to be returned is also simple using +select+:
View
2 doc/cheat_sheet.rdoc
@@ -64,7 +64,7 @@ Without a filename argument, the sqlite adapter will setup a new sqlite database
dataset.inject(0){|sum, r| sum + r[:value]}
dataset.sum(:value) # same as above
-== Filtering (see also doc/dataset_filtering.rdoc)
+== Filtering (see also {Dataset Filtering}[link:files/doc/dataset_filtering_rdoc.html])
=== Equality
View
374 doc/core_extensions.rdoc
@@ -0,0 +1,374 @@
+= Sequel's Core Extensions
+
+== Background
+
+Historically, Sequel added methods to many of the core classes, and usage of those methods was the primary and recommended way to use Sequel. For example:
+
+ DB[:table].select(:column.cast(Integer)). # Symbol#cast
+ where(:column.like('A%')). # Symbol#like
+ order({1=>2}.case(0, :a)) # Hash#case
+
+While Sequel never override any methods defined by ruby, it is possible that other libraries could define the same methods that Sequel defines, which could cause problems. Also, some rubyists do not like using libraries that add methods to the core classes.
+
+Alternatives for the core extension methods where added to Sequel, so the query above could be written as:
+
+ DB[:table].select(Sequel.cast(:column, Integer)).
+ where(Sequel.like(:column, 'A%')).
+ order(Sequel.case({1=>2}, 0, :a))
+
+Almost all of the core extension methods have a replacement on the Sequel module. So it is now up to the user which style to use. Using the methods on the Sequel module results in slightly more verbose code, but allows the code to work without modifications to the core classes.
+
+== Issues
+
+There is no recommendation on whether the core_extensions should be used or not. It is very rare that any of the methods added by core_extensions actually causes a problem, but some of them can make it more difficult to find other problems. For example, if you type:
+
+ do_somehting if value | other_value
+
+while meaning to type:
+
+ do_something if value || other_value
+
+and value is a Symbol, instead of a NoMethodError being raised because Symbol#| is not implemented by default, <tt>value | other_value</tt> will return a Sequel expression object, which if will evaluate as true, and do_something will be called.
+
+== Usage
+
+All of Sequel's extensions to the core classes are stored in Sequel's core_extensions extension, which you can load via:
+
+ Sequel.extension :core_extensions
+
+For backwards compatibility, the core_extensions are loaded by default in Sequel 3. Starting in Sequel 4, the core extensions will no longer be loaded by default, so you will have to load the core_extensions extension manually using the above code. If you plan to use the core extensions, it's recommended that you manually load the extension, even in Sequel 3. If you do not plan to use the core extensions, you can change from:
+
+ require 'sequel'
+
+to:
+
+ require 'sequel/no_core_ext'
+
+which will load Sequel without the core extensions.
+
+== No Internal Dependency
+
+Sequel has no internal dependency on the core extensions. This includes Sequel's core, Sequel::Model, and all plugins and extensions that ship with Sequel. However, it is possible that external plugins and extensions will depend on the core extensions. Such plugins and extensions should be updated so that they no longer depend on the core extensions.
+
+== Core Extension Methods
+
+This section will briefly describe all of the methods added to the core classes, and what the alternative method is that doesn't require the core extensions.
+
+=== Symbol & String
+
+==== as
+
+Symbol#as and String#as return Sequel aliased expressions using the provided alias:
+
+ :a.as(:b) # SQL: a AS b
+ 'a'.as(:b) # SQL: 'a' AS b
+
+Alternative: Sequel.as:
+
+ Sequel.as(:a, :b)
+
+==== cast
+
+Symbol#cast and String#cast return Sequel cast expressions for typecasting in the database:
+
+ :a.cast(Integer) # SQL: CAST(a AS integer)
+ 'a'.cast(Integer) # SQL: CAST('a' AS integer)
+
+Alternative: Sequel.cast:
+
+ Sequel.cast(:a, Integer)
+
+==== cast_numeric
+
+Symbol#cast_numeric and String#cast_numeric return Sequel cast expressions for typecasting in the database, defaulting to integers, where the returned expression is treated as an numeric value:
+
+ :a.cast_numeric # SQL: CAST(a AS integer)
+ 'a'.cast_numeric(Float) # SQL: CAST('a' AS double precision)
+
+Alternative: Sequel.cast_numeric:
+
+ Sequel.cast_numeric(:a)
+
+==== cast_string
+
+Symbol#cast_string and String#cast_string return Sequel cast expressions for typecasting in the database, defaulting to strings, where the returned expression is treated as a string value:
+
+ :a.cast_string # SQL: CAST(a AS varchar(255))
+ 'a'.cast_string(:text) # SQL: CAST('a' AS text)
+
+Alternative: Sequel.cast_string:
+
+ Sequel.cast_string(:a)
+
+=== Symbol
+
+==== identifier
+
+Symbol#identifier wraps the symbol in a single identifier that will not be split. By default, Sequel will split symbols with double or triple underscores to do qualifying and aliasing.
+
+ :table__column.identifier # SQL: table__column
+
+Alternative: Sequel.identifier:
+
+ Sequel.identifier(:table__column)
+
+==== asc
+
+Symbol#asc is used to define an ascending order on a column. It exists mostly for consistency with #desc, since ascending is the default order:
+
+ :a.asc # SQL: a ASC
+
+Alternative: Sequel.asc:
+
+ Sequel.asc(:a)
+
+==== desc
+
+Symbol#desc is used to defined a descending order on a column. The returned value is usually passed to one of the dataset order methods.
+
+ :a.desc # SQL: a DESC
+
+Alternative: Sequel.desc:
+
+ Sequel.desc(:a)
+
+==== +, -, *, /
+
+The standard mathematical operators are defined on Symbol, and return a Sequel numeric expression object representing the operation:
+
+ :a + :b # SQL: a + b
+ :a - :b # SQL: a - b
+ :a * :b # SQL: a * b
+ :a / :b # SQL: a / b
+
+Alternatives:
+
+ Sequel.+(:a, :b)
+ Sequel.-(:a, :b)
+ Sequel.*(:a, :b)
+ Sequel./(:a, :b)
+
+==== *
+
+The * operator is overloaded on Symbol such that if it is called with no arguments, it represents a selection of all columns in the table:
+
+ :a.* # SQL: a.*
+
+Alternative: Sequel.expr.*:
+
+ Sequel.expr(:a).*
+
+==== qualify
+
+Symbol#qualify qualifies the identifier (e.g. a column) with a another identifier (e.g. a table):
+
+ :column.qualify(:table) # SQL: table.column
+
+Alternative: Sequel.qualify:
+
+ Sequel.qualify(:table, :column)
+
+Note the reversed order of the arguments. For the Symbol#qualify method, the argument is the qualifier, while for Sequel.qualify, the qualifier is the first argument.
+
+==== like
+
+Symbol#like returns a case sensitive LIKE expression between the identifier and the given argument:
+
+ :a.like('b%') # SQL: a LIKE 'b%'
+
+Alternative: Sequel.like:
+
+ Sequel.like(:a, 'b%')
+
+==== like
+
+Symbol#ilike returns a case insensitive LIKE expression between the identifier and the given argument:
+
+ :a.ilike('b%') # SQL: a ILIKE 'b%'
+
+Alternative: Sequel.ilike:
+
+ Sequel.ilike(:a, 'b%')
+
+==== sql_subscript
+
+Symbol#sql_subscript returns a Sequel expression representing an SQL array access:
+
+ :a.sql_subscript(1) # SQL: a[1]
+
+Alternative: Sequel.subscript:
+
+ Sequel.subscript(:a, 1)
+
+==== extract
+
+Symbol#extract does a datetime part extraction from the receiver:
+
+ :a.extract(:year) # SQL: extract(year FROM a)
+
+Alternative: Sequel.extract:
+
+ Sequel.extract(:year, :a)
+
+Note the reversed order of the arguments. In Symbol#extract, the datetime part is the argument, while in Sequel.extract, the datetime part is the first argument.
+
+==== sql_boolean, sql_number, sql_string
+
+These Symbol methods are used to force the treating of the object as a specific SQL type, instead of as a general SQL type. For example:
+
+ :a.sql_boolean + 1 # NoMethodError
+ :a.sql_number << 1 # SQL: a << 1
+ :a.sql_string + 'a' # SQL: a || 'a'
+
+Alternative: Sequel.expr:
+
+ Sequel.expr(:a).sql_boolean
+ Sequel.expr(:a).sql_number
+ Sequel.expr(:a).sql_string
+
+==== sql_function
+
+Symbol#sql_function returns an SQL function call expression object:
+
+ :now.sql_function # SQL: now()
+ :sum.sql_function(:a) # SQL: sum(a)
+ :concat.sql_function(:a, :b) # SQL: concat(a, b)
+
+Alternative: Sequel.function:
+
+ Sequel.function(:now, :a)
+
+=== String
+
+==== lit
+
+String#lit creates a literal string, using placeholders if any arguments are given. Literal strings are not escaped, they are treated as SQL code, not as an SQL string:
+
+ 'a'.lit # SQL: a
+ '"a" = ?'.lit(1) # SQL: "a" = 1
+
+Alternative: Sequel.lit:
+
+ Sequel.lit('a')
+
+==== to_sequel_blob
+
+String#to_sequel_blob returns the string wrapper in Sequel blob object. Often blobs need to be handled differently than regular strings by the database adapters.
+
+ "a\0".to_sequel_blob # SQL: X'6100'
+
+Alternative: Sequel.blob:
+
+ Sequel.blob("a\0")
+
+=== Hash, Array, & Symbol
+
+==== ~
+
+Array#~, Hash#~, and Symbol#~ treat the receiver as a conditions specifier, not matching all of the conditions:
+
+ ~{:a=>1, :b=>[2, 3]} # SQL: a != 1 OR b NOT IN (2, 3)
+ ~[[:a, 1], [:b, [1, 2]]] # SQL: a != 1 OR b NOT IN (1, 2)
+
+Alternative: Sequel.~:
+
+ Sequel.~(:a=>1, :b=>[2, 3])
+
+=== Hash & Array
+
+==== case
+
+Array#case and Hash#case return an SQL CASE expression, where the keys are conditions and the values are results:
+
+ {{:a=>[2,3]}=>1}.case(0) # SQL: CASE WHEN a IN (2, 3) THEN 1 ELSE 0 END
+ [[{:a=>[2,3]}, 1]].case(0) # SQL: CASE WHEN a IN (2, 3) THEN 1 ELSE 0 END
+
+Alternative: Sequel.case:
+
+ Sequel.case({:a=>[2,3]}=>1}, 0)
+
+==== sql_expr
+
+Array#sql_expr and Hash#sql_expr treat the receiver as a conditions specifier, matching all of the conditions in the array.
+
+ {:a=>1, :b=>[2, 3]}.sql_expr # SQL: a = 1 AND b IN (2, 3)
+ [[:a, 1], [:b, [2, 3]]].sql_expr # SQL: a = 1 AND b IN (2, 3)
+
+Alternative: Sequel.expr:
+
+ Sequel.expr(:a=>1, :b=>[2, 3])
+
+==== sql_negate
+
+Array#sql_negate and Hash#sql_negate treat the receiver as a conditions specifier, matching none of the conditions in the array:
+
+ {:a=>1, :b=>[2, 3]}.sql_negate # SQL: a != 1 AND b NOT IN (2, 3)
+ [[:a, 1], [:b, [2, 3]]].sql_negate # SQL: a != 1 AND b NOT IN (2, 3)
+
+Alternative: Sequel.negate:
+
+ Sequel.negate(:a=>1, :b=>[2, 3])
+
+==== sql_or
+
+Array#sql_or nd Hash#sql_or treat the receiver as a conditions specifier, matching any of the conditions in the array:
+
+ {:a=>1, :b=>[2, 3]}.sql_or # SQL: a = 1 OR b IN (2, 3)
+ [[:a, 1], [:b, [2, 3]]].sql_or # SQL: a = 1 OR b IN (2, 3)
+
+Alternative: Sequel.or:
+
+ Sequel.or(:a=>1, :b=>[2, 3])
+
+=== Array
+
+==== sql_value_list
+
+Array#sql_value_list wraps the array in an array subclass, which Sequel will always treat as a value list and not a conditions specifier. By default, Sequel treats arrays of two element arrays as a conditions specifier.
+
+ DB[:a].filter('(a, b) IN ?', [[1, 2], [3, 4]]) # SQL: (a, b) IN ((1 = 2) AND (3 = 4))
+ DB[:a].filter('(a, b) IN ?', [[1, 2], [3, 4]].sql_value_list) # SQL: (a, b) IN ((1, 2), (3, 4))
+
+Alternative: Sequel.value_list:
+
+ Sequel.value_list([[1, 2], [3, 4]])
+
+==== sql_string_join
+
+Array#sql_string_join joins all of the elements in the array in an SQL string concatentation expression:
+
+ [:a].sql_string_join # SQL: a
+ [:a, :b].sql_string_join # SQL: a || b
+ [:a, 'b'].sql_string_join # SQL: a || 'b'
+ ['a', :b].sql_string_join(' ') # SQL: 'a' || ' ' || b
+
+Alternative: Sequel.join:
+
+ Sequel.join(['a', :b], ' ')
+
+=== Hash & Symbol
+
+==== &
+
+Hash#& and Symbol#& return a Sequel boolean expression, matching the condition specified by the receiver and the condition specified by the given argument:
+
+ :a & :b # SQL: a AND b
+ {:a=>1} & :b # SQL: a = 1 AND b
+ {:a=>true} & :b # SQL: a IS TRUE AND b
+
+Alternative: Sequel.&:
+
+ Sequel.&({:a=>1}, :b)
+
+==== |
+
+Hash#| returns a Sequel boolean expression, matching the condition specified by the receiver or the condition specified by the given argument:
+
+ :a | :b # SQL: a OR b
+ {:a=>1} | :b # SQL: a = 1 OR b
+ {:a=>true} | :b # SQL: a IS TRUE OR b
+
+Alternative: Sequel.|:
+
+ Sequel.|({:a=>1}, :b)
+
View
4 doc/dataset_filtering.rdoc
@@ -29,6 +29,10 @@ If you are specifying a filter/selection/order, you can use a virtual row block:
items.select{avg(price)}
+You can also use the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html] and the +sql_function+ method:
+
+ :avg.sql_function(:price)
+
== Filtering using a hash
If you just need to compare records against values, you can supply a hash:
View
2 doc/object_model.rdoc
@@ -204,6 +204,8 @@ If Sequel needs to represent an SQL concept that does not map directly to an exi
ruby class, it will generally use a Sequel::SQL::Expression subclass to represent that
concept.
+Some of the examples below show examples that require the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html].
+
=== Sequel::LiteralString
Sequel::LiteralString is not actually a Sequel::SQL::Expression subclass. It is
View
4 doc/querying.rdoc
@@ -10,7 +10,9 @@ aims to be a gentle introduction to Sequel's querying support.
While you can easily use raw SQL with Sequel, a large part of the
advantage you get from using Sequel is Sequel's ability to abstract
-SQL from you and give you a much nicer interface.
+SQL from you and give you a much nicer interface. Sequel also ships with
+a {core_extensions extension}[link:files/doc/core_extensions_rdoc.html],
+which better integrates Sequel's DSL into the ruby language.
== Retrieving Objects
View
4 doc/sql.rdoc
@@ -78,6 +78,10 @@ Almost everywhere in Sequel, you can drop down to literal SQL by providing a lit
DB[:albums].select('name') # SELECT 'name' FROM albums
DB[:albums].select(Sequel.lit('name')) # SELECT name FROM albums
+For a simpler way of creating literal strings, you can also use the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html], which adds the <tt>String#lit</tt> method, and other methods that integrate Sequel's DSL with the ruby language:
+
+ DB[:albums].select('name'.lit)
+
So you can use Sequel's DSL everywhere you find it helpful, and fallback to literal SQL if the DSL can't do what you want or you just find literal SQL easier.
== Translating SQL Expressions into Sequel
View
1 www/pages/documentation
@@ -26,6 +26,7 @@
<li><a href="rdoc/files/doc/active_record_rdoc.html">Sequel for ActiveRecord Users</a></li>
<li><a href="rdoc/files/doc/testing_rdoc.html">Testing With Sequel</a></li>
<li><a href="rdoc/files/doc/object_model_rdoc.html">Sequel Object Model</a></li>
+ <li><a href="rdoc/files/doc/core_extensions_rdoc.html">Core Extensions</a></li>
</ul>
<h3>RDoc</h3>

0 comments on commit 34222ed

Please sign in to comment.
Something went wrong with that request. Please try again.