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 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 = Criteria.new(User.email.eq(email)) do |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 and OR'd Criterion objects.
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
- end
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
- criteria = Criteria.new
44
- criteria.and do |c|
45
- c.or User.role.eq(:admin)
46
- c.or User.active.eq(false)
47
- c.or User.created_at.gt(10.hours.ago)
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 do |c|
50
- c.or User.role.eq(:editor)
51
- c.or User.active.eq(true)
52
- c.or do |c2|
53
- c2.and User.created_at.gt(20.days.go)
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 = 1
11
+ TINY = 3
12
12
 
13
13
  STRING = [MAJOR, MINOR, TINY].join('.')
14
14
  end
15
15
 
16
- attr_accessor :limit, :offset, :default_operator
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(c=nil) # :yields: self
21
- @and = []
22
- @or = []
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
- self.add(c) if c.is_a? Criterion
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 ands
37
- @and
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
- # Returns a modifiable array of OR'd criteria
41
- def ors
42
- @ors
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, :and, &block)
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, :or, &block)
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=self.default_operator)
76
- yield(c = Criteria.new) if c.nil? and block_given?
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} << OR #{c}"
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
- if operator==:or
84
- @or << c
85
- else
86
- @and << c
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
- and_clauses = []
104
-
105
- if @or.size>0
106
- c = @or.collect {|c| c.to_where_sql}.join(" OR ")
107
- if @and.size>0
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
- (@and + @or).each do |c|
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
- (@and + @or).each do |c|
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
- "#{@adapter.quote_table_name(table_name)}.#{@adapter.quote_column_name(column_name)}"
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
- c = Criteria.new
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
- c = Criteria.new
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 ActiveRecord::Base
509
- @@one_to_many_associations = []
510
- @@many_to_one_associations = []
511
- @@critera_columns = {}
512
-
513
- # Access the column object by using the class as a hash. This is useful if there are other
514
- # static methods that have the same name as your field, or the field name is not, for some reason,
515
- # allowed as a ruby method name
516
- #
517
- # Usually, you can just call Class.column_name to access this column object
518
- def self.[](column)
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
- # First check to see if there is a column on this class
521
- if self.columns.collect {|c| c.name }.include? column.to_s
522
- col = self.columns.reject {|c| c.name!=column.to_s }.first
523
- @@critera_columns[column.to_s] = Criteria::Column.new(self, col, self.connection)
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
- if @@critera_columns.has_key? column.to_s
534
- @@critera_columns[column.to_s]
535
- else
536
- nil
636
+ out
537
637
  end
538
- end
539
638
 
540
- def self.many_to_one_associations
541
- @@many_to_one_associations
542
- end
639
+ def unset(cl, var)
640
+ @vars[cl].delete var
641
+ end
543
642
 
544
- def self.one_to_many_associations
545
- @@one_to_many_associations
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 self.__criteria_method_missing(*a)
549
- # puts a.inspect
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
- # Provide the ability to search by criteria using the normal find() syntax. Basically we
554
- # just turn the criteria object into a normal query hash (:conditions, :order, :limit, etc)
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 :find, :__criteria_find
569
- end
570
-
571
- protected
572
-
573
- def self.rewrite_criteria_in_args(args)
574
- args.collect do |arg|
575
- if arg.is_a? Criteria or arg.is_a? Criteria::Criterion
576
- arg.to_hash
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
- arg
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
@@ -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
@@ -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
@@ -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 = Criteria.new
27
- criteria.and do |c|
28
- c.or User.role.eq(:admin)
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 |c|
33
- c.or User.role.eq(:editor)
34
- c.or User.active.eq(true)
35
- c.or do |c2|
36
- c2.and User.created_at.gt(TIME1)
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.1
7
- date: 2008-04-08 00:00:00 +10:00
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