aub-record_filter 0.6.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +1 -1
- data/Rakefile +35 -1
- data/VERSION.yml +2 -2
- data/lib/record_filter/active_record.rb +55 -12
- data/lib/record_filter/conjunctions.rb +1 -1
- data/lib/record_filter/dsl/class_join.rb +1 -1
- data/lib/record_filter/dsl/conjunction.rb +1 -1
- data/lib/record_filter/dsl/conjunction_dsl.rb +244 -18
- data/lib/record_filter/dsl/dsl.rb +90 -25
- data/lib/record_filter/dsl/dsl_factory.rb +19 -0
- data/lib/record_filter/dsl/group_by.rb +1 -1
- data/lib/record_filter/dsl/join.rb +1 -1
- data/lib/record_filter/dsl/join_condition.rb +1 -1
- data/lib/record_filter/dsl/join_dsl.rb +36 -1
- data/lib/record_filter/dsl/limit.rb +1 -1
- data/lib/record_filter/dsl/named_filter.rb +1 -1
- data/lib/record_filter/dsl/order.rb +1 -1
- data/lib/record_filter/dsl/restriction.rb +218 -22
- data/lib/record_filter/dsl.rb +16 -1
- data/lib/record_filter/filter.rb +15 -21
- data/lib/record_filter/group_by.rb +1 -1
- data/lib/record_filter/join.rb +1 -1
- data/lib/record_filter/order.rb +1 -1
- data/lib/record_filter/query.rb +4 -5
- data/lib/record_filter/restrictions.rb +1 -1
- data/lib/record_filter/table.rb +27 -13
- data/lib/record_filter.rb +26 -5
- data/spec/active_record_spec.rb +144 -0
- data/spec/exception_spec.rb +41 -0
- data/spec/explicit_join_spec.rb +5 -4
- data/spec/limits_and_ordering_spec.rb +6 -5
- data/spec/models.rb +19 -0
- data/spec/named_filter_spec.rb +5 -2
- data/spec/proxying_spec.rb +26 -12
- data/spec/restrictions_spec.rb +40 -0
- data/spec/test.db +0 -0
- data/test/performance_test.rb +36 -0
- data/{lib/record_filter/join_table.rb → test/test.db} +0 -0
- metadata +8 -3
data/README.rdoc
CHANGED
data/Rakefile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
ENV['RUBYOPT'] = '-W1'
|
1
|
+
# ENV['RUBYOPT'] = '-W1'
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'rake'
|
@@ -22,3 +22,37 @@ rescue LoadError
|
|
22
22
|
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
23
23
|
end
|
24
24
|
|
25
|
+
# Try to use hanna to create spiffier docs.
|
26
|
+
begin
|
27
|
+
require 'hanna/rdoctask'
|
28
|
+
rescue LoadError
|
29
|
+
require 'rake/rdoctask'
|
30
|
+
end
|
31
|
+
|
32
|
+
Rake::RDocTask.new do |rdoc|
|
33
|
+
if File.exist?('VERSION.yml')
|
34
|
+
config = YAML.load(File.read('VERSION.yml'))
|
35
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
36
|
+
else
|
37
|
+
version = ""
|
38
|
+
end
|
39
|
+
|
40
|
+
rdoc.rdoc_dir = 'rdoc'
|
41
|
+
rdoc.title = "record_filter #{version}"
|
42
|
+
rdoc.rdoc_files.include('README*')
|
43
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
44
|
+
end
|
45
|
+
|
46
|
+
begin
|
47
|
+
require 'ruby-prof/task'
|
48
|
+
|
49
|
+
RubyProf::ProfileTask.new do |t|
|
50
|
+
t.test_files = FileList['test/performance_test.rb']
|
51
|
+
t.output_dir = 'perf'
|
52
|
+
t.printer = :graph_html
|
53
|
+
t.min_percent = 5
|
54
|
+
end
|
55
|
+
rescue LoadError
|
56
|
+
puts 'Ruby-prof not available. Profiling tests are disabled.'
|
57
|
+
end
|
58
|
+
|
data/VERSION.yml
CHANGED
@@ -2,33 +2,68 @@ module RecordFilter
|
|
2
2
|
# The ActiveRecordExtension module is mixed in to ActiveRecord::Base to form the
|
3
3
|
# top-level API for interacting with record_filter. It adds public methods for
|
4
4
|
# executing ad-hoc filters as well as for creating and querying named filters.
|
5
|
+
# See RecordFilter::ActiveRecordExtension::ClassMethods for more detail on the
|
6
|
+
# API.
|
5
7
|
module ActiveRecordExtension
|
6
8
|
module ClassMethods
|
7
9
|
|
8
|
-
#
|
10
|
+
# Create a filter on the fly to find a set of results that matches the given criteria.
|
11
|
+
# This method, which can be called on any ActiveRecord::Base subclass, accepts a block
|
12
|
+
# that defines the contents of the filter and returns a Filter object that contains a list
|
13
|
+
# of the objects resulting from the query. See the documentation for RecordFilter::DSL
|
14
|
+
# for more information on how to specify the filter.
|
9
15
|
#
|
10
16
|
# ==== Parameters
|
11
|
-
# block
|
17
|
+
# block<Proc>::
|
12
18
|
# A block that specifies the contents of the filter.
|
13
19
|
#
|
14
20
|
# ==== Returns
|
15
|
-
# Filter::
|
16
|
-
# treated as an array of the results.
|
21
|
+
# Filter::
|
22
|
+
# The Filter object resulting from the query, which can be treated as an array of the results.
|
17
23
|
#
|
18
24
|
# ==== Example
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
|
25
|
+
# Blog.filter do
|
26
|
+
# having(:posts).with(:name, nil)
|
27
|
+
# end
|
28
|
+
#
|
23
29
|
# @public
|
24
30
|
def filter(&block)
|
25
31
|
Filter.new(self, nil, &block)
|
26
32
|
end
|
27
33
|
|
34
|
+
# Create a new named filter, which defines a function on the callee class that provides easy
|
35
|
+
# access to the query defined by the filter. Any number of named filters can be created on a
|
36
|
+
# class, and they can also be chained to create complex queries out of simple building blocks.
|
37
|
+
# In addition, named filters can accept any number of arguments in order to allow customization
|
38
|
+
# of their behavior when used. For more details on how to specify the contents of named filters,
|
39
|
+
# see the documentation for RecordFilter::DSL.
|
40
|
+
#
|
41
|
+
# Post.named_filter(:without_permalink) do
|
42
|
+
# with(:permalink, nil)
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# Post.named_filter(:created_after) do |time|
|
46
|
+
# with(:created_at).gt(time)
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# Post.without_permalink # :conditions => ['permalink IS NULL']
|
50
|
+
# Post.created_after(3.hours.ago) # :conditions => ['created_at > ?', 3.hours.ago]
|
51
|
+
# Post.without_permalink.created_after(3.hours.ago) # :conditions => ['permalink IS NULL AND created_at > ?', 3.hours.ago]
|
52
|
+
#
|
53
|
+
# ==== Raises
|
54
|
+
# InvalidFilterNameException::
|
55
|
+
# There is already a named filter with the given name on this class or one of its superclasses.
|
56
|
+
#
|
57
|
+
# ==== Returns
|
58
|
+
# nil
|
59
|
+
#
|
60
|
+
# @public
|
28
61
|
def named_filter(name, &block)
|
29
|
-
|
62
|
+
if named_filters.include?(name.to_sym)
|
63
|
+
raise InvalidFilterNameException.new("A named filter with the name #{name} already exists on the class #{self.name}.")
|
64
|
+
end
|
30
65
|
local_named_filters << name.to_sym
|
31
|
-
DSL::
|
66
|
+
DSL::DSLFactory::subclass(self).module_eval do
|
32
67
|
define_method(name, &block)
|
33
68
|
end
|
34
69
|
|
@@ -37,8 +72,16 @@ module RecordFilter
|
|
37
72
|
Filter.new(self, name, *args)
|
38
73
|
end
|
39
74
|
end
|
75
|
+
nil
|
40
76
|
end
|
41
77
|
|
78
|
+
# Retreive a list of named filters that apply to a specific class, including ones
|
79
|
+
# that were defined in its superclasses.
|
80
|
+
#
|
81
|
+
# ==== Returns
|
82
|
+
# Array:: A list of the names of the named filters, as symbols.
|
83
|
+
#
|
84
|
+
# @public
|
42
85
|
def named_filters
|
43
86
|
result = local_named_filters.dup
|
44
87
|
result.concat(superclass.named_filters) if (superclass && superclass.respond_to?(:named_filters))
|
@@ -46,8 +89,8 @@ module RecordFilter
|
|
46
89
|
end
|
47
90
|
|
48
91
|
protected
|
49
|
-
|
50
|
-
def local_named_filters
|
92
|
+
|
93
|
+
def local_named_filters # :nodoc:
|
51
94
|
@local_named_filters ||= []
|
52
95
|
end
|
53
96
|
end
|
@@ -1,78 +1,304 @@
|
|
1
1
|
module RecordFilter
|
2
2
|
module DSL
|
3
|
+
# The ConjunctionDSL is used for specifying restrictions, conjunctions and joins, with methods that
|
4
|
+
# can be accessed from any point in a filter declaration. The where method is used for creating
|
5
|
+
# restrictions, conjunctions are specified through any_of, all_of, none_of and not_all_of, and joins
|
6
|
+
# are described by having and join.
|
3
7
|
class ConjunctionDSL
|
4
8
|
|
5
|
-
attr_reader :conjunction
|
9
|
+
attr_reader :conjunction # :nodoc:
|
6
10
|
|
7
|
-
def initialize(model_class, conjunction)
|
11
|
+
def initialize(model_class, conjunction) # :nodoc:
|
8
12
|
@model_class = model_class
|
9
13
|
@conjunction = conjunction
|
10
14
|
end
|
11
15
|
|
12
|
-
#
|
16
|
+
# Specify a condition on the given column, which will be added to the WHERE clause
|
17
|
+
# of the resulting query. This method returns a Restriction object, which can be called
|
18
|
+
# with any of the specific restriction methods described there in order to create many
|
19
|
+
# types of conditions. If both a column name and a value are passed, this will automatically
|
20
|
+
# create an equality condition, so the following two examples are equal:
|
21
|
+
# with(:permalink, 'junk')
|
22
|
+
# with(:permalink).equal_to('junk')
|
23
|
+
# If nil is passed as the second argument, an is_null restriction will automatically be
|
24
|
+
# created, so these two examples are equal as well:
|
25
|
+
# with(:permalink, nil)
|
26
|
+
# with(:permalink).is_null
|
27
|
+
# This method can be called at any point in the filter specification, and the appropriate
|
28
|
+
# clauses will be created if it is called within or other conjunctions.
|
29
|
+
#
|
30
|
+
# ==== Parameters
|
31
|
+
# column<Symbol>::
|
32
|
+
# The name of the column to restrict. The column is assumed to exist in the table that is
|
33
|
+
# currently in scope. In the outer block of a filter, that would be the table being filtered,
|
34
|
+
# and within joins it would be the table being joined.
|
35
|
+
# value<value, optional>::
|
36
|
+
# If specified, the value will be used to automatically create either an equality restriction
|
37
|
+
# or an IS NULL test, as described above.
|
38
|
+
#
|
39
|
+
# ==== Returns
|
40
|
+
# Restriction::
|
41
|
+
# A restriction object that can be used to create a specific condition. See the API in
|
42
|
+
# Restriction for options.
|
43
|
+
#
|
44
|
+
# ==== Alternatives
|
45
|
+
# The value parameter is optional, as described above.
|
46
|
+
#
|
47
|
+
# @public
|
13
48
|
def with(column, value=Restriction::DEFAULT_VALUE)
|
14
49
|
return @conjunction.add_restriction(column, value)
|
15
50
|
end
|
16
51
|
|
17
|
-
#
|
52
|
+
# Add a where clause that will pass if any of the conditions specified within it
|
53
|
+
# are true. Any restrictions created inside the given block are OR'ed together
|
54
|
+
# in the final query, and the block can contain any number of joins, restrictions, or
|
55
|
+
# other conjunctions.
|
56
|
+
# Blog.filter do
|
57
|
+
# any_of do
|
58
|
+
# with(:created_at, nil)
|
59
|
+
# with(:created_at).greater_than(3.days.ago)
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# # :conditions => { ['blogs.created_at IS NULL OR blogs.created_at > ?', 3.days.ago] }
|
64
|
+
#
|
65
|
+
# ==== Parameters
|
66
|
+
# block<Proc>::
|
67
|
+
# The block can contain any sequence of calls, and the conditions that it contains will be
|
68
|
+
# OR'ed together to create a where clause.
|
69
|
+
#
|
70
|
+
# ==== Returns
|
71
|
+
# nil
|
72
|
+
#
|
73
|
+
# @public
|
18
74
|
def any_of(&block)
|
19
75
|
@conjunction.add_conjunction(:any_of, &block)
|
20
76
|
nil
|
21
77
|
end
|
22
78
|
|
23
|
-
#
|
79
|
+
# Add a where clause that will pass only if all of the conditions specified within it
|
80
|
+
# are true. Any restrictions created inside the given block are AND'ed together
|
81
|
+
# in the final query, and the block can contain any number of joins, restrictions, or
|
82
|
+
# other conjunctions.
|
83
|
+
# Blog.filter do
|
84
|
+
# all_of do
|
85
|
+
# with(:created_at, nil)
|
86
|
+
# with(:created_at).greater_than(3.days.ago)
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# # :conditions => { ['blogs.created_at IS NULL AND blogs.created_at > ?', 3.days.ago] }
|
91
|
+
#
|
92
|
+
# ==== Parameters
|
93
|
+
# block<Proc>::
|
94
|
+
# The block can contain any sequence of calls, and the conditions that it contains will be
|
95
|
+
# AND'ed together to create a where clause.
|
96
|
+
#
|
97
|
+
# ==== Returns
|
98
|
+
# nil
|
99
|
+
#
|
100
|
+
# @public
|
24
101
|
def all_of(&block)
|
25
102
|
@conjunction.add_conjunction(:all_of, &block)
|
26
103
|
nil
|
27
104
|
end
|
28
105
|
|
106
|
+
# Add a where clause that will pass only if none of the conditions specified within it
|
107
|
+
# are true. Any restrictions created inside the given block are OR'ed together
|
108
|
+
# in the final query and the result is negated. The block can contain any number of joins,
|
109
|
+
# restrictions, or other conjunctions.
|
110
|
+
# Blog.filter do
|
111
|
+
# none_of do
|
112
|
+
# with(:created_at, nil)
|
113
|
+
# with(:created_at).greater_than(3.days.ago)
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# # :conditions => { ['NOT (blogs.created_at IS NULL OR blogs.created_at > ?)', 3.days.ago] }
|
118
|
+
#
|
119
|
+
# ==== Parameters
|
120
|
+
# block<Proc>::
|
121
|
+
# The block can contain any sequence of calls, and the conditions that it contains will be
|
122
|
+
# OR'ed together and then negated to create a where clause.
|
123
|
+
#
|
124
|
+
# ==== Returns
|
125
|
+
# nil
|
126
|
+
#
|
127
|
+
# @public
|
29
128
|
def none_of(&block)
|
30
129
|
@conjunction.add_conjunction(:none_of, &block)
|
31
130
|
nil
|
32
131
|
end
|
33
132
|
|
133
|
+
# Add a where clause that will pass unless all of the conditions specified within it
|
134
|
+
# are true. Any restrictions created inside the given block are AND'ed together
|
135
|
+
# in the final query and the result is negated. The block can contain any number of joins,
|
136
|
+
# restrictions, or other conjunctions.
|
137
|
+
# Blog.filter do
|
138
|
+
# none_of do
|
139
|
+
# with(:created_at, nil)
|
140
|
+
# with(:created_at).greater_than(3.days.ago)
|
141
|
+
# end
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# # :conditions => { ['NOT (blogs.created_at IS NULL AND blogs.created_at > ?)', 3.days.ago] }
|
145
|
+
#
|
146
|
+
# ==== Parameters
|
147
|
+
# block<Proc>::
|
148
|
+
# The block can contain any sequence of calls, and the conditions that it contains will be
|
149
|
+
# AND'ed together and then negated to create a where clause.
|
150
|
+
#
|
151
|
+
# ==== Returns
|
152
|
+
# nil
|
153
|
+
#
|
154
|
+
# @public
|
34
155
|
def not_all_of(&block)
|
35
156
|
@conjunction.add_conjunction(:not_all_of, &block)
|
36
157
|
nil
|
37
158
|
end
|
38
159
|
|
39
|
-
# join
|
40
|
-
|
160
|
+
# Create an implicit join using an association as the target. This method allows you to
|
161
|
+
# easily specify a join without specifying the columns to use by taking any needed data
|
162
|
+
# from the specified ActiveRecord association. If given, the block will be evaluated in
|
163
|
+
# the context of the table that has been joined, so any restrictions or other joins will
|
164
|
+
# be performed using its columns and associations. For example, if a Post has_many comments
|
165
|
+
# then the following code will join to the comments table and restrict the comments based
|
166
|
+
# on their created_at field:
|
167
|
+
# Post.filter do
|
168
|
+
# having(:comments) do
|
169
|
+
# with(:created_at).greater_than(3.days.ago)
|
170
|
+
# end
|
171
|
+
# end
|
172
|
+
# If one argument is given, it is assumed to be a symbol representing the name of the association
|
173
|
+
# that will be used for the join and a join type of :inner will be used. If two arguments are
|
174
|
+
# provided, the first one is assumed to be the join type, which can be one of :inner, :left or
|
175
|
+
# :right and the second one is the association name. An alias will automatically be created
|
176
|
+
# for the joined table named "#{left_table}__#{association_name}", so in the above example, the
|
177
|
+
# alias would be posts__comments.
|
178
|
+
#
|
179
|
+
# ==== Parameters
|
180
|
+
# join_type<Symbol>::
|
181
|
+
# Specifies the type of join to perform, and can be one of :inner, :left or :right. :left
|
182
|
+
# and :right will create left and right outer joins, respectively.
|
183
|
+
# association<Symbol>::
|
184
|
+
# The name of the association to use as a base for the join.
|
185
|
+
#
|
186
|
+
# ==== Returns
|
187
|
+
# ConjunctionDSL::
|
188
|
+
# A DSL object is returned in order to allow constructs like: having(:comments).with(:offensive, true)
|
189
|
+
#
|
190
|
+
# ==== Alternatives
|
191
|
+
# If only one argument is given, the join type will default to :inner and the first argument will
|
192
|
+
# be used as the association name.
|
193
|
+
#
|
194
|
+
# @public
|
195
|
+
def having(join_type, association=nil, &block)
|
41
196
|
if association.nil?
|
42
|
-
association, join_type =
|
43
|
-
else
|
44
|
-
join_type = join_type_or_association
|
197
|
+
association, join_type = join_type, nil
|
45
198
|
end
|
46
199
|
@conjunction.add_join(association, join_type, &block)
|
47
200
|
end
|
48
201
|
|
202
|
+
# Create an explicit join on the table of the given class. This method allows more complex
|
203
|
+
# joins to be speficied than can be created using having, including jump joins and ones that
|
204
|
+
# include conditions on column values. The method accepts a block that can contain any sequence
|
205
|
+
# of conjunctions, restrictions, or other joins, but it must also contain at least one call to
|
206
|
+
# JoinDSL.on to specify the conditions for the join.
|
207
|
+
#
|
208
|
+
# ==== Parameters
|
209
|
+
# clazz<Class>::
|
210
|
+
# The class that is being joined to.
|
211
|
+
# join_type<Symbol>::
|
212
|
+
# Indicates the type of join to use and must be one of :inner, :left or :right, where :left
|
213
|
+
# or :right will create a LEFT or RIGHT OUTER join respectively.
|
214
|
+
# table_alias<String, optional>::
|
215
|
+
# If provided, will specify an alias to use in the SQL when referring to the joined table.
|
216
|
+
# If the argument is not given, the alias will be "#{left_table}__#{clazz.name}"
|
217
|
+
# block<Proc>
|
218
|
+
# The contents of the join block can contain any sequence of conjunctions, restrictions, or joins.
|
219
|
+
#
|
220
|
+
# ==== Returns
|
221
|
+
# JoinDSL::
|
222
|
+
# A DSL object that can be used to specify the contents of the join. Returning this value allows
|
223
|
+
# for constructions like: join(Comment, :inner).on(:id => :post_id)
|
224
|
+
#
|
225
|
+
# @public
|
49
226
|
def join(clazz, join_type, table_alias=nil, &block)
|
50
227
|
@conjunction.add_class_join(clazz, join_type, table_alias, &block)
|
51
228
|
end
|
52
229
|
|
230
|
+
# Access the class that the current filter is being applied to. This is necessary
|
231
|
+
# because the filter is evaluated in the context of the DSL object, so self will
|
232
|
+
# not give access to any methods that need to be called on the filtered class.
|
233
|
+
# It is especially useful in named filters that may be defined in a way that allows
|
234
|
+
# them to apply to multiple classes.
|
235
|
+
#
|
236
|
+
# ==== Returns
|
237
|
+
# Class::
|
238
|
+
# The class that is currently being filtered.
|
239
|
+
#
|
240
|
+
# @public
|
53
241
|
def filter_class
|
54
242
|
@model_class
|
55
243
|
end
|
244
|
+
|
245
|
+
# Enable calling of named filters from within other filters by catching unknown calls
|
246
|
+
# and assuming that they are to named filters. This enables the following examples:
|
247
|
+
# class Post < ActiveRecord::Base
|
248
|
+
# has_many :comments
|
249
|
+
# named_filter(:empty) { with(:contents).nil }
|
250
|
+
# end
|
251
|
+
#
|
252
|
+
# class Comment < ActiveRecord::Base
|
253
|
+
# belongs_to :post
|
254
|
+
# named_filter(:offensive) { |value| with(:offensive, value) }
|
255
|
+
# end
|
256
|
+
#
|
257
|
+
# Post.filter do
|
258
|
+
# with(:created_at).less_than(1.hour.ago)
|
259
|
+
# empty
|
260
|
+
# end
|
261
|
+
#
|
262
|
+
# # Results in:
|
263
|
+
# # :conditions => { ['posts.created_at < ? AND posts.contents IS NULL', 1.hour.ago] }
|
264
|
+
# # And even cooler:
|
265
|
+
#
|
266
|
+
# Post.filter do
|
267
|
+
# having(:comments).offensive(true)
|
268
|
+
# end
|
269
|
+
#
|
270
|
+
# # Results in:
|
271
|
+
# # :conditions => { ['posts__comments.offensive = ?', true] }
|
272
|
+
# # :joins => { 'INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id' }
|
273
|
+
#
|
274
|
+
# ==== Parameters
|
275
|
+
# args<Array>::
|
276
|
+
# The arguments to pass to the named filter when called.
|
277
|
+
#
|
278
|
+
# @public
|
279
|
+
def method_missing(method, *args)
|
280
|
+
@conjunction.add_named_filter(method, *args)
|
281
|
+
end
|
56
282
|
|
57
|
-
|
283
|
+
#
|
284
|
+
# Define these_methods here just so that we can throw exceptions when they are called. They should not
|
285
|
+
# be callable in the scope of a conjunction_dsl.
|
286
|
+
#
|
287
|
+
def limit(offset_or_limit, limit=nil) # :nodoc:
|
58
288
|
raise InvalidFilterException.new('Calls to limit can only be made in the outer block of a filter.')
|
59
289
|
end
|
60
290
|
|
61
|
-
def order(column, direction=:asc)
|
291
|
+
def order(column, direction=:asc) # :nodoc:
|
62
292
|
raise InvalidFilterException.new('Calls to order can only be made in the outer block of a filter.')
|
63
293
|
end
|
64
294
|
|
65
|
-
def group_by(column)
|
295
|
+
def group_by(column) # :nodoc:
|
66
296
|
raise InvalidFilterException.new('Calls to group_by can only be made in the outer block of a filter.')
|
67
297
|
end
|
68
298
|
|
69
|
-
def on(column, value=Restriction::DEFAULT_VALUE)
|
299
|
+
def on(column, value=Restriction::DEFAULT_VALUE) # :nodoc:
|
70
300
|
raise InvalidFilterException.new('Calls to on can only be made in the block of a call to join.')
|
71
301
|
end
|
72
|
-
|
73
|
-
def method_missing(method, *args)
|
74
|
-
@conjunction.add_named_filter(method, *args)
|
75
|
-
end
|
76
302
|
end
|
77
303
|
end
|
78
304
|
end
|
@@ -2,42 +2,107 @@ module RecordFilter
|
|
2
2
|
module DSL
|
3
3
|
class DSL < ConjunctionDSL
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
|
5
|
+
# Define an limit and/or offset for the results returned from the current
|
6
|
+
# filter. This method can only be called from the outermost scope of a filter
|
7
|
+
# (i.e. not inside of a having block, etc.). If it is called multiple times, the
|
8
|
+
# last one will override any others.
|
9
|
+
#
|
10
|
+
# ==== Parameters
|
11
|
+
# offset<Integer>::
|
12
|
+
# Used for the offset of the query.
|
13
|
+
# limit<Integer::
|
14
|
+
# Used as the limit for the query.
|
15
|
+
#
|
16
|
+
# ==== Returns
|
17
|
+
# nil
|
18
|
+
#
|
19
|
+
# ==== Alternatives
|
20
|
+
# If called with a single argument, it is assumed to represent the limit, and
|
21
|
+
# no offset will be specified.
|
22
|
+
#
|
23
|
+
# @public
|
24
|
+
def limit(offset, limit=nil)
|
23
25
|
if limit
|
24
|
-
@conjunction.add_limit(limit,
|
26
|
+
@conjunction.add_limit(limit, offset)
|
25
27
|
else
|
26
|
-
@conjunction.add_limit(
|
28
|
+
@conjunction.add_limit(offset, nil)
|
27
29
|
end
|
28
30
|
nil
|
29
31
|
end
|
30
32
|
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# order
|
35
|
-
# order
|
33
|
+
# Define an order clause for the current query, with options for specifying
|
34
|
+
# both the column to use as well as the direction. This method can only be called
|
35
|
+
# in the outermost scope of a filter (i.e. not inside of a having block, etc.).
|
36
|
+
# Multiple calls will create multiple order clauses in the resulting query, and
|
37
|
+
# they will be added in the order in which they were called in the filter. In order
|
38
|
+
# to specify ordering on columns added through joins, a hash can be passed as the
|
39
|
+
# first argument, specifying a path through the joins to the column, as in this
|
40
|
+
# example:
|
41
|
+
#
|
42
|
+
# Blog.filter do
|
43
|
+
# having(:posts) do
|
44
|
+
# having(:comments).with(:created_at).greater_than(3.days.ago)
|
45
|
+
# end
|
46
|
+
# order(:posts => :comments => :created_at, :desc)
|
47
|
+
# order(:id, :asc)
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# ==== Parameters
|
51
|
+
# column<Symbol, Hash>::
|
52
|
+
# Specify the column for the ordering. If a symbol is given, it is assumed to represent
|
53
|
+
# a column in the class that is being filtered. With a hash argument, it is possible
|
54
|
+
# to specify a path to a column in one of the joined tables, as seen above.
|
55
|
+
# direction<Symbol>::
|
56
|
+
# Specifies the direction of the join. Should be either :asc or :desc.
|
57
|
+
#
|
58
|
+
# ==== Returns
|
59
|
+
# nil
|
60
|
+
#
|
61
|
+
# ==== Raises
|
62
|
+
# InvalidFilterException::
|
63
|
+
# If the direction is neither :asc nor :desc.
|
64
|
+
#
|
65
|
+
# ==== Alternatives
|
66
|
+
# As described above, it is possible to pass either a symbol or a hash as the first
|
67
|
+
# argument.
|
68
|
+
#
|
69
|
+
# @public
|
36
70
|
def order(column, direction=:asc)
|
71
|
+
unless [:asc, :desc].include?(direction)
|
72
|
+
raise InvalidFilterException.new("The direction for orders must be either :asc or :desc but was #{direction}")
|
73
|
+
end
|
37
74
|
@conjunction.add_order(column, direction)
|
38
75
|
nil
|
39
76
|
end
|
40
77
|
|
78
|
+
# Specify a group_by clause for the resulting query. This method can only be called
|
79
|
+
# in the outermost scope of a filter (i.e. not inside of a having block, etc.).
|
80
|
+
# Multiple calls will create multiple group_by clauses in the resulting query, and
|
81
|
+
# they will be added in the order in which they were called in the filter. In order
|
82
|
+
# to specify grouping on columns added through joins, a hash can be passed as the
|
83
|
+
# argument, specifying a path through the joins to the column, as in this example:
|
84
|
+
#
|
85
|
+
# Blog.filter do
|
86
|
+
# having(:posts) do
|
87
|
+
# having(:comments).with(:created_at).greater_than(3.days.ago)
|
88
|
+
# end
|
89
|
+
# group_by(:posts => :comments => :offensive)
|
90
|
+
# group_by(:id)
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# ==== Parameters
|
94
|
+
# column<Symbol, Hash>::
|
95
|
+
# If a symbol is specified, it is taken to represent the name of a column on the
|
96
|
+
# class being filtered. If a hash is given, it should represent a path through the
|
97
|
+
# joins to a column in one of the joined tables.
|
98
|
+
#
|
99
|
+
# ==== Returns
|
100
|
+
# nil
|
101
|
+
#
|
102
|
+
# ==== Alternatives
|
103
|
+
# As described above, it is possible to pass either a symbol or a hash as the argument.
|
104
|
+
#
|
105
|
+
# @public
|
41
106
|
def group_by(column)
|
42
107
|
@conjunction.add_group_by(column)
|
43
108
|
nil
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RecordFilter
|
2
|
+
module DSL
|
3
|
+
class DSLFactory # :nodoc: all
|
4
|
+
SUBCLASSES = Hash.new do |h, k|
|
5
|
+
h[k] = Class.new(RecordFilter::DSL::DSL)
|
6
|
+
end
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def create(clazz)
|
10
|
+
subclass(clazz).new(clazz, Conjunction.new(clazz, :all_of))
|
11
|
+
end
|
12
|
+
|
13
|
+
def subclass(clazz)
|
14
|
+
SUBCLASSES[clazz.object_id]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|