meta_where 0.9.9.2 → 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,14 @@
1
+ Changes since 0.9.9 (2010-11-15):
2
+ * Multiple conditions on the same column with the same predicate will be "OR"ed
3
+ instead of dropping all but the last one. This tracks the same behavior in
4
+ Rails 3-0-stable.
5
+ * You can specify a polymorphic belongs_to join via something like:
6
+ Model.joins(:joinable.type(PolymorphicClass))
7
+ * You can specify ActiveRecord objects as values:
8
+ Developer.joins(:projects).where(:projects => Project.first) is the same as
9
+ Developer.joins(:projects).where(:projects => {:id => Project.first.id}), but
10
+ less hashy.
11
+
1
12
  Changes since 0.9.6 (2010-09-30):
2
13
  * ARel 2.x and Rails 3.0.2 compatibility
3
14
  * Allow MetaWhere::And and MetaWhere::Or on the value side of a condition hash
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.9.2
1
+ 0.9.10
@@ -25,6 +25,10 @@ class Symbol
25
25
  MetaWhere::JoinType.new(self, Arel::Nodes::OuterJoin)
26
26
  end
27
27
 
28
+ def type(klass)
29
+ MetaWhere::JoinType.new(self, Arel::Nodes::InnerJoin, klass)
30
+ end
31
+
28
32
  def asc
29
33
  MetaWhere::Column.new(self, :asc)
30
34
  end
@@ -0,0 +1,16 @@
1
+ module MetaWhere
2
+ module BelongsToPolymorphicAssociation
3
+ def self.included(base)
4
+ base.class_eval do
5
+ alias_method_chain :conditions, :metawhere
6
+ alias :sql_conditions :conditions_with_metawhere
7
+ end
8
+ end
9
+
10
+ # How does this even work in core? Oh, nevermind, it doesn't. Patch submitted. :)
11
+ def conditions_with_metawhere
12
+ @conditions ||= interpolate_sql(association_class.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]
13
+ end
14
+
15
+ end
16
+ end
@@ -4,6 +4,7 @@ module MetaWhere
4
4
  def self.included(base)
5
5
  base.class_eval do
6
6
  alias_method_chain :build, :metawhere
7
+ alias_method_chain :find_join_association, :metawhere
7
8
  end
8
9
  end
9
10
 
@@ -12,14 +13,26 @@ module MetaWhere
12
13
  class AssociationNotFoundError < StandardError; end
13
14
 
14
15
  def build_with_metawhere(associations, parent = nil, join_type = Arel::Nodes::InnerJoin)
16
+ parent ||= @joins.last
15
17
  if MetaWhere::JoinType === associations
16
- parent||= @joins.last
17
- reflection = parent.reflections[associations.name] or
18
- raise AssociationNotFoundError, "Association named '#{ associations.name }' was not found; perhaps you misspelled it?"
19
- unless association = find_join_association(reflection, parent)
18
+ klass = associations.klass
19
+ join_type = associations.join_type
20
+ associations = associations.name
21
+ end
22
+
23
+ case associations
24
+ when Symbol, String
25
+ reflection = parent.reflections[associations.to_s.intern] or
26
+ raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?"
27
+ unless (association = find_join_association(reflection, parent)) && (!klass || association.active_record == klass)
20
28
  @reflections << reflection
21
- association = build_join_association(reflection, parent)
22
- association.join_type = associations.join_type
29
+ if reflection.options[:polymorphic]
30
+ raise ArgumentError, "You can't create a polymorphic belongs_to join without specifying the polymorphic class!" unless klass
31
+ association = build_polymorphic_join_association(reflection, parent, klass)
32
+ else
33
+ association = build_join_association(reflection, parent)
34
+ end
35
+ association.join_type = join_type
23
36
  @joins << association
24
37
  end
25
38
  association
@@ -27,5 +40,65 @@ module MetaWhere
27
40
  build_without_metawhere(associations, parent, join_type)
28
41
  end
29
42
  end
43
+
44
+ def find_join_association_with_metawhere(name_or_reflection, parent)
45
+ case name_or_reflection
46
+ when MetaWhere::JoinType
47
+ join_associations.detect do |j|
48
+ (j.reflection.name == name_or_reflection.name) &&
49
+ (j.reflection.klass == name_or_reflection.klass) &&
50
+ (j.parent == parent)
51
+ end
52
+ else
53
+ find_join_association_without_metawhere(name_or_reflection, parent)
54
+ end
55
+ end
56
+
57
+ def build_polymorphic_join_association(reflection, parent, klass)
58
+ PolymorphicJoinAssociation.new(reflection, self, klass, parent)
59
+ end
60
+ end
61
+
62
+ class PolymorphicJoinAssociation < ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
63
+
64
+ def initialize(reflection, join_dependency, polymorphic_class, parent = nil)
65
+ reflection.check_validity!
66
+ @active_record = polymorphic_class
67
+ @cached_record = {}
68
+ @join_dependency = join_dependency
69
+ @parent = parent || join_dependency.join_base
70
+ @reflection = reflection.clone
71
+ @reflection.instance_variable_set :"@klass", polymorphic_class
72
+ @aliased_prefix = "t#{ join_dependency.joins.size }"
73
+ @parent_table_name = @parent.active_record.table_name
74
+ @aliased_table_name = aliased_table_name_for(table_name)
75
+ @join = nil
76
+ @join_type = Arel::Nodes::InnerJoin
77
+ end
78
+
79
+ def ==(other)
80
+ other.class == self.class &&
81
+ other.reflection == reflection &&
82
+ other.active_record == active_record &&
83
+ other.parent == parent
84
+ end
85
+
86
+ def association_join
87
+ return @join if @join
88
+
89
+ aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
90
+ parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name, :engine => arel_engine)
91
+
92
+ @join = [
93
+ aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name]),
94
+ parent_table[options[:foreign_type]].eq(active_record.name)
95
+ ]
96
+
97
+ if options[:conditions]
98
+ @join << interpolate_sql(sanitize_sql(options[:conditions], aliased_table_name))
99
+ end
100
+
101
+ @join
102
+ end
30
103
  end
31
104
  end
@@ -1,22 +1,43 @@
1
1
  module MetaWhere
2
2
  class JoinType
3
- attr_reader :name, :join_type
3
+ attr_reader :name, :join_type, :klass
4
4
 
5
- def initialize(name, join_type = Arel::Nodes::InnerJoin)
5
+ def initialize(name, join_type = Arel::Nodes::InnerJoin, klass = nil)
6
6
  @name = name
7
7
  @join_type = join_type
8
+ @klass = klass
8
9
  end
9
10
 
10
11
  def ==(other)
11
12
  self.class == other.class &&
12
13
  name == other.name &&
13
- join_type == other.join_type
14
+ join_type == other.join_type &&
15
+ klass == other.klass
14
16
  end
15
17
 
16
18
  alias_method :eql?, :==
17
19
 
18
20
  def hash
19
- [name, join_type].hash
21
+ [name, join_type, klass].hash
22
+ end
23
+
24
+ def outer
25
+ @join_type = Arel::Nodes::OuterJoin
26
+ self
27
+ end
28
+
29
+ def inner
30
+ @join_type = Arel::Nodes::InnerJoin
31
+ self
32
+ end
33
+
34
+ def type(klass)
35
+ @klass = klass
36
+ self
37
+ end
38
+
39
+ def to_sym
40
+ self
20
41
  end
21
42
  end
22
43
  end
@@ -34,7 +34,7 @@ module MetaWhere
34
34
 
35
35
  def scope_for_create_with_metawhere
36
36
  @scope_for_create ||= begin
37
- @create_with_value || predicate_wheres.inject({}) do |hash, where|
37
+ @create_with_value || predicates_without_conflicting_equality.inject({}) do |hash, where|
38
38
  if is_equality_predicate?(where)
39
39
  hash[where.left.name] = where.right.respond_to?(:value) ? where.right.value : where.right
40
40
  end
@@ -108,7 +108,7 @@ module MetaWhere
108
108
  arel.joins(arel)
109
109
  end unless defined?(:custom_join_sql)
110
110
 
111
- def predicate_wheres
111
+ def predicates_without_conflicting_equality
112
112
  remove_conflicting_equality_predicates(flatten_predicates(@where_values, predicate_visitor))
113
113
  end
114
114
 
@@ -155,19 +155,9 @@ module MetaWhere
155
155
 
156
156
  arel = build_intelligent_joins(arel, visitor) if @joins_values.present?
157
157
 
158
- predicate_wheres = remove_conflicting_equality_predicates(flatten_predicates(@where_values, visitor))
158
+ predicate_wheres = flatten_predicates(@where_values.uniq, visitor)
159
159
 
160
- predicate_wheres.each do |where|
161
- next if where.blank?
162
-
163
- case where
164
- when Arel::Nodes::SqlLiteral
165
- arel = arel.where(where)
166
- else
167
- sql = where.is_a?(String) ? where : where.to_sql
168
- arel = arel.where(Arel::Nodes::SqlLiteral.new("(#{sql})"))
169
- end
170
- end
160
+ arel = collapse_wheres(arel, (predicate_wheres - ['']).uniq)
171
161
 
172
162
  arel = arel.having(*flatten_predicates(@having_values, visitor).reject {|h| h.blank?}) unless @having_values.empty?
173
163
 
@@ -244,6 +234,27 @@ module MetaWhere
244
234
  }.reverse
245
235
  end
246
236
 
237
+ def collapse_wheres(arel, wheres)
238
+ binaries = wheres.grep(Arel::Nodes::Binary)
239
+
240
+ groups = binaries.group_by do |binary|
241
+ [binary.class, binary.left]
242
+ end
243
+
244
+ groups.each do |_, bins|
245
+ test = bins.inject(bins.shift) do |memo, expr|
246
+ memo.or(expr)
247
+ end
248
+ arel = arel.where(test)
249
+ end
250
+
251
+ (wheres - binaries).each do |where|
252
+ where = Arel.sql(where) if String === where
253
+ arel = arel.where(Arel::Nodes::Grouping.new(where))
254
+ end
255
+ arel
256
+ end
257
+
247
258
  def flatten_predicates(predicates, visitor)
248
259
  predicates.map {|p|
249
260
  predicate = visitor.can_accept?(p) ? visitor.accept(p) : p
@@ -266,7 +277,7 @@ module MetaWhere
266
277
  end
267
278
 
268
279
  def stashed_association_joins
269
- @mw_stashed_association_joins ||= unique_joins.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)}
280
+ @mw_stashed_association_joins ||= unique_joins.grep(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
270
281
  end
271
282
 
272
283
  def non_association_joins
@@ -2,6 +2,10 @@ module MetaWhere
2
2
  module Utility
3
3
  private
4
4
 
5
+ def array_of_activerecords(val)
6
+ val.is_a?(Array) && !val.empty? && val.all? {|v| v.is_a?(ActiveRecord::Base)}
7
+ end
8
+
5
9
  def association_from_parent_and_column(parent, column)
6
10
  parent.is_a?(Symbol) ? nil : @join_dependency.send(:find_join_association, column, parent)
7
11
  end
@@ -9,7 +9,8 @@ module MetaWhere
9
9
  end
10
10
 
11
11
  def visit_Hash(o, parent)
12
- table = build_table(parent)
12
+ parent = parent.name if parent.is_a? MetaWhere::JoinType
13
+ table = tables[parent]
13
14
  built_attributes = o.map do |column, value|
14
15
  if value.is_a?(Hash)
15
16
  association = association_from_parent_and_column(parent, column)
@@ -27,7 +28,7 @@ module MetaWhere
27
28
  end
28
29
 
29
30
  def visit_Symbol(o, parent)
30
- table = self.build_table(parent)
31
+ table = tables[parent]
31
32
 
32
33
  unless attribute = table[o]
33
34
  raise ::ActiveRecord::StatementInvalid, "No attribute named `#{o}` exists for table `#{table.name}`"
@@ -42,7 +43,7 @@ module MetaWhere
42
43
  table_name, column_name = column_name.split('.', 2)
43
44
  table = Arel::Table.new(table_name, :engine => parent.arel_engine)
44
45
  else
45
- table = self.build_table(parent)
46
+ table = tables[parent]
46
47
  end
47
48
 
48
49
  unless attribute = table[column_name]
@@ -5,12 +5,13 @@ module MetaWhere
5
5
  class Predicate < Visitor
6
6
 
7
7
  def self.visitables
8
- [Hash, MetaWhere::Or, MetaWhere::And, MetaWhere::Condition, MetaWhere::Function]
8
+ [Hash, Array, MetaWhere::Or, MetaWhere::And, MetaWhere::Condition, MetaWhere::Function]
9
9
  end
10
10
 
11
11
  def visit_Hash(o, parent)
12
12
  parent ||= join_dependency.join_base
13
- table = build_table(parent)
13
+ parent = parent.name if parent.is_a? MetaWhere::JoinType
14
+ table = tables[parent]
14
15
  predicates = o.map do |column, value|
15
16
  if value.is_a?(Hash)
16
17
  association = association_from_parent_and_column(parent, column)
@@ -21,6 +22,9 @@ module MetaWhere
21
22
  elsif value.is_a?(Array) && !value.empty? && value.all? {|v| can_accept?(v)}
22
23
  association = association_from_parent_and_column(parent, column)
23
24
  value.map {|val| accept(val, association || column)}
25
+ elsif (value.is_a?(ActiveRecord::Base) || array_of_activerecords(value)) &&
26
+ reflection = parent.active_record.reflect_on_association(column.is_a?(MetaWhere::JoinType) ? column.name : column)
27
+ accept_activerecord_values(column, value, parent, reflection)
24
28
  else
25
29
  if column.is_a?(MetaWhere::Column)
26
30
  method = column.method
@@ -34,15 +38,16 @@ module MetaWhere
34
38
  table = Arel::Table.new(table_name, :engine => parent.arel_engine)
35
39
  end
36
40
 
37
- unless attribute = attribute_from_column_and_table(column, table)
38
- raise ::ActiveRecord::StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`"
39
- end
40
-
41
41
  unless valid_comparison_method?(method)
42
42
  raise ::ActiveRecord::StatementInvalid, "No comparison method named `#{method}` exists for column `#{column}`"
43
43
  end
44
44
 
45
- attribute.send(method, args_for_predicate(value))
45
+ if attribute = attribute_from_column_and_table(column, table)
46
+ attribute.send(method, args_for_predicate(value))
47
+ else
48
+ raise ::ActiveRecord::StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`"
49
+ end
50
+
46
51
  end
47
52
  end
48
53
 
@@ -56,6 +61,14 @@ module MetaWhere
56
61
  end
57
62
  end
58
63
 
64
+ def visit_Array(o, parent)
65
+ if o.first.is_a? String
66
+ join_dependency.join_base.send(:sanitize_sql, o)
67
+ else
68
+ o.map {|e| accept(e, parent)}.flatten
69
+ end
70
+ end
71
+
59
72
  def visit_MetaWhere_Or(o, parent)
60
73
  accept(o.condition1, parent).or(accept(o.condition2, parent))
61
74
  end
@@ -65,7 +78,7 @@ module MetaWhere
65
78
  end
66
79
 
67
80
  def visit_MetaWhere_Condition(o, parent)
68
- table = self.build_table(parent)
81
+ table = tables[parent]
69
82
 
70
83
  unless attribute = attribute_from_column_and_table(o.column, table)
71
84
  raise ::ActiveRecord::StatementInvalid, "No attribute named `#{o.column}` exists for table `#{table.name}`"
@@ -78,11 +91,55 @@ module MetaWhere
78
91
  end
79
92
 
80
93
  def visit_MetaWhere_Function(o, parent)
81
- self.table = self.build_table(parent)
94
+ self.table = tables[parent]
82
95
 
83
96
  o.to_sqlliteral
84
97
  end
85
98
 
99
+ private
100
+
101
+ def accept_activerecord_values(column, value, parent, reflection)
102
+ groups = Array.wrap(value).group_by {|v| v.class.base_class}
103
+ unless reflection.options[:polymorphic] || groups.keys.all? {|k| reflection.klass == k}
104
+ raise ArgumentError, "An object you supplied to :#{reflection.name} is not a #{reflection.klass}!"
105
+ end
106
+ case reflection.macro
107
+ when :has_many, :has_one, :has_and_belongs_to_many
108
+ conditions = nil
109
+ groups.each do |klass, values|
110
+ condition = {
111
+ (reflection.options[:foreign_key] || reflection.klass.primary_key).to_sym => values.size == 1 ? values.first.id : values.map(&:id)
112
+ }
113
+ conditions = conditions ? conditions | condition : condition
114
+ end
115
+
116
+ accept(conditions, association_from_parent_and_column(parent, column) || column)
117
+ when :belongs_to
118
+ conditions = nil
119
+ groups.each do |klass, values|
120
+ condition = if reflection.options[:polymorphic]
121
+ {
122
+ (reflection.options[:foreign_key] || reflection.primary_key_name).to_sym => values.size == 1 ? values.first.id : values.map(&:id),
123
+ reflection.options[:foreign_type].to_sym => klass.name
124
+ }
125
+ else
126
+ {(reflection.options[:foreign_key] || reflection.primary_key_name).to_sym => values.size == 1 ? values.first.id : values.map(&:id)}
127
+ end
128
+ conditions = conditions ? conditions | condition : condition
129
+ end
130
+
131
+ accept(conditions, parent)
132
+ end
133
+ end
134
+
135
+ def sanitize_or_accept_reflection_conditions(reflection, parent, column)
136
+ if !can_accept?(reflection.options[:conditions])
137
+ reflection.sanitized_conditions
138
+ else
139
+ accept(reflection.options[:conditions], association_from_parent_and_column(parent, column) || column)
140
+ end
141
+ end
142
+
86
143
  end
87
144
  end
88
145
  end
@@ -5,16 +5,17 @@ module MetaWhere
5
5
  class Visitor
6
6
  include MetaWhere::Utility
7
7
 
8
- attr_reader :join_dependency
8
+ attr_reader :join_dependency, :tables
9
9
 
10
10
  def initialize(join_dependency)
11
11
  @join_dependency = join_dependency
12
12
  jb = join_dependency.join_base
13
13
  @engine = jb.arel_engine
14
14
  @default_table = Arel::Table.new(jb.table_name, :as => jb.aliased_table_name, :engine => @engine)
15
+ @tables = Hash.new {|hash, key| hash[key] = get_table(key)}
15
16
  end
16
17
 
17
- def build_table(parent_or_table_name = nil)
18
+ def get_table(parent_or_table_name = nil)
18
19
  if parent_or_table_name.is_a?(Symbol)
19
20
  Arel::Table.new(parent_or_table_name, :engine => @engine)
20
21
  elsif parent_or_table_name.respond_to?(:aliased_table_name)
data/lib/meta_where.rb CHANGED
@@ -39,8 +39,10 @@ require 'core_ext/hash'
39
39
  require 'meta_where/visitors/attribute'
40
40
  require 'meta_where/visitors/predicate'
41
41
  require 'meta_where/association_reflection'
42
+ require 'meta_where/belongs_to_polymorphic_association'
42
43
  require 'meta_where/relation'
43
44
  require 'meta_where/join_dependency'
44
45
  ActiveRecord::Relation.send(:include, MetaWhere::Relation)
45
46
  ActiveRecord::Reflection::AssociationReflection.send(:include, MetaWhere::AssociationReflection)
46
- ActiveRecord::Associations::ClassMethods::JoinDependency.send(:include, MetaWhere::JoinDependency)
47
+ ActiveRecord::Associations::ClassMethods::JoinDependency.send(:include, MetaWhere::JoinDependency)
48
+ ActiveRecord::Associations::BelongsToPolymorphicAssociation.send(:include, MetaWhere::BelongsToPolymorphicAssociation)
data/meta_where.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{meta_where}
8
- s.version = "0.9.9.2"
8
+ s.version = "0.9.10"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ernie Miller"]
12
- s.date = %q{2010-11-17}
12
+ s.date = %q{2011-01-06}
13
13
  s.description = %q{
14
14
  MetaWhere offers the ability to call any Arel predicate methods
15
15
  (with a few convenient aliases) on your Model's attributes instead
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
37
37
  "lib/core_ext/symbol_operators.rb",
38
38
  "lib/meta_where.rb",
39
39
  "lib/meta_where/association_reflection.rb",
40
+ "lib/meta_where/belongs_to_polymorphic_association.rb",
40
41
  "lib/meta_where/column.rb",
41
42
  "lib/meta_where/compound.rb",
42
43
  "lib/meta_where/condition.rb",
@@ -1,3 +1,3 @@
1
1
  class Note < ActiveRecord::Base
2
- belongs_to :notable, :polymorphic => true
2
+ belongs_to :notable, :polymorphic => true, :conditions => '1=1'
3
3
  end
@@ -1,79 +1,95 @@
1
1
  peter:
2
+ id : 1
2
3
  notable_type: Developer
3
4
  notable_id : 1
4
5
  note : A straight shooter with upper management written all over him.
5
-
6
+
6
7
  michael:
8
+ id : 2
7
9
  notable_type: Developer
8
10
  notable_id : 2
9
11
  note : Doesn't like the singer of the same name. The nerve!
10
12
 
11
13
  samir:
14
+ id : 3
12
15
  notable_type: Developer
13
16
  notable_id : 3
14
17
  note : Naga.... Naga..... Not gonna work here anymore anyway.
15
18
 
16
19
  herb:
20
+ id : 4
17
21
  notable_type: Developer
18
22
  notable_id : 4
19
23
  note : Will show you what he's doing.
20
24
 
21
25
  dude:
26
+ id : 5
22
27
  notable_type: Developer
23
28
  notable_id : 5
24
29
  note : Nothing of note.
25
30
 
26
31
  ernie:
32
+ id : 6
27
33
  notable_type: Developer
28
34
  notable_id : 6
29
35
  note : Complete slacker. Should probably be fired.
30
36
 
31
37
  someone:
38
+ id : 7
32
39
  notable_type: Developer
33
40
  notable_id : 7
34
41
  note : Just another developer.
35
42
 
36
43
  another:
44
+ id : 8
37
45
  notable_type: Developer
38
46
  notable_id : 8
39
47
  note : Placing a note in this guy's file for insubordination.
40
48
 
41
49
  initech:
50
+ id : 9
42
51
  notable_type: Company
43
52
  notable_id : 1
44
53
  note : Innovation + Technology!
45
54
 
46
55
  aos:
56
+ id : 10
47
57
  notable_type: Company
48
58
  notable_id : 2
49
59
  note : Advanced solutions of an optical nature.
50
60
 
51
61
  mission_data:
62
+ id : 11
52
63
  notable_type: Company
53
64
  notable_id : 3
54
65
  note : Best design + development shop in the 'ville.
55
66
 
56
67
  y2k:
68
+ id : 12
57
69
  notable_type: Project
58
70
  notable_id : 1
59
71
  note : It may have already passed but that's no excuse to be unprepared!
60
72
 
61
73
  virus:
74
+ id : 13
62
75
  notable_type: Project
63
76
  notable_id : 2
64
77
  note : It could bring the company to its knees.
65
78
 
66
79
  awesome:
80
+ id : 14
67
81
  notable_type: Project
68
82
  notable_id : 3
69
83
  note : This note is AWESOME!!!
70
84
 
71
85
  metasearch:
86
+ id : 15
72
87
  notable_type: Project
73
88
  notable_id : 4
74
89
  note : A complete waste of the developer's time.
75
90
 
76
91
  another:
92
+ id : 16
77
93
  notable_type: Project
78
94
  notable_id : 5
79
95
  note : This is another project note.
@@ -314,5 +314,99 @@ class TestRelations < Test::Unit::TestCase
314
314
  assert_match /IN \(1, 2, 3\)/, query.to_sql
315
315
  assert_same_elements Developer.all, query.all
316
316
  end
317
+
318
+ should "merge multiple conditions on the same column and predicate with ORs" do
319
+ assert_match /"developers"."name" = 'blah' OR "developers"."name" = 'blah2'/,
320
+ @r.where(:name => 'blah').where(:name => 'blah2').to_sql
321
+ assert_match /"developers"."name" LIKE '%blah%' OR "developers"."name" LIKE '%blah2%'/,
322
+ @r.where(:name.matches => '%blah%').where(:name.matches => '%blah2%').to_sql
323
+ end
324
+
325
+ should "erge multiple conditions on the same column but different predicate with ANDs" do
326
+ assert_match /"developers"."name" = 'blah' AND "developers"."name" LIKE '%blah2%'/,
327
+ @r.where(:name => 'blah').where(:name.matches => '%blah2%').to_sql
328
+ end
329
+ end
330
+
331
+ context "A relation" do
332
+ should "allow conditions on a belongs_to polymorphic association with an object" do
333
+ dev = Developer.first
334
+ assert_equal dev, Note.where(:notable.type(Developer) => dev).first.notable
335
+ end
336
+
337
+ should "allow conditions on a belongs_to association with an object" do
338
+ company = Company.first
339
+ assert_same_elements Developer.where(:company_id => company.id),
340
+ Developer.where(:company => company).all
341
+ end
342
+
343
+ should "allow conditions on a has_and_belongs_to_many association with an object" do
344
+ project = Project.first
345
+ assert_same_elements Developer.joins(:projects).where(:projects => {:id => project.id}),
346
+ Developer.joins(:projects).where(:projects => project)
347
+ end
348
+
349
+ should "not allow an object of the wrong class to be passed to a non-polymorphic association" do
350
+ company = Company.first
351
+ assert_raise ArgumentError do
352
+ Project.where(:developers => company).all
353
+ end
354
+ end
355
+
356
+ should "allow multiple AR objects on the value side of an association condition" do
357
+ projects = [Project.first, Project.last]
358
+ assert_same_elements Developer.joins(:projects).where(:projects => {:id => projects.map(&:id)}),
359
+ Developer.joins(:projects).where(:projects => projects)
360
+ end
361
+
362
+ should "allow multiple different kinds of AR objects on the value side of a polymorphic belongs_to" do
363
+ dev1 = Developer.first
364
+ dev2 = Developer.last
365
+ project = Project.first
366
+ company = Company.first
367
+ assert_same_elements Note.where(
368
+ {:notable_type => project.class.base_class.name, :notable_id => project.id} |
369
+ {:notable_type => dev1.class.base_class.name, :notable_id => [dev1.id, dev2.id]} |
370
+ {:notable_type => company.class.base_class.name, :notable_id => company.id}
371
+ ),
372
+ Note.where(:notable => [dev1, dev2, project, company]).all
373
+ end
374
+
375
+ should "allow an AR object on the value side of a polymorphic has_many condition" do
376
+ note = Note.first
377
+ peter = Developer.first
378
+ assert_equal [peter],
379
+ Developer.joins(:notes).where(:notes => note).all
380
+ end
381
+
382
+ should "allow a join of a polymorphic belongs_to relation with a type specified" do
383
+ dev = Developer.first
384
+ company = Company.first
385
+ assert_equal [company.notes.first],
386
+ Note.joins(:notable.type(Company) => :developers).where(:notable => {:developers => dev}).all
387
+ end
388
+
389
+ should "allow selection of a specific polymorphic join by name in the where clause" do
390
+ dev = Developer.first
391
+ company = Company.first
392
+ project = Project.first
393
+ dev_note = dev.notes.first
394
+ company_note = company.notes.first
395
+ project_note = project.notes.first
396
+ # Have to use outer joins since one inner join will cause remaining rows to be missing
397
+ # This is pretty convoluted, and way beyond the normal use case for polymorphic belongs_to
398
+ # joins anyway.
399
+ @r = Note.joins(:notable.type(Company).outer => :notes.outer, :notable.type(Developer).outer => :notes.outer, :notable.type(Project).outer => :notes.outer)
400
+ assert_equal [dev_note],
401
+ @r.where(:notable.type(Developer) => {:notes => dev_note}).all
402
+ assert_equal [company_note],
403
+ @r.where(:notable.type(Company) => {:notes => company_note}).all
404
+ assert_equal [project_note],
405
+ @r.where(:notable.type(Project) => {:notes => project_note}).all
406
+ end
407
+
408
+ should "maintain belongs_to conditions in a polymorphic join" do
409
+ assert_match /1=1/, Note.joins(:notable.type(Company)).to_sql
410
+ end
317
411
  end
318
412
  end
metadata CHANGED
@@ -5,9 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 9
8
- - 9
9
- - 2
10
- version: 0.9.9.2
8
+ - 10
9
+ version: 0.9.10
11
10
  platform: ruby
12
11
  authors:
13
12
  - Ernie Miller
@@ -15,7 +14,7 @@ autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
16
 
18
- date: 2010-11-17 00:00:00 -05:00
17
+ date: 2011-01-06 00:00:00 -05:00
19
18
  default_executable:
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
@@ -98,6 +97,7 @@ files:
98
97
  - lib/core_ext/symbol_operators.rb
99
98
  - lib/meta_where.rb
100
99
  - lib/meta_where/association_reflection.rb
100
+ - lib/meta_where/belongs_to_polymorphic_association.rb
101
101
  - lib/meta_where/column.rb
102
102
  - lib/meta_where/compound.rb
103
103
  - lib/meta_where/condition.rb