下一个可用的记录ID

时间:2011-03-28 08:52:42

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

@user = User.new 

@user.id返回nil,但在我保存之前我需要知道它。有可能吗?

7 个答案:

答案 0 :(得分:10)

不,您在保存前无法获取ID。 ID号来自数据库,但在您致电save之前,数据库不会分配ID。所有这一切都假设您当然正在使用ActiveRecord。

答案 1 :(得分:10)

是的,你可以!

我有同样的问题,并调查了文档。 解决这个问题的能力实际上与您的数据库类型密切相关。

Oracle Postgresql 确实有很多有用的功能来轻松解决这个问题。 对于MySQL(oracle)或 SkySQL (开源),它似乎更复杂(但仍然可能)。如果您需要高级数据库工具,我建议您避免使用这些(MySQL / SkySQL)数据库。

首先,您必须尽可能在应用程序设计中尽量避免这种情况,因为在ID保存之前使用ID会很危险。

可能存在您没有任何其他选择的情况: 例如,当两个表引用自身时,出于安全原因,您不允许对这些表进行DELETE或UPDATE。

在这种情况下,您可以使用(PostgreSQL,Oracle)数据库 nextval 功能生成下一个ID号,而无需实际插入新记录。

将它与 find_by_sql rails方法结合使用。

要使用postgreSQL和Rails执行此操作,请选择一个rails模型并添加一个类方法(而不是实例方法!)。 这可以通过方法名称开头的“ self ”单词来实现。 self 告诉Ruby这个方法只能由类使用,而不能由实例变量(用'new'创建的对象)使用。

My Rails模型:

class MyToy < ActiveRecord::Base

...

  def self.my_next_id_sequence
    self.find_by_sql "SELECT nextval('my_toys_id_seq') AS my_next_id"
  end
end

当您使用Rails迁移生成表格时,默认情况下,Rails会自动创建一个名为 id 的列,并将其设置为主键表。为确保您不会收到任何“重复主键错误”,Rails会自动在数据库中创建序列并将其应用于 id 专栏。对于您在表中插入的每个新记录(行),数据库将自行计算新记录的下一个ID。

Rails自动命名此序列,表名附加“_id_seq”。 PostgreSQL nextval函数必须按照here所解释的那样应用于此序列。

现在关于 find_by_sql ,正如here所解释的那样,它将创建包含您班级的新对象实例的数组 。每个对象都将包含SQL语句生成的所有列。这些列将以属性的形式出现在每个新对象实例中。 即使您的班级模型中不存在这些属性

正如您明智地意识到的那样,我们的nextval函数只返回一个值。 因此,find_by_sql将创建数组,其中包含具有单个属性单个对象实例。 为了便于读取此属性的值,我们将使用“my_next_id”命名生成的SQL列,因此我们的属性将具有相同的名称。

就是这样。我们可以使用我们的新方法:

my_resulting_array = MyToy.my_next_id_sequence
my_toy_object = my_resulting_array[0]
my_next_id_value = my_toy_object.my_next_id

用它来解决我们死锁的情况:

my_dog = DogModel.create(:name => 'Dogy', :toy_id => my_next_id_value)

a_dog_toy = MyToy.new(:my_dog_id => my_dog.id)
a_dog_toy.id = my_next_id_value
a_dog_toy.save

请注意如果您不使用my_next_id_value,则此ID号将永久丢失。 (我的意思是,未来任何记录都不会使用它。)

数据库不会等你使用它。如果在任何时候,您的应用程序需要在my_table_example中插入新记录(可能与我们正在使用my_next_id_sequence同时),数据库将始终在您生成的新记录之后立即为此新记录分配一个ID号my_next_id_sequence,考虑到你的my_next_id_value是保留的。 这可能会导致my_table_example中的记录看起来没有按创建时间排序。

答案 2 :(得分:3)

我有类似的情况。我在我的模型上使用find_by_sql调用序列,返回模型数组。我从arry的第一个对象得到了id。如下所示。

Class User < ActiveRecord::Base
        set_primary_key 'user_id'
        alias user_id= id=
    def self.get_sequence_id
        self.find_by_sql "select TEST_USER_ID_SEQ.nextval as contact_id from dual"
    end
end

以及您引用用户模型的类

@users = User.get_sequence_id
user = users[0]

答案 3 :(得分:1)

通常,ID会自动从数据库序列中填充。

在rails中,您可以使用after_create事件,这样您就可以在保存对象后访问该对象(因此它具有ID)。这将涵盖大多数情况。

当使用Oracle时我遇到了我想自己创建ID的情况(并没有使用序列),而在this post我提供了详细信息。简而言之,代码:

# a small patch as proposed by the author of OracleEnhancedAdapter: http://blog.rayapps.com/2008/05/13/activerecord-oracle-enhanced-adapter/#comment-240
# if a ActiveRecord model has a sequence with name "autogenerated", the id will not be filled in from any sequence
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
  alias_method :orig_next_sequence_value, :next_sequence_value

  def next_sequence_value(sequence_name)
    if sequence_name == 'autogenerated'
      # we assume id must have gotten a good value before insert!
      id
    else
      orig_next_sequence_value(sequence_name)
    end
  end
end

虽然此解决方案特定于Oracle增强,但我假设其他数据库将具有您可以重新定义的类似方法。

所以,虽然绝对不建议,但你想要绝对确定为什么你不想使用序列生成的id,如果需要它,它绝对是可能的。 这就是为什么我喜欢ruby和Ruby on Rails! :)

答案 4 :(得分:1)

在Oracle中,您可以使用此查询获取当前序列值:

SELECT last_number FROM user_sequences where sequence_name='your_sequence_name';

所以在你的模型类中,你可以这样做:

class MyModel < ActiveRecord::Base
  self.sequence_name = 'your_sequence_name'

  def self.my_next_id_sequence
    get_data = self.find_by_sql "SELECT last_number FROM user_sequences where sequence_name='your_sequence_name'"
    get_data[0].last_number
  end

end

最后,在控制器中你可以用这个获得这个值:

my_sequence_number = MyModel.my_next_id_sequence

所以,没有必要使用NEXTVAL获得你的下一个价值,你不会失去你的身份。

答案 5 :(得分:0)

你可以做的是User.max(id)。这将返回数据库中的最高ID,然后您可以添加1.虽然可能满足您的需求,但这不可靠。

答案 6 :(得分:0)

从Rails 5开始,您可以简单地致电next_sequence_value

注意:对于设置了self.sequence_name的Oracle,请求下一个序列值会通过增加序列值而产生副作用