good_sort 0.2.4

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