列タイトルをクリックして並べ替えたい!

一覧表示のページで、TitleやCreated_on、Updated_on等々の列タイトルをクリックしたら、その列の順番で一覧表全体を並べ替えできるようにしたい。

クリックした列で並び替えを実行する。

並べ替えをするためには、paginateのオプションに「:order => フィールド名」を追加すれば、指定したフィールドの順番で並べ替えが出来るようだ。キーワード絞り込みのコードを参考に、以下のように作業してみた。

ビュー list.rhtml
<h1>Listing softwares</h1>

<table>
  <tr>
  <% for column in Software.content_columns %>
    <th><%= link_to column.human_name, 
                      :action => 'list',  
                      :order_field => column.name unless column.name == 'url' %></th>
  <% end %>
    <th><%= link_to Keyword, 
                      :action => 'list', 
                      :order_field => 'keyword_id' %></th>
  </tr>
...(以下省略)...
コントローラ softwares_controller.rb
  def list
    @keyword_id = params[:keyword_id]
    @order_field = params[:order_field]
    
    conditions = ["keyword_id = ?", @keyword_id] unless @keyword_id == nil
    order = @order_field unless @order_field == nil
    @software_pages, @softwares = paginate :softwares, :per_page => 10,
                                           :conditions => conditions,
                                           :order => order
  end

列タイトルにはリンクが設定され、クリックすれば並び替えが実行できるようになった。

逆順の並び替えもしたい!

今の機能は、クリックした列を対象に昇順に並び替えるだけ。並び替えには降順も必要だ。同じ列のクリックを繰り返せば、昇順と降順が切り替わるようにしたい。ややこしそうだが、以下のようにやってみた。

ビュー list.rhtml
<h1>Listing softwares</h1>

<table>
  <tr>
  <% for column in Software.content_columns %>
    <th><%= link_to column.human_name, 
                    :action => 'list', 
                    :order_field => column.name,
                    :order_direction => @order_next_direction unless column.name == 'url' %></th>
  <% end %>
    <th><%= link_to Keyword, 
                    :action => 'list', 
                    :order_field => 'keyword_id'
                    :order_direction => @order_next_direction %></th>
  </tr>
...(以下省略)...
  • 並べ替え方向のパラメータ:order_directionを追加した。
コントローラ softwares_controller.rb
  def list
    @keyword_id = params[:keyword_id]
    
    @order_field = params[:order_field]
    @order_direction = params[:order_direction] if params[:order_field] == flash[:order_field]......(1)
    @order_next_direction = (@order_direction == nil) ? 'desc' : nil......(2)
    flash[:order_field] = @order_field......(3)
    
    conditions = ["keyword_id = ?", @keyword_id] unless @keyword_id == nil
    order = "#{@order_field} #{@order_direction}" unless @order_field == nil......(4)
    @software_pages, @softwares = paginate :softwares, :per_page => 10,
                                           :conditions => conditions,
                                           :order => order
  end
  • (1)で、同じ列タイトルのクリックを繰り返している場合は、パラメータを読み取って変数@order_directionに設定するようにした。
    • 同じ列タイトルのクリックでない場合は、常に昇順の並べ替えで始まるようにしたいための処理。
    • 今クリックした列params[:order_field]と、その前にクリックした列flash[:order_field]を比較して、同じ列のクリックを繰り返しているかどうかを判断している。
  • (2)で、逆方向の並び替え方向を変数@order_next_directionに設定している。
    • 並び替えの方向は、昇順なら何も設定せずnil、降順ならdescの文字列を設定している。
    • paginateで、order => 'title desc'のように設定すると、titleフィールドを降順に並べ替える指定になる。並べ替え方向を指定しないorder => 'title'なら昇順になる。
  • (3)で、現在クリックした列タイトルを、flash[:order_field]に保存している。
    • flash[]を利用すれば、保存した内容は、次の処理サイクルまで維持される。
    • @で始まるインスタンス変数では、今の処理サイクルが終わるとクリアされてしまう。
  • (4)で、@order_fieldと@order_directionから並べ替えの条件を作成している。

ずいぶん分かりづらくなったが、これでなんとか昇順、降順の切り替えも可能になった。

昇順、降順のマークを付ける。

並び替えの方向を示すマークを列タイトルに付けることにした。昇順なら▲、降順なら▼を使ってみる。例えば、タイトルで並びかえたら、「▲Title」のように表示するつもり。

ビュー list.rhtml
<h1>Listing softwares</h1>

<table>
  <tr>
  <% for column in Software.content_columns %>
    <th><%= link_to "#{@direction_mark if @order_field==column.name}#{column.human_name}", 
                    :action => 'list', 
                    :order_field => column.name,
                    :order_direction => @order_next_direction unless column.name == 'url' %></th>
  <% end %>
    <th><%= link_to "#{@direction_mark if @order_field=='keyword_id'}Keyword", 
                    :action => 'list', 
                    :order_field => 'keyword_id'
                    :order_direction => @order_next_direction %></th>
  </tr>
...(以下省略)...
  • @order_fieldと列タイトルが一致した時に、タイトルの頭に方向マークが表示される。
コントローラ softwares_controller.rb
  def list
    @keyword_id = params[:keyword_id]
    
    @order_field = params[:order_field]
    @order_direction = params[:order_direction] if params[:order_field] == flash[:order_field]
    @order_next_direction = (@order_direction == nil) ? 'desc' : nil
    @direction_mark = @order_direction == nil ? '▲' : '▼'
    flash[:order_field] = @order_field
    
    conditions = ["keyword_id = ?", @keyword_id] unless @keyword_id == nil
    order = "#{@order_field} #{@order_direction}" unless @order_field == nil
    @software_pages, @softwares = paginate :softwares, :per_page => 10,
                                           :conditions => conditions,
                                           :order => order
  end

並べ替えとキーワード絞り込みの連動

最初に比べると、ずいぶん進化した感じだが、まだ一つやっておきたいことがある。それは、並び替えとキーワード絞り込みを連動して操作できるようにすること。例えば、キーワードRuby on Railsで絞り込みしている状態で、Title順に並び替えを実行できるようにしたい。(現状では、Title順に並び替えを実行すると、キーワード絞り込みは解除されてしまう)

ビュー list.rhtml
<h1>Listing softwares</h1>

<table>
  <tr>
  <% for column in Software.content_columns %>
    <th><%= link_to "#{@direction_mark if @order_field==column.name}#{column.human_name}", 
                    :action => 'list', 
                    :keyword_id => @keyword_id,
                    :order_field => column.name,
                    :order_direction => @order_next_direction unless column.name == 'url' %></th>
  <% end %>
    <th><%= link_to "#{@direction_mark if @order_field=='keyword_id'}Keyword", 
                    :action => 'list', 
                    :keyword_id => @keyword_id,
                    :order_field => 'keyword_id'
                    :order_direction => @order_next_direction %></th>
  </tr>
...(途中省略)...
  <td><%= link_to h(software.keyword.name), 
                 :action => 'list', 
                 :keyword_id => software.keyword_id, 
                 :order_field => @order_field, 
                 :order_direction => @order_direction unless software.keyword_id == nil %></td>
...(途中省略)...
<%= link_to 'New software', :action => 'new' %>
<%= ' |' unless @keyword_id == nil && @order_field == nil %>
<%= link_to 'Back', :action => 'list', :id => nil unless @keyword_id == nil && @order_field == nil %>
  • 連動させるために、並び替えと絞り込み両方のパラメータを、常に設定するようにした。
  • 並び替えや絞り込みをしている時はBackリンクを表示して、クリックすれば全ての条件を解除する。

これで、条件を連動して操作できるようになった。

キーワードだけの絞り込みと解除

並べ替えと絞り込みが連動して気になってきたのが、キーワード絞り込みの解除。今はBackリンクで解除すると、並べ替えまで解除してしまう。キーワード絞り込みだけ解除する方法が必要だ。昇順降順の切り替えと同じように、同じキーワードを繰り返しクリックすると、絞り込みとその解除をするようにしてみた。

コントローラ softwares_controller.rb
  def list
    @keyword_id = params[:keyword_id] unless params[:keyword_id] == flash[:keyword_id]
    flash[:keyword_id] = @keyword_id
    
    @order_field = params[:order_field]
    @order_direction = params[:order_direction] if params[:order_field] == flash[:order_field]
    @order_next_direction = (@order_direction == nil) ? 'desc' : nil
    @direction_mark = @order_direction == nil ? '▲' : '▼'
    flash[:order_field] = @order_field
    
    conditions = ["keyword_id = ?", @keyword_id] unless @keyword_id == nil
    order = "#{@order_field} #{@order_direction}" unless @order_field == nil
    @software_pages, @softwares = paginate :softwares, :per_page => 10,
                                           :conditions => conditions,
                                           :order => order
  end

  def list_by_keyword
    list
    render :action => 'list'
  end

  def list_in_order
    @keyword_id = params[:keyword_id] # キーワード絞り込み維持のため
    list
    render :action => 'list'
  end
ビュー list.rhtml
<h1>Listing softwares</h1>

<table>
  <tr>
  <% for column in Software.content_columns %>
    <th><%= link_to "#{@direction_mark if @order_field==column.name}#{column.human_name}", 
                    :action => 'list_in_order', 
                    :keyword_id => @keyword_id,
                    :order_field => column.name,
                    :order_direction => @order_next_direction unless column.name == 'url' %></th>
  <% end %>
    <th><%= link_to "#{@direction_mark if @order_field=='keyword_id'}Keyword", 
                    :action => 'list_in_order', 
                    :keyword_id => @keyword_id,
                    :order_field => 'keyword_id'
                    :order_direction => @order_next_direction %></th>
  </tr>
...(途中省略)...
  <td><%= link_to h(software.keyword.name), 
                 :action => 'list_by_keyword', 
                 :keyword_id => software.keyword_id, 
                 :order_field => @order_field, 
                 :order_direction => @order_direction unless software.keyword_id == nil %></td>
...(途中省略)...
<%= link_to 'New software', :action => 'new' %>
<%= ' |' unless @keyword_id == nil && @order_field == nil %>
<%= link_to 'Back', :action => 'list', :id => nil unless @keyword_id == nil && @order_field == nil %>

なんとか目的の機能は実現できたが、非常に分かりづらいコードになってしまった...。おそらく、並び替えとか、絞り込みは定番の機能だから、もっと分かり易い定石のような書き方があるのだと思う。今後は検索との連動にも対応したいので、もう少しシンプルに表現できるように勉強が必要だ。

  • 同じ列タイトルのクリックを繰り返すと、昇順、降順を切り替えて並べ替えが行われる。(最初のクリックは常に昇順)
  • 同じキーワードのクリックを繰り返すと、絞り込みと、その解除が交互に行われる。
  • 並び替えと、キーワード絞り込みが連動する。並び替え状態を維持しながら絞り込み、絞り込み状態を維持しながらの並び替えが可能。
  • 並び替え、または絞り込みの状態ではBackリンクを表示して、クリックすると条件を全て解除する。