Найти или создать вложенные атрибуты в таблице полиморфного объединения за одну транзакцию - PullRequest
2 голосов
/ 23 сентября 2019

Фон

Я хочу сохранить навыки на различных типах объектов.Как правило, пользователь может иметь проект или опыт и пометить его различными навыками и т. Д. Ruby, Python или Excel.Эти навыки являются глобальными и используются в различных возможных объектах, которые «умелые».

Задача состоит из трех моделей:

  1. Project - содержит информацию о проекте
  2. Skill - содержит название навыка
  3. SkillObject - таблица соединений между навыком и проектами (полиморфная)

Пожалуйста, посмотрите на эту ER-диаграмму дляболее детальная картина.


Проблема

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

Я хочу что-то вроде find_or_create_by, чтобы избежать дублирования навыков.В то же время проверки должны гарантировать, что два одинаковых навыка не могут быть применены к одному и тому же проекту ( skill_object.rb ) и что имя навыка не может быть нулевым ( skill.rb )

Требуемое поведение: если проверка навыков не удалась, тогда ни проект, ни навыки не сохраняются в базе данных.Если вы введете два разных имени навыка, и одно уже находится в базе данных, то существующее должно быть найдено и подключено к проекту, а другое создано и подключено через таблицу объединения, если не произойдет сбой проверки.


project.rb
class Project < ApplicationRecord
  include Skillable
  [...] 
end

skill_object.rb

class SkillObject < ApplicationRecord
  belongs_to :skill, inverse_of: :skill_objects
  belongs_to :skillable, polymorphic: true

  delegate :delete_if_empty_skill_objects, to: :skill
  after_destroy :delete_if_empty_skill_objects

  # Avoiding duplicates of the same skill
  validates :skill, uniqueness: { scope: :skillable }

end

skill.rb

class Skill < ApplicationRecord

  has_many :skill_objects, inverse_of: :skill

  has_many :projects, through: :skill_objects, source: :sectorable, source_type: 'Project'
  has_many :experiences, through: :skill_objects, source: :sectorable, source_type: 'Experience'

  validates :name, uniqueness: true, presence: true

  def delete_if_empty_skill_objects
    self.destroy if self.skill_objects.empty? and not self.is_global
  end
end

Я использую вопрос, который включен в различные типы умелых объектов:

Concercns / skillable.rb

module Skillable

  extend ActiveSupport::Concern

  included do
    attr_accessor :skills_attributes
    after_save :set_skills


    # Adding needed relations for skillable objects
    has_many :skill_objects, -> { order(created_at: :asc) }, as: :skillable, dependent: :destroy
    has_many :skills, through: :skill_objects


    private
    def set_skills

      # THIS CODE IS THE ONE I AM STRUGGLING WITH


      # Parsing out all the skill names
      skill_names = skills_attributes.pluck(:name)

      # Removing existing skills
      self.skills = []


      # Building new skills
      skill_names.each do |name|
        existing = Skill.find_by(name: name)

        if existing
          self.skills << existing
        else
          self.skills.new(name: name)
        end
      end

      raise ActiveRecord::Rollback unless self.valid?
      self.skills.each(&:save)
    end
  end
end

Кто-нибудь знает, как я могу написать функцию set_skill в skillable.rb, чтобы иметь возможность сохранять и обновлять навыки, проверять родительский объект и навыки, делать откат, если он не был проверен, и добавлять соответствующие ошибки в объект проекта, если он не работает?

...