@user = User.new
@user.id
返回nil,但在我保存之前我需要知道它。有可能吗?
答案 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,请求下一个序列值会通过增加序列值而产生副作用