From c93591c2f8ed342bd1620568a336a8308ada9af9 Mon Sep 17 00:00:00 2001 From: Alex Fortuna Date: Thu, 14 Jul 2011 17:18:54 +0400 Subject: [PATCH] Updates. --- .gitignore | 14 +- README.html | 323 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 41 ++++-- Rakefile | 26 ++-- VERSION.yml | 2 +- dev/github.css | 41 ++++++ dev/head.html | 4 + init.rb | 1 + lib/smart_tuple.rb | 105 ++++++++------ smart_tuple.gemspec | 35 +++-- 10 files changed, 501 insertions(+), 91 deletions(-) create mode 100644 README.html create mode 100644 dev/github.css create mode 100644 dev/head.html diff --git a/.gitignore b/.gitignore index fb85694..7f2a89a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,9 @@ -# General. Sorted by first letter. -.old*/ +# General Ruby, sorted by first letter. +.old* *-old* -*-old*/ -/*.patch -.proto-* -.proto-*/ -*.rbx .ref* -.ref*/ # Project-specific. -/dev/ +/doc/ /pkg/ -/README.html +/.rvmrc diff --git a/README.html b/README.html new file mode 100644 index 0000000..0ec6fc8 --- /dev/null +++ b/README.html @@ -0,0 +1,323 @@ + + + + + +

SmartTuple: A Simple Yet Smart SQL Conditions Builder

+ +

Introduction

+ +

Sometimes we need to build SQL WHERE statements which are compound or conditional by nature. SmartTuple simplifies this task by letting us build statements of virtually unlimited complexity out of smaller ones.

+ +

SmartTuple is suitable for use with Ruby on Rails (ActiveRecord) and other Ruby frameworks and ORMs.

+ +

Setup (Rails 3)

+ +

In your app’s Gemfile, add:

+ +
gem "smart_tuple"
+
+ +

To install the gem with RDoc/ri documentation, do a:

+ +
$ gem install smart_tuple
+
+ +

Otherwise, do a bundle install.

+ +

Setup (Rails 2)

+ +

In your app’s config/environment.rb do a:

+ +
config.gem "smart_tuple"
+
+ +

To install the gem, do a:

+ +
$ gem sources --add http://rubygems.org
+$ gem install smart_tuple
+
+ +

, or use rake gems:install.

+ +

Kickstart Demo

+ +
tup = SmartTuple.new(" AND ")
+tup << {:brand => params[:brand]} if params[:brand].present?
+tup << ["min_price >= ?", params[:min_price]] if params[:min_price].present?
+tup << ["max_price <= ?", params[:max_price]] if params[:max_price].present?
+
+@phones = Phone.find(:all, :conditions => tup.compile)
+
+ +

There’s a number of ways you can use SmartTuple. Some of them is covered in the tutorial below.

+ +

Tutorial

+ +

Suppose we’ve got a mobile phone catalog with a search form. We are starting with a price filter of two values: min_price and max_price, both optional.

+ +

Filter logic:

+ + + +

Suppose the HTML form passed to a controller results in a params hash:

+ +
params[:min_price] = 100    # Can be blank.
+params[:max_price] = 300    # Can be blank.
+
+ +

Now let’s write condition-building code:

+ +
# Start by creating a tuple whose statements are glued with " AND ".
+tup = SmartTuple.new(" AND ")
+
+# If min_price is not blank, append its statement.
+if params[:min_price].present?
+  tup << ["min_price >= ?", params[:min_price]]
+end
+
+# Same for max_price.
+if params[:max_price].present?
+  tup << ["max_price <= ?", params[:max_price]]
+end
+
+# Finally, fire up the query.
+@phones = Phone.find(:all, {:conditions => tup.compile})
+
+ +

That’s basically it. Now let’s see how different params values affect the resulting :conditions value. Labelled p and c in this and following listings:

+ +
p: {}
+c: []
+
+p: {:max_price=>300}
+c: ["max_price <= ?", 300]
+
+p: {:min_price=>100, :max_price=>300}
+c: ["min_price >= ? AND max_price <= ?", 100, 300]
+
+ +

Plus Another Condition

+ +

Let’s make things a bit more user-friendly. Let user filter phones by brand. We do it by adding another field, let’s call it brand, bearing a straight string value (that’s just a simple tutorial, remember?).

+ +

Our params now becomes something like:

+ +
params[:brand] = "Nokia"    # Can be blank.
+params[:min_price] = 100    # Can be blank.
+params[:max_price] = 300    # Can be blank.
+
+ +

Let’s build a tuple:

+ +
tup = SmartTuple.new(" AND ") +
+  ({:brand => params[:brand]} if params[:brand].present?) +
+  (["min_price >= ?", params[:min_price]] if params[:min_price].present?) +
+  (["max_price <= ?", params[:max_price]] if params[:max_price].present?)
+
+ +

The above code shows that we can construct ready-made tuples with a single expression, using + operator. Also, if a condition is an equality test, we can use Hash notation: {:brand => params[:brand]}.

+ +

A quick look at params and :conditions, again:

+ +
p: {:brand=>"Nokia"}
+c: ["brand = ?", "Nokia"]
+
+p: {:brand=>"Nokia", :max_price=>300}
+c: ["brand = ? AND max_price <= ?", "Nokia", 300]
+
+p: {:brand=>"Nokia", :min_price=>100, :max_price=>300}
+c: ["brand = ? AND min_price >= ? AND max_price <= ?", "Nokia", 100, 300]
+
+ +

We Want More!

+ +

Since we now see how easy it’s to build compound conditions, we decide to further extend our search form. Now we want to:

+ + + +

From params perspective that’s something like:

+ +
params[:brands] = ["Nokia", "Motorola"]         # Can be blank.
+params[:min_price] = 100                        # Can be blank.
+params[:max_price] = 300                        # Can be blank.
+params[:colors] = ["Black", "Silver", "Pink"]   # Can be blank.
+
+ +

Quite obvious is that supplied values for brands and colors should be OR’ed. We’re now facing the task of creating a “sub-tuple”, e.g. to match brand, and then merging this sub-tuple into main tuple. Doing it straight is something like:

+ +
tup = SmartTuple.new(" AND ")
+
+if params[:brands].present?
+  subtup = SmartTuple.new(" OR ")
+  params[:brands].each {|brand| subtup << ["brand = ?", brand]}
+  tup << subtup
+end
+
+ +

Or, in a smarter way by utilizing #add_each method:

+ +
tup = SmartTuple.new(" AND ")
+tup << SmartTuple.new(" OR ").add_each(params[:brands]) {|v| ["brand = ?", v]} if params[:brands].present?
+
+ +

The final query:

+ +
Phone.find(:all, {:conditions => [SmartTuple.new(" AND "),
+  (SmartTuple.new(" OR ").add_each(params[:brands]) {|v| ["brand = ?", v]} if params[:brands].present?),
+  (["min_price >= ?", params[:min_price]] if params[:min_price].present?),
+  (["max_price <= ?", params[:max_price]] if params[:max_price].present?),
+  (SmartTuple.new(" OR ").add_each(params[:colors]) {|v| ["color = ?", v]} if params[:colors].present?),
+].sum.compile})
+
+ +
+

NOTE: In the above sample I’ve used Array#sum (available in ActiveSupport) instead of + to add statements to the tuple. I prefer to write it like this since it allows to comment and swap lines without breaking the syntax.

+
+ +
+

NOTE: Recommended Rails 3 usage is:

+ +
Phone.where(...)      # Pass a compiled SmartTuple object in place of `...`.
+
+
+ +

Checking out params and :conditions:

+ +
p: {:brands=>["Nokia"], :max_price=>300}
+c: ["brand = ? AND max_price <= ?", "Nokia", 300]
+
+p: {:brands=>["Nokia", "Motorola"], :max_price=>300}
+c: ["(brand = ? OR brand = ?) AND max_price <= ?", "Nokia", "Motorola", 300]
+     ^--                    ^-- note the auto brackets
+
+p: {:brands=>["Nokia", "Motorola"], :max_price=>300, :colors=>["Black"]}
+c: ["(brand = ? OR brand = ?) AND max_price <= ? AND color = ?", "Nokia", "Motorola", 300, "Black"]
+
+p: {:brands=>["Nokia", "Motorola"], :colors=>["Black", "Silver", "Pink"]}
+c: ["(brand = ? OR brand = ?) AND (color = ? OR color = ? OR color = ?)", "Nokia", "Motorola", "Black", "Silver", "Pink"]
+
+ +

That’s the end of our tutorial. Hope now you’ve got an idea of what SmartTuple is.

+ +

API Summary

+ +

Here’s a brief cheatsheet, which outlines the main SmartTuple features.

+ +

Appending Statements

+ +
# Array.
+tup << ["brand = ?", "Nokia"]
+tup << ["brand = ? AND color = ?", "Nokia", "Black"]
+
+# Hash.
+tup << {:brand => "Nokia"}
+tup << {:brand => "Nokia", :color => "Black"}
+
+# Another SmartTuple.
+tup << other_tuple
+
+# String. Generally NOT recommended.
+tup << "min_price >= 75"
+
+ +

Appending empty or blank (where appropriate) statements has no effect on the receiver:

+ +
tup << nil
+tup << []
+tup << {}
+tup << an_empty_tuple
+tup << ""
+tup << "  "     # Will be treated as blank if ActiveSupport is on.
+
+ +

Another way to append something is to use +.

+ +
tup = SmartTuple.new(" AND ") + {:brand => "Nokia"} + ["max_price <= ?", 300]
+
+ +

Appending one statement per each collection item is easy through #add_each:

+ +
tup.add_each(["Nokia", "Motorola"]) {|v| ["brand = ?", v]}
+
+ +

The latter can be made conditional. Remember, appending nil has no effect on the receiving tuple, which gives us freedom to use conditions whenever we want to:

+ +
tup.add_each(["Nokia", "Motorola"]) do |v|
+  ["brand = ?", v] if v =~ /^Moto/
+end
+
+ +

Bracketing the Statements: Always, Never and Auto

+ +

This chapter still has to be written.

+ +
tup = SmartTuple.new(" AND ")
+tup.brackets
+=> :auto
+
+tup.brackets = true
+tup.brackets = false
+tup.brackets = :auto
+
+ +

Clearing

+ +

To put tuple into its initial state, do a:

+ +
tup.clear
+
+ +

Compiling

+ +

Compiling is converting the tuple into something suitable for use as :conditions of an ActiveRecord call.

+ +

It’s as straight as:

+ +
tup.compile
+tup.to_a        # An alias, does the same.
+
+# Go fetch!
+Phone.find(:all, :conditions => tup.compile)    # Rails 2
+Phone.where(tup.compile)                        # Rails 3
+
+ +

Contents and Size

+ +

You can examine tuple’s state with methods often found in other Ruby classes: #empty?, #size, and attribute accessors #statements and #args.

+ +
tup = SmartTuple.new(" AND ")
+tup.empty?
+=> true
+tup.size
+=> 0
+
+tup << ["brand = ?", "Nokia"]
+tup.empty?
+=> false
+tup.size
+=> 1
+
+tup << ["max_price >= ?", 300]
+tup.size
+=> 2
+
+tup.statements
+=> ["brand = ?", "max_price >= ?"]
+tup.args
+=> ["Nokia", 300]
+
+ +

Feedback

+ +

Send bug reports, suggestions and criticisms through project’s page on GitHub.

+ +

Licensed under the MIT License.

diff --git a/README.md b/README.md index 7dd83d4..79a199a 100644 --- a/README.md +++ b/README.md @@ -6,21 +6,39 @@ SmartTuple: A Simple Yet Smart SQL Conditions Builder Introduction ------------ -Sometimes we need to build SQL WHERE statements which are compound or conditional by nature. SmartTuple simplifies this task by letting us build statements of virtually unlimited complexity out of smaller ones. +Sometimes we need to build SQL `WHERE` statements which are compound or conditional by nature. **SmartTuple** simplifies this task by letting us build statements of virtually unlimited complexity out of smaller ones. SmartTuple is suitable for use with Ruby on Rails (ActiveRecord) and other Ruby frameworks and ORMs. -Setup ------ +Setup (Rails 3) +--------------- + +In your app's `Gemfile`, add: + + gem "smart_tuple" + +To install the gem with RDoc/ri documentation, do a: - $ gem sources --add http://rubygems.org $ gem install smart_tuple +Otherwise, do a `bundle install`. + + +Setup (Rails 2) +--------------- + In your app's `config/environment.rb` do a: config.gem "smart_tuple" +To install the gem, do a: + + $ gem sources --add http://rubygems.org + $ gem install smart_tuple + +, or use `rake gems:install`. + Kickstart Demo -------------- @@ -32,7 +50,7 @@ Kickstart Demo @phones = Phone.find(:all, :conditions => tup.compile) -There's a number of ways you can use SmartTuple depending on the situation. They are covered in the tutorial below. +There's a number of ways you can use SmartTuple. Some of them is covered in the tutorial below. Tutorial @@ -151,6 +169,10 @@ The final query: > NOTE: In the above sample I've used `Array#sum` (available in ActiveSupport) instead of `+` to add statements to the tuple. I prefer to write it like this since it allows to comment and swap lines without breaking the syntax. +> NOTE: Recommended Rails 3 usage is: +> +> Phone.where(...) # Pass a compiled SmartTuple object in place of `...`. + Checking out `params` and `:conditions`: p: {:brands=>["Nokia"], :max_price=>300} @@ -172,7 +194,7 @@ That's the end of our tutorial. Hope now you've got an idea of what SmartTuple i API Summary ----------- -Here's a brief cheatsheet, which outlines main SmartTuple features. +Here's a brief cheatsheet, which outlines the main SmartTuple features. ### Appending Statements ### @@ -187,8 +209,8 @@ Here's a brief cheatsheet, which outlines main SmartTuple features. # Another SmartTuple. tup << other_tuple - # String. - tup << "brand IS NULL" + # String. Generally NOT recommended. + tup << "min_price >= 75" Appending empty or blank (where appropriate) statements has no effect on the receiver: @@ -244,7 +266,8 @@ It's as straight as: tup.to_a # An alias, does the same. # Go fetch! - Phone.find(:all, :conditions => tup.compile) + Phone.find(:all, :conditions => tup.compile) # Rails 2 + Phone.where(tup.compile) # Rails 3 ### Contents and Size ### diff --git a/Rakefile b/Rakefile index d8fa4ac..8e065bd 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,5 @@ require "rake/rdoctask" +require "yaml" GEM_NAME = "smart_tuple" @@ -14,9 +15,9 @@ begin gem.files = FileList[ "[A-Z]*", "*.gemspec", - "generators/**/*", - "lib/**/*.rb", "init.rb", + "lib/**/*.rb", + "spec/**/*.rb", ] end rescue LoadError @@ -26,13 +27,22 @@ end desc "Rebuild gemspec and package" task :rebuild => [:gemspec, :build] -desc "Push (publish) gem to RubyGems (aka Gemcutter)" -task :push => :rebuild do - # Yet found no way to ask Jeweler forge a complete version string for us. +desc "Push (publish) gem to RubyGems.org" +task :push do + # NOTE: Yet found no way to ask Jeweler forge a complete version string for us. vh = YAML.load(File.read("VERSION.yml")) - version = [vh[:major], vh[:minor], vh[:patch]].join(".") - pkgfile = File.join("pkg", [GEM_NAME, "-", version, ".gem"].to_s) - system("gem", "push", pkgfile) + version = [vh[:major], vh[:minor], vh[:patch], vh[:build]].compact.join(".") + pkgfile = File.join("pkg", "#{GEM_NAME}-#{version}.gem") + Kernel.system("gem", "push", pkgfile) +end + +desc "Generate RDoc documentation" +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = "doc" + rdoc.title = "SmartTuple" + #rdoc.options << "--line-numbers" + #rdoc.options << "--inline-source" + rdoc.rdoc_files.include("lib/**/*.rb") end desc "Compile README preview" diff --git a/VERSION.yml b/VERSION.yml index 195f944..6d2311f 100644 --- a/VERSION.yml +++ b/VERSION.yml @@ -1,4 +1,4 @@ --- :major: 0 :minor: 1 -:patch: 1 +:patch: 2 diff --git a/dev/github.css b/dev/github.css new file mode 100644 index 0000000..c9ad160 --- /dev/null +++ b/dev/github.css @@ -0,0 +1,41 @@ + +body{font:13.34px helvetica,arial,freesans,clean,sans-serif;} +body{width:920px;margin:0 auto;padding:0 15px;text-align:left;} + +/* Это неважно работает, с виду погано. */ +xbody{background-color:#f8f8f8;padding:.7em;} + +/* +#readme div.plain,#readme div.wikistyle{background-color:#f8f8f8;padding:.7em;} +.site{width:920px;margin:0 auto;padding:0 15px;text-align:left;} +#readme{font:13.34px helvetica,arial,freesans,clean,sans-serif;} +#readme.announce{margin:1em 0;} +#readme span.name{font-size:140%;padding:.8em 0;} +#readme div.plain,#readme div.wikistyle{background-color:#f8f8f8;padding:.7em;} +#readme.announce div.plain,#readme.announce div.wikistyle{border:1px solid #e9e9e9;} +#readme.blob div.plain,#readme.blob div.wikistyle{border-top:none;} +#readme div.plain pre{font-family:'Bitstream Vera Sans Mono','Courier',monospace;font-size:85%;color:#444;} +*/ + +h1,h2,h3,h4,h5,h6{border:0!important;} +h1{font-size:170%!important;border-top:4px solid #aaa!important;padding-top:.5em!important;margin-top:1.5em!important;} +h1:first-child{margin-top:0!important;padding-top:.25em!important;border-top:none!important;} +h2{font-size:150%!important;margin-top:1.5em!important;border-top:4px solid #e0e0e0!important;padding-top:.5em!important;} +h3{margin-top:1em!important;} +p{margin:1em 0!important;line-height:1.5em!important;} +ul{margin:1em 0 1em 2em!important;} +ol{margin:1em 0 1em 2em!important;} +ul ul,ul ol,ol ol,ol ul{margin-top:0!important;margin-bottom:0!important;} +blockquote{margin:1em 0!important;border-left:5px solid #ddd!important;padding-left:.6em!important;color:#555!important;} +dt{font-weight:bold!important;margin-left:1em!important;} +dd{margin-left:2em!important;margin-bottom:1em!important;} +table{margin:1em 0!important;} +table th{border-bottom:1px solid #bbb!important;padding:.2em 1em!important;} +table td{border-bottom:1px solid #ddd!important;padding:.2em 1em!important;} +pre{margin:1em 0!important;font-size:90%!important;background-color:#f8f8ff!important;border:1px solid #dedede!important;padding:.5em!important;line-height:1.5em!important;color:#444!important;overflow:auto!important;} +pre code{padding:0!important;font-size:100%!important;background-color:#f8f8ff!important;border:none!important;} +code{font-size:90%!important;background-color:#f8f8ff!important;color:#444!important;padding:0 .2em!important;border:1px solid #dedede!important;} +pre.console{margin:1em 0!important;font-size:90%!important;background-color:black!important;padding:.5em!important;line-height:1.5em!important;color:white!important;} +pre.console code{padding:0!important;font-size:100%!important;background-color:black!important;border:none!important;color:white!important;} +pre.console span{color:#888!important;} +pre.console span.command{color:yellow!important;} diff --git a/dev/head.html b/dev/head.html new file mode 100644 index 0000000..46f9cf9 --- /dev/null +++ b/dev/head.html @@ -0,0 +1,4 @@ + + + + diff --git a/init.rb b/init.rb index 2e4ed38..32acd38 100644 --- a/init.rb +++ b/init.rb @@ -1,3 +1,4 @@ +# Rails plugin init. Dir[File.join(File.dirname(__FILE__), "lib/**/*.rb")].each do |fn| require fn end diff --git a/lib/smart_tuple.rb b/lib/smart_tuple.rb index 46758c4..f209019 100644 --- a/lib/smart_tuple.rb +++ b/lib/smart_tuple.rb @@ -1,34 +1,50 @@ +# SQL condition builder. +# +# tup = SmartTuple.new(" AND ") +# tup << {:brand => "Nokia"} +# tup << ["min_price >= ?", 75] +# tup.compile # => ["brand = ? AND min_price >= ?", "Nokia", 75] class SmartTuple + # Array of SQL argument parts. attr_reader :args + + # Put brackets around statements. true, false or :auto. Default: + # + # :auto attr_reader :brackets + + # String to glue statements together. attr_accessor :glue + + # Array of SQL statement parts. attr_reader :statements + # Initializer. + # # new(" AND ") - # new(" OR ") - # new(", ") # E.g. for a SET or UPDATE statement. + # new(" OR ", :brackets => true) def initialize(glue, attrs = {}) @glue = glue clear attrs.each {|k, v| send("#{k}=", v)} end - # We need it to control #dup behaviour. - def initialize_copy(src) + # Service initializer for dup. + def initialize_copy(src) #:nodoc: @statements = src.statements.dup @args = src.args.dup end - # NOTE: Alphabetical order below. - - # Add a sub-statement, return new object. See #<<. + # Add a statement, return new object. See #<<. + # # SmartTuple.new(" AND ") + {:brand => "Nokia"} + ["max_price <= ?", 300] - def +(sub) + def +(arg) # Since #<< supports chaining, it boils down to this. - dup << sub + dup << arg end - # Append a sub-statement. + # Add a statement, return self. + # # # Array. # tup << ["brand = ?", "Nokia"] # tup << ["brand = ? AND color = ?", "Nokia", "Black"] @@ -41,75 +57,71 @@ def +(sub) # tup << other_tuple # # # String. Generally NOT recommended. - # tup << "brand IS NULL" + # tup << "min_price >= 75" + # + # Adding anything empty or blank (where appropriate) has no effect on the receiver: # - # Appending empty or blank (where appropriate) statements has no effect on the receiver: # tup << nil # tup << [] # tup << {} # tup << another_empty_tuple # tup << "" # tup << " " # Will be treated as blank if ActiveSupport is on. - def <<(sub) - ##p "self.class", self.class - - # NOTE: Autobracketing help is placing [value] instead of (value) into @statements. #compile will take it into account. + def <<(arg) + # NOTE: Autobracketing is placing `[value]` instead of `value` into `@statements`. #compile understands it. # Chop off everything empty first time. - if sub.nil? or (sub.empty? rescue false) or (sub.blank? rescue false) - ##puts "-- empty" - elsif sub.is_a? String or (sub.acts_like? :string rescue false) - ##puts "-- is a string" - @statements << sub.to_s - elsif sub.is_a? Array - # NOTE: If sub == [], the execution won't get here. + if arg.nil? or (arg.empty? rescue false) or (arg.blank? rescue false) + elsif arg.is_a? String or (arg.acts_like? :string rescue false) + @statements << arg.to_s + elsif arg.is_a? Array + # NOTE: If arg == [], the execution won't get here. # So, we've got at least one element. Therefore stmt will be scalar, and args -- DEFINITELY an array. - stmt, args = sub[0], sub[1..-1] - ##p "stmt", stmt - ##p "args", args + stmt, args = arg[0], arg[1..-1] if not (stmt.nil? or (stmt.empty? rescue false) or (stmt.blank? rescue false)) - ##puts "-- stmt nempty" # Help do autobracketing later. Here we can ONLY judge by number of passed arguments. @statements << (args.size > 1 ? [stmt] : stmt) @args += args end - elsif sub.is_a? Hash - sub.each do |k, v| + elsif arg.is_a? Hash + arg.each do |k, v| if v.nil? - # NOTE: AR supports it for hashes only. ["kk = ?", nil] will not be converted. + # NOTE: AR supports it for Hashes only. ["kk = ?", nil] will not be converted. @statements << "#{k} IS NULL" else @statements << "#{k} = ?" @args << v end end - elsif sub.is_a? self.class - # NOTE: If sub is empty, the execution won't get here. + elsif arg.is_a? self.class + # NOTE: If arg is empty, the execution won't get here. # Autobrackets here are smarter, than in Array processing case. - stmt = sub.compile[0] - @statements << ((sub.size > 1 or sub.args.size > 1) ? [stmt] : stmt) - @args += sub.args + stmt = arg.compile[0] + @statements << ((arg.size > 1 or arg.args.size > 1) ? [stmt] : stmt) + @args += arg.args else - raise ArgumentError, "Invalid sub-statement #{sub.inspect}" + raise ArgumentError, "Invalid statement #{arg.inspect}" end # Return self, it's IMPORTANT to make chaining possible. self end - # Iterate over collection and add block's result per each record. + # Iterate over collection and add block's result to self once per each record. + # # add_each(brands) do |v| # ["brand = ?", v] # end # # Can be conditional: + # # tup.add_each(["Nokia", "Motorola"]) do |v| # ["brand = ?", v] if v =~ /^Moto/ # end def add_each(collection, &block) raise ArgumentError, "Code block expected" if not block - ##p "collection", collection + collection.each do |v| self << yield(v) end @@ -119,6 +131,7 @@ def add_each(collection, &block) end # Set bracketing mode. + # # brackets = true # Put brackets around each sub-statement. # brackets = false # Don't put brackets. # brackets = :auto # Automatically put brackets around compound sub-statements. @@ -127,23 +140,23 @@ def brackets=(value) @brackets = value end - # Set self into default state. + # Clear self. def clear @statements = [] @args = [] @brackets = :auto - # Array does it like this. We do either. + # `Array` does it like this. We do either. self end - # Compile self into an array. + # Compile self into an array. Empty self yields empty array. + # + # compile # => [] + # compile # => ["brand = ? AND min_price >= ?", "Nokia", 75] def compile return [] if empty? - ##p "@statements", @statements - ##p "@args", @args - # Build "bracketed" statements. bsta = @statements.map do |s| auto_brackets, scalar_s = s.is_a?(Array) ? [true, s[0]] : [false, s] @@ -178,6 +191,10 @@ def compile end alias_method :to_a, :compile + # Return true if self is empty. + # + # tup = SmartTuple.new(" AND ") + # tup.empty? # => true def empty? @statements.empty? end diff --git a/smart_tuple.gemspec b/smart_tuple.gemspec index b30fb5e..a0220ed 100644 --- a/smart_tuple.gemspec +++ b/smart_tuple.gemspec @@ -1,43 +1,39 @@ # Generated by jeweler -# DO NOT EDIT THIS FILE -# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec` +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' # -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = %q{smart_tuple} - s.version = "0.1.1" + s.version = "0.1.2" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Alex Fortuna"] - s.date = %q{2010-11-16} + s.date = %q{2011-07-14} s.description = %q{A Simple Yet Smart SQL Conditions Builder} s.email = %q{alex.r@askit.org} s.extra_rdoc_files = [ "README.html", - "README.md" + "README.md" ] s.files = [ "MIT-LICENSE", - "README.html", - "README.md", - "Rakefile", - "VERSION.yml", - "init.rb", - "lib/smart_tuple.rb", - "smart_tuple.gemspec" + "README.html", + "README.md", + "Rakefile", + "VERSION.yml", + "init.rb", + "lib/smart_tuple.rb", + "smart_tuple.gemspec", + "spec/smart_tuple_spec.rb", + "spec/spec_helper.rb" ] s.homepage = %q{http://github.com/dadooda/smart_tuple} - s.rdoc_options = ["--charset=UTF-8"] s.require_paths = ["lib"] - s.rubygems_version = %q{1.3.7} + s.rubygems_version = %q{1.6.2} s.summary = %q{A Simple Yet Smart SQL Conditions Builder} - s.test_files = [ - "spec/spec_helper.rb", - "spec/smart_tuple_spec.rb" - ] if s.respond_to? :specification_version then - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION s.specification_version = 3 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then @@ -46,3 +42,4 @@ Gem::Specification.new do |s| else end end +