maksar-meta_where 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|