0

I'm mixing 2 arrays and want to sort them by their created_at attribute:

@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 %>
sawa
  • 165,429
  • 45
  • 277
  • 381
gal
  • 929
  • 4
  • 21
  • 39
  • I'm sorry, but I'm really struggling to understand what the problem is. Would you mind rephrasing the text after the first code sample? – Leo Cassarani Jul 02 '11 at 14:35
  • 2
    "the friends statuses always showing up on the top sorted by there created at attribute and the current_user_statuses in the bottem sorted by there created at attribute" --- and the times in created_at suggest that the records should be intermingled? – seph Jul 02 '11 at 14:38
  • @gal, please show the desired result. Thanks. – Mark Thomas Jul 02 '11 at 14:42
  • 2
    Post an example output, so we can see the dates. Have you checked to make sure they're not just coincidentally ordered this way? – d11wtq Jul 02 '11 at 15:09
  • Unless you show a good sample of the data being tested, we can't help you further. Telling everyone who has answered "the sorting is not my problem... my friends statuses always showing up in the top" does not help. Take the time to provide a full data sample. – the Tin Man Jul 03 '11 at 17:17
  • Change your view code to print the created_at dates so that we, and you, can see them. All of the sorting approaches suggested on this page will intermingle the `@current_user_statuses` and `@friends_statuses` assuming that created_at dates are intermingled. – Ray Baxter Jul 03 '11 at 18:54

5 Answers5

3

Try:

(current_user.statuses + current_user.friends.collect(&:statuses)) \
  .flatten.compact.sort_by(&:created_at)
Mori
  • 27,279
  • 10
  • 68
  • 73
  • the sorting is not my problem... my friends statuses always showing up in the top... – gal Jul 03 '11 at 00:54
1

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! { ... }
Casper
  • 33,403
  • 4
  • 84
  • 79
  • Quite true, though this isn't really an answer to the question ;) When you sort nil, you'll get `NoMethodError` – d11wtq Jul 02 '11 at 15:07
0

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.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • the sorting is not my problem... my friends statuses always showing up in the top... – gal Jul 03 '11 at 00:55
  • Actually, sorting is a problem in your code. You're using `sort` instead of `sort_by` which is significantly slower when dealing with data structures or objects. – the Tin Man Jul 03 '11 at 05:24
0
@statuses = (@current_user_statuses + @friends_statuses).sort_by(&:created_at)
Ray Baxter
  • 3,181
  • 23
  • 27
0

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.

Community
  • 1
  • 1
Harish Shetty
  • 64,083
  • 21
  • 152
  • 198
  • the sorting is not my problem... my friends statuses always showing up in the top... – gal Jul 03 '11 at 00:55
  • If you use the method I have suggested, this can only happen ONLY if the friend's statuses are newer than your statuses. – Harish Shetty Jul 03 '11 at 05:21