Как сделать так, чтобы проверка дополненной реальности происходила перед расширением модуля в моей модели?

Я использую FriendlyID, и он создает слаг на основе некоторых атрибутов записи при создании.

  extend FriendlyId
  friendly_id :name_and_school, use: :slugged

  def name_and_school
    "#{first_name} #{last_name} #{school.name}"
  end

У меня также есть следующее на моей модели:

  validates :first_name, :last_name, presence: true
  validates_associated :school, presence: true

Однако, когда я иду проверять эту проверку и отправляю форму с пустым значением для first_name, last_name and school, я получаю следующую ошибку:

  Position Load (0.8ms)  SELECT "positions".* FROM "positions" WHERE 1=0
   (0.8ms)  BEGIN
   (0.6ms)  ROLLBACK
Completed 500 Internal Server Error in 51ms (ActiveRecord: 11.7ms)



NoMethodError - undefined method `name' for nil:NilClass:
  app/models/profile.rb:79:in `name_and_school'
  friendly_id (5.1.0) lib/friendly_id/slugged.rb:295:in `should_generate_new_friendly_id?'
  friendly_id (5.1.0) lib/friendly_id/slugged.rb:304:in `set_slug'

Итак, ясно, что он обращается к этому методу name_and_school еще до того, как он попадает в проверки ActiveRecord.

Если он сначала попадет на проверку проверки, он не сгенерирует эту ошибку, а просто перезагрузит страницу с соответствующими ошибками.

Итак, как мне исправить это и убедиться, что слаг FriendlyID генерируется только в том случае, если все проверки пройдены?

Изменить 1

Поэтому я попытался настаивать на том, чтобы name_and_school возвращала верную строку только в том случае, если значения присутствуют так:

  def name_and_school
    "#{first_name} #{last_name} #{school.name}" if first_name.present? && last_name.present? && school.present?
  end

Теперь это работает для пустых/недействительных атрибутов first_name и last_name.

Однако, когда я оставляю поле школы пустым, теперь появляется эта ошибка:

Completed 500 Internal Server Error in 31689ms (Searchkick: 9.5ms | ActiveRecord: 23.6ms)

NoMethodError - undefined method `name' for nil:NilClass:
  app/models/profile.rb:139:in `search_data'
  searchkick (1.4.0) lib/searchkick/index.rb:289:in `search_data'
  searchkick (1.4.0) lib/searchkick/index.rb:66:in `block in bulk_index'
  searchkick (1.4.0) lib/searchkick/index.rb:66:in `bulk_index'
  searchkick (1.4.0) lib/searchkick/index.rb:54:in `store'
  searchkick (1.4.0) lib/searchkick/logging.rb:28:in `block in store'
  activesupport (5.0.0.1) lib/active_support/notifications.rb:164:in `block in instrument'
  activesupport (5.0.0.1) lib/active_support/notifications/instrumenter.rb:21:in `instrument'
  activesupport (5.0.0.1) lib/active_support/notifications.rb:164:in `instrument'
  searchkick (1.4.0) lib/searchkick/logging.rb:27:in `store'
  searchkick (1.4.0) lib/searchkick/index.rb:96:in `reindex_record'
  searchkick (1.4.0) lib/searchkick/model.rb:113:in `reindex'
  app/models/profile.rb:146:in `reindex_profile'

Что соответствует этому в моей модели:

after_commit :reindex_profile

  def search_data
    {
      name: name,
      bib_color: bib_color,
      height: height,
      weight: weight,
      player_type: player_type,
      school_name: school.name,
      age: age,
      position_name: positions.map(&:name)
    }
  end

  def reindex_profile
    reindex
  end

Как только новая запись зафиксирована, она запускает этот обратный вызов, который ожидает допустимый атрибут school.

Таким образом, значение nil по-прежнему не проходит проверку validates_associated.

Я даже пытался добавить ту же самую инструкцию if к методу reindex_profile вот так:

  def reindex_profile
    reindex if first_name.present? && last_name.present? && school.present? && bib_color.present? && player_type.present? && age.present?
  end

Но это тоже не работает.

Изменить 2

Попробовав ответ @codyeatworld, мы добились прогресса.

Теперь проблема в том, что, хотя я больше не получаю эти ошибки, запись создается, несмотря на то, что validates_associated :school, presence: true не соответствует действительности.

Например, вот пример запроса:

Started POST "/profiles" for ::1 at 2016-11-13 16:32:19 -0500
Processing by ProfilesController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"skJA+9NB3XhR/JLgQ==", "profile"=>{"avatar"=>#<ActionDispatch::Http::UploadedFile:0x007fbf3b35d9a8 @tempfile=#<Tempfile:/var/folders/0f/hgplttnd7d/T/RackMultipart20161113-16651-1wvdzdx.jpg>, @original_filename="Random-Image.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"profile[avatar]\"; filename=\"Random-Image.jpg\"\r\nContent-Type: image/jpeg\r\n">, "first_name"=>"Random", "last_name"=>"Brown", "dob(3i)"=>"13", "dob(2i)"=>"11", "dob(1i)"=>"1986", "weight"=>"", "height"=>"", "bib_color"=>"", "player_type"=>"player", "position_ids"=>[""], "school_id"=>"", "grade"=>"", "tournament_ids"=>[""], "email"=>"", "cell_phone"=>"", "home_phone"=>"", "grades_attributes"=>{"0"=>{"subject"=>"", "result"=>"", "grade_type"=>"csec", "_destroy"=>"false"}}, "transcripts_attributes"=>{"0"=>{"url_cache"=>"", "_destroy"=>"false"}}, "achievements_attributes"=>{"0"=>{"body"=>"", "achievement_type"=>"academic", "_destroy"=>"false"}}, "videos_attributes"=>{"0"=>{"vimeo_url"=>"", "official"=>"", "_destroy"=>"false"}}, "articles_attributes"=>{"0"=>{"source"=>"", "title"=>"", "url"=>"", "_destroy"=>"false"}}}, "commit"=>"Create Profile"}
  User Load (1.8ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 2], ["LIMIT", 1]]
  Role Load (1.8ms)  SELECT "roles".* FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL)))  [["user_id", 2]]
  Role Load (1.3ms)  SELECT "roles".* FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'coach') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL)))  [["user_id", 2]]
  Role Load (1.3ms)  SELECT "roles".* FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'player') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL)))  [["user_id", 2]]
   (1.7ms)  SELECT COUNT(*) FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL)) OR ((roles.name = 'coach') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL)))  [["user_id", 2]]
The "mime_type" Shrine metadata field will be set from the "Content-Type" request header, which might not hold the actual MIME type of the file. It is recommended to load the determine_mime_type plugin which determines MIME type from file content.
  Tournament Load (1.2ms)  SELECT "tournaments".* FROM "tournaments" WHERE 1=0
  Position Load (0.8ms)  SELECT "positions".* FROM "positions" WHERE 1=0
   (0.9ms)  BEGIN
  SQL (2.2ms)  INSERT INTO "profiles" ("first_name", "last_name", "dob", "bib_color", "created_at", "updated_at", "player_type", "grade", "home_phone", "cell_phone", "email", "avatar_data") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING "id"  [["first_name", "Yashin"], ["last_name", "Brown"], ["dob", Thu, 13 Nov 1986], ["bib_color", ""], ["created_at", 2016-11-13 21:32:19 UTC], ["updated_at", 2016-11-13 21:32:19 UTC], ["player_type", 0], ["grade", ""], ["home_phone", ""], ["cell_phone", ""], ["email", ""], ["avatar_data", "{\"id\":\"83d162a3d33bdc5d2527502e1d423ab3.jpg\",\"storage\":\"cache\",\"metadata\":{\"filename\":\"Random-Image.jpg\",\"size\":61798,\"mime_type\":\"image/jpeg\"}}"]]
  SQL (2.0ms)  INSERT INTO "transcripts" ("profile_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["profile_id", 30], ["created_at", 2016-11-13 21:32:19 UTC], ["updated_at", 2016-11-13 21:32:19 UTC]]
  Profile Load (3.7ms)  SELECT  "profiles".* FROM "profiles" WHERE "profiles"."id" = $1 LIMIT $2  [["id", 30], ["LIMIT", 1]]
   (1.6ms)  COMMIT
   (0.9ms)  BEGIN
  SQL (2.6ms)  UPDATE "profiles" SET "updated_at" = $1, "avatar_data" = $2 WHERE "profiles"."id" = $3  [["updated_at", 2016-11-13 21:32:19 UTC], ["avatar_data", "{\"id\":\"ec63dcc18ed5d60aa7a6626550f9f9ea.jpg\",\"storage\":\"store\",\"metadata\":{\"filename\":\"Random-Image.jpg\",\"size\":61798,\"mime_type\":\"image/jpeg\"}}"], ["id", 30]]
   (1.3ms)  COMMIT
  Profile Store (311.8ms)  {"id":30}
  Profile Store (32.2ms)  {"id":30}
  Profile Store (26.7ms)  {"id":30}
  Profile Store (18.9ms)  {"id":30}
Redirected to http://localhost:3000/profiles/30
Completed 302 Found in 530ms (Searchkick: 389.6ms | ActiveRecord: 32.2ms)

Обратите внимание, что "school_id"=>"", также обратите внимание, что URL-адрес теперь profiles/30 (а не URL-адрес FriendlyID, что означает, что слаг friendlyID не выполнялся, чего я и хочу).

Почему эта запись все еще создается, несмотря на мой validates_associated вызов модели?


person marcamillion    schedule 13.11.2016    source источник
comment
Попробуйте удалить use: :slugged ?   -  person codyeatworld    schedule 13.11.2016
comment
Но тогда это не будет генерировать слаг, который я хочу, в действительных записях, не так ли?   -  person marcamillion    schedule 13.11.2016
comment
Вы правы, я давно не использовал friendly_id. Этот ответ может объяснить, почему: stackoverflow.com/a/36478639/6742278 . Я думаю, вам нужно указать friendlyId генерировать слаг только в том случае, если школа присутствует.   -  person codyeatworld    schedule 13.11.2016
comment
@codyeatworld Часть этого подхода работает. Я обновил вопрос, чтобы показать, что работает, а что нет.   -  person marcamillion    schedule 14.11.2016


Ответы (1)


Я думаю, вы ищете should_generate_new_friendly_id тогда.

def should_generate_new_friendly_id?
  first_name.present? && last_name.present? && school.present?
end

FriendlyId попытается создать слаг только в том случае, если эти условия соблюдены.

Я думаю, что оскорбительной строкой в ​​​​searchkick является атрибут school.name.

def search_data
  {
    name: name,
    bib_color: bib_color,
    height: height,
    weight: weight,
    player_type: player_type,
    # school_name: school.name,
    school_name: (school.present? ? school.name : nil),
    age: age,
    position_name: positions.map(&:name)
  }
end

Я предполагаю, что вы хотите проверить наличие school. Метод validates_associated запустит проверку объекта/модели school. Он не проверяет наличие school_id.

validates :first_name, :last_name, :school_id, presence: true
# Remove validates_associated :school, presence: true

Я думаю, вы также можете опустить _id:

belongs_to :school
validates :school, presence: true
person codyeatworld    schedule 13.11.2016
comment
Хорошо... это делает то же самое, что и добавление if first_name.present? && ... Однако проблема, с которой я сталкиваюсь, заключается в том, что теперь другой модуль -- searchkick -- делает то же самое, что и FriendlyID. Мысли есть? Другими словами, я попробовал ваш ответ и все еще получаю сообщение об ошибке, связанное с school.name is nil. - person marcamillion; 14.11.2016
comment
Я обновил свой ответ. Трудно сказать, не видя больше моделей/как они связаны друг с другом. - person codyeatworld; 14.11.2016
comment
Хорошо, вы медленно, но верно движетесь к этому. Вы пропустили тернарный оператор, который я добавил, и который сработал, в том смысле, что он больше не выдает странную ошибку. Однако происходит нечто еще более странное. Запись была создана, хотя validates_associated :school, presence: true недействительна. Я собираюсь обновить вопрос с более подробной информацией. - person marcamillion; 14.11.2016
comment
Аааа идеально. Спасибо, бро. Именно в этом и была проблема. Спасибо большое! - person marcamillion; 14.11.2016
comment
Хм.... presence: true у меня не работает. Я получаю эту ошибку в этой строке: Unknown key: :presence. Valid keys are: :class_name, :anonymous_class, :foreign_key, :validate, :autosave, :foreign_type, :dependent, :primary_key, :inverse_of, :required, :polymorphic, :touch, :counter_cache, :optional - person marcamillion; 14.11.2016
comment
Извините, это было неправильно, исправлен пример с правильной информацией. Я знал, что есть способ обойтись без _id. - person codyeatworld; 14.11.2016
comment
Странно, когда я прочитал эту проблему - github.com/rails/rails/issues/18233 - кажется, что в Rails 5 все belongs_to должны быть действительными или присутствовать по умолчанию. Но, похоже, это не так со мной. - person marcamillion; 14.11.2016