ambition 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest +24 -2
- data/Rakefile +1 -1
- data/lib/ambition/database_statements.rb +31 -0
- data/lib/ambition/enumerable.rb +1 -1
- data/lib/ambition/limit.rb +34 -12
- data/lib/ambition/proc_to_ruby.rb +25 -0
- data/lib/ambition/processor.rb +31 -5
- data/lib/ambition/query.rb +7 -2
- data/lib/ambition/where.rb +21 -8
- data/lib/ambition.rb +6 -3
- data/test/console +9 -0
- data/test/databases/boot.rb +3 -0
- data/test/databases/database.yml +17 -0
- data/test/databases/fixtures/admin.rb +3 -0
- data/test/databases/fixtures/companies.yml +24 -0
- data/test/databases/fixtures/company.rb +23 -0
- data/test/databases/fixtures/developer.rb +11 -0
- data/test/databases/fixtures/developers_projects.yml +13 -0
- data/test/databases/fixtures/project.rb +4 -0
- data/test/databases/fixtures/projects.yml +7 -0
- data/test/databases/fixtures/replies.yml +20 -0
- data/test/databases/fixtures/reply.rb +5 -0
- data/test/databases/fixtures/topic.rb +19 -0
- data/test/databases/fixtures/topics.yml +32 -0
- data/test/databases/fixtures/user.rb +2 -0
- data/test/databases/fixtures/users.yml +35 -0
- data/test/databases/lib/activerecord_test_connector.rb +65 -0
- data/test/databases/lib/load_fixtures.rb +13 -0
- data/test/databases/lib/schema.rb +41 -0
- data/test/enumerable_test.rb +3 -3
- data/test/helper.rb +25 -7
- data/test/limit_test.rb +13 -15
- data/test/types_test.rb +6 -1
- data/test/where_test.rb +88 -4
- metadata +26 -4
- data/lib/proc_to_ruby.rb +0 -36
data/Manifest
CHANGED
@@ -1,18 +1,41 @@
|
|
1
1
|
./init.rb
|
2
2
|
./lib/ambition/count.rb
|
3
|
+
./lib/ambition/database_statements.rb
|
3
4
|
./lib/ambition/enumerable.rb
|
4
5
|
./lib/ambition/limit.rb
|
5
6
|
./lib/ambition/order.rb
|
7
|
+
./lib/ambition/proc_to_ruby.rb
|
6
8
|
./lib/ambition/processor.rb
|
7
9
|
./lib/ambition/query.rb
|
8
10
|
./lib/ambition/where.rb
|
9
11
|
./lib/ambition.rb
|
10
|
-
./lib/proc_to_ruby.rb
|
11
12
|
./LICENSE
|
13
|
+
./Manifest
|
12
14
|
./Rakefile
|
13
15
|
./README
|
14
16
|
./test/chaining_test.rb
|
17
|
+
./test/console
|
18
|
+
./test/constructive_test.rb
|
15
19
|
./test/count_test.rb
|
20
|
+
./test/databases/boot.rb
|
21
|
+
./test/databases/database.yml
|
22
|
+
./test/databases/fixtures/admin.rb
|
23
|
+
./test/databases/fixtures/companies.yml
|
24
|
+
./test/databases/fixtures/company.rb
|
25
|
+
./test/databases/fixtures/developer.rb
|
26
|
+
./test/databases/fixtures/developers_projects.yml
|
27
|
+
./test/databases/fixtures/project.rb
|
28
|
+
./test/databases/fixtures/projects.yml
|
29
|
+
./test/databases/fixtures/replies.yml
|
30
|
+
./test/databases/fixtures/reply.rb
|
31
|
+
./test/databases/fixtures/topic.rb
|
32
|
+
./test/databases/fixtures/topics.yml
|
33
|
+
./test/databases/fixtures/user.rb
|
34
|
+
./test/databases/fixtures/users.yml
|
35
|
+
./test/databases/lib/activerecord_test_connector.rb
|
36
|
+
./test/databases/lib/load_fixtures.rb
|
37
|
+
./test/databases/lib/schema.rb
|
38
|
+
./test/destructive_test.rb
|
16
39
|
./test/enumerable_test.rb
|
17
40
|
./test/helper.rb
|
18
41
|
./test/join_test.rb
|
@@ -20,4 +43,3 @@
|
|
20
43
|
./test/order_test.rb
|
21
44
|
./test/types_test.rb
|
22
45
|
./test/where_test.rb
|
23
|
-
./Manifest
|
data/Rakefile
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Ambition
|
2
|
+
module DatabaseStatements
|
3
|
+
def self.const_missing(*args)
|
4
|
+
Abstract
|
5
|
+
end
|
6
|
+
|
7
|
+
class Abstract
|
8
|
+
def regexp(options)
|
9
|
+
'REGEXP'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class PostgreSQL < Abstract
|
14
|
+
def regexp(regexp)
|
15
|
+
if regexp.options == 1
|
16
|
+
'~*'
|
17
|
+
else
|
18
|
+
'~'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def negated_regexp(regexp)
|
23
|
+
if regexp.options == 1
|
24
|
+
'!~*'
|
25
|
+
else
|
26
|
+
'!~'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/ambition/enumerable.rb
CHANGED
data/lib/ambition/limit.rb
CHANGED
@@ -1,27 +1,35 @@
|
|
1
1
|
module Ambition
|
2
2
|
module Limit
|
3
|
-
def first(limit = 1
|
4
|
-
query_context.add LimitProcessor.new(limit
|
5
|
-
|
3
|
+
def first(limit = 1)
|
4
|
+
query_context.add LimitProcessor.new(limit)
|
5
|
+
|
6
|
+
if limit == 1
|
7
|
+
find(:first, query_context.to_hash)
|
8
|
+
else
|
9
|
+
query_context
|
10
|
+
end
|
6
11
|
end
|
7
12
|
|
8
|
-
def
|
9
|
-
return first(offset, limit) if limit
|
13
|
+
def slice(offset, limit = nil)
|
10
14
|
|
11
15
|
if offset.is_a? Range
|
12
16
|
limit = offset.end
|
13
17
|
limit -= 1 if offset.exclude_end?
|
14
|
-
|
15
|
-
|
16
|
-
|
18
|
+
offset = offset.first
|
19
|
+
limit -= offset
|
20
|
+
elsif limit.nil?
|
21
|
+
return find(offset)
|
17
22
|
end
|
23
|
+
|
24
|
+
query_context.add OffsetProcessor.new(offset)
|
25
|
+
query_context.add LimitProcessor.new(limit)
|
18
26
|
end
|
19
|
-
alias_method :
|
27
|
+
alias_method :[], :slice
|
20
28
|
end
|
21
29
|
|
22
30
|
class LimitProcessor
|
23
|
-
def initialize(
|
24
|
-
@
|
31
|
+
def initialize(limit)
|
32
|
+
@limit = limit
|
25
33
|
end
|
26
34
|
|
27
35
|
def key
|
@@ -29,7 +37,21 @@ module Ambition
|
|
29
37
|
end
|
30
38
|
|
31
39
|
def to_s
|
32
|
-
@
|
40
|
+
@limit
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class OffsetProcessor
|
45
|
+
def initialize(offset)
|
46
|
+
@offset = offset
|
47
|
+
end
|
48
|
+
|
49
|
+
def key
|
50
|
+
:offset
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
@offset
|
33
55
|
end
|
34
56
|
end
|
35
57
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
##
|
2
|
+
# Taken from ruby2ruby, Copyright (c) 2006 Ryan Davis under the MIT License
|
3
|
+
require 'parse_tree'
|
4
|
+
require 'sexp_processor'
|
5
|
+
|
6
|
+
class ProcHolder
|
7
|
+
end
|
8
|
+
|
9
|
+
class Method
|
10
|
+
def to_sexp
|
11
|
+
ParseTree.translate(ProcHolder, :proc_to_method)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Proc
|
16
|
+
def to_method
|
17
|
+
ProcHolder.send(:define_method, :proc_to_method, self)
|
18
|
+
ProcHolder.new.method(:proc_to_method)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_sexp
|
22
|
+
body = to_method.to_sexp[2][1..-1]
|
23
|
+
[:proc, *body]
|
24
|
+
end
|
25
|
+
end
|
data/lib/ambition/processor.rb
CHANGED
@@ -45,15 +45,41 @@ module Ambition
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def sanitize(value)
|
48
|
-
|
49
|
-
|
50
|
-
when 'false' then '0'
|
51
|
-
else ActiveRecord::Base.connection.quote(value) rescue quote(value)
|
48
|
+
if value.is_a? Array
|
49
|
+
return value.map { |v| sanitize(v) }.join(', ')
|
52
50
|
end
|
51
|
+
|
52
|
+
case value
|
53
|
+
when true, 'true'
|
54
|
+
'1'
|
55
|
+
when false, 'false'
|
56
|
+
'0'
|
57
|
+
when Regexp
|
58
|
+
"'#{value.source}'"
|
59
|
+
else
|
60
|
+
ActiveRecord::Base.connection.quote(value)
|
61
|
+
end
|
62
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
63
|
+
quote(value)
|
64
|
+
rescue
|
65
|
+
"'#{value}'"
|
53
66
|
end
|
54
67
|
|
55
68
|
def quote_column_name(value)
|
56
|
-
ActiveRecord::Base.connection.quote_column_name(value)
|
69
|
+
ActiveRecord::Base.connection.quote_column_name(value)
|
70
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
71
|
+
value.to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
def statement(*args)
|
75
|
+
@statement_instnace ||= DatabaseStatements.const_get(adapter_name).new
|
76
|
+
@statement_instnace.send(*args)
|
77
|
+
end
|
78
|
+
|
79
|
+
def adapter_name
|
80
|
+
ActiveRecord::Base.connection.adapter_name
|
81
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
82
|
+
'Abstract'
|
57
83
|
end
|
58
84
|
|
59
85
|
def extract_includes(receiver, method)
|
data/lib/ambition/query.rb
CHANGED
@@ -45,7 +45,11 @@ module Ambition
|
|
45
45
|
end
|
46
46
|
|
47
47
|
if limit = keyed[:limit]
|
48
|
-
hash[:limit] = limit.
|
48
|
+
hash[:limit] = limit.last
|
49
|
+
end
|
50
|
+
|
51
|
+
if offset = keyed[:offset]
|
52
|
+
hash[:offset] = offset.last
|
49
53
|
end
|
50
54
|
|
51
55
|
hash
|
@@ -58,7 +62,8 @@ module Ambition
|
|
58
62
|
sql << "JOIN #{hash[:includes].join(', ')}" unless hash[:includes].blank?
|
59
63
|
sql << "WHERE #{hash[:conditions].join(' AND ')}" unless hash[:conditions].blank?
|
60
64
|
sql << "ORDER BY #{hash[:order].join(', ')}" unless hash[:order].blank?
|
61
|
-
sql << "LIMIT #{hash[:limit]
|
65
|
+
sql << "LIMIT #{hash[:limit]}" unless hash[:limit].blank?
|
66
|
+
sql << "OFFSET #{hash[:limit]}" unless hash[:offset].blank?
|
62
67
|
|
63
68
|
@@select % [ @table_name, sql.join(' ') ]
|
64
69
|
end
|
data/lib/ambition/where.rb
CHANGED
@@ -30,9 +30,16 @@ module Ambition
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def process_not(exp)
|
33
|
-
|
33
|
+
type, receiver, method, other = *exp.first
|
34
34
|
exp.clear
|
35
|
-
|
35
|
+
|
36
|
+
case type
|
37
|
+
when :call
|
38
|
+
translation(receiver, negate(method, other), other)
|
39
|
+
when :match3
|
40
|
+
regexp = receiver.last
|
41
|
+
"#{process(method)} #{statement(:negated_regexp, regexp)} #{sanitize(regexp)}"
|
42
|
+
end
|
36
43
|
end
|
37
44
|
|
38
45
|
def process_call(exp)
|
@@ -63,8 +70,8 @@ module Ambition
|
|
63
70
|
end
|
64
71
|
|
65
72
|
def process_match3(exp)
|
66
|
-
regexp, target = exp.shift.last
|
67
|
-
"#{target}
|
73
|
+
regexp, target = exp.shift.last, process(exp.shift)
|
74
|
+
"#{target} #{statement(:regexp, regexp)} #{sanitize(regexp)}"
|
68
75
|
end
|
69
76
|
|
70
77
|
def process_dvar(exp)
|
@@ -111,23 +118,29 @@ module Ambition
|
|
111
118
|
sanitize eval(variable, @block)
|
112
119
|
end
|
113
120
|
|
114
|
-
def negate(method)
|
121
|
+
def negate(method, target = nil)
|
122
|
+
if Array(target).last == [:nil]
|
123
|
+
return 'IS NOT'
|
124
|
+
end
|
125
|
+
|
115
126
|
case method
|
116
127
|
when :==
|
117
128
|
'<>'
|
118
129
|
when :=~
|
119
130
|
'!~'
|
120
131
|
else
|
121
|
-
raise "Not implemented: #{method}"
|
132
|
+
raise "Not implemented: #{method.inspect}"
|
122
133
|
end
|
123
134
|
end
|
124
135
|
|
125
|
-
def translation(receiver, method, other)
|
136
|
+
def translation(receiver, method, other = nil)
|
126
137
|
case method.to_s
|
138
|
+
when 'IS NOT'
|
139
|
+
"#{process(receiver)} IS NOT #{process(other)}"
|
127
140
|
when '=='
|
128
141
|
case other_value = process(other)
|
129
142
|
when "NULL"
|
130
|
-
"#{process(receiver)}
|
143
|
+
"#{process(receiver)} IS #{other_value}"
|
131
144
|
else
|
132
145
|
"#{process(receiver)} = #{other_value}"
|
133
146
|
end
|
data/lib/ambition.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
2
|
-
require '
|
3
|
-
require '
|
1
|
+
unless defined? ActiveRecord
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_record'
|
4
|
+
end
|
5
|
+
require 'ambition/proc_to_ruby'
|
4
6
|
require 'ambition/processor'
|
5
7
|
require 'ambition/query'
|
6
8
|
require 'ambition/where'
|
@@ -8,6 +10,7 @@ require 'ambition/order'
|
|
8
10
|
require 'ambition/limit'
|
9
11
|
require 'ambition/count'
|
10
12
|
require 'ambition/enumerable'
|
13
|
+
require 'ambition/database_statements'
|
11
14
|
|
12
15
|
module Ambition
|
13
16
|
include Where, Order, Limit, Enumerable, Count
|
data/test/console
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
3
|
+
libs = []
|
4
|
+
dirname = File.dirname(__FILE__)
|
5
|
+
|
6
|
+
libs << 'irb/completion'
|
7
|
+
libs << File.join(dirname, 'databases', 'boot')
|
8
|
+
|
9
|
+
exec "#{irb} #{libs.map{|l| " -r #{l}" }.join} --simple-prompt"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
sqlite3:
|
2
|
+
adapter: sqlite3
|
3
|
+
database: db/development.sqlite3
|
4
|
+
|
5
|
+
mysql:
|
6
|
+
adapter: mysql
|
7
|
+
database: ambition_development
|
8
|
+
username: root
|
9
|
+
password:
|
10
|
+
host: localhost
|
11
|
+
|
12
|
+
postgres:
|
13
|
+
adapter: postgresql
|
14
|
+
database: ambition_development
|
15
|
+
username: root
|
16
|
+
password:
|
17
|
+
host: localhost
|
@@ -0,0 +1,24 @@
|
|
1
|
+
thirty_seven_signals:
|
2
|
+
id: 1
|
3
|
+
name: 37Signals
|
4
|
+
rating: 4
|
5
|
+
|
6
|
+
TextDrive:
|
7
|
+
id: 2
|
8
|
+
name: TextDrive
|
9
|
+
rating: 3
|
10
|
+
|
11
|
+
PlanetArgon:
|
12
|
+
id: 3
|
13
|
+
name: Planet Argon
|
14
|
+
rating: 3
|
15
|
+
|
16
|
+
Google:
|
17
|
+
id: 4
|
18
|
+
name: Google
|
19
|
+
rating: 5
|
20
|
+
|
21
|
+
Ionist:
|
22
|
+
id: 5
|
23
|
+
name: Ioni.st
|
24
|
+
rating: 4
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Company < ActiveRecord::Base
|
2
|
+
attr_protected :rating
|
3
|
+
set_sequence_name :companies_nonstd_seq
|
4
|
+
|
5
|
+
validates_presence_of :name
|
6
|
+
def validate
|
7
|
+
errors.add('rating', 'rating should not be 2') if rating == 2
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.with_best
|
11
|
+
with_scope :find => { :conditions => ['companies.rating > ?', 3] } do
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.find_best(*args)
|
17
|
+
with_best { find(*args) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.calculate_best(*args)
|
21
|
+
with_best { calculate(*args) }
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Developer < User
|
2
|
+
has_and_belongs_to_many :projects, :include => :topics, :order => 'projects.name'
|
3
|
+
|
4
|
+
def self.with_poor_ones(&block)
|
5
|
+
with_scope :find => { :conditions => ['salary <= ?', 80000], :order => 'salary' } do
|
6
|
+
yield
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.per_page() 10 end
|
11
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
witty_retort:
|
2
|
+
id: 1
|
3
|
+
topic_id: 1
|
4
|
+
content: Birdman is better!
|
5
|
+
created_at: <%= 6.hours.ago.to_s(:db) %>
|
6
|
+
updated_at: nil
|
7
|
+
|
8
|
+
another:
|
9
|
+
id: 2
|
10
|
+
topic_id: 2
|
11
|
+
content: Nuh uh!
|
12
|
+
created_at: <%= 1.hour.ago.to_s(:db) %>
|
13
|
+
updated_at: nil
|
14
|
+
|
15
|
+
spam:
|
16
|
+
id: 3
|
17
|
+
topic_id: 1
|
18
|
+
content: Nice site!
|
19
|
+
created_at: <%= 1.hour.ago.to_s(:db) %>
|
20
|
+
updated_at: nil
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Topic < ActiveRecord::Base
|
2
|
+
has_many :replies, :dependent => :destroy, :order => 'replies.created_at DESC'
|
3
|
+
belongs_to :project
|
4
|
+
|
5
|
+
# pretend find and count were extended and accept an extra option
|
6
|
+
# if there is a :foo option, prepend its value to collection
|
7
|
+
def self.find(*args)
|
8
|
+
more = []
|
9
|
+
more << args.last.delete(:foo) if args.last.is_a?(Hash) and args.last[:foo]
|
10
|
+
res = super
|
11
|
+
more.empty?? res : more + res
|
12
|
+
end
|
13
|
+
|
14
|
+
# if there is a :foo option, always return 100
|
15
|
+
def self.count(*args)
|
16
|
+
return 100 if args.last.is_a?(Hash) and args.last[:foo]
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
futurama:
|
2
|
+
id: 1
|
3
|
+
title: Isnt futurama awesome?
|
4
|
+
subtitle: It really is, isnt it.
|
5
|
+
content: I like futurama
|
6
|
+
created_at: <%= 1.day.ago.to_s(:db) %>
|
7
|
+
updated_at: <%= 1.day.ago.to_s(:db) %>
|
8
|
+
|
9
|
+
harvey_birdman:
|
10
|
+
id: 2
|
11
|
+
title: Harvey Birdman is the king of all men
|
12
|
+
subtitle: yup
|
13
|
+
content: He really is
|
14
|
+
created_at: <%= 2.hours.ago.to_s(:db) %>
|
15
|
+
updated_at: <%= 2.hours.ago.to_s(:db) %>
|
16
|
+
|
17
|
+
rails:
|
18
|
+
id: 3
|
19
|
+
project_id: 1
|
20
|
+
title: Rails is nice
|
21
|
+
subtitle: It makes me happy
|
22
|
+
content: except when I have to hack internals to fix pagination. even then really.
|
23
|
+
created_at: <%= 20.minutes.ago.to_s(:db) %>
|
24
|
+
updated_at: <%= 20.minutes.ago.to_s(:db) %>
|
25
|
+
|
26
|
+
ar:
|
27
|
+
id: 4
|
28
|
+
project_id: 1
|
29
|
+
title: ActiveRecord sometimes freaks me out
|
30
|
+
content: "I mean, what's the deal with eager loading?"
|
31
|
+
created_at: <%= 15.minutes.ago.to_s(:db) %>
|
32
|
+
updated_at: <%= 15.minutes.ago.to_s(:db) %>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
david:
|
2
|
+
id: 1
|
3
|
+
name: David
|
4
|
+
salary: 80000
|
5
|
+
type: Developer
|
6
|
+
|
7
|
+
jamis:
|
8
|
+
id: 2
|
9
|
+
name: Jamis
|
10
|
+
salary: 150000
|
11
|
+
type: Developer
|
12
|
+
|
13
|
+
<% for digit in 3..10 %>
|
14
|
+
dev_<%= digit %>:
|
15
|
+
id: <%= digit %>
|
16
|
+
name: fixture_<%= digit %>
|
17
|
+
salary: 100000
|
18
|
+
type: Developer
|
19
|
+
<% end %>
|
20
|
+
|
21
|
+
poor_jamis:
|
22
|
+
id: 11
|
23
|
+
name: Jamis
|
24
|
+
salary: 9000
|
25
|
+
type: Developer
|
26
|
+
|
27
|
+
admin:
|
28
|
+
id: 12
|
29
|
+
name: admin
|
30
|
+
type: Admin
|
31
|
+
|
32
|
+
goofy:
|
33
|
+
id: 13
|
34
|
+
name: Goofy
|
35
|
+
type: Admin
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class ActiveRecordTestConnector
|
2
|
+
cattr_accessor :able_to_connect
|
3
|
+
cattr_accessor :connected
|
4
|
+
|
5
|
+
# Set our defaults
|
6
|
+
self.connected = false
|
7
|
+
self.able_to_connect = true
|
8
|
+
|
9
|
+
def self.setup
|
10
|
+
unless connected || !able_to_connect
|
11
|
+
setup_connection
|
12
|
+
load_schema
|
13
|
+
require_fixture_models
|
14
|
+
self.connected = true
|
15
|
+
end
|
16
|
+
rescue Exception => e # errors from ActiveRecord setup
|
17
|
+
if e.to_s =~ /unknown database/i
|
18
|
+
puts "\nPlease create an `ambition_development' database to play!"
|
19
|
+
else
|
20
|
+
$stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}"
|
21
|
+
end
|
22
|
+
self.able_to_connect = false
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.setup_connection
|
28
|
+
if Object.const_defined?(:ActiveRecord)
|
29
|
+
config_file = File.dirname(__FILE__) + '/../database.yml'
|
30
|
+
ActiveRecord::Base.logger = Logger.new STDOUT
|
31
|
+
|
32
|
+
case adapter = ENV['ADAPTER'] || 'mysql'
|
33
|
+
when 'sqlite3'
|
34
|
+
options = { :database => ':memory:', :adapter => 'sqlite3', :timeout => 500 }
|
35
|
+
ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options }
|
36
|
+
else
|
37
|
+
options = YAML.load_file(config_file)[adapter]
|
38
|
+
end
|
39
|
+
|
40
|
+
puts "Using #{adapter}"
|
41
|
+
|
42
|
+
ActiveRecord::Base.establish_connection(options)
|
43
|
+
ActiveRecord::Base.connection
|
44
|
+
|
45
|
+
unless Object.const_defined?(:QUOTED_TYPE)
|
46
|
+
Object.send :const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')
|
47
|
+
end
|
48
|
+
else
|
49
|
+
raise "Can't setup connection since ActiveRecord isn't loaded."
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Load actionpack sqlite tables
|
54
|
+
def self.load_schema
|
55
|
+
ActiveRecord::Base.silence do
|
56
|
+
load File.dirname(__FILE__) + "/schema.rb"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.require_fixture_models
|
61
|
+
models = Dir.glob(File.dirname(__FILE__) + "/../fixtures/*.rb")
|
62
|
+
models = (models.grep(/user.rb/) + models).uniq
|
63
|
+
models.each {|f| require f}
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
dirname = File.dirname(__FILE__)
|
2
|
+
%w(rubygems active_record active_record/version active_record/fixtures).each {|f| require f}
|
3
|
+
puts " (ActiveRecord v#{ActiveRecord::VERSION::STRING})"
|
4
|
+
require File.join(dirname, 'activerecord_test_connector')
|
5
|
+
|
6
|
+
# setup the connection
|
7
|
+
ActiveRecordTestConnector.setup
|
8
|
+
|
9
|
+
# load all fixtures
|
10
|
+
fixture_path = File.join(dirname, '..', 'fixtures')
|
11
|
+
Fixtures.create_fixtures(fixture_path, tables = ActiveRecord::Base.connection.tables - %w(schema_info))
|
12
|
+
|
13
|
+
puts "Available models: #{Dir[fixture_path+'/*.rb'].map{|f|File.basename(f,'.rb')}.map(&:classify).to_sentence}"
|
@@ -0,0 +1,41 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 0) do
|
2
|
+
create_table :users, :force => true do |t|
|
3
|
+
t.column :name, :string
|
4
|
+
t.column :type, :string
|
5
|
+
t.column :salary, :integer, :default => 70_000
|
6
|
+
t.column :created_at, :datetime, :null => false
|
7
|
+
t.column :updated_at, :datetime, :null => false
|
8
|
+
end
|
9
|
+
|
10
|
+
create_table :replies, :force => true do |t|
|
11
|
+
t.column :content, :string
|
12
|
+
t.column :topic_id, :integer
|
13
|
+
t.column :created_at, :datetime, :null => false
|
14
|
+
t.column :updated_at, :datetime, :null => false
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table :topics, :force => true do |t|
|
18
|
+
t.column :project_id, :integer
|
19
|
+
t.column :title, :string
|
20
|
+
t.column :subtitle, :string
|
21
|
+
t.column :content, :text
|
22
|
+
t.column :created_at, :datetime, :null => false
|
23
|
+
t.column :updated_at, :datetime, :null => false
|
24
|
+
end
|
25
|
+
|
26
|
+
create_table :projects, :force => true do |t|
|
27
|
+
t.column :name, :string
|
28
|
+
end
|
29
|
+
|
30
|
+
create_table :developers_projects, :force => true do |t|
|
31
|
+
t.column :developer_id, :integer
|
32
|
+
t.column :project_id, :integer
|
33
|
+
t.column :joined_on, :datetime
|
34
|
+
t.column :access_level, :integer, :default => 1
|
35
|
+
end
|
36
|
+
|
37
|
+
create_table :companies, :force => true do |t|
|
38
|
+
t.column :name, :string
|
39
|
+
t.column :rating, :integer
|
40
|
+
end
|
41
|
+
end
|
data/test/enumerable_test.rb
CHANGED
@@ -10,7 +10,7 @@ context "Each" do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
specify "limit and conditions" do
|
13
|
-
hash = { :limit =>
|
13
|
+
hash = { :limit => 5, :conditions => "users.age = 21" }
|
14
14
|
User.expects(:find).with(:all, hash).returns([])
|
15
15
|
User.select { |m| m.age == 21 }.first(5).each do |user|
|
16
16
|
puts user.name
|
@@ -18,7 +18,7 @@ context "Each" do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
specify "limit and conditions and order" do
|
21
|
-
hash = { :limit =>
|
21
|
+
hash = { :limit => 5, :conditions => "users.age = 21", :order => 'users.name' }
|
22
22
|
User.expects(:find).with(:all, hash).returns([])
|
23
23
|
User.select { |m| m.age == 21 }.sort_by { |m| m.name }.first(5).each do |user|
|
24
24
|
puts user.name
|
@@ -26,7 +26,7 @@ context "Each" do
|
|
26
26
|
end
|
27
27
|
|
28
28
|
specify "limit and order" do
|
29
|
-
hash = { :limit =>
|
29
|
+
hash = { :limit => 5, :order => 'users.name' }
|
30
30
|
User.expects(:find).with(:all, hash).returns([])
|
31
31
|
User.sort_by { |m| m.name }.first(5).each do |user|
|
32
32
|
puts user.name
|
data/test/helper.rb
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
begin
|
3
|
-
require 'test/spec'
|
3
|
+
require 'test/spec' unless $NO_TEST
|
4
4
|
require 'mocha'
|
5
|
-
require 'active_support'
|
6
5
|
rescue LoadError
|
7
|
-
puts "=> You need the test-spec
|
6
|
+
puts "=> You need the test-spec and mocha gems to run these tests."
|
8
7
|
exit
|
9
8
|
end
|
9
|
+
require 'active_support'
|
10
10
|
require 'active_record'
|
11
11
|
|
12
|
-
begin require 'redgreen'; rescue LoadError; end
|
12
|
+
begin require 'redgreen'; rescue LoadError; end unless $NO_TEST
|
13
13
|
|
14
14
|
$:.unshift File.dirname(__FILE__) + '/../lib'
|
15
15
|
require 'ambition'
|
16
16
|
|
17
|
-
class User
|
18
|
-
extend Ambition
|
19
|
-
|
17
|
+
class User < ActiveRecord::Base
|
20
18
|
def self.reflections
|
21
19
|
return @reflections if @reflections
|
22
20
|
@reflections = {}
|
@@ -34,3 +32,23 @@ end
|
|
34
32
|
|
35
33
|
class Reflection < Struct.new(:macro, :primary_key_name, :name, :table_name)
|
36
34
|
end
|
35
|
+
|
36
|
+
module ActiveRecord
|
37
|
+
module ConnectionAdapters
|
38
|
+
class MysqlAdapter
|
39
|
+
def connect(*args)
|
40
|
+
true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class PostgreSQLAdapter
|
45
|
+
def connect(*args)
|
46
|
+
true
|
47
|
+
end
|
48
|
+
class PGError; end
|
49
|
+
end
|
50
|
+
|
51
|
+
class FakeAdapter < AbstractAdapter
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/test/limit_test.rb
CHANGED
@@ -6,38 +6,36 @@ context "Limit" do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
specify "first" do
|
9
|
-
conditions = { :conditions => "users.name = 'jon'", :limit =>
|
9
|
+
conditions = { :conditions => "users.name = 'jon'", :limit => 1 }
|
10
10
|
User.expects(:find).with(:first, conditions)
|
11
11
|
@sql.first
|
12
12
|
end
|
13
13
|
|
14
14
|
specify "first with argument" do
|
15
|
-
conditions = { :conditions => "users.name = 'jon'", :limit =>
|
15
|
+
conditions = { :conditions => "users.name = 'jon'", :limit => 5 }
|
16
16
|
User.expects(:find).with(:all, conditions)
|
17
|
-
@sql.first(5)
|
18
|
-
end
|
19
|
-
|
20
|
-
specify "[] with one element" do
|
21
|
-
conditions = { :conditions => "users.name = 'jon'", :limit => '10, 1' }
|
22
|
-
User.expects(:find).with(:all, conditions)
|
23
|
-
@sql[10]
|
17
|
+
@sql.first(5).entries
|
24
18
|
end
|
25
19
|
|
26
20
|
specify "[] with two elements" do
|
27
|
-
conditions = { :conditions => "users.name = 'jon'", :limit =>
|
21
|
+
conditions = { :conditions => "users.name = 'jon'", :limit => 20, :offset => 10 }
|
22
|
+
User.expects(:find).with(:all, conditions)
|
23
|
+
@sql[10, 20].entries
|
24
|
+
|
25
|
+
conditions = { :conditions => "users.name = 'jon'", :limit => 20, :offset => 20 }
|
28
26
|
User.expects(:find).with(:all, conditions)
|
29
|
-
@sql[
|
27
|
+
@sql[20, 20].entries
|
30
28
|
end
|
31
29
|
|
32
30
|
specify "slice is an alias of []" do
|
33
|
-
conditions = { :conditions => "users.name = 'jon'", :limit =>
|
31
|
+
conditions = { :conditions => "users.name = 'jon'", :limit => 20, :offset => 10 }
|
34
32
|
User.expects(:find).with(:all, conditions)
|
35
|
-
@sql.slice(10, 20)
|
33
|
+
@sql.slice(10, 20).entries
|
36
34
|
end
|
37
35
|
|
38
36
|
specify "[] with range" do
|
39
|
-
conditions = { :conditions => "users.name = 'jon'", :limit =>
|
37
|
+
conditions = { :conditions => "users.name = 'jon'", :limit => 10, :offset => 10 }
|
40
38
|
User.expects(:find).with(:all, conditions)
|
41
|
-
@sql[10..20]
|
39
|
+
@sql[10..20].entries
|
42
40
|
end
|
43
41
|
end
|
data/test/types_test.rb
CHANGED
@@ -44,7 +44,12 @@ context "Different types" do
|
|
44
44
|
|
45
45
|
specify "nil" do
|
46
46
|
sql = User.select { |m| m.name == nil }.to_sql
|
47
|
-
sql.should == "SELECT * FROM users WHERE users.name
|
47
|
+
sql.should == "SELECT * FROM users WHERE users.name IS NULL"
|
48
|
+
end
|
49
|
+
|
50
|
+
specify "not nil" do
|
51
|
+
sql = User.select { |m| m.name != nil }.to_sql
|
52
|
+
sql.should == "SELECT * FROM users WHERE users.name IS NOT NULL"
|
48
53
|
end
|
49
54
|
|
50
55
|
xspecify "Time" do
|
data/test/where_test.rb
CHANGED
@@ -47,6 +47,12 @@ context "Where (using select)" do
|
|
47
47
|
sql.should == "SELECT * FROM users WHERE users.id IN (1, 2, 3, 4)"
|
48
48
|
end
|
49
49
|
|
50
|
+
specify "variable'd array.include? item" do
|
51
|
+
array = [1, 2, 3, 4]
|
52
|
+
sql = User.select { |m| array.include? m.id }.to_sql
|
53
|
+
sql.should == "SELECT * FROM users WHERE users.id IN (1, 2, 3, 4)"
|
54
|
+
end
|
55
|
+
|
50
56
|
specify "simple == with variables" do
|
51
57
|
me = 'chris'
|
52
58
|
sql = User.select { |m| m.name == me }.to_sql
|
@@ -144,14 +150,92 @@ end
|
|
144
150
|
|
145
151
|
context "Where (using detect)" do
|
146
152
|
specify "simple ==" do
|
147
|
-
|
148
|
-
User.expects(:find).with(:first, conditions)
|
153
|
+
User.expects(:select).returns(mock(:first => true))
|
149
154
|
User.detect { |m| m.name == 'chris' }
|
150
155
|
end
|
151
156
|
|
152
157
|
specify "nothing found" do
|
153
|
-
|
154
|
-
User.expects(:find).with(:first, conditions).returns(nil)
|
158
|
+
User.expects(:select).returns(mock(:first => nil))
|
155
159
|
User.detect { |m| m.name == 'chris' }.should.be.nil
|
156
160
|
end
|
157
161
|
end
|
162
|
+
|
163
|
+
context "Where (using [])" do
|
164
|
+
specify "finds a single row" do
|
165
|
+
User.expects(:find).with(1)
|
166
|
+
User[1]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context "PostgreSQL specific" do
|
171
|
+
setup do
|
172
|
+
ActiveRecord::Base.connection = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.new 'fake_connection', 'fake_logger'
|
173
|
+
end
|
174
|
+
|
175
|
+
teardown do
|
176
|
+
ActiveRecord::Base.remove_connection
|
177
|
+
end
|
178
|
+
|
179
|
+
specify "quoting of column name" do
|
180
|
+
me = 'chris'
|
181
|
+
sql = User.select { |m| m.name == me }.to_sql
|
182
|
+
sql.should == %(SELECT * FROM users WHERE users."name" = '#{me}')
|
183
|
+
end
|
184
|
+
|
185
|
+
specify "simple =~ with regexp" do
|
186
|
+
sql = User.select { |m| m.name =~ /chris/ }.to_sql
|
187
|
+
sql.should == %(SELECT * FROM users WHERE users."name" ~ 'chris')
|
188
|
+
end
|
189
|
+
|
190
|
+
specify "insensitive =~" do
|
191
|
+
sql = User.select { |m| m.name =~ /chris/i }.to_sql
|
192
|
+
sql.should == %(SELECT * FROM users WHERE users."name" ~* 'chris')
|
193
|
+
end
|
194
|
+
|
195
|
+
specify "negated =~" do
|
196
|
+
sql = User.select { |m| m.name !~ /chris/ }.to_sql
|
197
|
+
sql.should == %(SELECT * FROM users WHERE users."name" !~ 'chris')
|
198
|
+
end
|
199
|
+
|
200
|
+
specify "negated insensitive =~" do
|
201
|
+
sql = User.select { |m| m.name !~ /chris/i }.to_sql
|
202
|
+
sql.should == %(SELECT * FROM users WHERE users."name" !~* 'chris')
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context "MySQL specific" do
|
207
|
+
setup do
|
208
|
+
ActiveRecord::Base.connection = ActiveRecord::ConnectionAdapters::MysqlAdapter.new('connection', 'logger', 'options', 'config')
|
209
|
+
end
|
210
|
+
|
211
|
+
teardown do
|
212
|
+
ActiveRecord::Base.remove_connection
|
213
|
+
end
|
214
|
+
|
215
|
+
specify "quoting of column name" do
|
216
|
+
me = 'chris'
|
217
|
+
sql = User.select { |m| m.name == me }.to_sql
|
218
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = '#{me}'"
|
219
|
+
end
|
220
|
+
|
221
|
+
specify "simple =~ with regexp" do
|
222
|
+
sql = User.select { |m| m.name =~ /chris/ }.to_sql
|
223
|
+
sql.should == "SELECT * FROM users WHERE users.`name` REGEXP 'chris'"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context "Adapter without overrides" do
|
228
|
+
setup do
|
229
|
+
ActiveRecord::Base.connection = ActiveRecord::ConnectionAdapters::FakeAdapter.new('connection', 'logger')
|
230
|
+
end
|
231
|
+
|
232
|
+
teardown do
|
233
|
+
ActiveRecord::Base.remove_connection
|
234
|
+
end
|
235
|
+
|
236
|
+
specify "quoting of column name" do
|
237
|
+
me = 'chris'
|
238
|
+
sql = User.select { |m| m.name == me }.to_sql
|
239
|
+
sql.should == "SELECT * FROM users WHERE users.name = '#{me}'"
|
240
|
+
end
|
241
|
+
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: ambition
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.1.6
|
7
|
+
date: 2007-09-02 00:00:00 -07:00
|
8
8
|
summary: Ambition builds SQL from plain jane Ruby.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -31,19 +31,42 @@ authors:
|
|
31
31
|
files:
|
32
32
|
- ./init.rb
|
33
33
|
- ./lib/ambition/count.rb
|
34
|
+
- ./lib/ambition/database_statements.rb
|
34
35
|
- ./lib/ambition/enumerable.rb
|
35
36
|
- ./lib/ambition/limit.rb
|
36
37
|
- ./lib/ambition/order.rb
|
38
|
+
- ./lib/ambition/proc_to_ruby.rb
|
37
39
|
- ./lib/ambition/processor.rb
|
38
40
|
- ./lib/ambition/query.rb
|
39
41
|
- ./lib/ambition/where.rb
|
40
42
|
- ./lib/ambition.rb
|
41
|
-
- ./lib/proc_to_ruby.rb
|
42
43
|
- ./LICENSE
|
44
|
+
- ./Manifest
|
43
45
|
- ./Rakefile
|
44
46
|
- ./README
|
45
47
|
- ./test/chaining_test.rb
|
48
|
+
- ./test/console
|
49
|
+
- ./test/constructive_test.rb
|
46
50
|
- ./test/count_test.rb
|
51
|
+
- ./test/databases/boot.rb
|
52
|
+
- ./test/databases/database.yml
|
53
|
+
- ./test/databases/fixtures/admin.rb
|
54
|
+
- ./test/databases/fixtures/companies.yml
|
55
|
+
- ./test/databases/fixtures/company.rb
|
56
|
+
- ./test/databases/fixtures/developer.rb
|
57
|
+
- ./test/databases/fixtures/developers_projects.yml
|
58
|
+
- ./test/databases/fixtures/project.rb
|
59
|
+
- ./test/databases/fixtures/projects.yml
|
60
|
+
- ./test/databases/fixtures/replies.yml
|
61
|
+
- ./test/databases/fixtures/reply.rb
|
62
|
+
- ./test/databases/fixtures/topic.rb
|
63
|
+
- ./test/databases/fixtures/topics.yml
|
64
|
+
- ./test/databases/fixtures/user.rb
|
65
|
+
- ./test/databases/fixtures/users.yml
|
66
|
+
- ./test/databases/lib/activerecord_test_connector.rb
|
67
|
+
- ./test/databases/lib/load_fixtures.rb
|
68
|
+
- ./test/databases/lib/schema.rb
|
69
|
+
- ./test/destructive_test.rb
|
47
70
|
- ./test/enumerable_test.rb
|
48
71
|
- ./test/helper.rb
|
49
72
|
- ./test/join_test.rb
|
@@ -51,7 +74,6 @@ files:
|
|
51
74
|
- ./test/order_test.rb
|
52
75
|
- ./test/types_test.rb
|
53
76
|
- ./test/where_test.rb
|
54
|
-
- ./Manifest
|
55
77
|
test_files:
|
56
78
|
- test/chaining_test.rb
|
57
79
|
- test/constructive_test.rb
|
data/lib/proc_to_ruby.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
##
|
2
|
-
# Taken from ruby2ruby, Copyright (c) 2006 Ryan Davis under the MIT License
|
3
|
-
require 'parse_tree'
|
4
|
-
require 'unique'
|
5
|
-
require 'sexp_processor'
|
6
|
-
|
7
|
-
class Method
|
8
|
-
def with_class_and_method_name
|
9
|
-
if self.inspect =~ /<Method: (.*)\#(.*)>/ then
|
10
|
-
klass = eval $1
|
11
|
-
method = $2.intern
|
12
|
-
raise "Couldn't determine class from #{self.inspect}" if klass.nil?
|
13
|
-
return yield(klass, method)
|
14
|
-
else
|
15
|
-
raise "Can't parse signature: #{self.inspect}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def to_sexp
|
20
|
-
with_class_and_method_name do |klass, method|
|
21
|
-
ParseTree.new(false).parse_tree_for_method(klass, method)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
class Proc
|
27
|
-
def to_method
|
28
|
-
Unique.send(:define_method, :proc_to_method, self)
|
29
|
-
Unique.new.method(:proc_to_method)
|
30
|
-
end
|
31
|
-
|
32
|
-
def to_sexp
|
33
|
-
body = self.to_method.to_sexp[2][1..-1]
|
34
|
-
[:proc, *body]
|
35
|
-
end
|
36
|
-
end
|