ambition 0.3.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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 CHANGED
@@ -1,15 +1,10 @@
1
- unless defined? ActiveRecord
2
- require 'rubygems'
3
- require 'active_record'
4
- end
1
+ require 'ambition/enumerable'
5
2
  require 'ambition/api'
6
- require 'ambition/source'
7
- require 'ambition/processor'
8
- require 'ambition/ruby_processor'
9
- require 'ambition/select_processor'
10
- require 'ambition/sort_processor'
11
- require 'ambition/simple_processor'
12
- require 'ambition/query'
13
- require 'ambition/database_statements'
3
+ require 'ambition/context'
4
+ require 'ambition/core_ext'
14
5
 
15
- ActiveRecord::Base.extend Ambition::API
6
+ require 'ambition/processors/base'
7
+ require 'ambition/processors/select'
8
+ require 'ambition/processors/sort'
9
+ require 'ambition/processors/slice'
10
+ require 'ambition/processors/ruby'
data/lib/ambition/api.rb CHANGED
@@ -2,57 +2,43 @@ module Ambition
2
2
  module API
3
3
  include Enumerable
4
4
 
5
- attr_accessor :query_context
6
- def query_context
7
- @query_context || Query.new(self)
5
+ ##
6
+ # Entry methods
7
+ def select(&block)
8
+ context = ambition_context
9
+ context << Processors::Select.new(context, block)
8
10
  end
9
11
 
10
- def entries
11
- find(:all, query_context.to_hash)
12
+ def sort_by(&block)
13
+ context = ambition_context
14
+ context << Processors::Sort.new(context, block)
12
15
  end
13
- alias_method :to_a, :entries
14
16
 
15
- def select(*args, &block)
16
- query_context.add SelectProcessor.new(self, block)
17
+ def entries
18
+ ambition_context.kick
17
19
  end
20
+ alias_method :to_a, :entries
18
21
 
19
- def first(limit = 1)
20
- query_context.add SimpleProcessor.new(:limit => limit)
21
-
22
- if limit == 1
23
- find(:first, query_context.to_hash)
24
- else
25
- query_context
26
- end
22
+ def size
23
+ ambition_context.size
27
24
  end
28
25
 
29
- def slice(offset, limit = nil)
30
- if offset.is_a? Range
31
- limit = offset.end
32
- limit -= 1 if offset.exclude_end?
33
- offset = offset.first
34
- limit -= offset
35
- elsif limit.nil?
36
- return find(offset)
37
- end
38
-
39
- query_context.add SimpleProcessor.new(:offset => offset)
40
- query_context.add SimpleProcessor.new(:limit => limit)
26
+ def slice(start, length = nil)
27
+ context = ambition_context
28
+ context << Processors::Slice.new(context, start, length)
41
29
  end
42
30
  alias_method :[], :slice
43
31
 
44
- def sort_by(&block)
45
- query_context.add SortProcessor.new(self, block)
46
- end
47
-
32
+ ##
33
+ # Convenience methods
48
34
  def detect(&block)
49
35
  select(&block).first
50
36
  end
51
37
 
52
- def size
53
- count(query_context.to_hash)
38
+ def first(count = 1)
39
+ sliced = slice(0, count)
40
+ count == 1 ? sliced.kick : sliced
54
41
  end
55
- alias_method :length, :size
56
42
 
57
43
  def each(&block)
58
44
  entries.each(&block)
@@ -69,5 +55,26 @@ module Ambition
69
55
  def empty?
70
56
  size.zero?
71
57
  end
58
+
59
+ ##
60
+ # Plumbing
61
+ def ambition_context
62
+ Context.new(self)
63
+ end
64
+
65
+ def ambition_adapter
66
+ name = respond_to?(:name) ? name : self.class.name
67
+ parent = respond_to?(:superclass) ? superclass : self.class.superclass
68
+ @@ambition_adapter[name] || @@ambition_adapter[parent.name]
69
+ end
70
+
71
+ def ambition_adapter=(klass)
72
+ @@ambition_adapter ||= {}
73
+ @@ambition_adapter[name] = klass
74
+ end
75
+
76
+ def ambition_owner
77
+ @owner || self
78
+ end
72
79
  end
73
80
  end
@@ -0,0 +1,62 @@
1
+ module Ambition
2
+ class Context
3
+ include API
4
+
5
+ ##
6
+ # These are the methods your Query and Translator classes will
7
+ # want to access.
8
+ #
9
+ # +owner+ The class everything was called on. Like `User'
10
+ #
11
+ # +clauses+ A hash of arrays, one key per processor.
12
+ # So, if someone called User.select, your
13
+ # +clauses+ hash would have a :select key with
14
+ # an array of translated strings via your Select
15
+ # class.
16
+ #
17
+ # +stash+ A place for you to stick stuff. Available to
18
+ # all Translators and your Query class.
19
+ attr_reader :clauses, :owner, :stash
20
+
21
+ def initialize(owner)
22
+ @owner = owner
23
+ @clauses = {}
24
+ @stash = {}
25
+ end
26
+
27
+ def ambition_context
28
+ self
29
+ end
30
+
31
+ def <<(clause)
32
+ @clauses[clause.key] ||= []
33
+ @clauses[clause.key] << clause.to_s
34
+ self
35
+ end
36
+
37
+ def adapter_query
38
+ Processors::Base.translator(self, :Query)
39
+ end
40
+
41
+ def to_hash
42
+ adapter_query.to_hash
43
+ end
44
+
45
+ def to_s
46
+ adapter_query.to_s
47
+ end
48
+
49
+ def kick
50
+ adapter_query.kick
51
+ end
52
+
53
+ def size
54
+ adapter_query.size
55
+ end
56
+ alias_method :length, :size
57
+
58
+ def inspect
59
+ "(Query object: call #to_s or #to_hash to inspect, call an Enumerable (such as #each or #first) to request data)"
60
+ end
61
+ end
62
+ end
@@ -3,6 +3,19 @@
3
3
  require 'parse_tree'
4
4
  require 'ruby2ruby'
5
5
 
6
+ class Object
7
+ def to_sexp
8
+ instance_eval <<-end_eval
9
+ return proc { #{inspect} }.to_sexp.last
10
+ end_eval
11
+ end
12
+
13
+ def metaclass; (class << self; self end) end
14
+ def meta_eval(&blk) metaclass.instance_eval(&blk) end
15
+ def meta_def(name, &blk) meta_eval { define_method name, &blk } end
16
+ def class_def(name, &blk) class_eval { define_method name, &blk } end
17
+ end
18
+
6
19
  class ProcHolder
7
20
  end
8
21
 
@@ -0,0 +1,6 @@
1
+ module Ambition
2
+ Enumerable = ::Enumerable.dup
3
+ Enumerable.class_eval do
4
+ remove_method :find, :find_all
5
+ end
6
+ end
@@ -0,0 +1,126 @@
1
+ module Ambition
2
+ module Processors
3
+ class Base
4
+ ##
5
+ # Processing methods
6
+ def process_proc(exp)
7
+ # puts "=> #{exp.inspect}"
8
+ receiver, body = process(exp.shift), exp.shift
9
+ process(body)
10
+ end
11
+
12
+ def process_dasgn_curr(exp)
13
+ @receiver = exp.first
14
+ @receiver.to_s
15
+ end
16
+ alias_method :process_dasgn, :process_dasgn_curr
17
+
18
+ def process_array(exp)
19
+ # Branch on whether this is straight Ruby or a real array
20
+ if ruby = rubify(exp)
21
+ ruby
22
+ else
23
+ exp.map { |m| process(m) }
24
+ end
25
+ end
26
+
27
+ def process_str(exp)
28
+ exp.first
29
+ end
30
+
31
+ def process_lit(exp)
32
+ exp.first
33
+ end
34
+
35
+ def process_nil(exp)
36
+ nil
37
+ end
38
+
39
+ def process_true(exp)
40
+ true
41
+ end
42
+
43
+ def process_false(exp)
44
+ false
45
+ end
46
+
47
+ def process_dvar(exp)
48
+ target = exp.shift
49
+ value(target.to_s[0..-1])
50
+ end
51
+
52
+ def process_ivar(exp)
53
+ value(exp.shift.to_s[0..-1])
54
+ end
55
+
56
+ def process_lvar(exp)
57
+ value(exp.shift.to_s)
58
+ end
59
+
60
+ def process_vcall(exp)
61
+ value(exp.shift.to_s)
62
+ end
63
+
64
+ def process_gvar(exp)
65
+ value(exp.shift.to_s)
66
+ end
67
+
68
+ def process(node)
69
+ node ||= []
70
+
71
+ if node.is_a? Symbol
72
+ node
73
+ elsif respond_to?(method = "process_#{node.first}")
74
+ send(method, node[1..-1])
75
+ elsif node.blank?
76
+ ''
77
+ else
78
+ raise "Missing process method for sexp: #{node.inspect}"
79
+ end
80
+ end
81
+
82
+ ##
83
+ # Helper methods
84
+ def to_s
85
+ process @block.to_sexp
86
+ end
87
+
88
+ def key
89
+ self.class.name.split('::').last.downcase.intern
90
+ end
91
+
92
+ def value(variable)
93
+ eval variable, @block
94
+ end
95
+
96
+ def translator
97
+ @translator ||= self.class.translator(@context)
98
+ end
99
+
100
+ def self.translator(context, name = nil)
101
+ name ||= self.name.split('::').last
102
+ klass = context.owner.ambition_adapter.const_get(name)
103
+ instance = klass.new
104
+
105
+ unless instance.respond_to? :context
106
+ klass.class_eval do
107
+ attr_accessor :context
108
+ def owner; @context.owner end
109
+ def clauses; @context.clauses end
110
+ def stash; @context.stash end
111
+ end
112
+ end
113
+
114
+ instance.context = context
115
+ instance
116
+ end
117
+
118
+ def rubify(exp)
119
+ # TODO: encapsulate this check in Ruby.should_process?(exp)
120
+ if exp.first.first == :call && exp.first[1].last != @receiver && Array(exp.first[1][1]).last != @receiver
121
+ value Ruby.process(exp.first)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end