Newer
Older
dmpopidor / app / models / madmp_fragment.rb
# == Schema Information
#
# Table name: madmp_fragments
#
#  id                        :integer          not null, primary key
#  data                      :json
#  answer_id                 :integer
#  madmp_schema_id :integer
#  created_at                :datetime         not null
#  updated_at                :datetime         not null
#  classname                 :string
#  dmp_id                    :integer
#  parent_id                 :integer
#
# Indexes
#
#  index_madmp_fragments_on_answer_id                  (answer_id)
#  index_madmp_fragments_on_madmp_schema_id  (madmp_schema_id)
#
require 'jsonpath'

class MadmpFragment < ActiveRecord::Base
  
  include ValidationMessages
  include DynamicFormHelper

  # ================
  # = Associations =
  # ================

  belongs_to :answer
  belongs_to :madmp_schema, class_name: "MadmpSchema"
  belongs_to :dmp, class_name: "Fragment::Dmp", foreign_key: "dmp_id"
  has_many :children, class_name: "MadmpFragment", foreign_key: "parent_id"
  belongs_to :parent, class_name: "MadmpFragment", foreign_key: "parent_id" 

  # ===============
  # = Validations =
  # ===============

  #validates :madmp_schema, presence: { message: PRESENCE_MESSAGE }

  # ================
  # = Single Table Inheritence =
  # ================
  self.inheritance_column = :classname 
  scope :budgets, -> { where(classname: 'budgets') } 
  scope :costs, -> { where(classname: 'cost') } 
  scope :data_collections, -> { where(classname: 'data_collection') } 
  scope :data_processings, -> { where(classname: 'data_processing') } 
  scope :data_storages, -> { where(classname: 'data_storage') } 
  scope :distributions, -> { where(classname: 'distribution') } 
  scope :dmps, -> { where(classname: 'dmp') } 
  scope :documentation_qualities, -> { where(classname: 'documentation_quality') } 
  scope :ethical_issues, -> { where(classname: 'ethical_issue') } 
  scope :funders, -> { where(classname: 'funder') } 
  scope :fundings, -> { where(classname: 'funding') } 
  scope :metas, -> { where(classname: 'meta') } 
  scope :metadata_standards, -> { where(classname: 'metadata_standard') } 
  scope :partners, -> { where(classname: 'partner') } 
  scope :persons, -> { where(classname: 'person') }
  scope :personal_data_issues, -> { where(classname: 'personal_data_issue') }
  scope :preservation_issues, -> { where(classname: 'preservation_issue') }
  scope :projects, -> { where(classname: 'project') } 
  scope :research_outputs, -> { where(classname: 'research_output') } 
  scope :research_output_descriptions, -> { where(classname: 'research_output_description') } 
  scope :reuse_datas, -> { where(classname: 'reuse_data') } 
  scope :sharings, -> { where(classname: 'sharing') } 
  scope :technical_resource_usages, -> { where(classname: 'technical_resource_usage') } 
  scope :technical_resources, -> { where(classname: 'technical_resource') } 


  # =============
  # = Callbacks =
  # =============

  after_create  :update_parent_references
  after_destroy :update_parent_references

  # =====================
  # = Nested Attributes =
  # =====================
  accepts_nested_attributes_for :answer, allow_destroy: true

  # =================
  # = Class methods =
  # =================

  def plan
    if self.dmp.nil?
      Plan.find(data["plan_id"])
    else
      self.dmp.plan
    end
  end

  # Returns the schema associated to the JSON fragment
  def json_schema
    self.madmp_schema.schema
  end

  def get_dmp_fragments
    MadmpFragment.where(dmp_id: dmp_id).group_by(&:madmp_schema_id)
  end

  # Returns a human readable version of the structured answer
  def to_s 
  # displayable = ""
  # if json_schema["to_string"]
  #   json_schema["to_string"].each do |pattern|
  #     # if it's a JsonPath pattern
  #     if pattern.first == "$"
  #       displayable += JsonPath.on(self.data, pattern).first
  #     else 
  #       displayable += pattern
  #     end
  #   end
  # else 
  #   displayable = self.data.to_s
  # end
  # displayable
    self.data.to_s
  end

  # This method generates references to the child fragments in the parent fragment
  # it updates the json "data" field in the database
  # it groups the children fragment by classname and extracts the list of ids
  # to create the json structure needed to update the "data" field
  # this method should be called when creating or deleting a child fragment
  def update_parent_references
    return if classname.nil?
    unless self.parent.nil?
      # Get each fragment grouped by its classname
      classified_children = parent.children.group_by(&:classname)
      parent_data = self.parent.data

      classified_children.each do |classname, children|
        if children.count >= 2
          # if there is more than 1 child, should pluralize the classname
          parent_data = parent_data.merge( { 
            classname.pluralize(2) => children.map { |c| { "dbid" => c.id } }
          } )
          parent_data.delete(classname) if parent_data[classname] && classname != "meta"
        else 
          parent_data = parent_data.merge( { 
            classname => { "dbid" => children.first.id }
          } )
          parent_data.delete(classname.pluralize(2)) if parent_data[classname.pluralize(2)] && classname != "meta"
        end 
      end
      self.parent.update(data: parent_data)
    end
  end

  # This method return the fragment full record
  # It integrates its children into the JSON 
  def get_full_fragment
    children = self.children
    editable_data = self.data
    editable_data.each do |prop, value|
      case value
      when Hash
        if value["dbid"].present?
          child_data = children.exists?(value["dbid"]) ? children.find(value["dbid"]) : MadmpFragment.find(value["dbid"])
          editable_data = editable_data.merge(
            { 
              prop => child_data.get_full_fragment()
            }
          )
        end
      when Array
        unless value.length == 0
          fragment_tab = Array.new
          value.each do |v|
            next if v.nil?

            if v.instance_of?(Hash) && v["dbid"].present?
              child_data = children.exists?(v["dbid"]) ? children.find(v["dbid"]) : MadmpFragment.find(v["dbid"])
              fragment_tab.push(child_data.get_full_fragment())
            else
              fragment_tab.push(v) 
            end
          end
          editable_data = editable_data.merge(
            { 
              prop => fragment_tab
            }
          )
        end
      end
    end
    editable_data
  end

  # Saves (and creates, if needed) the structured answer ("fragment")
  def self.save_madmp_fragment(answer, data, schema, parent_id = nil)
    # Extract the form data corresponding to the schema of the structured question
    s_answer = MadmpFragment.find_or_initialize_by(answer_id: answer.id) do |sa|
      sa.answer = answer
      sa.madmp_schema = schema
      sa.classname = schema.classname
      sa.dmp_id = answer.plan.json_fragment().id
      sa.parent_id = parent_id
    end
    additional_info = { 
      "validations" => self.validate_data(data, schema.schema)
    }
    s_answer.assign_attributes(data: data, additional_info: additional_info)
    s_answer.save
  end


  # Validate the fragment data with the linked schema 
  # and saves the result with the fragment data
  def self.validate_data(data, schema)
    schemer = JSONSchemer.schema(schema)
    unformated = schemer.validate(data).to_a
    validations = {}
    unformated.each do |valid| 
      unless valid['type'] == "object"
        key = valid['data_pointer'][1..-1]
        if valid['type'] == "required"
          required = JsonPath.on(valid, '$..missing_keys').flatten
          required.each do |req| 
            validations[req] ? validations[req].push("required") : validations[req] = ["required"]
          end
        else 
          validations[key] ? validations[key].push(valid['type']) : validations[key] = [valid['type']]
        end 
      end
    end
    validations
  end

  def save_as_multifrag(previous_data)
    # save!
    schema_properties = json_schema['properties']
    data.each do |prop, content|
      schema_prop = schema_properties[prop]
      if !schema_prop.nil? && schema_prop['type'].eql?('object')
        sub_schema = MadmpSchema.find(schema_prop['schema_id'])
        sub_fragment_id = previous_data[prop]['dbid'] if previous_data
        if sub_fragment_id.nil?
          sub_fragment = MadmpFragment.new
        else
          sub_fragment = MadmpFragment.find(sub_fragment_id)
        end
        # sub_fragment = MadmpFragment.new(
        sub_fragment.assign_attributes(
          data: content,
          dmp_id: dmp_id,
          parent_id: id,
          madmp_schema_id: sub_schema.id
        )
        sub_fragment.classname = sub_schema.classname
        sub_fragment.save_as_multifrag(nil) #TODO: pass the real value
        data[prop] = { "dbid": sub_fragment_id }
      end
    end
    save
  end

  def self.find_sti_class(type_name)
    self
  end
end