Question

I am using rails to make a datatable that paginates with Ajax, and I am following railscast #340 to do so.

This episode makes use of a normal ActiveModel Class called ProductsDatatable or in my case OrdersDatatable to create and configure the table. My question has to do with ruby syntax in this class. I am trying to pass a collection of orders to the OrdersDatatable object, from the controller. I want to access this collection in the fetch_orders method.

I create the table object like this in order.rb:

@datatable = OrdersDatatable.new(view_context)
@datatable.shop_id = @current_shop.id
@datatable.orders_list = @orders # which is Order.in_process

And my OrdersDatatable class looks like this: (the important parts which probably need to change is the second line in initialize and the first line in fetch_orders)

class OrdersDatatable
  include Rails.application.routes.url_helpers
  include ActionView::Helpers::DateHelper
  include ActionView::Helpers::TagHelper

  delegate :params, :h, :link_to, :number_to_currency, to: :@view

  attr_accessor :shop_id, :orders_list

  def initialize(view)
    @view = view
    @orders_list = self.orders_list
  end

  def current_shop
    Shop.find(shop_id)
  end

  def as_json(options = {})
    {
      sEcho: params[:sEcho].to_i,
      iTotalRecords: orders.count,
      iTotalDisplayRecords: orders.count,
      aaData: data
    }
  end

private


  def data
    orders.map do |order|
      [
        order.id,
        order.name,
        h(time_tag(order.date_placed.in_time_zone)),
        order.state,
        order.source,
        order.payment_status,
        h(order.delivered? ? 'shipped' : 'unshipped'),
        h(number_to_currency order.final_total, unit: order.currency.symbol),
        h(link_to 'details', edit_admin_shop_order_path(current_shop, order)),
        h(link_to 'delete', admin_shop_order_path(current_shop, order), method: :delete, data: { confirm: 'Are you sure?' } ),
      ]
    end
  end

  def orders
    @orders ||= fetch_orders
  end

  def fetch_orders
    orders = orders_list.order("#{sort_column} #{sort_direction}")
    orders = orders.page(page).per_page(per_page)
    if params[:sSearch].present?
      orders = orders.where("title like :search", search: "%#{params[:sSearch]}%")
    end
    orders
  end

  def page
    params[:iDisplayStart].to_i/per_page + 1
  end

  def per_page
    params[:iDisplayLength].to_i > 0 ? params[:iDisplayLength].to_i : 10
  end

  def sort_column
    columns = %w[id name date_placed state source payment_status delivered final_total]
    columns[params[:iSortCol_0].to_i]
  end

  def sort_direction
    params[:sSortDir_0] == "desc" ? "desc" : "asc"
  end
end

When I change the first line in fetch_orders to this

orders = Order.in_process.order("#{sort_column} #{sort_direction}")

which is the hard-coded equivalent, it does work. So I just need the correct syntax

Was it helpful?

Solution

Short answer: If you've got an array, and want to sort it, use the sort_by method:

orders = orders_list.sort_by{|order| "#{order.sort_column} #{order.sort_direction}"}

Long answer: The reason your original code doesn't work is that in this case

Order.in_process.order("#{sort_column} #{sort_direction}")

you are building a query. in_process is a named scope (passing in some conditions), and .order tells rails what to order the query by. Then, when it runs out of chained methods, the query executes (runs some sql) and gets the records out of the DB to build a collection of objects.

Once you are working with a collection of objects, you can't call the .order method on it, as that's just used to assemble an sql query. You need to use Array#sort_by instead. sort_by takes a code block, into which is passed each object in the collection (as order in my example but you could call it anything, it's just a variable name).

BTW, if you just want to call a method on all the objects to sort them, you can use a "shortcut syntax" like .sort_by(&:methodname). This uses a little trick of ruby called Symbol#to_proc (http://railscasts.com/episodes/6-shortcut-blocks-with-symbol-to-proc).

So, for example, if there was a method in Order like so

def sort_string
  "#{self.sort_column} #{self.sort_direction}"
end

then you could change your code to

orders = orders_list.sort_by(&:sort_string)

which is neat.

OTHER TIPS

If you have an array, then you can sort like this.

   orders = orders_list.sort! {|a,b| a.sort_column <=> b.sort_direction}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top