class PostCreator class VideoMsParseError < ArgumentError; end attr_reader :field_warnings def initialize actor:, attributes: @actor = actor @attributes = attributes.symbolize_keys @field_warnings = { } end def create! thumbnail_attachment = prepare_thumbnail_attachment post = Post.new(title: @attributes[:title].presence, url: @attributes[:url], thumbnail_base: @attributes[:thumbnail_base].presence, uploaded_user: @actor, original_created_from: @attributes[:original_created_from].presence, original_created_before: @attributes[:original_created_before].presence) ApplicationRecord.transaction do post.save! post.thumbnail.attach(thumbnail_attachment) if thumbnail_attachment.present? tags = planned_tags sections = planned_sections TagVersioning.record_tag_snapshots!(tags, created_by_user: @actor) post.video_ms = planned_video_ms post.save! sync_post_tags!(post, tags, sections) sync_parent_posts!(post, planned_parent_post_ids) PostVersionRecorder.record!(post:, event_type: :create, created_by_user: @actor) end post rescue StandardError post&.thumbnail&.purge if post&.thumbnail&.attached? raise end private def prepare_thumbnail_attachment PostThumbnailAttachmentBuilder.build( thumbnail: @attributes[:thumbnail], thumbnail_base: @attributes[:thumbnail_base].presence) end def planned_tags = planned_create_attributes[:normalised_tags] def planned_sections = planned_create_attributes[:tag_sections] def planned_parent_post_ids = planned_create_attributes[:normalised_parent_post_ids] def planned_video_ms planned_create_attributes[:video_ms] end def planned_create_attributes @planned_create_attributes ||= begin if @attributes.key?(:normalised_tags) { normalised_tags: @attributes[:normalised_tags], tag_sections: @attributes[:tag_sections] || { }, normalised_parent_post_ids: @attributes[:normalised_parent_post_ids] || [], video_ms: @attributes[:video_ms] } else PostCreatePlan.new(attributes: @attributes).build! end end end def sync_post_tags! post, desired_tags, sections desired_ids = desired_tags.map(&:id).to_set current_ids = post.tags.pluck(:id).to_set Tag.where(id: desired_ids - current_ids).find_each do |tag| PostTag.create_or_find_by!(post:, tag:, created_user: @actor) end PostTagSection.where(post_id: post.id).destroy_all sections.each do |tag_id, ranges| ranges.each do |begin_ms, end_ms| PostTagSection.create!(post_id: post.id, tag_id:, begin_ms:, end_ms:) end end PostTag.where(post_id: post.id, tag_id: (current_ids - desired_ids).to_a).kept.find_each do |post_tag| post_tag.discard_by!(@actor) end end def sync_parent_posts! post, ids if ids.include?(post.id) post.errors.add :parent_post_ids, '自分自身を親投稿にはできません.' raise ActiveRecord::RecordInvalid, post end missing = ids - Post.where(id: ids).pluck(:id) if missing.present? post.errors.add :parent_post_ids, "存在しない親投稿 Id. があります: #{ missing.join(' ') }" raise ActiveRecord::RecordInvalid, post end ids.each { |parent_post_id| PostImplication.create_or_find_by!(post:, parent_post_id:) } end end