为什么Foo.first返回最后一条记录?

时间:2012-05-01 00:37:44

标签: ruby-on-rails ruby-on-rails-3 postgresql activerecord

我在Foo中有2条记录,ID为1和2.两者都是按顺序创建的。记住,在Postgres中,记录没有固有的顺序。

在Rails控制台中。 Foo.firstFoo.last返回最后一条记录。我的印象是Foo.first会返回第一条记录。

这是一个问题。 SQL查询看起来像:

SELECT "foos".* FROM "foos" LIMIT 1
SELECT "foos".* FROM "foos" ORDER BY "foos"."id" DESC LIMIT 1

第二个查询(Foo.last)有一个ORDER BY DESC。那么为什么AR没有ORDER BY ASC .first?这背后的逻辑是什么?似乎有点“不一致”。

我可以通过执行以下操作轻松解决此问题:Foo.order('id ASC').first。但是寻找解释。

2 个答案:

答案 0 :(得分:6)

没有任何逻辑,如果对first(或last有任何意义),如果您忽略指定显式顺序,则会引发异常作为first的参数或作为当前范围链的一部分。除非指定了明确的排序,否则firstlast在关系数据库的上下文中都没有任何意义。

我的猜测是,如果没有明确的first,那么写order by whatever_the_pk_is的人认为order by是隐含的。然后他们可能做了一些实验来经验验证他们的假设,它恰好按照他们所期望的特定表格和数据库进行工作(迷你咆哮:这就是为什么你永远不会假设未指明的行为;如果某个特定的行为是明确指定,即使当前实现的行为方式或者经验证据表明它的行为方式,也不要假设它。

如果您追踪简单的M.first,就会发现it does this

limit(1).to_a[0]

没有明确的排序,所以你得到数据库使用的随机排序,可能是order by pk,也可能是磁盘上表的块顺序。如果您追踪M.last,则会转到find_last

def find_last
  #...
        reverse_order.limit(1).to_a[0]
  #...
end

reverse_order

def reverse_order
  relation = clone
  relation.reverse_order_value = !relation.reverse_order_value
  relation
end

@reverse_order_value实例变量未初始化,因此它将以nil开头,而!会将其变为true。如果您四处寻找@reverse_order_value的使用方式,那么您将进入reverse_sql_order

def reverse_sql_order(order_query)
  order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
  #...

并且有作者关于订购的无效假设让所有人都知道。该行应该是:

raise 'Specify an order you foolish person!' if order_query.empty?

我建议您始终使用.order(...).limit(1).first代替firstlast,以便一切都很好而且明确;当然,如果你想要last,你就会反转.order条件。或者,您可以随时说.first(:order => :whatever).last(:order => :whatever)再次明确一切。

答案 1 :(得分:2)

对于Rails版本4+,如果您没有定义任何订单,它将按主键排序。

# Find the first record (or first N records if a parameter is supplied).
# If no order is defined it will order by primary key.
#
#   Person.first # returns the first object fetched by SELECT * FROM people
#   Person.where(["user_name = ?", user_name]).first
#   Person.where(["user_name = :u", { u: user_name }]).first
#   Person.order("created_on DESC").offset(5).first
#   Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
def first(limit = nil)
  if limit
    if order_values.empty? && primary_key
      order(arel_table[primary_key].asc).limit(limit).to_a
    else
      limit(limit).to_a
    end
  else
    find_first
  end
end

来源:https://github.com/rails/rails/blob/4-0-stable/activerecord/lib/active_record/relation/finder_methods.rb#L75-L82