og 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. data/ProjectInfo +2 -2
  2. data/README +8 -4
  3. data/Rakefile +1 -1
  4. data/doc/AUTHORS +1 -1
  5. data/doc/RELEASES +81 -0
  6. data/examples/README +7 -0
  7. data/lib/glue/cacheable.rb +152 -0
  8. data/lib/glue/hierarchical.rb +5 -4
  9. data/lib/glue/optimistic_locking.rb +0 -1
  10. data/lib/glue/orderable.rb +46 -44
  11. data/lib/glue/taggable.rb +7 -4
  12. data/lib/glue/timestamped.rb +1 -1
  13. data/lib/og.rb +13 -6
  14. data/lib/og/entity.rb +226 -9
  15. data/lib/og/evolution.rb +2 -2
  16. data/lib/og/ez/clause.rb +147 -0
  17. data/lib/og/ez/condition.rb +181 -0
  18. data/lib/og/manager.rb +31 -30
  19. data/lib/og/relation.rb +5 -5
  20. data/lib/og/relation/has_many.rb +3 -1
  21. data/lib/og/relation/joins_many.rb +1 -1
  22. data/lib/og/store.rb +6 -3
  23. data/lib/og/store/kirby.rb +3 -5
  24. data/lib/og/store/mysql.rb +0 -1
  25. data/lib/og/store/sql.rb +43 -7
  26. data/lib/og/store/sqlite.rb +97 -11
  27. data/lib/og/store/sqlite2.rb +231 -0
  28. data/lib/og/test/testcase.rb +1 -1
  29. data/lib/og/vendor/mysql.rb +103 -25
  30. data/test/glue/tc_revisable.rb +11 -11
  31. data/test/og/CONFIG.rb +20 -8
  32. data/test/og/mixin/tc_hierarchical.rb +5 -3
  33. data/test/og/mixin/tc_optimistic_locking.rb +6 -4
  34. data/test/og/mixin/tc_orderable.rb +22 -22
  35. data/test/og/mixin/tc_taggable.rb +15 -11
  36. data/test/og/mixin/tc_timestamped.rb +4 -2
  37. data/test/og/multi_validations_model.rb +8 -0
  38. data/test/og/store/tc_filesys.rb +15 -12
  39. data/test/og/store/tc_kirby.rb +14 -11
  40. data/test/og/tc_accumulator.rb +1 -3
  41. data/test/og/tc_cacheable.rb +58 -0
  42. data/test/og/tc_delete_all.rb +13 -16
  43. data/test/og/tc_ez.rb +33 -0
  44. data/test/og/tc_finder.rb +2 -4
  45. data/test/og/tc_inheritance.rb +3 -3
  46. data/test/og/tc_inheritance2.rb +2 -3
  47. data/test/og/tc_join.rb +3 -2
  48. data/test/og/tc_multi_validations.rb +3 -3
  49. data/test/og/tc_multiple.rb +3 -6
  50. data/test/og/tc_override.rb +19 -13
  51. data/test/og/tc_polymorphic.rb +1 -3
  52. data/test/og/tc_resolve.rb +32 -0
  53. data/test/og/tc_reverse.rb +27 -28
  54. data/test/og/tc_scoped.rb +2 -4
  55. data/test/og/tc_select.rb +1 -3
  56. data/test/og/tc_store.rb +3 -8
  57. data/test/og/tc_validation.rb +2 -2
  58. data/test/og/tc_validation2.rb +56 -58
  59. data/test/og/tc_validation_loop.rb +2 -5
  60. metadata +15 -7
  61. data/lib/og/vendor/mysql411.rb +0 -306
@@ -1,4 +1,4 @@
1
- require 'nano/inflect'
1
+ require 'facet/inflect'
2
2
 
3
3
  # The default Tag implementation. A tag attaches semantics to
4
4
  # a given object.
@@ -25,7 +25,10 @@ class Tag
25
25
  #++
26
26
 
27
27
  def tag(obj)
28
- send(obj.class.name.underscore.pluralize.to_sym) << obj
28
+ class_name = obj.class.name
29
+ method_name = class_name.index('::') ? (class_name =~ /.*?\:\:(.*)/; $1) : class_name
30
+
31
+ send(method_name.pluralize.underscore.to_sym) << obj
29
32
  @count += 1
30
33
  save!
31
34
  end
@@ -119,7 +122,7 @@ module Taggable
119
122
  def find_with_tags(*names)
120
123
  info = ogmanager.store.join_table_info(self, Tag)
121
124
  count = names.size
122
- names = names.map { |n| "'#{n}'" }.join(',')
125
+ names = names.map { |n| ogmanager.store.quote(n) }.join(',')
123
126
  sql = %{
124
127
  SELECT o.*
125
128
  FROM
@@ -142,7 +145,7 @@ module Taggable
142
145
  def find_with_any_tag(*names)
143
146
  info = ogmanager.store.join_table_info(self, Tag)
144
147
  count = names.size
145
- names = names.map { |n| "'#{n}'" }.join(',')
148
+ names = names.map { |n| ogmanager.store.quote(n) }.join(',')
146
149
  sql = %{
147
150
  SELECT o.*
148
151
  FROM
@@ -1,4 +1,4 @@
1
- require 'nano/time/stamp'
1
+ require 'facet/time/stamp'
2
2
 
3
3
  require 'glue/aspects'
4
4
 
data/lib/og.rb CHANGED
@@ -1,15 +1,15 @@
1
1
  # = Og
2
2
  #
3
- # Copyright (c) 2004-2005, Navel Ltd (http://www.navel.gr)
4
3
  # Copyright (c) 2004-2005, George Moschovitis (http://www.gmosx.com)
4
+ # Copyright (c) 2004-2005, Navel Ltd (http://www.navel.gr)
5
5
  #
6
6
  # Og (http://www.nitrohq.com) is copyrighted free software
7
7
  # created and maintained by George Moschovitis (mailto:gm@navel.gr)
8
8
  # and released under the standard BSD Licence. For details
9
9
  # consult the file doc/LICENCE.
10
10
 
11
- require 'mega/synchash'
12
- require 'mega/syncarray'
11
+ require 'facet/synchash'
12
+ require 'facet/syncarray'
13
13
 
14
14
  require 'glue'
15
15
  require 'glue/logger'
@@ -43,7 +43,7 @@ module Og
43
43
 
44
44
  # The version.
45
45
 
46
- Version = '0.27.0'
46
+ Version = '0.28.0'
47
47
 
48
48
  # Library path.
49
49
 
@@ -95,6 +95,14 @@ module Og
95
95
 
96
96
  setting :thread_safe, :default => true, :doc => 'Enable/dissable thread safe mode'
97
97
 
98
+ # Address of the Og cache (if distributed caching enabled).
99
+
100
+ setting :cache_address, :default => '127.0.0.1', :doc => 'Address of the Og cache'
101
+
102
+ # Port of the Og cache (if distributed caching enabled).
103
+
104
+ setting :cache_port, :default => '9070', :doc => 'Port of the Og cache'
105
+
98
106
  # The active manager
99
107
 
100
108
  mattr_accessor :manager
@@ -119,7 +127,7 @@ module Og
119
127
 
120
128
  m = @@manager = Manager.new(options)
121
129
  m.manage_classes
122
-
130
+
123
131
  # Allows functionality that requires a store is
124
132
  # finalized to be implemented. A vastly superior
125
133
  # method of constructing foreign key constraints is an
@@ -147,7 +155,6 @@ module Og
147
155
  def escape(str)
148
156
  @@manager.store.escape(str)
149
157
  end
150
-
151
158
  end
152
159
 
153
160
  end
@@ -1,30 +1,55 @@
1
- require 'mega/class_inherit'
2
- require 'nano/kernel/assign_with'
1
+ require 'facet/classinherit'
2
+ require 'facet/kernel/assign_with'
3
3
 
4
4
  require 'glue/property'
5
5
  require 'og/relation'
6
+ require 'og/ez/clause'
7
+ require 'og/ez/condition'
6
8
 
7
9
  module Og
8
10
 
9
11
  # Include this module to classes to make them managable by Og.
12
+ #--
13
+ # gmosx, WARTNING: If you change the methods here, don't
14
+ # forget to update the Cacheable overrides.
15
+ #++
10
16
 
11
17
  module EntityMixin
12
18
 
19
+ def self.included(base)
20
+ # If the after_enchant callback is defined, call it
21
+ # to allow for some customization. Have a look at cacheable
22
+ # for an example.
23
+
24
+ if base.respond_to?(:after_enchant)
25
+ base.after_enchant(base)
26
+ end
27
+ end
28
+
29
+ # Persist the object.
30
+
13
31
  def save(options = nil)
14
32
  self.class.ogmanager.store.save(self, options)
15
33
  end
16
34
  alias_method :save!, :save
17
35
  alias_method :validate_and_save, :save
18
36
 
37
+ # Force saving of the objects, even if the validations
38
+ # don't pass.
39
+
19
40
  def force_save!(options = nil)
20
41
  self.class.ogmanager.store.force_save(self, options)
21
42
  end
22
43
 
44
+ # Insert the object in the store.
45
+
23
46
  def insert
24
47
  self.class.ogmanager.store.insert(self)
25
48
  return self
26
49
  end
27
50
 
51
+ # Update an existing object in the store.
52
+
28
53
  def update(options = nil)
29
54
  self.class.ogmanager.store.update(self, options)
30
55
  end
@@ -59,15 +84,13 @@ module EntityMixin
59
84
  self.class.ogmanager.store.transaction(&block)
60
85
  end
61
86
 
87
+ # Is this object saved in the store?
88
+
62
89
  def saved?
63
90
  not @oid.nil?
64
91
  end
65
92
  alias_method :serialized?, :saved?
66
93
 
67
- def og_quote(obj)
68
- self.class.ogmanager.store.quote(obj)
69
- end
70
-
71
94
  def assign_properties(values, options = {})
72
95
  Property.populate_object(self, values, options)
73
96
  return self
@@ -84,6 +107,14 @@ module EntityMixin
84
107
  end
85
108
  end
86
109
 
110
+ def og_quote(obj)
111
+ self.class.ogmanager.store.quote(obj)
112
+ end
113
+
114
+ def og_clone(*args)
115
+ Og::Entity.clone(self,*args)
116
+ end
117
+
87
118
  include RelationDSL
88
119
 
89
120
  class_inherit do
@@ -129,8 +160,25 @@ module EntityMixin
129
160
 
130
161
  # Find a specific instance of this class according
131
162
  # to the given conditions.
163
+ #
164
+ # Unlike the lower level store.find method it accepts
165
+ # Strings and Arrays instead of an option hash.
166
+ #
167
+ # === Examples
168
+ #
169
+ # User.find :condition => "name LIKE 'g%'", :order => 'name ASC'
170
+ # User.find :where => "name LIKE 'g%'", :order => 'name ASC'
171
+ # User.find :sql => "WHERE name LIKE 'g%' ORDER BY name ASC"
172
+ # User.find :condition => [ 'name LIKE ?', 'g%' ], :order => 'name ASC', :limit => 10
173
+ # User.find "name LIKE 'g%'"
174
+ # User.find "WHERE name LIKE 'g%' LIMIT 10"
175
+ # User.find [ 'name LIKE ?', 'g%' ]
132
176
 
133
- def find(options = {})
177
+ def find(options = {}, &block)
178
+ options = resolve_non_hash_options(options)
179
+
180
+ ez_resolve_options(options, &block) if block_given?
181
+
134
182
  if find_options = self.ann.self[:find_options]
135
183
  options = find_options.dup.update(options)
136
184
  end
@@ -144,17 +192,31 @@ module EntityMixin
144
192
 
145
193
  # Find a single instance of this class.
146
194
 
147
- def find_one(options = {})
195
+ def find_one(options = {}, &block)
196
+ options = resolve_non_hash_options(options)
197
+
198
+ ez_resolve_options(options, &block) if block_given?
199
+
200
+ if find_options = self.ann.self[:find_options]
201
+ options = find_options.dup.update(options)
202
+ end
203
+
148
204
  options[:class] = self
205
+ options[:type] = self if self.schema_inheritance_child?
206
+
149
207
  ogmanager.store.find_one(options)
150
208
  end
151
209
  alias_method :one, :find_one
152
210
  alias_method :first, :find_one
153
211
 
212
+ # Select an object using an sql query.
213
+
154
214
  def select(sql)
155
215
  ogmanager.store.select(sql, self)
156
216
  end
157
217
 
218
+ # Select one instance using an sqll query.
219
+
158
220
  def select_one(sql)
159
221
  ogmanager.store.select_one(sql, self)
160
222
  end
@@ -375,6 +437,60 @@ module EntityMixin
375
437
 
376
438
  private
377
439
 
440
+ # Resolve String/Array options.
441
+ #--
442
+ # FIXME: move to sql store?
443
+ #++
444
+
445
+ def resolve_non_hash_options(options)
446
+ if options.is_a? String
447
+ if options =~ /^WHERE/i
448
+ # pass the string as sql.
449
+ return { :sql => options }
450
+ else
451
+ # pass the string as a condition.
452
+ return { :condition => options }
453
+ end
454
+ elsif options.is_a? Array
455
+ # pass the array as condition (prepared statement style
456
+ # parsing/quoting.
457
+ return { :condition => options }
458
+ end
459
+
460
+ return options
461
+ end
462
+
463
+ # Resolve ez options, ie options provided using the
464
+ # Ruby query language.
465
+ #--
466
+ # gmosx: investigate this.
467
+ #++
468
+
469
+ def ez_resolve_options(options, &block)
470
+ klass = self.name.downcase.to_sym
471
+ # conditions on self first
472
+ # conditions = [ez_condition(:outer => outer_mapping[klass], :inner => inner_mapping[klass])]
473
+ conditions = [ez_condition()]
474
+
475
+ # options[:include].uniq.each do |assoc|
476
+ # conditions << reflect_on_association(assoc).klass.ez_condition(:outer => outer_mapping[assoc], :inner => inner_mapping[assoc])
477
+ # end
478
+
479
+ yield *conditions
480
+
481
+ condition = Caboose::EZ::Condition.new
482
+ conditions.each { |c| condition << c }
483
+ options[:condition] = condition.to_sql
484
+ # p options[:conditions] if $DEBUG
485
+ end
486
+
487
+ # Returns a Condition for this object.
488
+
489
+ def ez_condition(options = {}, &block)
490
+ options[:table_name] ||= table()
491
+ Caboose::EZ::Condition.new(options, &block)
492
+ end
493
+
378
494
  # Helper method for dynamic finders. Finds the object dynamically parsed
379
495
  # method name is after.
380
496
 
@@ -386,7 +502,11 @@ private
386
502
  options = args.pop if args.last.is_a?(Hash)
387
503
 
388
504
  condition = attrs.zip(args).map do |name, value|
389
- %|#{name} #{options.delete("#{name}_op".to_sym) || '='} #{ogmanager.store.quote(value)}|
505
+ field_name = properties[name.to_sym][:field] ||
506
+ properties[name.to_sym][:name] ||
507
+ properties[name.to_sym][:symbol]
508
+
509
+ %|#{field_name} #{options.delete("#{name}_op".to_sym) || '='} #{ogmanager.store.quote(value)}|
390
510
  end.join(' AND ')
391
511
 
392
512
  options.update(
@@ -448,6 +568,102 @@ class Entity
448
568
  res
449
569
  end
450
570
 
571
+ # Entity copying support. Eventually this should all
572
+ # be eval'd in at enchanting stage for the minor
573
+ # speed increase.
574
+ # TODO: Convert to enchantments on objects
575
+
576
+ # Accepts source object, destination and ignore.
577
+ # Source and destination are self explanatory; ignore
578
+ # is a list of properties not to copy (i.e.
579
+ # :create_time,:update_time).
580
+ # By default sets the class variables directly on the
581
+ # remote model instance, if you set use_setter_method to
582
+ # true, uses create_time= style copying tactics,
583
+
584
+ def copy_properties(source, destination, ignore = [], use_setter_method = false)
585
+ property_copier(source, destination, ignore, use_setter_method, false)
586
+ end
587
+
588
+ # Copies relations of one record to another. Only copies
589
+ # has_one, refers_to, belongs_to relationships as
590
+ # has_many requires modifying of other objects and
591
+ # cannot be copied (by design). If you think you need to copy
592
+ # these relations, what you need is a joins_many relationship
593
+ # which can be copied.
594
+
595
+ def copy_inferior_relations(source, destination, ignore = [])
596
+ real_ignore = Array.new
597
+
598
+ # Map relation symbols to foreign keys.
599
+
600
+ ignore.each do |symbol|
601
+ source.class.relations.reject{|r| [Og::JoinsMany, Og::ManyToMany, Og::HasMany].include?(r.class)}.each do |relation|
602
+ if relation.name == symbol.to_s
603
+ real_ignore << relation.foreign_key.to_sym
604
+ break
605
+ end
606
+ end
607
+ end
608
+
609
+ # Use instance variable property copier method.
610
+
611
+ property_copier(source, destination, real_ignore, false, true)
612
+ end
613
+
614
+ def copy_equal_relations(source, destination, ignore = [])
615
+ source.class.relations.reject{|r| not [Og::JoinsMany, Og::ManyToMany].include?(r.class)}.each do |relation|
616
+ next if relation.name == nil or ignore.include?(relation.name)
617
+ source.send(relation.name).each do |related|
618
+ destination.send(relation.name).send(:<<, related)
619
+ end
620
+ end
621
+ end
622
+
623
+ # Copies all relations *except* HasMany which is impossible
624
+ # to copy. Use a JoinsMany relation instead if you need a
625
+ # copyable HasMany (which is irrational).
626
+
627
+ def copy_relations(source, destination, ignore = [])
628
+ copy_inferior_relations(source, destination, ignore)
629
+ copy_equal_relations(source, destination, ignore)
630
+ end
631
+
632
+ # Clones an object in every possible way (cannot copy
633
+ # HasMany but can copy all others - BelongsTo, etc).
634
+ # Provide a source object as first arguments, the rest
635
+ # (if any) are passed along to the initialize constructor
636
+ # when calling new to make the copied object.
637
+
638
+ def clone(source,*args)
639
+ destination = source.class.new(*args)
640
+ copy_properties(source, destination, [], false)
641
+ # Must save here to copy join tables.
642
+ destination.save!
643
+ copy_relations(source, destination, [])
644
+ destination.save!
645
+ destination
646
+ end
647
+
648
+ # Does the work of clone_properties and copy_inferior_relations.
649
+ # Syntax is the same with one extra field to tell the
650
+ # routine what it is copying.
651
+
652
+ def property_copier(source,destination,ignore,use_setter_method,relations)
653
+ primary_key_symbol = source.class.primary_key.symbol
654
+ source.class.properties.to_a.each do |symbol, property|
655
+ next if primary_key_symbol == symbol or ignore.include?(symbol) or
656
+ (relations and not property.relation) or (not relations and property.relation)
657
+
658
+ variable = "@#{symbol}"
659
+ if use_setter_method
660
+ destination.send("#{symbol}=".to_sym,source.instance_variable_get(variable))
661
+ else
662
+ destination.instance_variable_set(variable, source.instance_variable_get(variable))
663
+ end
664
+ end
665
+ end
666
+
451
667
  end
452
668
 
453
669
  end
@@ -456,3 +672,4 @@ end
456
672
 
457
673
  # * George Moschovitis <gm@navel.gr>
458
674
  # * Tom Sawyer <transfire@gmail.com>
675
+ # * Rob Pitt <rob@motionpath.com>
@@ -1,7 +1,7 @@
1
1
  require 'fileutils'
2
2
 
3
- require 'nano/kernel/constant'
4
- require 'nano/kernel/assign_with'
3
+ require 'facet/kernel/constant'
4
+ require 'facet/kernel/assign_with'
5
5
 
6
6
  # $DBG = true
7
7
 
@@ -0,0 +1,147 @@
1
+ # gmosx: Work in progress.
2
+
3
+ module Caboose
4
+
5
+ module EZ
6
+ # EZ::Condition plugin for generating the :conditions where clause
7
+ # for ActiveRecord::Base.find. And an extension to ActiveRecord::Base
8
+ # called AR::Base.find_with_conditions that takes a block and builds
9
+ # the where clause dynamically for you.
10
+
11
+ class AbstractClause
12
+
13
+ attr_reader :test
14
+ attr_accessor :outer
15
+
16
+ def to_sql; 'TRUE'; end
17
+
18
+ end
19
+
20
+ class Clause < AbstractClause
21
+ # need this so that id doesn't call Object#id
22
+ # left it open to add more methods that
23
+ # conflict when I find them
24
+ [:id].each { |m| undef_method m }
25
+
26
+ attr_reader :name, :test, :value
27
+
28
+ # Initialize a Clause object with the name of the
29
+ # column.
30
+ def initialize(*args)
31
+ @table_prefix = ''
32
+ @negate = false
33
+ case args.length
34
+ when 0:
35
+ raise 'Expected at least one parameter'
36
+ when 1:
37
+ @name = args.first.to_s
38
+ when 2:
39
+ @table_prefix = args[0].to_s + '.' unless args[0].to_s.empty?
40
+ @name = args[1].to_s
41
+ when 3:
42
+ @table_prefix = args[0].to_s + '.' unless args[0].to_s.empty?
43
+ @name = args[1].to_s
44
+ @negate = args[2]
45
+ end
46
+ # append ! to negate the statement
47
+ if @name[-1] == '!'
48
+ @negate = true
49
+ @name = @name.slice(0, @name.length - 1)
50
+ end
51
+ # prefix with esc_ to avoid clashes with standard methods like 'alias'
52
+ @name = @name.slice(4, @name.length) if @name =~ /^esc_.*/
53
+ end
54
+
55
+ # The == operator has been over-ridden here to
56
+ # stand in for an exact match ["foo = ?", "bar"]
57
+ def ==(other)
58
+ @test = :equals
59
+ @value = other
60
+ end
61
+
62
+ # The =~ operator has been over-ridden here to
63
+ # stand in for the sql LIKE "%foobar%" clause.
64
+ def =~(pattern)
65
+ @test = :like
66
+ @value = pattern
67
+ end
68
+
69
+ # The spaceship <=> operator has been over-ridden here to
70
+ # stand in for the sql ["BETWEEN ? AND ?", 1, 5] "%foobar%" clause.
71
+ def <=>(range)
72
+ @test = :between
73
+ @value = range
74
+ end
75
+
76
+ # The === operator has been over-ridden here to
77
+ # stand in for the sql ["IN (?)", [1,2,3]] clause.
78
+ def ===(range)
79
+ @test = :in
80
+ @value = range
81
+ end
82
+
83
+ # switch on @test and build appropriate clause to
84
+ # match the operation.
85
+ def to_sql
86
+ case @test
87
+ when :equals
88
+ if @value == :null
89
+ @negate ? ["#{@table_prefix}#{@name} IS NOT NULL"] : ["#{@table_prefix}#{@name} IS NULL"]
90
+ else
91
+ @negate ? ["#{@table_prefix}#{@name} != ?", @value] : ["#{@table_prefix}#{@name} = ?", @value]
92
+ end
93
+ when :like
94
+ @negate ? ["#{@table_prefix}#{@name} NOT LIKE ?", @value] : ["#{@table_prefix}#{@name} LIKE ?", @value]
95
+ when :between
96
+ @negate ? ["#{@table_prefix}#{@name} NOT BETWEEN ? AND ?", @value.begin, @value.end] : ["#{@table_prefix}#{@name} BETWEEN ? AND ?", @value.begin, @value.end]
97
+ when :in
98
+ @negate ? ["#{@table_prefix}#{@name} NOT IN (?)", @value.to_a] : ["#{@table_prefix}#{@name} IN (?)", @value.to_a]
99
+ else
100
+ ["#{@table_prefix}#{@name} #{@test} ?", @value]
101
+ end
102
+ end
103
+
104
+ # This method_missing takes care of setting
105
+ # @test to any operator thats not covered
106
+ # above. And @value to the value
107
+ def method_missing(name, *args)
108
+ @test = name
109
+ @value = args.first
110
+ end
111
+ end
112
+
113
+ class ArrayClause < AbstractClause
114
+
115
+ # wraps around an Array in ActiveRecord format ['column = ?', 2]
116
+
117
+ def initialize(cond_array)
118
+ @test = :array
119
+ @cond_array = cond_array
120
+ end
121
+
122
+ def to_sql
123
+ return nil if @cond_array.first.to_s.empty?
124
+ query = (@cond_array.first =~ /^\([^\(\)]+\)$/) ? "#{@cond_array.first}" : "(#{@cond_array.first})"
125
+ [query, @cond_array[1..@cond_array.length] ]
126
+ end
127
+
128
+ end
129
+
130
+ class SqlClause < AbstractClause
131
+
132
+ # wraps around a raw SQL string
133
+
134
+ def initialize(sql)
135
+ @test = :sql
136
+ @sql = sql
137
+ end
138
+
139
+ def to_sql
140
+ [@sql]
141
+ end
142
+
143
+ end
144
+
145
+ end # EZ
146
+
147
+ end # Caboose