record_filter 0.9.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.gitignore +9 -0
  2. data/CHANGELOG +232 -0
  3. data/README.rdoc +354 -0
  4. data/Rakefile +92 -0
  5. data/TODO +3 -0
  6. data/VERSION.yml +4 -0
  7. data/config/roodi.yml +14 -0
  8. data/lib/record_filter/active_record.rb +108 -0
  9. data/lib/record_filter/column_parser.rb +14 -0
  10. data/lib/record_filter/conjunctions.rb +169 -0
  11. data/lib/record_filter/dsl/class_join.rb +16 -0
  12. data/lib/record_filter/dsl/conjunction.rb +57 -0
  13. data/lib/record_filter/dsl/conjunction_dsl.rb +317 -0
  14. data/lib/record_filter/dsl/dsl.rb +143 -0
  15. data/lib/record_filter/dsl/dsl_factory.rb +19 -0
  16. data/lib/record_filter/dsl/group_by.rb +11 -0
  17. data/lib/record_filter/dsl/join.rb +12 -0
  18. data/lib/record_filter/dsl/join_condition.rb +21 -0
  19. data/lib/record_filter/dsl/join_dsl.rb +49 -0
  20. data/lib/record_filter/dsl/limit.rb +12 -0
  21. data/lib/record_filter/dsl/named_filter.rb +12 -0
  22. data/lib/record_filter/dsl/order.rb +12 -0
  23. data/lib/record_filter/dsl/restriction.rb +314 -0
  24. data/lib/record_filter/dsl.rb +21 -0
  25. data/lib/record_filter/filter.rb +105 -0
  26. data/lib/record_filter/group_by.rb +21 -0
  27. data/lib/record_filter/join.rb +66 -0
  28. data/lib/record_filter/order.rb +27 -0
  29. data/lib/record_filter/query.rb +60 -0
  30. data/lib/record_filter/restriction_factory.rb +21 -0
  31. data/lib/record_filter/restrictions.rb +97 -0
  32. data/lib/record_filter/table.rb +172 -0
  33. data/lib/record_filter.rb +35 -0
  34. data/record_filter.gemspec +108 -0
  35. data/script/console +8 -0
  36. data/spec/active_record_spec.rb +211 -0
  37. data/spec/exception_spec.rb +208 -0
  38. data/spec/explicit_join_spec.rb +132 -0
  39. data/spec/implicit_join_spec.rb +403 -0
  40. data/spec/limits_and_ordering_spec.rb +230 -0
  41. data/spec/models.rb +109 -0
  42. data/spec/named_filter_spec.rb +264 -0
  43. data/spec/proxying_spec.rb +63 -0
  44. data/spec/restrictions_spec.rb +251 -0
  45. data/spec/select_spec.rb +79 -0
  46. data/spec/spec_helper.rb +39 -0
  47. data/spec/test.db +0 -0
  48. data/tasks/db.rake +106 -0
  49. data/tasks/rcov.rake +9 -0
  50. data/tasks/spec.rake +10 -0
  51. data/test/performance_test.rb +39 -0
  52. data/test/test.db +0 -0
  53. 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,11 @@
1
+ module RecordFilter
2
+ module DSL
3
+ class GroupBy # :nodoc: all
4
+ attr_reader :column
5
+
6
+ def initialize(column)
7
+ @column = column
8
+ end
9
+ end
10
+ end
11
+ 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
@@ -0,0 +1,12 @@
1
+ module RecordFilter
2
+ module DSL
3
+ class Limit # :nodoc: all
4
+
5
+ attr_reader :limit, :offset
6
+
7
+ def initialize(limit, offset)
8
+ @limit, @offset = limit, offset
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module RecordFilter
2
+ module DSL
3
+ class NamedFilter # :nodoc: all
4
+
5
+ attr_reader :name, :args
6
+
7
+ def initialize(name, *args)
8
+ @name, @args = name, args
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module RecordFilter
2
+ module DSL
3
+ class Order # :nodoc: all
4
+
5
+ attr_reader :column, :direction
6
+
7
+ def initialize(column, direction)
8
+ @column, @direction = column, direction
9
+ end
10
+ end
11
+ end
12
+ end