criteria 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README +17 -17
- data/lib/criteria.rb +337 -125
- data/test/test_associations.rb +14 -0
- data/test/test_class_heirarchy.rb +33 -0
- data/test/test_criteria.rb +8 -1
- data/test/test_examples.rb +9 -11
- data/test/test_suite.rb +7 -1
- metadata +3 -2
data/README
CHANGED
@@ -18,20 +18,20 @@ Now, what if we wanted to then delegate to another method to add some additional
|
|
18
18
|
|
19
19
|
All that icky SQL, not so pretty. Depending on what you need to do, you might find a nicer way around things, but with criteria, we always stay in OO land:
|
20
20
|
|
21
|
-
c =
|
22
|
-
c.and User.password.eq(password)
|
23
|
-
end
|
21
|
+
c = User.email.eq(email) & User.password.eq(password)
|
24
22
|
c = apply_filter(c)
|
25
23
|
u = User.find(:first, c)
|
26
24
|
|
27
|
-
The important thing to notice here is that you can get a Column object by calling ActiveRecord::Base.column_name, e.g. User.email in the above example. This Column object allows you to then express some criteria on it, in the above example we use eq() to mean this column must be equal to the value passed to it. This in turn returns a Criterion object. A Criteria object is a collection of AND'd
|
25
|
+
The important thing to notice here is that you can get a Column object by calling ActiveRecord::Base.column_name, e.g. User.email in the above example. This Column object allows you to then express some criteria on it, in the above example we use eq() to mean this column must be equal to the value passed to it. This in turn returns a Criterion object. A Criteria object is a collection of Criterion objects that are AND'd or OR'd depending on how the Criteria was created.
|
28
26
|
|
29
27
|
As a further example, imagine we are implementing apply_filer to only allow a user to log in if their 'role' field is one of :admin, :user or :editor
|
30
28
|
|
31
29
|
def apply_filter(c)
|
32
30
|
if apply_fiter?
|
33
31
|
c.and User.role.in([:admin, :user, :editor])
|
34
|
-
|
32
|
+
else
|
33
|
+
c
|
34
|
+
end
|
35
35
|
end
|
36
36
|
|
37
37
|
As you can see, appending criteria is pretty easy.
|
@@ -40,22 +40,22 @@ As you can see, appending criteria is pretty easy.
|
|
40
40
|
|
41
41
|
The above was quite a simple example, and we only used ANDs and no nested boolean operations.
|
42
42
|
|
43
|
-
|
44
|
-
criteria
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
# We create a Criteria by calling new_criteria on User. This bind the Criteria instance to the User class
|
44
|
+
criteria = User.new_criteria
|
45
|
+
|
46
|
+
# We call and() and pass it a block. The return
|
47
|
+
criteria.and do
|
48
|
+
User.role.eq(:admin).or(User.active.eq(false)).or(User.created_at.gt(10.hours.ago))
|
48
49
|
end
|
49
|
-
criteria.and
|
50
|
-
c
|
51
|
-
c
|
52
|
-
c.or do
|
53
|
-
|
54
|
-
c2.and User.created_at.lt(10.hours.ago)
|
50
|
+
criteria.and
|
51
|
+
c = User.role.eq(:editor)
|
52
|
+
c|= User.active.eq(true)
|
53
|
+
c.or do
|
54
|
+
User.created_at.gt(20.days.go) & User.created_at.lt(10.hours.ago)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
The above query demonstrates how to nest Criteria objects, using them as if they were Criterion, and thus nest ANDs and ORs. If we call to_where_sql on the criteria, we would get:
|
58
|
+
The above query demonstrates how to nest Criteria objects, using them as if they were Criterion, and thus nest ANDs and ORs. It also shows the various syntactic methods that can be used to achieve the same results. If we call to_where_sql on the criteria, we would get:
|
59
59
|
|
60
60
|
((users.role=":admin" OR users.active=0 OR users.created_at>"2008-04-04 13:55:42") AND (users.role=":editor" OR users.active=1 OR (users.created_at>"2008-03-15 23:57:04" AND users.created_at<"2008-04-04 13:55:42")))
|
61
61
|
|
data/lib/criteria.rb
CHANGED
@@ -8,38 +8,75 @@ class Criteria
|
|
8
8
|
module VERSION #:nodoc:
|
9
9
|
MAJOR = 0
|
10
10
|
MINOR = 0
|
11
|
-
TINY =
|
11
|
+
TINY = 3
|
12
12
|
|
13
13
|
STRING = [MAJOR, MINOR, TINY].join('.')
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
@@debug = false
|
17
|
+
|
18
|
+
attr_accessor :limit, :offset #, :default_operator #, :operator
|
19
|
+
|
20
|
+
def self.debug
|
21
|
+
@@debug
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.debug=(val)
|
25
|
+
@@debug=val
|
26
|
+
end
|
17
27
|
|
18
28
|
# Create a new criteria, optionally pass in a Criterion object
|
19
29
|
# You can also pass a block, and self will be yielded
|
20
|
-
def initialize(
|
21
|
-
@
|
22
|
-
@
|
30
|
+
def initialize(clazz=nil, def_operator=:AND) # :yields: self
|
31
|
+
@associations = []
|
32
|
+
@criteria = []
|
33
|
+
@operator = def_operator
|
34
|
+
# @and = []
|
35
|
+
# @or = []
|
23
36
|
@order_by = []
|
24
37
|
@group_by = []
|
25
38
|
@select = []
|
26
39
|
@limit = nil
|
27
40
|
@offset = nil
|
28
41
|
@joins = []
|
29
|
-
@default_operator = :or
|
30
|
-
|
42
|
+
# @default_operator = :or
|
43
|
+
@clazz = clazz if clazz.is_a? Class
|
44
|
+
|
45
|
+
# Add the supplied criterion if it is defined
|
46
|
+
# add(clazz) if clazz.is_a? Criterion
|
47
|
+
# add(c) if c.is_a? Criterion
|
31
48
|
|
32
49
|
yield(self) if block_given?
|
33
50
|
end
|
34
51
|
|
52
|
+
# If this Criteria was created by passing an ActiveRecord::Base subclass as the first argument
|
53
|
+
# to the constructor, then this will return that class. Criteria that are bound to classes can
|
54
|
+
# perform additional introspection to resolve association dependencies
|
55
|
+
def bound_class
|
56
|
+
@clazz
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns an unmodifiable array of all AND'd and OR'd criteria
|
60
|
+
def all_criteria
|
61
|
+
and_criteria+or_criteria
|
62
|
+
end
|
63
|
+
|
35
64
|
# Return a modifiable array of AND'd criteria
|
36
|
-
def
|
37
|
-
|
65
|
+
def and_criteria
|
66
|
+
if operator==:AND
|
67
|
+
@criteria
|
68
|
+
else
|
69
|
+
@other_criteria.and_criteria
|
70
|
+
end
|
38
71
|
end
|
39
72
|
|
40
|
-
#
|
41
|
-
def
|
42
|
-
|
73
|
+
# Return a modifiable array of OR'd criteria
|
74
|
+
def or_criteria
|
75
|
+
if operator==:OR
|
76
|
+
@criteria
|
77
|
+
else
|
78
|
+
@other_criteria.or_criteria
|
79
|
+
end
|
43
80
|
end
|
44
81
|
|
45
82
|
# Returns an array of column's to be selected
|
@@ -59,12 +96,12 @@ class Criteria
|
|
59
96
|
|
60
97
|
# AND a criterion with the existing Criteria
|
61
98
|
def and(c=nil, &block)
|
62
|
-
add(c, :
|
99
|
+
add(c, :AND, &block)
|
63
100
|
end
|
64
101
|
|
65
102
|
# OR a criterion with the existing Criteria
|
66
103
|
def or(c=nil, &block)
|
67
|
-
add(c, :
|
104
|
+
add(c, :OR, &block)
|
68
105
|
end
|
69
106
|
|
70
107
|
def <<(c)
|
@@ -72,19 +109,42 @@ class Criteria
|
|
72
109
|
add(c)
|
73
110
|
end
|
74
111
|
|
75
|
-
def add(c=nil, operator=
|
76
|
-
|
112
|
+
def add(c=nil, operator=@operator, opts={}, &block)
|
113
|
+
|
114
|
+
# If we are using an :or and this is an :and, or vice versa, then create a new sub-criteria to contain it
|
115
|
+
if operator!=@operator
|
116
|
+
newcriteria = Criteria.new(bound_class, @operator==:AND ? :OR : :AND)
|
117
|
+
newcriteria << self
|
118
|
+
newcriteria.add(c, operator, opts, &block)
|
119
|
+
return newcriteria
|
120
|
+
end
|
121
|
+
|
122
|
+
# If the criterion object is nil, and there is a block given, yield a new Criteria to the block
|
123
|
+
c = yield(self) if c.nil? and block_given?
|
124
|
+
|
125
|
+
raise "Supplied argument is nil" if c.nil?
|
77
126
|
|
78
|
-
# puts "#{self}
|
127
|
+
# puts "Criteria.add: #{self} #{operator} #{c}"
|
128
|
+
# If the c is a column, moan about it and refuse to continue
|
79
129
|
if c.is_a? Column
|
80
130
|
raise "You cannot directly #{operator.to_s.upcase} an instanceof Column, you must call some sort of expression (eq, ne, gt, ge, etc) on it."
|
81
131
|
end
|
82
132
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
133
|
+
# If the criterion is bound to a class and this is also bound to a class we try and find an applicable association for the criterion
|
134
|
+
if !c.bound_class.nil? and !@clazz.nil?
|
135
|
+
# puts " + #{c.bound_class} AND #{@clazz}"
|
136
|
+
a=@clazz.find_associations_with(c.bound_class)
|
137
|
+
|
138
|
+
if a.size>1 and a.include? opts[:via]
|
139
|
+
@associations << opts[:via]
|
140
|
+
elsif a.size==0
|
141
|
+
# do nothing
|
142
|
+
else
|
143
|
+
@associations << a.first
|
144
|
+
end
|
87
145
|
end
|
146
|
+
|
147
|
+
@criteria << c
|
88
148
|
self
|
89
149
|
end
|
90
150
|
|
@@ -100,22 +160,12 @@ class Criteria
|
|
100
160
|
|
101
161
|
# Convert the AND and OR statements into the WHERE SQL
|
102
162
|
def to_where_sql
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
and_clauses << "(#{c})"
|
109
|
-
else
|
110
|
-
and_clauses << c
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
if @and.size>0
|
115
|
-
and_clauses << @and.collect {|c| c.to_where_sql}
|
163
|
+
if @criteria.size>0
|
164
|
+
out = @criteria.collect {|c| "#{c.to_where_sql}"}.join(" #{@operator} ")
|
165
|
+
"(#{out})"
|
166
|
+
else
|
167
|
+
""
|
116
168
|
end
|
117
|
-
|
118
|
-
and_clauses.size>0 ? "(#{and_clauses.join(" AND ")})" : ""
|
119
169
|
end
|
120
170
|
|
121
171
|
def to_order_by_sql
|
@@ -133,7 +183,7 @@ class Criteria
|
|
133
183
|
# Return a unique list of column objects that are referenced in this query
|
134
184
|
def columns
|
135
185
|
columns = []
|
136
|
-
|
186
|
+
@criteria.each do |c|
|
137
187
|
c.columns.each do |c2|
|
138
188
|
columns << c2
|
139
189
|
end
|
@@ -148,12 +198,24 @@ class Criteria
|
|
148
198
|
end
|
149
199
|
|
150
200
|
def associations
|
151
|
-
out =
|
152
|
-
|
201
|
+
out = @associations
|
202
|
+
@criteria.each do |c|
|
153
203
|
out+=c.associations
|
154
204
|
end
|
155
205
|
out
|
156
206
|
end
|
207
|
+
|
208
|
+
def list
|
209
|
+
@clazz.find(:all, self)
|
210
|
+
end
|
211
|
+
|
212
|
+
def count
|
213
|
+
@clazz.count(self)
|
214
|
+
end
|
215
|
+
|
216
|
+
def first
|
217
|
+
@clazz.find(:first, self)
|
218
|
+
end
|
157
219
|
|
158
220
|
# def list
|
159
221
|
# @clazz.find(:all, self.to_hash)
|
@@ -202,6 +264,13 @@ class Criteria
|
|
202
264
|
"Criteria(#{to_hash.inspect})"
|
203
265
|
end
|
204
266
|
|
267
|
+
def inspect(indent=0)
|
268
|
+
indent_str = " " * indent
|
269
|
+
"(\n" + @criteria.collect do |c|
|
270
|
+
"#{" " * (indent+1)}#{@operator} #{c.inspect(indent+1)}"
|
271
|
+
end.join("\n") + "\n#{indent_str})"
|
272
|
+
end
|
273
|
+
|
205
274
|
# This class reprsents an ordering by a particular column. The normal way to retreive and instance of
|
206
275
|
# this object is by calling the direction on the Column instance, e.g. User.email.asc
|
207
276
|
class Order
|
@@ -228,7 +297,8 @@ class Criteria
|
|
228
297
|
end
|
229
298
|
|
230
299
|
class Column
|
231
|
-
def initialize(clazz, column, adapter=ActiveRecord::Base.connection)
|
300
|
+
def initialize(clazz, column, options={}, adapter=ActiveRecord::Base.connection)
|
301
|
+
@options = {}
|
232
302
|
@adapter = adapter
|
233
303
|
if clazz.is_a? Class
|
234
304
|
@clazz = clazz
|
@@ -237,6 +307,10 @@ class Criteria
|
|
237
307
|
end
|
238
308
|
@column = (column.is_a? String) ? column.intern : column
|
239
309
|
end
|
310
|
+
|
311
|
+
def bound_class
|
312
|
+
@clazz if @clazz.is_a? Class
|
313
|
+
end
|
240
314
|
|
241
315
|
def column_name
|
242
316
|
(@column.is_a? Symbol) ? @column : @column.name.intern
|
@@ -319,7 +393,20 @@ class Criteria
|
|
319
393
|
end
|
320
394
|
|
321
395
|
def to_sql_name
|
322
|
-
"
|
396
|
+
out = ""
|
397
|
+
if @adapter.methods.include? "quote_table_name"
|
398
|
+
# puts @adapter.method(:quote_table_name).inspect
|
399
|
+
out+="#{@adapter.quote_table_name(table_name)}"
|
400
|
+
else
|
401
|
+
out+="#{table_name}"
|
402
|
+
end
|
403
|
+
out+="."
|
404
|
+
if @adapter.methods.include? "quote_column_name"
|
405
|
+
out+="#{@adapter.quote_column_name(column_name)}"
|
406
|
+
else
|
407
|
+
out+="#{column_name}"
|
408
|
+
end
|
409
|
+
out
|
323
410
|
end
|
324
411
|
|
325
412
|
def to_s
|
@@ -430,6 +517,10 @@ class Criteria
|
|
430
517
|
@opts = opts
|
431
518
|
end
|
432
519
|
|
520
|
+
def bound_class
|
521
|
+
@column.bound_class
|
522
|
+
end
|
523
|
+
|
433
524
|
def associations
|
434
525
|
if @column.is_a? Association
|
435
526
|
[@column.association_name]
|
@@ -445,22 +536,35 @@ class Criteria
|
|
445
536
|
# AND this with another criterion
|
446
537
|
def &(criterion)
|
447
538
|
if !criterion.nil?
|
448
|
-
|
449
|
-
c.and self
|
450
|
-
c.and criterion
|
451
|
-
c
|
539
|
+
to_criteria(:AND).and(criterion)
|
452
540
|
end
|
453
541
|
end
|
454
542
|
|
455
543
|
# OR this with another criterion
|
456
544
|
def |(criterion)
|
457
545
|
if !criterion.nil?
|
458
|
-
|
459
|
-
c.or self
|
460
|
-
c.or criterion
|
461
|
-
c
|
546
|
+
to_criteria(:OR).or(criterion)
|
462
547
|
end
|
463
548
|
end
|
549
|
+
|
550
|
+
# Convert this lone Criterion into a proper Criteria object. This is used by the following first(), list() and count() methods to
|
551
|
+
# provide a shortcut to the Criteria behaviour
|
552
|
+
def to_criteria(operator=:AND)
|
553
|
+
bound_class.new_criteria(self, operator)
|
554
|
+
end
|
555
|
+
|
556
|
+
# Allow Criteria-esque calls directly on this Criterion
|
557
|
+
def first
|
558
|
+
to_criteria.first
|
559
|
+
end
|
560
|
+
|
561
|
+
def list
|
562
|
+
to_criteria.list
|
563
|
+
end
|
564
|
+
|
565
|
+
def count
|
566
|
+
to_criteria.count
|
567
|
+
end
|
464
568
|
|
465
569
|
# Return a list of columns associated with this criterion, always an array with one element
|
466
570
|
def columns
|
@@ -483,6 +587,10 @@ class Criteria
|
|
483
587
|
"#{self.class}(#{to_where_sql})"
|
484
588
|
end
|
485
589
|
|
590
|
+
def inspect(indent=0)
|
591
|
+
to_s
|
592
|
+
end
|
593
|
+
|
486
594
|
end
|
487
595
|
|
488
596
|
class NotCriterion < Criterion
|
@@ -503,101 +611,205 @@ class Criteria
|
|
503
611
|
"NOT (#{@criterion.to_where_sql})"
|
504
612
|
end
|
505
613
|
end
|
506
|
-
end
|
507
614
|
|
508
|
-
class
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
615
|
+
class ClassHeirarchyContext
|
616
|
+
def initialize()
|
617
|
+
@vars = {}
|
618
|
+
end
|
619
|
+
def get(cl, var, default=nil)
|
620
|
+
@vars[cl]||={}
|
621
|
+
out = @vars[cl][var]
|
622
|
+
|
623
|
+
# search up the inheretence heirarchy
|
624
|
+
clazz = cl
|
625
|
+
while !clazz.nil? and out.nil?
|
626
|
+
out = (@vars[clazz]||{})[var]
|
627
|
+
clazz = clazz.superclass
|
628
|
+
end
|
519
629
|
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
# No? well lets check if there is an association on this class
|
526
|
-
# FIXME: this is very prone to error, we need a way of confirming this
|
527
|
-
elsif self.one_to_many_associations.include? column.to_s.intern
|
528
|
-
@@critera_columns[column.to_s] = Criteria::OneToManyAssociation.new(self, column, self.connection)
|
529
|
-
elsif self.many_to_one_associations.include? column.to_s.intern
|
530
|
-
@@critera_columns[column.to_s] = Criteria::ManyToOneAssociation.new(self, column, self.connection)
|
531
|
-
end
|
630
|
+
# set default if no value was found
|
631
|
+
if out.nil? and !default.nil?
|
632
|
+
@vars[cl][var] = default
|
633
|
+
out = @vars[cl][var]
|
634
|
+
end
|
532
635
|
|
533
|
-
|
534
|
-
@@critera_columns[column.to_s]
|
535
|
-
else
|
536
|
-
nil
|
636
|
+
out
|
537
637
|
end
|
538
|
-
end
|
539
638
|
|
540
|
-
|
541
|
-
|
542
|
-
|
639
|
+
def unset(cl, var)
|
640
|
+
@vars[cl].delete var
|
641
|
+
end
|
543
642
|
|
544
|
-
|
545
|
-
|
643
|
+
def set(cl,var, value)
|
644
|
+
@vars[cl]||={}
|
645
|
+
@vars[cl][var] = value
|
646
|
+
end
|
546
647
|
end
|
648
|
+
end
|
649
|
+
|
650
|
+
class Class
|
651
|
+
@@static = Criteria::ClassHeirarchyContext.new
|
547
652
|
|
548
|
-
def
|
549
|
-
|
550
|
-
self[a[0]] || __ar_method_missing(*a)
|
653
|
+
def static_get(key, default=nil)
|
654
|
+
@@static.get(self, key, default)
|
551
655
|
end
|
552
656
|
|
553
|
-
|
554
|
-
|
555
|
-
#
|
556
|
-
# Since this can come as the first or second argument in the find() method, we just scan for
|
557
|
-
# any instances of Criteria and then call to_hash
|
558
|
-
def self.__criteria_find(*a)
|
559
|
-
a = rewrite_criteria_in_args(a)
|
560
|
-
# puts a.inspect
|
561
|
-
__ar_find(*a)
|
657
|
+
def static_set(name, value)
|
658
|
+
@@static.set(self, name, value)
|
562
659
|
end
|
563
|
-
|
660
|
+
def static_unset(name)
|
661
|
+
@@static.unset(self, name)
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
# FIXME: Argh! ruby's class variables are not per-class... so setting a class variable in a subclass overrides the
|
666
|
+
# parent class... in order to get around this, we need to store the associations per class, and then iterate over a classes
|
667
|
+
# parent classes to find all applicable associations
|
668
|
+
class ActiveRecord::Base
|
669
|
+
|
670
|
+
def self.new_criteria(c=nil, operator=:AND)
|
671
|
+
if c==:AND or c==:OR
|
672
|
+
Criteria.new(self, c)
|
673
|
+
else
|
674
|
+
crit = Criteria.new(self, operator)
|
675
|
+
crit << c if !c.nil?
|
676
|
+
crit
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
564
680
|
class << self
|
681
|
+
# alias_method :__ar_inherited, :inherited
|
565
682
|
alias_method :__ar_method_missing, :method_missing
|
566
|
-
alias_method :method_missing, :__criteria_method_missing
|
567
683
|
alias_method :__ar_find, :find
|
568
|
-
alias_method :
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
684
|
+
alias_method :__ar_count, :count
|
685
|
+
alias_method :__ar_belongs_to, :belongs_to
|
686
|
+
alias_method :__ar_has_many, :has_many
|
687
|
+
|
688
|
+
# Provide the ability to search by criteria using the normal find() syntax. Basically we
|
689
|
+
# just turn the criteria object into a normal query hash (:conditions, :order, :limit, etc)
|
690
|
+
#
|
691
|
+
# Since this can come as the first or second argument in the find() method, we just scan for
|
692
|
+
# any instances of Criteria and then call to_hash
|
693
|
+
def find(*a)
|
694
|
+
a = rewrite_criteria_in_args(a)
|
695
|
+
__ar_find(*a)
|
696
|
+
end
|
697
|
+
|
698
|
+
def count(*a)
|
699
|
+
a = rewrite_criteria_in_args(a)
|
700
|
+
__ar_count(*a)
|
701
|
+
end
|
702
|
+
|
703
|
+
def method_missing(*a)
|
704
|
+
puts "seeking method #{a[0]}: #{criteria_columns[a[0]]}" if Criteria.debug
|
705
|
+
if !criteria_columns[a[0].to_s].nil?
|
706
|
+
criteria_columns[a[0].to_s]
|
577
707
|
else
|
578
|
-
|
708
|
+
__ar_method_missing(*a)
|
579
709
|
end
|
580
710
|
end
|
711
|
+
|
712
|
+
# protected
|
713
|
+
|
714
|
+
def rewrite_criteria_in_args(args)
|
715
|
+
args.collect do |arg|
|
716
|
+
if arg.is_a? Criteria or arg.is_a? Criteria::Criterion
|
717
|
+
# puts "#{arg.to_hash.inspect}"
|
718
|
+
arg.to_hash
|
719
|
+
else
|
720
|
+
arg
|
721
|
+
end
|
722
|
+
end
|
723
|
+
end
|
724
|
+
# end
|
725
|
+
#
|
726
|
+
# def self.inherited(clazz)
|
727
|
+
# __ar_inherited(clazz)
|
728
|
+
#
|
729
|
+
# puts "Enhance #{clazz} < #{clazz.superclass}"
|
730
|
+
#
|
731
|
+
# class << clazz
|
732
|
+
# puts "enhancing #{self} < #{self.superclass}"
|
733
|
+
# @@one_to_many_associations = {}
|
734
|
+
# @@many_to_one_associations = {}
|
735
|
+
# @@criteria_columns = nil
|
736
|
+
|
737
|
+
def criteria_columns
|
738
|
+
if static_get(:criteria_columns).nil?
|
739
|
+
static_set(:criteria_columns, {})
|
740
|
+
# do some more initialization
|
741
|
+
self.columns.each do |c|
|
742
|
+
static_get(:criteria_columns)[c.name.to_s] = Criteria::Column.new(self, c, {}, self.connection)
|
743
|
+
end
|
744
|
+
self.one_to_many_associations.each do |k,v|
|
745
|
+
puts "found 1->m #{k} => #{v.inspect}" if Criteria.debug
|
746
|
+
static_get(:criteria_columns)[k.to_s] = Criteria::OneToManyAssociation.new(self, k, v, self.connection)
|
747
|
+
end
|
748
|
+
self.many_to_one_associations.each do |k,v|
|
749
|
+
puts "found m->1 #{k} => #{v.inspect}" if Criteria.debug
|
750
|
+
static_get(:criteria_columns)[k.to_s] = Criteria::ManyToOneAssociation.new(self, k, v, self.connection)
|
751
|
+
end
|
752
|
+
|
753
|
+
end
|
754
|
+
puts "call criteria_columns on #{self} (#{static_get(:criteria_columns).keys.inspect})" if Criteria.debug
|
755
|
+
static_get(:criteria_columns)
|
756
|
+
end
|
757
|
+
|
758
|
+
def one_to_many_associations
|
759
|
+
static_get(:one_to_many_associations, {})
|
760
|
+
end
|
761
|
+
|
762
|
+
def many_to_one_associations
|
763
|
+
static_get(:many_to_one_associations, {})
|
764
|
+
end
|
765
|
+
|
766
|
+
# Access the column object by using the class as a hash. This is useful if there are other
|
767
|
+
# static methods that have the same name as your field, or the field name is not, for some reason,
|
768
|
+
# allowed as a ruby method name
|
769
|
+
#
|
770
|
+
# Usually, you can just call Class.column_name to access this column object
|
771
|
+
def [](column)
|
772
|
+
criteria_columns[column.to_s]
|
773
|
+
end
|
774
|
+
|
775
|
+
# Search the associations defined on this class for one that would map to the given class
|
776
|
+
# This returns an array of symbols which are equal to the symbols used in the has_many and
|
777
|
+
# belongs_to definitions
|
778
|
+
def find_associations_with(clazz)
|
779
|
+
associations = []
|
780
|
+
pluralized = Inflector.pluralize(clazz.table_name.to_s).intern
|
781
|
+
singularized = clazz.table_name.to_s.singularize.intern
|
782
|
+
puts "does #{self} 1->m #{one_to_many_associations.inspect} contain #{pluralized}" if Criteria.debug
|
783
|
+
if !one_to_many_associations[pluralized].nil?
|
784
|
+
associations << pluralized
|
785
|
+
associations << one_to_many_associations[pluralized][:through] if !one_to_many_associations[pluralized][:through].nil?
|
786
|
+
end
|
787
|
+
puts "does #{self} m->1 #{many_to_one_associations.inspect} contain #{singularized}" if Criteria.debug
|
788
|
+
if !many_to_one_associations[singularized].nil?
|
789
|
+
associations << singularized
|
790
|
+
# associations << many_to_one_associations[singularized][:through] if !many_to_one_associations[singularized][:through].nil?
|
791
|
+
end
|
792
|
+
associations #+clazz.find_associations_with(self)
|
793
|
+
end
|
794
|
+
|
795
|
+
def belongs_to(id, opts={})
|
796
|
+
# self.__criteria_enhance
|
797
|
+
puts "#{self} belongs to #{id} with #{opts.inspect}" if Criteria.debug
|
798
|
+
many_to_one_associations[id] = opts
|
799
|
+
__ar_belongs_to(id, opts)
|
800
|
+
end
|
801
|
+
|
802
|
+
def has_many(id, opts={}, &block)
|
803
|
+
# self.__criteria_enhance
|
804
|
+
puts "#{self} has many #{id} with #{opts.inspect}" if Criteria.debug
|
805
|
+
puts "#{self.one_to_many_associations.inspect}" if Criteria.debug
|
806
|
+
one_to_many_associations[id] = opts
|
807
|
+
puts "#{self.one_to_many_associations.inspect}" if Criteria.debug
|
808
|
+
__ar_has_many(id, opts, &block)
|
809
|
+
end
|
810
|
+
# end
|
581
811
|
end
|
582
812
|
end
|
583
813
|
|
584
|
-
|
585
814
|
module ActiveRecord::Associations::ClassMethods
|
586
|
-
def __criteria_belongs_to(id, opts={})
|
587
|
-
# puts "#{self} belongs to #{id} with #{opts.inspect}"
|
588
|
-
self.many_to_one_associations << id
|
589
|
-
__ar_belongs_to(id, opts)
|
590
|
-
end
|
591
|
-
|
592
|
-
alias_method :__ar_belongs_to, :belongs_to
|
593
|
-
alias_method :belongs_to, :__criteria_belongs_to
|
594
|
-
|
595
|
-
def __criteria_has_many(id, opts={}, &block)
|
596
|
-
# puts "#{self} has many #{id} with #{opts.inspect}"
|
597
|
-
self.one_to_many_associations << id
|
598
|
-
__ar_has_many(id, opts, &block)
|
599
|
-
end
|
600
|
-
|
601
|
-
alias_method :__ar_has_many, :has_many
|
602
|
-
alias_method :has_many, :__criteria_has_many
|
603
815
|
end
|
data/test/test_associations.rb
CHANGED
@@ -4,6 +4,20 @@ require 'test/mock_classes.rb'
|
|
4
4
|
|
5
5
|
class AssociationTest < Test::Unit::TestCase
|
6
6
|
|
7
|
+
def test_associations_from_subject
|
8
|
+
u = User.find(1)
|
9
|
+
c = Criteria.new(User).and Receipt.title.eq("Receipt1")
|
10
|
+
u2 = c.first
|
11
|
+
assert_equal u, u2
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_associations_from_subject_through_another
|
15
|
+
u = User.find(1)
|
16
|
+
c = Criteria.new(User).and Query.created_at.lt(Time.now)
|
17
|
+
u2 = c.first
|
18
|
+
assert_equal u, u2
|
19
|
+
end
|
20
|
+
|
7
21
|
def test_many_to_one
|
8
22
|
u = User.find(1)
|
9
23
|
c = Receipt.user.eq(u)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/criteria.rb'
|
3
|
+
require 'test/mock_classes.rb'
|
4
|
+
|
5
|
+
class ClassHeirarchyTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_inherit_class_vars
|
8
|
+
ActiveRecord::Base.static_set("hello", :world)
|
9
|
+
assert_equal :world, ActiveRecord::Base.static_get("hello")
|
10
|
+
assert_equal :world, User.static_get("hello")
|
11
|
+
ActiveRecord::Base.static_set("hello", :world_again)
|
12
|
+
assert_equal :world_again, ActiveRecord::Base.static_get("hello")
|
13
|
+
assert_equal :world_again, User.static_get("hello")
|
14
|
+
|
15
|
+
# Define the same variable at the child level
|
16
|
+
User.static_set("hello", :mondo)
|
17
|
+
assert_equal :mondo, User.static_get("hello")
|
18
|
+
|
19
|
+
# Unsetting the User-level static var should revert to the parent ActiveRecord::Base-level var
|
20
|
+
User.static_unset("hello")
|
21
|
+
assert_equal :world_again, User.static_get("hello")
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_no_overwrite
|
25
|
+
User.static_set("foo", "bar")
|
26
|
+
Class.static_set("foo", "baz")
|
27
|
+
ActiveRecord::Base.static_set("foo", "bum")
|
28
|
+
assert_equal "bar", User.static_get("foo")
|
29
|
+
assert_equal "baz", Class.static_get("foo")
|
30
|
+
assert_equal "bum", ActiveRecord::Base.static_get("foo")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/test/test_criteria.rb
CHANGED
@@ -4,6 +4,13 @@ require "test/mock_classes.rb"
|
|
4
4
|
|
5
5
|
class CriteriaTest < Test::Unit::TestCase
|
6
6
|
|
7
|
+
def test_bind_to_class
|
8
|
+
u = User.find(1)
|
9
|
+
c = User.new_criteria(Query.created_at < Time.now)
|
10
|
+
puts c.to_hash.inspect
|
11
|
+
assert_equal u, c.first
|
12
|
+
end
|
13
|
+
|
7
14
|
def test_order_by
|
8
15
|
c = Criteria.new
|
9
16
|
assert_equal nil, c.to_order_by_sql
|
@@ -80,4 +87,4 @@ class CriteriaTest < Test::Unit::TestCase
|
|
80
87
|
assert_equal nil, c.to_hash[:offset]
|
81
88
|
end
|
82
89
|
|
83
|
-
end
|
90
|
+
end
|
data/test/test_examples.rb
CHANGED
@@ -19,22 +19,20 @@ class ExamplesTest < Test::Unit::TestCase
|
|
19
19
|
a = (User.role == :admin) | (User.active == false) | (User.created_at > TIME2)
|
20
20
|
b = (User.role==:editor) | (User.active==true)
|
21
21
|
b|= ((User.created_at>TIME1) & (User.created_at<TIME2))
|
22
|
+
puts (a&b).inspect
|
22
23
|
assert_equal EXPECTED_HASH, (a&b).to_hash
|
23
24
|
end
|
24
25
|
|
25
26
|
def test_big_full
|
26
|
-
criteria =
|
27
|
-
criteria.and do
|
28
|
-
|
29
|
-
c.or User.active.eq(false)
|
30
|
-
c.or User.created_at.gt(TIME2)
|
27
|
+
criteria = User.new_criteria
|
28
|
+
criteria.and do
|
29
|
+
User.role.eq(:admin) | User.active.eq(false) | User.created_at.gt(TIME2)
|
31
30
|
end
|
32
|
-
criteria.and do
|
33
|
-
c
|
34
|
-
c
|
35
|
-
c.or do
|
36
|
-
|
37
|
-
c2.and User.created_at.lt(TIME2)
|
31
|
+
criteria.and do
|
32
|
+
c = User.role.eq(:editor)
|
33
|
+
c|= User.active.eq(true)
|
34
|
+
c.or do
|
35
|
+
User.created_at.gt(TIME1) & User.created_at.lt(TIME2)
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
data/test/test_suite.rb
CHANGED
@@ -1,3 +1,9 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
require 'test/test_criterion.rb'
|
3
|
-
require 'test/test_criteria.rb'
|
3
|
+
require 'test/test_criteria.rb'
|
4
|
+
require 'test/test_order.rb'
|
5
|
+
require 'test/test_examples.rb'
|
6
|
+
require 'test/test_column.rb'
|
7
|
+
require 'test/test_class_heirarchy.rb'
|
8
|
+
require 'test/test_associations.rb'
|
9
|
+
require 'test/test_active_record.rb'
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
|
|
3
3
|
specification_version: 1
|
4
4
|
name: criteria
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2008-
|
6
|
+
version: 0.0.3
|
7
|
+
date: 2008-06-10 00:00:00 +10:00
|
8
8
|
summary: Object-orientated Criteria for ActiveRecord
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -34,6 +34,7 @@ files:
|
|
34
34
|
test_files:
|
35
35
|
- test/test_active_record.rb
|
36
36
|
- test/test_associations.rb
|
37
|
+
- test/test_class_heirarchy.rb
|
37
38
|
- test/test_column.rb
|
38
39
|
- test/test_criteria.rb
|
39
40
|
- test/test_criterion.rb
|