og 0.27.0 → 0.28.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ProjectInfo +2 -2
- data/README +8 -4
- data/Rakefile +1 -1
- data/doc/AUTHORS +1 -1
- data/doc/RELEASES +81 -0
- data/examples/README +7 -0
- data/lib/glue/cacheable.rb +152 -0
- data/lib/glue/hierarchical.rb +5 -4
- data/lib/glue/optimistic_locking.rb +0 -1
- data/lib/glue/orderable.rb +46 -44
- data/lib/glue/taggable.rb +7 -4
- data/lib/glue/timestamped.rb +1 -1
- data/lib/og.rb +13 -6
- data/lib/og/entity.rb +226 -9
- data/lib/og/evolution.rb +2 -2
- data/lib/og/ez/clause.rb +147 -0
- data/lib/og/ez/condition.rb +181 -0
- data/lib/og/manager.rb +31 -30
- data/lib/og/relation.rb +5 -5
- data/lib/og/relation/has_many.rb +3 -1
- data/lib/og/relation/joins_many.rb +1 -1
- data/lib/og/store.rb +6 -3
- data/lib/og/store/kirby.rb +3 -5
- data/lib/og/store/mysql.rb +0 -1
- data/lib/og/store/sql.rb +43 -7
- data/lib/og/store/sqlite.rb +97 -11
- data/lib/og/store/sqlite2.rb +231 -0
- data/lib/og/test/testcase.rb +1 -1
- data/lib/og/vendor/mysql.rb +103 -25
- data/test/glue/tc_revisable.rb +11 -11
- data/test/og/CONFIG.rb +20 -8
- data/test/og/mixin/tc_hierarchical.rb +5 -3
- data/test/og/mixin/tc_optimistic_locking.rb +6 -4
- data/test/og/mixin/tc_orderable.rb +22 -22
- data/test/og/mixin/tc_taggable.rb +15 -11
- data/test/og/mixin/tc_timestamped.rb +4 -2
- data/test/og/multi_validations_model.rb +8 -0
- data/test/og/store/tc_filesys.rb +15 -12
- data/test/og/store/tc_kirby.rb +14 -11
- data/test/og/tc_accumulator.rb +1 -3
- data/test/og/tc_cacheable.rb +58 -0
- data/test/og/tc_delete_all.rb +13 -16
- data/test/og/tc_ez.rb +33 -0
- data/test/og/tc_finder.rb +2 -4
- data/test/og/tc_inheritance.rb +3 -3
- data/test/og/tc_inheritance2.rb +2 -3
- data/test/og/tc_join.rb +3 -2
- data/test/og/tc_multi_validations.rb +3 -3
- data/test/og/tc_multiple.rb +3 -6
- data/test/og/tc_override.rb +19 -13
- data/test/og/tc_polymorphic.rb +1 -3
- data/test/og/tc_resolve.rb +32 -0
- data/test/og/tc_reverse.rb +27 -28
- data/test/og/tc_scoped.rb +2 -4
- data/test/og/tc_select.rb +1 -3
- data/test/og/tc_store.rb +3 -8
- data/test/og/tc_validation.rb +2 -2
- data/test/og/tc_validation2.rb +56 -58
- data/test/og/tc_validation_loop.rb +2 -5
- metadata +15 -7
- data/lib/og/vendor/mysql411.rb +0 -306
data/lib/glue/taggable.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
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
|
-
|
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|
|
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|
|
148
|
+
names = names.map { |n| ogmanager.store.quote(n) }.join(',')
|
146
149
|
sql = %{
|
147
150
|
SELECT o.*
|
148
151
|
FROM
|
data/lib/glue/timestamped.rb
CHANGED
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 '
|
12
|
-
require '
|
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.
|
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
|
data/lib/og/entity.rb
CHANGED
@@ -1,30 +1,55 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
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
|
-
|
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>
|
data/lib/og/evolution.rb
CHANGED
data/lib/og/ez/clause.rb
ADDED
@@ -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
|