benhoskings-ambition 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +18 -0
- data/Manifest +42 -0
- data/README +24 -0
- data/ambition.gemspec +120 -0
- data/app_generators/ambition_adapter/USAGE +1 -0
- data/app_generators/ambition_adapter/ambition_adapter_generator.rb +66 -0
- data/app_generators/ambition_adapter/templates/LICENSE +18 -0
- data/app_generators/ambition_adapter/templates/README +6 -0
- data/app_generators/ambition_adapter/templates/Rakefile +31 -0
- data/app_generators/ambition_adapter/templates/lib/adapter/base.rb.erb +12 -0
- data/app_generators/ambition_adapter/templates/lib/adapter/query.rb.erb +52 -0
- data/app_generators/ambition_adapter/templates/lib/adapter/select.rb.erb +100 -0
- data/app_generators/ambition_adapter/templates/lib/adapter/slice.rb.erb +19 -0
- data/app_generators/ambition_adapter/templates/lib/adapter/sort.rb.erb +43 -0
- data/app_generators/ambition_adapter/templates/lib/init.rb.erb +22 -0
- data/app_generators/ambition_adapter/templates/test/helper.rb.erb +9 -0
- data/app_generators/ambition_adapter/templates/test/select_test.rb.erb +157 -0
- data/app_generators/ambition_adapter/templates/test/slice_test.rb.erb +36 -0
- data/app_generators/ambition_adapter/templates/test/sort_test.rb.erb +53 -0
- data/bin/ambition_adapter +13 -0
- data/lib/ambition.rb +11 -0
- data/lib/ambition/api.rb +116 -0
- data/lib/ambition/context.rb +66 -0
- data/lib/ambition/core_ext.rb +21 -0
- data/lib/ambition/enumerable.rb +6 -0
- data/lib/ambition/processors/base.rb +134 -0
- data/lib/ambition/processors/ruby.rb +26 -0
- data/lib/ambition/processors/select.rb +105 -0
- data/lib/ambition/processors/slice.rb +15 -0
- data/lib/ambition/processors/sort.rb +51 -0
- data/lib/ambition/sexp_translator.rb +16 -0
- data/test/adapters/exemplar/association_test.rb +34 -0
- data/test/adapters/exemplar/count_test.rb +0 -0
- data/test/adapters/exemplar/detect_test.rb +9 -0
- data/test/adapters/exemplar/enumerable_test.rb +0 -0
- data/test/adapters/exemplar/helper.rb +3 -0
- data/test/adapters/exemplar/index_operator.rb +6 -0
- data/test/adapters/exemplar/reject_test.rb +0 -0
- data/test/adapters/exemplar/select_test.rb +151 -0
- data/test/adapters/exemplar/slice_test.rb +0 -0
- data/test/adapters/exemplar/sort_test.rb +0 -0
- data/test/debug +9 -0
- data/test/helper.rb +4 -0
- metadata +142 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Ambition
|
2
|
+
module Adapters
|
3
|
+
module <%= adapter_module %>
|
4
|
+
class Slice < Base
|
5
|
+
# >> User.first(5)
|
6
|
+
# => #slice(0, 5)
|
7
|
+
#
|
8
|
+
# >> User.first
|
9
|
+
# => #slice(0, 1)
|
10
|
+
#
|
11
|
+
# >> User[10, 20]
|
12
|
+
# => #slice(10, 20)
|
13
|
+
def slice(start, length)
|
14
|
+
raise "Not implemented."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Ambition
|
2
|
+
module Adapters
|
3
|
+
module <%= adapter_module %>
|
4
|
+
class Sort < Base
|
5
|
+
# >> sort_by { |u| u.age }
|
6
|
+
# => #sort_by(:age)
|
7
|
+
def sort_by(method)
|
8
|
+
raise "Not implemented."
|
9
|
+
end
|
10
|
+
|
11
|
+
# >> sort_by { |u| -u.age }
|
12
|
+
# => #reverse_sort_by(:age)
|
13
|
+
def reverse_sort_by(method)
|
14
|
+
raise "Not implemented."
|
15
|
+
end
|
16
|
+
|
17
|
+
# >> sort_by { |u| u.profile.name }
|
18
|
+
# => #chained_sort_by(:profile, :name)
|
19
|
+
def chained_sort_by(receiver, method)
|
20
|
+
raise "Not implemented."
|
21
|
+
end
|
22
|
+
|
23
|
+
# >> sort_by { |u| -u.profile.name }
|
24
|
+
# => #chained_reverse_sort_by(:profile, :name)
|
25
|
+
def chained_reverse_sort_by(receiver, method)
|
26
|
+
raise "Not implemented."
|
27
|
+
end
|
28
|
+
|
29
|
+
# >> sort_by(&:name)
|
30
|
+
# => #to_proc(:name)
|
31
|
+
def to_proc(symbol)
|
32
|
+
raise "Not implemented."
|
33
|
+
end
|
34
|
+
|
35
|
+
# >> sort_by { rand }
|
36
|
+
# => #rand
|
37
|
+
def rand
|
38
|
+
raise "Not implemented."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'ambition'
|
2
|
+
require '<%= adapter_name %>'
|
3
|
+
require 'ambition/adapters/<%= adapter_name %>/base'
|
4
|
+
require 'ambition/adapters/<%= adapter_name %>/query'
|
5
|
+
require 'ambition/adapters/<%= adapter_name %>/select'
|
6
|
+
require 'ambition/adapters/<%= adapter_name %>/sort'
|
7
|
+
require 'ambition/adapters/<%= adapter_name %>/slice'
|
8
|
+
|
9
|
+
##
|
10
|
+
# This is where you inject Ambition into your target.
|
11
|
+
#
|
12
|
+
# Use `extend' if you are injecting a class, `include' if you are
|
13
|
+
# injecting instances of that class.
|
14
|
+
#
|
15
|
+
# You must also set the `ambition_adapter' class variable on your target
|
16
|
+
# class, regardless of whether you are injecting instances or the class itself.
|
17
|
+
#
|
18
|
+
# You probably want something like this:
|
19
|
+
#
|
20
|
+
# <%= adapter_module %>::Base.extend Ambition::API
|
21
|
+
# <%= adapter_module %>::Base.ambition_adapter = Ambition::Adapters::<%= adapter_module %>
|
22
|
+
#
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
context "<%= adapter_module %> Adapter :: Select" do
|
4
|
+
setup do
|
5
|
+
@klass = User
|
6
|
+
end
|
7
|
+
|
8
|
+
xspecify "==" do
|
9
|
+
translator = @klass.select { |m| m.name == 'jon' }
|
10
|
+
translator.to_s.should == %Q(foo)
|
11
|
+
end
|
12
|
+
|
13
|
+
xspecify "!=" do
|
14
|
+
translator = @klass.select { |m| m.name != 'jon' }
|
15
|
+
translator.to_s.should == %Q(foo)
|
16
|
+
end
|
17
|
+
|
18
|
+
xspecify "== && ==" do
|
19
|
+
translator = @klass.select { |m| m.name == 'jon' && m.age == 21 }
|
20
|
+
translator.to_s.should == %Q(foo)
|
21
|
+
end
|
22
|
+
|
23
|
+
xspecify "== || ==" do
|
24
|
+
translator = @klass.select { |m| m.name == 'jon' || m.age == 21 }
|
25
|
+
translator.to_s.should == %Q(foo)
|
26
|
+
end
|
27
|
+
|
28
|
+
xspecify "mixed && and ||" do
|
29
|
+
translator = @klass.select { |m| m.name == 'jon' || m.age == 21 && m.password == 'pass' }
|
30
|
+
translator.to_s.should == %Q(foo)
|
31
|
+
end
|
32
|
+
|
33
|
+
xspecify "grouped && and ||" do
|
34
|
+
translator = @klass.select { |m| (m.name == 'jon' || m.name == 'rick') && m.age == 21 }
|
35
|
+
translator.to_s.should == %Q(foo)
|
36
|
+
end
|
37
|
+
|
38
|
+
xspecify ">/<" do
|
39
|
+
translator = @klass.select { |m| m.age > 21 }
|
40
|
+
translator.to_s.should == %Q(foo)
|
41
|
+
|
42
|
+
translator = @klass.select { |m| m.age >= 21 }
|
43
|
+
translator.to_s.should == %Q(foo)
|
44
|
+
|
45
|
+
translator = @klass.select { |m| m.age < 21 }
|
46
|
+
translator.to_s.should == %Q(foo)
|
47
|
+
end
|
48
|
+
|
49
|
+
xspecify "array.include? item" do
|
50
|
+
translator = @klass.select { |m| [1, 2, 3, 4].include? m.id }
|
51
|
+
translator.to_s.should == %Q(foo)
|
52
|
+
end
|
53
|
+
|
54
|
+
xspecify "variabled array.include? item" do
|
55
|
+
array = [1, 2, 3, 4]
|
56
|
+
translator = @klass.select { |m| array.include? m.id }
|
57
|
+
translator.to_s.should == %Q(foo)
|
58
|
+
end
|
59
|
+
|
60
|
+
xspecify "== with variables" do
|
61
|
+
me = 'chris'
|
62
|
+
translator = @klass.select { |m| m.name == me }
|
63
|
+
translator.to_s.should == %Q(foo)
|
64
|
+
end
|
65
|
+
|
66
|
+
xspecify "== with method arguments" do
|
67
|
+
def test_it(name)
|
68
|
+
translator = @klass.select { |m| m.name == name }
|
69
|
+
translator.to_s.should == %Q(foo)
|
70
|
+
end
|
71
|
+
|
72
|
+
test_it('chris')
|
73
|
+
end
|
74
|
+
|
75
|
+
xspecify "== with instance variables" do
|
76
|
+
@me = 'chris'
|
77
|
+
translator = @klass.select { |m| m.name == @me }
|
78
|
+
translator.to_s.should == %Q(foo)
|
79
|
+
end
|
80
|
+
|
81
|
+
xspecify "== with instance variable method call" do
|
82
|
+
require 'ostruct'
|
83
|
+
@person = OpenStruct.new(:name => 'chris')
|
84
|
+
|
85
|
+
translator = @klass.select { |m| m.name == @person.name }
|
86
|
+
translator.to_s.should == %Q(foo)
|
87
|
+
end
|
88
|
+
|
89
|
+
xspecify "== with global variables" do
|
90
|
+
$my_name = 'boston'
|
91
|
+
translator = @klass.select { |m| m.name == $my_name }
|
92
|
+
translator.to_s.should == %Q(foo)
|
93
|
+
end
|
94
|
+
|
95
|
+
xspecify "== with method call" do
|
96
|
+
def band
|
97
|
+
'skinny puppy'
|
98
|
+
end
|
99
|
+
|
100
|
+
translator = @klass.select { |m| m.name == band }
|
101
|
+
translator.to_s.should == %Q(foo)
|
102
|
+
end
|
103
|
+
|
104
|
+
xspecify "=~ with string" do
|
105
|
+
translator = @klass.select { |m| m.name =~ 'chris' }
|
106
|
+
translator.to_s.should == %Q(foo)
|
107
|
+
|
108
|
+
translator = @klass.select { |m| m.name =~ 'chri%' }
|
109
|
+
translator.to_s.should == %Q(foo)
|
110
|
+
end
|
111
|
+
|
112
|
+
xspecify "!~ with string" do
|
113
|
+
translator = @klass.select { |m| m.name !~ 'chris' }
|
114
|
+
translator.to_s.should == %Q(foo)
|
115
|
+
|
116
|
+
translator = @klass.select { |m| !(m.name =~ 'chris') }
|
117
|
+
translator.to_s.should == %Q(foo)
|
118
|
+
end
|
119
|
+
|
120
|
+
xspecify "=~ with regexp" do
|
121
|
+
translator = @klass.select { |m| m.name =~ /chris/ }
|
122
|
+
translator.to_s.should == %Q(foo)
|
123
|
+
end
|
124
|
+
|
125
|
+
xspecify "=~ with regexp flags" do
|
126
|
+
translator = @klass.select { |m| m.name =~ /chris/i }
|
127
|
+
translator.to_s.should == %Q(foo)
|
128
|
+
end
|
129
|
+
|
130
|
+
xspecify "downcase" do
|
131
|
+
translator = @klass.select { |m| m.name.downcase =~ 'chris%' }
|
132
|
+
translator.to_s.should == %Q(foo)
|
133
|
+
end
|
134
|
+
|
135
|
+
xspecify "upcase" do
|
136
|
+
translator = @klass.select { |m| m.name.upcase =~ 'chris%' }
|
137
|
+
translator.to_s.should == %Q(foo)
|
138
|
+
end
|
139
|
+
|
140
|
+
xspecify "undefined equality symbol" do
|
141
|
+
should.raise { @klass.select { |m| m.name =* /chris/ } }
|
142
|
+
end
|
143
|
+
|
144
|
+
xspecify "block variable / assigning variable conflict" do
|
145
|
+
m = @klass.select { |m| m.name == 'chris' }
|
146
|
+
m.should == %Q(foo)
|
147
|
+
end
|
148
|
+
|
149
|
+
xspecify "== with inline ruby" do
|
150
|
+
translator = @klass.select { |m| m.created_at == Time.now.to_s }
|
151
|
+
translator.to_s.should == %Q(foo)
|
152
|
+
end
|
153
|
+
|
154
|
+
xspecify "inspect" do
|
155
|
+
@klass.select { |u| u.name }.inspect.should.match %r(call #to_s or #to_hash)
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
context "<%= adapter_module %> Adapter :: Slice" do
|
4
|
+
setup do
|
5
|
+
@klass = User
|
6
|
+
@block = @klass.select { |m| m.name == 'jon' }
|
7
|
+
end
|
8
|
+
|
9
|
+
xspecify "first" do
|
10
|
+
@klass.expects(:find).with(:limit => 1, :name => 'jon')
|
11
|
+
@block.first
|
12
|
+
end
|
13
|
+
|
14
|
+
xspecify "first with argument" do
|
15
|
+
@klass.expects(:find).with(:limit => 5, :name => 'jon')
|
16
|
+
@block.first(5).entries
|
17
|
+
end
|
18
|
+
|
19
|
+
xspecify "[] with two elements" do
|
20
|
+
@klass.expects(:find).with(:limit => 20, :offset => 10, :name => 'jon')
|
21
|
+
@block[10, 20].entries
|
22
|
+
|
23
|
+
@klass.expects(:find).with(:limit => 20, :offset => 20, :name => 'jon')
|
24
|
+
@block[20, 20].entries
|
25
|
+
end
|
26
|
+
|
27
|
+
xspecify "slice is an alias of []" do
|
28
|
+
@klass.expects(:find).with(:limit => 20, :offset => 10, :name => 'jon')
|
29
|
+
@block.slice(10, 20).entries
|
30
|
+
end
|
31
|
+
|
32
|
+
xspecify "[] with range" do
|
33
|
+
@klass.expects(:find).with(:limit => 10, :offset => 10, :name => 'jon')
|
34
|
+
@block[11..20].entries
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
context "<%= adapter_module %> Adapter :: Sort" do
|
4
|
+
setup do
|
5
|
+
@klass = User
|
6
|
+
@block = @klass.select { |m| m.name == 'jon' }
|
7
|
+
end
|
8
|
+
|
9
|
+
xspecify "order" do
|
10
|
+
string = @block.sort_by { |m| m.name }.to_s
|
11
|
+
string.should == "foo"
|
12
|
+
end
|
13
|
+
|
14
|
+
xspecify "combined order" do
|
15
|
+
string = @block.sort_by { |m| [ m.name, m.age ] }.to_s
|
16
|
+
string.should == "foo"
|
17
|
+
end
|
18
|
+
|
19
|
+
xspecify "combined order with single reverse" do
|
20
|
+
string = @block.sort_by { |m| [ m.name, -m.age ] }.to_s
|
21
|
+
string.should == "foo"
|
22
|
+
end
|
23
|
+
|
24
|
+
xspecify "combined order with two reverses" do
|
25
|
+
string = @block.sort_by { |m| [ -m.name, -m.age ] }.to_s
|
26
|
+
string.should == "foo"
|
27
|
+
end
|
28
|
+
|
29
|
+
xspecify "reverse order with -" do
|
30
|
+
string = @block.sort_by { |m| -m.age }.to_s
|
31
|
+
string.should == "foo"
|
32
|
+
end
|
33
|
+
|
34
|
+
xspecify "reverse order with #reverse" do
|
35
|
+
# TODO: not implemented
|
36
|
+
string = @block.sort_by { |m| m.age }.reverse.to_s
|
37
|
+
string.should == "foo"
|
38
|
+
end
|
39
|
+
|
40
|
+
xspecify "random order" do
|
41
|
+
string = @block.sort_by { rand }.to_s
|
42
|
+
string.should == "foo"
|
43
|
+
end
|
44
|
+
|
45
|
+
xspecify "non-existent method to sort by" do
|
46
|
+
should.raise(NoMethodError) { @block.sort_by { foo }.to_s }
|
47
|
+
end
|
48
|
+
|
49
|
+
xspecify "Symbol#to_proc" do
|
50
|
+
string = @klass.sort_by(&:name).to_s
|
51
|
+
string.should == "foo"
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rubigen'
|
4
|
+
|
5
|
+
if %w(-v --version).include? ARGV.first
|
6
|
+
version = File.read('./Rakefile').scan(/Version = '(.+)'/).first.first
|
7
|
+
puts "#{File.basename($0)} #{version}"
|
8
|
+
exit(0)
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'rubigen/scripts/generate'
|
12
|
+
RubiGen::Base.use_application_sources! :ambition_adapter
|
13
|
+
RubiGen::Scripts::Generate.new.run(ARGV, :generator => 'ambition_adapter')
|
data/lib/ambition.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'ambition/enumerable'
|
2
|
+
require 'ambition/api'
|
3
|
+
require 'ambition/context'
|
4
|
+
require 'ambition/core_ext'
|
5
|
+
require 'ambition/sexp_translator'
|
6
|
+
|
7
|
+
require 'ambition/processors/base'
|
8
|
+
require 'ambition/processors/select'
|
9
|
+
require 'ambition/processors/sort'
|
10
|
+
require 'ambition/processors/slice'
|
11
|
+
require 'ambition/processors/ruby'
|
data/lib/ambition/api.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
module Ambition #:nodoc:
|
2
|
+
# Module that you will extend from in your adapters in your toplevel file.
|
3
|
+
#
|
4
|
+
# For example, for ambitious_sphinx in lib/ambition/adapters/ambitious_sphinx.rb, we have:
|
5
|
+
# ActiveRecord::Base.extend Ambition::API
|
6
|
+
module API
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def chain(other_context)
|
10
|
+
other_context.clauses.inject(ambition_context) {|context,(k,v)|
|
11
|
+
context.clauses[k].concat(other_context.clauses[k]).uniq!
|
12
|
+
context
|
13
|
+
} unless other_context.nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Entry methods
|
18
|
+
def select(&block)
|
19
|
+
context = ambition_context
|
20
|
+
context << Processors::Select.new(context, block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def sort_by(&block)
|
24
|
+
context = ambition_context
|
25
|
+
context << Processors::Sort.new(context, block)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Entries that our context is able to find.
|
29
|
+
def entries
|
30
|
+
ambition_context.kick
|
31
|
+
end
|
32
|
+
alias_method :to_a, :entries
|
33
|
+
|
34
|
+
def size
|
35
|
+
context = ambition_context
|
36
|
+
context == self ? super : context.size
|
37
|
+
end
|
38
|
+
|
39
|
+
def slice(start, length = nil)
|
40
|
+
context = ambition_context
|
41
|
+
context << Processors::Slice.new(context, start, length)
|
42
|
+
end
|
43
|
+
alias_method :[], :slice
|
44
|
+
|
45
|
+
##
|
46
|
+
# Convenience methods
|
47
|
+
|
48
|
+
# See Enumerable#detect
|
49
|
+
def detect(&block)
|
50
|
+
select(&block).first
|
51
|
+
end
|
52
|
+
|
53
|
+
# See Array#first
|
54
|
+
def first(count = 1)
|
55
|
+
sliced = slice(0, count)
|
56
|
+
count == 1 ? Array(sliced.kick).first : sliced
|
57
|
+
end
|
58
|
+
|
59
|
+
# See Array#each, applied to +entries+
|
60
|
+
def each(&block)
|
61
|
+
entries.each(&block)
|
62
|
+
end
|
63
|
+
|
64
|
+
# See Enumerable#any?
|
65
|
+
def any?(&block)
|
66
|
+
select(&block).size > 0
|
67
|
+
end
|
68
|
+
|
69
|
+
# See Enumerable#all?
|
70
|
+
def all?(&block)
|
71
|
+
size == select(&block).size
|
72
|
+
end
|
73
|
+
|
74
|
+
# See Array#empty?
|
75
|
+
def empty?
|
76
|
+
size.zero?
|
77
|
+
end
|
78
|
+
|
79
|
+
# Builds a new +Context+.
|
80
|
+
def ambition_context
|
81
|
+
Context.new(self)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Gives you the current ambitious adapter.
|
85
|
+
def ambition_adapter
|
86
|
+
own_name = respond_to?(:name) ? name : self.class.name
|
87
|
+
parent = respond_to?(:superclass) ? superclass : self.class.superclass
|
88
|
+
@@ambition_adapter[own_name] || @@ambition_adapter[parent.name] || infer_ambition_adapter
|
89
|
+
end
|
90
|
+
|
91
|
+
# If we're in a class that doesn't have an adapter assigned, but descends from a class
|
92
|
+
# that does, use that adapter (storing it for next time, since ancestors.include? is
|
93
|
+
# expensive to run constantly).
|
94
|
+
def infer_ambition_adapter
|
95
|
+
famous_ancestor = @@ambition_adapter.keys.detect {|key|
|
96
|
+
ancestors.include?(Object.recursive_const_get(key))
|
97
|
+
}
|
98
|
+
self.ambition_adapter = @@ambition_adapter[famous_ancestor] unless famous_ancestor.nil?
|
99
|
+
end
|
100
|
+
|
101
|
+
# Assign the ambition adapter. Typically, you use this in the toplevel file of your adapter.
|
102
|
+
#
|
103
|
+
# For example, for ambitious_sphinx, in our lib/ambition/adapters/ambitious_sphinx.rb:
|
104
|
+
#
|
105
|
+
# ActiveRecord::Base.ambition_adapter = Ambition::Adapters::AmbitiousSphinx
|
106
|
+
def ambition_adapter=(klass)
|
107
|
+
@@ambition_adapter ||= {}
|
108
|
+
# should this be doing the same check for respond_to?(:name) like above?
|
109
|
+
@@ambition_adapter[name] = klass
|
110
|
+
end
|
111
|
+
|
112
|
+
def ambition_owner
|
113
|
+
@owner || self
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|