linqr 0.1.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.
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
+