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 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
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 2
3
+ :patch: 4
4
+ :major: 0
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'
@@ -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
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ begin
4
+ require 'redgreen'
5
+ require 'turn'
6
+ rescue LoadError
7
+ end
8
+ require 'mocha'
9
+
10
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
11
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
@@ -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
+