record_filter 0.9.12
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/.gitignore
ADDED
data/CHANGELOG
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
= 0.9.9
|
2
|
+
|
3
|
+
* Fixed a bug where distinct wasn't being applied when chaining with has_many
|
4
|
+
:through associations.
|
5
|
+
|
6
|
+
= 0.9.8
|
7
|
+
|
8
|
+
* BREAKING CHANGE: changed the 'limit' method so that the limit is always the
|
9
|
+
first argument, with an optional offset as the second argument. This makes the
|
10
|
+
functionality much clearer by avoiding obnoxious argument swapping.
|
11
|
+
|
12
|
+
* BREAKING CHANGE: changed the arguments to the 'join' method so that it
|
13
|
+
takes a class and an options hash. The options hash can contain a :join_type
|
14
|
+
and an :alias, allowing only the arguments that the client wants to use to be
|
15
|
+
specified. This will break existing code where the join type was passed as
|
16
|
+
the second argument.
|
17
|
+
|
18
|
+
* BREAKING CHANGE: changed the arguments to the 'having' method so that it
|
19
|
+
takes an association name and an options hash. The options hash takes
|
20
|
+
:join_type and :alias. This will break existing code where the join_type was
|
21
|
+
passed as the first argument to the method. The alias is a new option that
|
22
|
+
allows you to alias joins, which lets you join in the same association twice.
|
23
|
+
|
24
|
+
* Added a distinct method to force queries to be distinct.
|
25
|
+
|
26
|
+
= 0.9.7
|
27
|
+
|
28
|
+
* Fix a bug where explicit joins would not honor different aliases if joining
|
29
|
+
the same class twice.
|
30
|
+
|
31
|
+
= 0.9.6
|
32
|
+
|
33
|
+
* Build the alias for explicit join tables correctly for multi-word class
|
34
|
+
names.
|
35
|
+
|
36
|
+
* Set params[:readonly] to false by default. Not sure why we need to do that.
|
37
|
+
|
38
|
+
* If nil is passed as the argument to a comparison operator, don't fail with a
|
39
|
+
lack of bind variables, just do what AR does and compare against NULL.
|
40
|
+
|
41
|
+
* Add ability to order on columns in tables that were explicitly joined.
|
42
|
+
|
43
|
+
= 0.9.5
|
44
|
+
|
45
|
+
* Added 'and' and 'or' methods to the DSL::Restriction class so that you can do
|
46
|
+
things like with(:expired_at).gt(Time.now).or.is_null
|
47
|
+
|
48
|
+
= 0.9.4
|
49
|
+
|
50
|
+
* Stop forcing order to take a real column name and let it accept a string
|
51
|
+
|
52
|
+
* Added an offset method to the DSL so that we can do offsets separately from
|
53
|
+
limits
|
54
|
+
|
55
|
+
* Some small performance improvements
|
56
|
+
|
57
|
+
* Added a test to make sure that the IN restriction can take a range
|
58
|
+
|
59
|
+
* Changed the default rake task to run db:spec:prepare first
|
60
|
+
|
61
|
+
* Added metric_fu for code quality metrics tests
|
62
|
+
|
63
|
+
* Refactored a bunch of code to improve the metric numbers
|
64
|
+
|
65
|
+
= 0.9.3
|
66
|
+
|
67
|
+
* Fix a bug when using class_name on belongs_to associations without
|
68
|
+
foreign_key
|
69
|
+
|
70
|
+
= 0.9.2
|
71
|
+
|
72
|
+
* Ruby 1.9 compatability
|
73
|
+
|
74
|
+
= 0.9.1
|
75
|
+
|
76
|
+
* Change group_by so that it will not throw an exception for missing column
|
77
|
+
names, since we want to be able to group by arbitrary things.
|
78
|
+
|
79
|
+
= 0.9.0
|
80
|
+
|
81
|
+
* Added a github pages page with a quick intro.
|
82
|
+
|
83
|
+
* Add tests for working with default scopes.
|
84
|
+
|
85
|
+
* de-hackify the default aliases for explicit joins.
|
86
|
+
|
87
|
+
* Fix a bug where strings wouldn't work as the names of associations in
|
88
|
+
implicit joins.
|
89
|
+
|
90
|
+
* Improve the README
|
91
|
+
|
92
|
+
* Fix a bug when combining joins across multiple named filters that introduce
|
93
|
+
the same join.
|
94
|
+
|
95
|
+
= 0.8.0
|
96
|
+
|
97
|
+
* Raise an exception when calling order with something that isn't :asc or
|
98
|
+
:desc
|
99
|
+
|
100
|
+
* Use hanna for spiffy rdocs.
|
101
|
+
|
102
|
+
* Raise an exception when attempting to add a named filter to a class if there
|
103
|
+
is already an existing filter with the same name.
|
104
|
+
|
105
|
+
* Documentation.
|
106
|
+
|
107
|
+
* Refactored the custom DSL subclass creation code into a DSLFactory class.
|
108
|
+
|
109
|
+
* Added a (fairly simple) performance test.
|
110
|
+
|
111
|
+
* Get source_type option working for has_many.
|
112
|
+
|
113
|
+
* Correct hard-coded primary key columns.
|
114
|
+
|
115
|
+
= 0.6.0
|
116
|
+
|
117
|
+
* 100% test coverage. That's right, 100%.
|
118
|
+
|
119
|
+
* Added fixes for various options on AR associations.
|
120
|
+
|
121
|
+
* Support calling named filters from within joins in filters.
|
122
|
+
|
123
|
+
* Stop using _inheritable_attribute for accessing the named filters and just recurse for
|
124
|
+
them.
|
125
|
+
|
126
|
+
* Change the tests for named filters to use anonymous classes so that we're
|
127
|
+
sure we're starting from a blank slate.
|
128
|
+
|
129
|
+
* Make the API work correctly for anonymous classes.
|
130
|
+
|
131
|
+
* Correctly handle the case where nil or [] is passed as an argument to the IN
|
132
|
+
restriction.
|
133
|
+
|
134
|
+
* Added is_not_null as a nicer version of is_null.not
|
135
|
+
|
136
|
+
= 0.2.0
|
137
|
+
|
138
|
+
* Changed the available join types to inner, left and right.
|
139
|
+
|
140
|
+
* Fix a bug where we used the wrong type in polymorphic association joins.
|
141
|
+
|
142
|
+
* Use with_scope when chaining filters, named_filters, and AR associations in
|
143
|
+
order to keep the filter data instead of passing the query parameters directly
|
144
|
+
and combining them in order to make this work correctly with AR.
|
145
|
+
|
146
|
+
* Fix a bug when calling .filter in a chain of associations and named filters.
|
147
|
+
|
148
|
+
* Don't include conditions unless there are actually conditions specified.
|
149
|
+
|
150
|
+
* Fix a bug where the first and last methods would fail.
|
151
|
+
|
152
|
+
* Delegate array methods through to the result of the filter.
|
153
|
+
|
154
|
+
* Add proxy_options to mimic the API of named_scope for getting the options
|
155
|
+
that result from a given query.
|
156
|
+
|
157
|
+
* Support chaining with AR associations... (i.e. Blog.posts.published)
|
158
|
+
|
159
|
+
* Support has_many and has_one :through
|
160
|
+
|
161
|
+
= 0.1.4
|
162
|
+
|
163
|
+
* Join on both the id and type for polymorphic joins.
|
164
|
+
|
165
|
+
* Fix a bug where we were using the incorrect table names in joins and
|
166
|
+
ordering with joins.
|
167
|
+
|
168
|
+
* Change != to the SQL standard <>.
|
169
|
+
|
170
|
+
* Fix invalid SQL generated by not_all and not_any.
|
171
|
+
|
172
|
+
* Detect count/size/length queries and do the appropriate distinct clause for
|
173
|
+
those count queries.
|
174
|
+
|
175
|
+
* Do DISTINCT searches for queries that involve specific types of joins.
|
176
|
+
|
177
|
+
* Allow passing a join type as the first argument to having.
|
178
|
+
|
179
|
+
* Restructured the explicit join API
|
180
|
+
|
181
|
+
* Raise exceptions when doing an explicit join using columns that don't exist.
|
182
|
+
|
183
|
+
* Allow explicit joins on values: left_join(:class_name, :table_alias, :rcol
|
184
|
+
=> 'Post'...)
|
185
|
+
|
186
|
+
* Added a filter_class method to the DSL for getting the class that is being
|
187
|
+
filtered.
|
188
|
+
|
189
|
+
* Added custom join feature: left_join(:table_name, :table_alias, :rcol =>
|
190
|
+
:lcol...)
|
191
|
+
|
192
|
+
* Changed custom exceptions to subclass StandardError and not crash the entire
|
193
|
+
app when throwing one (thanks RailsEnvy).
|
194
|
+
|
195
|
+
= 0.1.3
|
196
|
+
|
197
|
+
* Fixed a bug in filter.rb when trying to chain to named_scopes or things that
|
198
|
+
are otherwise not named_filters.
|
199
|
+
|
200
|
+
* Made a named_filters accessor for getting the list of filters that apply to
|
201
|
+
a particular class.
|
202
|
+
|
203
|
+
* Added none_of and not_all_of conjunctions.
|
204
|
+
|
205
|
+
* Changed the between restriction to take either a range, a tuple, or two
|
206
|
+
values.
|
207
|
+
|
208
|
+
* Support multiple joins in one having statement through having(:posts =>
|
209
|
+
:comments)
|
210
|
+
|
211
|
+
* Added a CHANGELOG
|
212
|
+
|
213
|
+
= 0.1.2
|
214
|
+
|
215
|
+
* Add LIKE and NOT LIKE restrictions
|
216
|
+
|
217
|
+
* Replace active record objects with their ids if passed as the value for a
|
218
|
+
resriction.
|
219
|
+
|
220
|
+
= 0.1.1
|
221
|
+
|
222
|
+
* Add group_by
|
223
|
+
|
224
|
+
* Raise informative exceptions when columns or associations are not found for
|
225
|
+
a given filter
|
226
|
+
|
227
|
+
* Alias is_null restriction to nil and null
|
228
|
+
|
229
|
+
* Alias comparison restrictions to gt, lt, lte, gte
|
230
|
+
|
231
|
+
* Add greater_than_or_equal_to and less_than_or_equal_to restrictions.
|
232
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,354 @@
|
|
1
|
+
= record_filter
|
2
|
+
|
3
|
+
record_filter is a DSL for specifying ActiveRecord queries in pure Ruby.
|
4
|
+
It has support for filters created on the fly and for named filters that are associated with object types.
|
5
|
+
record_filter has the following top-level features:
|
6
|
+
|
7
|
+
* Pure ruby API eliminates the need for hard-coded SQL in most cases.
|
8
|
+
* Works seamlessly with existing ActiveRecord APIs, including named scopes.
|
9
|
+
* Supports creation of ad-hoc filters as well as named filters that can be associated with object types.
|
10
|
+
* Allows chaining of filters with each other and with named scopes to create complex queries.
|
11
|
+
* Takes advantage of the associations in your ActiveRecord objects for a clean implicit join API.
|
12
|
+
|
13
|
+
== Documentation
|
14
|
+
|
15
|
+
The complete RDoc documentation is available at http://aub.github.com/record_filter/rdoc/. This page is
|
16
|
+
intended to be a getting started guide that should cover the most common uses.
|
17
|
+
|
18
|
+
== Installation
|
19
|
+
|
20
|
+
gem install aub-record_filter --source=http://gems.github.com
|
21
|
+
|
22
|
+
In Rails, you'll need to add this to your config/environment.rb file:
|
23
|
+
|
24
|
+
config.gem 'aub-record_filter', :lib => 'record_filter', :source => 'http://gems.github.com'
|
25
|
+
|
26
|
+
== Using Filters
|
27
|
+
|
28
|
+
Given a Blog model having a has_many relationship with a Post model, a simple
|
29
|
+
filter with conditions and joins might look like this.
|
30
|
+
|
31
|
+
Blog.filter do
|
32
|
+
with(:created_at).greater_than(1.day.ago)
|
33
|
+
having(:posts).with(:permalink, nil)
|
34
|
+
end
|
35
|
+
|
36
|
+
This could be expressed in ActiveRecord as:
|
37
|
+
|
38
|
+
Blog.all(
|
39
|
+
:joins => :posts,
|
40
|
+
:conditions => {
|
41
|
+
:posts => {:permalink => nil},
|
42
|
+
:created_at => 1.day.ago..Time.now
|
43
|
+
}
|
44
|
+
)
|
45
|
+
|
46
|
+
and it returns the same result, a list of Blog objects that are the result of the query. This
|
47
|
+
type of filter is designed to be created on the fly, but if you have a filter that you would like
|
48
|
+
to use in more than one place, it can be added to a class as a named filter. The following example
|
49
|
+
creates the same filter as above and executes it:
|
50
|
+
|
51
|
+
class Blog < ActiveRecord::Base
|
52
|
+
named_filter(:new_with_unlinked_posts) do
|
53
|
+
with(:created_at).greater_than(1.day.ago)
|
54
|
+
having(:posts).with(:permalink, nil)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
Blog.new_with_unlinked_posts
|
59
|
+
|
60
|
+
This returns the same result as the example above but with the advantages that it is
|
61
|
+
easily reusable and that it can be combined with other named filters to produce a more
|
62
|
+
complex query:
|
63
|
+
|
64
|
+
class Post < ActiveRecord::Base
|
65
|
+
named_filter(:title_is_monkeys) { with(:title, 'monkeys') }
|
66
|
+
named_filter(:permalink_is_donkeys) { with(:permalink, 'donkeys') }
|
67
|
+
end
|
68
|
+
|
69
|
+
Post.title_is_monkeys.permalink_is_donkeys
|
70
|
+
|
71
|
+
This example will return all of the posts that meet both animal-related conditions.
|
72
|
+
There is no limit to the number of filters that can be combined, and because record_filter works
|
73
|
+
seamlessly with named scopes, they can also be combined in this way as well.
|
74
|
+
|
75
|
+
Named filters can also be customized by taking any number of arguments. The example above can
|
76
|
+
be replicated with the following filter:
|
77
|
+
|
78
|
+
class Post < ActiveRecord::Base
|
79
|
+
named_filter(:with_title_and_permalink) do |title, permalink|
|
80
|
+
with(:title, title)
|
81
|
+
with(:permalink, permalink)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Post.with_title_and_permalink('monkeys', 'donkeys')
|
86
|
+
|
87
|
+
Named filters can also be called from other named filters and will be invoked on the correct
|
88
|
+
model even if called from a join.
|
89
|
+
|
90
|
+
class Comment < ActiveRecord::Base
|
91
|
+
named_filter(:offensive) { with(:offensive, true) }
|
92
|
+
end
|
93
|
+
|
94
|
+
class Post < ActiveRecord::Base
|
95
|
+
named_filter(:using_other_filter) do
|
96
|
+
having(:comments) do
|
97
|
+
offensive
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
Post.using_other_filter
|
103
|
+
|
104
|
+
== Specifying Filters
|
105
|
+
|
106
|
+
record_filter supports all of the SQL query abstractions provided by ActiveRecord, specifically:
|
107
|
+
|
108
|
+
* Conditions
|
109
|
+
* Boolean operations
|
110
|
+
* Joins
|
111
|
+
* Limits
|
112
|
+
* Offsets
|
113
|
+
* Ordering
|
114
|
+
* Grouping
|
115
|
+
|
116
|
+
The following example shows the use of each of these techniques:
|
117
|
+
|
118
|
+
Post.filter do
|
119
|
+
any_of do
|
120
|
+
with(:permalink).is_null
|
121
|
+
having(:comments) do
|
122
|
+
with(:offensive, true)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
limit(100, 10)
|
126
|
+
order(:created_at, :desc)
|
127
|
+
group_by(:comments => :offensive)
|
128
|
+
end
|
129
|
+
|
130
|
+
=== Conditions
|
131
|
+
|
132
|
+
Conditions are specified using the 'with' function, which takes as its first argument
|
133
|
+
the name of the column to restrict. If a second argument is given, it will automatically
|
134
|
+
be used as the value in an equality condition. The 'with' function will return a Restriction
|
135
|
+
object that has methods to specify a number of different conditions and to negate them:
|
136
|
+
|
137
|
+
with(:permalink, 'aardvarks') # ['permalink = ?', 'aardvarks']
|
138
|
+
with(:permalink).equal_to('sheep') # ['permalink = ?', 'sheep']
|
139
|
+
with(:permalink).not.equal_to('cats') # ['permailnk <> ?', 'cats']
|
140
|
+
|
141
|
+
with(:permalink, nil) # ['permalink IS NULL']
|
142
|
+
with(:permalink).is_null # ['permalink IS NULL']
|
143
|
+
with(:permalink, nil).not # ['permalink IS NOT NULL']
|
144
|
+
with(:permalink).is_not_null # ['permalink IS NOT NULL']
|
145
|
+
|
146
|
+
The following condition types are supported through the Restriction API:
|
147
|
+
|
148
|
+
* Equality
|
149
|
+
* Comparisons (> >= < <=)
|
150
|
+
* Between
|
151
|
+
* In
|
152
|
+
* Is null
|
153
|
+
* Like
|
154
|
+
* Negation of all of the above
|
155
|
+
|
156
|
+
And here are some examples. See the RDoc page for
|
157
|
+
{DSL::Restriction}[http://aub.github.com/record_filter/rdoc/classes/RecordFilter/DSL/Restriction.html]
|
158
|
+
for more details on how to use them.
|
159
|
+
|
160
|
+
with(:featured_at).greater_than(Time.now) # ['featured_at > ?', Time.now]
|
161
|
+
|
162
|
+
with(:price).lte(1000) # ['price <= ?', 1000]
|
163
|
+
|
164
|
+
with(:created_at).between(time_a..time_b) # ['created_at BETWEEN ? AND ?', time_a, time_b]
|
165
|
+
|
166
|
+
with(:id).in([1, 2, 3]) # ['id in (?)', [1, 2, 3]]
|
167
|
+
|
168
|
+
with(:id).not_in([4, 5, 6]) # ['id NOT IN (?)', [4, 5, 6]]
|
169
|
+
|
170
|
+
with(:content).like('%easy%') # ['content LIKE ?', '%easy%']
|
171
|
+
|
172
|
+
with(:content).not.like('%hard%') # ['content NOT LIKE ?', '%hard%']
|
173
|
+
|
174
|
+
The comparison operators (greater_than, less_than, greater_than_or_equal_to and less_than_or_equal_to)
|
175
|
+
are aliased to their short forms (gt, lt, gte and lte).
|
176
|
+
|
177
|
+
It is also possible to specify multiple conditions on a single line using the 'and' and 'or' methods,
|
178
|
+
eliminating the need to use a conjunction block in many common cases.
|
179
|
+
|
180
|
+
with(:expired_at).gt(Time.now).or.is_null # ['expired_at > ? OR expired_at IS NULL', Time.now]
|
181
|
+
|
182
|
+
with(:id).gt(100).and.lt(1000) # ['id > ? AND id < ?', 100, 1000]
|
183
|
+
|
184
|
+
=== Boolean Operations
|
185
|
+
|
186
|
+
Conditions can be combined with boolean operators using the methods all_of, any_of, none_of
|
187
|
+
and not_all_of. These methods take a block where any conditions they contain will be combined
|
188
|
+
using AND, OR and NOT to create the correct clause. The block can also contain any number of
|
189
|
+
joins or other boolean operations. The default operator is all_of.
|
190
|
+
|
191
|
+
Post.filter do
|
192
|
+
with(:id, 4)
|
193
|
+
with(:permalink, 'ack')
|
194
|
+
end
|
195
|
+
|
196
|
+
# ['id = ? AND permalink = ?', 4, 'ack']
|
197
|
+
|
198
|
+
Post.filter do
|
199
|
+
any_of do
|
200
|
+
with(:id, 3)
|
201
|
+
with(:permalink, 'booya')
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# ['id = ? OR permalink = ?', 3, 'booya']
|
206
|
+
|
207
|
+
Post.filter do
|
208
|
+
none_of do
|
209
|
+
with(:id, 2)
|
210
|
+
with(:permalink, 'ouch')
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# ['NOT (id = ? OR permalink = ?)', 2, 'ouch']
|
215
|
+
|
216
|
+
Post.filter do
|
217
|
+
not_all_of do
|
218
|
+
with(:id, 1)
|
219
|
+
with(:permalink, 'bonobo')
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# ['NOT (id = ? AND permalink = ?)', 1, 'bonobo']
|
224
|
+
|
225
|
+
=== Joins
|
226
|
+
|
227
|
+
Joins in record_filter come in two varieties. Using the information in ActiveRecord associations,
|
228
|
+
it is possible to perform most joins easily using the 'having' method, which requires no specification
|
229
|
+
of the columns to use for the join. In cases where an association does not apply, it is also possible
|
230
|
+
to create an explicit join that can include both the columns to combine as well as restrictions on
|
231
|
+
the columns in the join table.
|
232
|
+
|
233
|
+
In a filter for a Post model that has_many comments, the following two examples are equivalent:
|
234
|
+
|
235
|
+
having(:comments)
|
236
|
+
|
237
|
+
join(Comment, :join_type => :inner) do
|
238
|
+
on(:id => :post_id)
|
239
|
+
end
|
240
|
+
|
241
|
+
With an explicit join, any number of columns can be matched in this way, and both join types
|
242
|
+
accept a block in which any number of conditions, boolean operations, or other joins can be
|
243
|
+
added. Explicit joins also allow conditions to be set on columns of the table being joined:
|
244
|
+
|
245
|
+
having(:comments).with(:offensive, true)
|
246
|
+
|
247
|
+
having(:comments) do
|
248
|
+
with(:created_at).greater_than(2.days.ago)
|
249
|
+
end
|
250
|
+
|
251
|
+
join(Comment, :join_type => :inner) do
|
252
|
+
on(:id => :commentable_id)
|
253
|
+
on(:commentable_type).equal_to('Post')
|
254
|
+
with(:created_at).less_than(1.year.ago)
|
255
|
+
end
|
256
|
+
|
257
|
+
With implicit joins, it is also possible to use a hash as the association name, in which case
|
258
|
+
multiple joins can be created with one statement. If the comment model has_one Author, this
|
259
|
+
example will join both tables and add a condition on the author.
|
260
|
+
|
261
|
+
having(:comments => :author).with(:name, 'Bob')
|
262
|
+
|
263
|
+
For both join types, an options hash can be provided as the second argument for passing the join
|
264
|
+
type and/or an alias to use for the joined table. The join type defaults to :inner, and the alias
|
265
|
+
defaults to a unique name for identifying the table. Using aliases allows you to join to a given table
|
266
|
+
twice with two different names. How about a contrived example? Awesome.
|
267
|
+
|
268
|
+
Blog.filter do
|
269
|
+
having(:posts, :join_type => :left, :alias => 'posts_1').with(:title, 'a')
|
270
|
+
having(:posts, :alias => 'posts_2').with(:title, 'b')
|
271
|
+
end
|
272
|
+
|
273
|
+
# SELECT DISTINCT "blogs".* FROM "blogs"
|
274
|
+
# LEFT OUTER JOIN "posts" AS posts_1 ON "blogs".id = posts_1.blog_id
|
275
|
+
# INNER JOIN "posts" AS posts_2 ON "blogs".id = posts_2.blog_id
|
276
|
+
# WHERE ((posts_1.title = 'a') AND (posts_2.title = 'b'))
|
277
|
+
|
278
|
+
=== Limits and Offsets
|
279
|
+
|
280
|
+
These are specified using the 'limit' method, which takes two arguments, the limit and the
|
281
|
+
offset. The offset is optional. For specifying only offsets, the 'offset' method is also available.
|
282
|
+
|
283
|
+
limit(100, 10) # :limit => 100, :offset => 100
|
284
|
+
limit(100) # :limit => 100
|
285
|
+
offset(10) # :offset => 10
|
286
|
+
|
287
|
+
=== Ordering
|
288
|
+
|
289
|
+
Ordering is done through the 'order' method, which accepts arguments for the column and direction.
|
290
|
+
The column can either be passed as the name of a column in the class that is being filtered or as
|
291
|
+
a hash that represents a path through the joined associations to the correct column. The direction argument
|
292
|
+
should be either :asc or :desc and defaults to :asc if not given. Multiple calls to 'order' are
|
293
|
+
allowed and will be applied in the order in which they were given.
|
294
|
+
|
295
|
+
Post.filter do
|
296
|
+
having(:comments).with(:offensive, true)
|
297
|
+
order(:created_at, :desc)
|
298
|
+
order(:comments => :id)
|
299
|
+
end
|
300
|
+
|
301
|
+
# :order => "'posts'.created_at DESC posts__comments.id ASC"
|
302
|
+
|
303
|
+
=== Grouping
|
304
|
+
|
305
|
+
Grouping is specified with the 'group_by' method, which accepts either the name of a column in the
|
306
|
+
class that is being filtered or a hash that represents a path through the joined associations. If
|
307
|
+
there are multiple calls to 'group_by' they will be combined in the final result, maintaining the
|
308
|
+
order in which they were given.
|
309
|
+
|
310
|
+
Post.filter do
|
311
|
+
having(:comments).with(:created_at).greater_than(1.hour.ago)
|
312
|
+
group_by(:permalink)
|
313
|
+
group_by(:comments => :offensive)
|
314
|
+
end
|
315
|
+
|
316
|
+
# :group => "'posts'.permalink, posts__comments.offensive'
|
317
|
+
|
318
|
+
=== Distinct
|
319
|
+
|
320
|
+
Filters that include outer joins are automatically made distinct by record_filter. For filters that
|
321
|
+
use inner joins, use the 'distinct' method in the DSL to force the select to be distinct.
|
322
|
+
|
323
|
+
Blog.filter do
|
324
|
+
with(:created_at).greater_than(1.day.ago)
|
325
|
+
having(:posts).with(:permalink, nil)
|
326
|
+
distinct
|
327
|
+
end
|
328
|
+
|
329
|
+
# :select => "DISTINCT 'blogs'.*"
|
330
|
+
|
331
|
+
== LICENSE:
|
332
|
+
|
333
|
+
(The MIT License)
|
334
|
+
|
335
|
+
Copyright (c) 2008-2009 Aubrey Holland, Mat Brown
|
336
|
+
|
337
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
338
|
+
a copy of this software and associated documentation files (the
|
339
|
+
'Software'), to deal in the Software without restriction, including
|
340
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
341
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
342
|
+
permit persons to whom the Software is furnished to do so, subject to
|
343
|
+
the following conditions:
|
344
|
+
|
345
|
+
The above copyright notice and this permission notice shall be
|
346
|
+
included in all copies or substantial portions of the Software.
|
347
|
+
|
348
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
349
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
350
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
351
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
352
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
353
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
354
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
FileList['tasks/**/*.rake'].each { |file| load file }
|
6
|
+
|
7
|
+
task :default => ["db:spec:prepare", :spec]
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'jeweler'
|
11
|
+
Jeweler::Tasks.new do |gemspec|
|
12
|
+
gemspec.name = 'record_filter'
|
13
|
+
gemspec.summary = 'An ActiveRecord query API for replacing SQL with awesome'
|
14
|
+
gemspec.email = 'aubreyholland@gmail.com'
|
15
|
+
gemspec.homepage = 'http://github.com/aub/record_filter'
|
16
|
+
gemspec.add_dependency 'activerecord'
|
17
|
+
gemspec.add_development_dependency 'rspec'
|
18
|
+
gemspec.rubyforge_project = 'record-filter'
|
19
|
+
gemspec.description = 'RecordFilter is a Pure-ruby criteria API for building complex queries in ActiveRecord. It supports queries that are built on the fly as well as named filters that can be added to objects and chained to create complex queries. It also gets rid of the nasty hard-coded SQL that shows up in most ActiveRecord code with a clean API that makes queries simple and intuitive to build.'
|
20
|
+
gemspec.authors = ['Aubrey Holland', 'Mat Brown']
|
21
|
+
end
|
22
|
+
Jeweler::RubyforgeTasks.new do |rubyforge|
|
23
|
+
rubyforge.doc_task = 'rdoc'
|
24
|
+
end
|
25
|
+
rescue LoadError
|
26
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Try to use hanna to create spiffier docs.
|
30
|
+
begin
|
31
|
+
require 'hanna/rdoctask'
|
32
|
+
rescue LoadError
|
33
|
+
require 'rake/rdoctask'
|
34
|
+
end
|
35
|
+
|
36
|
+
Rake::RDocTask.new do |rdoc|
|
37
|
+
if File.exist?('VERSION.yml')
|
38
|
+
config = YAML.load(File.read('VERSION.yml'))
|
39
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
40
|
+
else
|
41
|
+
version = ""
|
42
|
+
end
|
43
|
+
|
44
|
+
rdoc.rdoc_dir = 'rdoc'
|
45
|
+
rdoc.title = "record_filter #{version}"
|
46
|
+
rdoc.rdoc_files.include('README*')
|
47
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
48
|
+
rdoc.options << '--webcvs=http://github.com/aub/record_filter/tree/master/'
|
49
|
+
end
|
50
|
+
|
51
|
+
begin
|
52
|
+
require 'ruby-prof/task'
|
53
|
+
|
54
|
+
RubyProf::ProfileTask.new do |t|
|
55
|
+
t.test_files = FileList['test/performance_test.rb']
|
56
|
+
t.output_dir = 'perf'
|
57
|
+
t.printer = :graph_html
|
58
|
+
t.min_percent = 5
|
59
|
+
end
|
60
|
+
rescue LoadError
|
61
|
+
puts 'Ruby-prof not available. Profiling tests are disabled.'
|
62
|
+
end
|
63
|
+
|
64
|
+
begin
|
65
|
+
require 'metric_fu'
|
66
|
+
MetricFu::Configuration.run do |config|
|
67
|
+
#define which metrics you want to use
|
68
|
+
config.metrics = [:churn, :flog, :flay, :reek, :roodi, :rcov] # :saikuro, :stats
|
69
|
+
config.flay = { :dirs_to_flay => ['lib'] }
|
70
|
+
config.flog = { :dirs_to_flog => ['lib'] }
|
71
|
+
config.reek = { :dirs_to_reek => ['lib'] }
|
72
|
+
config.roodi = { :dirs_to_roodi => ['lib'] }
|
73
|
+
config.saikuro = { :output_directory => 'scratch_directory/saikuro',
|
74
|
+
:input_directory => ['lib'],
|
75
|
+
:cyclo => "",
|
76
|
+
:filter_cyclo => "0",
|
77
|
+
:warn_cyclo => "5",
|
78
|
+
:error_cyclo => "7",
|
79
|
+
:formater => "text"} #this needs to be set to "text"
|
80
|
+
config.churn = { :start_date => "1 year ago", :minimum_churn_count => 10}
|
81
|
+
config.rcov = { :test_files => ['spec/**/*_spec.rb'],
|
82
|
+
:rcov_opts => ["--sort coverage",
|
83
|
+
"--no-html",
|
84
|
+
"--text-coverage",
|
85
|
+
"--no-color",
|
86
|
+
"--profile",
|
87
|
+
"--exclude spec"]}
|
88
|
+
end
|
89
|
+
rescue LoadError
|
90
|
+
puts 'Install metric_fu for code quality metric tests.'
|
91
|
+
end
|
92
|
+
|
data/TODO
ADDED
data/VERSION.yml
ADDED