идентификатор следующей доступной записи

@user = User.new 

@user.id возвращает ноль, но мне нужно знать это перед сохранением. Является ли это возможным ?


person meah    schedule 28.03.2011    source источник
comment
Я бы посмотрел на следующие ответы на stackoverflow.com/questions/3466908/ перед тем, как пойти по этому пути.   -  person McStretch    schedule 28.03.2011
comment
Чего вы пытаетесь достичь? Возможно, вы приближаетесь к чему-то не так, например построение отношений ...   -  person Markus Proske    schedule 28.03.2011
comment
User.maximum(:id).next [stackoverflow.com/a/17523916/2231236]   -  person Nithin    schedule 07.10.2014


Ответы (7)


Нет, вы не можете получить идентификатор перед сохранением. Идентификационный номер берется из базы данных, но база данных не назначит идентификатор, пока вы не позвоните save. Все это, конечно, при условии, что вы используете ActiveRecord.

person mu is too short    schedule 28.03.2011

Да, ты можешь!

У меня был тот же вопрос, и я исследовал документы. Возможность решить этот вопрос на самом деле очень зависит от типа вашей базы данных.

В Oracle и Postgresql есть полезные функции, позволяющие легко решить эту проблему. Для MySQL (oracle) или SkySQL (с открытым исходным кодом) это кажется более сложным (но все же возможным). Я бы порекомендовал вам избегать использования этих (MySQL / SkySQL) баз данных, если вам нужны продвинутые инструменты для работы с базами данных.

Во-первых, вы должны попытаться избежать этой ситуации в максимально возможной степени в дизайне приложения, так как опасно играть с идентификаторами, прежде чем они будут сохранены.

Может возникнуть ситуация, когда у вас нет другого выбора: например, когда две таблицы ссылаются на себя, и по соображениям безопасности вы не разрешаете DELETE или UPDATE для этих таблиц.

В этом случае вы можете использовать функцию nextval базы данных (PostgreSQL, Oracle) для генерации следующего идентификационного номера без фактической вставки новой записи.

Используйте его вместе с методом rails find_by_sql.

Чтобы сделать это, например, с postgreSQL и Rails, выберите одну из своих моделей rails и добавьте метод класса (а не метод экземпляра!). Это возможно с помощью слова «self» в начале имени метода. self сообщает Ruby, что этот метод может использоваться только классом, а не его переменными экземпляра (объектами, созданными с помощью 'new').

Моя модель 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 столбец. Для каждой новой записи (строки), которую вы вставляете в свою таблицу, база данных сама вычисляет, каким будет следующий идентификатор для вашей новой записи.

Rails автоматически присваивает этой последовательности имя с добавлением имени таблицы с помощью «_id_seq». К этой последовательности необходимо применить функцию PostgreSQL nextval, как описано здесь .

Теперь о find_by_sql, как объяснено здесь, он создаст массив , содержащий экземпляры новых объектов вашего класса. Каждый из этих объектов будет содержать все столбцы, генерируемые оператором SQL. Эти столбцы будут появляться в каждом новом экземпляре объекта в виде атрибутов. Даже если этих атрибутов нет в вашей модели класса!

Как вы мудро поняли, наша функция nextval будет возвращать только одно значение. Таким образом, find_by_sql создаст массив, содержащий один экземпляр объекта с одним атрибутом. Чтобы упростить чтение значения этого самого атрибута, мы назовем результирующий столбец SQL «my_next_id», чтобы наш атрибут имел такое же имя.

Ну это все. Мы можем использовать наш новый метод:

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, этот номер идентификатора будет утерян навсегда. (Я имею в виду, что в будущем он не будет использоваться ни одной записью).

База данных не ждет, пока вы ею воспользуетесь. Если где-то в любое время вашему приложению необходимо вставить новую запись в my_table_example (возможно, в то же время, когда мы играем с my_next_id_sequence), база данных всегда будет назначать номер идентификатора этой новой записи сразу после той, которую вы создали с помощью my_next_id_sequence, учитывая, что ваше my_next_id_value зарезервировано. Это может привести к ситуациям, когда записи в my_table_example не будут отсортированы по времени создания.

person Douglas    schedule 25.01.2013

У меня была похожая ситуация. Я вызвал последовательность, используя find_by_sql в моей модели, которая возвращает массив модели. Я получил идентификатор от первого объекта arry. что-то вроде ниже.

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]
person Aanu    schedule 20.04.2011
comment
Если вы используете Postgres, from dual следует отбросить, поскольку Postgres не поддерживает этот синтаксис, но также не требует from для возврата значения. Таким образом, оператор find by sql становится self.find_by_sql "select nextval(TEST_USER_ID_SEQ) as contact_id". - person Paul Pettengill; 05.12.2012

Обычно идентификатор заполняется автоматически из последовательности базы данных.

В рельсах вы можете использовать событие after_create, которое дает вам доступ к объекту сразу после того, как он был сохранен (и, следовательно, у него есть идентификатор). Это подойдет для большинства случаев.

При использовании Oracle у меня был случай, когда я хотел создать идентификатор самостоятельно (а не использовать последовательность) и в этот пост, я подробно рассказываю, как я это сделал. Короче код:

# 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-расширенного, я предполагаю, что другие базы данных будут иметь аналогичный метод, который вы могли бы переопределить.

Итак, хотя это определенно не рекомендуется, и вы хотите быть абсолютно уверены, почему вы не хотите использовать идентификатор, сгенерированный последовательностью, если он необходим, это определенно возможно. Вот почему я люблю Ruby и Ruby on Rails! :)

person nathanvda    schedule 28.03.2011

В 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, и вы не потеряете свой идентификатор.

person Nikola Todorovic    schedule 23.12.2014
comment
Это очень старый, но достаточно опасный комментарий. Выбор последнего числа из user_sequences очень неверен. Конечно, это работает на поверхности, но игнорирует всевозможные проблемы (например, некоторые другие транзакции, получающие идентификатор). Кроме того, в этом конкретном примере вы получаете ПОСЛЕДНИЙ идентификатор, а метод называется NEXT_id ... Плохая идея! Невозможно получить следующий идентификатор, кроме как использовать .nextval, и да, это будет использовать идентификаторы, если вы их фактически не сохраните. Но этого не избежать. - person AnoE; 15.01.2016

Что вы можете сделать, так это User.max (id). который вернет наивысший идентификатор в базе данных, вы можете добавить 1. Это ненадежно, хотя может удовлетворить ваши потребности.

person Yule    schedule 28.03.2011
comment
Ни в малейшей степени ненадежный. Возьмем пример с использованием postgres - если кому-то не удается вставить запись 3 раза, и транзакция откатывается, следующее значение будет отключено на 3, лучше сделать следующее значение для конкретной базы данных ('seq') или, что еще лучше, не будет делать это вообще. - person Omar Qureshi; 28.03.2011
comment
И у вас будет состояние гонки: вычислим максимум, кто-то другой сделает вставку, а затем вы попробуете свою вставку. - person mu is too short; 21.04.2011

Начиная с Rails 5 вы можете просто вызвать next_sequence_value

Примечание: для Oracle, когда установлено self.sequence_name, запрос следующего значения последовательности создает побочный эффект за счет увеличения значения последовательности.

person Kirill    schedule 25.08.2020