maksar-meta_where 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/CHANGELOG +90 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.rdoc +343 -0
- data/Rakefile +11 -0
- data/lib/core_ext/hash.rb +5 -0
- data/lib/core_ext/symbol.rb +39 -0
- data/lib/core_ext/symbol_operators.rb +48 -0
- data/lib/meta_where.rb +51 -0
- data/lib/meta_where/association_reflection.rb +51 -0
- data/lib/meta_where/column.rb +31 -0
- data/lib/meta_where/compound.rb +20 -0
- data/lib/meta_where/condition.rb +32 -0
- data/lib/meta_where/condition_operators.rb +19 -0
- data/lib/meta_where/function.rb +108 -0
- data/lib/meta_where/join_dependency.rb +105 -0
- data/lib/meta_where/join_type.rb +43 -0
- data/lib/meta_where/not.rb +13 -0
- data/lib/meta_where/relation.rb +290 -0
- data/lib/meta_where/utility.rb +51 -0
- data/lib/meta_where/version.rb +3 -0
- data/lib/meta_where/visitors/attribute.rb +58 -0
- data/lib/meta_where/visitors/predicate.rb +149 -0
- data/lib/meta_where/visitors/visitor.rb +52 -0
- data/meta_where.gemspec +48 -0
- data/test/fixtures/companies.yml +17 -0
- data/test/fixtures/company.rb +7 -0
- data/test/fixtures/data_type.rb +3 -0
- data/test/fixtures/data_types.yml +15 -0
- data/test/fixtures/developer.rb +5 -0
- data/test/fixtures/developers.yml +55 -0
- data/test/fixtures/developers_projects.yml +25 -0
- data/test/fixtures/fixed_bid_project.rb +2 -0
- data/test/fixtures/invalid_company.rb +4 -0
- data/test/fixtures/invalid_developer.rb +4 -0
- data/test/fixtures/note.rb +3 -0
- data/test/fixtures/notes.yml +95 -0
- data/test/fixtures/people.yml +14 -0
- data/test/fixtures/person.rb +4 -0
- data/test/fixtures/project.rb +7 -0
- data/test/fixtures/projects.yml +29 -0
- data/test/fixtures/schema.rb +53 -0
- data/test/fixtures/time_and_materials_project.rb +2 -0
- data/test/helper.rb +33 -0
- data/test/test_base.rb +21 -0
- data/test/test_relations.rb +455 -0
- metadata +173 -0
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
class Symbol
|
2
|
+
MetaWhere::PREDICATES.each do |predication|
|
3
|
+
define_method(predication) do
|
4
|
+
MetaWhere::Column.new(self, predication)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
MetaWhere::METHOD_ALIASES.each_pair do |aliased, predication|
|
9
|
+
define_method(aliased) do
|
10
|
+
MetaWhere::Column.new(self, predication)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
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
|
+
end
|
23
|
+
|
24
|
+
def outer
|
25
|
+
MetaWhere::JoinType.new(self, Arel::Nodes::OuterJoin)
|
26
|
+
end
|
27
|
+
|
28
|
+
def type(klass)
|
29
|
+
MetaWhere::JoinType.new(self, Arel::Nodes::InnerJoin, klass)
|
30
|
+
end
|
31
|
+
|
32
|
+
def asc
|
33
|
+
MetaWhere::Column.new(self, :asc)
|
34
|
+
end
|
35
|
+
|
36
|
+
def desc
|
37
|
+
MetaWhere::Column.new(self, :desc)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Symbol
|
2
|
+
def [](*values)
|
3
|
+
MetaWhere::Function.new(self, *values)
|
4
|
+
end
|
5
|
+
|
6
|
+
def >>(value)
|
7
|
+
MetaWhere::Condition.new(self, value, :eq)
|
8
|
+
end
|
9
|
+
|
10
|
+
def ^(value)
|
11
|
+
MetaWhere::Condition.new(self, value, :not_eq)
|
12
|
+
end
|
13
|
+
|
14
|
+
def +(value)
|
15
|
+
MetaWhere::Condition.new(self, value, :in)
|
16
|
+
end
|
17
|
+
|
18
|
+
def -(value)
|
19
|
+
MetaWhere::Condition.new(self, value, :not_in)
|
20
|
+
end
|
21
|
+
|
22
|
+
def =~(value)
|
23
|
+
MetaWhere::Condition.new(self, value, :matches)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Won't work on Ruby 1.8.x so need to do this conditionally
|
27
|
+
if respond_to?('!~')
|
28
|
+
define_method('!~') do |value|
|
29
|
+
MetaWhere::Condition.new(self, value, :does_not_match)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def >(value)
|
34
|
+
MetaWhere::Condition.new(self, value, :gt)
|
35
|
+
end
|
36
|
+
|
37
|
+
def >=(value)
|
38
|
+
MetaWhere::Condition.new(self, value, :gteq)
|
39
|
+
end
|
40
|
+
|
41
|
+
def <(value)
|
42
|
+
MetaWhere::Condition.new(self, value, :lt)
|
43
|
+
end
|
44
|
+
|
45
|
+
def <=(value)
|
46
|
+
MetaWhere::Condition.new(self, value, :lteq)
|
47
|
+
end
|
48
|
+
end
|
data/lib/meta_where.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module MetaWhere
|
2
|
+
METHOD_ALIASES = {
|
3
|
+
'ne' => :not_eq,
|
4
|
+
'like' => :matches,
|
5
|
+
'not_matches' => :does_not_match,
|
6
|
+
'nlike' => :does_not_match,
|
7
|
+
'lte' => :lteq,
|
8
|
+
'gte' => :gteq,
|
9
|
+
'nin' => :not_in
|
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
|
+
|
25
|
+
def self.operator_overload!
|
26
|
+
require 'core_ext/symbol_operators'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'arel'
|
31
|
+
require 'active_record'
|
32
|
+
require 'active_support'
|
33
|
+
|
34
|
+
# TODO: Trim this down, as the individual files should be
|
35
|
+
# handling their requires.
|
36
|
+
require 'meta_where/column'
|
37
|
+
require 'meta_where/condition'
|
38
|
+
require 'meta_where/not'
|
39
|
+
require 'meta_where/compound'
|
40
|
+
require 'meta_where/function'
|
41
|
+
require 'meta_where/join_type'
|
42
|
+
require 'core_ext/symbol'
|
43
|
+
require 'core_ext/hash'
|
44
|
+
require 'meta_where/visitors/attribute'
|
45
|
+
require 'meta_where/visitors/predicate'
|
46
|
+
require 'meta_where/association_reflection'
|
47
|
+
require 'meta_where/relation'
|
48
|
+
require 'meta_where/join_dependency'
|
49
|
+
ActiveRecord::Relation.send(:include, MetaWhere::Relation)
|
50
|
+
ActiveRecord::Reflection::AssociationReflection.send(:include, MetaWhere::AssociationReflection)
|
51
|
+
ActiveRecord::Associations::ClassMethods::JoinDependency.send(:include, MetaWhere::JoinDependency)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module MetaWhere
|
2
|
+
class MetaWhereInAssociationError < StandardError; end
|
3
|
+
|
4
|
+
module AssociationReflection
|
5
|
+
|
6
|
+
def initialize(macro, name, options, active_record)
|
7
|
+
super
|
8
|
+
|
9
|
+
if options.has_key?(:conditions)
|
10
|
+
ensure_no_metawhere_in_conditions(options[:conditions])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def ensure_no_metawhere_in_conditions(obj)
|
17
|
+
case obj
|
18
|
+
when Hash
|
19
|
+
if obj.keys.grep(MetaWhere::Column).any?
|
20
|
+
raise MetaWhereInAssociationError, <<END
|
21
|
+
The :#{name} association has a MetaWhere::Column in its :conditions. \
|
22
|
+
If you actually needed to access conditions other than equality, then you most \
|
23
|
+
likely meant to set up a scope or method, instead. Associations only work with \
|
24
|
+
standard equality conditions, since they can be used to create records as well.
|
25
|
+
END
|
26
|
+
end
|
27
|
+
|
28
|
+
obj.values.each do |v|
|
29
|
+
case v
|
30
|
+
when MetaWhere::Condition, Array, Hash
|
31
|
+
ensure_no_metawhere_in_conditions(v)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
when Array
|
35
|
+
obj.each do |v|
|
36
|
+
case v
|
37
|
+
when MetaWhere::Condition, Array, Hash
|
38
|
+
ensure_no_metawhere_in_conditions(v)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
when MetaWhere::Condition
|
42
|
+
raise MetaWhereInAssociationError, <<END
|
43
|
+
The :#{name} association has a MetaWhere::Condition in its :conditions. \
|
44
|
+
If you actually needed to access conditions other than equality, then you most \
|
45
|
+
likely meant to set up a scope or method, instead. Associations only work with \
|
46
|
+
standard equality conditions, since they can be used to create records as well.
|
47
|
+
END
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MetaWhere
|
2
|
+
class Column
|
3
|
+
attr_reader :column, :method
|
4
|
+
|
5
|
+
def initialize(column, method)
|
6
|
+
@column = column
|
7
|
+
@method = method
|
8
|
+
end
|
9
|
+
|
10
|
+
def %(value)
|
11
|
+
MetaWhere::Condition.new(column, value, method)
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other_column)
|
15
|
+
other_column.is_a?(Column) &&
|
16
|
+
other_column.column == column &&
|
17
|
+
other_column.method == method
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method :eql?, :==
|
21
|
+
|
22
|
+
def hash
|
23
|
+
[column, method].hash
|
24
|
+
end
|
25
|
+
|
26
|
+
# Play nicely with expand_hash_conditions_for_aggregates
|
27
|
+
def to_sym
|
28
|
+
"#{column}.#{method}".to_sym
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'meta_where/condition_operators'
|
2
|
+
|
3
|
+
module MetaWhere
|
4
|
+
class Compound
|
5
|
+
include ConditionOperators
|
6
|
+
|
7
|
+
attr_reader :condition1, :condition2
|
8
|
+
|
9
|
+
def initialize(condition1, condition2)
|
10
|
+
@condition1 = condition1
|
11
|
+
@condition2 = condition2
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Or < Compound
|
16
|
+
end
|
17
|
+
|
18
|
+
class And < Compound
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'meta_where/utility'
|
2
|
+
require 'meta_where/condition_operators'
|
3
|
+
|
4
|
+
module MetaWhere
|
5
|
+
class Condition
|
6
|
+
include ConditionOperators
|
7
|
+
include Utility
|
8
|
+
|
9
|
+
attr_reader :column, :value, :method
|
10
|
+
|
11
|
+
def initialize(column, value, method)
|
12
|
+
@column = column
|
13
|
+
@value = value
|
14
|
+
@method = (MetaWhere::METHOD_ALIASES[method.to_s] || method).to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other_condition)
|
18
|
+
other_condition.is_a?(Condition) &&
|
19
|
+
other_condition.column == column &&
|
20
|
+
other_condition.value == value &&
|
21
|
+
other_condition.method == method
|
22
|
+
end
|
23
|
+
|
24
|
+
alias_method :eql?, :==
|
25
|
+
|
26
|
+
# Play "nicely" with expand_hash_conditions_for_aggregates
|
27
|
+
def to_sym
|
28
|
+
self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module MetaWhere
|
2
|
+
module ConditionOperators
|
3
|
+
def |(other)
|
4
|
+
Or.new(self, other)
|
5
|
+
end
|
6
|
+
|
7
|
+
def &(other)
|
8
|
+
And.new(self, other)
|
9
|
+
end
|
10
|
+
|
11
|
+
def -(other)
|
12
|
+
And.new(self, Not.new(other))
|
13
|
+
end
|
14
|
+
|
15
|
+
def -@
|
16
|
+
Not.new(self)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
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
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module MetaWhere
|
2
|
+
module JoinDependency
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
alias_method_chain :build, :metawhere
|
7
|
+
alias_method_chain :find_join_association, :metawhere
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class BaseMismatchError < StandardError; end
|
12
|
+
class ConfigurationError < StandardError; end
|
13
|
+
class AssociationNotFoundError < StandardError; end
|
14
|
+
|
15
|
+
def build_with_metawhere(associations, parent = nil, join_type = Arel::InnerJoin)
|
16
|
+
parent ||= join_parts.last
|
17
|
+
if MetaWhere::JoinType === associations
|
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 '#{ associations }' was not found; perhaps you misspelled it?"
|
27
|
+
unless (association = find_join_association(reflection, parent)) && (!klass || association.active_record == klass)
|
28
|
+
@reflections << reflection
|
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 = PolymorphicJoinAssociation.new(reflection, self, klass, parent)
|
32
|
+
else
|
33
|
+
association = build_join_association(reflection, parent)
|
34
|
+
end
|
35
|
+
association.join_type = join_type
|
36
|
+
@join_parts << association
|
37
|
+
cache_joined_association(association)
|
38
|
+
end
|
39
|
+
association
|
40
|
+
else
|
41
|
+
build_without_metawhere(associations, parent, join_type)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_join_association_with_metawhere(name_or_reflection, parent)
|
46
|
+
case name_or_reflection
|
47
|
+
when MetaWhere::JoinType
|
48
|
+
join_associations.detect do |j|
|
49
|
+
(j.reflection.name == name_or_reflection.name) &&
|
50
|
+
(j.reflection.klass == name_or_reflection.klass) &&
|
51
|
+
(j.parent == parent)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
case name_or_reflection
|
55
|
+
when Symbol, String
|
56
|
+
join_associations.detect {|j| (j.reflection.name == name_or_reflection.to_s.intern) && (j.parent == parent)}
|
57
|
+
else
|
58
|
+
join_associations.detect {|j| (j.reflection == name_or_reflection) && (j.parent == parent)}
|
59
|
+
end
|
60
|
+
#find_join_association_without_metawhere(name_or_reflection, parent)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class PolymorphicJoinAssociation < ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
|
66
|
+
|
67
|
+
def initialize(reflection, join_dependency, polymorphic_class, parent = nil)
|
68
|
+
reflection.check_validity!
|
69
|
+
@active_record = polymorphic_class
|
70
|
+
@cached_record = {}
|
71
|
+
@column_names_with_alias = nil
|
72
|
+
@reflection = reflection.clone
|
73
|
+
@reflection.instance_variable_set :"@klass", polymorphic_class
|
74
|
+
@join_dependency = join_dependency
|
75
|
+
@parent = parent
|
76
|
+
@join_type = Arel::InnerJoin
|
77
|
+
@aliased_prefix = "t#{ join_dependency.join_parts.size }"
|
78
|
+
|
79
|
+
allocate_aliases
|
80
|
+
@table = Arel::Table.new(
|
81
|
+
table_name, :as => aliased_table_name, :engine => arel_engine
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def ==(other)
|
87
|
+
other.class == self.class &&
|
88
|
+
other.reflection == reflection &&
|
89
|
+
other.active_record == active_record &&
|
90
|
+
other.parent == parent
|
91
|
+
end
|
92
|
+
|
93
|
+
def join_belongs_to_to(relation)
|
94
|
+
foreign_key = options[:foreign_key] || reflection.foreign_key
|
95
|
+
foreign_type = options[:foreign_type] || reflection.foreign_type
|
96
|
+
primary_key = options[:primary_key] || reflection.klass.primary_key
|
97
|
+
join_target_table(
|
98
|
+
relation,
|
99
|
+
target_table[primary_key].eq(parent_table[foreign_key]).
|
100
|
+
and(parent_table[foreign_type].eq(active_record.name))
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|