refinuri 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README.md +157 -120
  2. data/VERSION +1 -1
  3. data/refinuri.gemspec +1 -1
  4. metadata +1 -1
data/README.md CHANGED
@@ -1,63 +1,42 @@
1
- # Refinuri
1
+ # Refinuri #
2
+ ## ri-fahy-nuh-ree ##
2
3
 
3
- Refinuri provides two primary functions related to querying and filtering data:
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
- ## Basics
9
+ ## The dime tour ##
9
10
 
10
- @filters = Refinuri::Parser.parse_url('name:apple,banana,cherry;price:0-5;age:7-')
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
- Will become
25
-
26
- craigslist.org/search/housing/type:loft;price:500-1000;pets:cats,dogs;bedrooms:2/
15
+ to
27
16
 
28
- ### Internal interface to filters
17
+ craigslist.org/search/housing/type:loft;price:500-1000;pets:cats,dogs;bedrooms:2/
29
18
 
30
- Filters are stored within a FilterSet, a Hash-like object that allows for easy access to properly-typed data and merging in changes to the set of filters. Filters and changes are passed into FilterSet as hashes.
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
- >> set[:name].value # => ['apple','banana','cherry']
38
- >> set.to_url # => 'name:apple,banana,cherry;price:0-5;weight:4+'
39
- >> set.to_h # => { :name => ['apple','banana','cherry'], :price => 0..5, :weight => '4..' }
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
- # implicit changes generally update existing values
44
- >> changes = { :name => 'dewberry' }
45
- >> set.merge!(changes)
46
- >> set[:name].value # => ['apple','banana','cherry','dewberry']
25
+ Apartment.filtered(@filters)
47
26
 
48
- # explicilty stated CRUD functions
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
- ## Benefits
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 data type automatically. A range of prices is an actual Range, a list of brands is an Array, etc.
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 change very freely. A filter can change from an Integer to a Range with very little overhear need to change things on the backend.
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 these more advanced controls than are generally being offered.
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
- ### Common API aspects
47
+ ### Example Application ###
69
48
 
70
- #### URL parsing
71
-
72
- >> Refinuri::Parser.parse_url('name:apple,banana,cherry')
73
- => #<Refinuri::Base::FilterSet:0x1003ad300>
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
- #### Generating URL string
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
- >> @filter_set.to_url
78
- => 'name:apple,banana,cherry'
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
- #### Available data types
73
+ ## API ##
81
74
 
82
- Currently supported datatypes include:
75
+ ### Defining filters ###
83
76
 
84
- ##### Arrays
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
- # in URLs an array is represented as
87
- key:item1,item2,item3
79
+ The following hash creates three filters:
80
+
81
+ { :name => ['apple','banana','cherry'], :price => 0..5, :weight => '4..' }
88
82
 
89
- # which are parsed as standard Ruby arrays, available through the FilterSet
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
- ##### Bounded ranges
85
+ Refinuri::Base::FilterSet.new({ :name => ['apple','banana','cherry'], :price => 0..5, :weight => '4..' })
93
86
 
94
- # in URLs a range is represented as
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
- (Support for both inclusive and exclusive ranges is not yet included)
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
- ##### Unbounded ranges
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
- Unbounded ranges are a non-standard datatype, which allows for a convenient way of defining only an upper- or lower-bound on a range.
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
- Lower-bound ranges
97
+ ### Returning values ###
107
98
 
108
- # represented in URLs as
109
- key:10+
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
- # are parsed into strings in the format
112
- @set[:key] => '10..'
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
- # and provides a convenience method for use in an ActiveRecord #where
115
- @set[:key].to_db => 'key >= 10'
108
+ @fitlers.to_url
109
+ => "name:apple,banana,cherry,dewberry;color:orange;price:0-10;weight:4+"
116
110
 
117
- Upper-bound ranges
111
+ #### Values of individual filters within a set: ####
118
112
 
119
- # represented in URLs as
120
- key:10-
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
- # are parsed into strings in the format
123
- @set[:key] => '..10'
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
- # and provides a convenience method for use in an ActiveRecord #where
126
- @set[:key].to_db => 'key <= 10'
125
+ @filters[:name].to_s
126
+ => "apple,banana,cherry"
127
127
 
128
- #### Creating and modifying FilterSets
128
+ ### Parsing filter strings ###
129
129
 
130
- New filters a populated with a hash
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
- >> Refinuri::Base::FilterSet.new({ :name => ['apple','banana','cherry'] })
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
- Filters are modified with #merge! by passing in a hash of changes.
146
+ #### En masse ####
135
147
 
136
- Changes that are not explicitly stated as a CRUD function are assumed to be UPDATEs. UPDATEs will create a new filter if it does not already exist, or will add unique values to an array, or replace a range. CREATE will either create a new filter or completely replace an existing filter.
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
- # implicit changes, treated as UPDATE
139
- >> @filter.merge!({ :name => ['dewberry','eggplant'] })
140
- # @filter[:name].value => ['apple','banana','cherry','dewberry','eggplant']
150
+ Product.filtered(@filters)
141
151
 
142
- # explicit UPDATE
143
- >> @filter.merge!({ :update => { :name => ['dewberry','eggplant'] } })
144
- # @filter[:name].value => ['apple','banana','cherry','dewberry','eggplant']
152
+ This is like doing
145
153
 
146
- # explicit CREATE
147
- >> @filter.merge!({ :creaet => { :name => ['dewberry','eggplant'] } })
148
- # @filter[:name].value => ['dewberry','eggplant']
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
- # explicit DELETE
151
- >> @filter.merge!({ :delete => { :name => nil } })
152
- # @filter[:name] => nil
162
+ filter_with_link("+ apple", { :name => 'apple' })
163
+ filter_with_link("- apple", { :delete => { :name => 'apple' } })
153
164
 
154
- #### Using filters with ActiveRecord
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
- Each data type has a #to_db method which returns a value suitable for use as the argument for ActiveRecord's #where method
169
+ ## Data types ##
157
170
 
158
- @set = Refinuri::Base::FilterSet.new({ :price => 10..20, :age => '10..' })
171
+ ### Arrays
172
+
173
+ In URLs an array is represented as
174
+
175
+ key:item1,item2,item3
159
176
 
160
- Product.where(@set[:price].to_db)
161
- # is equivalent to
162
- Product.where(:price => 10..20)
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
- and
165
- Product.where(@set[:age].to_db)
166
- # is equivalent to
167
- Product.where('age >= 10')
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
- If you would like to simply query an ActiveRecord object against all the filters in a FilterSet, the #filtered() method comes in handy
203
+ Parsed into the filter as strings in the format
170
204
 
171
- Product.filtered(@set)
172
- # is equivalent to
173
- Product.where(:price => 10..20).where('age >= 10')
205
+ @set[:key] => '10..'
174
206
 
175
- #### Helpers
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
- # creates a link to the current view, adding 'apple' the the :name filter,
178
- # or returning an unchanged link if 'apple' is already in the :name filter
179
- >> filter_with_link("Apple", { :name => 'apple' })
217
+ Are parsed into strings in the format
218
+
219
+ @set[:key] => '..10'
180
220
 
181
- # toggles the value 'apple' within the name filter, adding it if is not
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
- ### Example Application
186
- *coming soon...*
223
+ @set[:key].to_db => 'key <= 10'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.5.1
data/refinuri.gemspec CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{refinuri}
8
- s.version = "0.5.0"
8
+ s.version = "0.5.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Chris Kalafarski"]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: refinuri
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Kalafarski