#ruby-on-rails
#ruby-on-rails
Вопрос:
У меня есть 3 модели:
- Пользователь
- Головоломка
- Решение
Решение представляет собой первый раз, когда пользователь решил данную головоломку. Я хочу записать только в первый раз — последующие попытки не учитываются. Решение имеет индекс для комбинации user-puzzle.
Моя миграция выглядит так:
class CreateSolutions < ActiveRecord::Migration[6.0]
def change
create_table :solutions do |t|
t.integer :time
t.integer :attempts
t.references :user
t.references :puzzle
t.timestamps
end
add_index :solutions, [:user_id, :puzzle_id], unique: true
end
end
Я использую MySQL. Модели Puzzle и User имеют: has_many :solutions
.
В solutions_controller.rb
, у меня есть:
def create
@solution = Solution.new(solution_params)
@solution.user = current_user
@solution.puzzle = Puzzle.find(params["puzzle"])
respond_to do |format|
if @solution.save
format.json { render :show, status: :created }
else
format.json { render json: @solution.errors, status: :unprocessable_entity }
end
end
end
Проблема в том, что во второй раз, когда я пытаюсь сохранить головоломку, я получаю исключение:
ActiveRecord::RecordNotUnique in SolutionsController#create
Mysql2::Error: Duplicate entry '28-13' for key 'index_solutions_on_user_id_and_puzzle_id'
Цель: я бы хотел, чтобы он просто молча игнорировал последующие сохранения, а не создавал исключение.
Вот что я пробовал — все в solutions_controller.rb:
if @solution.save(validate: false)...
if @solution.save(:validate => false)...
if @solution.save(false)...
В идеале я хочу пропустить проверку только для повторяющегося индекса — я бы хотел перехватить другие ошибки.
Ответ №1:
В модели решения вы можете добавить
validates_uniqueness_of :user_id, scope: :puzzle_id
Он будет запускать запрос select каждый раз, когда вы пытаетесь сохранить запись решения. Если запись существует для решения запроса select.сохранение выдаст ошибку.
Или вы могли бы использовать first_or_initialize
Solution.where(user_id: current_user.id, puzzle_id: params[:puzzle][:id]).first_or_initialize(solution_params)
Это проверит, существует ли запись для данного запроса, если она не существует, она присвоит атрибуты в условии where и solution_params .
Обновить
def create
@solution = Solution.where(user_id: current_user.id, puzzle_id: params[:puzzle][:id]).first_or_initialize(solution_params)
respond_to do |format|
if @solution.save
format.json { render :show, status: :created }
else
format.json { render json: @solution.errors, status: :unprocessable_entity }
end
end
end
если вы сделаете это таким образом, нет необходимости проверять сообщение об ошибке, если запись уже существует в таблице, она будет присвоена @solution, а данные в параметрах решения не будут присвоены. Если запись не существует, @solution будет присвоен с данными в where condition и solution_params(значения user_id и puzzle_id будут взяты из того, что указано в where condition, а другие — из solution_params). Если для атрибута данные указаны в where condition и solution_params, то будет принято значение solution_params.
Комментарии:
1. Спасибо — первый
validates_uniqueness_of
подход, похоже, улавливает ошибку, но он может сработать. Я мог бы проверить@solution.errors.messages[:user_id]
, «ли это уже принято», и игнорировать только этот случай.2. Второй подход тоже работает, но меня это смущает. Это работает, только если я все еще инициализирую модель с
@solution = Solution.new(solution_params)
помощью etc. в контроллере. Но я не вижу, где я на самом деле использую@solution
то, что я инициализировал. Мой блок if теперь выглядит так:if Solution.where(user_id: current_user.id, puzzle_id: @solution.puzzle.id).first_or_initialize(solution_params) ...
— Я@solution.save
больше не звоню.3. Я обновил свой ответ, пожалуйста, проверьте его. Я полагаю, вы не хотите обновлять запись, если она уже существует правильно.
4. Теперь я вижу — еще раз спасибо. Единственное, что мне пришлось сделать иначе, чем в вашем примере, — указать
puzzle_id: params[:puzzle]
вместоparams[:puzzle][:id]
.params
это просто параметры POST, поэтому они представляют собой простые пары имя / значение. Если нет способа, которым объект головоломки должен расширяться, которого мне не хватает?5. моя ошибка запуталась, в идеале она должна быть отправлена в объект решения как puzzle_id. поскольку он назван puzzle, я подумал, что это может быть объект puzzle, исходящий из параметров.