good_sort 0.2.4
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/LICENSE +20 -0
- data/README.markdown +163 -0
- data/VERSION.yml +4 -0
- data/lib/good_sort.rb +18 -0
- data/lib/good_sort/sorter.rb +59 -0
- data/lib/good_sort/view_helpers.rb +74 -0
- data/lib/good_sort/will_paginate.rb +29 -0
- data/test/sorter_test.rb +112 -0
- data/test/test_helper.rb +11 -0
- data/test/view_helpers_test.rb +159 -0
- metadata +66 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Jason King
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
Good Sort
|
2
|
+
=========
|
3
|
+
|
4
|
+
Hate not having _the right way_™ to do column sorting in list (collection)
|
5
|
+
views? Well, fear not my dear friend, for good_sort has arrived.
|
6
|
+
|
7
|
+
It does Ajax for those with JS and regular links for those without.
|
8
|
+
|
9
|
+
It _just works_™ with `will_paginate`?
|
10
|
+
|
11
|
+
Installation
|
12
|
+
------------
|
13
|
+
|
14
|
+
### gem
|
15
|
+
|
16
|
+
To perform a system wide installation:
|
17
|
+
|
18
|
+
gem source -a http://gems.github.com
|
19
|
+
gem install JasonKing-good_sort
|
20
|
+
|
21
|
+
Then add it to your `config/environment.rb`:
|
22
|
+
|
23
|
+
config.gem 'JasonKing-good_sort', :lib => 'good_sort'
|
24
|
+
|
25
|
+
### plugin
|
26
|
+
|
27
|
+
script/plugin install git://github.com/JasonKing/good_sort.git
|
28
|
+
|
29
|
+
### git submodule
|
30
|
+
|
31
|
+
git submodule add git://github.com/JasonKing/good_sort.git vendor/plugins/good_sort
|
32
|
+
|
33
|
+
Usage
|
34
|
+
-----
|
35
|
+
|
36
|
+
### app/models/author.rb
|
37
|
+
sort_on :name, :updated_at
|
38
|
+
|
39
|
+
### app/controllers/site_controller.rb
|
40
|
+
def index
|
41
|
+
@authors = Author.all( Author.sort_by(params[:sort]) )
|
42
|
+
|
43
|
+
if request.xhr?
|
44
|
+
return render :partial => 'authors'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
### app/views/site/index.html.erb
|
49
|
+
<div id="authors">
|
50
|
+
<%= render :partial => 'authors' %>
|
51
|
+
</div>
|
52
|
+
|
53
|
+
### app/views/site/_authors.html.erb
|
54
|
+
<table>
|
55
|
+
<thead>
|
56
|
+
<tr>
|
57
|
+
<%
|
58
|
+
sort_headers_for :author, %w{name ranking phone updated_at} do |header|
|
59
|
+
"Last Changed" if header == 'updated_at'
|
60
|
+
end
|
61
|
+
%>
|
62
|
+
</tr>
|
63
|
+
</thead>
|
64
|
+
<tbody>
|
65
|
+
<% @authors.each do |author| -%>
|
66
|
+
<tr>
|
67
|
+
<td><%=h author.name %></td>
|
68
|
+
<td><%=h author.ranking %></td>
|
69
|
+
<td><%=h author.phone %></td>
|
70
|
+
<td><%=h author.updated_at %></td>
|
71
|
+
</tr>
|
72
|
+
<% end -%>
|
73
|
+
</tbody>
|
74
|
+
</table>
|
75
|
+
|
76
|
+
That's simple enough isn't it?
|
77
|
+
|
78
|
+
The `sort_headers_for` helper will make a heading for each one of the elements
|
79
|
+
in the array you pass in - if it's one of the fields that you've set sorting on
|
80
|
+
in your model (using the `sort_on` class method).
|
81
|
+
|
82
|
+
Methods
|
83
|
+
-------
|
84
|
+
|
85
|
+
### ActiveRecord::Base.sort\_on( *args )
|
86
|
+
|
87
|
+
This is the class method that you use in your model in order to let `good_sort`
|
88
|
+
know which attributes of your model can be used to sort the collection.
|
89
|
+
Obviously these can't be virtual attributes because we're generating SQL here
|
90
|
+
(if you don't know what virtual attributes are then google is your friend).
|
91
|
+
|
92
|
+
As well as attributes in your model, you can also supply `belongs_to`
|
93
|
+
association names which will make `good_sort` sort your collection based on the
|
94
|
+
fields in a JOINed table.
|
95
|
+
|
96
|
+
class Author < ActiveRecord::Base
|
97
|
+
belongs_to :state
|
98
|
+
sort_on :name, :updated_at, :state
|
99
|
+
end
|
100
|
+
|
101
|
+
The convention is that this will use the `name` attribute of the associated
|
102
|
+
model, but if you want the sorting done using a different field then you can
|
103
|
+
just specify it using key => value style params, like so:
|
104
|
+
|
105
|
+
class Author < ActiveRecord::Base
|
106
|
+
belongs_to :state
|
107
|
+
sort_on :name, :updated_at, :state => :long_name
|
108
|
+
end
|
109
|
+
|
110
|
+
If you're confident that you know what you're doing, then you can also specify a string for the value of the associated attribute, in which case `good_sort` will just trust you and use this as the `ORDER BY` clause. Make sure you qualify your field names with the join table name if there's any ambiguity. Like so:
|
111
|
+
|
112
|
+
class Author < ActiveRecord::Base
|
113
|
+
belongs_to :state
|
114
|
+
sort_on :name, :updated_at, :state => "COALESCE( states.long_name, states.short_name, '' )"
|
115
|
+
end
|
116
|
+
|
117
|
+
There's also no requirement to cram it all in on one line, you can have multiple
|
118
|
+
`sort_on` declarations, and they will just be accumulated.
|
119
|
+
|
120
|
+
### ActiveRecord::Base.sort\_by( params[:sort] )
|
121
|
+
|
122
|
+
This produces a `:order` hash suitable to be merged into your `Model.find` (or
|
123
|
+
`Model.paginate`) parameters based on the `:field` and `:down` input parameters.
|
124
|
+
|
125
|
+
### ActionView::Base#sort\_headers\_for( model\_name, header\_array, options = {}, &block )
|
126
|
+
|
127
|
+
With no options, this will create `<th>` elements for each element of the
|
128
|
+
header_array, they will be given an id which, for the `name` field of our
|
129
|
+
`author` example would be `author_header_name`. If it has sorting set for it
|
130
|
+
with `sort_on` in your model, then it will also be wrapped in a gracefully
|
131
|
+
degrading re-sorting ajaxified link which will replace the element with id of
|
132
|
+
pluralized model name, so for our author example it will replace the element
|
133
|
+
with the id of `"authors"` (it will also show/hide an element with id of
|
134
|
+
`"spinner"` during the request). If the list is already sorted by that field,
|
135
|
+
then a class of either `"up"` or `"down"` will be added to the `<th>` element.
|
136
|
+
|
137
|
+
So, all of those things can be overridden. The options you can pass in are as
|
138
|
+
follows:
|
139
|
+
|
140
|
+
* **:spinner** - The id of the element to show/hide during the AJAX request, defaults to `:spinner`
|
141
|
+
* **:tag** - The type of element to wrap your header links in, defaults to `:th`
|
142
|
+
* **:header** - Options passed to the content_tag for the :tag wrapper.
|
143
|
+
* No defaults, but :id is set to `<model>\_header\_<field>` and :class will have `"up"` or `"down"` added to it appropriately.
|
144
|
+
* **:remote** - Options passed to `link\_to\_remote` as second arg, see the docs for `link\_to\_remote` for these options, defaults below:
|
145
|
+
* **:update** - Defaults to the lower-case pluralized and underscored version of your model name - ie. model\_name.tablelize
|
146
|
+
* **:before** - Defaults to showing the `:spinner` element (whatever you set that to, or `"spinner"` if you don't set it).
|
147
|
+
* **:complete** - Defaults to hiding the `:spinner` element
|
148
|
+
* **:method** - :get - you probably shouldn't change this
|
149
|
+
* **:url** - No point in setting this, it is overridden with the link URL.
|
150
|
+
* **:html** - Options pass to the `link\_to\_remote` as the third argument, see the docs for `link\_to\_remote` for these, defaults below:
|
151
|
+
* **:title** - Defaults to "Sort by #{sort\_field\_tag}". If you embed the sort\_field\_tag attribute in your string then that will be replaced with the field\_name.titlize for you, eg: :title => "Order by #{sort\_field\_tag}" If you want anything fancier then you can override `sort\_header\_title` and do whatever you want.
|
152
|
+
* **:html** - No point in setting this, it is overridden with the link URL.
|
153
|
+
|
154
|
+
Finally, if you pass a block, then it will be yielded to for each field in your
|
155
|
+
header\_array, and you can provide different text to be displayed for as many of
|
156
|
+
the headings as you like.
|
157
|
+
|
158
|
+
As long as you require `good_sort` after you've required `will_paginate` then
|
159
|
+
`good_sort` will override the `will_paginate` view helper to inject the params
|
160
|
+
needed to ensure that the page links will all know about the sorting column.
|
161
|
+
The only caveat is that the call to `will_paginate` needs to be **within** the
|
162
|
+
partial that is rendered by the AJAX call so that it is re-rendered when you
|
163
|
+
sort.
|
data/VERSION.yml
ADDED
data/lib/good_sort.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module GoodSort
|
2
|
+
class << self
|
3
|
+
def shwing
|
4
|
+
require 'good_sort/view_helpers'
|
5
|
+
ActionView::Base.send :include, ViewHelpers
|
6
|
+
|
7
|
+
require 'good_sort/sorter'
|
8
|
+
ActiveRecord::Base.send :extend, Sorter
|
9
|
+
|
10
|
+
if ActionView::Base.instance_methods.include? 'will_paginate'
|
11
|
+
require 'good_sort/will_paginate'
|
12
|
+
ActionView::Base.send :include, WillPaginate
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
GoodSort.shwing
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module GoodSort
|
2
|
+
module Sorter
|
3
|
+
def sort_fields; @sort_fields ||= {}; end
|
4
|
+
def sort_by(p)
|
5
|
+
return unless p && p[:field] && p[:down]
|
6
|
+
f = p[:field]
|
7
|
+
unless options = sort_fields[f.to_sym]
|
8
|
+
raise ArgumentError, "Requested field #{f} was not defined in #{class_name} for sorting"
|
9
|
+
end
|
10
|
+
options = options.dup
|
11
|
+
options[:order] += ' DESC' unless p[:down].blank?
|
12
|
+
options
|
13
|
+
end
|
14
|
+
protected
|
15
|
+
def sort_on(*fields)
|
16
|
+
fields.each do |f|
|
17
|
+
|
18
|
+
if f.is_a? String or f.is_a? Symbol
|
19
|
+
if self.columns_hash[f.to_s]
|
20
|
+
sort_fields[f.to_sym] = { :order => quoted_table_name + '.' + f.to_s }
|
21
|
+
next
|
22
|
+
else
|
23
|
+
# if it's not one of ours, we'll see if it's an association
|
24
|
+
f = { f => :name }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if f.is_a? Hash
|
29
|
+
f.each do |k,v|
|
30
|
+
ass = association_for( k )
|
31
|
+
if v.is_a? String
|
32
|
+
# if we're supplied a string, then assume they know what they're
|
33
|
+
# doing and just string it up
|
34
|
+
sort_fields[k.to_sym] = { :order => v, :joins => k.to_sym }
|
35
|
+
else
|
36
|
+
unless ass_has_attr( ass, v )
|
37
|
+
raise ArgumentError, "belongs_to association #{k} does not have specified column #{v}"
|
38
|
+
end
|
39
|
+
sort_fields[k.to_sym] = { :order => ass_to_table(ass) + '.' + v.to_s, :joins => k.to_sym }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
next
|
43
|
+
end
|
44
|
+
raise ArgumentError, "Unrecognized option to sort_by"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
private
|
48
|
+
def association_for(k)
|
49
|
+
ass = self.reflect_on_association(k.to_sym) and ass.belongs_to? or raise ArgumentError, "belongs_to association not found for #{k}"
|
50
|
+
ass
|
51
|
+
end
|
52
|
+
def ass_has_attr(ass,v)
|
53
|
+
ass.klass.column_names.find{|e|e==v.to_s}
|
54
|
+
end
|
55
|
+
def ass_to_table(ass)
|
56
|
+
ass.quoted_table_name
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module GoodSort
|
2
|
+
module ViewHelpers
|
3
|
+
@@field_tag = '__FIELD__'
|
4
|
+
def sort_field_tag; @@field_tag; end
|
5
|
+
def sort_headers_for( m, h, options = {} )
|
6
|
+
|
7
|
+
id = m.to_s.singularize
|
8
|
+
m = m.to_s.classify
|
9
|
+
c = m.constantize
|
10
|
+
|
11
|
+
options.symbolize_keys!
|
12
|
+
options[:spinner] ||= :spinner
|
13
|
+
options[:tag] ||= :th
|
14
|
+
|
15
|
+
options[:header] ||= {}
|
16
|
+
options[:header].symbolize_keys!
|
17
|
+
|
18
|
+
options[:remote] ||= {}
|
19
|
+
options[:remote].symbolize_keys!
|
20
|
+
options[:remote][:update] ||= m.tableize
|
21
|
+
options[:remote][:before] = "$('#{options[:spinner]}').show()" unless options[:remote].has_key? :before
|
22
|
+
options[:remote][:complete] = "$('#{options[:spinner]}').hide()" unless options[:remote].has_key? :complete
|
23
|
+
options[:remote][:method] ||= :get
|
24
|
+
|
25
|
+
# save these for pagination calls later in the request
|
26
|
+
@remote_options = options[:remote].dup
|
27
|
+
|
28
|
+
options[:html] ||= {}
|
29
|
+
options[:html].symbolize_keys!
|
30
|
+
options[:html][:title] ||= "Sort by #{sort_field_tag}"
|
31
|
+
|
32
|
+
sf = c.sort_fields
|
33
|
+
logger.warn "GoodSort: #{m} has not had any sort_on fields set" if sf.nil?
|
34
|
+
|
35
|
+
h.each do |f|
|
36
|
+
options[:header][:id] = "#{id}_header_#{f}"
|
37
|
+
|
38
|
+
text = yield(f) if block_given?
|
39
|
+
text ||= f.to_s.titleize
|
40
|
+
|
41
|
+
unless sf[f.to_sym]
|
42
|
+
concat content_tag(options[:tag], text, options[:header])
|
43
|
+
next
|
44
|
+
end
|
45
|
+
|
46
|
+
concat sort_header( f, text, options )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def sort_header(f, text, options )
|
51
|
+
params[:sort] ||= {}
|
52
|
+
|
53
|
+
tag_options = options[:header].dup
|
54
|
+
if params[:sort][:field] == f.to_s
|
55
|
+
tag_options[:class] ||= ''
|
56
|
+
(tag_options[:class] += params[:sort][:down].blank? ? ' up' : ' down' ).strip!
|
57
|
+
end
|
58
|
+
content_tag( options[:tag], sort_link(f, text, options), tag_options )
|
59
|
+
end
|
60
|
+
|
61
|
+
def sort_link(f, text, options)
|
62
|
+
s = { :field => f, :down => params[:sort][:field] == f.to_s && params[:sort][:down].blank? ? true : nil }
|
63
|
+
ps = params.merge( :sort => s, :page => nil )
|
64
|
+
|
65
|
+
options[:remote][:url] = options[:html][:href] = url_for( :params => ps )
|
66
|
+
title = sort_header_title( text, options)
|
67
|
+
link_to_remote(text, options[:remote], options[:html].merge( :title => title))
|
68
|
+
end
|
69
|
+
|
70
|
+
def sort_header_title( field, options )
|
71
|
+
options[:html][:title].gsub(sort_field_tag, field)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module GoodSort
|
2
|
+
module WillPaginate
|
3
|
+
def self.included(base)
|
4
|
+
base.send :include, InstanceMethods
|
5
|
+
base.alias_method_chain :will_paginate, :good_sort
|
6
|
+
end
|
7
|
+
|
8
|
+
module InstanceMethods
|
9
|
+
def will_paginate_with_good_sort( collection = nil, options = {} )
|
10
|
+
will_paginate_without_good_sort( collection, options.merge( :remote => @remote_options, :params => params ) )
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class RemoteLinkRenderer < WillPaginate::LinkRenderer
|
17
|
+
def prepare(collection, options, template)
|
18
|
+
@remote = options.delete(:remote) || {}
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def page_link(page, text, attributes = {})
|
24
|
+
_url = url_for(page)
|
25
|
+
@template.link_to_remote(text, {:url => _url, :method => :get}.merge(@remote), { :href => _url })
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
WillPaginate::ViewHelpers.pagination_options[:renderer] = 'RemoteLinkRenderer'
|
data/test/sorter_test.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'good_sort/sorter'
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
module MockupStuff
|
6
|
+
def setup_mock(m,n); @_m = m; @_n = n; end
|
7
|
+
def columns_hash; { 'foo' => true, 'bar' => true }; end
|
8
|
+
def quoted_table_name; self.to_s.tableize; end
|
9
|
+
def class_name; self.to_s; end
|
10
|
+
def reflect_on_association(a)
|
11
|
+
ass = @_m
|
12
|
+
ass.stubs(:belongs_to?).returns(a.to_sym == :ass_exist)
|
13
|
+
|
14
|
+
ass.stubs(:quoted_table_name).returns(a.to_s.tableize)
|
15
|
+
|
16
|
+
ass_klass = @_n
|
17
|
+
ass_klass.stubs(:column_names).returns( %w{name last_name} )
|
18
|
+
|
19
|
+
ass.stubs(:klass).returns(ass_klass)
|
20
|
+
|
21
|
+
ass
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Todel
|
26
|
+
extend GoodSort::Sorter
|
27
|
+
extend MockupStuff
|
28
|
+
end
|
29
|
+
|
30
|
+
class Yodel
|
31
|
+
extend GoodSort::Sorter
|
32
|
+
extend MockupStuff
|
33
|
+
end
|
34
|
+
|
35
|
+
class GoodSortSorterTest < Test::Unit::TestCase
|
36
|
+
|
37
|
+
def setup
|
38
|
+
Todel.instance_variable_set :@sort_fields, {}
|
39
|
+
Todel.setup_mock(mock,mock)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_multiple_models
|
43
|
+
Yodel.instance_variable_set :@sort_fields, {}
|
44
|
+
Yodel.setup_mock(mock,mock)
|
45
|
+
|
46
|
+
Yodel.send :sort_on, :ass_exist => :last_name
|
47
|
+
Todel.send :sort_on, :foo, :ass_exist
|
48
|
+
|
49
|
+
assert_equal( { :joins => :ass_exist, :order => "ass_exists.last_name" }, Yodel.sort_fields[:ass_exist])
|
50
|
+
|
51
|
+
assert_equal( { :order => "todels.foo" }, Todel.sort_fields[:foo])
|
52
|
+
assert_equal( { :joins => :ass_exist, :order => "ass_exists.name" }, Todel.sort_fields[:ass_exist])
|
53
|
+
|
54
|
+
assert_nil Yodel.sort_fields[:foo]
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_sort_on_our_attributes
|
58
|
+
Todel.send :sort_on, :foo, :bar
|
59
|
+
assert_equal 2, Todel.sort_fields.length
|
60
|
+
assert_equal( { :order => "todels.foo" }, Todel.sort_fields[:foo])
|
61
|
+
assert_equal( { :order => "todels.bar" }, Todel.sort_fields[:bar])
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_association_sort_fields
|
65
|
+
Todel.send :sort_on, :ass_exist => :last_name
|
66
|
+
assert_equal "ass_exists.last_name", Todel.sort_fields[:ass_exist][:order]
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_default_association_sort_field
|
70
|
+
Todel.send :sort_on, :ass_exist
|
71
|
+
assert_equal "ass_exists.name", Todel.sort_fields[:ass_exist][:order]
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_argument_errors
|
75
|
+
assert_raise ArgumentError do
|
76
|
+
Todel.send :sort_on, :ass_imaginary
|
77
|
+
end
|
78
|
+
|
79
|
+
assert_raise ArgumentError do
|
80
|
+
Todel.send :sort_on, false
|
81
|
+
end
|
82
|
+
|
83
|
+
assert_raise ArgumentError do
|
84
|
+
Todel.send :sort_on, :ass_exist => :nonexistent
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_multiple_declarations
|
89
|
+
Todel.send :sort_on, :foo
|
90
|
+
Todel.send :sort_on, :bar
|
91
|
+
assert_equal 2, Todel.sort_fields.length
|
92
|
+
assert_equal( { :order => "todels.foo" }, Todel.sort_fields[:foo])
|
93
|
+
assert_equal( { :order => "todels.bar" }, Todel.sort_fields[:bar])
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_sort_by
|
97
|
+
Todel.send :sort_on, :foo
|
98
|
+
assert_equal( { :order => "todels.foo" }, Todel.sort_by( :field => 'foo', :down => '' ))
|
99
|
+
assert_equal( { :order => "todels.foo DESC"}, Todel.sort_by( :field => 'foo', :down => 'true' ))
|
100
|
+
assert_raise ArgumentError do
|
101
|
+
Todel.sort_by( :field => 'bar', :down => '' )
|
102
|
+
end
|
103
|
+
assert_nil Todel.sort_by nil
|
104
|
+
assert_nil Todel.sort_by( :field => "todels.foo" )
|
105
|
+
assert_nil Todel.sort_by( :down => '' )
|
106
|
+
assert_nil Todel.sort_by( :down => 'true' )
|
107
|
+
|
108
|
+
Todel.send :sort_on, :ass_exist
|
109
|
+
assert_equal( { :joins => :ass_exist, :order => "ass_exists.name" }, Todel.sort_by( :field => 'ass_exist', :down => '' ))
|
110
|
+
assert_equal( { :joins => :ass_exist, :order => "ass_exists.name DESC" }, Todel.sort_by( :field => 'ass_exist', :down => 'true' ))
|
111
|
+
end
|
112
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'good_sort/view_helpers'
|
3
|
+
require 'active_support'
|
4
|
+
require 'action_view'
|
5
|
+
|
6
|
+
class Foo; end
|
7
|
+
class Logger; end
|
8
|
+
class GoodSortViewHelperTest < Test::Unit::TestCase
|
9
|
+
include ActionView::Helpers::TagHelper
|
10
|
+
include GoodSort::ViewHelpers
|
11
|
+
|
12
|
+
def concat(a); @output << a; end
|
13
|
+
def params; @p ||= {}; end
|
14
|
+
def logger; @l ||= Logger.new; end
|
15
|
+
|
16
|
+
def setup
|
17
|
+
@output = ''
|
18
|
+
Foo.stubs(:sort_fields).returns( :name => true, :age => true )
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_default_headers
|
22
|
+
%w{name age}.each do |f|
|
23
|
+
expects(:url_for).returns( "/foo" )
|
24
|
+
expects(:link_to_remote).with(
|
25
|
+
f.titleize,
|
26
|
+
{
|
27
|
+
:update => 'foos',
|
28
|
+
:method => :get,
|
29
|
+
:url => "/foo",
|
30
|
+
:complete => %q{$('spinner').hide()},
|
31
|
+
:before => %q{$('spinner').show()}
|
32
|
+
},
|
33
|
+
{
|
34
|
+
:href => "/foo",
|
35
|
+
:title => "Sort by #{f.titleize}"
|
36
|
+
}).returns( "<link>#{f}</link>" )
|
37
|
+
end
|
38
|
+
|
39
|
+
sort_headers_for :foo, %w{name age bar}
|
40
|
+
assert_equal %q{<th id="foo_header_name"><link>name</link></th><th id="foo_header_age"><link>age</link></th><th id="foo_header_bar">Bar</th>},
|
41
|
+
@output
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_some_options
|
45
|
+
spinner_name = :foobar
|
46
|
+
update_id = :sesame
|
47
|
+
%w{name age}.each do |f|
|
48
|
+
expects(:url_for).returns( "/foo" )
|
49
|
+
expects(:link_to_remote).with(
|
50
|
+
f.titleize,
|
51
|
+
{
|
52
|
+
:update => update_id,
|
53
|
+
:method => :get,
|
54
|
+
:url => "/foo",
|
55
|
+
:complete => %Q{$('#{spinner_name}').hide()},
|
56
|
+
:before => %Q{$('#{spinner_name}').show()}
|
57
|
+
},
|
58
|
+
{
|
59
|
+
:class => :horton,
|
60
|
+
:href => "/foo",
|
61
|
+
:title => "Sort by #{f.titleize}"
|
62
|
+
}).returns( "<link>#{f}</link>" )
|
63
|
+
end
|
64
|
+
|
65
|
+
bar_text = 'Big bar'
|
66
|
+
sort_headers_for :foo, %w{name age bar}, :spinner => spinner_name, :tag => :td, :remote => { :update => update_id }, :html => { :class => :horton } do |f|
|
67
|
+
bar_text if f == 'bar'
|
68
|
+
end
|
69
|
+
assert_equal %Q{<td id="foo_header_name"><link>name</link></td><td id="foo_header_age"><link>age</link></td><td id="foo_header_bar">#{bar_text}</td>},
|
70
|
+
@output
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_sorting_name_up
|
74
|
+
params[:sort] ||= {}
|
75
|
+
params[:sort][:field] = 'name'
|
76
|
+
params[:sort][:down] = ''
|
77
|
+
|
78
|
+
p = { :name => true, :age => nil }
|
79
|
+
|
80
|
+
%w{name age}.each do |f|
|
81
|
+
expects(:url_for).with(:params => {:sort => {:field => f, :down => p[f.to_sym]}, :page => nil}).returns( "/foo" )
|
82
|
+
expects(:link_to_remote).with(
|
83
|
+
f.titleize,
|
84
|
+
{
|
85
|
+
:update => 'foos',
|
86
|
+
:method => :get,
|
87
|
+
:url => "/foo",
|
88
|
+
:complete => %q{$('spinner').hide()},
|
89
|
+
:before => %q{$('spinner').show()}
|
90
|
+
},
|
91
|
+
{
|
92
|
+
:href => "/foo",
|
93
|
+
:title => "Sort by #{f.titleize}"
|
94
|
+
}).returns( "<link>#{f}</link>" )
|
95
|
+
end
|
96
|
+
|
97
|
+
sort_headers_for :foo, %w{name age}
|
98
|
+
assert_equal %q{<th class="up" id="foo_header_name"><link>name</link></th><th id="foo_header_age"><link>age</link></th>},
|
99
|
+
@output
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_sorting_name_down
|
103
|
+
params[:sort] ||= {}
|
104
|
+
params[:sort][:field] = 'name'
|
105
|
+
params[:sort][:down] = 'true'
|
106
|
+
|
107
|
+
p = { :name => nil, :age => nil }
|
108
|
+
|
109
|
+
%w{name age}.each do |f|
|
110
|
+
expects(:url_for).with(:params => {:sort => {:field => f, :down => p[f.to_sym]}, :page => nil}).returns( "/foo" )
|
111
|
+
expects(:link_to_remote).with(
|
112
|
+
f.titleize,
|
113
|
+
{
|
114
|
+
:update => 'foos',
|
115
|
+
:method => :get,
|
116
|
+
:url => "/foo",
|
117
|
+
:complete => %q{$('spinner').hide()},
|
118
|
+
:before => %q{$('spinner').show()}
|
119
|
+
},
|
120
|
+
{
|
121
|
+
:href => "/foo",
|
122
|
+
:title => "Sort by #{f.titleize}"
|
123
|
+
}).returns( "<link>#{f}</link>" )
|
124
|
+
end
|
125
|
+
|
126
|
+
sort_headers_for :foo, %w{name age}
|
127
|
+
assert_equal %q{<th class="down" id="foo_header_name"><link>name</link></th><th id="foo_header_age"><link>age</link></th>},
|
128
|
+
@output
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_sorting_age_up
|
132
|
+
params[:sort] ||= {}
|
133
|
+
params[:sort][:field] = 'age'
|
134
|
+
params[:sort][:down] = ''
|
135
|
+
|
136
|
+
p = { :name => nil, :age => true }
|
137
|
+
|
138
|
+
%w{name age}.each do |f|
|
139
|
+
expects(:url_for).with(:params => {:sort => {:field => f, :down => p[f.to_sym]}, :page => nil}).returns( "/foo" )
|
140
|
+
expects(:link_to_remote).with(
|
141
|
+
f.titleize,
|
142
|
+
{
|
143
|
+
:update => 'foos',
|
144
|
+
:method => :get,
|
145
|
+
:url => "/foo",
|
146
|
+
:complete => %q{$('spinner').hide()},
|
147
|
+
:before => %q{$('spinner').show()}
|
148
|
+
},
|
149
|
+
{
|
150
|
+
:href => "/foo",
|
151
|
+
:title => "Sort by #{f.titleize}"
|
152
|
+
}).returns( "<link>#{f}</link>" )
|
153
|
+
end
|
154
|
+
|
155
|
+
sort_headers_for :foo, %w{name age}
|
156
|
+
assert_equal %q{<th id="foo_header_name"><link>name</link></th><th class="up" id="foo_header_age"><link>age</link></th>},
|
157
|
+
@output
|
158
|
+
end
|
159
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: good_sort
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jason King
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-12 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Simple column sorting for tables of data, AJAX or plain.
|
17
|
+
email: jk@handle.it
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.markdown
|
24
|
+
- LICENSE
|
25
|
+
files:
|
26
|
+
- README.markdown
|
27
|
+
- VERSION.yml
|
28
|
+
- lib/good_sort/sorter.rb
|
29
|
+
- lib/good_sort/view_helpers.rb
|
30
|
+
- lib/good_sort/will_paginate.rb
|
31
|
+
- lib/good_sort.rb
|
32
|
+
- test/sorter_test.rb
|
33
|
+
- test/view_helpers_test.rb
|
34
|
+
- test/test_helper.rb
|
35
|
+
- LICENSE
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://github.com/JasonKing/good_sort
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --inline-source
|
43
|
+
- --charset=UTF-8
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.3.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 2
|
64
|
+
summary: README.markdown
|
65
|
+
test_files: []
|
66
|
+
|