开发者

Problem sorting an array

I'm mixing 2 arrays and want to sort them by their created_at attr开发者_如何学编程ibute:

@current_user_statuses = current_user.statuses
@friends_statuses = current_user.friends.collect { |f| f.statuses }
@statuses = @current_user_statuses + @friends_statuses
@statuses.flatten!.sort!{ |a,b| b.created_at <=> a.created_at }

The @current_user_statuses and @friends_statuses each sort correctly, but combined they sort incorrectly, with the @friends_statuses always showing up on top sorted by their created_at attribute and the @current_user_statuses on the bottom sorted by their created_at attribute.

This is the view:

<% @statuses.each do |d| %>
<%= d.content %>
<% end %>


Try:

(current_user.statuses + current_user.friends.collect(&:statuses)) \
  .flatten.compact.sort_by(&:created_at)


You can not daisy chain the flatten! method like that. flatten! returns nil if no changes were made to the array. When you sort nil nothing will happen.

You need to separate them:

@statuses.flatten!
@statuses.sort! { ... }


Here's how I'd do it:

Set up the classes:

class User

  class Status
    attr_reader :statuses, :created_at
    def initialize(stats)
      @statuses = stats
      @created_at = Time.now
    end
  end

  attr_reader :statuses, :friends
  def initialize(stats=[], friends=[])
    @statuses = Status.new(stats)
    @friends = friends
  end
end

Define some instances, with some time gaps just for fun:

friend2 = User.new(%w[yellow 2])
sleep 1
friend1 = User.new(%w[orange 1])
sleep 2
current_user = User.new(%w[green 1], [friend1, friend2])

Here's how I'd do it differently; Get the statuses in created_at order:

statuses = [
  current_user.statuses, 
  current_user.friends.collect(&:statuses)
].flatten.sort_by(&:created_at)

Which looks like:

require 'pp'
pp statuses

# >> [#<User::Status:0x0000010086bd60
# >>   @created_at=2011-07-02 10:49:49 -0700,
# >>   @statuses=["yellow", "2"]>,
# >>  #<User::Status:0x0000010086bc48
# >>   @created_at=2011-07-02 10:49:50 -0700,
# >>   @statuses=["orange", "1"]>,
# >>  #<User::Status:0x0000010086bb30
# >>   @created_at=2011-07-02 10:49:52 -0700,
# >>   @statuses=["green", "1"]>]

I'm just building a temporary containing array to hold the current_user's status, plus the status of all the friends, then flattening it.

The (&:statuses) and (&:created_at) parameters are Rails short-hand for the statuses method of the instance, or created_at method of the instance.


@statuses = (@current_user_statuses + @friends_statuses).sort_by(&:created_at)


I know there are several solutions posted for your question. But all of these solutions can kill your system when the number of statuses grow in size. For this dataset, you have to perform the sorting and pagination in the database layer and NOT in the Ruby layer

Approach 1: Simple and concise

Status.find_all_by_user_id([id, friend_ids].compact, :order => :created_at)

Approach 2: Long and efficient

class User    
  def all_statuses
    @all_statuses ||=Status.all( :joins => "JOIN (
        SELECT friend_id AS user_id 
        FROM   friendships 
        WHERE  user_id = #{self.id}
      ) AS friends ON statuses.user_id = friends.user_id OR 
                      statuses.user_id = {self.id}",
      :order => :created_at
    )
  end    
end

Now you can get the sorted statuses in single query:

user.all_statuses

PPS: If this is my code I would further optimize the SQL. Refer to this answer for some more details.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜