linqr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+ group :development do
3
+ gem "bundler", "~> 1.0.0"
4
+ gem "jeweler", "~> 1.6.4"
5
+ gem "rspec", "~> 2.4.0"
6
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,26 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ git (1.2.5)
6
+ jeweler (1.6.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.9.2)
11
+ rspec (2.4.0)
12
+ rspec-core (~> 2.4.0)
13
+ rspec-expectations (~> 2.4.0)
14
+ rspec-mocks (~> 2.4.0)
15
+ rspec-core (2.4.0)
16
+ rspec-expectations (2.4.0)
17
+ diff-lcs (~> 1.1.2)
18
+ rspec-mocks (2.4.0)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ bundler (~> 1.0.0)
25
+ jeweler (~> 1.6.4)
26
+ rspec (~> 2.4.0)
data/README.rdoc ADDED
@@ -0,0 +1,90 @@
1
+ = Linqr - Query Comprehensions for Ruby
2
+
3
+ Linq ( http://msdn.microsoft.com/en-us/vcsharp/aa336746 )like sytax for ruby.
4
+
5
+ Linq in C# is a great example of bringing a uniform query model to
6
+ different sources of Data. Different providers can be hooked into the
7
+ model so developers can still use the familiar sytax to query the data
8
+ source instead of learning a whole new api.
9
+
10
+
11
+ == Usage
12
+
13
+ === Querying enumerable
14
+ * Quering an array
15
+
16
+ numbers = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ]
17
+ output = _{
18
+ from x
19
+ in_ numbers
20
+ where (x == 3 || x == 5 || x == 8) && ( x % 2) == 0
21
+ select x * 3
22
+ }
23
+ output.should == [24]
24
+
25
+ * Quering a hash
26
+
27
+ hash = {:a => 1 , :b => 2 , :c => 3}
28
+ output = _{
29
+ from k,v
30
+ in_ hash
31
+ where v == 3
32
+ select k
33
+ }
34
+ output.should == [:c]
35
+
36
+ * Grouping(select can select into anonymous types)
37
+
38
+ numbers = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ]
39
+ number_groups = _{
40
+ from n
41
+ in_ numbers
42
+ where n > 3 && n < 100
43
+ group_by n % 5 => :g
44
+ select :remainder => g.key , :values => g.values
45
+ }
46
+ number_groups.collect(&:remainder).should == [0, 4, 1, 3, 2]
47
+
48
+ * Ordering
49
+
50
+ words = [ "cherry", "apple", "blueberry" ]
51
+ sorted_words = _{
52
+ from word
53
+ in_ words
54
+ order_by word.length
55
+ select word
56
+ }
57
+ sorted_words.should == ["apple", "cherry", "blueberry"]
58
+ * Selecting into anonymous types
59
+
60
+ products = [Product.new("shoes", 1.75), Product.new("glasses", 55.55), Product.new("pencil", 5.20)]
61
+
62
+ cheap_product_names = _{
63
+ from p
64
+ in_ products
65
+ where p.price < 10
66
+ select :name => "Cheap - #{p.name}" , :new_price => p.price + 10
67
+ }
68
+
69
+ cheap_product_names.collect(&:name).should == ["Cheap - shoes","Cheap - pencil"]
70
+ cheap_product_names.collect(&:new_price).should == [11.75, 15.20]
71
+
72
+ === Querying ActiveRecord Models
73
+ All the examples listed for enumerable should work with activerecord models too
74
+
75
+ grouped_by_name = _{
76
+ from o
77
+ in_ Order
78
+ order_by o.name
79
+ group_by o.name => :g
80
+ select :name => g.key , :orders => g.values
81
+ }
82
+
83
+ === Querying Groupon Api
84
+ output = _{·
85
+ from deal
86
+ in_ GrouponDeals
87
+ where deal.lat == 38.8339 && deal.lng == -104.821·
88
+ select deal
89
+ }
90
+
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+
2
+ require 'rubygems'
3
+ require 'bundler'
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+ require 'rake'
12
+
13
+ require 'jeweler'
14
+ Jeweler::Tasks.new do |gem|
15
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
16
+ gem.name = "linqr"
17
+ gem.homepage = "http://github.com/suryagaddipati/linqr"
18
+ gem.license = "MIT"
19
+ gem.summary = %Q{Query Comprehensions for ruby}
20
+ gem.description = %Q{Linq like sytax for querying multiple-datasources}
21
+ gem.email = "surya.gaddipati@gmail.com"
22
+ gem.authors = ["surya"]
23
+ # dependencies defined in Gemfile
24
+ end
25
+ Jeweler::RubygemsDotOrgTasks.new
26
+ require 'rspec/core/rake_task'
27
+
28
+ desc 'Default: run specs.'
29
+ task :default => :spec
30
+
31
+ desc "Run specs"
32
+ RSpec::Core::RakeTask.new do |t|
33
+ t.pattern = ["./spec/**/*_spec.rb","./examples/**/*_spec.rb"]
34
+ end
35
+
36
+ desc "Generate code coverage"
37
+ RSpec::Core::RakeTask.new(:coverage) do |t|
38
+ t.pattern = ["./spec/**/*_spec.rb","./examples/**/*_spec.rb"]
39
+ t.rcov = true
40
+ t.rcov_opts = ['--exclude', 'spec']
41
+ end
42
+
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
+
48
+ rdoc.rdoc_dir = 'rdoc'
49
+ rdoc.title = "linqr #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,54 @@
1
+ require 'linqr'
2
+ require 'spec_helper'
3
+ describe "order by" do
4
+ Item = Struct.new(:name , :price, :discount_price)
5
+ it "should order by the order by operator" do
6
+ words = [ "cherry", "apple", "blueberry" ]
7
+ sorted_words = __{
8
+ from word in_ words order_by word
9
+ select word
10
+ }
11
+ sorted_words.should == ["apple", "blueberry","cherry" ]
12
+ end
13
+
14
+ it "simple-2" do
15
+ words = [ "cherry", "apple", "blueberry" ]
16
+ sorted_words = __{
17
+ from word
18
+ in_ words
19
+ order_by word.length
20
+ select word
21
+ }
22
+ sorted_words.should == ["apple", "cherry", "blueberry"]
23
+ end
24
+
25
+ it "descending" do
26
+ doubles = [ 1.7, 2.3, 1.9, 4.1, 2.9 ]
27
+ desc_doubles = __{
28
+ from d
29
+ in_ doubles
30
+ order_by d => descending
31
+ select d
32
+ }
33
+ desc_doubles.should == [4.1, 2.9, 2.3, 1.9, 1.7]
34
+ end
35
+ it "then-by-descending" do
36
+ products = [Item.new("bag", 9.00,5.00),Item.new("shoes", 5.00,2.00), Item.new("apple", 5.00,4.00), Item.new("cup", 25.00,14.00)]
37
+ __{
38
+ from p
39
+ in_ products
40
+ order_by p.price, p.discount_price
41
+ select p
42
+ }.collect(&:name).should == ["shoes","apple", "bag", "cup"]
43
+ end
44
+
45
+ it "then-by" do
46
+ products = [Item.new("bag", 9.00,5.00),Item.new("apple", 5.00,2.00), Item.new("shoes", 5.00,4.00), Item.new("cup", 25.00,5.00)]
47
+ __{
48
+ from p
49
+ in_ products
50
+ order_by p.price, p.discount_price => descending
51
+ select p
52
+ }.collect(&:name).should == ["cup","bag","shoes","apple"]
53
+ end
54
+ end
@@ -0,0 +1,54 @@
1
+ require 'linqr'
2
+ describe "Projection operators" do
3
+ it "Simple 1" do
4
+ numbers = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ]
5
+ output = _{
6
+ from n
7
+ in_ numbers
8
+ select n + 1
9
+ }.to_a
10
+ output.should == [6, 5, 2, 4, 10, 9, 7, 8, 3, 1]
11
+ end
12
+
13
+ it "Simple 2" do
14
+ Product = Struct.new(:name, :price)
15
+ products = [Product.new("shoes", 1.75), Product.new("glasses", 55.55), Product.new("pencil", 5.20)]
16
+
17
+ product_names = _{
18
+ from p
19
+ in_ products
20
+ select p.name
21
+ }.to_a
22
+ product_names.should == ["shoes","glasses","pencil"]
23
+ end
24
+
25
+ it "Select - Transformation" do
26
+ numbers = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ]
27
+ strings = [ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" ]
28
+ text_nums = _{
29
+ from n
30
+ in_ numbers
31
+ select strings[n]
32
+ }.to_a
33
+ text_nums.should == ["five", "four", "one", "three", "nine", "eight", "six", "seven", "two", "zero"]
34
+ end
35
+ it "should return a thunk" do
36
+ natural_numbers = Enumerator.new do |yielder|
37
+ number = 1
38
+ loop do
39
+ yielder.yield number
40
+ number += 1
41
+ end
42
+ end
43
+
44
+ plus_ones = _{
45
+ from n
46
+ where n < 5
47
+ in_ natural_numbers
48
+ select n
49
+ }.take(4)
50
+
51
+ plus_ones.to_a.should == [1,2,3,4]
52
+
53
+ end
54
+ end
@@ -0,0 +1,45 @@
1
+ class ExpressionEvaluator
2
+
3
+ def initialize(linq_exp)
4
+ @linq_exp = linq_exp
5
+ end
6
+
7
+ def visit_symbol(node)
8
+ node.value
9
+ end
10
+
11
+ def visit_variable(node)
12
+ @linq_exp.variable_val(node.to_s)
13
+ end
14
+
15
+ def visit_integer(node)
16
+ node.value
17
+ end
18
+
19
+ def visit_float(node)
20
+ node.value
21
+ end
22
+ def visit_unary(node)
23
+ target = node.operand.visit(self)
24
+ target.send("#{node.operator.to_s}@")
25
+ end
26
+ def visit_arg(node)
27
+ node.arg.visit(self)
28
+ end
29
+ def visit_string(node)
30
+ output = ""
31
+ node.each do |e|
32
+ output << e.visit(self)
33
+ end
34
+ output
35
+ end
36
+
37
+ def visit_stringcontent(node)
38
+ node.to_s
39
+ end
40
+
41
+ def visit_statements(node)
42
+ binary_exp = node.elements.first
43
+ binary_exp.visit(self)
44
+ end
45
+ end
data/lib/group_by.rb ADDED
@@ -0,0 +1 @@
1
+ Grouped = Struct.new(:key,:values)
@@ -0,0 +1,31 @@
1
+ class Enumerator
2
+
3
+ def lazy_sort_by(&block)
4
+ Enumerator.new do |yielder|
5
+ index = 0
6
+ sorted = sort_by(&block)
7
+ loop do
8
+ yielder.yield sorted[index]
9
+ index += 1
10
+ break if sorted.size == index
11
+ end
12
+ end
13
+ end
14
+
15
+ def lazy_select(&block)
16
+ Enumerator.new do |yielder|
17
+ self.each do |val|
18
+ yielder.yield(val) if block.call(val)
19
+ end
20
+ end
21
+ end
22
+
23
+ def lazy_map(&block)
24
+ Enumerator.new do |yielder|
25
+ self.each do |value|
26
+ yielder.yield(block.call(value))
27
+ end
28
+ end
29
+ end
30
+ end
31
+
data/lib/linqr.rb ADDED
@@ -0,0 +1,55 @@
1
+ $:.unshift(File.expand_path(File.dirname(__FILE__)))
2
+ require 'providers/enumerable_provider'
3
+ require 'providers/hash_provider'
4
+ require 'linqr_exp'
5
+ require 'ripper2ruby'
6
+ class Ruby::Node
7
+ def visit(visitor)
8
+ class_name=self.class.name.split('::').size == 2 ? self.class.name.split('::')[1]: self.class.name
9
+ visitor.send("visit_#{class_name.downcase}".to_sym, self)
10
+ end
11
+ def evaluate_source_name(visitor)
12
+ visitor.send("source_name_#{self.class.name.split('::')[1].downcase}".to_sym, self)
13
+ end
14
+ def to_sym
15
+ to_ruby.to_sym
16
+ end
17
+ end
18
+
19
+
20
+ class OrderBy
21
+ def initialize(node)
22
+ @node = node
23
+ end
24
+
25
+ def expressions
26
+ descending?? (@node.arguments[0...-1] << last_arg.first.key): @node.arguments
27
+ end
28
+
29
+ def descending?
30
+ last_arg.is_a? Ruby::Hash
31
+ end
32
+
33
+ def last_arg
34
+ @node.arguments.last.arg
35
+ end
36
+ end
37
+
38
+ class GroupBy < Ruby::Node
39
+ def initialize(node)
40
+ @node = node
41
+ end
42
+ def expression
43
+ return @node.arg.first.key if @node.arg.is_a? Ruby::Hash
44
+ @node
45
+ end
46
+ def grouping_var
47
+ @node.arg.first.value.first.to_sym
48
+ end
49
+ end
50
+
51
+ class Object
52
+ def _(&proc_exp)
53
+ LinqrExp.new(proc_exp).evaluate
54
+ end
55
+ end
data/lib/linqr_exp.rb ADDED
@@ -0,0 +1,84 @@
1
+ require 'sourcify'
2
+ require 'ripper'
3
+ require 'source_name_evaluator'
4
+ class LinqrExp
5
+
6
+ %w(where from select).each do | q |
7
+ send(:define_method,q.to_sym) { fexp(@exp,q) }
8
+ send(:define_method,(q+"?").to_sym) {!fcall(@exp,q).nil?}
9
+ end
10
+
11
+ def with_vars
12
+ @variables ||= {}
13
+ Proc.new do |args|
14
+ args= [args] unless args.is_a? Array
15
+ args.each_with_index do |param, idx|
16
+ @variables[variables[idx].to_s]= param
17
+ end
18
+ yield *args
19
+ end
20
+ end
21
+
22
+ def set_variable(var,val)
23
+ @variables ||= {}
24
+ @variables[var] = val
25
+ end
26
+
27
+ def variable_val(var_name)
28
+ if (@variables && var = @variables[var_name])
29
+ var
30
+ else
31
+ @binding.eval(var_name)
32
+ end
33
+ end
34
+
35
+ def group_by
36
+ GroupBy.new(fexp(@exp,"group_by"))
37
+ end
38
+
39
+ def group_by?
40
+ !fcall(@exp,"group_by").nil?
41
+ end
42
+
43
+ def order_by
44
+ OrderBy.new(fcall(@exp,"order_by"))
45
+ end
46
+
47
+ def order_by?
48
+ !fcall(@exp,"order_by").nil?
49
+ end
50
+
51
+
52
+ attr_reader :binding
53
+ def initialize(proc_exp)
54
+ @exp = Ripper::RubyBuilder.build(proc_exp.to_source)
55
+ @binding = proc_exp.binding
56
+ end
57
+
58
+ def evaluate
59
+ source.linqr_provider.evaluate(self)
60
+ end
61
+
62
+ def variable
63
+ variables.first
64
+ end
65
+ def variables
66
+ fcall(@exp,"from").arguments.collect(&:name)
67
+ end
68
+ def fcall(exp, fname)
69
+ exp.select(Ruby::Call).select {|call|call.identifier && call.token == fname}.first
70
+ end
71
+
72
+
73
+ def fexp(exp, fname)
74
+ f_arg = fcall(exp,fname).arguments.first
75
+ f_arg # make this less confusing
76
+ end
77
+ def source
78
+ token =fcall(@exp,"in_").arguments.first
79
+ source_name = token.evaluate_source_name(SourceNameEvaluator.new)
80
+ @binding.eval(source_name)
81
+ end
82
+
83
+
84
+ end
@@ -0,0 +1,58 @@
1
+ require 'expression_evaluator_base'
2
+ class ActiveRecordExpressionEvaluator < ExpressionEvaluator
3
+ attr_reader :conditions
4
+ def initialize(linq_exp)
5
+ @binding = linq_exp.binding
6
+ @conditions = {}
7
+ end
8
+
9
+ def visit_argslist(node)
10
+ node.first.visit(self)
11
+ end
12
+ def visit_variable(node)
13
+ @binding.eval(node.to_s)
14
+ end
15
+
16
+ def visit_integer(node)
17
+ node.value
18
+ end
19
+
20
+ def visit_binary(node)
21
+ right_val = node.right.visit(self)
22
+ left_val = node.left.visit(self)
23
+ @conditions[left_val] = right_val
24
+ end
25
+
26
+ def visit_arg(node)
27
+ node.arg.visit(self)
28
+ end
29
+ def visit_string(node)
30
+ node.value
31
+ end
32
+
33
+ def visit_call(node)
34
+ call = node.identifier.to_sym
35
+ return call unless call == :member?
36
+ target = node.target.visit(self)
37
+ argument = node.arguments.visit(self)
38
+ @conditions[argument] = target
39
+ end
40
+ def visit_array(node)
41
+ node.value
42
+ end
43
+
44
+ def visit_statements(node)
45
+ binary_exp = node.elements.first
46
+ binary_exp.visit(self)
47
+ end
48
+
49
+ end
50
+
51
+ class ArGroupByExpressionEvaluator < ActiveRecordExpressionEvaluator
52
+ attr_reader :grouping_var , :group_by
53
+ def visit_hash(node)
54
+ @grouping_var = node.first.value.visit(self)
55
+ @group_by = node.first.key.visit(self)
56
+ @group_by
57
+ end
58
+ end
@@ -0,0 +1,48 @@
1
+ require 'active_record'
2
+ require 'group_by'
3
+ require 'providers/enumerable_provider'
4
+ require 'providers/active_record/active_record_expression_evaluator'
5
+ class ActiveRecordProvider
6
+ def initialize(active_record_class)
7
+ @active_record_class = active_record_class
8
+ end
9
+ def evaluate(linq_exp)
10
+ query_params = {}
11
+ evaluator = ActiveRecordExpressionEvaluator.new(linq_exp)
12
+ if (linq_exp.where?)
13
+ linq_exp.where.visit(evaluator)
14
+ query_params.merge!(:conditions =>evaluator.conditions)
15
+ end
16
+
17
+ group_by_evaluator = ArGroupByExpressionEvaluator.new(linq_exp)
18
+ if(linq_exp.group_by?)
19
+ query_params.merge!(:group =>linq_exp.group_by.visit(group_by_evaluator))
20
+ end
21
+ if(linq_exp.order_by?)
22
+ query_params.merge!(:order =>linq_exp.order_by.visit(evaluator))
23
+ end
24
+
25
+ selected_values = @active_record_class.find(:all,query_params)
26
+
27
+ if (linq_exp.group_by?)
28
+ grouped_values = selected_values.group_by(&group_by_evaluator.group_by)
29
+ grouped_values.collect do |(k,v)|
30
+ Object.send(:define_method,group_by_evaluator.grouping_var) { Grouped.new(k,v) }
31
+ linq_exp.select.visit(EnumerableExpessionEvaluator.new(linq_exp))
32
+ end
33
+ else
34
+ selected_values.collect do |e|
35
+ Object.send(:define_method,linq_exp.variable.to_sym) { e }
36
+ linq_exp.select.visit(EnumerableExpessionEvaluator.new(linq_exp))
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ class ActiveRecord::Base
43
+ def self.linqr_provider
44
+ ActiveRecordProvider.new(self)
45
+ end
46
+ end
47
+
48
+
@@ -0,0 +1,46 @@
1
+ require 'ostruct'
2
+ require 'expression_evaluator_base'
3
+ class EnumerableExpessionEvaluator < ExpressionEvaluator
4
+
5
+ def visit_groupby(node)
6
+ node.expression.visit(self)
7
+ end
8
+
9
+ def visit_hash(node)
10
+ record = OpenStruct.new
11
+ node.elements.each do |e|
12
+ key = e.key.visit(self)
13
+ value = e.value.visit(self)
14
+ record.send("#{key.to_s}=".to_sym,value)
15
+ end
16
+ record
17
+ end
18
+
19
+ def visit_argslist(node)
20
+ node.map {|arg| arg.visit(self)}
21
+ end
22
+
23
+ def visit_binary(node)
24
+ right_val = node.right.visit(self)
25
+ left_val = node.left.visit(self)
26
+ if node.operator.to_sym == :and
27
+ left_val && right_val
28
+ elsif node.operator.to_sym == :or
29
+ left_val || right_val
30
+ else
31
+ left_val.send(node.operator.to_ruby.to_sym, right_val)
32
+ end
33
+ end
34
+
35
+ def visit_call(node)
36
+ target = node.target.visit(self)
37
+ method_name = node.identifier ? node.identifier.to_sym : :[]
38
+ if (node.arguments)
39
+ arguments = node.arguments.collect { |x| x.visit(self) }
40
+ target.send(method_name, *arguments)
41
+ else
42
+ target.send(method_name)
43
+ end
44
+ end
45
+ end
46
+
@@ -0,0 +1,57 @@
1
+ require 'providers/enumerable_expression_evaluator'
2
+ require 'lazy_enumerator'
3
+ require 'group_by'
4
+ class EnumerableProvider
5
+ def initialize(enumerable)
6
+ @enumerable = enumerable.to_enum
7
+ end
8
+ def handle_where(linq_exp)
9
+ evaluator = EnumerableExpessionEvaluator.new(linq_exp)
10
+ @enumerable.lazy_select(&linq_exp.with_vars do|e|
11
+ linq_exp.where.visit(evaluator)
12
+ end)
13
+ end
14
+
15
+ def handle_order_by(linq_exp,filtered_values)
16
+ order_by = linq_exp.order_by
17
+ order_by.expressions.reduce(filtered_values) do |values, sort_exp|
18
+ values.lazy_sort_by(&linq_exp.with_vars do|e|
19
+ sort_val = sort_exp.visit(EnumerableExpessionEvaluator.new(linq_exp))
20
+ order_by.descending?? 1 - sort_val : sort_val
21
+ end)
22
+ end
23
+ end
24
+
25
+ def handle_group_by(linq_exp,filtered_values)
26
+ grouped_values = filtered_values.group_by(&linq_exp.with_vars do |e|
27
+ linq_exp.group_by.visit(EnumerableExpessionEvaluator.new(linq_exp))
28
+ end)
29
+
30
+ grouped_values.collect do |(k,v)|
31
+ linq_exp.set_variable(linq_exp.group_by.grouping_var.to_s, Grouped.new(k,v) )
32
+ linq_exp.select.visit(EnumerableExpessionEvaluator.new(linq_exp))
33
+ end
34
+ end
35
+
36
+ def handle_select(linq_exp,filtered_values)
37
+ filtered_values.lazy_map(&linq_exp.with_vars do |e|
38
+ linq_exp.select.visit(EnumerableExpessionEvaluator.new(linq_exp))
39
+ end)
40
+ end
41
+
42
+ def evaluate (linq_exp)
43
+ filtered_values =linq_exp.where?? handle_where(linq_exp) : @enumerable
44
+ filtered_values = linq_exp.order_by?? handle_order_by(linq_exp,filtered_values) : filtered_values
45
+ if (linq_exp.group_by?)
46
+ handle_group_by(linq_exp,filtered_values)
47
+ else
48
+ handle_select(linq_exp,filtered_values)
49
+ end
50
+ end
51
+ end
52
+
53
+ module Enumerable
54
+ def linqr_provider
55
+ EnumerableProvider.new(self)
56
+ end
57
+ end
@@ -0,0 +1,41 @@
1
+ require 'groupon'
2
+ require 'expression_evaluator_base'
3
+ class GrouponDeals
4
+
5
+ def self.linqr_provider
6
+ self
7
+ end
8
+ def self.evaluate(linq_exp)
9
+ Groupon.api_key = '966a0273f2974c725e25d507d4e07daabcb0ee00'
10
+ evaluator = GrouponExpressionEvaluator.new(linq_exp)
11
+ linq_exp.where.visit(evaluator)
12
+ selected_values = Groupon.deals(evaluator.conditions)
13
+ #puts evaluator.conditions.inspect
14
+ selected_values.collect do |e|
15
+ Object.send(:define_method,linq_exp.variable.to_sym) { e }
16
+ linq_exp.select.visit(EnumerableExpessionEvaluator.new(linq_exp))
17
+ end
18
+ end
19
+ end
20
+
21
+
22
+ class GrouponExpressionEvaluator < ExpressionEvaluator
23
+ attr_reader :conditions
24
+ def initialize(linq_exp)
25
+ @conditions = {}
26
+ super
27
+ end
28
+
29
+ def visit_binary(node)
30
+ right_val = node.right.visit(self)
31
+ left_val = node.left.visit(self)
32
+ @conditions[left_val] = right_val
33
+ end
34
+
35
+
36
+ def visit_call(node)
37
+ node.identifier.to_s.to_sym
38
+ end
39
+
40
+
41
+ end
@@ -0,0 +1,22 @@
1
+ require 'providers/enumerable_provider'
2
+
3
+ class HashProvider < EnumerableProvider
4
+
5
+ def handle_where(linq_exp)
6
+ filtered_values = @enumerable.lazy_select(&linq_exp.with_vars do|k,v|
7
+ linq_exp.where.visit(EnumerableExpessionEvaluator.new(linq_exp))
8
+ end)
9
+ end
10
+
11
+ def handle_select(linq_exp,filtered_values)
12
+ filtered_values.lazy_map(&linq_exp.with_vars do |k,v|
13
+ linq_exp.select.visit(EnumerableExpessionEvaluator.new(linq_exp))
14
+ end)
15
+ end
16
+ end
17
+
18
+ class Hash
19
+ def linqr_provider
20
+ HashProvider.new(self)
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ require 'ripper2ruby'
2
+ class SourceNameEvaluator
3
+ def source_name_arg(node)
4
+ node.name
5
+ end
6
+
7
+ def source_name_const(node)
8
+ node.identifier.to_s
9
+ end
10
+
11
+ def source_name_variable(node)
12
+ node.token.to_s
13
+ end
14
+ end
15
+
16
+ class Ruby::Arg
17
+ def name
18
+ arg.name
19
+ end
20
+ end
21
+ class Ruby::Call
22
+ def name
23
+ identifier.to_s
24
+ end
25
+ end
26
+ class Ruby::Variable
27
+ def name
28
+ to_s
29
+ end
30
+ end
31
+
32
+ class Ruby::Const
33
+ def name
34
+ identifier.to_s
35
+ end
36
+ end
@@ -0,0 +1,93 @@
1
+ require 'active_record'
2
+ require 'linqr'
3
+ require 'providers/active_record/activerecord_provider'
4
+ require 'spec_helper'
5
+ ENV['DB'] ||= "mysql"
6
+ database_yml = File.expand_path('../database.yml', __FILE__)
7
+ if File.exists?(database_yml)
8
+ active_record_configuration = YAML.load_file(database_yml)[ENV['DB']]
9
+ ActiveRecord::Base.establish_connection(active_record_configuration)
10
+ ActiveRecord::Base.silence do
11
+ ActiveRecord::Migration.verbose = false
12
+ load(File.dirname(__FILE__) + '/schema.rb')
13
+ load(File.dirname(__FILE__) + '/order.rb')
14
+ end
15
+ end
16
+ describe "ActiveRecord Provider" do
17
+ before {
18
+ ActiveRecord::Base.connection.execute "DELETE FROM Orders"
19
+ }
20
+
21
+
22
+ it "should select from active-record" do
23
+ Order.create(:name => "first")
24
+ Order.create(:name => "second")
25
+ second_order = Order.find(:all , :conditions => {:name => "second"})
26
+
27
+ output = _{
28
+ from o
29
+ in_ Order
30
+ where o.name == "second"
31
+ select o
32
+
33
+ }
34
+ output.should == second_order
35
+ end
36
+ describe "group_by" do
37
+ it "should group by the attribute" do
38
+ Order.create(:name => "first")
39
+ Order.create(:name => "second")
40
+ Order.create(:name => "second")
41
+ Order.create(:name => "third")
42
+
43
+ expected_grouped_orders = Order.find(:all , :group => :name)
44
+
45
+ grouped_by_name = _{
46
+ from o
47
+ in_ Order
48
+ group_by o.name => :g
49
+ select :name => g.key , :orders => g.values
50
+ }
51
+ grouped_by_name.collect(&:name).should == ["first", "second", "third"]
52
+ end
53
+ end
54
+
55
+ describe "order by" do
56
+
57
+ it "should order by the order by operator" do
58
+ Order.create(:name => "berry")
59
+ Order.create(:name => "apple")
60
+ Order.create(:name => "peach")
61
+ Order.create(:name => "cherry")
62
+
63
+ expected_output = Order.find(:all , :order => :name).collect(&:name)
64
+
65
+ ordered_names = _{
66
+ from o
67
+ in_ Order
68
+ order_by o.name
69
+ select o.name
70
+ }
71
+ ordered_names.should == expected_output
72
+ end
73
+ end
74
+
75
+ describe "in" do
76
+ it "should retrive all object" do
77
+ Order.create(:name => "first")
78
+ Order.create(:name => "second")
79
+ Order.create(:name => "third")
80
+
81
+
82
+ first_second = Order.find(:all , :conditions => {:name => ["second", "first"]})
83
+
84
+ output = _{
85
+ from o
86
+ in_ Order
87
+ where ["second", "first"].member?(o.name)
88
+ select o
89
+ }
90
+ output.should == first_second
91
+ end
92
+ end
93
+ end
data/spec/database.yml ADDED
@@ -0,0 +1,6 @@
1
+ mysql:
2
+ adapter: mysql
3
+ hostname: localhost
4
+ username: root
5
+ database: acts_as_aggregate_root
6
+ socket: /tmp/mysql.sock
@@ -0,0 +1,15 @@
1
+ require 'linqr'
2
+ require 'spec_helper'
3
+
4
+ describe EnumerableExpessionEvaluator do
5
+
6
+ describe "#visit_binary" do
7
+ it "should invoke the operator" do
8
+ x = true
9
+ y = false
10
+ p = lambda{ x && y }
11
+
12
+ #puts EnumerableExpessionEvaluator.new LinqrExp.new(p)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,101 @@
1
+ require 'linqr'
2
+ require 'spec_helper'
3
+ describe "linqr" do
4
+ it "simple binary expression" do
5
+ numbers = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ]
6
+ output = __{
7
+ from x
8
+ in_ numbers
9
+ where x > 1
10
+ select x * 3
11
+ }
12
+ output.should == [15, 12, 9, 27, 24, 18, 21, 6]
13
+ end
14
+ it"a little complex binary expression"do
15
+ numbers = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ]
16
+ output = __{
17
+ from x
18
+ in_ numbers
19
+ where (x % 2) > 0
20
+ select x
21
+ }
22
+ output.should == [ 5, 1, 3, 9, 7 ]
23
+ end
24
+ it"compound 'or' binary expression"do
25
+ numbers = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ]
26
+ output = __{
27
+ from x
28
+ in_ numbers
29
+ where (x == 3 || x == 5 || x == 8) && ( x % 2) == 0
30
+ select x
31
+ }
32
+ output.should == [8]
33
+ end
34
+ it"compound 'and' binary expression"do
35
+ numbers = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ]
36
+ output = __{
37
+ from x
38
+ in_ numbers
39
+ where x > 3 && x <= 5
40
+ select x.to_s
41
+ }
42
+ output.should == [ '5','4']
43
+ end
44
+
45
+ it "query associative array" do
46
+ hash = {:a => 1 , :b => 2 , :c => 3}
47
+ output = __{
48
+ from k,v
49
+ in_ hash
50
+ where v == 3
51
+ select k
52
+ }
53
+ output.should == [:c]
54
+ end
55
+
56
+ it "should work with models" do
57
+ Product = Struct.new(:name, :price)
58
+ products = [Product.new("shoes", 1.75), Product.new("glasses", 55.55), Product.new("pencil", 5.20)]
59
+
60
+ cheap_product_names = __{
61
+ from p
62
+ in_ products
63
+ where p.price < 10
64
+ select p.name
65
+ }
66
+
67
+ cheap_product_names.should == ["shoes","pencil"]
68
+ end
69
+
70
+ it "select into anonymous types" do
71
+ Product = Struct.new(:name, :price)
72
+ products = [Product.new("shoes", 1.75), Product.new("glasses", 55.55), Product.new("pencil", 5.20)]
73
+
74
+ cheap_product_names = __{
75
+ from p
76
+ in_ products
77
+ where p.price < 10
78
+ select :name => "Cheap - #{p.name}" , :new_price => p.price + 10
79
+ }
80
+
81
+ cheap_product_names.collect(&:name).should == ["Cheap - shoes","Cheap - pencil"]
82
+ cheap_product_names.collect(&:new_price).should == [11.75, 15.20]
83
+
84
+ end
85
+
86
+ context "group by" do
87
+ it "simple-1" do
88
+ numbers = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ]
89
+ number_groups = __{
90
+ from n
91
+ in_ numbers
92
+ where n > 3 && n < 100
93
+ group_by n % 5 => g
94
+ select :remainder => g.key , :values => g.values
95
+ }
96
+
97
+ number_groups.collect(&:remainder).should == [0, 4, 3, 1, 2]
98
+ end
99
+ end
100
+
101
+ end
@@ -0,0 +1,17 @@
1
+ require 'linqr'
2
+ require 'spec_helper'
3
+ require 'providers/groupon/groupon_provider'
4
+ require 'awesome_print'
5
+
6
+ describe 'Groupon provider' do
7
+
8
+ it "should query groupon" do
9
+ output = _{
10
+ from deal
11
+ in_ GrouponDeals
12
+ where deal.lat == 38.8339 && deal.lng == -104.821
13
+ select deal
14
+ }
15
+ ap output.inspect
16
+ end
17
+ end
data/spec/order.rb ADDED
@@ -0,0 +1,2 @@
1
+ class Order < ActiveRecord::Base
2
+ end
data/spec/schema.rb ADDED
@@ -0,0 +1,19 @@
1
+ ActiveRecord::Schema.define :version => 0 do
2
+ create_table "orders", :force => true do |t|
3
+ t.column :name, :string
4
+ t.timestamps
5
+ end
6
+
7
+
8
+ create_table "sub_orders", :force => true do |t|
9
+ t.column :order_id, :integer
10
+ t.column :name, :string
11
+ t.timestamps
12
+ end
13
+
14
+ create_table "order_items", :force => true do |t|
15
+ t.column :sub_order_id, :integer
16
+ t.column :name, :string
17
+ t.timestamps
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ class Object
2
+ # alias_method :try, :__send__
3
+ def try(method, *args, &block)
4
+ send(method, *args, &block) if respond_to?(method)
5
+ end
6
+ def __(&block)
7
+ _(&block).to_a
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: linqr
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - surya
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-09-10 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: bundler
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.0
24
+ type: :development
25
+ prerelease: false
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: jeweler
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 1.6.4
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rspec
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 2.4.0
46
+ type: :development
47
+ prerelease: false
48
+ version_requirements: *id003
49
+ description: Linq like sytax for querying multiple-datasources
50
+ email: surya.gaddipati@gmail.com
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ extra_rdoc_files:
56
+ - README.rdoc
57
+ files:
58
+ - Gemfile
59
+ - Gemfile.lock
60
+ - README.rdoc
61
+ - Rakefile
62
+ - VERSION
63
+ - examples/linqr2enumerable/ordering_operators_spec.rb
64
+ - examples/linqr2enumerable/projection_operators_spec.rb
65
+ - lib/expression_evaluator_base.rb
66
+ - lib/group_by.rb
67
+ - lib/lazy_enumerator.rb
68
+ - lib/linqr.rb
69
+ - lib/linqr_exp.rb
70
+ - lib/providers/active_record/active_record_expression_evaluator.rb
71
+ - lib/providers/active_record/activerecord_provider.rb
72
+ - lib/providers/enumerable_expression_evaluator.rb
73
+ - lib/providers/enumerable_provider.rb
74
+ - lib/providers/groupon/groupon_provider.rb
75
+ - lib/providers/hash_provider.rb
76
+ - lib/source_name_evaluator.rb
77
+ - spec/active_record_provider_spec.rb
78
+ - spec/database.yml
79
+ - spec/enumerable_expression_evaluator_spec.rb
80
+ - spec/enumerable_provider_spec.rb
81
+ - spec/groupon_provider_spec.rb
82
+ - spec/order.rb
83
+ - spec/schema.rb
84
+ - spec/spec_helper.rb
85
+ has_rdoc: true
86
+ homepage: http://github.com/suryagaddipati/linqr
87
+ licenses:
88
+ - MIT
89
+ post_install_message:
90
+ rdoc_options: []
91
+
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: -4136635536432510828
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: "0"
109
+ requirements: []
110
+
111
+ rubyforge_project:
112
+ rubygems_version: 1.6.1
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Query Comprehensions for ruby
116
+ test_files: []
117
+