querybuilder 0.8.3 → 0.9.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/History.txt CHANGED
@@ -1,3 +1,10 @@
1
+ == 0.9.0 2010-09-14
2
+
3
+ * 1 Major enhancements
4
+ * Lazy loading of custom queries.
5
+ * Select keys introspection in Query class.
6
+ * Using new RubyLess.translate API.
7
+
1
8
  == 0.8.3 2010-08-31
2
9
 
3
10
  * 2 Major enhancements
data/README.rdoc CHANGED
@@ -43,7 +43,7 @@ xml/html environments):
43
43
 
44
44
  '+' | '-' | '<' | '<=' | '=' | '>=' | '>'
45
45
  'or' | 'and' | 'lt' | 'le' | 'eq' | 'ne' | 'ge' | 'gt'
46
- 'like' | 'not like' | 'match'
46
+ 'like' | 'not like' | 'match' | 'in'
47
47
 
48
48
  This lets you build complex queries like:
49
49
 
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ begin
16
16
  gem.email = "gaspard@teti.ch"
17
17
  gem.homepage = "http://zenadmin.org/524"
18
18
  gem.authors = ["Gaspard Bucher"]
19
- gem.add_dependency "rubyless", ">= 0.5.0"
19
+ gem.add_dependency "rubyless", ">= 0.7.0"
20
20
  gem.add_development_dependency "shoulda", ">= 0"
21
21
  gem.add_development_dependency "yamltest", ">= 0.5.0"
22
22
  gem.extensions << 'lib/extconf.rb'
@@ -1,3 +1,3 @@
1
1
  module QueryBuilder
2
- VERSION = '0.8.3'
2
+ VERSION = '0.9.0'
3
3
  end
@@ -6,7 +6,7 @@ module QueryBuilder
6
6
 
7
7
  class << self
8
8
  # class variable
9
- attr_accessor :main_table, :main_class, :custom_queries
9
+ attr_accessor :main_table, :main_class, :custom_queries, :custom_query_files
10
10
  attr_accessor :defaults
11
11
  attr_accessor :before_process_callbacks, :after_process_callbacks
12
12
 
@@ -68,9 +68,16 @@ module QueryBuilder
68
68
  # Once loaded, this 'custom query' can be used in a query like:
69
69
  # "images from abc where a > 54"
70
70
  def load_custom_queries(directories)
71
+ # lazy loading (evaluation happens on first query)
72
+ self.custom_query_files ||= []
73
+ self.custom_query_files << directories
74
+ end
75
+
76
+ def load_custom_queries!
77
+ return unless list = self.custom_query_files
71
78
  klass = nil
72
79
  self.custom_queries ||= {}
73
- Dir.glob(directories).each do |dir|
80
+ Dir.glob(list.flatten).each do |dir|
74
81
  if File.directory?(dir)
75
82
  Dir.foreach(dir) do |file|
76
83
  next unless file =~ /(.+).yml$/
@@ -97,6 +104,7 @@ module QueryBuilder
97
104
  end
98
105
  end
99
106
  end
107
+ self.custom_query_files = nil
100
108
  rescue NameError => err
101
109
  raise ArgumentError.new("Invalid Processor class (#{klass})")
102
110
  end
@@ -427,7 +435,7 @@ module QueryBuilder
427
435
 
428
436
  def process_attr(fld_name)
429
437
  if @rubyless_helper
430
- insert_bind(RubyLess.translate(fld_name, @rubyless_helper))
438
+ insert_bind(RubyLess.translate(@rubyless_helper, fld_name))
431
439
  else
432
440
  insert_bind(fld_name)
433
441
  end
@@ -449,14 +457,14 @@ module QueryBuilder
449
457
 
450
458
  def process_dstring(string)
451
459
  raise QueryBuilder::SyntaxError.new("Cannot parse rubyless (missing binding context).") unless helper = @rubyless_helper
452
- res = RubyLess.translate_string(string, helper)
460
+ res = RubyLess.translate_string(helper, string)
453
461
  res.literal ? quote(res.literal) : insert_bind(res)
454
462
  end
455
463
 
456
464
  def process_rubyless(string)
457
465
  # compile RubyLess...
458
466
  raise QueryBuilder::SyntaxError.new("Cannot parse rubyless (missing binding context).") unless helper = @rubyless_helper
459
- res = RubyLess.translate(string, helper)
467
+ res = RubyLess.translate(helper, string)
460
468
  res.literal ? quote(res.literal) : insert_bind(res)
461
469
  end
462
470
 
@@ -656,13 +664,13 @@ module QueryBuilder
656
664
  end
657
665
  end
658
666
 
659
- def custom_query(relation)
667
+ def custom_query_without_loading(relation)
660
668
  return false unless first? && last? # current safety net until "from" is correctly implemented and tested
661
669
 
662
670
  custom_queries = self.class.custom_queries[self.class]
663
671
  if custom_queries &&
664
672
  custom_queries[@opts[:custom_query_group]] &&
665
- custom_query = custom_queries[@opts[:custom_query_group]][relation]
673
+ custom_query = custom_queries[@opts[:custom_query_group]][relation.singularize]
666
674
 
667
675
  custom_query.each do |k,v|
668
676
  @query.send(:instance_variable_set, "@#{k}", prepare_custom_query_arguments(k.to_sym, v))
@@ -675,6 +683,15 @@ module QueryBuilder
675
683
  end
676
684
  end
677
685
 
686
+ # Method executed only once, then alias resolves to custom_query_without_loading.
687
+ def custom_query(*args)
688
+ self.class.load_custom_queries!
689
+ self.class.class_eval do
690
+ alias custom_query custom_query_without_loading
691
+ end
692
+ custom_query_without_loading(*args)
693
+ end
694
+
678
695
  private
679
696
 
680
697
  %W{filter scope limit offset paginate group order}.each do |context|
@@ -53,6 +53,20 @@ module QueryBuilder
53
53
  @where << filter
54
54
  end
55
55
 
56
+ # Return all explicit selected keys (currently selection is only available in custom queries)
57
+ # For example, sql such as "SELECT form.*, MAX(form.date) AS last_date" would provice 'last_date' key.
58
+ def select_keys
59
+ @select_keys ||= (@select || []).map do |field|
60
+ if field =~ %r{AS\s+(.+)$}
61
+ $1
62
+ elsif field =~ %r{^(\w+\.|)([^\*]+)$}
63
+ $2
64
+ else
65
+ nil
66
+ end
67
+ end.compact
68
+ end
69
+
56
70
  # Convert query object to a string. This string should then be evaluated.
57
71
  #
58
72
  # ==== Parameters
@@ -285,4 +299,4 @@ module QueryBuilder
285
299
  end
286
300
  end
287
301
  end
288
- end
302
+ end
data/querybuilder.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{querybuilder}
8
- s.version = "0.8.3"
8
+ s.version = "0.9.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Gaspard Bucher"]
12
- s.date = %q{2010-08-31}
12
+ s.date = %q{2010-09-14}
13
13
  s.description = %q{QueryBuilder is an interpreter for the "pseudo sql" language. This language
14
14
  can be used for two purposes:
15
15
 
@@ -86,16 +86,16 @@ Gem::Specification.new do |s|
86
86
  s.specification_version = 3
87
87
 
88
88
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
89
- s.add_runtime_dependency(%q<rubyless>, [">= 0.5.0"])
89
+ s.add_runtime_dependency(%q<rubyless>, [">= 0.7.0"])
90
90
  s.add_development_dependency(%q<shoulda>, [">= 0"])
91
91
  s.add_development_dependency(%q<yamltest>, [">= 0.5.0"])
92
92
  else
93
- s.add_dependency(%q<rubyless>, [">= 0.5.0"])
93
+ s.add_dependency(%q<rubyless>, [">= 0.7.0"])
94
94
  s.add_dependency(%q<shoulda>, [">= 0"])
95
95
  s.add_dependency(%q<yamltest>, [">= 0.5.0"])
96
96
  end
97
97
  else
98
- s.add_dependency(%q<rubyless>, [">= 0.5.0"])
98
+ s.add_dependency(%q<rubyless>, [">= 0.7.0"])
99
99
  s.add_dependency(%q<shoulda>, [">= 0"])
100
100
  s.add_dependency(%q<yamltest>, [">= 0.5.0"])
101
101
  end
@@ -11,17 +11,32 @@ DummyProcessor:
11
11
  - '2'
12
12
  - '3'
13
13
  order: a ASC
14
-
14
+
15
+ star:
16
+ select:
17
+ - test.*
18
+ - '*'
19
+ - a
20
+ - 34 AS number
21
+ - c
22
+ tables:
23
+ - test
24
+ where:
25
+ - '1'
26
+ - '2'
27
+ - '3'
28
+ order: a ASC
29
+
15
30
  two_table:
16
31
  main_table: 'table_one'
17
32
  select:
18
33
  - x AS x
19
34
  - IF(table_one.y,table_one.y,table_two.z) AS y
20
35
  - table_two.name
21
- tables:
36
+ tables:
22
37
  - table_two
23
38
  - table_one
24
-
39
+
25
40
  two_table_main:
26
41
  main_table: foo
27
42
  select:
@@ -67,6 +67,20 @@ class DummyQueryBuilder < Test::Unit::TestCase
67
67
  end
68
68
  end # Including QueryBuilder
69
69
 
70
+ context 'A query with custom select' do
71
+ subject do
72
+ DummyProcessor.new('star where number < 4', :custom_query_group => 'test').query
73
+ end
74
+
75
+ should 'respond to select_keys' do
76
+ assert_equal %w{a number c}, subject.select_keys
77
+ end
78
+
79
+ should 'not include star keys' do
80
+ assert !subject.select_keys.include?('*')
81
+ end
82
+ end # A query with custom select
83
+
70
84
 
71
85
  def yt_parse(key, source, opts)
72
86
  opts = {:rubyless_helper => self}.merge(Hash[*(opts.map{|k,v| [k.to_sym, v]}.flatten)])
@@ -114,4 +128,4 @@ class DummyQueryBuilder < Test::Unit::TestCase
114
128
  end
115
129
  =end
116
130
  yt_make
117
- end
131
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 8
8
- - 3
9
- version: 0.8.3
7
+ - 9
8
+ - 0
9
+ version: 0.9.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Gaspard Bucher
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-08-31 00:00:00 +02:00
17
+ date: 2010-09-14 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -26,9 +26,9 @@ dependencies:
26
26
  - !ruby/object:Gem::Version
27
27
  segments:
28
28
  - 0
29
- - 5
29
+ - 7
30
30
  - 0
31
- version: 0.5.0
31
+ version: 0.7.0
32
32
  type: :runtime
33
33
  version_requirements: *id001
34
34
  - !ruby/object:Gem::Dependency