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
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'meta_where/visitors/visitor'
|
2
|
+
|
3
|
+
module MetaWhere
|
4
|
+
module Visitors
|
5
|
+
class Predicate < Visitor
|
6
|
+
|
7
|
+
def self.visitables
|
8
|
+
[Hash, Array, MetaWhere::Or, MetaWhere::And, MetaWhere::Not, MetaWhere::Condition, MetaWhere::Function]
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit_Hash(o, parent)
|
12
|
+
parent ||= join_dependency.join_base
|
13
|
+
parent = parent.name if parent.is_a? MetaWhere::JoinType
|
14
|
+
table = tables[parent]
|
15
|
+
predicates = o.map do |column, value|
|
16
|
+
if value.is_a?(Hash)
|
17
|
+
association = association_from_parent_and_column(parent, column)
|
18
|
+
accept(value, association || column)
|
19
|
+
elsif [MetaWhere::Condition, MetaWhere::And, MetaWhere::Or, MetaWhere::Not].include?(value.class)
|
20
|
+
association = association_from_parent_and_column(parent, column)
|
21
|
+
accept(value, association || column)
|
22
|
+
elsif value.is_a?(Array) && !value.empty? && value.all? {|v| can_accept?(v)}
|
23
|
+
association = association_from_parent_and_column(parent, column)
|
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)
|
28
|
+
else
|
29
|
+
if column.is_a?(MetaWhere::Column)
|
30
|
+
method = column.method
|
31
|
+
column = column.column
|
32
|
+
else
|
33
|
+
method = method_from_value(value)
|
34
|
+
end
|
35
|
+
|
36
|
+
if [String, Symbol].include?(column.class) && column.to_s.include?('.')
|
37
|
+
table_name, column = column.to_s.split('.', 2)
|
38
|
+
table = Arel::Table.new(table_name, :engine => parent.arel_engine)
|
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
|
+
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
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
predicates.flatten!
|
55
|
+
|
56
|
+
if predicates.size > 1
|
57
|
+
first = predicates.shift
|
58
|
+
Arel::Nodes::Grouping.new(predicates.inject(first) {|memo, expr| Arel::Nodes::And.new([memo, expr])})
|
59
|
+
else
|
60
|
+
predicates.first
|
61
|
+
end
|
62
|
+
end
|
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
|
+
|
72
|
+
def visit_MetaWhere_Or(o, parent)
|
73
|
+
accept(o.condition1, parent).or(accept(o.condition2, parent))
|
74
|
+
end
|
75
|
+
|
76
|
+
def visit_MetaWhere_And(o, parent)
|
77
|
+
accept(o.condition1, parent).and(accept(o.condition2, parent))
|
78
|
+
end
|
79
|
+
|
80
|
+
def visit_MetaWhere_Not(o, parent)
|
81
|
+
accept(o.expr, parent).not
|
82
|
+
end
|
83
|
+
|
84
|
+
def visit_MetaWhere_Condition(o, parent)
|
85
|
+
table = tables[parent]
|
86
|
+
|
87
|
+
unless attribute = attribute_from_column_and_table(o.column, table)
|
88
|
+
raise ::ActiveRecord::StatementInvalid, "No attribute named `#{o.column}` exists for table `#{table.name}`"
|
89
|
+
end
|
90
|
+
|
91
|
+
unless valid_comparison_method?(o.method)
|
92
|
+
raise ::ActiveRecord::StatementInvalid, "No comparison method named `#{o.method}` exists for column `#{o.column}`"
|
93
|
+
end
|
94
|
+
attribute.send(o.method, args_for_predicate(o.value))
|
95
|
+
end
|
96
|
+
|
97
|
+
def visit_MetaWhere_Function(o, parent)
|
98
|
+
self.table = tables[parent]
|
99
|
+
|
100
|
+
o.to_sqlliteral
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def accept_activerecord_values(column, value, parent, reflection)
|
106
|
+
groups = Array.wrap(value).group_by {|v| v.class.base_class}
|
107
|
+
unless reflection.options[:polymorphic] || groups.keys.all? {|k| reflection.klass == k}
|
108
|
+
raise ArgumentError, "An object you supplied to :#{reflection.name} is not a #{reflection.klass}!"
|
109
|
+
end
|
110
|
+
case reflection.macro
|
111
|
+
when :has_many, :has_one, :has_and_belongs_to_many
|
112
|
+
conditions = nil
|
113
|
+
groups.each do |klass, values|
|
114
|
+
condition = {
|
115
|
+
(reflection.options[:foreign_key] || reflection.klass.primary_key).to_sym => values.size == 1 ? values.first.id : values.map(&:id)
|
116
|
+
}
|
117
|
+
conditions = conditions ? conditions | condition : condition
|
118
|
+
end
|
119
|
+
|
120
|
+
accept(conditions, association_from_parent_and_column(parent, column) || column)
|
121
|
+
when :belongs_to
|
122
|
+
conditions = nil
|
123
|
+
groups.each do |klass, values|
|
124
|
+
condition = if reflection.options[:polymorphic]
|
125
|
+
{
|
126
|
+
(reflection.options[:foreign_key] || reflection.foreign_key).to_sym => values.size == 1 ? values.first.id : values.map(&:id),
|
127
|
+
reflection.foreign_type.to_sym => klass.name
|
128
|
+
}
|
129
|
+
else
|
130
|
+
{(reflection.options[:foreign_key] || reflection.foreign_key).to_sym => values.size == 1 ? values.first.id : values.map(&:id)}
|
131
|
+
end
|
132
|
+
conditions = conditions ? conditions | condition : condition
|
133
|
+
end
|
134
|
+
|
135
|
+
accept(conditions, parent)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def sanitize_or_accept_reflection_conditions(reflection, parent, column)
|
140
|
+
if !can_accept?(reflection.options[:conditions])
|
141
|
+
reflection.sanitized_conditions
|
142
|
+
else
|
143
|
+
accept(reflection.options[:conditions], association_from_parent_and_column(parent, column) || column)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'meta_where/utility'
|
2
|
+
|
3
|
+
module MetaWhere
|
4
|
+
module Visitors
|
5
|
+
class Visitor
|
6
|
+
include MetaWhere::Utility
|
7
|
+
|
8
|
+
attr_reader :tables
|
9
|
+
attr_reader :join_dependency
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@tables = Hash.new {|hash, key| hash[key] = get_table(key)}
|
13
|
+
end
|
14
|
+
|
15
|
+
def join_dependency=(jd)
|
16
|
+
@join_dependency = jd
|
17
|
+
jb = jd.join_base
|
18
|
+
@engine = jb.arel_engine
|
19
|
+
@default_table = Arel::Table.new(jb.table_name, :as => jb.aliased_table_name, :engine => @engine)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_table(parent_or_table_name = nil)
|
24
|
+
if parent_or_table_name.is_a?(Symbol)
|
25
|
+
Arel::Table.new(parent_or_table_name, :engine => @engine)
|
26
|
+
elsif parent_or_table_name.respond_to?(:aliased_table_name)
|
27
|
+
Arel::Table.new(parent_or_table_name.table_name, :as => parent_or_table_name.aliased_table_name, :engine => @engine)
|
28
|
+
else
|
29
|
+
@default_table
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def accept(object, parent = nil)
|
34
|
+
visit(object, parent)
|
35
|
+
end
|
36
|
+
|
37
|
+
def can_accept?(object)
|
38
|
+
respond_to? DISPATCH[object.class]
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
DISPATCH = Hash.new do |hash, klass|
|
44
|
+
hash[klass] = "visit_#{klass.name.gsub('::', '_')}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def visit(object, parent)
|
48
|
+
send(DISPATCH[object.class], object, parent)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/meta_where.gemspec
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "meta_where/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "maksar-meta_where"
|
7
|
+
s.version = MetaWhere::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Ernie Miller"]
|
10
|
+
s.email = ["ernie@metautonomo.us"]
|
11
|
+
s.homepage = "http://metautonomo.us/projects/metawhere"
|
12
|
+
s.summary = %q{ActiveRecord 3 query syntax on steroids.}
|
13
|
+
s.description = %q{
|
14
|
+
MetaWhere offers the ability to call any Arel predicate methods
|
15
|
+
(with a few convenient aliases) on your Model's attributes instead
|
16
|
+
of the ones normally offered by ActiveRecord's hash parameters. It also
|
17
|
+
adds convenient syntax for order clauses, smarter mapping of nested hash
|
18
|
+
conditions, and a debug_sql method to see the real SQL your code is
|
19
|
+
generating without running it against the database. If you like the new
|
20
|
+
AR 3.0 query interface, you'll love it with MetaWhere.
|
21
|
+
}
|
22
|
+
|
23
|
+
s.post_install_message = %q{
|
24
|
+
*** Thanks for installing MetaWhere! ***
|
25
|
+
Be sure to check out http://metautonomo.us/projects/metawhere/ for a
|
26
|
+
walkthrough of MetaWhere's features, and click the donate button if
|
27
|
+
you're feeling especially appreciative. It'd help me justify this
|
28
|
+
"open source" stuff to my lovely wife. :)
|
29
|
+
|
30
|
+
}
|
31
|
+
|
32
|
+
s.extra_rdoc_files = [
|
33
|
+
"LICENSE",
|
34
|
+
"README.rdoc"
|
35
|
+
]
|
36
|
+
|
37
|
+
s.rubyforge_project = "meta_where"
|
38
|
+
|
39
|
+
s.add_dependency 'activerecord', '~> 3.1.0.rc1'
|
40
|
+
s.add_dependency 'activesupport', '~> 3.1.0.rc1'
|
41
|
+
s.add_development_dependency 'shoulda'
|
42
|
+
s.add_development_dependency 'sqlite3', '~> 1.3.3'
|
43
|
+
|
44
|
+
s.files = `git ls-files`.split("\n")
|
45
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
46
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
47
|
+
s.require_paths = ["lib"]
|
48
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
initech:
|
2
|
+
name : Initech
|
3
|
+
id : 1
|
4
|
+
created_at: 1999-02-19 08:00
|
5
|
+
updated_at: 1999-02-19 08:00
|
6
|
+
|
7
|
+
aos:
|
8
|
+
name: Advanced Optical Solutions
|
9
|
+
id : 2
|
10
|
+
created_at: 2004-02-01 08:00
|
11
|
+
updated_at: 2004-02-01 08:00
|
12
|
+
|
13
|
+
mission_data:
|
14
|
+
name: Mission Data
|
15
|
+
id : 3
|
16
|
+
created_at: 1996-09-21 08:00
|
17
|
+
updated_at: 1996-09-21 08:00
|
@@ -0,0 +1,7 @@
|
|
1
|
+
class Company < ActiveRecord::Base
|
2
|
+
has_many :developers
|
3
|
+
has_many :developer_notes, :through => :developers, :source => :notes
|
4
|
+
has_many :slackers, :class_name => "Developer", :conditions => {:slacker => true}
|
5
|
+
has_many :notes, :as => :notable
|
6
|
+
has_many :data_types
|
7
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<% 1.upto(9) do |n| %>
|
2
|
+
dt_<%= n %>:
|
3
|
+
company_id: <%= n % 3 + 1 %>
|
4
|
+
str : This string has <%= n %> exclamation points<%= '!' * n %>
|
5
|
+
txt : <%= 'This is some text that may or may not repeat based on the value of n.' * n %>
|
6
|
+
int : <%= n ** 3 %>
|
7
|
+
flt : <%= n.to_f / 2.0 %>
|
8
|
+
dec : <%= n.to_f ** (n + 0.1) %>
|
9
|
+
dtm : <%= (Time.utc(2009, 12, 24) + 86400 * n).to_s(:db) %>
|
10
|
+
tms : <%= (Time.utc(2009, 12, 24) + 86400 * n).to_s(:db) %>
|
11
|
+
tim : <%= Time.utc(2000, 01, 01, n+8, n).to_s(:db) %>
|
12
|
+
dat : <%= (Date.new(2009, 12, 24) + n).strftime("%Y-%m-%d") %>
|
13
|
+
bin : <%= "BLOB#{n}" * n %>
|
14
|
+
bln : <%= n % 2 > 0 ? true : false %>
|
15
|
+
<% end %>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
peter:
|
2
|
+
id : 1
|
3
|
+
company_id: 1
|
4
|
+
name : Peter Gibbons
|
5
|
+
salary : 100000
|
6
|
+
slacker : true
|
7
|
+
|
8
|
+
michael:
|
9
|
+
id : 2
|
10
|
+
company_id: 1
|
11
|
+
name : Michael Bolton
|
12
|
+
salary : 70000
|
13
|
+
slacker : false
|
14
|
+
|
15
|
+
samir:
|
16
|
+
id : 3
|
17
|
+
company_id: 1
|
18
|
+
name : Samir Nagheenanajar
|
19
|
+
salary : 65000
|
20
|
+
slacker : false
|
21
|
+
|
22
|
+
herb:
|
23
|
+
id : 4
|
24
|
+
company_id: 2
|
25
|
+
name : Herb Myers
|
26
|
+
salary : 50000
|
27
|
+
slacker : false
|
28
|
+
|
29
|
+
dude:
|
30
|
+
id : 5
|
31
|
+
company_id: 2
|
32
|
+
name : Some Dude
|
33
|
+
salary : 84000
|
34
|
+
slacker : true
|
35
|
+
|
36
|
+
ernie:
|
37
|
+
id : 6
|
38
|
+
company_id: 3
|
39
|
+
name : Ernie Miller
|
40
|
+
salary : 45000
|
41
|
+
slacker : true
|
42
|
+
|
43
|
+
someone:
|
44
|
+
id : 7
|
45
|
+
company_id: 3
|
46
|
+
name : Someone Else
|
47
|
+
salary : 70000
|
48
|
+
slacker : true
|
49
|
+
|
50
|
+
another:
|
51
|
+
id : 8
|
52
|
+
company_id: 3
|
53
|
+
name : Another Guy
|
54
|
+
salary : 80000
|
55
|
+
slacker : false
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<% 1.upto(3) do |d| %>
|
2
|
+
y2k_<%= d %>:
|
3
|
+
developer_id: <%= d %>
|
4
|
+
project_id : 1
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
virus:
|
8
|
+
developer_id: 2
|
9
|
+
project_id : 2
|
10
|
+
|
11
|
+
<% 1.upto(8) do |d| %>
|
12
|
+
awesome_<%= d %>:
|
13
|
+
developer_id: <%= d %>
|
14
|
+
project_id : 3
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
metasearch:
|
18
|
+
developer_id: 6
|
19
|
+
project_id : 4
|
20
|
+
|
21
|
+
<% 4.upto(8) do |d| %>
|
22
|
+
another_<%= d %>:
|
23
|
+
developer_id: <%= d %>
|
24
|
+
project_id : 5
|
25
|
+
<% end %>
|
@@ -0,0 +1,95 @@
|
|
1
|
+
peter:
|
2
|
+
id : 1
|
3
|
+
notable_type: Developer
|
4
|
+
notable_id : 1
|
5
|
+
note : A straight shooter with upper management written all over him.
|
6
|
+
|
7
|
+
michael:
|
8
|
+
id : 2
|
9
|
+
notable_type: Developer
|
10
|
+
notable_id : 2
|
11
|
+
note : Doesn't like the singer of the same name. The nerve!
|
12
|
+
|
13
|
+
samir:
|
14
|
+
id : 3
|
15
|
+
notable_type: Developer
|
16
|
+
notable_id : 3
|
17
|
+
note : Naga.... Naga..... Not gonna work here anymore anyway.
|
18
|
+
|
19
|
+
herb:
|
20
|
+
id : 4
|
21
|
+
notable_type: Developer
|
22
|
+
notable_id : 4
|
23
|
+
note : Will show you what he's doing.
|
24
|
+
|
25
|
+
dude:
|
26
|
+
id : 5
|
27
|
+
notable_type: Developer
|
28
|
+
notable_id : 5
|
29
|
+
note : Nothing of note.
|
30
|
+
|
31
|
+
ernie:
|
32
|
+
id : 6
|
33
|
+
notable_type: Developer
|
34
|
+
notable_id : 6
|
35
|
+
note : Complete slacker. Should probably be fired.
|
36
|
+
|
37
|
+
someone:
|
38
|
+
id : 7
|
39
|
+
notable_type: Developer
|
40
|
+
notable_id : 7
|
41
|
+
note : Just another developer.
|
42
|
+
|
43
|
+
another:
|
44
|
+
id : 8
|
45
|
+
notable_type: Developer
|
46
|
+
notable_id : 8
|
47
|
+
note : Placing a note in this guy's file for insubordination.
|
48
|
+
|
49
|
+
initech:
|
50
|
+
id : 9
|
51
|
+
notable_type: Company
|
52
|
+
notable_id : 1
|
53
|
+
note : Innovation + Technology!
|
54
|
+
|
55
|
+
aos:
|
56
|
+
id : 10
|
57
|
+
notable_type: Company
|
58
|
+
notable_id : 2
|
59
|
+
note : Advanced solutions of an optical nature.
|
60
|
+
|
61
|
+
mission_data:
|
62
|
+
id : 11
|
63
|
+
notable_type: Company
|
64
|
+
notable_id : 3
|
65
|
+
note : Best design + development shop in the 'ville.
|
66
|
+
|
67
|
+
y2k:
|
68
|
+
id : 12
|
69
|
+
notable_type: Project
|
70
|
+
notable_id : 1
|
71
|
+
note : It may have already passed but that's no excuse to be unprepared!
|
72
|
+
|
73
|
+
virus:
|
74
|
+
id : 13
|
75
|
+
notable_type: Project
|
76
|
+
notable_id : 2
|
77
|
+
note : It could bring the company to its knees.
|
78
|
+
|
79
|
+
awesome:
|
80
|
+
id : 14
|
81
|
+
notable_type: Project
|
82
|
+
notable_id : 3
|
83
|
+
note : This note is AWESOME!!!
|
84
|
+
|
85
|
+
metasearch:
|
86
|
+
id : 15
|
87
|
+
notable_type: Project
|
88
|
+
notable_id : 4
|
89
|
+
note : A complete waste of the developer's time.
|
90
|
+
|
91
|
+
another:
|
92
|
+
id : 16
|
93
|
+
notable_type: Project
|
94
|
+
notable_id : 5
|
95
|
+
note : This is another project note.
|