record_filter 0.9.12
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 +9 -0
- data/CHANGELOG +232 -0
- data/README.rdoc +354 -0
- data/Rakefile +92 -0
- data/TODO +3 -0
- data/VERSION.yml +4 -0
- data/config/roodi.yml +14 -0
- data/lib/record_filter/active_record.rb +108 -0
- data/lib/record_filter/column_parser.rb +14 -0
- data/lib/record_filter/conjunctions.rb +169 -0
- data/lib/record_filter/dsl/class_join.rb +16 -0
- data/lib/record_filter/dsl/conjunction.rb +57 -0
- data/lib/record_filter/dsl/conjunction_dsl.rb +317 -0
- data/lib/record_filter/dsl/dsl.rb +143 -0
- data/lib/record_filter/dsl/dsl_factory.rb +19 -0
- data/lib/record_filter/dsl/group_by.rb +11 -0
- data/lib/record_filter/dsl/join.rb +12 -0
- data/lib/record_filter/dsl/join_condition.rb +21 -0
- data/lib/record_filter/dsl/join_dsl.rb +49 -0
- data/lib/record_filter/dsl/limit.rb +12 -0
- data/lib/record_filter/dsl/named_filter.rb +12 -0
- data/lib/record_filter/dsl/order.rb +12 -0
- data/lib/record_filter/dsl/restriction.rb +314 -0
- data/lib/record_filter/dsl.rb +21 -0
- data/lib/record_filter/filter.rb +105 -0
- data/lib/record_filter/group_by.rb +21 -0
- data/lib/record_filter/join.rb +66 -0
- data/lib/record_filter/order.rb +27 -0
- data/lib/record_filter/query.rb +60 -0
- data/lib/record_filter/restriction_factory.rb +21 -0
- data/lib/record_filter/restrictions.rb +97 -0
- data/lib/record_filter/table.rb +172 -0
- data/lib/record_filter.rb +35 -0
- data/record_filter.gemspec +108 -0
- data/script/console +8 -0
- data/spec/active_record_spec.rb +211 -0
- data/spec/exception_spec.rb +208 -0
- data/spec/explicit_join_spec.rb +132 -0
- data/spec/implicit_join_spec.rb +403 -0
- data/spec/limits_and_ordering_spec.rb +230 -0
- data/spec/models.rb +109 -0
- data/spec/named_filter_spec.rb +264 -0
- data/spec/proxying_spec.rb +63 -0
- data/spec/restrictions_spec.rb +251 -0
- data/spec/select_spec.rb +79 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/test.db +0 -0
- data/tasks/db.rake +106 -0
- data/tasks/rcov.rake +9 -0
- data/tasks/spec.rake +10 -0
- data/test/performance_test.rb +39 -0
- data/test/test.db +0 -0
- metadata +137 -0
@@ -0,0 +1,317 @@
|
|
1
|
+
module RecordFilter
|
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.
|
7
|
+
class ConjunctionDSL
|
8
|
+
|
9
|
+
attr_reader :conjunction # :nodoc:
|
10
|
+
|
11
|
+
def initialize(model_class, conjunction) # :nodoc:
|
12
|
+
@model_class = model_class
|
13
|
+
@conjunction = conjunction
|
14
|
+
end
|
15
|
+
|
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
|
48
|
+
def with(column, value=Restriction::DEFAULT_VALUE)
|
49
|
+
return @conjunction.add_restriction(column, value)
|
50
|
+
end
|
51
|
+
|
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
|
74
|
+
def any_of(&block)
|
75
|
+
@conjunction.add_conjunction(:any_of, &block)
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
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
|
101
|
+
def all_of(&block)
|
102
|
+
@conjunction.add_conjunction(:all_of, &block)
|
103
|
+
nil
|
104
|
+
end
|
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
|
128
|
+
def none_of(&block)
|
129
|
+
@conjunction.add_conjunction(:none_of, &block)
|
130
|
+
nil
|
131
|
+
end
|
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
|
155
|
+
def not_all_of(&block)
|
156
|
+
@conjunction.add_conjunction(:not_all_of, &block)
|
157
|
+
nil
|
158
|
+
end
|
159
|
+
|
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 given ActiveRecord association. If provided, 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
|
+
# Options can be passed to provide a custom join type or table alias through the options
|
173
|
+
# hash. The :join_type option can be either :inner, :left or :right and will default to
|
174
|
+
# :inner if not provided. The :alias option allows you to provide an alias for use in joining
|
175
|
+
# the table. If the same association is joined twice with different aliases, it will be treated
|
176
|
+
# as two separate joins. By default an alias will automatically be created
|
177
|
+
# for the joined table named "#{left_table}__#{association_name}", so in the above example, the
|
178
|
+
# alias would be posts__comments. It is also possible to provide a hash as the association
|
179
|
+
# name, in which case a trail of associations can be joined in one statment.
|
180
|
+
#
|
181
|
+
# ==== Parameters
|
182
|
+
# association<Symbol>::
|
183
|
+
# The name of the association to use as a base for the join.
|
184
|
+
# options<Hash>::
|
185
|
+
# An options hash (see below)
|
186
|
+
#
|
187
|
+
# ==== Options (options)
|
188
|
+
# :join_type<Symbol>::
|
189
|
+
# The type of join to use. Available options are :inner, :left and :right. Defaults to :inner.
|
190
|
+
# :alias<String>::
|
191
|
+
# An alias to use for the table name in the join. If provided, will create a unique name for
|
192
|
+
# the join and allow the same association to be joined multiple times. By default, the alias
|
193
|
+
# will be "#{left_table}__#{association_name}".
|
194
|
+
#
|
195
|
+
# ==== Returns
|
196
|
+
# ConjunctionDSL::
|
197
|
+
# A DSL object is returned in order to allow constructs like: having(:comments).with(:offensive, true)
|
198
|
+
#
|
199
|
+
# ==== Alternatives
|
200
|
+
# If only one argument is given, the join type will default to :inner and the first argument will
|
201
|
+
# be used as the association name.
|
202
|
+
#
|
203
|
+
# @public
|
204
|
+
def having(association, options={}, &block)
|
205
|
+
@conjunction.add_join(association, options[:join_type], options[:alias], &block)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Create an explicit join on the table of the given class. This method allows more complex
|
209
|
+
# joins to be speficied than can be created using having, including jump joins and ones that
|
210
|
+
# include conditions on column values. The method accepts a block that can contain any sequence
|
211
|
+
# of conjunctions, restrictions, or other joins, but it must also contain at least one call to
|
212
|
+
# JoinDSL.on to specify the conditions for the join. The options hash accepts :join_type and
|
213
|
+
# :alias parameters. The :join_type parameter can be either :inner, :left or :right and defaults
|
214
|
+
# to :inner. The :alias parameter allows you to specify an alias for the table in the join and
|
215
|
+
# defaults to "#{left_table}__#{clazz.name}".
|
216
|
+
#
|
217
|
+
# ==== Parameters
|
218
|
+
# clazz<Class>::
|
219
|
+
# The class that is being joined to.
|
220
|
+
# options<Hash>::
|
221
|
+
# An options hash (see below)
|
222
|
+
# block<Proc>
|
223
|
+
# The contents of the join block can contain any sequence of conjunctions, restrictions, or joins.
|
224
|
+
#
|
225
|
+
# ==== Options(options)
|
226
|
+
# :join_type<Symbol>::
|
227
|
+
# Indicates the type of join to use and must be one of :inner, :left or :right, where :left
|
228
|
+
# or :right will create a LEFT or RIGHT OUTER join respectively.
|
229
|
+
# :alias<String>::
|
230
|
+
# If provided, will specify an alias to use in the SQL when referring to the joined table.
|
231
|
+
# If the argument is not given, the alias will be "#{left_table}__#{clazz.name}"
|
232
|
+
#
|
233
|
+
# ==== Returns
|
234
|
+
# JoinDSL::
|
235
|
+
# A DSL object that can be used to specify the contents of the join. Returning this value allows
|
236
|
+
# for constructions like: join(Comment, :inner).on(:id => :post_id)
|
237
|
+
#
|
238
|
+
# @public
|
239
|
+
def join(clazz, options={}, &block)
|
240
|
+
@conjunction.add_class_join(clazz, options[:join_type], options[:alias], &block)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Access the class that the current filter is being applied to. This is necessary
|
244
|
+
# because the filter is evaluated in the context of the DSL object, so self will
|
245
|
+
# not give access to any methods that need to be called on the filtered class.
|
246
|
+
# It is especially useful in named filters that may be defined in a way that allows
|
247
|
+
# them to apply to multiple classes.
|
248
|
+
#
|
249
|
+
# ==== Returns
|
250
|
+
# Class::
|
251
|
+
# The class that is currently being filtered.
|
252
|
+
#
|
253
|
+
# @public
|
254
|
+
def filter_class
|
255
|
+
@model_class
|
256
|
+
end
|
257
|
+
|
258
|
+
# Enable calling of named filters from within other filters by catching unknown calls
|
259
|
+
# and assuming that they are to named filters. This enables the following examples:
|
260
|
+
# class Post < ActiveRecord::Base
|
261
|
+
# has_many :comments
|
262
|
+
# named_filter(:empty) { with(:contents).nil }
|
263
|
+
# end
|
264
|
+
#
|
265
|
+
# class Comment < ActiveRecord::Base
|
266
|
+
# belongs_to :post
|
267
|
+
# named_filter(:offensive) { |value| with(:offensive, value) }
|
268
|
+
# end
|
269
|
+
#
|
270
|
+
# Post.filter do
|
271
|
+
# with(:created_at).less_than(1.hour.ago)
|
272
|
+
# empty
|
273
|
+
# end
|
274
|
+
#
|
275
|
+
# # Results in:
|
276
|
+
# # :conditions => { ['posts.created_at < ? AND posts.contents IS NULL', 1.hour.ago] }
|
277
|
+
# # And even cooler:
|
278
|
+
#
|
279
|
+
# Post.filter do
|
280
|
+
# having(:comments).offensive(true)
|
281
|
+
# end
|
282
|
+
#
|
283
|
+
# # Results in:
|
284
|
+
# # :conditions => { ['posts__comments.offensive = ?', true] }
|
285
|
+
# # :joins => { 'INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id' }
|
286
|
+
#
|
287
|
+
# ==== Parameters
|
288
|
+
# args<Array>::
|
289
|
+
# The arguments to pass to the named filter when called.
|
290
|
+
#
|
291
|
+
# @public
|
292
|
+
def method_missing(method, *args)
|
293
|
+
@conjunction.add_named_filter(method, *args)
|
294
|
+
end
|
295
|
+
|
296
|
+
#
|
297
|
+
# Define these_methods here just so that we can throw exceptions when they are called. They should not
|
298
|
+
# be callable in the scope of a conjunction_dsl.
|
299
|
+
#
|
300
|
+
def limit(limit, offset=nil) # :nodoc:
|
301
|
+
raise InvalidFilterException.new('Calls to limit can only be made in the outer block of a filter.')
|
302
|
+
end
|
303
|
+
|
304
|
+
def order(column, direction=:asc) # :nodoc:
|
305
|
+
raise InvalidFilterException.new('Calls to order can only be made in the outer block of a filter.')
|
306
|
+
end
|
307
|
+
|
308
|
+
def group_by(column) # :nodoc:
|
309
|
+
raise InvalidFilterException.new('Calls to group_by can only be made in the outer block of a filter.')
|
310
|
+
end
|
311
|
+
|
312
|
+
def on(column, value=Restriction::DEFAULT_VALUE) # :nodoc:
|
313
|
+
raise InvalidFilterException.new('Calls to on can only be made in the block of a call to join.')
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module RecordFilter
|
2
|
+
module DSL
|
3
|
+
class DSL < ConjunctionDSL
|
4
|
+
|
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
|
+
# limit<Integer>::
|
12
|
+
# Used for the limit of the query.
|
13
|
+
# offset<Integer>::
|
14
|
+
# Used as the offset for the query. This argument is optional, with the default
|
15
|
+
# being no offset.
|
16
|
+
#
|
17
|
+
# ==== Returns
|
18
|
+
# nil
|
19
|
+
#
|
20
|
+
# @public
|
21
|
+
def limit(limit, offset=nil)
|
22
|
+
@conjunction.add_limit(limit, offset)
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# Define an offset for the results returned from the current
|
27
|
+
# filter. This method can only be called from the outermost scope of a filter
|
28
|
+
# (i.e. not inside of a having block, etc.). If it is called multiple times, the
|
29
|
+
# last one will override any others.
|
30
|
+
#
|
31
|
+
# ==== Parameters
|
32
|
+
# offset<Integer>::
|
33
|
+
# The offset of the query.
|
34
|
+
#
|
35
|
+
# ==== Returns
|
36
|
+
# nil
|
37
|
+
#
|
38
|
+
# @public
|
39
|
+
def offset(offset)
|
40
|
+
@conjunction.add_limit(nil, offset)
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Define an order clause for the current query, with options for specifying
|
45
|
+
# both the column to use as well as the direction. This method can only be called
|
46
|
+
# in the outermost scope of a filter (i.e. not inside of a having block, etc.).
|
47
|
+
# Multiple calls will create multiple order clauses in the resulting query, and
|
48
|
+
# they will be added in the order in which they were called in the filter. In order
|
49
|
+
# to specify ordering on columns added through joins, a hash can be passed as the
|
50
|
+
# first argument, specifying a path through the joins to the column, as in this
|
51
|
+
# example:
|
52
|
+
#
|
53
|
+
# Blog.filter do
|
54
|
+
# having(:posts) do
|
55
|
+
# having(:comments).with(:created_at).greater_than(3.days.ago)
|
56
|
+
# end
|
57
|
+
# order(:posts => :comments => :created_at, :desc)
|
58
|
+
# order(:id, :asc)
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# ==== Parameters
|
62
|
+
# column<Symbol, Hash>::
|
63
|
+
# Specify the column for the ordering. If a symbol is given, it is assumed to represent
|
64
|
+
# a column in the class that is being filtered. With a hash argument, it is possible
|
65
|
+
# to specify a path to a column in one of the joined tables, as seen above. If a string
|
66
|
+
# is given and it doesn't match up with a column name, it is used as a literal string
|
67
|
+
# for ordering.
|
68
|
+
# direction<Symbol>::
|
69
|
+
# Specifies the direction of the order. Should be either :asc or :desc and defaults to :asc.
|
70
|
+
#
|
71
|
+
# ==== Returns
|
72
|
+
# nil
|
73
|
+
#
|
74
|
+
# ==== Raises
|
75
|
+
# InvalidFilterException::
|
76
|
+
# If the direction is neither :asc nor :desc.
|
77
|
+
#
|
78
|
+
# ==== Alternatives
|
79
|
+
# As described above, it is possible to pass a symbol, a hash or a string as the first
|
80
|
+
# argument.
|
81
|
+
#
|
82
|
+
# @public
|
83
|
+
def order(column, direction=:asc)
|
84
|
+
unless [:asc, :desc].include?(direction)
|
85
|
+
raise InvalidFilterException.new("The direction for orders must be either :asc or :desc but was #{direction}")
|
86
|
+
end
|
87
|
+
@conjunction.add_order(column, direction)
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
# Specify a group_by clause for the resulting query. This method can only be called
|
92
|
+
# in the outermost scope of a filter (i.e. not inside of a having block, etc.).
|
93
|
+
# Multiple calls will create multiple group_by clauses in the resulting query, and
|
94
|
+
# they will be added in the order in which they were called in the filter. In order
|
95
|
+
# to specify grouping on columns added through joins, a hash can be passed as the
|
96
|
+
# argument, specifying a path through the joins to the column, as in this example:
|
97
|
+
#
|
98
|
+
# Blog.filter do
|
99
|
+
# having(:posts) do
|
100
|
+
# having(:comments).with(:created_at).greater_than(3.days.ago)
|
101
|
+
# end
|
102
|
+
# group_by(:posts => :comments => :offensive)
|
103
|
+
# group_by(:id)
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# ==== Parameters
|
107
|
+
# column<Symbol, Hash>::
|
108
|
+
# If a symbol is specified, it is taken to represent the name of a column on the
|
109
|
+
# class being filtered. If a hash is given, it should represent a path through the
|
110
|
+
# joins to a column in one of the joined tables. If a string is given, it is used
|
111
|
+
# without modification as the grouping parameter.
|
112
|
+
#
|
113
|
+
# ==== Returns
|
114
|
+
# nil
|
115
|
+
#
|
116
|
+
# ==== Alternatives
|
117
|
+
# As described above, it is possible to pass either a symbol, a hash, or a string
|
118
|
+
# as the argument.
|
119
|
+
#
|
120
|
+
# @public
|
121
|
+
def group_by(column)
|
122
|
+
@conjunction.add_group_by(column)
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
|
126
|
+
# Specify that the resulting query should select distinct results. This method
|
127
|
+
# can only be called in the outermost scope of a filter (i.e. not inside of a having
|
128
|
+
# block, etc.).
|
129
|
+
#
|
130
|
+
# ==== Parameters
|
131
|
+
# none
|
132
|
+
#
|
133
|
+
# ==== Returns
|
134
|
+
# nil
|
135
|
+
#
|
136
|
+
# @public
|
137
|
+
def distinct
|
138
|
+
@conjunction.set_distinct
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -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
|
+
get_subclass(clazz).new(clazz, Conjunction.new(clazz, :all_of))
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_subclass(clazz)
|
14
|
+
SUBCLASSES[clazz.object_id]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module RecordFilter
|
2
|
+
module DSL
|
3
|
+
class Join # :nodoc: all
|
4
|
+
|
5
|
+
attr_reader :association, :join_type, :conjunction, :aliaz
|
6
|
+
|
7
|
+
def initialize(association, join_type, conjunction, aliaz)
|
8
|
+
@association, @join_type, @conjunction, @aliaz = association, join_type, conjunction, aliaz
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RecordFilter
|
2
|
+
module DSL
|
3
|
+
class JoinCondition # :nodoc: all
|
4
|
+
|
5
|
+
attr_reader :restriction
|
6
|
+
|
7
|
+
def initialize(column, value)
|
8
|
+
@column = column
|
9
|
+
if column.is_a?(Hash) && value == Restriction::DEFAULT_VALUE
|
10
|
+
@condition = column
|
11
|
+
else
|
12
|
+
@restriction = Restriction.new(column, value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def condition
|
17
|
+
@condition || restriction
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module RecordFilter
|
2
|
+
module DSL
|
3
|
+
# This class is used as the active DSL when executing the block provided to explicit
|
4
|
+
# joins created with ConjunctionDSL.join. It is a subclass of ConjunctionDSL that
|
5
|
+
# adds a method for specifying how the join is to be performed by giving either a pair
|
6
|
+
# of columns or a restriction that is applied to a column of the right table.
|
7
|
+
class JoinDSL < ConjunctionDSL
|
8
|
+
|
9
|
+
attr_reader :conditions # :nodoc:
|
10
|
+
|
11
|
+
# Specify parameters for explicit joins. This method should be called at least once
|
12
|
+
# within the block of a call to ConjunctionDSL.join and accepts various combinations
|
13
|
+
# of arguments to determine how to join the two tables. The possible options are:
|
14
|
+
#
|
15
|
+
# * Pass a hash where the key is a symbol that represents a column in the left table and the value is a symbol that represents a column in the right table.
|
16
|
+
# * Pass a symbol and a value, in which case an equality condition will be created on the column in the right table with the given column name.
|
17
|
+
# * Pass only the name of a column in the right table, in which case the method can be chained with any of the calls in the Restriction API to create generic restrictions on the column.
|
18
|
+
# Post.filter do
|
19
|
+
# join(Comment, :inner) do
|
20
|
+
# on(:id => :post_id)
|
21
|
+
# on(:offensive, false)
|
22
|
+
# on(:id).greater_than(12)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # Results in:
|
26
|
+
# # :joins => "INNER JOIN "comments" AS posts__Comment ON "posts".id = posts__Comment.post_id AND posts__Comment.offensive = false AND posts__Comment.id > 12"
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# ==== Parameters
|
30
|
+
# column<Hash, Symbol>::
|
31
|
+
# Either a hash representing the column pair to join or a symbol representing the column in the
|
32
|
+
# right table to add a condition to.
|
33
|
+
# value<value, optional>::
|
34
|
+
# If provided along with a symbol for the column argument, creates an equality condition on that
|
35
|
+
# column.
|
36
|
+
#
|
37
|
+
# ==== Returns
|
38
|
+
# Restriction::
|
39
|
+
# A restriction that can be used to limit the column when a symbol is passed as the column name.
|
40
|
+
#
|
41
|
+
# @public
|
42
|
+
def on(column, value=Restriction::DEFAULT_VALUE)
|
43
|
+
@conditions ||= []
|
44
|
+
@conditions << (condition = JoinCondition.new(column, value))
|
45
|
+
return condition.restriction
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|