Newer
Older
dmpopidor / lib / dmpopidor / controllers / answers.rb
module Dmpopidor
  module Controllers
    module Answers
      # Added Research outputs support
      def create_or_update
        p_params = permitted_params()

        # First it is checked plan exists and question exist for that plan
        begin
          p = Plan.find(p_params[:plan_id])
          if !p.question_exists?(p_params[:question_id])
            # rubocop:disable LineLength
            render(status: :not_found, json: {
              msg: _("There is no question with id %{question_id} associated to plan id %{plan_id} for which to create or update an answer") % {
                question_id: p_params[:question_id],
                plan_id: p_params[:plan_id]
              }
            })
            # rubocop:enable LineLength
            return
          end
        rescue ActiveRecord::RecordNotFound
          # rubocop:disable LineLength
          render(status: :not_found, json: {
            msg: _("There is no plan with id %{id} for which to create or update an answer") % {
              id: p_params[:plan_id]
            }
          })
          # rubocop:enable LineLength
          return
        end
        q = Question.find(p_params[:question_id])

        # rubocop:disable BlockLength
        Answer.transaction do
          begin
            @answer = Answer.find_by!({
                plan_id: p_params[:plan_id], question_id: p_params[:question_id],
                research_output_id: p_params[:research_output_id]
            })
            authorize @answer
            pa = p_params.merge(user_id: current_user.id)
            # Exclude structured answer parameters (since they are not stored directly in the Answer object)
            pa = pa.select { |k, v| !schema_params(flat = true).include?(k) } if q.question_format.structured  
            @answer.update(pa)
            if p_params[:question_option_ids].present?
              # Saves the record with the updated_at set to the current time.
              # Needed if only answer.question_options is updated
              @answer.touch()
            end
            if q.question_format.structured
              save_structured_answer(q)
            end
            if q.question_format.rda_metadata?
              @answer.update_answer_hash(
                JSON.parse(params[:standards]), p_params[:text]
              )
              @answer.save!
            end
          rescue ActiveRecord::RecordNotFound
            pa = p_params.merge(user_id: current_user.id)
            # Exclude structured answer parameters (since they are not stored directly in the Answer object)
            pa = pa.select { |k, v| !schema_params(flat = true).include?(k) } if q.question_format.structured
            @answer = Answer.new(pa)
            @answer.lock_version = 1
            authorize @answer
            if q.question_format.structured
              save_structured_answer(q)
            end
            if q.question_format.rda_metadata?
              @answer.update_answer_hash(
                JSON.parse(params[:standards]), p_params[:text]
              )
            end
              @answer.save!
              rescue ActiveRecord::StaleObjectError
                @stale_answer = @answer
                @answer = Answer.find_by(
                  plan_id: p_params[:plan_id],
                  question_id: p_params[:question_id], 
                  research_output_id: p_params[:research_output_id] 
                )
              end
            end
            # rubocop:enable BlockLength

            if @answer.present?
              @plan = Plan.includes(
                sections: {
                  questions: [
                    :answers,
                    :question_format
                  ]
                }
              ).find(p_params[:plan_id])
              @question = @answer.question
              @section = @plan.sections.find_by(id: @question.section_id)
              template = @section.phase.template
              @research_output = @answer.research_output
              # rubocop:disable LineLength
              render json: {
              "answer" => {
                "id" => @answer.id
              },
              "question" => {
                "id" => @question.id,
                "answer_lock_version" => @answer.lock_version,
                "locking" => @stale_answer ?
                  render_to_string(partial: "answers/locking", locals: {
                    question: @question,
                    answer: @stale_answer,
                    research_output: @research_output,
                    user: @answer.user
                  }, formats: [:html]) :
                  nil,
                "form" => render_to_string(partial: "answers/new_edit", locals: {
                  template: template,
                  question: @question,
                  answer: @answer,
                  research_output: @research_output,
                  readonly: false,
                  locking: false,
                  base_template_org: template.base_org
                }, formats: [:html]),
                "answer_status" => render_to_string(partial: "answers/status", locals: {
                  answer: @answer
                }, formats: [:html])
              },
              "section" => {
                "id" => @section.id,
                "progress" => render_to_string(partial: "/org_admin/sections/progress", locals: {
                  section: @section,
                  plan: @plan
                }, formats: [:html])
              },
              "plan" => {
                "id" => @plan.id,
                "progress" => render_to_string(partial: "plans/progress", locals: {
                  plan: @plan,
                  current_phase: @section.phase
                }, formats: [:html])
              },
              "research_output" => {
                "id" => @research_output.id
              }
            }.to_json
            # rubocop:enable LineLength
          end
        end

        private

        # Saves (and creates, if needed) the structured answer ("fragment")
        def save_structured_answer(q)
          # Extract the form data corresponding to the schema of the structured question
          form_data = permitted_params.select { |k, v| schema_params(flat = true).include?(k) }
          s_answer = StructuredAnswer.find_or_initialize_by(answer_id: @answer.id) do |sa|
            sa.answer = @answer
            sa.structured_data_schema = q.structured_data_schema
            end
          s_answer.assign_attributes(data: data_reformater(json_schema, form_data))
          s_answer.save
        end

        # Formats the data extract from the structured answer form to valid JSON data
        # This is useful because Rails converts all form data to strings and JSON needs the actual types
        def data_reformater(schema, data)
          schema["properties"].each do |key, value|
            case value["type"]
            when "integer"
              data[key] = data[key].to_i
            when "boolean"
              data[key] = data[key] == "1"
            when "array"
              data[key] = data[key].kind_of?(Array) ? data[key] : [data[key]]
            when "object"
              if value["dictionnary"]
                data[key] = JSON.parse(DictionnaryValue.where(id: data[key]).select(:id, :uri, :label).take.to_json)
              end
            end
          end
          data
        end

        # Generates a permitted params array from a structured answer schema
        def permitted_params_from_properties(properties, flat = false)
            parameters = Array.new
            properties.each do |key, prop|
                if prop["type"] == "array" && !flat
                    parameters.append({key => []})
                    # parameters.append(key)
                else
                    parameters.append(key)
                end
            end
            parameters
        end

        # Get the schema from the question, if any (works for strucutred questions/answers only)
        # TODO: move to global var with before_action trigger + rename accordingly (set_json_schema ?)
        def json_schema
          question = Question.find(params['question_id'])

          question.structured_data_schema.schema
        end

        # Get the parameters conresponding to the schema
        # TODO: Useless, merge the first use case (flat = false) with permitted_params_form_properties (using global json_schema var)
        # + split the second use case (flat = true) to a more obvious function
        # Point: split between the two use cases : 'get the parameters from the schema' and 'get the actual data corresponding to the schema'
        def schema_params(flat = false)
          permitted_params_from_properties(json_schema['properties'], flat)
        end

        def permitted_params
          permit_arr = [:id, :text, :plan_id, :user_id,
            :question_id, :lock_version,
            :research_output_id, :is_common,
            question_option_ids: []]
          # Append parameters from schema if the question/answer is structured
          permit_arr.append(schema_params) if Question.find(params[:question_id]).question_format.structured
          permitted = params.require(:answer).permit(permit_arr.flatten)
          # If question_option_ids has been filtered out because it was a
          # scalar value (e.g. radiobutton answer)
          if !params[:answer][:question_option_ids].nil? &&
             !permitted[:question_option_ids].present?
            permitted[:question_option_ids] = [params[:answer][:question_option_ids]]
          end
          if !permitted[:id].present?
            permitted.delete(:id)
          end
          # If no question options has been chosen.
          if params[:answer][:question_option_ids].nil?
              permitted[:question_option_ids] = []
          end
          permitted
        end
        
        def set_answers_as_common
          answerIds = params[:answer_ids]
          common_value = params[:is_common]

          Answer.where(id: answerIds).update_all(is_common: common_value)
          
          render json: {
            "updated_answers": answerIds
          }.to_json
      end
    end
  end