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.
精彩评论