meta_where 0.9.6 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,15 @@
1
+ Changes since 0.9.6 (2010-09-30):
2
+ * ARel 2.x and Rails 3.0.2 compatibility
3
+ * Allow MetaWhere::And and MetaWhere::Or on the value side of a condition hash
4
+ to trigger treatment of key as an association.
5
+ ex: :comments => (:created_at.lt % 1.day.ago | :body.matches % '%hey%')
6
+ * Support for selecting join type: :association.outer / :association.inner results
7
+ in an outer or inner join.
8
+ * Having clauses are now fully MetaWhere-enabled.
9
+ * SQL functions are now supported. :concat.func(val1, val2) will result in an
10
+ SQL concat() call, with two parameters. If you've enabled Symbol operators, then
11
+ you can also just do :concat[val1, val2].
12
+
1
13
  Changes since 0.9.5 (2010-09-27):
2
14
  * Don't skip adding condition if an empty array is supplied
3
15
 
data/README.rdoc CHANGED
@@ -68,7 +68,7 @@ to enclose other conditions in {}, you should place operator conditions before a
68
68
 
69
69
  Operators are:
70
70
 
71
- * [] (equal)
71
+ * >> (equal)
72
72
  * ^ (not equal)
73
73
  * + (in array/range)
74
74
  * - (not in array/range)
@@ -78,6 +78,7 @@ Operators are:
78
78
  * >= (greater than or equal to)
79
79
  * < (less than)
80
80
  * <= (less than or equal to)
81
+ * [] (SQL functions -- more on those below)
81
82
 
82
83
  === Compounds
83
84
  You can use the & and | operators to perform ands and ors within your queries.
@@ -148,6 +149,33 @@ parentheses around everything. So MetaWhere also supports a substitution-inspire
148
149
 
149
150
  (*) Formatting added for clarity. I said you could do this, not that you should. :)
150
151
 
152
+ == Join type specification
153
+ You can choose whether to use an inner join (the default) or a left outer join by tacking
154
+ <tt>.outer</tt> or <tt>.inner</tt> to the symbols specified in your joins() call:
155
+
156
+ Article.joins(:comments => :moderations.outer).to_sql
157
+ => SELECT "articles".* FROM "articles"
158
+ INNER JOIN "comments" ON "comments"."article_id" = "articles"."id"
159
+ LEFT OUTER JOIN "moderations" ON "moderations"."comment_id" = "comments"."id"
160
+
161
+ == SQL Functions
162
+ You can use SQL functions in your queries:
163
+
164
+ Manager.joins(:employees.outer).group('managers.id').
165
+ having(:employees => (:count.func(:id) < 3))
166
+ => SELECT "managers".* FROM "managers"
167
+ LEFT OUTER JOIN "employees" ON "employees"."manager_id" = "managers"."id"
168
+ GROUP BY managers.id HAVING count("employees"."id") < 3
169
+
170
+ If you enable Symbol operators, you can just use <tt>:count[:id]</tt>, instead of calling
171
+ <tt>func</tt> as shown above. SQL functions work in the SELECT, WHERE, and HAVING clauses,
172
+ and can be aliased with <tt>as</tt>:
173
+
174
+ Manager.select('managers.*').
175
+ select(:find_in_set[:id, '3,2,1'].as('position'))
176
+ => SELECT managers.*, find_in_set("managers"."id",'3,2,1') AS position
177
+ FROM "managers"
178
+
151
179
  === But wait, there's more!
152
180
 
153
181
  == Intelligent hash condition mapping
@@ -182,7 +210,7 @@ With MetaWhere:
182
210
  => SELECT "articles".* FROM "articles" INNER JOIN "comments"
183
211
  ON "comments"."article_id" = "articles"."id" WHERE (("comments"."body" = 'hey'))
184
212
 
185
- The general idea is that if an association with the name provided exists, MetaWhere::Builder
213
+ The general idea is that if an association with the name provided exists, MetaWhere
186
214
  will build the conditions against that association's table as it's been aliased, before falling
187
215
  back to assuming you're specifying a table by name. It also handles nested associations:
188
216
 
data/Rakefile CHANGED
@@ -19,9 +19,9 @@ begin
19
19
  gem.homepage = "http://metautonomo.us/projects/metawhere/"
20
20
  gem.authors = ["Ernie Miller"]
21
21
  gem.add_development_dependency "shoulda"
22
- gem.add_dependency "activerecord", "~> 3.0.0"
23
- gem.add_dependency "activesupport", "~> 3.0.0"
24
- gem.add_dependency "arel", "~> 1.0.1"
22
+ gem.add_dependency "activerecord", "~> 3.0.2"
23
+ gem.add_dependency "activesupport", "~> 3.0.2"
24
+ gem.add_dependency "arel", "~> 2.0.2"
25
25
  gem.post_install_message = <<END
26
26
 
27
27
  *** Thanks for installing MetaWhere! ***
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.6
1
+ 0.9.9
data/lib/core_ext/hash.rb CHANGED
@@ -1,16 +1,9 @@
1
1
  class Hash
2
- def to_predicate(builder, parent = nil)
3
- Arel::Predicates::All.new(*builder.build_predicates_from_hash(self, parent || builder.join_dependency.join_base))
4
- end
5
-
6
- def to_attribute(builder, parent = nil)
7
- builder.build_attributes_from_hash(self, parent)
8
- end
9
-
2
+
10
3
  def |(other)
11
4
  MetaWhere::Or.new(self, other)
12
5
  end
13
-
6
+
14
7
  def &(other)
15
8
  MetaWhere::And.new(self, other)
16
9
  end
@@ -1,30 +1,34 @@
1
1
  class Symbol
2
- Arel::Attribute::PREDICATES.each do |predication|
2
+ MetaWhere::PREDICATES.each do |predication|
3
3
  define_method(predication) do
4
4
  MetaWhere::Column.new(self, predication)
5
5
  end
6
6
  end
7
-
7
+
8
8
  MetaWhere::METHOD_ALIASES.each_pair do |aliased, predication|
9
9
  define_method(aliased) do
10
10
  MetaWhere::Column.new(self, predication)
11
11
  end
12
12
  end
13
-
14
- def to_attribute(builder, parent = nil)
15
- table = builder.build_table(parent)
16
-
17
- unless attribute = table[self]
18
- raise ::ActiveRecord::StatementInvalid, "No attribute named `#{self}` exists for table `#{table.name}`"
19
- end
20
13
 
21
- attribute
14
+ def mw_func(*args)
15
+ MetaWhere::Function.new(self, *args)
16
+ end
17
+
18
+ alias_method :func, :mw_func unless method_defined?(:func)
19
+
20
+ def inner
21
+ MetaWhere::JoinType.new(self, Arel::Nodes::InnerJoin)
22
22
  end
23
-
23
+
24
+ def outer
25
+ MetaWhere::JoinType.new(self, Arel::Nodes::OuterJoin)
26
+ end
27
+
24
28
  def asc
25
29
  MetaWhere::Column.new(self, :asc)
26
30
  end
27
-
31
+
28
32
  def desc
29
33
  MetaWhere::Column.new(self, :desc)
30
34
  end
@@ -1,5 +1,9 @@
1
1
  class Symbol
2
- def [](value)
2
+ def [](*values)
3
+ MetaWhere::Function.new(self, *values)
4
+ end
5
+
6
+ def >>(value)
3
7
  MetaWhere::Condition.new(self, value, :eq)
4
8
  end
5
9
 
@@ -22,7 +26,7 @@ class Symbol
22
26
  # Won't work on Ruby 1.8.x so need to do this conditionally
23
27
  if respond_to?('!~')
24
28
  define_method('!~') do |value|
25
- MetaWhere::Condition.new(self, value, :not_matches)
29
+ MetaWhere::Condition.new(self, value, :does_not_match)
26
30
  end
27
31
  end
28
32
 
@@ -18,7 +18,7 @@ module MetaWhere
18
18
  when Hash
19
19
  if obj.keys.grep(MetaWhere::Column).any?
20
20
  raise MetaWhereInAssociationError, <<END
21
- The :#{name} association has a MetaWhere::Column in its :conditions. \
21
+ The :#{name} association has a MetaWhere::Column in its :conditions. \
22
22
  If you actually needed to access conditions other than equality, then you most \
23
23
  likely meant to set up a scope or method, instead. Associations only work with \
24
24
  standard equality conditions, since they can be used to create records as well.
@@ -3,8 +3,8 @@ module MetaWhere
3
3
  attr_reader :column, :method
4
4
 
5
5
  def initialize(column, method)
6
- @column = column.to_s
7
- @method = method.to_s
6
+ @column = column
7
+ @method = method
8
8
  end
9
9
 
10
10
  def %(value)
@@ -19,22 +19,6 @@ module MetaWhere
19
19
 
20
20
  alias_method :eql?, :==
21
21
 
22
- def to_attribute(builder, parent = nil)
23
- column_name = column
24
- if column_name.include?('.')
25
- table_name, column_name = column_name.split('.', 2)
26
- table = Arel::Table.new(table_name, :engine => parent.arel_engine)
27
- else
28
- table = builder.build_table(parent)
29
- end
30
-
31
- unless attribute = table[column_name]
32
- raise ::ActiveRecord::StatementInvalid, "No attribute named `#{column_name}` exists for table `#{table.name}`"
33
- end
34
-
35
- attribute.send(method)
36
- end
37
-
38
22
  def hash
39
23
  [column, method].hash
40
24
  end
@@ -1,30 +1,24 @@
1
1
  module MetaWhere
2
2
  class Compound
3
3
  attr_reader :condition1, :condition2
4
-
4
+
5
5
  def initialize(condition1, condition2)
6
6
  @condition1 = condition1
7
7
  @condition2 = condition2
8
8
  end
9
-
9
+
10
10
  def |(other)
11
11
  Or.new(self, other)
12
12
  end
13
-
13
+
14
14
  def &(other)
15
15
  And.new(self, other)
16
16
  end
17
17
  end
18
-
18
+
19
19
  class Or < Compound
20
- def to_predicate(builder, parent = nil)
21
- condition1.to_predicate(builder, parent).or(condition2.to_predicate(builder, parent))
22
- end
23
20
  end
24
-
21
+
25
22
  class And < Compound
26
- def to_predicate(builder, parent = nil)
27
- condition1.to_predicate(builder, parent).and(condition2.to_predicate(builder, parent))
28
- end
29
23
  end
30
24
  end
@@ -3,49 +3,36 @@ require 'meta_where/utility'
3
3
  module MetaWhere
4
4
  class Condition
5
5
  include MetaWhere::Utility
6
-
6
+
7
7
  attr_reader :column, :value, :method
8
-
8
+
9
9
  def initialize(column, value, method)
10
- @column = column.to_s
10
+ @column = column
11
11
  @value = value
12
12
  @method = (MetaWhere::METHOD_ALIASES[method.to_s] || method).to_s
13
13
  end
14
-
15
- def to_predicate(builder, parent = nil)
16
- table = builder.build_table(parent)
17
-
18
- unless attribute = table[column]
19
- raise ::ActiveRecord::StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`"
20
- end
21
-
22
- unless valid_comparison_method?(method)
23
- raise ::ActiveRecord::StatementInvalid, "No comparison method named `#{method}` exists for column `#{column}`"
24
- end
25
- attribute.send(method, *args_for_predicate(method.to_s, value))
26
- end
27
-
14
+
28
15
  def ==(other_condition)
29
16
  other_condition.is_a?(Condition) &&
30
17
  other_condition.column == column &&
31
- other_condition.value = value &&
18
+ other_condition.value == value &&
32
19
  other_condition.method == method
33
20
  end
34
-
21
+
35
22
  alias_method :eql?, :==
36
-
23
+
37
24
  def |(other)
38
25
  Or.new(self, other)
39
26
  end
40
-
27
+
41
28
  def &(other)
42
29
  And.new(self, other)
43
30
  end
44
-
31
+
45
32
  # Play "nicely" with expand_hash_conditions_for_aggregates
46
33
  def to_sym
47
34
  self
48
35
  end
49
36
  end
50
-
37
+
51
38
  end
@@ -0,0 +1,108 @@
1
+ module MetaWhere
2
+ class Function
3
+ attr_reader :name, :args
4
+ attr_accessor :table, :alias
5
+
6
+ def initialize(name, *args)
7
+ @name = name
8
+ @args = args
9
+ end
10
+
11
+ def as(val)
12
+ self.alias = val
13
+ self
14
+ end
15
+
16
+ def to_sqlliteral
17
+ Arel.sql(
18
+ ("#{name}(#{(['%s'] * args.size).join(',')})" % contextualize_args) +
19
+ (self.alias ? " AS #{self.alias}" : '')
20
+ )
21
+ end
22
+
23
+ alias_method :to_sql, :to_sqlliteral
24
+
25
+ MetaWhere::PREDICATES.each do |predication|
26
+ define_method(predication) do
27
+ MetaWhere::Column.new(self, predication)
28
+ end
29
+ end
30
+
31
+ MetaWhere::METHOD_ALIASES.each_pair do |aliased, predication|
32
+ define_method(aliased) do
33
+ MetaWhere::Column.new(self, predication)
34
+ end
35
+ end
36
+
37
+ def >>(value)
38
+ MetaWhere::Condition.new(self, value, :eq)
39
+ end
40
+
41
+ def ^(value)
42
+ MetaWhere::Condition.new(self, value, :not_eq)
43
+ end
44
+
45
+ def +(value)
46
+ MetaWhere::Condition.new(self, value, :in)
47
+ end
48
+
49
+ def -(value)
50
+ MetaWhere::Condition.new(self, value, :not_in)
51
+ end
52
+
53
+ def =~(value)
54
+ MetaWhere::Condition.new(self, value, :matches)
55
+ end
56
+
57
+ # Won't work on Ruby 1.8.x so need to do this conditionally
58
+ if respond_to?('!~')
59
+ define_method('!~') do |value|
60
+ MetaWhere::Condition.new(self, value, :does_not_match)
61
+ end
62
+ end
63
+
64
+ def >(value)
65
+ MetaWhere::Condition.new(self, value, :gt)
66
+ end
67
+
68
+ def >=(value)
69
+ MetaWhere::Condition.new(self, value, :gteq)
70
+ end
71
+
72
+ def <(value)
73
+ MetaWhere::Condition.new(self, value, :lt)
74
+ end
75
+
76
+ def <=(value)
77
+ MetaWhere::Condition.new(self, value, :lteq)
78
+ end
79
+
80
+ # Play "nicely" with expand_hash_conditions_for_aggregates
81
+ def to_sym
82
+ self
83
+ end
84
+
85
+ private
86
+
87
+ def contextualize_args
88
+ args.map do |arg|
89
+ if arg.is_a? Symbol
90
+ self.table && self.table[arg] ? Arel::Visitors.for(ActiveRecord::Base).accept(table[arg]) : arg
91
+ else
92
+ arg.table = self.table if arg.is_a? MetaWhere::Function
93
+ Arel::Visitors.for(ActiveRecord::Base).accept(arg)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ module Arel
101
+ module Visitors
102
+ class ToSql
103
+ def visit_MetaWhere_Function o
104
+ o.to_sqlliteral
105
+ end
106
+ end
107
+ end
108
+ end
@@ -11,37 +11,20 @@ module MetaWhere
11
11
  class ConfigurationError < StandardError; end
12
12
  class AssociationNotFoundError < StandardError; end
13
13
 
14
- def build_with_metawhere(associations, parent = nil, join_class = Arel::InnerJoin)
15
- parent ||= @joins.last
16
- case associations
17
- when Symbol, String
18
- reflection = parent.reflections[associations.to_s.intern] or
19
- raise AssociationNotFoundError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
14
+ def build_with_metawhere(associations, parent = nil, join_type = Arel::Nodes::InnerJoin)
15
+ 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?"
20
19
  unless association = find_join_association(reflection, parent)
21
20
  @reflections << reflection
22
- association = (@joins << build_join_association(reflection, parent).with_join_class(join_class)).last
21
+ association = build_join_association(reflection, parent)
22
+ association.join_type = associations.join_type
23
+ @joins << association
23
24
  end
24
25
  association
25
- when Array
26
- associations.each do |association|
27
- build(association, parent, join_class)
28
- end
29
- when Hash
30
- associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
31
- association = build(name, parent, join_class)
32
- build(associations[name], association, join_class)
33
- end
34
- else
35
- raise ConfigurationError, associations.inspect
36
- end
37
- end
38
-
39
- def find_join_association(name_or_reflection, parent)
40
- case name_or_reflection
41
- when Symbol, String
42
- join_associations.detect {|j| (j.reflection.name == name_or_reflection.to_s.intern) && (j.parent == parent)}
43
26
  else
44
- join_associations.detect {|j| (j.reflection == name_or_reflection) && (j.parent == parent)}
27
+ build_without_metawhere(associations, parent, join_type)
45
28
  end
46
29
  end
47
30
  end
@@ -0,0 +1,22 @@
1
+ module MetaWhere
2
+ class JoinType
3
+ attr_reader :name, :join_type
4
+
5
+ def initialize(name, join_type = Arel::Nodes::InnerJoin)
6
+ @name = name
7
+ @join_type = join_type
8
+ end
9
+
10
+ def ==(other)
11
+ self.class == other.class &&
12
+ name == other.name &&
13
+ join_type == other.join_type
14
+ end
15
+
16
+ alias_method :eql?, :==
17
+
18
+ def hash
19
+ [name, join_type].hash
20
+ end
21
+ end
22
+ end
@@ -15,12 +15,12 @@ module MetaWhere
15
15
 
16
16
  def merge(r, association_name = nil)
17
17
  if (r && (association_name || base_class.name != r.klass.base_class.name)) # Merging relations with different base.
18
- association_name ||= (default_association = reflect_on_all_associations.detect {|a| a.klass.name == r.klass.name}) ?
18
+ association_name ||= (default_association = reflect_on_all_associations.detect {|a| a.class_name == r.klass.name}) ?
19
19
  default_association.name : r.table_name.to_sym
20
20
  r = r.clone
21
- r.where_values.map! {|w| w.respond_to?(:to_predicate) ? {association_name => w} : w}
22
- r.joins_values.map! {|j| [Symbol, Hash].include?(j.class) ? {association_name => j} : j}
23
- self.joins_values += [association_name]
21
+ r.where_values.map! {|w| MetaWhere::Visitors::Predicate.visitables.include?(w.class) ? {association_name => w} : w}
22
+ r.joins_values.map! {|j| [Symbol, Hash, MetaWhere::JoinType].include?(j.class) ? {association_name => j} : j}
23
+ self.joins_values += [association_name] if reflect_on_association(association_name)
24
24
  end
25
25
 
26
26
  super(r)
@@ -36,7 +36,7 @@ module MetaWhere
36
36
  @scope_for_create ||= begin
37
37
  @create_with_value || predicate_wheres.inject({}) do |hash, where|
38
38
  if is_equality_predicate?(where)
39
- hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2
39
+ hash[where.left.name] = where.right.respond_to?(:value) ? where.right.value : where.right
40
40
  end
41
41
 
42
42
  hash
@@ -74,7 +74,7 @@ module MetaWhere
74
74
 
75
75
  case join
76
76
  when ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
77
- arel = arel.join(join.relation, Arel::OuterJoin).on(*join.on)
77
+ arel = arel.join(join.relation, Arel::Nodes::OuterJoin).on(*join.on)
78
78
  when Hash, Array, Symbol
79
79
  if array_of_strings?(join)
80
80
  join_string = join.join(' ')
@@ -109,14 +109,19 @@ module MetaWhere
109
109
  end unless defined?(:custom_join_sql)
110
110
 
111
111
  def predicate_wheres
112
- remove_conflicting_equality_predicates(flatten_predicates(@where_values, metawhere_builder))
112
+ remove_conflicting_equality_predicates(flatten_predicates(@where_values, predicate_visitor))
113
113
  end
114
114
 
115
- # Very occasionally, we need to get a builder for another relation, so it makes sense to factor
116
- # this out into a public method despite only being two lines long.
117
- def metawhere_builder
115
+ # Very occasionally, we need to get a visitor for another relation, so it makes sense to factor
116
+ # these out into a public method despite only being two lines long.
117
+ def predicate_visitor
118
118
  join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
119
- MetaWhere::Builder.new(join_dependency)
119
+ MetaWhere::Visitors::Predicate.new(join_dependency)
120
+ end
121
+
122
+ def attribute_visitor
123
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
124
+ MetaWhere::Visitors::Attribute.new(join_dependency)
120
125
  end
121
126
 
122
127
  # Simulate the logic that occurs in ActiveRecord::Relation.to_a
@@ -136,9 +141,9 @@ module MetaWhere
136
141
  end
137
142
 
138
143
  def construct_limited_ids_condition(relation)
139
- builder = relation.metawhere_builder
144
+ visitor = relation.attribute_visitor
140
145
 
141
- relation.order_values.map! {|o| o.respond_to?(:to_attribute) ? o.to_attribute(builder).to_sql : o}
146
+ relation.order_values.map! {|o| visitor.can_accept?(o) ? visitor.accept(o).to_sql : o}
142
147
 
143
148
  super
144
149
  end
@@ -146,68 +151,70 @@ module MetaWhere
146
151
  def build_arel
147
152
  arel = table
148
153
 
149
- builder = metawhere_builder
154
+ visitor = predicate_visitor
150
155
 
151
- arel = build_intelligent_joins(arel, builder) if @joins_values.present?
156
+ arel = build_intelligent_joins(arel, visitor) if @joins_values.present?
152
157
 
153
- predicate_wheres = remove_conflicting_equality_predicates(flatten_predicates(@where_values, builder))
158
+ predicate_wheres = remove_conflicting_equality_predicates(flatten_predicates(@where_values, visitor))
154
159
 
155
160
  predicate_wheres.each do |where|
156
161
  next if where.blank?
157
162
 
158
163
  case where
159
- when Arel::SqlLiteral
164
+ when Arel::Nodes::SqlLiteral
160
165
  arel = arel.where(where)
161
166
  else
162
167
  sql = where.is_a?(String) ? where : where.to_sql
163
- arel = arel.where(Arel::SqlLiteral.new("(#{sql})"))
168
+ arel = arel.where(Arel::Nodes::SqlLiteral.new("(#{sql})"))
164
169
  end
165
170
  end
166
171
 
167
- arel = arel.having(*@having_values.uniq.select{|h| h.present?}) if @having_values.present?
172
+ arel = arel.having(*flatten_predicates(@having_values, visitor).reject {|h| h.blank?}) unless @having_values.empty?
168
173
 
169
- arel = arel.take(@limit_value) if @limit_value.present?
170
- arel = arel.skip(@offset_value) if @offset_value.present?
174
+ arel = arel.take(@limit_value) if @limit_value
175
+ arel = arel.skip(@offset_value) if @offset_value
171
176
 
172
- arel = arel.group(*@group_values.uniq.select{|g| g.present?}) if @group_values.present?
177
+ arel = arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
173
178
 
174
- arel = build_order(arel, builder, @order_values) if @order_values.present?
179
+ arel = build_order(arel, attribute_visitor, @order_values) unless @order_values.empty?
175
180
 
176
181
  arel = build_select(arel, @select_values.uniq)
177
182
 
178
- arel = arel.from(@from_value) if @from_value.present?
179
-
180
- case @lock_value
181
- when TrueClass
182
- arel = arel.lock
183
- when String
184
- arel = arel.lock(@lock_value)
185
- end if @lock_value.present?
183
+ arel = arel.from(@from_value) if @from_value
184
+ arel = arel.lock(@lock_value) if @lock_value
186
185
 
187
186
  arel
188
187
  end
189
188
 
189
+ def select(value = Proc.new)
190
+ if MetaWhere::Function === value
191
+ value.table = self.arel_table
192
+ end
193
+
194
+ super
195
+ end
196
+
190
197
  private
191
198
 
192
199
  def is_equality_predicate?(predicate)
193
- predicate.respond_to?(:operator) && predicate.operator == :==
200
+ predicate.class == Arel::Nodes::Equality
194
201
  end
195
202
 
196
- def build_intelligent_joins(arel, builder)
203
+ def build_intelligent_joins(arel, visitor)
197
204
  joined_associations = []
198
205
 
199
- builder.join_dependency.graft(*stashed_association_joins)
206
+ visitor.join_dependency.graft(*stashed_association_joins)
200
207
 
201
208
  @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
202
209
 
203
210
  to_join = []
204
211
 
205
- builder.join_dependency.join_associations.each do |association|
212
+ visitor.join_dependency.join_associations.each do |association|
206
213
  if (association_relation = association.relation).is_a?(Array)
207
- to_join << [association_relation.first, association.join_class, association.association_join.first]
208
- to_join << [association_relation.last, association.join_class, association.association_join.last]
214
+ to_join << [association_relation.first, association.join_type, association.association_join.first]
215
+ to_join << [association_relation.last, association.join_type, association.association_join.last]
209
216
  else
210
- to_join << [association_relation, association.join_class, association.association_join]
217
+ to_join << [association_relation, association.join_type, association.association_join]
211
218
  end
212
219
  end
213
220
 
@@ -221,11 +228,10 @@ module MetaWhere
221
228
  arel = arel.join(custom_joins)
222
229
  end
223
230
 
224
- def build_order(arel, builder, orders)
231
+ def build_order(arel, visitor, orders)
225
232
  order_attributes = orders.map {|o|
226
- o.respond_to?(:to_attribute) ? o.to_attribute(builder, builder.join_dependency.join_base) : o
227
- }.flatten.uniq.select {|o| o.present?}
228
- order_attributes.map! {|a| Arel::SqlLiteral.new(a.is_a?(String) ? a : a.to_sql)}
233
+ visitor.can_accept?(o) ? visitor.accept(o, visitor.join_dependency.join_base) : o
234
+ }.flatten.uniq.reject {|o| o.blank?}
229
235
  order_attributes.present? ? arel.order(*order_attributes) : arel
230
236
  end
231
237
 
@@ -238,11 +244,11 @@ module MetaWhere
238
244
  }.reverse
239
245
  end
240
246
 
241
- def flatten_predicates(predicates, builder)
247
+ def flatten_predicates(predicates, visitor)
242
248
  predicates.map {|p|
243
- predicate = p.respond_to?(:to_predicate) ? p.to_predicate(builder) : p
244
- if predicate.is_a?(Arel::Predicates::All)
245
- flatten_predicates(predicate.predicates, builder)
249
+ predicate = visitor.can_accept?(p) ? visitor.accept(p) : p
250
+ if predicate.is_a?(Arel::Nodes::Grouping) && predicate.expr.is_a?(Arel::Nodes::And)
251
+ flatten_predicates([predicate.expr.left, predicate.expr.right], visitor)
246
252
  else
247
253
  predicate
248
254
  end
@@ -255,7 +261,7 @@ module MetaWhere
255
261
 
256
262
  def association_joins
257
263
  @mw_association_joins ||= unique_joins.select{|j|
258
- [Hash, Array, Symbol].include?(j.class) && !array_of_strings?(j)
264
+ [Hash, Array, Symbol, MetaWhere::JoinType].include?(j.class) && !array_of_strings?(j)
259
265
  }
260
266
  end
261
267
 
@@ -2,12 +2,28 @@ module MetaWhere
2
2
  module Utility
3
3
  private
4
4
 
5
- def args_for_predicate(method, value)
6
- value = [Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation].include?(value.class) ? value.to_a : value
7
- if method =~ /_(any|all)$/ && value.is_a?(Array)
8
- value
5
+ def association_from_parent_and_column(parent, column)
6
+ parent.is_a?(Symbol) ? nil : @join_dependency.send(:find_join_association, column, parent)
7
+ end
8
+
9
+ def attribute_from_column_and_table(column, table)
10
+ case column
11
+ when String, Symbol
12
+ table[column]
13
+ when MetaWhere::Function
14
+ column.table = table
15
+ column.to_sqlliteral
9
16
  else
10
- [value]
17
+ nil
18
+ end
19
+ end
20
+
21
+ def args_for_predicate(value)
22
+ case value
23
+ when ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
24
+ value.to_a
25
+ else
26
+ value
11
27
  end
12
28
  end
13
29
 
@@ -21,7 +37,7 @@ module MetaWhere
21
37
  end
22
38
 
23
39
  def valid_comparison_method?(method)
24
- Arel::Attribute::PREDICATES.map(&:to_s).include?(method.to_s)
40
+ MetaWhere::PREDICATES.map(&:to_s).include?(method.to_s)
25
41
  end
26
42
  end
27
43
  end
@@ -0,0 +1,57 @@
1
+ require 'meta_where/visitors/visitor'
2
+
3
+ module MetaWhere
4
+ module Visitors
5
+ class Attribute < Visitor
6
+
7
+ def self.visitables
8
+ [Hash, Symbol, MetaWhere::Column]
9
+ end
10
+
11
+ def visit_Hash(o, parent)
12
+ table = build_table(parent)
13
+ built_attributes = o.map do |column, value|
14
+ if value.is_a?(Hash)
15
+ association = association_from_parent_and_column(parent, column)
16
+ accept(value, association || column)
17
+ elsif value.is_a?(Array) && value.all? {|v| can_accept?(v)}
18
+ association = association_from_parent_and_column(parent, column)
19
+ value.map {|val| self.accept(val, association || column)}
20
+ else
21
+ association = association_from_parent_and_column(parent, column)
22
+ can_accept?(value) ? self.accept(value, association || column) : value
23
+ end
24
+ end
25
+
26
+ built_attributes.flatten
27
+ end
28
+
29
+ def visit_Symbol(o, parent)
30
+ table = self.build_table(parent)
31
+
32
+ unless attribute = table[o]
33
+ raise ::ActiveRecord::StatementInvalid, "No attribute named `#{o}` exists for table `#{table.name}`"
34
+ end
35
+
36
+ attribute
37
+ end
38
+
39
+ def visit_MetaWhere_Column(o, parent)
40
+ column_name = o.column.to_s
41
+ if column_name.include?('.')
42
+ table_name, column_name = column_name.split('.', 2)
43
+ table = Arel::Table.new(table_name, :engine => parent.arel_engine)
44
+ else
45
+ table = self.build_table(parent)
46
+ end
47
+
48
+ unless attribute = table[column_name]
49
+ raise ::ActiveRecord::StatementInvalid, "No attribute named `#{column_name}` exists for table `#{table.name}`"
50
+ end
51
+
52
+ attribute.send(o.method)
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,88 @@
1
+ require 'meta_where/visitors/visitor'
2
+
3
+ module MetaWhere
4
+ module Visitors
5
+ class Predicate < Visitor
6
+
7
+ def self.visitables
8
+ [Hash, MetaWhere::Or, MetaWhere::And, MetaWhere::Condition, MetaWhere::Function]
9
+ end
10
+
11
+ def visit_Hash(o, parent)
12
+ parent ||= join_dependency.join_base
13
+ table = build_table(parent)
14
+ predicates = o.map do |column, value|
15
+ if value.is_a?(Hash)
16
+ association = association_from_parent_and_column(parent, column)
17
+ accept(value, association || column)
18
+ elsif [MetaWhere::Condition, MetaWhere::And, MetaWhere::Or].include?(value.class)
19
+ association = association_from_parent_and_column(parent, column)
20
+ accept(value, association || column)
21
+ elsif value.is_a?(Array) && !value.empty? && value.all? {|v| can_accept?(v)}
22
+ association = association_from_parent_and_column(parent, column)
23
+ value.map {|val| accept(val, association || column)}
24
+ else
25
+ if column.is_a?(MetaWhere::Column)
26
+ method = column.method
27
+ column = column.column
28
+ else
29
+ method = method_from_value(value)
30
+ end
31
+
32
+ if [String, Symbol].include?(column.class) && column.to_s.include?('.')
33
+ table_name, column = column.to_s.split('.', 2)
34
+ table = Arel::Table.new(table_name, :engine => parent.arel_engine)
35
+ end
36
+
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
+ unless valid_comparison_method?(method)
42
+ raise ::ActiveRecord::StatementInvalid, "No comparison method named `#{method}` exists for column `#{column}`"
43
+ end
44
+
45
+ attribute.send(method, args_for_predicate(value))
46
+ end
47
+ end
48
+
49
+ predicates.flatten!
50
+
51
+ if predicates.size > 1
52
+ first = predicates.shift
53
+ Arel::Nodes::Grouping.new(predicates.inject(first) {|memo, expr| Arel::Nodes::And.new(memo, expr)})
54
+ else
55
+ predicates.first
56
+ end
57
+ end
58
+
59
+ def visit_MetaWhere_Or(o, parent)
60
+ accept(o.condition1, parent).or(accept(o.condition2, parent))
61
+ end
62
+
63
+ def visit_MetaWhere_And(o, parent)
64
+ accept(o.condition1, parent).and(accept(o.condition2, parent))
65
+ end
66
+
67
+ def visit_MetaWhere_Condition(o, parent)
68
+ table = self.build_table(parent)
69
+
70
+ unless attribute = attribute_from_column_and_table(o.column, table)
71
+ raise ::ActiveRecord::StatementInvalid, "No attribute named `#{o.column}` exists for table `#{table.name}`"
72
+ end
73
+
74
+ unless valid_comparison_method?(o.method)
75
+ raise ::ActiveRecord::StatementInvalid, "No comparison method named `#{o.method}` exists for column `#{o.column}`"
76
+ end
77
+ attribute.send(o.method, args_for_predicate(o.value))
78
+ end
79
+
80
+ def visit_MetaWhere_Function(o, parent)
81
+ self.table = self.build_table(parent)
82
+
83
+ o.to_sqlliteral
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,46 @@
1
+ require 'meta_where/utility'
2
+
3
+ module MetaWhere
4
+ module Visitors
5
+ class Visitor
6
+ include MetaWhere::Utility
7
+
8
+ attr_reader :join_dependency
9
+
10
+ def initialize(join_dependency)
11
+ @join_dependency = join_dependency
12
+ jb = join_dependency.join_base
13
+ @engine = jb.arel_engine
14
+ @default_table = Arel::Table.new(jb.table_name, :as => jb.aliased_table_name, :engine => @engine)
15
+ end
16
+
17
+ def build_table(parent_or_table_name = nil)
18
+ if parent_or_table_name.is_a?(Symbol)
19
+ Arel::Table.new(parent_or_table_name, :engine => @engine)
20
+ elsif parent_or_table_name.respond_to?(:aliased_table_name)
21
+ Arel::Table.new(parent_or_table_name.table_name, :as => parent_or_table_name.aliased_table_name, :engine => @engine)
22
+ else
23
+ @default_table
24
+ end
25
+ end
26
+
27
+ def accept(object, parent = nil)
28
+ visit(object, parent)
29
+ end
30
+
31
+ def can_accept?(object)
32
+ respond_to? DISPATCH[object.class]
33
+ end
34
+
35
+ private
36
+
37
+ DISPATCH = Hash.new do |hash, klass|
38
+ hash[klass] = "visit_#{klass.name.gsub('::', '_')}"
39
+ end
40
+
41
+ def visit(object, parent)
42
+ send(DISPATCH[object.class], object, parent)
43
+ end
44
+ end
45
+ end
46
+ end
data/lib/meta_where.rb CHANGED
@@ -2,12 +2,26 @@ module MetaWhere
2
2
  METHOD_ALIASES = {
3
3
  'ne' => :not_eq,
4
4
  'like' => :matches,
5
- 'nlike' => :not_matches,
5
+ 'not_matches' => :does_not_match,
6
+ 'nlike' => :does_not_match,
6
7
  'lte' => :lteq,
7
8
  'gte' => :gteq,
8
9
  'nin' => :not_in
9
10
  }
10
11
 
12
+ PREDICATES = [
13
+ :eq, :eq_any, :eq_all,
14
+ :not_eq, :not_eq_any, :not_eq_all,
15
+ :matches, :matches_any, :matches_all,
16
+ :does_not_match, :does_not_match_any, :does_not_match_all,
17
+ :lt, :lt_any, :lt_all,
18
+ :lteq, :lteq_any, :lteq_all,
19
+ :gt, :gt_any, :gt_all,
20
+ :gteq, :gteq_any, :gteq_all,
21
+ :in, :in_any, :in_all,
22
+ :not_in, :not_in_any, :not_in_all
23
+ ]
24
+
11
25
  def self.operator_overload!
12
26
  require 'core_ext/symbol_operators'
13
27
  end
@@ -18,9 +32,12 @@ require 'active_support'
18
32
  require 'meta_where/column'
19
33
  require 'meta_where/condition'
20
34
  require 'meta_where/compound'
35
+ require 'meta_where/function'
36
+ require 'meta_where/join_type'
21
37
  require 'core_ext/symbol'
22
38
  require 'core_ext/hash'
23
- require 'meta_where/builder'
39
+ require 'meta_where/visitors/attribute'
40
+ require 'meta_where/visitors/predicate'
24
41
  require 'meta_where/association_reflection'
25
42
  require 'meta_where/relation'
26
43
  require 'meta_where/join_dependency'
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.6"
8
+ s.version = "0.9.9"
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-09-30}
12
+ s.date = %q{2010-11-15}
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
@@ -39,13 +39,17 @@ Gem::Specification.new do |s|
39
39
  "lib/core_ext/symbol_operators.rb",
40
40
  "lib/meta_where.rb",
41
41
  "lib/meta_where/association_reflection.rb",
42
- "lib/meta_where/builder.rb",
43
42
  "lib/meta_where/column.rb",
44
43
  "lib/meta_where/compound.rb",
45
44
  "lib/meta_where/condition.rb",
45
+ "lib/meta_where/function.rb",
46
46
  "lib/meta_where/join_dependency.rb",
47
+ "lib/meta_where/join_type.rb",
47
48
  "lib/meta_where/relation.rb",
48
49
  "lib/meta_where/utility.rb",
50
+ "lib/meta_where/visitors/attribute.rb",
51
+ "lib/meta_where/visitors/predicate.rb",
52
+ "lib/meta_where/visitors/visitor.rb",
49
53
  "meta_where.gemspec",
50
54
  "test/fixtures/companies.yml",
51
55
  "test/fixtures/company.rb",
@@ -105,20 +109,20 @@ you're feeling especially appreciative. It'd help me justify this
105
109
 
106
110
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
107
111
  s.add_development_dependency(%q<shoulda>, [">= 0"])
108
- s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.0"])
109
- s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.0"])
110
- s.add_runtime_dependency(%q<arel>, ["~> 1.0.1"])
112
+ s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.2"])
113
+ s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.2"])
114
+ s.add_runtime_dependency(%q<arel>, ["~> 2.0.2"])
111
115
  else
112
116
  s.add_dependency(%q<shoulda>, [">= 0"])
113
- s.add_dependency(%q<activerecord>, ["~> 3.0.0"])
114
- s.add_dependency(%q<activesupport>, ["~> 3.0.0"])
115
- s.add_dependency(%q<arel>, ["~> 1.0.1"])
117
+ s.add_dependency(%q<activerecord>, ["~> 3.0.2"])
118
+ s.add_dependency(%q<activesupport>, ["~> 3.0.2"])
119
+ s.add_dependency(%q<arel>, ["~> 2.0.2"])
116
120
  end
117
121
  else
118
122
  s.add_dependency(%q<shoulda>, [">= 0"])
119
- s.add_dependency(%q<activerecord>, ["~> 3.0.0"])
120
- s.add_dependency(%q<activesupport>, ["~> 3.0.0"])
121
- s.add_dependency(%q<arel>, ["~> 1.0.1"])
123
+ s.add_dependency(%q<activerecord>, ["~> 3.0.2"])
124
+ s.add_dependency(%q<activesupport>, ["~> 3.0.2"])
125
+ s.add_dependency(%q<arel>, ["~> 2.0.2"])
122
126
  end
123
127
  end
124
128
 
@@ -29,6 +29,35 @@ class TestRelations < Test::Unit::TestCase
29
29
  assert_equal results.first, Company.find_by_name('Initech')
30
30
  end
31
31
 
32
+ should "allow selection of join type in association joins" do
33
+ assert_match /INNER JOIN/, @r.joins(:developers.inner).to_sql
34
+ assert_match /LEFT OUTER JOIN/, @r.joins(:developers.outer).to_sql
35
+ end
36
+
37
+ should "only join once even if two join types are used" do
38
+ assert_equal 1, @r.joins(:developers.inner, :developers.outer).to_sql.scan("JOIN").size
39
+ end
40
+
41
+ should "allow SQL functions via Symbol#func" do
42
+ assert_equal @r.where(:name.in => ['Initech', 'Mission Data']), @r.joins(:developers).group('companies.id').having(:developers => {:count.func(:id).gt => 2}).all
43
+ end
44
+
45
+ should "allow SQL functions via Symbol#[]" do
46
+ assert_equal @r.where(:name.in => ['Initech', 'Mission Data']), @r.joins(:developers).group('companies.id').having(:developers => {:count[:id].gt => 2}).all
47
+ end
48
+
49
+ should "allow SQL functions in select clause" do
50
+ assert_equal [3,2,3], @r.joins(:developers).group('companies.id').select(:count[Developer.arel_table[:id]].as(:developers_count)).map {|c| c.developers_count}
51
+ end
52
+
53
+ should "allow operators on MetaWhere::Function objects" do
54
+ assert_equal @r.where(:name.in => ['Initech', 'Mission Data']), @r.joins(:developers).group('companies.id').having(:developers => [:count[:id] > 2]).all
55
+ end
56
+
57
+ should "join multiple parameters to an SQL function with commas" do
58
+ assert_match /concat\("companies"."id","companies"."name"\) LIKE '%blah%'/, @r.where(:concat[:id,:name].matches => '%blah%').to_sql
59
+ end
60
+
32
61
  should "create new records with values from equality predicates" do
33
62
  assert_equal "New Company",
34
63
  @r.where(:name => 'New Company').new.name
@@ -59,6 +88,12 @@ class TestRelations < Test::Unit::TestCase
59
88
  assert_equal results.first, Company.find_by_name('Initech')
60
89
  end
61
90
 
91
+ should "behave as expected with empty arrays" do
92
+ none = @r.where("3 = 1").all
93
+ assert_equal none, @r.where(:name => []).all
94
+ assert_equal none, @r.where(:name.in => []).all
95
+ end
96
+
62
97
  should "allow multiple condition params in a single where" do
63
98
  results = @r.where(['name like ?', '%tech'], :created_at => 100.years.ago..Time.now)
64
99
  assert_equal 1, results.size
@@ -92,6 +127,21 @@ class TestRelations < Test::Unit::TestCase
92
127
  @r.joins(:data_types).where(:data_types => [:dec >= 2, :dec <= 5]).all
93
128
  end
94
129
 
130
+ should "allow nested conditions hashes to have MetaWhere::Condition values" do
131
+ assert_equal @r.joins(:data_types).where(:data_types => {:dec.gt => 2}).all,
132
+ @r.joins(:data_types).where(:data_types => :dec > 2).all
133
+ end
134
+
135
+ should "allow nested conditions hashes to have MetaWhere::And values" do
136
+ assert_equal @r.joins(:data_types).where(:data_types => {:dec => 2..5}).all,
137
+ @r.joins(:data_types).where(:data_types => ((:dec >= 2) & (:dec <= 5))).all
138
+ end
139
+
140
+ should "allow nested conditions hashes to have MetaWhere::Or values" do
141
+ assert_equal @r.joins(:data_types).where(:data_types => [:dec.gteq % 2 | :bln.eq % true]).all,
142
+ @r.joins(:data_types).where(:data_types => ((:dec >= 2) | (:bln >> true))).all
143
+ end
144
+
95
145
  should "allow combinations of options that no sane developer would ever try to use" do
96
146
  assert_equal @r.find_all_by_name('Initech'),
97
147
  @r.joins(:data_types, :developers => [:projects, :notes]).
@@ -165,7 +215,7 @@ class TestRelations < Test::Unit::TestCase
165
215
  end
166
216
  end
167
217
 
168
- context "A merged relation" do
218
+ context "A merged relation with a different base class" do
169
219
  setup do
170
220
  @r = Developer.where(:salary.gteq % 70000) & Company.where(:name.matches % 'Initech')
171
221
  end
@@ -179,7 +229,18 @@ class TestRelations < Test::Unit::TestCase
179
229
  end
180
230
  end
181
231
 
182
- context "A merged relation with an alternate association" do
232
+ context "A merged relation with a different base class and a MetaWhere::JoinType in joins" do
233
+ setup do
234
+ @r = Developer.where(:salary.gteq % 70000) & Company.where(:name.matches % 'Initech').joins(:data_types.outer)
235
+ end
236
+
237
+ should "merge the JoinType under the association for the merged relation" do
238
+ assert_match /LEFT OUTER JOIN #{DataType.quoted_table_name} ON #{DataType.quoted_table_name}."company_id" = #{Company.quoted_table_name}."id"/,
239
+ @r.to_sql
240
+ end
241
+ end
242
+
243
+ context "A merged relation with with a different base class and an alternate association" do
183
244
  setup do
184
245
  @r = Company.scoped.merge(Developer.where(:salary.gt => 70000), :slackers)
185
246
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 9
8
- - 6
9
- version: 0.9.6
8
+ - 9
9
+ version: 0.9.9
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ernie Miller
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-09-30 00:00:00 -04:00
17
+ date: 2010-11-15 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -41,8 +41,8 @@ dependencies:
41
41
  segments:
42
42
  - 3
43
43
  - 0
44
- - 0
45
- version: 3.0.0
44
+ - 2
45
+ version: 3.0.2
46
46
  type: :runtime
47
47
  version_requirements: *id002
48
48
  - !ruby/object:Gem::Dependency
@@ -56,8 +56,8 @@ dependencies:
56
56
  segments:
57
57
  - 3
58
58
  - 0
59
- - 0
60
- version: 3.0.0
59
+ - 2
60
+ version: 3.0.2
61
61
  type: :runtime
62
62
  version_requirements: *id003
63
63
  - !ruby/object:Gem::Dependency
@@ -69,10 +69,10 @@ dependencies:
69
69
  - - ~>
70
70
  - !ruby/object:Gem::Version
71
71
  segments:
72
- - 1
72
+ - 2
73
73
  - 0
74
- - 1
75
- version: 1.0.1
74
+ - 2
75
+ version: 2.0.2
76
76
  type: :runtime
77
77
  version_requirements: *id004
78
78
  description: "\n MetaWhere offers the ability to call any Arel predicate methods\n (with a few convenient aliases) on your Model's attributes instead\n of the ones normally offered by ActiveRecord's hash parameters. It also\n adds convenient syntax for order clauses, smarter mapping of nested hash\n conditions, and a debug_sql method to see the real SQL your code is\n generating without running it against the database. If you like the new\n AR 3.0 query interface, you'll love it with MetaWhere.\n "
@@ -99,13 +99,17 @@ files:
99
99
  - lib/core_ext/symbol_operators.rb
100
100
  - lib/meta_where.rb
101
101
  - lib/meta_where/association_reflection.rb
102
- - lib/meta_where/builder.rb
103
102
  - lib/meta_where/column.rb
104
103
  - lib/meta_where/compound.rb
105
104
  - lib/meta_where/condition.rb
105
+ - lib/meta_where/function.rb
106
106
  - lib/meta_where/join_dependency.rb
107
+ - lib/meta_where/join_type.rb
107
108
  - lib/meta_where/relation.rb
108
109
  - lib/meta_where/utility.rb
110
+ - lib/meta_where/visitors/attribute.rb
111
+ - lib/meta_where/visitors/predicate.rb
112
+ - lib/meta_where/visitors/visitor.rb
109
113
  - meta_where.gemspec
110
114
  - test/fixtures/companies.yml
111
115
  - test/fixtures/company.rb
@@ -1,84 +0,0 @@
1
- require 'meta_where/utility'
2
-
3
- module MetaWhere
4
- class Builder
5
- include MetaWhere::Utility
6
- attr_reader :join_dependency
7
-
8
- def initialize(join_dependency)
9
- @join_dependency = join_dependency
10
- @engine = join_dependency.join_base.arel_engine
11
- @default_table = Arel::Table.new(join_dependency.join_base.table_name, :engine => @engine)
12
- end
13
-
14
- def build_table(parent_or_table_name = nil)
15
- if parent_or_table_name.is_a?(Symbol)
16
- Arel::Table.new(parent_or_table_name, :engine => @engine)
17
- elsif parent_or_table_name.respond_to?(:aliased_table_name)
18
- Arel::Table.new(parent_or_table_name.table_name, :as => parent_or_table_name.aliased_table_name, :engine => @engine)
19
- else
20
- @default_table
21
- end
22
- end
23
-
24
- def build_predicates_from_hash(attributes, parent = nil)
25
- table = build_table(parent)
26
- predicates = attributes.map do |column, value|
27
- if value.is_a?(Hash)
28
- association = parent.is_a?(Symbol) ? nil : @join_dependency.find_join_association(column, parent)
29
- build_predicates_from_hash(value, association || column)
30
- elsif value.is_a?(MetaWhere::Condition)
31
- association = parent.is_a?(Symbol) ? nil : @join_dependency.find_join_association(column, parent)
32
- value.to_predicate(self, association || column)
33
- elsif value.is_a?(Array) && !value.empty? && value.all? {|v| v.respond_to?(:to_predicate)}
34
- association = parent.is_a?(Symbol) ? nil : @join_dependency.find_join_association(column, parent)
35
- value.map {|val| val.to_predicate(self, association || column)}
36
- else
37
- if column.is_a?(MetaWhere::Column)
38
- method = column.method
39
- column = column.column
40
- else
41
- column = column.to_s
42
- method = method_from_value(value)
43
- end
44
-
45
- if column.include?('.')
46
- table_name, column = column.split('.', 2)
47
- table = Arel::Table.new(table_name, :engine => parent.arel_engine)
48
- end
49
-
50
- unless attribute = table[column]
51
- raise ::ActiveRecord::StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`"
52
- end
53
-
54
- unless valid_comparison_method?(method)
55
- raise ::ActiveRecord::StatementInvalid, "No comparison method named `#{method}` exists for column `#{column}`"
56
- end
57
-
58
- attribute.send(method, *args_for_predicate(method.to_s, value))
59
- end
60
- end
61
-
62
- predicates.flatten
63
- end
64
-
65
- def build_attributes_from_hash(attributes, parent = nil)
66
- table = build_table(parent)
67
- built_attributes = attributes.map do |column, value|
68
- if value.is_a?(Hash)
69
- association = parent.is_a?(Symbol) ? nil : @join_dependency.find_join_association(column, parent)
70
- build_attributes_from_hash(value, association || column)
71
- elsif value.is_a?(Array) && value.all? {|v| v.respond_to?(:to_attribute)}
72
- association = parent.is_a?(Symbol) ? nil : @join_dependency.find_join_association(column, parent)
73
- value.map {|val| val.to_attribute(self, association || column)}
74
- else
75
- association = parent.is_a?(Symbol) ? nil : @join_dependency.find_join_association(column, parent)
76
- value.respond_to?(:to_attribute) ? value.to_attribute(self, association || column) : value
77
- end
78
- end
79
-
80
- built_attributes.flatten
81
- end
82
-
83
- end
84
- end