Rails sorting on a virtual attribute generated from a model method

1.1k views Asked by At

I'm wondering about an awesome feature that could help a lot for sorting by model attributes or model virtual attributes.

I have a model called Box with some attributes. I'm showing on my index view a list of these boxes with attributes and a status. This status is a bit special before it's generated and not record in my database (we are talking about real time status, there isn't any interest to record it).

I'm stuck with a sorting problem on this status.

My idea is the following:

#Box.rb
attr_accessor :status

def generate_status
  self.status = "awesome status" 
end

def status
  @status
end

def status=(val)
  @status = val
end

And I would like to access to this information on my controller

#BoxesController.rb
@boxes = Box.all #With my sort by virtual attribute status

I don't really want to do (before knowing how to sort the virtual attribute):

@boxes = Box.all
@boxes.each do |b|
  b.status = b.generate_status
end

Finally if it's possible I'll generalize this to all listed method on my model through a Concern I think. But to start this work I need to know if it's possible to sort by a virtual attribute

Edit

Now I'm trying with this way in a scope:

#In a Concern included in my model
(["status"] rescue []).each do |method|
  scope "sort_by_#{method}", ->(order){
    if order.equal?("asc")
      self.where(nil).sort!{|a,b| a.send(method) <=> b.send(method)}
    else
      self.where(nil).sort!{|a,b| b.send(method) <=> a.send(method)}
    end
  }
end

There isn't any error but my elements aren't sorted.

Note that I called ["status"] temporary because I'm currently trying to access to this variable declared on my Model (where my concern in included).

1

There are 1 answers

2
KPheasey On

It isn't possible to sort by a virtual attribute using ActiveRecord, however, we can use sort_by! on the result. http://www.ruby-doc.org/core-2.1.5/Array.html#method-i-sort_by-21

@boxes.sort_by! { |a,b| a.status <=> b.status }

To automatically set the status, you could use after_find callback.

#Box.rb
attr_accessor :status

after_find :generate_status

def generate_status
    self.status = 'awesome status`
end