ambition 0.3.1 → 0.5.0

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.
Files changed (83) hide show
  1. data/Manifest +41 -48
  2. data/README +6 -203
  3. data/ambition.gemspec +111 -0
  4. data/app_generators/ambition_adapter/USAGE +1 -0
  5. data/app_generators/ambition_adapter/ambition_adapter_generator.rb +66 -0
  6. data/app_generators/ambition_adapter/templates/LICENSE +18 -0
  7. data/app_generators/ambition_adapter/templates/README +6 -0
  8. data/app_generators/ambition_adapter/templates/Rakefile +31 -0
  9. data/app_generators/ambition_adapter/templates/lib/base.rb.erb +12 -0
  10. data/app_generators/ambition_adapter/templates/lib/init.rb.erb +22 -0
  11. data/app_generators/ambition_adapter/templates/lib/query.rb.erb +52 -0
  12. data/app_generators/ambition_adapter/templates/lib/select.rb.erb +100 -0
  13. data/app_generators/ambition_adapter/templates/lib/slice.rb.erb +19 -0
  14. data/app_generators/ambition_adapter/templates/lib/sort.rb.erb +43 -0
  15. data/app_generators/ambition_adapter/templates/test/helper.rb.erb +9 -0
  16. data/app_generators/ambition_adapter/templates/test/select_test.rb.erb +157 -0
  17. data/app_generators/ambition_adapter/templates/test/slice_test.rb.erb +36 -0
  18. data/app_generators/ambition_adapter/templates/test/sort_test.rb.erb +53 -0
  19. data/bin/ambition_adapter +13 -0
  20. data/lib/ambition.rb +8 -13
  21. data/lib/ambition/api.rb +42 -35
  22. data/lib/ambition/context.rb +62 -0
  23. data/lib/ambition/{proc_to_ruby.rb → core_ext.rb} +13 -0
  24. data/lib/ambition/enumerable.rb +6 -0
  25. data/lib/ambition/processors/base.rb +126 -0
  26. data/lib/ambition/processors/ruby.rb +24 -0
  27. data/lib/ambition/processors/select.rb +105 -0
  28. data/lib/ambition/processors/slice.rb +15 -0
  29. data/lib/ambition/processors/sort.rb +51 -0
  30. data/test/adapters/exemplar/association_test.rb +34 -0
  31. data/test/adapters/exemplar/count_test.rb +0 -0
  32. data/test/adapters/exemplar/detect_test.rb +9 -0
  33. data/test/adapters/exemplar/enumerable_test.rb +0 -0
  34. data/test/adapters/exemplar/helper.rb +3 -0
  35. data/test/adapters/exemplar/index_operator.rb +6 -0
  36. data/test/adapters/exemplar/reject_test.rb +0 -0
  37. data/test/adapters/exemplar/select_test.rb +151 -0
  38. data/test/adapters/exemplar/slice_test.rb +0 -0
  39. data/test/adapters/exemplar/sort_test.rb +0 -0
  40. data/test/debug +9 -0
  41. data/test/helper.rb +2 -52
  42. metadata +56 -71
  43. data/Rakefile +0 -64
  44. data/init.rb +0 -1
  45. data/lib/ambition/database_statements.rb +0 -31
  46. data/lib/ambition/processor.rb +0 -123
  47. data/lib/ambition/query.rb +0 -91
  48. data/lib/ambition/ruby_processor.rb +0 -22
  49. data/lib/ambition/select_processor.rb +0 -149
  50. data/lib/ambition/simple_processor.rb +0 -10
  51. data/lib/ambition/sort_processor.rb +0 -47
  52. data/lib/ambition/source.rb +0 -53
  53. data/test/benchmark.rb +0 -68
  54. data/test/chaining_test.rb +0 -34
  55. data/test/console +0 -9
  56. data/test/count_test.rb +0 -17
  57. data/test/databases/boot.rb +0 -3
  58. data/test/databases/database.yml +0 -17
  59. data/test/databases/fixtures/admin.rb +0 -3
  60. data/test/databases/fixtures/companies.yml +0 -24
  61. data/test/databases/fixtures/company.rb +0 -23
  62. data/test/databases/fixtures/developer.rb +0 -11
  63. data/test/databases/fixtures/developers_projects.yml +0 -13
  64. data/test/databases/fixtures/project.rb +0 -4
  65. data/test/databases/fixtures/projects.yml +0 -7
  66. data/test/databases/fixtures/replies.yml +0 -20
  67. data/test/databases/fixtures/reply.rb +0 -5
  68. data/test/databases/fixtures/topic.rb +0 -19
  69. data/test/databases/fixtures/topics.yml +0 -32
  70. data/test/databases/fixtures/user.rb +0 -2
  71. data/test/databases/fixtures/users.yml +0 -35
  72. data/test/databases/lib/activerecord_test_connector.rb +0 -65
  73. data/test/databases/lib/load_fixtures.rb +0 -13
  74. data/test/databases/lib/schema.rb +0 -41
  75. data/test/enumerable_test.rb +0 -95
  76. data/test/join_test.rb +0 -61
  77. data/test/limit_test.rb +0 -41
  78. data/test/order_test.rb +0 -52
  79. data/test/profiler.rb +0 -34
  80. data/test/ruby_test.rb +0 -9
  81. data/test/source_test.rb +0 -43
  82. data/test/types_test.rb +0 -59
  83. data/test/where_test.rb +0 -245
@@ -1,22 +0,0 @@
1
- module Ambition
2
- class RubyProcessor < RubyToRuby
3
- def self.process(node)
4
- @processor ||= new
5
- @processor.process node
6
- end
7
-
8
- ##
9
- # This is not DRY, and I don't care.
10
- def process(node)
11
- node ||= []
12
-
13
- if respond_to?(method = "process_#{node.first}")
14
- send(method, node[1..-1])
15
- elsif node.blank?
16
- ''
17
- else
18
- raise "Missing process method for sexp: #{node.inspect}"
19
- end
20
- end
21
- end
22
- end
@@ -1,149 +0,0 @@
1
- module Ambition
2
- class SelectProcessor < Processor
3
- def initialize(owner, block)
4
- super()
5
- @receiver = nil
6
- @owner = owner
7
- @table_name = owner.table_name
8
- @block = block
9
- @key = :conditions
10
- end
11
-
12
- ##
13
- # Sexp Processing Methods
14
- def process_and(exp)
15
- joined_expressions 'AND', exp
16
- end
17
-
18
- def process_or(exp)
19
- joined_expressions 'OR', exp
20
- end
21
-
22
- def process_not(exp)
23
- type, receiver, method, other = *exp.first
24
-
25
- case type
26
- when :call
27
- translation(receiver, negate(method, other), other)
28
- when :match3
29
- regexp = receiver.last
30
- "#{process(method)} #{statement(:negated_regexp, regexp)} #{sanitize(regexp)}"
31
- else
32
- process_error(exp)
33
- end
34
- end
35
-
36
- def process_call(exp)
37
- receiver, method, other = *exp
38
-
39
- translation(receiver, method, other)
40
- end
41
-
42
- def process_lit(exp)
43
- exp.shift.to_s
44
- end
45
-
46
- def process_str(exp)
47
- sanitize exp.shift
48
- end
49
-
50
- def process_nil(exp)
51
- 'NULL'
52
- end
53
-
54
- def process_false(exp)
55
- sanitize 'false'
56
- end
57
-
58
- def process_true(exp)
59
- sanitize 'true'
60
- end
61
-
62
- def process_match3(exp)
63
- regexp, target = exp.shift.last, process(exp.shift)
64
- "#{target} #{statement(:regexp, regexp)} #{sanitize(regexp)}"
65
- end
66
-
67
- def process_dvar(exp)
68
- target = exp.shift
69
- if target == @receiver
70
- @table_name
71
- else
72
- value(target.to_s[0..-1])
73
- end
74
- end
75
-
76
- def process_ivar(exp)
77
- value(exp.shift.to_s[0..-1])
78
- end
79
-
80
- def process_lvar(exp)
81
- value(exp.shift.to_s)
82
- end
83
-
84
- def process_vcall(exp)
85
- value(exp.shift.to_s)
86
- end
87
-
88
- def process_gvar(exp)
89
- value(exp.shift.to_s)
90
- end
91
-
92
- def process_attrasgn(exp)
93
- raise "Assignment not supported. Maybe you meant ==?"
94
- end
95
-
96
- ##
97
- # Processor helper methods
98
- def joined_expressions(with, exp)
99
- clauses = []
100
- while clause = exp.shift
101
- clauses << clause
102
- end
103
- "(" + clauses.map { |c| process(c) }.join(" #{with} ") + ")"
104
- end
105
-
106
- def negate(method, target = nil)
107
- if Array(target).last == [:nil]
108
- return 'IS NOT'
109
- end
110
-
111
- case method
112
- when :==
113
- '<>'
114
- when :=~
115
- '!~'
116
- else
117
- raise "Not implemented: #{method.inspect}"
118
- end
119
- end
120
-
121
- def translation(receiver, method, other = nil)
122
- case method.to_s
123
- when 'IS NOT'
124
- "#{process(receiver)} IS NOT #{process(other)}"
125
- when '=='
126
- case other_value = process(other)
127
- when "NULL"
128
- "#{process(receiver)} IS #{other_value}"
129
- else
130
- "#{process(receiver)} = #{other_value}"
131
- end
132
- when '<>', '>', '<', '>=', '<='
133
- "#{process(receiver)} #{method} #{process(other)}"
134
- when 'include?'
135
- "#{process(other)} IN (#{process(receiver)})"
136
- when '=~'
137
- "#{process(receiver)} LIKE #{process(other)}"
138
- when '!~'
139
- "#{process(receiver)} NOT LIKE #{process(other)}"
140
- when 'upcase'
141
- "UPPER(#{process(receiver)})"
142
- when 'downcase'
143
- "LOWER(#{process(receiver)})"
144
- else
145
- extract_includes(receiver, method) || "#{process(receiver)}.#{quote_column_name(method)}"
146
- end
147
- end
148
- end
149
- end
@@ -1,10 +0,0 @@
1
- module Ambition
2
- class SimpleProcessor
3
- attr_reader :key, :value
4
- alias_method :to_s, :value
5
-
6
- def initialize(info = {})
7
- @key, @value = info.to_a.first
8
- end
9
- end
10
- end
@@ -1,47 +0,0 @@
1
- module Ambition
2
- class SortProcessor < Processor
3
- def initialize(owner, block)
4
- super()
5
- @receiver = nil
6
- @owner = owner
7
- @table_name = owner.table_name
8
- @block = block
9
- @key = :order
10
- end
11
-
12
- ##
13
- # Sexp Processing Methods
14
- def process_call(exp)
15
- receiver, method, other = *exp
16
- exp.clear
17
-
18
- translation(receiver, method, other)
19
- end
20
-
21
- def process_vcall(exp)
22
- if (method = exp.shift) == :rand
23
- 'RAND()'
24
- else
25
- raise "Not implemented: :vcall for #{method}"
26
- end
27
- end
28
-
29
- def process_masgn(exp)
30
- exp.clear
31
- ''
32
- end
33
-
34
- ##
35
- # Helpers!
36
- def translation(receiver, method, other)
37
- case method
38
- when :-@
39
- "#{process(receiver)} DESC"
40
- when :__send__
41
- "#{@table_name}.#{eval('to_s', @block)}"
42
- else
43
- extract_includes(receiver, method) || "#{@table_name}.#{method}"
44
- end
45
- end
46
- end
47
- end
@@ -1,53 +0,0 @@
1
- module Ambition
2
- module Source
3
- def self.api_methods
4
- Ambition::API.instance_methods(false).reject do |method|
5
- method =~ /ambition|context/
6
- end
7
- end
8
-
9
- def self.included(base)
10
- singleton = (class << base; self end)
11
- api_methods.each do |method|
12
- ##
13
- # This is less than cool, but we do it to get what we want.
14
- # When define_method can create a method which is block-aware,
15
- # then we can move to pure Ruby.
16
- singleton.class_eval <<-end_eval
17
- def #{method}(*args, &block)
18
- return super unless @ambition_source
19
- @ambition_source.#{method}(*args, &block)
20
- end
21
- end_eval
22
- end
23
- end
24
- end
25
-
26
- module API
27
- attr_reader :ambition_source
28
-
29
- ##
30
- # This is a destructive method. When given an array,
31
- # all Enumerable operations will be performed on the array
32
- # rather than on the database.
33
- #
34
- # User.ambition_source = users(:chris, :pj)
35
- # User.select { |u| u.age > 30 }
36
- #
37
- # You don't have to use fixtures, of course. Make stuff up on the fly.
38
- #
39
- # users = [ User.new(:name => 'chris', :age => 11), User.new(:name => 'pj', :age => 100) ]
40
- # User.ambition_source = users
41
- # User.select { |u| u.age > 30 }
42
- #
43
- # Revert back to normal by setting ambition_source to nil.
44
- #
45
- # User.ambition_source = nil
46
- # User.select { |u| u.age > 30 }
47
- #
48
- def ambition_source=(records)
49
- @ambition_source = records
50
- include Source unless ancestors.include? Source
51
- end
52
- end
53
- end
data/test/benchmark.rb DELETED
@@ -1,68 +0,0 @@
1
- $:.unshift File.dirname(__FILE__) + '/../lib'
2
- $NO_TEST = true
3
- %w( ambition rubygems active_record benchmark ).each { |f| require f }
4
-
5
- class User < ActiveRecord::Base
6
- def self.reflections
7
- return @reflections if @reflections
8
- @reflections = {}
9
- @reflections[:ideas] = Reflection.new(:has_many, 'user_id', :ideas, 'ideas')
10
- @reflections[:invites] = Reflection.new(:has_many, 'referrer_id', :invites, 'invites')
11
- @reflections[:profile] = Reflection.new(:has_one, 'user_id', :profile, 'profiles')
12
- @reflections[:account] = Reflection.new(:belongs_to, 'account_id', :account, 'accounts')
13
- @reflections
14
- end
15
-
16
- def self.table_name
17
- 'users'
18
- end
19
- end
20
-
21
- class Reflection < Struct.new(:macro, :primary_key_name, :name, :table_name)
22
- end
23
-
24
- Times = 1000
25
-
26
- Benchmark.bm(30) do |x|
27
- x.report 'simple select' do
28
- Times.times do
29
- User.select { |u| u.id == 20 }.to_hash
30
- end
31
- end
32
-
33
- x.report 'simple select w/ eval' do
34
- Times.times do
35
- User.select { |u| u.created_at == Time.now }.to_hash
36
- end
37
- end
38
-
39
- x.report 'dual select' do
40
- Times.times do
41
- User.select { |u| u.id == 20 && u.age > 20 }.to_hash
42
- end
43
- end
44
-
45
- x.report 'join select' do
46
- Times.times do
47
- User.select { |u| u.id == 20 && u.ideas.name =~ /stuff/ }.to_hash
48
- end
49
- end
50
-
51
- x.report 'dual select w/ sort' do
52
- Times.times do
53
- User.select { |u| u.id == 20 && u.age > 20 }.sort_by { |u| u.id }.to_hash
54
- end
55
- end
56
-
57
- x.report 'dual select w/ sort & first' do
58
- Times.times do
59
- User.select { |u| u.id == 20 && u.age > 20 }.sort_by { |u| u.id }.first(20).to_hash
60
- end
61
- end
62
-
63
- x.report "it's complicated" do
64
- Times.times do
65
- User.select { |u| (u.id == 20 && u.age > 20) || u.profile.name == 'Jon' }.sort_by { |u| [u.id, -u.name] }.first(20).to_hash
66
- end
67
- end
68
- end
@@ -1,34 +0,0 @@
1
- require File.dirname(__FILE__) + '/helper'
2
-
3
- context "Chaining" do
4
- specify "should join selects with AND" do
5
- sql = User.select { |m| m.name == 'jon' }
6
- sql = sql.select { |m| m.age == 22 }
7
- sql.to_sql.should == "SELECT * FROM users WHERE users.name = 'jon' AND users.age = 22"
8
- end
9
-
10
- specify "should join sort_bys with a comma" do
11
- sql = User.select { |m| m.name == 'jon' }
12
- sql = sql.sort_by { |m| m.name }
13
- sql = sql.sort_by { |m| m.age }
14
- sql.to_sql.should == "SELECT * FROM users WHERE users.name = 'jon' ORDER BY users.name, users.age"
15
- end
16
-
17
- specify "should join selects and sorts intelligently" do
18
- sql = User.select { |m| m.name == 'jon' }
19
- sql = sql.select { |m| m.age == 22 }
20
- sql = sql.sort_by { |m| -m.name }
21
- sql = sql.sort_by { |m| m.age }
22
- sql.to_sql.should == "SELECT * FROM users WHERE users.name = 'jon' AND users.age = 22 ORDER BY users.name DESC, users.age"
23
- end
24
-
25
- specify "should join lots of selects and sorts intelligently" do
26
- sql = User.select { |m| m.name == 'jon' }
27
- sql = sql.select { |m| m.age == 22 }
28
- sql = sql.sort_by { |m| m.name }
29
- sql = sql.select { |m| m.power == true }
30
- sql = sql.sort_by { |m| m.email }
31
- sql = sql.select { |m| m.admin == true && m.email == 'chris@ozmm.org' }
32
- sql.to_sql.should == "SELECT * FROM users WHERE users.name = 'jon' AND users.age = 22 AND users.power = 1 AND (users.admin = 1 AND users.email = 'chris@ozmm.org') ORDER BY users.name, users.email"
33
- end
34
- end
data/test/console DELETED
@@ -1,9 +0,0 @@
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"
data/test/count_test.rb DELETED
@@ -1,17 +0,0 @@
1
- require File.dirname(__FILE__) + '/helper'
2
-
3
- context "Count" do
4
- setup do
5
- hash = { :conditions => "users.name = 'jon'" }
6
- User.expects(:count).with(hash)
7
- @sql = User.select { |m| m.name == 'jon' }
8
- end
9
-
10
- specify "size" do
11
- @sql.size
12
- end
13
-
14
- specify "length" do
15
- @sql.length
16
- end
17
- end
@@ -1,3 +0,0 @@
1
- require File.dirname(__FILE__) + '/lib/load_fixtures'
2
- $:.unshift File.dirname(__FILE__) + '/../../lib'
3
- require 'ambition'
@@ -1,17 +0,0 @@
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