restful_query-rails3 0.4

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/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ tmp*
3
+ pkg*
4
+ .specification
5
+ _site*
data/History.txt ADDED
@@ -0,0 +1,29 @@
1
+ == 0.3.1 2010-02-16
2
+
3
+ * Parser takes a :map_columns option which maps aliases column names
4
+ * The parser now strips all conditions with blank values. If you actually want
5
+ to compare on a blank value, use the convertable value ':blank'
6
+
7
+ == 0.3.0 2009-08-10
8
+
9
+ * Vast improvements/enhancements
10
+ * Added Sequel::Dataset extension: restful_query - works like the can_query named_scope
11
+ * Added IN and NOT IN to Condition operators - value can be split on delimiter option
12
+ * Changed the default behavior to storing conditions as an array of condition hashes
13
+ * Added ability to pass an array of :conditions to the parser, see tests for format.
14
+ * Added English/Human names to condition operators
15
+ * Added list of CONVERTABLE_VALUES which are prefixed w/ : and are converted to their true ruby counterparts (nil, null, true, etc)
16
+ * Added operators for IS and IS NOT
17
+ * Added neq/!= operator to conditions
18
+ * Added a clear_default_sort! action that deletes the default before switching
19
+
20
+ == 0.2.0 2009-03-20
21
+
22
+ * 1 major enhancement:
23
+ * Now includes interface for sorting.
24
+ * Better documentation on the way.
25
+
26
+ == 0.0.1 2009-01-11
27
+
28
+ * 1 major enhancement:
29
+ * Initial release
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Aaron Quint, Quirkey NYC, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ 'Software'), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,64 @@
1
+ = restful_query
2
+
3
+ http://github.com/quirkey/restful_query
4
+
5
+ == DESCRIPTION:
6
+
7
+ RestfulQuery provides a RESTful interface for easily and safely querying ActiveRecord data.
8
+
9
+ == USAGE:
10
+
11
+ === Rails/ActiveRecord:
12
+
13
+ # as a gem, in environment.rb
14
+ config.gem 'restful_query'
15
+
16
+ or install the plugin.
17
+ In your model:
18
+
19
+ class Post < ActiveRecord::Base
20
+ can_query
21
+ end
22
+
23
+ In your controller:
24
+
25
+ class PostsController < ApplicationController
26
+ # ...
27
+ def index
28
+ @posts = Post.restful_query(params[:query])
29
+ end
30
+ # ...
31
+ end
32
+
33
+ Now you can query your model via the URL:
34
+
35
+ /posts?query[name][like]=jon&query[_sort]=created_at-desc
36
+
37
+ === Sequel
38
+
39
+ Theres also a Sequel extension that works very similar to ActiveRecord::Base.can_query:
40
+
41
+ require 'sequel/extensions/restful_query'
42
+
43
+ DB[:posts].restful_query({'name' => {'like' => 'jon'}, '_sort' => 'created_at-desc'})
44
+ <Sequel::SQLite::Dataset: "SELECT * FROM `posts` WHERE (name LIKE %jon%) ORDER BY `created_at` DESC">
45
+
46
+ === More!
47
+
48
+ Please see the project homepage for detailed usage and info:
49
+
50
+ http://code.quirkey.com/restful_query
51
+
52
+ == INSTALL:
53
+
54
+ To install as a gem:
55
+
56
+ sudo gem install restful_query
57
+
58
+ To install as a rails plugin
59
+
60
+ ./script/plugin install git://github.com/quirkey/restful_query.git
61
+
62
+ == LICENSE:
63
+
64
+ Free for use under the terms of the MIT License - see LICENSE for details
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ %w[rubygems rake rake/clean rake/testtask fileutils].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/restful_query'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |s|
7
+ s.name = %q{restful_query}
8
+ s.version = RestfulQuery::VERSION
9
+ s.authors = ["Aaron Quint"]
10
+ s.summary = 'Simple ActiveRecord and Sequel queries from a RESTful and safe interface'
11
+ s.description = %q{RestfulQuery provides a simple interface in front of a complex parser to parse specially formatted query hashes into complex SQL queries. It includes ActiveRecord and Sequel extensions.}
12
+ s.rubyforge_project = %q{quirkey}
13
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.2.0"])
14
+ s.add_runtime_dependency(%q<chronic>, [">= 0.2.3"])
15
+ s.add_development_dependency(%q<Shoulda>, [">= 1.2.0"])
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ Rake::TestTask.new do |t|
23
+ t.libs << "test"
24
+ t.test_files = FileList['test/test*.rb']
25
+ t.verbose = true
26
+ end
27
+
28
+ Dir['tasks/**/*.rake'].each { |t| load t }
29
+
30
+ task :default => :test
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'rails', 'init.rb')
@@ -0,0 +1,38 @@
1
+ module RestfulQuery
2
+ module CanQuery
3
+
4
+ def self.included(klass)
5
+ klass.extend MacroMethods
6
+ end
7
+
8
+ module MacroMethods
9
+ def can_query(options = {})
10
+ @include = options.delete(:include) || []
11
+ @query_options = options
12
+ @can_query = true
13
+ module_eval do
14
+ def self.restful_query_parser(query_hash, options = {})
15
+ RestfulQuery::Parser.new(query_hash, @query_options.merge(options))
16
+ end
17
+
18
+ scope_meth = self.respond_to?(:named_scope) ? :named_scope : :scope
19
+
20
+ send(scope_meth, :restful_query, lambda {|query_hash|
21
+ parser = self.restful_query_parser(query_hash)
22
+ query_hash = {}
23
+ query_hash[:conditions] = parser.to_conditions_array if parser.has_conditions?
24
+ query_hash[:include] = @include if @include && !@include.empty?
25
+ query_hash[:order] = parser.sort_sql if parser.has_sort?
26
+ logger.info 'Rest query:' + query_hash.inspect
27
+ query_hash
28
+ })
29
+ end
30
+ end
31
+
32
+ def can_query?
33
+ @can_query
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,110 @@
1
+ module RestfulQuery
2
+ class InvalidOperator < Error; end;
3
+
4
+ class Condition
5
+ attr_reader :column, :value, :operator, :options
6
+
7
+ OPERATOR_MAPPING = {
8
+ 'lt' => '<',
9
+ 'gt' => '>',
10
+ 'gteq' => '>=',
11
+ 'lteq' => '<=',
12
+ 'eq' => '=',
13
+ 'neq' => '!=',
14
+ 'is' => 'IS',
15
+ 'not' => 'IS NOT',
16
+ 'like' => 'LIKE',
17
+ 'in' => 'IN',
18
+ 'notin' => 'NOT IN'
19
+ }.freeze
20
+
21
+ CONVERTABLE_VALUES = {
22
+ ':true' => true,
23
+ ':false' => false,
24
+ ':nil' => nil,
25
+ ':null' => nil,
26
+ ':blank' => '',
27
+ ':empty' => ''
28
+ }.freeze
29
+
30
+ ENGLISH_OPERATOR_MAPPING = {
31
+ 'Less than' => 'lt',
32
+ 'Greater than' => 'gt',
33
+ 'Less than or equal to' => 'lteq',
34
+ 'Greater than or equal to' => 'gteq',
35
+ 'Equal to' => 'eq',
36
+ 'Not equal to' => 'neq',
37
+ 'Is' => 'is',
38
+ 'Is not' => 'not',
39
+ 'Like' => 'like',
40
+ 'In' => 'in',
41
+ 'Not in' => 'notin'
42
+ }.freeze
43
+
44
+
45
+ def initialize(column, value, operator = '=', options = {})
46
+ @options = {}
47
+ self.options = options if options.is_a?(Hash)
48
+ self.column = column
49
+ self.operator = operator
50
+ self.value = value
51
+ end
52
+
53
+ def map_operator(operator_to_look_up, reverse = false)
54
+ mapping = reverse ? OPERATOR_MAPPING.dup.invert : OPERATOR_MAPPING.dup
55
+ return operator_to_look_up if mapping.values.include?(operator_to_look_up)
56
+ found = mapping[operator_to_look_up.to_s]
57
+ end
58
+
59
+ def operator=(operator)
60
+ @operator = map_operator(operator)
61
+ raise(RestfulQuery::InvalidOperator, "#{@operator} is not a valid operator") unless @operator
62
+ end
63
+
64
+ def column=(column)
65
+ @column = column.to_s
66
+ end
67
+
68
+ def value=(value)
69
+ @value = parse_value(value)
70
+ end
71
+
72
+ def options=(options)
73
+ options.each {|k, v| @options[k.to_sym] = v }
74
+ end
75
+
76
+ def to_hash
77
+ {column => {map_operator(operator, true) => value}}
78
+ end
79
+
80
+ def to_condition_array
81
+ ["#{column} #{operator} #{placeholder}", value]
82
+ end
83
+
84
+ def placeholder
85
+ if ['IN', 'NOT IN'].include?(operator)
86
+ '(?)'
87
+ else
88
+ '?'
89
+ end
90
+ end
91
+
92
+ protected
93
+ def parse_value(value)
94
+ if operator == 'LIKE'
95
+ "%#{value}%"
96
+ elsif ['IN', 'NOT IN'].include?(operator) && !value.is_a?(Array)
97
+ value.split(options[:delimiter] || ',')
98
+ elsif options[:integer]
99
+ value.to_i
100
+ elsif options[:chronic]
101
+ Chronic.parse(value.to_s)
102
+ elsif value =~ /^\:/ && CONVERTABLE_VALUES.has_key?(value)
103
+ CONVERTABLE_VALUES[value]
104
+ else
105
+ value
106
+ end
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,166 @@
1
+ module RestfulQuery
2
+ class Parser
3
+ attr_reader :query, :exclude_columns, :map_columns, :integer_columns, :options
4
+
5
+ def initialize(query, options = {})
6
+ @options = options || {}
7
+ @exclude_columns = columns_from_options(:exclude, options)
8
+ @integer_columns = columns_from_options(:integer, options)
9
+ @map_columns = options[:map_columns] || {}
10
+ @default_sort = options[:default_sort] ? [Sort.parse(options[:default_sort])] : []
11
+ @single_sort = options[:single_sort] || false
12
+ @query = (query || {}).dup
13
+ @default_join = @query.delete(:join) || :and
14
+ extract_sorts_from_conditions
15
+ map_conditions
16
+ end
17
+
18
+ def conditions
19
+ conditions_hash.values.flatten
20
+ end
21
+
22
+ def has_conditions?
23
+ !conditions.empty?
24
+ end
25
+
26
+ def conditions_for(column)
27
+ conditions_hash[column.to_s]
28
+ end
29
+
30
+ def to_conditions_array(join = nil)
31
+ join ||= @default_join
32
+ join_string = (join == :or) ? ' OR ' : ' AND '
33
+ conditions_string = []
34
+ conditions_values = []
35
+ conditions.each do |c|
36
+ ca = c.to_condition_array
37
+ conditions_string << ca[0]
38
+ conditions_values << ca[1]
39
+ end
40
+ conditions_values.unshift(conditions_string.join(join_string))
41
+ end
42
+
43
+ def to_query_hash
44
+ hash = @query
45
+ hash['_sort'] = sorts.collect {|s| s.to_s } unless sorts.empty?
46
+ hash
47
+ end
48
+
49
+ def sort_sql
50
+ @sorts.collect {|s| s.to_sql }.join(', ')
51
+ end
52
+
53
+ def has_sort?
54
+ !sorts.empty?
55
+ end
56
+
57
+ def sorts
58
+ @sorts ||= []
59
+ end
60
+
61
+ def sorted_columns
62
+ sorts.collect {|s| s.column }
63
+ end
64
+
65
+ def sorted_by?(column_name)
66
+ column = map_column(column_name)
67
+ sorted_columns.include?(column.to_s)
68
+ end
69
+
70
+ def sort(column_name)
71
+ column = map_column(column_name)
72
+ sorts.detect {|s| s && s.column == column }
73
+ end
74
+
75
+ def set_sort(column_name, direction)
76
+ column = map_column(column_name)
77
+ if new_sort = self.sort(column_name)
78
+ if direction.nil?
79
+ self.sorts.reject! {|s| s.column == column.to_s }
80
+ else
81
+ new_sort.direction = direction
82
+ end
83
+ else
84
+ new_sort = Sort.new(column, direction)
85
+ self.sorts << new_sort
86
+ end
87
+ new_sort
88
+ end
89
+
90
+ def clear_default_sort!
91
+ @sorts.reject! {|s| s == @default_sort.first }
92
+ end
93
+
94
+ protected
95
+ def add_condition_for(column, condition)
96
+ conditions_hash[column.to_s] ||= []
97
+ conditions_hash[column.to_s] << condition
98
+ end
99
+
100
+ def conditions_hash
101
+ @conditions_hash ||= {}
102
+ end
103
+
104
+ def chronic_columns
105
+ if chronic = options[:chronic]
106
+ chronic.is_a?(Array) ? chronic.collect {|c| c.to_s } : ['created_at', 'updated_at']
107
+ else
108
+ []
109
+ end
110
+ end
111
+
112
+ def extract_sorts_from_conditions
113
+ @sorts = sorts_from_hash(@query.delete('_sort'))
114
+ @sorts = @default_sort if @sorts.empty?
115
+ end
116
+
117
+ def map_conditions
118
+ conditions = query.delete(:conditions) || query
119
+ if conditions.is_a?(Hash)
120
+ conditions_array = []
121
+ conditions.each do |column, hash_conditions|
122
+ if hash_conditions.is_a?(Hash)
123
+ hash_conditions.each do |operator, value|
124
+ conditions_array << {'column' => column, 'operator' => operator, 'value' => value}
125
+ end
126
+ else
127
+ conditions_array << {'column' => column, 'value' => hash_conditions}
128
+ end
129
+ end
130
+ conditions = conditions_array
131
+ end
132
+ # with a normalized array of conditions
133
+ conditions.each do |condition|
134
+ column, operator, value = condition['column'], condition['operator'] || 'eq', condition['value']
135
+ unless exclude_columns.include?(column.to_s) || value == ''
136
+ condition_options = {}
137
+ condition_options[:chronic] = true if chronic_columns.include?(column.to_s)
138
+ condition_options[:integer] = true if integer_columns.include?(column.to_s)
139
+ condition_options.merge!(condition['options'] || {})
140
+ column_name = map_column(column)
141
+ add_condition_for(column, Condition.new(column_name, value, operator, condition_options))
142
+ end
143
+ end
144
+ end
145
+
146
+ def sorts_from_hash(sorts)
147
+ sort_conditions = [sorts].flatten.compact
148
+ sort_conditions.collect do |c|
149
+ s = Sort.parse(c)
150
+ s.column = map_column(s.column)
151
+ s
152
+ end
153
+ end
154
+
155
+ def columns_from_options(column_type, options)
156
+ option = "#{column_type}_columns".to_sym
157
+ options[option] ? [options.delete(option)].flatten.collect {|c| c.to_s } : []
158
+ end
159
+
160
+ def map_column(column_name)
161
+ map_columns[column_name.to_s] || column_name.to_s
162
+ end
163
+
164
+
165
+ end
166
+ end
@@ -0,0 +1,72 @@
1
+ module RestfulQuery
2
+ class InvalidDirection < Error; end
3
+
4
+ class Sort
5
+ include Comparable
6
+
7
+ attr_reader :column, :direction
8
+
9
+ DIRECTIONS = {
10
+ 'up' => 'ASC',
11
+ 'asc' => 'ASC',
12
+ 'ASC' => 'ASC',
13
+ 'down' => 'DESC',
14
+ 'desc' => 'DESC',
15
+ 'DESC' => 'DESC'
16
+ }.freeze
17
+
18
+
19
+ def initialize(column, direction)
20
+ self.column = column
21
+ self.direction = direction
22
+ end
23
+
24
+ def self.parse(sort_string, split_on = /\-|\ /)
25
+ return unless sort_string
26
+ column, direction = sort_string.split(split_on)
27
+ new(column, direction)
28
+ end
29
+
30
+ def column=(column)
31
+ @column = column.to_s
32
+ end
33
+
34
+ def direction=(direction)
35
+ @direction = DIRECTIONS[direction.to_s]
36
+ raise(InvalidDirection, "'#{direction}' is not a valid order direction") unless @direction
37
+ end
38
+
39
+ def reverse_direction
40
+ direction == 'ASC' ? 'DESC' : 'ASC'
41
+ end
42
+
43
+ def ==(other)
44
+ return false unless other.is_a?(Sort)
45
+ column == other.column && direction == other.direction
46
+ end
47
+
48
+ # Makes a roundabout for directions nil -> desc -> asc -> nil
49
+ def self.next_direction(current_direction)
50
+ case current_direction.to_s.downcase
51
+ when 'desc'
52
+ 'asc'
53
+ when 'asc'
54
+ nil
55
+ else
56
+ 'desc'
57
+ end
58
+ end
59
+
60
+ def next_direction
61
+ self.class.next_direction(direction)
62
+ end
63
+
64
+ def to_s(join = '-')
65
+ "#{column}#{join}#{direction.downcase}"
66
+ end
67
+
68
+ def to_sql
69
+ "#{column} #{direction}"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,23 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'active_support'
5
+ begin
6
+ require 'chronic'
7
+ rescue LoadError
8
+ warn 'In order to use the time parsing functionalities you must install the Chronic gem: sudo gem install chronic'
9
+ end
10
+
11
+ module RestfulQuery
12
+ VERSION = '0.3.1'
13
+
14
+ class Error < RuntimeError; end
15
+ end
16
+
17
+ %w{condition sort parser can_query}.each do |lib|
18
+ require File.join("restful_query","#{lib}.rb")
19
+ end
20
+
21
+ if defined?(ActiveRecord)
22
+ ActiveRecord::Base.send(:include, RestfulQuery::CanQuery)
23
+ end
@@ -0,0 +1,25 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'restful_query.rb')
2
+
3
+ module RestfulQuery
4
+ class Sort
5
+
6
+ def to_sequel
7
+ column.to_sym.send(direction.downcase)
8
+ end
9
+
10
+ end
11
+ end
12
+
13
+ module Sequel
14
+ class Dataset
15
+
16
+ def restful_query(query_hash, options = {})
17
+ parser = RestfulQuery::Parser.new(query_hash, options = {})
18
+ collection = self
19
+ collection = collection.filter(*parser.to_conditions_array) if parser.has_conditions?
20
+ collection = collection.order(*parser.sorts.collect {|s| s.to_sequel }) if parser.has_sort?
21
+ collection
22
+ end
23
+ end
24
+
25
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ require File.join(File.expand_path(File.dirname(__FILE__)),'..','lib','restful_query.rb')
2
+
3
+ ActiveRecord::Base.send(:include, RestfulQuery::CanQuery)
@@ -0,0 +1,72 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{restful_query-rails3}
8
+ s.version = "0.4"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Aaron Quint"]
12
+ s.date = %q{2011-02-16}
13
+ s.description = %q{Rails3 Version of RestfulQuery, provides a simple interface in front of a complex parser to parse specially formatted query hashes into complex SQL queries. It includes ActiveRecord and Sequel extensions.}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "History.txt",
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "init.rb",
25
+ "restful_query-rails3.gemspec",
26
+ "lib/restful_query.rb",
27
+ "lib/restful_query/can_query.rb",
28
+ "lib/restful_query/condition.rb",
29
+ "lib/restful_query/parser.rb",
30
+ "lib/restful_query/sort.rb",
31
+ "lib/sequel/extensions/restful_query.rb",
32
+ "rails/init.rb",
33
+ "tasks/restful_query_tasks.rake",
34
+ "test/test_helper.rb",
35
+ "test/test_restful_query_can_query.rb",
36
+ "test/test_restful_query_condition.rb",
37
+ "test/test_restful_query_parser.rb",
38
+ "test/test_restful_query_sort.rb"
39
+ ]
40
+ s.rdoc_options = ["--charset=UTF-8"]
41
+ s.require_paths = ["lib"]
42
+ s.rubyforge_project = %q{quirkey}
43
+ s.rubygems_version = %q{1.3.5}
44
+ s.summary = %q{Simple ActiveRecord and Sequel queries from a RESTful and safe interface}
45
+ s.test_files = [
46
+ "test/test_helper.rb",
47
+ "test/test_restful_query_can_query.rb",
48
+ "test/test_restful_query_condition.rb",
49
+ "test/test_restful_query_parser.rb",
50
+ "test/test_restful_query_sort.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
58
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.2.0"])
59
+ s.add_runtime_dependency(%q<chronic>, [">= 0.2.3"])
60
+ s.add_development_dependency(%q<Shoulda>, [">= 1.2.0"])
61
+ else
62
+ s.add_dependency(%q<activesupport>, [">= 2.2.0"])
63
+ s.add_dependency(%q<chronic>, [">= 0.2.3"])
64
+ s.add_dependency(%q<Shoulda>, [">= 1.2.0"])
65
+ end
66
+ else
67
+ s.add_dependency(%q<activesupport>, [">= 2.2.0"])
68
+ s.add_dependency(%q<chronic>, [">= 0.2.3"])
69
+ s.add_dependency(%q<Shoulda>, [">= 1.2.0"])
70
+ end
71
+ end
72
+
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :restful_query do
3
+ # # Task goes here
4
+ # end