refinuri 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +157 -120
- data/VERSION +1 -1
- data/refinuri.gemspec +1 -1
- metadata +1 -1
data/README.md
CHANGED
@@ -1,63 +1,42 @@
|
|
1
|
-
# Refinuri
|
1
|
+
# Refinuri #
|
2
|
+
## ri-fahy-nuh-ree ##
|
2
3
|
|
3
|
-
Refinuri
|
4
|
+
Refinuri has two primary functions related to querying and filtering data:
|
4
5
|
|
5
6
|
+ a simple way to produce pretty, meaningful URLs, even with complex query strings
|
6
7
|
+ a standardized, extensible interface to filtering metadata
|
7
8
|
|
8
|
-
##
|
9
|
+
## The dime tour ##
|
9
10
|
|
10
|
-
|
11
|
-
# sets up a new FilterSet based on the filter part of a URL
|
12
|
-
|
13
|
-
Product.filtered(@filters)
|
14
|
-
# automatically applies all the filters to an ActiveRecord object just as though each were it's own #where()
|
15
|
-
|
16
|
-
## In practice
|
17
|
-
|
18
|
-
### Pretty URLs
|
19
|
-
|
20
|
-
A 'traditional' style URL with standard query strings such as
|
11
|
+
Refactor an ugly 'traditional' URL, such as
|
21
12
|
|
22
13
|
craigslist.org/search/housing?cat=loft&minPrice=500&maxPrice=1000&cats=true&dogs=true&bedrooms=2
|
23
14
|
|
24
|
-
|
25
|
-
|
26
|
-
craigslist.org/search/housing/type:loft;price:500-1000;pets:cats,dogs;bedrooms:2/
|
15
|
+
to
|
27
16
|
|
28
|
-
|
17
|
+
craigslist.org/search/housing/type:loft;price:500-1000;pets:cats,dogs;bedrooms:2/
|
29
18
|
|
30
|
-
|
31
|
-
|
32
|
-
>> base_filters = { :name => ['apple','banana','cherry'], :price => 0..5, :weight => '4..' }
|
33
|
-
>> set = FilterSet.new(base_filters)
|
34
|
-
|
35
|
-
The filters can be exposed through
|
19
|
+
And parse it into a convenient set of filters
|
36
20
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
Changes are merged into an existing filter set, either implicitly or explicitly as CRUD functions
|
21
|
+
@filters = Refinuri::Parser.parse_url(params[:filters])
|
22
|
+
|
23
|
+
Now you can pass all that filtery goodness straight into your ActiveRecord object
|
42
24
|
|
43
|
-
|
44
|
-
>> changes = { :name => 'dewberry' }
|
45
|
-
>> set.merge!(changes)
|
46
|
-
>> set[:name].value # => ['apple','banana','cherry','dewberry']
|
25
|
+
Apartment.filtered(@filters)
|
47
26
|
|
48
|
-
|
49
|
-
>> more_changes = { :update => { :name => ['eggplant','fig] }, :destroy => { :price => nil } }
|
50
|
-
>> set.merge!(more_changes)
|
51
|
-
>> set[:name].value # => ['apple','banana','cherry','dewberry','eggplant','fig']
|
52
|
-
>> set[:price] # => nil
|
27
|
+
And easily add links to your views to create, update, or delete the filters
|
53
28
|
|
54
|
-
|
29
|
+
filter_with_link("Allow Ferrets", { :pets => 'ferrets' })
|
30
|
+
filter_with_link("Only cats", { :create => { :pets => 'cats' } })
|
31
|
+
filter_with_link("Any Price", { :delete => { :price => nil } })
|
32
|
+
|
33
|
+
## Benefits ##
|
55
34
|
|
56
|
-
Not only does Refinuri improve the readability and length of URLs, but behind the scenes each piece of filtering data is being handled as the appropriate
|
35
|
+
Not only does Refinuri improve the readability and length of URLs, but behind the scenes each piece of filtering data is being handled as the appropriate datatype automatically. A range of prices is an actual Range, a list of items is actually an Array, etc.
|
57
36
|
|
58
|
-
This is useful both because it allows you to hand the queries off to ActiveRecord very easily, and also because the data can
|
37
|
+
This is useful both because it allows you to hand the queries off to ActiveRecord very easily, and also because the data can be changed very easily. Individuals filters can accept arbitrary numbers of items, numeric filters and go from Integers to Ranges with no effort, etc.
|
59
38
|
|
60
|
-
The hope is that with most of the heavy lifting being done automatically and in a consistent way, it will be much easier to actually implement a user-facing interface that allows for
|
39
|
+
The hope is that, with most of the heavy lifting being done automatically and in a consistent way, it will be much easier to actually implement a user-facing interface that allows for more advanced and useful controls to be implemented.
|
61
40
|
|
62
41
|
## Usage
|
63
42
|
|
@@ -65,122 +44,180 @@ The hope is that with most of the heavy lifting being done automatically and in
|
|
65
44
|
|
66
45
|
$ sudo gem install refinuri
|
67
46
|
|
68
|
-
###
|
47
|
+
### Example Application ###
|
69
48
|
|
70
|
-
####
|
71
|
-
|
72
|
-
|
73
|
-
|
49
|
+
#### routes.rb ####
|
50
|
+
|
51
|
+
match 'products/:id/:filters' => 'products#index'
|
52
|
+
|
53
|
+
#### application_controller.rb ####
|
54
|
+
|
55
|
+
before_filter :refine_filters
|
74
56
|
|
75
|
-
|
57
|
+
private
|
58
|
+
def refine_filters
|
59
|
+
@filters = Refinuri::Parser.parse_url(params[:filters]) if params[:filters]
|
60
|
+
end
|
61
|
+
|
62
|
+
#### products_controller.rb ####
|
63
|
+
|
64
|
+
@products = Product.filtered(@filters)
|
76
65
|
|
77
|
-
|
78
|
-
|
66
|
+
#### products/index.html.erb ####
|
67
|
+
|
68
|
+
<%= filter_with_link("Price less than $50", { :price => '..50' }) %>
|
69
|
+
<% @products.each do |product| %>
|
70
|
+
<%= product.name %>
|
71
|
+
<% end %>
|
79
72
|
|
80
|
-
|
73
|
+
## API ##
|
81
74
|
|
82
|
-
|
75
|
+
### Defining filters ###
|
83
76
|
|
84
|
-
|
77
|
+
Filters are initially defined as a Hash, where each key represents an attribute upon which a resource can be filtered, and the value represents the values that will pass the filter.
|
85
78
|
|
86
|
-
|
87
|
-
|
79
|
+
The following hash creates three filters:
|
80
|
+
|
81
|
+
{ :name => ['apple','banana','cherry'], :price => 0..5, :weight => '4..' }
|
88
82
|
|
89
|
-
|
90
|
-
@set[:key] => ['item1','item2','item3']
|
83
|
+
The Hash is used to create a new FilterSet, the class responsible for handling changes to the filters and outputting the filter values appropriately when necessary.
|
91
84
|
|
92
|
-
|
85
|
+
Refinuri::Base::FilterSet.new({ :name => ['apple','banana','cherry'], :price => 0..5, :weight => '4..' })
|
93
86
|
|
94
|
-
|
95
|
-
key:10-20
|
96
|
-
|
97
|
-
# which are parsed as standard Ruby ranges, available through the FilterSet
|
98
|
-
@set[:key] => 10..20
|
87
|
+
### Modifying filters ###
|
99
88
|
|
100
|
-
|
89
|
+
The FilterSet uses #merge! to accept changes to an existing set of filters. #merge! also accepts a hash, but allows for defining how the filters should be merged into the set.
|
101
90
|
|
102
|
-
|
91
|
+
By incorporating the various CRUD functions into the Hash, the new filters and values will be merged into the existing set.
|
103
92
|
|
104
|
-
|
93
|
+
@filters.merge!({ :create => { :color => 'orange' }, :update => { :name => 'dewberry', :price => 0..10 }, :delete => { :weight => nil } })
|
94
|
+
|
95
|
+
Any filters being merged that aren't explicitly stated as one of the CRUD functions is assumed to be an :update.
|
105
96
|
|
106
|
-
|
97
|
+
### Returning values ###
|
107
98
|
|
108
|
-
|
109
|
-
|
99
|
+
#### Values of entire filter sets: ####
|
100
|
+
|
101
|
+
\#to_h returns the entire set as a hash, similar to the one used to initialize the filter, but with any changes that have been made
|
110
102
|
|
111
|
-
|
112
|
-
|
103
|
+
@filters.to_h
|
104
|
+
=> { :name => ['apple','banana','cherry','dewberry'], :color => ['orange'], :price => 0..10, :weight => '4..' }
|
105
|
+
|
106
|
+
\#to\_url returns the entire set as a URL-friendly string, which could be included as an option in a link_to() helper
|
113
107
|
|
114
|
-
|
115
|
-
|
108
|
+
@fitlers.to_url
|
109
|
+
=> "name:apple,banana,cherry,dewberry;color:orange;price:0-10;weight:4+"
|
116
110
|
|
117
|
-
|
111
|
+
#### Values of individual filters within a set: ####
|
118
112
|
|
119
|
-
|
120
|
-
|
113
|
+
\#value returns the filter value as it was defined
|
114
|
+
|
115
|
+
@filters[:name].value
|
116
|
+
=> ['apple','banana','cherry']
|
117
|
+
|
118
|
+
\#to_db returns a value suitable for passing to a #where chain of an ActiveRecord object
|
121
119
|
|
122
|
-
|
123
|
-
|
120
|
+
@filters[:name].to_db
|
121
|
+
=> { :name => ['apple','banana','cherry'] }
|
122
|
+
|
123
|
+
\#to_s returns a value suitable for use in a URL
|
124
124
|
|
125
|
-
|
126
|
-
|
125
|
+
@filters[:name].to_s
|
126
|
+
=> "apple,banana,cherry"
|
127
127
|
|
128
|
-
|
128
|
+
### Parsing filter strings ###
|
129
129
|
|
130
|
-
|
130
|
+
Filters defined within strings, such as those contained in a URL, can be parsed into a FilterSet using:
|
131
|
+
|
132
|
+
Refinuri::Parser.parse_url()
|
133
|
+
|
134
|
+
### ActiveRecord integration ###
|
135
|
+
|
136
|
+
There are two ways of using FilterSets and filters with ActiveRecord.
|
131
137
|
|
132
|
-
|
138
|
+
#### Individually ####
|
139
|
+
|
140
|
+
The first is to utilize an individual filter manually along side the #where method, which is part of the Rails 3 query interface. The #to_db method returns values for filters specifically designed for use as a #where argument.
|
141
|
+
|
142
|
+
Product.where(@filters[:price].to_db) # => like Product.where(:price => 0..10)
|
143
|
+
Product.where(@filters[:name].to_db) # => like Product.where(:name => ['apple','banana','cherry'])
|
144
|
+
Product.where(@filters[:weight].to_db) # => like Product.where('weight <= 10')
|
133
145
|
|
134
|
-
|
146
|
+
#### En masse ####
|
135
147
|
|
136
|
-
|
148
|
+
The second is to pass the entire FilterSet off to ActiveRecord, and filter the object using all currently defined filters. The #filtered method is provided to ActiveRecord::Base for this purpose.
|
137
149
|
|
138
|
-
|
139
|
-
>> @filter.merge!({ :name => ['dewberry','eggplant'] })
|
140
|
-
# @filter[:name].value => ['apple','banana','cherry','dewberry','eggplant']
|
150
|
+
Product.filtered(@filters)
|
141
151
|
|
142
|
-
|
143
|
-
>> @filter.merge!({ :update => { :name => ['dewberry','eggplant'] } })
|
144
|
-
# @filter[:name].value => ['apple','banana','cherry','dewberry','eggplant']
|
152
|
+
This is like doing
|
145
153
|
|
146
|
-
|
147
|
-
|
148
|
-
|
154
|
+
Product.where(@filters[:price].to_db).where(@filters[:name].to_db).where(@filters[:weight].to_db)
|
155
|
+
|
156
|
+
### ActionView helpers ###
|
157
|
+
|
158
|
+
There are several link helpers included to serve common needs of creating and updating filters through the UI.
|
159
|
+
|
160
|
+
The first is a simple interface for passing a set of changes to the current set of filters, and returning the appropriate link.
|
149
161
|
|
150
|
-
|
151
|
-
|
152
|
-
# @filter[:name] => nil
|
162
|
+
filter_with_link("+ apple", { :name => 'apple' })
|
163
|
+
filter_with_link("- apple", { :delete => { :name => 'apple' } })
|
153
164
|
|
154
|
-
|
165
|
+
The next acts as a toggle for values within array-based filters, and will toggle individual values (not the entire filter, per se)
|
166
|
+
|
167
|
+
toggle_filter_with_link("+/- apple", { :name => 'apple' })
|
155
168
|
|
156
|
-
|
169
|
+
## Data types ##
|
157
170
|
|
158
|
-
|
171
|
+
### Arrays
|
172
|
+
|
173
|
+
In URLs an array is represented as
|
174
|
+
|
175
|
+
key:item1,item2,item3
|
159
176
|
|
160
|
-
|
161
|
-
|
162
|
-
|
177
|
+
Which are parsed as standard Ruby arrays, available through the FilterSet
|
178
|
+
|
179
|
+
@set[:key] => ['item1','item2','item3']
|
180
|
+
|
181
|
+
### Ranges
|
182
|
+
|
183
|
+
In URLs a range is represented as
|
184
|
+
|
185
|
+
key:10-20
|
163
186
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
187
|
+
Which are parsed as standard Ruby ranges, available through the FilterSet
|
188
|
+
|
189
|
+
@set[:key] => 10..20
|
190
|
+
|
191
|
+
(Support for both inclusive and exclusive ranges is not yet included, but coming soon)
|
192
|
+
|
193
|
+
### Unbounded ranges
|
194
|
+
|
195
|
+
Unbounded ranges are a non-ruby-standard datatype, which allows for a convenient way of defining only an upper- or lower-bound on a range.
|
196
|
+
|
197
|
+
### Lower-bound ranges
|
198
|
+
|
199
|
+
Represented in URLs as
|
200
|
+
|
201
|
+
key:10+
|
168
202
|
|
169
|
-
|
203
|
+
Parsed into the filter as strings in the format
|
170
204
|
|
171
|
-
|
172
|
-
# is equivalent to
|
173
|
-
Product.where(:price => 10..20).where('age >= 10')
|
205
|
+
@set[:key] => '10..'
|
174
206
|
|
175
|
-
|
207
|
+
And provides a convenience method for use in an ActiveRecord #where
|
208
|
+
|
209
|
+
@set[:key].to_db => 'key >= 10'
|
210
|
+
|
211
|
+
### Upper-bound ranges
|
212
|
+
|
213
|
+
Represented in URLs as
|
214
|
+
|
215
|
+
key:10-
|
176
216
|
|
177
|
-
|
178
|
-
|
179
|
-
|
217
|
+
Are parsed into strings in the format
|
218
|
+
|
219
|
+
@set[:key] => '..10'
|
180
220
|
|
181
|
-
|
182
|
-
# in the set, or removing it if it is
|
183
|
-
>> toggle_filter_with_link("Apple", { :name => 'apple' })
|
221
|
+
And provides a convenience method for use in an ActiveRecord #where
|
184
222
|
|
185
|
-
|
186
|
-
*coming soon...*
|
223
|
+
@set[:key].to_db => 'key <= 10'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.1
|
data/refinuri.gemspec
CHANGED