Rails before_create приращение в алфавитном порядке - PullRequest
0 голосов
/ 27 сентября 2018

Итак, в моей таблице есть поле - exposure.Название поля называется setup_code. Каждый раз, когда создается exposure, я хочу увеличить setup_code от AZ, как мне этого добиться?

Вот что я сделал до сих пор, в соответствии сна этот другой ответ Я видел, что можно увеличить с помощью next

class Exposure < ApplicationRecord
  before_create :create_setup_code

  def create_setup_code
    last_setup_code = self.last.setup_code
    last_setup_code.next
  end
end

Этот подход будет работать, если у меня уже есть хотя бы один код_установки в базе данных, скажем, setup_code =«A», тогда для следующего кода_установки, это будет «B», когда вызывается before_create.

Как инициализировать с первым setup_code = 'A'?

Ответы [ 3 ]

0 голосов
/ 27 сентября 2018

Чтобы избежать условий гонки, вы можете использовать автоинкрементный столбец.

Сначала создайте автоинкрементный столбец:

class AddSetupCodeToExposures < ActiveRecord::Migration[5.2]
  def change
    add_column :exposures, :setup_code, :serial
  end
end

Этот пример для PostgreSQL.В MySQL вы можете создать вторичный столбец с автоинкрементом только в том случае, если вы используете MyISAM, а не INNODB (зачем вам?), Поэтому вам нужно будет найти другое решение.

Возможно, вы можете использовать столбец ID, так какв любом случае он автоматически увеличивается.

Отображение целого числа в буквы алфавита довольно прост в Ruby:

irb(main):008:0> ALPHABET = ('A'..'Z').to_a.unshift(nil)
=> [nil, "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
irb(main):009:0> ALPHABET[1]
=> "A"
irb(main):010:0> ALPHABET[26]
=> "Z"

В модели мы могли бы обрабатывать приведение значений с помощью пользовательского установщикаи getter:

class Exposure < ApplicationRecord
  # This creates a array of the 24 ASCII letters A..Z
  # adding null to the beginning lets us treat it as a 1 indexed array
  ALPHABET = ('A'..'Z').to_a.unshift(nil)

  def setup_code
    # the default value here handles out of range values
    self.class.integer_to_letter(super || 0) || "default_value"
  end

  def setup_code=(value)
    super self.class.integer_to_letter(value)
  end

  def self.integer_to_letter(integer)
    ALPHABET[integer]
  end

  def self.letter_to_integer(letter)
    ALPHABET.index(letter)
  end
end

Одна ошибка с автоматически увеличивающимися столбцами и значениями по умолчанию для базы данных заключается в том, что столбец не заполняется при вставке записи:

irb(main):005:0> e = Exposure.create
   (0.2ms)  BEGIN
  Exposure Create (0.7ms)  INSERT INTO "exposures" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id"  [["created_at", "2018-09-27 12:26:24.672016"], ["updated_at", "2018-09-27 12:26:24.672016"]]
   (0.6ms)  COMMIT
=> #<Exposure id: 3, created_at: "2018-09-27 12:26:24", updated_at: "2018-09-27 12:26:24", setup_code: nil>
irb(main):006:0> e.setup_code
=> "default_value"
irb(main):007:0> e.reload
  Exposure Load (0.7ms)  SELECT  "exposures".* FROM "exposures" WHERE "exposures"."id" = $1 LIMIT $2  [["id", 3], ["LIMIT", 1]]
=> #<Exposure id: 3, created_at: "2018-09-27 12:26:24", updated_at: "2018-09-27 12:26:24", setup_code: 3>
irb(main):008:0> e.setup_code
=> "C"

Поскольку ActiveRecord возвращает только id столбец при вставке.

Изменение существующего столбца на последовательный

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

# rename the old column
class RenameExposuresSequenceCode < ActiveRecord::Migration[5.2]
  def change
    rename_column :exposures, :setup_code, :old_setup_code
  end
end

# add the new column
class AddSequencedSetupCodeToExposures < ActiveRecord::Migration[5.2]
  def change
    add_column :exposures, :setup_code, :serial
  end
end

# convert the existing values 
class ConvertOldSetupCodes < ActiveRecord::Migration[5.2]
  def up
    Exposure.find_each do |e|
      converted_code = Exposure.letter_to_integer(e.old_setup_code)
      e.update_attribute(setup_code: converted_code) if converted_code
    end
  end

  def down
    Exposure.find_each do |e|
      converted_code = Exposure.integer_to_letter(e.setup_code)
      e.update_attribute(old_setup_code: converted_code) if converted_code
    end
  end
end

# remove the old column
class RemoveOldSetupCodeFromExposures < ActiveRecord::Migration[5.2]
  def change
    remove_column :exposures, :old_setup_code, :string
  end
end
0 голосов
/ 27 сентября 2018

На самом деле в Postgres есть довольно крутой способ сделать это, применив вычисленное значение по умолчанию к столбцу.

Сначала мы хотим определить функцию Postgres, которая дает вам букву с целым числом (1 =A, 26 = Z), чтобы мы могли получить букву с указанным идентификатором.

class CreateIdToLetterFunction < ActiveRecord::Migration[5.2]
  def up
    execute <<-SQL
    CREATE OR REPLACE FUNCTION id_to_letter(integer) RETURNS varchar
      AS 'select chr(64 + $1)' 
      LANGUAGE SQL
      IMMUTABLE
      RETURNS NULL ON NULL INPUT;
    SQL
  end

  def down
    execute <<-SQL
      DROP FUNCTION id_to_letter(integer);
    SQL
  end
end

chr(int) возвращает символ с заданным кодом ASCII.В ASCII A значение 65, поэтому нам нужно дополнить значение 64.

Затем мы добавим функцию, которая подсчитывает экспозиции, чтобы мы могли использовать ее по умолчанию:

class AddExposureCountFunction < ActiveRecord::Migration[5.2]
  def up
    execute <<-SQL
      CREATE OR REPLACE FUNCTION count_exposures() RETURNS bigint
        AS 'select count(*) from exposures'
        LANGUAGE SQL
        VOLATILE;
    SQL
  end

  def down
    execute <<-SQL
      DROP FUNCTION count_exposures();
    SQL
  end
end

Затеммы хотим изменить столбец expures.setup_code, чтобы добавить значение по умолчанию.

class AddDefaultSetupCodeToExposures < ActiveRecord::Migration[5.2]
  def change
    change_column_default(
      :exposures,
      :setup_code,
      from: nil,
      to: -> {"id_to_letter(CAST(count_exposures() AS integer) + 1)"}
    )
  end
end

Мы заключаем новое значение по умолчанию в лямбду (->{}), так как это говорит ActiveRecord, что значение не является литеральным значением и должнобыть добавлен в оператор как SQL.

Поскольку это обрабатывается на уровне БД, дополнительный код модели не требуется.Обратите внимание, что действует предостережение относительно значений по умолчанию, установленных БД, - значение будет равно нулю, пока вы не перезагрузите экземпляр из БД.

добавлено

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

class Exposure < ApplicationRecord
  SETUP_CODES = %w{ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A2 B2 C2 D2 E2 F2 G2 H2 }
  def self.next_setup_code(current_code)
    if !SETUP_CODES.index(current_code) || current_code == SETUP_CODES.last
      "error"
    else
      SETUP_CODES[ SETUP_CODES.index(current_code) + 1 ]
    end 
  end
end
0 голосов
/ 27 сентября 2018
class Exposure < ApplicationRecord
  NO_FORMAT = "A"
  before_create :create_setup_code

  def next_setup_code
    (Exposure.count > 0 ? Exposure.last.setup_code : Exposure::NO_FORMAT).succ
  end

  def create_setup_code
    self.setup_code = next_setup_code
  end
end

Вы можете создать столько, сколько хотите.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...