good_sort 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|